diff --git a/gulpfile.js b/gulpfile.js index 42caa6a6..712ed3fc 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -5,14 +5,14 @@ const electron = require('electron-connect').server.create({ }); const tape = require('gulp-tape'); const tapColorize = require('tap-colorize'); -const ts = require("gulp-typescript"); -const tsProject = ts.createProject("tsconfig.json"); +const ts = require('gulp-typescript'); +const tsProject = ts.createProject('tsconfig.json'); const glob = require('glob'); -const { execSync } = require('child_process'); +const {execSync} = require('child_process'); const baseFilePattern = 'app/+(main|renderer)/**/*'; -const globOptions = { cwd: __dirname }; +const globOptions = {cwd: __dirname}; const jsFiles = glob.sync(baseFilePattern + '.js', globOptions); const tsFiles = glob.sync(baseFilePattern + '.ts', globOptions); if (jsFiles.length !== tsFiles.length) { @@ -21,18 +21,18 @@ if (jsFiles.length !== tsFiles.length) { execSync(`${npx} tsc`); } -gulp.task("compile", function () { - return tsProject.src() - .pipe(tsProject()) - .js.pipe(gulp.dest("app")); +gulp.task('compile', () => { + return tsProject.src() + .pipe(tsProject()) + .js.pipe(gulp.dest('app')); }); gulp.task('dev', () => { - // Start browser process + // Start browser process electron.start(); - // Restart browser process + // Restart browser process gulp.watch('app/main/*.ts', gulp.series('compile', 'restart:browser')); - // Reload renderer process + // Reload renderer process gulp.watch('app/renderer/css/*.css', gulp.series('reload:renderer')); gulp.watch('app/renderer/*.html', gulp.series('reload:renderer')); gulp.watch('app/renderer/js/**/*.ts', gulp.series('compile', 'reload:renderer')); @@ -45,16 +45,16 @@ gulp.task('restart:browser', done => { }); gulp.task('reload:renderer', done => { - // Reload renderer process + // Reload renderer process electron.reload(); done(); }); gulp.task('test-e2e', () => { return gulp.src('tests/*.js') - .pipe(tape({ - reporter: tapColorize() - })); + .pipe(tape({ + reporter: tapColorize() + })); }); gulp.task('default', gulp.parallel('dev', 'test-e2e')); diff --git a/package.json b/package.json index 8de1f3e2..dff97838 100644 --- a/package.json +++ b/package.json @@ -195,12 +195,14 @@ "electron-notarize": "^0.2.1", "eslint-config-xo-typescript": "^0.26.0", "fs-extra": "^8.1.0", + "glob": "^5.0.15", "gulp": "^4.0.2", "gulp-tape": "^1.0.0", "gulp-typescript": "^6.0.0-alpha.1", "htmlhint": "^0.11.0", "nodemon": "^2.0.2", "pre-commit": "^1.2.2", + "rimraf": "^2.7.1", "spectron": "^10.0.1", "stylelint": "^13.2.0", "tap-colorize": "^1.2.0", @@ -209,22 +211,16 @@ "xo": "^0.27.2" }, "xo": { - "extends": "xo-typescript", "extensions": [ "ts" ], "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module", - "ecmaFeatures": { - "globalReturn": true, - "modules": true - } + "ecmaVersion": 2019 }, - "esnext": true, "overrides": [ { - "files": "app/**/*.ts", + "files": "**/*.ts", + "extends": "xo-typescript", "rules": { "@typescript-eslint/member-ordering": "off", "@typescript-eslint/no-dynamic-delete": "off", @@ -250,17 +246,9 @@ } } ], - "ignore": [ - "tests/*.js", - "tools/locale-helper/*.js", - "*/**/*.js", - "*.js", - "typings.d.ts" - ], "envs": [ "node", - "browser", - "mocha" + "browser" ] } } diff --git a/scripts/notarize.js b/scripts/notarize.js index ddabb4c5..08e5ba1f 100644 --- a/scripts/notarize.js +++ b/scripts/notarize.js @@ -1,22 +1,22 @@ const path = require('path'); const dotenv = require('dotenv'); -dotenv.config({ path: path.join(__dirname, '/../.env') }); +dotenv.config({path: path.join(__dirname, '/../.env')}); -const { notarize } = require('electron-notarize'); +const {notarize} = require('electron-notarize'); -exports.default = async function notarizing(context) { - const { electronPlatformName, appOutDir } = context; - if (electronPlatformName !== 'darwin') { - return; - } +exports.default = async function (context) { + const {electronPlatformName, appOutDir} = context; + if (electronPlatformName !== 'darwin') { + return; + } - const appName = context.packager.appInfo.productFilename; + const appName = context.packager.appInfo.productFilename; - return await notarize({ - appBundleId: 'org.zulip.zulip-electron', - appPath: `${appOutDir}/${appName}.app`, - appleId: process.env.APPLE_ID, - appleIdPassword: process.env.APPLE_ID_PASS, - }); + return notarize({ + appBundleId: 'org.zulip.zulip-electron', + appPath: `${appOutDir}/${appName}.app`, + appleId: process.env.APPLE_ID, + appleIdPassword: process.env.APPLE_ID_PASS + }); }; diff --git a/tests/config.js b/tests/config.js index b371d226..d383dc95 100644 --- a/tests/config.js +++ b/tests/config.js @@ -1,7 +1,5 @@ -const path = require('path') - -const TEST_APP_PRODUCT_NAME = 'ZulipTest' +const TEST_APP_PRODUCT_NAME = 'ZulipTest'; module.exports = { - TEST_APP_PRODUCT_NAME -} + TEST_APP_PRODUCT_NAME +}; diff --git a/tests/index.js b/tests/index.js index a9c8b54c..9ee4c0e3 100644 --- a/tests/index.js +++ b/tests/index.js @@ -1,13 +1,16 @@ -const test = require('tape') -const setup = require('./setup') +const test = require('tape'); +const setup = require('./setup'); -test('app runs', function (t) { - t.timeoutAfter(10e3) - setup.resetTestDataDir() - const app = setup.createApp() - setup.waitForLoad(app, t) - .then(() => app.client.windowByIndex(1)) // focus on webview - .then(() => app.client.waitForExist('//*[@id="connect"]')) // id of the connect button - .then(() => setup.endTest(app, t), - (err) => setup.endTest(app, t, err || 'error')) -}) \ No newline at end of file +test('app runs', async t => { + t.timeoutAfter(10e3); + setup.resetTestDataDir(); + const app = setup.createApp(); + try { + await setup.waitForLoad(app, t); + await app.client.windowByIndex(1); // Focus on webview + await app.client.waitForExist('//*[@id="connect"]'); // Id of the connect button + await setup.endTest(app, t); + } catch (error) { + await setup.endTest(app, t, error || 'error'); + } +}); diff --git a/tests/setup.js b/tests/setup.js index b63a04bd..3c04b2a8 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -1,97 +1,95 @@ -const Application = require('spectron').Application -const fs = require('fs') -const mkdirp = require('mkdirp') -const path = require('path') -const rimraf = require('rimraf') +const {Application} = require('spectron'); +const fs = require('fs'); +const path = require('path'); +const rimraf = require('rimraf'); -const config = require('./config') +const config = require('./config'); module.exports = { - createApp, - endTest, - waitForLoad, - wait, - resetTestDataDir -} + createApp, + endTest, + waitForLoad, + wait, + resetTestDataDir +}; // Runs Zulip Desktop. // Returns a promise that resolves to a Spectron Application once the app has loaded. // Takes a Tape test. Makes some basic assertions to verify that the app loaded correctly. -function createApp (t) { - generateTestAppPackageJson() - return new Application({ - path: path.join(__dirname, '..', 'node_modules', '.bin', - 'electron' + (process.platform === 'win32' ? '.cmd' : '')), - args: [path.join(__dirname)], // Ensure this dir has a package.json file with a 'main' entry piont - env: {NODE_ENV: 'test'}, - waitTimeout: 10e3 - }) +function createApp() { + generateTestAppPackageJson(); + return new Application({ + path: path.join(__dirname, '..', 'node_modules', '.bin', + 'electron' + (process.platform === 'win32' ? '.cmd' : '')), + args: [path.join(__dirname)], // Ensure this dir has a package.json file with a 'main' entry piont + env: {NODE_ENV: 'test'}, + waitTimeout: 10e3 + }); } // Generates package.json for test app -// Reads app package.json and updates the productName to config.TEST_APP_PRODUCT_NAME +// Reads app package.json and updates the productName to config.TEST_APP_PRODUCT_NAME // We do this so that the app integration doesn't doesn't share the same appDataDir as the dev application -function generateTestAppPackageJson () { - let packageJson = require(path.join(__dirname, '../package.json')) - packageJson.productName = config.TEST_APP_PRODUCT_NAME - packageJson.main = '../app/main' +function generateTestAppPackageJson() { + const packageJson = require(path.join(__dirname, '../package.json')); + packageJson.productName = config.TEST_APP_PRODUCT_NAME; + packageJson.main = '../app/main'; - const testPackageJsonPath = path.join(__dirname, 'package.json') - fs.writeFileSync(testPackageJsonPath, JSON.stringify(packageJson, null, ' '), 'utf-8') + const testPackageJsonPath = path.join(__dirname, 'package.json'); + fs.writeFileSync(testPackageJsonPath, JSON.stringify(packageJson, null, ' '), 'utf-8'); } // Starts the app, waits for it to load, returns a promise -function waitForLoad (app, t, opts) { - if (!opts) opts = {} - return app.start().then(function () { - return app.client.waitUntilWindowLoaded() - }) - .then(function() { - return app.client.pause(2000); - }) - .then(function () { - return app.webContents.getTitle() - }).then(function (title) { - t.equal(title, 'Zulip', 'html title') - }) +async function waitForLoad(app, t, opts) { + if (!opts) { + opts = {}; + } + + await app.start(); + await app.client.waitUntilWindowLoaded(); + await app.client.pause(2000); + const title = await app.webContents.getTitle(); + t.equal(title, 'Zulip', 'html title'); } // Returns a promise that resolves after 'ms' milliseconds. Default: 1 second -function wait (ms) { - if (ms === undefined) ms = 1000 // Default: wait long enough for the UI to update - return new Promise(function (resolve, reject) { - setTimeout(resolve, ms) - }) +async function wait(ms) { + if (ms === undefined) { + ms = 1000; + } // Default: wait long enough for the UI to update + + return new Promise((resolve => { + setTimeout(resolve, ms); + })); } // Quit the app, end the test, either in success (!err) or failure (err) -function endTest (app, t, err) { - return app.stop().then(function () { - t.end(err) - }) +async function endTest(app, t, err) { + await app.stop(); + t.end(err); } -function getAppDataDir () { - let base +function getAppDataDir() { + let base; - if (process.platform === 'darwin') { - base = path.join(process.env.HOME, 'Library', 'Application Support') - } else if (process.platform === 'linux') { - base = process.env.XDG_CONFIG_HOME ? - process.env.XDG_CONFIG_HOME : path.join(process.env.HOME, '.config') - } else if (process.platform === 'win32') { - base = process.env.APPDATA - } else { - console.log('Could not detect app data dir base. Exiting...') - process.exit(1) - } - console.log('Detected App Data Dir base:', base) - return path.join(base, config.TEST_APP_PRODUCT_NAME) + if (process.platform === 'darwin') { + base = path.join(process.env.HOME, 'Library', 'Application Support'); + } else if (process.platform === 'linux') { + base = process.env.XDG_CONFIG_HOME ? + process.env.XDG_CONFIG_HOME : path.join(process.env.HOME, '.config'); + } else if (process.platform === 'win32') { + base = process.env.APPDATA; + } else { + throw new Error('Could not detect app data dir base.'); + } + + console.log('Detected App Data Dir base:', base); + return path.join(base, config.TEST_APP_PRODUCT_NAME); } // Resets the test directory, containing domain.json, window-state.json, etc -function resetTestDataDir () { - appDataDir = getAppDataDir() - rimraf.sync(appDataDir) - rimraf.sync(path.join(__dirname, 'package.json')) +function resetTestDataDir() { + const appDataDir = getAppDataDir(); + rimraf.sync(appDataDir); + rimraf.sync(path.join(__dirname, 'package.json')); } diff --git a/tests/test-add-organization.js b/tests/test-add-organization.js index 322758dd..cf762540 100644 --- a/tests/test-add-organization.js +++ b/tests/test-add-organization.js @@ -1,19 +1,21 @@ -const test = require('tape') -const setup = require('./setup') - -test('add-organization', function (t) { - t.timeoutAfter(50e3) - setup.resetTestDataDir() - const app = setup.createApp() - setup.waitForLoad(app, t) - .then(() => app.client.windowByIndex(1)) // focus on webview - .then(() => app.client.setValue('.setting-input-value', 'chat.zulip.org')) - .then(() => app.client.click('#connect')) - .then(() => setup.wait(5000)) - .then(() => app.client.windowByIndex(0)) // Switch focus back to main win - .then(() => app.client.windowByIndex(1)) // Switch focus back to org webview - .then(() => app.client.waitForExist('//*[@id="id_username"]')) - .then(() => setup.endTest(app, t), - (err) => setup.endTest(app, t, err || 'error')) -}) +const test = require('tape'); +const setup = require('./setup'); +test('add-organization', async t => { + t.timeoutAfter(50e3); + setup.resetTestDataDir(); + const app = setup.createApp(); + try { + await setup.waitForLoad(app, t); + await app.client.windowByIndex(1); // Focus on webview + await app.client.setValue('.setting-input-value', 'chat.zulip.org'); + await app.client.click('#connect'); + await setup.wait(5000); + await app.client.windowByIndex(0); // Switch focus back to main win + await app.client.windowByIndex(1); // Switch focus back to org webview + await app.client.waitForExist('//*[@id="id_username"]'); + await setup.endTest(app, t); + } catch (error) { + await setup.endTest(app, t, error || 'error'); + } +}); diff --git a/tests/test-new-organization.js b/tests/test-new-organization.js index a59b5048..9cd8fa26 100644 --- a/tests/test-new-organization.js +++ b/tests/test-new-organization.js @@ -1,17 +1,19 @@ -const test = require('tape') -const setup = require('./setup') +const test = require('tape'); +const setup = require('./setup'); // Create new org link should open in the default browser [WIP] -test('new-org-link', function (t) { - t.timeoutAfter(50e3) - setup.resetTestDataDir() - const app = setup.createApp() - setup.waitForLoad(app, t) - .then(() => app.client.windowByIndex(1)) // focus on webview - .then(() => app.client.click('#open-create-org-link')) // Click on new org link button - .then(() => setup.wait(5000)) - .then(() => setup.endTest(app, t), - (err) => setup.endTest(app, t, err || 'error')) -}) - +test('new-org-link', async t => { + t.timeoutAfter(50e3); + setup.resetTestDataDir(); + const app = setup.createApp(); + try { + await setup.waitForLoad(app, t); + await app.client.windowByIndex(1); // Focus on webview + await app.client.click('#open-create-org-link'); // Click on new org link button + await setup.wait(5000); + await setup.endTest(app, t); + } catch (error) { + await setup.endTest(app, t, error || 'error'); + } +}); diff --git a/tools/locale-helper/index.js b/tools/locale-helper/index.js index 2b4e310c..720087fb 100644 --- a/tools/locale-helper/index.js +++ b/tools/locale-helper/index.js @@ -5,28 +5,30 @@ const fs = require('fs'); const translationDir = path.resolve(__dirname, '../../app/translations'); function writeJSON(file, data) { - const filePath = path.resolve(translationDir, file); - fs.writeFileSync(filePath, `${JSON.stringify(data, null, '\t')}\n`, 'utf8'); + const filePath = path.resolve(translationDir, file); + fs.writeFileSync(filePath, `${JSON.stringify(data, null, '\t')}\n`, 'utf8'); } -const { phrases } = require('./locale-template'); +const {phrases} = require('./locale-template'); const supportedLocales = require('./supported-locales.json'); phrases.sort(); -for (let locale in supportedLocales) { - console.log(`fetching translation for: ${supportedLocales[locale]} - ${locale}..`); - translate(phrases.join('\n'), { to: locale }) - .then(res => { - const localeFile = `${locale}.json`; - const translatedText = res.text.split('\n'); - const translationJSON = {}; - phrases.forEach((phrase, index) => { - translationJSON[phrase] = translatedText[index]; - }); +for (const [locale, name] of Object.entries(supportedLocales)) { + console.log(`fetching translation for: ${name} - ${locale}..`); + (async () => { + try { + const res = await translate(phrases.join('\n'), {to: locale}); + const localeFile = `${locale}.json`; + const translatedText = res.text.split('\n'); + const translationJSON = {}; + phrases.forEach((phrase, index) => { + translationJSON[phrase] = translatedText[index]; + }); - writeJSON(localeFile, translationJSON); - console.log(`create: ${localeFile}`); - }).catch(err => { - console.error(err); - }); + writeJSON(localeFile, translationJSON); + console.log(`create: ${localeFile}`); + } catch (error) { + console.error(error); + } + })(); } diff --git a/typings.d.ts b/typings.d.ts index 89bff1c4..6334a099 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -3,20 +3,20 @@ declare module 'node-mac-notifier'; declare module 'wurl'; interface PageParamsObject { - realm_uri: string; - default_language: string; - external_authentication_methods: any; + realm_uri: string; + default_language: string; + external_authentication_methods: any; } -declare var page_params: PageParamsObject; +declare let page_params: PageParamsObject; // This is mostly zulip side of code we access from window interface Window { - $: any; - narrow: any + $: any; + narrow: any; } interface ZulipWebWindow extends Window { - electron_bridge: any; - tray: any; - lightbox: any; + electron_bridge: any; + tray: any; + lightbox: any; }