From c6db47182a93608c5ad8454647f9a27d877953fd Mon Sep 17 00:00:00 2001 From: Milan Burda Date: Wed, 1 Jul 2020 00:10:36 +0200 Subject: [PATCH] test: make sure tests fail properly instead of timing out (#24316) --- spec-main/api-app-spec.ts | 42 +- spec-main/api-auto-updater-spec.ts | 31 +- spec-main/api-browser-view-spec.ts | 15 +- spec-main/api-browser-window-spec.ts | 769 ++++++++++++++------------- spec-main/api-debugger-spec.ts | 94 +--- spec-main/api-menu-item-spec.ts | 10 +- spec-main/api-protocol-spec.ts | 16 +- spec-main/api-remote-spec.ts | 10 +- spec-main/api-session-spec.ts | 92 +++- spec-main/api-web-contents-spec.ts | 359 ++++++------- spec-main/asar-spec.ts | 35 +- spec-main/chromium-spec.ts | 197 ++++--- spec-main/modules-spec.ts | 9 +- spec-main/node-spec.ts | 65 +-- spec/api-web-frame-spec.js | 68 +-- spec/asar-spec.js | 589 +++++++++++++------- spec/chromium-spec.js | 260 ++++----- spec/node-spec.js | 116 ++-- spec/spec-helpers.js | 2 + spec/webview-spec.js | 72 +-- 20 files changed, 1484 insertions(+), 1367 deletions(-) diff --git a/spec-main/api-app-spec.ts b/spec-main/api-app-spec.ts index 4a136bf99ff5..05e3822c32d9 100644 --- a/spec-main/api-app-spec.ts +++ b/spec-main/api-app-spec.ts @@ -209,21 +209,17 @@ describe('app module', () => { }); describe('app.requestSingleInstanceLock', () => { - it('prevents the second launch of app', function (done) { + it('prevents the second launch of app', async function () { this.timeout(120000); const appPath = path.join(fixturesPath, 'api', 'singleton'); const first = cp.spawn(process.execPath, [appPath]); - first.once('exit', code => { - expect(code).to.equal(0); - }); + await emittedOnce(first.stdout, 'data'); // Start second app when received output. - first.stdout.once('data', () => { - const second = cp.spawn(process.execPath, [appPath]); - second.once('exit', code => { - expect(code).to.equal(1); - done(); - }); - }); + const second = cp.spawn(process.execPath, [appPath]); + const [code2] = await emittedOnce(second, 'exit'); + expect(code2).to.equal(1); + const [code1] = await emittedOnce(first, 'exit'); + expect(code1).to.equal(0); }); it('passes arguments to the second-instance event', async () => { @@ -1003,34 +999,28 @@ describe('app module', () => { } }); - it('does not launch for argument following a URL', done => { + it('does not launch for argument following a URL', async () => { const appPath = path.join(fixturesPath, 'api', 'quit-app'); // App should exit with non 123 code. const first = cp.spawn(process.execPath, [appPath, 'electron-test:?', 'abc']); - first.once('exit', code => { - expect(code).to.not.equal(123); - done(); - }); + const [code] = await emittedOnce(first, 'exit'); + expect(code).to.not.equal(123); }); - it('launches successfully for argument following a file path', done => { + it('launches successfully for argument following a file path', async () => { const appPath = path.join(fixturesPath, 'api', 'quit-app'); // App should exit with code 123. const first = cp.spawn(process.execPath, [appPath, 'e:\\abc', 'abc']); - first.once('exit', code => { - expect(code).to.equal(123); - done(); - }); + const [code] = await emittedOnce(first, 'exit'); + expect(code).to.equal(123); }); - it('launches successfully for multiple URIs following --', done => { + it('launches successfully for multiple URIs following --', async () => { const appPath = path.join(fixturesPath, 'api', 'quit-app'); // App should exit with code 123. const first = cp.spawn(process.execPath, [appPath, '--', 'http://electronjs.org', 'electron-test://testdata']); - first.once('exit', code => { - expect(code).to.equal(123); - done(); - }); + const [code] = await emittedOnce(first, 'exit'); + expect(code).to.equal(123); }); }); diff --git a/spec-main/api-auto-updater-spec.ts b/spec-main/api-auto-updater-spec.ts index 4d1e19fcbbbe..1839303b6fcf 100644 --- a/spec-main/api-auto-updater-spec.ts +++ b/spec-main/api-auto-updater-spec.ts @@ -1,16 +1,16 @@ import { autoUpdater } from 'electron/main'; import { expect } from 'chai'; import { ifit, ifdescribe } from './spec-helpers'; +import { emittedOnce } from './events-helpers'; ifdescribe(!process.mas)('autoUpdater module', function () { describe('checkForUpdates', function () { - ifit(process.platform === 'win32')('emits an error on Windows if the feed URL is not set', function (done) { - autoUpdater.once('error', function (error) { - expect(error.message).to.equal('Update URL is not set'); - done(); - }); + ifit(process.platform === 'win32')('emits an error on Windows if the feed URL is not set', async function () { + const errorEvent = emittedOnce(autoUpdater, 'error'); autoUpdater.setFeedURL({ url: '' }); autoUpdater.checkForUpdates(); + const [error] = await errorEvent; + expect(error.message).to.equal('Update URL is not set'); }); }); @@ -19,11 +19,10 @@ ifdescribe(!process.mas)('autoUpdater module', function () { expect(autoUpdater.getFeedURL()).to.equal(''); }); - ifit(process.platform === 'win32')('correctly fetches the previously set FeedURL', function (done) { + ifit(process.platform === 'win32')('correctly fetches the previously set FeedURL', function () { const updateURL = 'https://fake-update.electron.io'; autoUpdater.setFeedURL({ url: updateURL }); expect(autoUpdater.getFeedURL()).to.equal(updateURL); - done(); }); }); @@ -56,12 +55,11 @@ ifdescribe(!process.mas)('autoUpdater module', function () { }); ifdescribe(process.platform === 'darwin')('on Mac', function () { - it('emits an error when the application is unsigned', done => { - autoUpdater.once('error', function (error) { - expect(error.message).equal('Could not get code signature for running application'); - done(); - }); + it('emits an error when the application is unsigned', async () => { + const errorEvent = emittedOnce(autoUpdater, 'error'); autoUpdater.setFeedURL({ url: '' }); + const [error] = await errorEvent; + expect(error.message).equal('Could not get code signature for running application'); }); it('does not throw if default is the serverType', () => { @@ -81,12 +79,11 @@ ifdescribe(!process.mas)('autoUpdater module', function () { }); describe('quitAndInstall', () => { - ifit(process.platform === 'win32')('emits an error on Windows when no update is available', function (done) { - autoUpdater.once('error', function (error) { - expect(error.message).to.equal('No update available, can\'t quit and install'); - done(); - }); + ifit(process.platform === 'win32')('emits an error on Windows when no update is available', async function () { + const errorEvent = emittedOnce(autoUpdater, 'error'); autoUpdater.quitAndInstall(); + const [error] = await errorEvent; + expect(error.message).to.equal('No update available, can\'t quit and install'); }); }); }); diff --git a/spec-main/api-browser-view-spec.ts b/spec-main/api-browser-view-spec.ts index 19e0ab3212f1..2c17badce875 100644 --- a/spec-main/api-browser-view-spec.ts +++ b/spec-main/api-browser-view-spec.ts @@ -233,17 +233,16 @@ describe('BrowserView module', () => { }); describe('window.open()', () => { - it('works in BrowserView', (done) => { + it('works in BrowserView', async () => { view = new BrowserView(); w.setBrowserView(view); - view.webContents.once('new-window', (e, url, frameName, disposition, options, additionalFeatures) => { - e.preventDefault(); - expect(url).to.equal('http://host/'); - expect(frameName).to.equal('host'); - expect(additionalFeatures[0]).to.equal('this-is-not-a-standard-feature'); - done(); - }); + const newWindow = emittedOnce(view.webContents, 'new-window'); + view.webContents.once('new-window', event => event.preventDefault()); view.webContents.loadFile(path.join(fixtures, 'pages', 'window-open.html')); + const [, url, frameName,,, additionalFeatures] = await newWindow; + expect(url).to.equal('http://host/'); + expect(frameName).to.equal('host'); + expect(additionalFeatures[0]).to.equal('this-is-not-a-standard-feature'); }); }); }); diff --git a/spec-main/api-browser-window-spec.ts b/spec-main/api-browser-window-spec.ts index 78aa8ded1ba3..57756c70cd2a 100644 --- a/spec-main/api-browser-window-spec.ts +++ b/spec-main/api-browser-window-spec.ts @@ -68,21 +68,18 @@ describe('BrowserWindow module', () => { const v8Util = process._linkedBinding('electron_common_v8_util'); afterEach(closeAllWindows); - it('window does not get garbage collected when opened', (done) => { + it('window does not get garbage collected when opened', async () => { const w = new BrowserWindow({ show: false }); // Keep a weak reference to the window. // eslint-disable-next-line no-undef const wr = new (globalThis as any).WeakRef(w); - setTimeout(() => { - // Do garbage collection, since |w| is not referenced in this closure - // it would be gone after next call if there is no other reference. - v8Util.requestGarbageCollectionForTesting(); + await delay(); + // Do garbage collection, since |w| is not referenced in this closure + // it would be gone after next call if there is no other reference. + v8Util.requestGarbageCollectionForTesting(); - setTimeout(() => { - expect(wr.deref()).to.not.be.undefined(); - done(); - }); - }); + await delay(); + expect(wr.deref()).to.not.be.undefined(); }); }); @@ -324,30 +321,27 @@ describe('BrowserWindow module', () => { }); // TODO(deepak1556): The error code now seems to be `ERR_FAILED`, verify what // changed and adjust the test. - it.skip('should emit did-fail-load event for files that do not exist', (done) => { - w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { - expect(code).to.equal(-6); - expect(desc).to.equal('ERR_FILE_NOT_FOUND'); - expect(isMainFrame).to.equal(true); - done(); - }); + it.skip('should emit did-fail-load event for files that do not exist', async () => { + const didFailLoad = emittedOnce(w.webContents, 'did-fail-load'); w.loadURL('file://a.txt'); + const [, code, desc,, isMainFrame] = await didFailLoad; + expect(code).to.equal(-6); + expect(desc).to.equal('ERR_FILE_NOT_FOUND'); + expect(isMainFrame).to.equal(true); }); - it('should emit did-fail-load event for invalid URL', (done) => { - w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { - expect(desc).to.equal('ERR_INVALID_URL'); - expect(code).to.equal(-300); - expect(isMainFrame).to.equal(true); - done(); - }); + it('should emit did-fail-load event for invalid URL', async () => { + const didFailLoad = emittedOnce(w.webContents, 'did-fail-load'); w.loadURL('http://example:port'); + const [, code, desc,, isMainFrame] = await didFailLoad; + expect(desc).to.equal('ERR_INVALID_URL'); + expect(code).to.equal(-300); + expect(isMainFrame).to.equal(true); }); - it('should set `mainFrame = false` on did-fail-load events in iframes', (done) => { - w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { - expect(isMainFrame).to.equal(false); - done(); - }); + it('should set `mainFrame = false` on did-fail-load events in iframes', async () => { + const didFailLoad = emittedOnce(w.webContents, 'did-fail-load'); w.loadFile(path.join(fixtures, 'api', 'did-fail-load-iframe.html')); + const [,,,, isMainFrame] = await didFailLoad; + expect(isMainFrame).to.equal(false); }); it('does not crash in did-fail-provisional-load handler', (done) => { w.webContents.once('did-fail-provisional-load', () => { @@ -356,15 +350,14 @@ describe('BrowserWindow module', () => { }); w.loadURL('http://127.0.0.1:11111'); }); - it('should emit did-fail-load event for URL exceeding character limit', (done) => { - w.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => { - expect(desc).to.equal('ERR_INVALID_URL'); - expect(code).to.equal(-300); - expect(isMainFrame).to.equal(true); - done(); - }); + it('should emit did-fail-load event for URL exceeding character limit', async () => { const data = Buffer.alloc(2 * 1024 * 1024).toString('base64'); + const didFailLoad = emittedOnce(w.webContents, 'did-fail-load'); w.loadURL(`data:image/png;base64,${data}`); + const [, code, desc,, isMainFrame] = await didFailLoad; + expect(desc).to.equal('ERR_INVALID_URL'); + expect(code).to.equal(-300); + expect(isMainFrame).to.equal(true); }); it('should return a promise', () => { @@ -433,12 +426,11 @@ describe('BrowserWindow module', () => { }); }); - it('should support support base url for data urls', (done) => { - ipcMain.once('answer', (event, test) => { - expect(test).to.equal('test'); - done(); - }); + it('should support support base url for data urls', async () => { + const answer = emittedOnce(ipcMain, 'answer'); w.loadURL('data:text/html,', { baseURLForDataURL: `other://${path.join(fixtures, 'api')}${path.sep}` }); + const [, test] = await answer; + expect(test).to.equal('test'); }); }); @@ -485,8 +477,12 @@ describe('BrowserWindow module', () => { w.webContents.on('did-stop-loading', () => { if (willNavigate) { // i.e. it shouldn't have had '?navigated' appended to it. - expect(w.webContents.getURL().endsWith('will-navigate.html')).to.be.true(); - done(); + try { + expect(w.webContents.getURL().endsWith('will-navigate.html')).to.be.true(); + done(); + } catch (e) { + done(e); + } } }); w.loadFile(path.join(fixtures, 'pages', 'will-navigate.html')); @@ -550,28 +546,26 @@ describe('BrowserWindow module', () => { w.loadURL(`${url}/302`); }); - it('is emitted after will-navigate on redirects', (done) => { + it('is emitted after will-navigate on redirects', async () => { let navigateCalled = false; w.webContents.on('will-navigate', () => { navigateCalled = true; }); - w.webContents.on('will-redirect', () => { - expect(navigateCalled).to.equal(true, 'should have called will-navigate first'); - done(); - }); + const willRedirect = emittedOnce(w.webContents, 'will-redirect'); w.loadURL(`${url}/navigate-302`); + await willRedirect; + expect(navigateCalled).to.equal(true, 'should have called will-navigate first'); }); - it('is emitted before did-stop-loading on redirects', (done) => { + it('is emitted before did-stop-loading on redirects', async () => { let stopCalled = false; w.webContents.on('did-stop-loading', () => { stopCalled = true; }); - w.webContents.on('will-redirect', () => { - expect(stopCalled).to.equal(false, 'should not have called did-stop-loading first'); - done(); - }); + const willRedirect = emittedOnce(w.webContents, 'will-redirect'); w.loadURL(`${url}/302`); + await willRedirect; + expect(stopCalled).to.equal(false, 'should not have called did-stop-loading first'); }); it('allows the window to be closed from the event listener', (done) => { @@ -590,14 +584,22 @@ describe('BrowserWindow module', () => { expect(u).to.equal(`${url}/302`); }); w.webContents.on('did-stop-loading', () => { - expect(w.webContents.getURL()).to.equal( - `${url}/navigate-302`, - 'url should not have changed after navigation event' - ); - done(); + try { + expect(w.webContents.getURL()).to.equal( + `${url}/navigate-302`, + 'url should not have changed after navigation event' + ); + done(); + } catch (e) { + done(e); + } }); w.webContents.on('will-redirect', (e, u) => { - expect(u).to.equal(`${url}/200`); + try { + expect(u).to.equal(`${url}/200`); + } catch (e) { + done(e); + } }); w.loadURL(`${url}/navigate-302`); }); @@ -624,12 +626,11 @@ describe('BrowserWindow module', () => { w.show(); expect(w.isVisible()).to.equal(true); }); - it('emits when window is shown', (done) => { - w.once('show', () => { - expect(w.isVisible()).to.equal(true); - done(); - }); + it('emits when window is shown', async () => { + const show = emittedOnce(w, 'show'); w.show(); + await show; + expect(w.isVisible()).to.equal(true); }); }); @@ -845,27 +846,24 @@ describe('BrowserWindow module', () => { }); describe('BrowserWindow.setAspectRatio(ratio)', () => { - it('resets the behaviour when passing in 0', (done) => { + it('resets the behaviour when passing in 0', async () => { const size = [300, 400]; w.setAspectRatio(1 / 2); w.setAspectRatio(0); - w.once('resize', () => { - expectBoundsEqual(w.getSize(), size); - done(); - }); + const resize = emittedOnce(w, 'resize'); w.setSize(size[0], size[1]); + await resize; + expectBoundsEqual(w.getSize(), size); }); }); describe('BrowserWindow.setPosition(x, y)', () => { - it('sets the window position', (done) => { + it('sets the window position', async () => { const pos = [10, 10]; - w.once('move', () => { - const newPos = w.getPosition(); - expect(newPos).to.deep.equal(pos); - done(); - }); + const move = emittedOnce(w, 'move'); w.setPosition(pos[0], pos[1]); + await move; + expect(w.getPosition()).to.deep.equal(pos); }); }); @@ -898,17 +896,15 @@ describe('BrowserWindow module', () => { }); describe('BrowserWindow.setContentBounds(bounds)', () => { - it('sets the content size and position', (done) => { + it('sets the content size and position', async () => { const bounds = { x: 10, y: 10, width: 250, height: 250 }; - w.once('resize', () => { - setTimeout(() => { - expectBoundsEqual(w.getContentBounds(), bounds); - done(); - }); - }); + const resize = emittedOnce(w, 'resize'); w.setContentBounds(bounds); + await resize; + await delay(); + expectBoundsEqual(w.getContentBounds(), bounds); }); - it('works for a frameless window', (done) => { + it('works for a frameless window', async () => { w.destroy(); w = new BrowserWindow({ show: false, @@ -917,13 +913,11 @@ describe('BrowserWindow module', () => { height: 300 }); const bounds = { x: 10, y: 10, width: 250, height: 250 }; - w.once('resize', () => { - setTimeout(() => { - expect(w.getContentBounds()).to.deep.equal(bounds); - done(); - }); - }); + const resize = emittedOnce(w, 'resize'); w.setContentBounds(bounds); + await resize; + await delay(); + expectBoundsEqual(w.getContentBounds(), bounds); }); }); @@ -952,69 +946,63 @@ describe('BrowserWindow module', () => { describe('BrowserWindow.getNormalBounds()', () => { describe('Normal state', () => { - it('checks normal bounds after resize', (done) => { + it('checks normal bounds after resize', async () => { const size = [300, 400]; - w.once('resize', () => { - expectBoundsEqual(w.getNormalBounds(), w.getBounds()); - done(); - }); + const resize = emittedOnce(w, 'resize'); w.setSize(size[0], size[1]); + await resize; + expectBoundsEqual(w.getNormalBounds(), w.getBounds()); }); - it('checks normal bounds after move', (done) => { + it('checks normal bounds after move', async () => { const pos = [10, 10]; - w.once('move', () => { - expectBoundsEqual(w.getNormalBounds(), w.getBounds()); - done(); - }); + const move = emittedOnce(w, 'move'); w.setPosition(pos[0], pos[1]); + await move; + expectBoundsEqual(w.getNormalBounds(), w.getBounds()); }); }); ifdescribe(process.platform !== 'linux')('Maximized state', () => { - it('checks normal bounds when maximized', (done) => { + it('checks normal bounds when maximized', async () => { const bounds = w.getBounds(); - w.once('maximize', () => { - expectBoundsEqual(w.getNormalBounds(), bounds); - done(); - }); + const maximize = emittedOnce(w, 'maximize'); w.show(); w.maximize(); + await maximize; + expectBoundsEqual(w.getNormalBounds(), bounds); }); - it('checks normal bounds when unmaximized', (done) => { + it('checks normal bounds when unmaximized', async () => { const bounds = w.getBounds(); w.once('maximize', () => { w.unmaximize(); }); - w.once('unmaximize', () => { - expectBoundsEqual(w.getNormalBounds(), bounds); - done(); - }); + const unmaximize = emittedOnce(w, 'unmaximize'); w.show(); w.maximize(); + await unmaximize; + expectBoundsEqual(w.getNormalBounds(), bounds); }); }); ifdescribe(process.platform !== 'linux')('Minimized state', () => { - it('checks normal bounds when minimized', (done) => { + it('checks normal bounds when minimized', async () => { const bounds = w.getBounds(); - w.once('minimize', () => { - expectBoundsEqual(w.getNormalBounds(), bounds); - done(); - }); + const minimize = emittedOnce(w, 'minimize'); w.show(); w.minimize(); + await minimize; + expectBoundsEqual(w.getNormalBounds(), bounds); }); - it('checks normal bounds when restored', (done) => { + it('checks normal bounds when restored', async () => { const bounds = w.getBounds(); w.once('minimize', () => { w.restore(); }); - w.once('restore', () => { - expectBoundsEqual(w.getNormalBounds(), bounds); - done(); - }); + const restore = emittedOnce(w, 'restore'); w.show(); w.minimize(); + await restore; + expectBoundsEqual(w.getNormalBounds(), bounds); }); }); @@ -1032,27 +1020,25 @@ describe('BrowserWindow module', () => { expect(w.fullScreen).to.be.true(); }); - it(`checks normal bounds when fullscreen'ed`, (done) => { + it(`checks normal bounds when fullscreen'ed`, async () => { const bounds = w.getBounds(); - w.once('enter-full-screen', () => { - expectBoundsEqual(w.getNormalBounds(), bounds); - done(); - }); + const enterFullScreen = emittedOnce(w, 'enter-full-screen'); w.show(); w.fullScreen = true; + await enterFullScreen; + expectBoundsEqual(w.getNormalBounds(), bounds); }); - it(`checks normal bounds when unfullscreen'ed`, (done) => { + it(`checks normal bounds when unfullscreen'ed`, async () => { const bounds = w.getBounds(); w.once('enter-full-screen', () => { w.fullScreen = false; }); - w.once('leave-full-screen', () => { - expectBoundsEqual(w.getNormalBounds(), bounds); - done(); - }); + const leaveFullScreen = emittedOnce(w, 'leave-full-screen'); w.show(); w.fullScreen = true; + await leaveFullScreen; + expectBoundsEqual(w.getNormalBounds(), bounds); }); }); @@ -1069,27 +1055,30 @@ describe('BrowserWindow module', () => { expect(w.isFullScreen()).to.be.true(); }); - it(`checks normal bounds when fullscreen'ed`, (done) => { + it(`checks normal bounds when fullscreen'ed`, async () => { const bounds = w.getBounds(); - w.once('enter-full-screen', () => { - expectBoundsEqual(w.getNormalBounds(), bounds); - done(); - }); w.show(); + + const enterFullScreen = emittedOnce(w, 'enter-full-screen'); w.setFullScreen(true); + await enterFullScreen; + + expectBoundsEqual(w.getNormalBounds(), bounds); }); - it(`checks normal bounds when unfullscreen'ed`, (done) => { + it(`checks normal bounds when unfullscreen'ed`, async () => { const bounds = w.getBounds(); - w.once('enter-full-screen', () => { - w.setFullScreen(false); - }); - w.once('leave-full-screen', () => { - expectBoundsEqual(w.getNormalBounds(), bounds); - done(); - }); w.show(); + + const enterFullScreen = emittedOnce(w, 'enter-full-screen'); w.setFullScreen(true); + await enterFullScreen; + + const leaveFullScreen = emittedOnce(w, 'leave-full-screen'); + w.setFullScreen(false); + await leaveFullScreen; + + expectBoundsEqual(w.getNormalBounds(), bounds); }); }); }); @@ -1304,14 +1293,12 @@ describe('BrowserWindow module', () => { expect(w.isAlwaysOnTop()).to.be.true('is not alwaysOnTop'); }); - it('causes the right value to be emitted on `always-on-top-changed`', (done) => { - w.on('always-on-top-changed', (e, alwaysOnTop) => { - expect(alwaysOnTop).to.be.true('is not alwaysOnTop'); - done(); - }); - + it('causes the right value to be emitted on `always-on-top-changed`', async () => { + const alwaysOnTopChanged = emittedOnce(w, 'always-on-top-changed'); expect(w.isAlwaysOnTop()).to.be.false('is alwaysOnTop'); w.setAlwaysOnTop(true); + const [, alwaysOnTop] = await alwaysOnTopChanged; + expect(alwaysOnTop).to.be.true('is not alwaysOnTop'); }); }); @@ -1344,16 +1331,13 @@ describe('BrowserWindow module', () => { server = null as unknown as http.Server; }); - it('calling preconnect() connects to the server', (done) => { + it('calling preconnect() connects to the server', async () => { w = new BrowserWindow({ show: false }); w.webContents.on('did-start-navigation', (event, url) => { w.webContents.session.preconnect({ url, numSockets: 4 }); }); - w.webContents.on('did-finish-load', () => { - expect(connections).to.equal(4); - done(); - }); - w.loadURL(url); + await w.loadURL(url); + expect(connections).to.equal(4); }); it('does not preconnect unless requested', async () => { @@ -2296,8 +2280,12 @@ describe('BrowserWindow module', () => { // We need to give it some time so the windows get properly disposed (at least on OSX). setTimeout(() => { const currentWebContents = webContents.getAllWebContents().map((i) => i.id); - expect(currentWebContents).to.deep.equal(initialWebContents); - done(); + try { + expect(currentWebContents).to.deep.equal(initialWebContents); + done(); + } catch (error) { + done(e); + } }, 100); }); w.loadFile(path.join(fixtures, 'pages', 'window-open.html')); @@ -2332,8 +2320,12 @@ describe('BrowserWindow module', () => { ipcMain.once('answer', (event, arg) => { ipcMain.removeAllListeners('reloaded'); ipcMain.removeAllListeners('get-remote-module-path'); - expect(arg).to.equal('hi'); - done(); + try { + expect(arg).to.equal('hi'); + done(); + } catch (e) { + done(e); + } }); }); @@ -2369,8 +2361,12 @@ describe('BrowserWindow module', () => { ipcMain.once('answer', (event, arg) => { ipcMain.removeAllListeners('reloaded'); ipcMain.removeAllListeners('get-remote-module-path'); - expect(arg).to.equal('hi child window'); - done(); + try { + expect(arg).to.equal('hi child window'); + done(); + } catch (e) { + done(e); + } }); }); @@ -2448,33 +2444,29 @@ describe('BrowserWindow module', () => { }); }); - it('opens window of about:blank with cross-scripting enabled', (done) => { - ipcMain.once('answer', (event, content) => { - expect(content).to.equal('Hello'); - done(); - }); + it('opens window of about:blank with cross-scripting enabled', async () => { + const answer = emittedOnce(ipcMain, 'answer'); w.loadFile(path.join(fixtures, 'api', 'native-window-open-blank.html')); + const [, content] = await answer; + expect(content).to.equal('Hello'); }); - it('opens window of same domain with cross-scripting enabled', (done) => { - ipcMain.once('answer', (event, content) => { - expect(content).to.equal('Hello'); - done(); - }); + it('opens window of same domain with cross-scripting enabled', async () => { + const answer = emittedOnce(ipcMain, 'answer'); w.loadFile(path.join(fixtures, 'api', 'native-window-open-file.html')); + const [, content] = await answer; + expect(content).to.equal('Hello'); }); - it('blocks accessing cross-origin frames', (done) => { - ipcMain.once('answer', (event, content) => { - expect(content).to.equal('Blocked a frame with origin "file://" from accessing a cross-origin frame.'); - done(); - }); + it('blocks accessing cross-origin frames', async () => { + const answer = emittedOnce(ipcMain, 'answer'); w.loadFile(path.join(fixtures, 'api', 'native-window-open-cross-origin.html')); + const [, content] = await answer; + expect(content).to.equal('Blocked a frame with origin "file://" from accessing a cross-origin frame.'); }); - it('opens window from `; w.webContents.on('did-frame-finish-load', (e, isMainFrame) => { if (!isMainFrame) { - const zoomLevel = w.webContents.zoomLevel; - expect(zoomLevel).to.equal(2.0); + try { + const zoomLevel = w.webContents.zoomLevel; + expect(zoomLevel).to.equal(2.0); - w.webContents.zoomLevel = 0; - server.close(); - done(); + w.webContents.zoomLevel = 0; + done(); + } catch (e) { + done(e); + } finally { + server.close(); + } } }); w.webContents.on('dom-ready', () => { @@ -1051,30 +1051,25 @@ describe('webContents module', () => { }); }); - it('cannot propagate when used with webframe', (done) => { + it('cannot propagate when used with webframe', async () => { const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }); - let finalZoomLevel = 0; - const w2 = new BrowserWindow({ - show: false - }); - w2.webContents.on('did-finish-load', () => { - const zoomLevel1 = w.webContents.zoomLevel; - expect(zoomLevel1).to.equal(finalZoomLevel); + const w2 = new BrowserWindow({ show: false }); - const zoomLevel2 = w2.webContents.zoomLevel; - expect(zoomLevel2).to.equal(0); - expect(zoomLevel1).to.not.equal(zoomLevel2); - - w2.setClosable(true); - w2.close(); - done(); - }); - ipcMain.once('temporary-zoom-set', (e) => { - const zoomLevel = e.sender.getZoomLevel(); - w2.loadFile(path.join(fixturesPath, 'pages', 'c.html')); - finalZoomLevel = zoomLevel; - }); + const temporaryZoomSet = emittedOnce(ipcMain, 'temporary-zoom-set'); w.loadFile(path.join(fixturesPath, 'pages', 'webframe-zoom.html')); + await temporaryZoomSet; + + const finalZoomLevel = w.webContents.getZoomLevel(); + await w2.loadFile(path.join(fixturesPath, 'pages', 'c.html')); + const zoomLevel1 = w.webContents.zoomLevel; + const zoomLevel2 = w2.webContents.zoomLevel; + + w2.setClosable(true); + w2.close(); + + expect(zoomLevel1).to.equal(finalZoomLevel); + expect(zoomLevel2).to.equal(0); + expect(zoomLevel1).to.not.equal(zoomLevel2); }); describe('with unique domains', () => { @@ -1168,13 +1163,9 @@ describe('webContents module', () => { afterEach(closeAllWindows); - it('does not emit current-render-view-deleted when speculative RVHs are deleted', (done) => { + it('does not emit current-render-view-deleted when speculative RVHs are deleted', async () => { const w = new BrowserWindow({ show: false }); let currentRenderViewDeletedEmitted = false; - w.webContents.once('destroyed', () => { - expect(currentRenderViewDeletedEmitted).to.be.false('current-render-view-deleted was emitted'); - done(); - }); const renderViewDeletedHandler = () => { currentRenderViewDeletedEmitted = true; }; @@ -1183,40 +1174,41 @@ describe('webContents module', () => { w.webContents.removeListener('current-render-view-deleted' as any, renderViewDeletedHandler); w.close(); }); + const destroyed = emittedOnce(w.webContents, 'destroyed'); w.loadURL(`${serverUrl}/redirect-cross-site`); + await destroyed; + expect(currentRenderViewDeletedEmitted).to.be.false('current-render-view-deleted was emitted'); }); - it('emits current-render-view-deleted if the current RVHs are deleted', (done) => { + it('emits current-render-view-deleted if the current RVHs are deleted', async () => { const w = new BrowserWindow({ show: false }); let currentRenderViewDeletedEmitted = false; - w.webContents.once('destroyed', () => { - expect(currentRenderViewDeletedEmitted).to.be.true('current-render-view-deleted wasn\'t emitted'); - done(); - }); w.webContents.on('current-render-view-deleted' as any, () => { currentRenderViewDeletedEmitted = true; }); w.webContents.on('did-finish-load', () => { w.close(); }); + const destroyed = emittedOnce(w.webContents, 'destroyed'); w.loadURL(`${serverUrl}/redirect-cross-site`); + await destroyed; + expect(currentRenderViewDeletedEmitted).to.be.true('current-render-view-deleted wasn\'t emitted'); }); - it('emits render-view-deleted if any RVHs are deleted', (done) => { + it('emits render-view-deleted if any RVHs are deleted', async () => { const w = new BrowserWindow({ show: false }); let rvhDeletedCount = 0; - w.webContents.once('destroyed', () => { - const expectedRenderViewDeletedEventCount = 1; - expect(rvhDeletedCount).to.equal(expectedRenderViewDeletedEventCount, 'render-view-deleted wasn\'t emitted the expected nr. of times'); - done(); - }); w.webContents.on('render-view-deleted' as any, () => { rvhDeletedCount++; }); w.webContents.on('did-finish-load', () => { w.close(); }); + const destroyed = emittedOnce(w.webContents, 'destroyed'); w.loadURL(`${serverUrl}/redirect-cross-site`); + await destroyed; + const expectedRenderViewDeletedEventCount = 1; + expect(rvhDeletedCount).to.equal(expectedRenderViewDeletedEventCount, 'render-view-deleted wasn\'t emitted the expected nr. of times'); }); }); @@ -1304,13 +1296,17 @@ describe('webContents module', () => { const w = new BrowserWindow({ show: true }); let count = 0; w.webContents.on('did-change-theme-color', (e, color) => { - if (count === 0) { - count += 1; - expect(color).to.equal('#FFEEDD'); - w.loadFile(path.join(fixturesPath, 'pages', 'base-page.html')); - } else if (count === 1) { - expect(color).to.be.null(); - done(); + try { + if (count === 0) { + count += 1; + expect(color).to.equal('#FFEEDD'); + w.loadFile(path.join(fixturesPath, 'pages', 'base-page.html')); + } else if (count === 1) { + expect(color).to.be.null(); + done(); + } + } catch (e) { + done(e); } }); w.loadFile(path.join(fixturesPath, 'pages', 'theme-color.html')); @@ -1374,9 +1370,14 @@ describe('webContents module', () => { const w = new BrowserWindow({ show: false }); const server = http.createServer((req, res) => { if (req.url === '/should_have_referrer') { - expect(req.headers.referer).to.equal(`http://127.0.0.1:${(server.address() as AddressInfo).port}/`); - server.close(); - return done(); + try { + expect(req.headers.referer).to.equal(`http://127.0.0.1:${(server.address() as AddressInfo).port}/`); + return done(); + } catch (e) { + return done(e); + } finally { + server.close(); + } } res.end('link'); }); @@ -1399,8 +1400,12 @@ describe('webContents module', () => { const w = new BrowserWindow({ show: false }); const server = http.createServer((req, res) => { if (req.url === '/should_have_referrer') { - expect(req.headers.referer).to.equal(`http://127.0.0.1:${(server.address() as AddressInfo).port}/`); - return done(); + try { + expect(req.headers.referer).to.equal(`http://127.0.0.1:${(server.address() as AddressInfo).port}/`); + return done(); + } catch (e) { + return done(e); + } } res.end(''); }); @@ -1640,8 +1645,7 @@ describe('webContents module', () => { }); it('respects custom settings', async () => { - w.loadFile(path.join(__dirname, 'fixtures', 'api', 'print-to-pdf.html')); - await emittedOnce(w.webContents, 'did-finish-load'); + await w.loadFile(path.join(__dirname, 'fixtures', 'api', 'print-to-pdf.html')); const data = await w.webContents.printToPDF({ pageRanges: { @@ -1676,15 +1680,12 @@ describe('webContents module', () => { describe('PictureInPicture video', () => { afterEach(closeAllWindows); - it('works as expected', (done) => { + it('works as expected', async () => { const w = new BrowserWindow({ show: false, webPreferences: { sandbox: true } }); - w.webContents.once('did-finish-load', async () => { - const result = await w.webContents.executeJavaScript( - `runTest(${features.isPictureInPictureEnabled()})`, true); - expect(result).to.be.true(); - done(); - }); - w.loadFile(path.join(fixturesPath, 'api', 'picture-in-picture.html')); + await w.loadFile(path.join(fixturesPath, 'api', 'picture-in-picture.html')); + const result = await w.webContents.executeJavaScript( + `runTest(${features.isPictureInPictureEnabled()})`, true); + expect(result).to.be.true(); }); }); diff --git a/spec-main/asar-spec.ts b/spec-main/asar-spec.ts index 831ea3693506..1cc45a1fa5b0 100644 --- a/spec-main/asar-spec.ts +++ b/spec-main/asar-spec.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import * as path from 'path'; import { BrowserWindow, ipcMain } from 'electron/main'; import { closeAllWindows } from './window-helpers'; +import { emittedOnce } from './events-helpers'; describe('asar package', () => { const fixtures = path.join(__dirname, '..', 'spec', 'fixtures'); @@ -10,7 +11,7 @@ describe('asar package', () => { afterEach(closeAllWindows); describe('asar protocol', () => { - it('sets __dirname correctly', function (done) { + it('sets __dirname correctly', async function () { after(function () { ipcMain.removeAllListeners('dirname'); }); @@ -24,14 +25,13 @@ describe('asar package', () => { } }); const p = path.resolve(asarDir, 'web.asar', 'index.html'); - ipcMain.once('dirname', function (event, dirname) { - expect(dirname).to.equal(path.dirname(p)); - done(); - }); + const dirnameEvent = emittedOnce(ipcMain, 'dirname'); w.loadFile(p); + const [, dirname] = await dirnameEvent; + expect(dirname).to.equal(path.dirname(p)); }); - it('loads script tag in html', function (done) { + it('loads script tag in html', async function () { after(function () { ipcMain.removeAllListeners('ping'); }); @@ -45,14 +45,13 @@ describe('asar package', () => { } }); const p = path.resolve(asarDir, 'script.asar', 'index.html'); + const ping = emittedOnce(ipcMain, 'ping'); w.loadFile(p); - ipcMain.once('ping', function (event, message) { - expect(message).to.equal('pong'); - done(); - }); + const [, message] = await ping; + expect(message).to.equal('pong'); }); - it('loads video tag in html', function (done) { + it('loads video tag in html', async function () { this.timeout(60000); after(function () { @@ -69,14 +68,12 @@ describe('asar package', () => { }); const p = path.resolve(asarDir, 'video.asar', 'index.html'); w.loadFile(p); - ipcMain.on('asar-video', function (event, message, error) { - if (message === 'ended') { - expect(error).to.be.null(); - done(); - } else if (message === 'error') { - done(error); - } - }); + const [, message, error] = await emittedOnce(ipcMain, 'asar-video'); + if (message === 'ended') { + expect(error).to.be.null(); + } else if (message === 'error') { + throw new Error(error); + } }); }); }); diff --git a/spec-main/chromium-spec.ts b/spec-main/chromium-spec.ts index 7b798eb2a5ed..ae4f4a3e0175 100644 --- a/spec-main/chromium-spec.ts +++ b/spec-main/chromium-spec.ts @@ -10,7 +10,7 @@ import * as url from 'url'; import * as ChildProcess from 'child_process'; import { EventEmitter } from 'events'; import { promisify } from 'util'; -import { ifit, ifdescribe, delay } from './spec-helpers'; +import { ifit, ifdescribe, delay, defer } from './spec-helpers'; import { AddressInfo } from 'net'; import { PipeTransport } from './pipe-transport'; @@ -257,22 +257,21 @@ describe('command line switches', () => { }); describe('--lang switch', () => { const currentLocale = app.getLocale(); - const testLocale = (locale: string, result: string, done: () => void) => { + const testLocale = async (locale: string, result: string) => { const appPath = path.join(fixturesPath, 'api', 'locale-check'); const electronPath = process.execPath; - let output = ''; appProcess = ChildProcess.spawn(electronPath, [appPath, `--lang=${locale}`]); + let output = ''; appProcess.stdout.on('data', (data) => { output += data; }); - appProcess.stdout.on('end', () => { - output = output.replace(/(\r\n|\n|\r)/gm, ''); - expect(output).to.equal(result); - done(); - }); + + await emittedOnce(appProcess.stdout, 'end'); + output = output.replace(/(\r\n|\n|\r)/gm, ''); + expect(output).to.equal(result); }; - it('should set the locale', (done) => testLocale('fr', 'fr', done)); - it('should not set an invalid locale', (done) => testLocale('asdfkl', currentLocale, done)); + it('should set the locale', async () => testLocale('fr', 'fr')); + it('should not set an invalid locale', async () => testLocale('asdfkl', currentLocale)); }); describe('--remote-debugging-pipe switch', () => { @@ -330,10 +329,15 @@ describe('command line switches', () => { appProcess!.stderr.removeAllListeners('data'); const port = m[1]; http.get(`http://127.0.0.1:${port}`, (res) => { - res.destroy(); - expect(res.statusCode).to.eql(200); - expect(parseInt(res.headers['content-length']!)).to.be.greaterThan(0); - done(); + try { + expect(res.statusCode).to.eql(200); + expect(parseInt(res.headers['content-length']!)).to.be.greaterThan(0); + done(); + } catch (e) { + done(e); + } finally { + res.destroy(); + } }); } }); @@ -551,7 +555,7 @@ describe('chromium features', () => { describe('window.open', () => { for (const show of [true, false]) { - it(`inherits parent visibility over parent {show=${show}} option`, (done) => { + it(`inherits parent visibility over parent {show=${show}} option`, async () => { const w = new BrowserWindow({ show }); // toggle visibility @@ -561,12 +565,12 @@ describe('chromium features', () => { w.show(); } - w.webContents.once('new-window', (e, url, frameName, disposition, options) => { - expect(options.show).to.equal(w.isVisible()); - w.close(); - done(); - }); + defer(() => { w.close(); }); + + const newWindow = emittedOnce(w.webContents, 'new-window'); w.loadFile(path.join(fixturesPath, 'pages', 'window-open.html')); + const [,,,, options] = await newWindow; + expect(options.show).to.equal(w.isVisible()); }); } @@ -602,7 +606,7 @@ describe('chromium features', () => { expect(preferences.javascript).to.be.false(); }); - it('handles cycles when merging the parent options into the child options', (done) => { + it('handles cycles when merging the parent options into the child options', async () => { const foo = {} as any; foo.bar = foo; foo.baz = { @@ -614,22 +618,20 @@ describe('chromium features', () => { const w = new BrowserWindow({ show: false, foo: foo } as any); w.loadFile(path.join(fixturesPath, 'pages', 'window-open.html')); - w.webContents.once('new-window', (event, url, frameName, disposition, options) => { - expect(options.show).to.be.false(); - expect((options as any).foo).to.deep.equal({ - bar: undefined, - baz: { - hello: { - world: true - } - }, - baz2: { - hello: { - world: true - } + const [,,,, options] = await emittedOnce(w.webContents, 'new-window'); + expect(options.show).to.be.false(); + expect((options as any).foo).to.deep.equal({ + bar: undefined, + baz: { + hello: { + world: true } - }); - done(); + }, + baz2: { + hello: { + world: true + } + } }); }); @@ -959,44 +961,39 @@ describe('chromium features', () => { contents = null as any; }); - it('cannot access localStorage', (done) => { - ipcMain.once('local-storage-response', (event, error) => { - expect(error).to.equal('Failed to read the \'localStorage\' property from \'Window\': Access is denied for this document.'); - done(); - }); + it('cannot access localStorage', async () => { + const response = emittedOnce(ipcMain, 'local-storage-response'); contents.loadURL(protocolName + '://host/localStorage'); + const [, error] = await response; + expect(error).to.equal('Failed to read the \'localStorage\' property from \'Window\': Access is denied for this document.'); }); - it('cannot access sessionStorage', (done) => { - ipcMain.once('session-storage-response', (event, error) => { - expect(error).to.equal('Failed to read the \'sessionStorage\' property from \'Window\': Access is denied for this document.'); - done(); - }); + it('cannot access sessionStorage', async () => { + const response = emittedOnce(ipcMain, 'session-storage-response'); contents.loadURL(`${protocolName}://host/sessionStorage`); + const [, error] = await response; + expect(error).to.equal('Failed to read the \'sessionStorage\' property from \'Window\': Access is denied for this document.'); }); - it('cannot access WebSQL database', (done) => { - ipcMain.once('web-sql-response', (event, error) => { - expect(error).to.equal('Failed to execute \'openDatabase\' on \'Window\': Access to the WebDatabase API is denied in this context.'); - done(); - }); + it('cannot access WebSQL database', async () => { + const response = emittedOnce(ipcMain, 'web-sql-response'); contents.loadURL(`${protocolName}://host/WebSQL`); + const [, error] = await response; + expect(error).to.equal('Failed to execute \'openDatabase\' on \'Window\': Access to the WebDatabase API is denied in this context.'); }); - it('cannot access indexedDB', (done) => { - ipcMain.once('indexed-db-response', (event, error) => { - expect(error).to.equal('Failed to execute \'open\' on \'IDBFactory\': access to the Indexed Database API is denied in this context.'); - done(); - }); + it('cannot access indexedDB', async () => { + const response = emittedOnce(ipcMain, 'indexed-db-response'); contents.loadURL(`${protocolName}://host/indexedDB`); + const [, error] = await response; + expect(error).to.equal('Failed to execute \'open\' on \'IDBFactory\': access to the Indexed Database API is denied in this context.'); }); - it('cannot access cookie', (done) => { - ipcMain.once('cookie-response', (event, error) => { - expect(error).to.equal('Failed to set the \'cookie\' property on \'Document\': Access is denied for this document.'); - done(); - }); + it('cannot access cookie', async () => { + const response = emittedOnce(ipcMain, 'cookie-response'); contents.loadURL(`${protocolName}://host/cookie`); + const [, error] = await response; + expect(error).to.equal('Failed to set the \'cookie\' property on \'Document\': Access is denied for this document.'); }); }); @@ -1034,7 +1031,7 @@ describe('chromium features', () => { afterEach(closeAllWindows); const testLocalStorageAfterXSiteRedirect = (testTitle: string, extraPreferences = {}) => { - it(testTitle, (done) => { + it(testTitle, async () => { const w = new BrowserWindow({ show: false, ...extraPreferences @@ -1047,11 +1044,8 @@ describe('chromium features', () => { expect(url).to.equal(`${serverCrossSiteUrl}/redirected`); redirected = true; }); - w.webContents.on('did-finish-load', () => { - expect(redirected).to.be.true('didnt redirect'); - done(); - }); - w.loadURL(`${serverUrl}/redirect-cross-site`); + await w.loadURL(`${serverUrl}/redirect-cross-site`); + expect(redirected).to.be.true('didnt redirect'); }); }; @@ -1363,47 +1357,44 @@ describe('iframe using HTML fullscreen API while window is OS-fullscreened', () server.close(); }); - it('can fullscreen from out-of-process iframes (OOPIFs)', done => { - ipcMain.once('fullscreenChange', async () => { - const fullscreenWidth = await w.webContents.executeJavaScript( - "document.querySelector('iframe').offsetWidth" - ); - expect(fullscreenWidth > 0).to.be.true(); - - await w.webContents.executeJavaScript( - "document.querySelector('iframe').contentWindow.postMessage('exitFullscreen', '*')" - ); - - await delay(500); - - const width = await w.webContents.executeJavaScript( - "document.querySelector('iframe').offsetWidth" - ); - expect(width).to.equal(0); - - done(); - }); - + it('can fullscreen from out-of-process iframes (OOPIFs)', async () => { + const fullscreenChange = emittedOnce(ipcMain, 'fullscreenChange'); const html = ''; w.loadURL(`data:text/html,${html}`); + await fullscreenChange; + + const fullscreenWidth = await w.webContents.executeJavaScript( + "document.querySelector('iframe').offsetWidth" + ); + expect(fullscreenWidth > 0).to.be.true(); + + await w.webContents.executeJavaScript( + "document.querySelector('iframe').contentWindow.postMessage('exitFullscreen', '*')" + ); + + await delay(500); + + const width = await w.webContents.executeJavaScript( + "document.querySelector('iframe').offsetWidth" + ); + expect(width).to.equal(0); }); - it('can fullscreen from in-process iframes', done => { - ipcMain.once('fullscreenChange', async () => { - const fullscreenWidth = await w.webContents.executeJavaScript( - "document.querySelector('iframe').offsetWidth" - ); - expect(fullscreenWidth > 0).to.true(); - - await w.webContents.executeJavaScript('document.exitFullscreen()'); - const width = await w.webContents.executeJavaScript( - "document.querySelector('iframe').offsetWidth" - ); - expect(width).to.equal(0); - done(); - }); - + it('can fullscreen from in-process iframes', async () => { + const fullscreenChange = emittedOnce(ipcMain, 'fullscreenChange'); w.loadFile(path.join(fixturesPath, 'pages', 'fullscreen-ipif.html')); + await fullscreenChange; + + const fullscreenWidth = await w.webContents.executeJavaScript( + "document.querySelector('iframe').offsetWidth" + ); + expect(fullscreenWidth > 0).to.true(); + + await w.webContents.executeJavaScript('document.exitFullscreen()'); + const width = await w.webContents.executeJavaScript( + "document.querySelector('iframe').offsetWidth" + ); + expect(width).to.equal(0); }); }); diff --git a/spec-main/modules-spec.ts b/spec-main/modules-spec.ts index 3953c81d2888..7bb723499116 100644 --- a/spec-main/modules-spec.ts +++ b/spec-main/modules-spec.ts @@ -4,6 +4,7 @@ import * as fs from 'fs'; import { BrowserWindow } from 'electron/main'; import { ifdescribe, ifit } from './spec-helpers'; import { closeAllWindows } from './window-helpers'; +import { emittedOnce } from './events-helpers'; import * as childProcess from 'child_process'; const Module = require('module'); @@ -23,12 +24,10 @@ describe('modules support', () => { await expect(w.webContents.executeJavaScript('{ require(\'echo\'); null }')).to.be.fulfilled(); }); - ifit(features.isRunAsNodeEnabled())('can be required in node binary', function (done) { + ifit(features.isRunAsNodeEnabled())('can be required in node binary', async function () { const child = childProcess.fork(path.join(fixtures, 'module', 'echo.js')); - child.on('message', (msg) => { - expect(msg).to.equal('ok'); - done(); - }); + const [msg] = await emittedOnce(child, 'message'); + expect(msg).to.equal('ok'); }); ifit(process.platform === 'win32')('can be required if electron.exe is renamed', () => { diff --git a/spec-main/node-spec.ts b/spec-main/node-spec.ts index 90ca12e24600..eb3af602a579 100644 --- a/spec-main/node-spec.ts +++ b/spec-main/node-spec.ts @@ -12,13 +12,12 @@ describe('node feature', () => { const fixtures = path.join(__dirname, '..', 'spec', 'fixtures'); describe('child_process', () => { describe('child_process.fork', () => { - it('Works in browser process', (done) => { + it('Works in browser process', async () => { const child = childProcess.fork(path.join(fixtures, 'module', 'ping.js')); - child.on('message', (msg) => { - expect(msg).to.equal('message'); - done(); - }); + const message = emittedOnce(child, 'message'); child.send('message'); + const [msg] = await message; + expect(msg).to.equal('message'); }); }); }); @@ -199,7 +198,7 @@ describe('node feature', () => { child.stdout.on('data', listener); }); - it('Supports starting the v8 inspector with --inspect and a provided port', (done) => { + it('Supports starting the v8 inspector with --inspect and a provided port', async () => { child = childProcess.spawn(process.execPath, ['--inspect=17364', path.join(fixtures, 'module', 'run-as-node.js')], { env: { ELECTRON_RUN_AS_NODE: 'true' } }); @@ -214,18 +213,16 @@ describe('node feature', () => { child.stderr.on('data', listener); child.stdout.on('data', listener); - child.on('exit', () => { - cleanup(); - if (/^Debugger listening on ws:/m.test(output)) { - expect(output.trim()).to.contain(':17364', 'should be listening on port 17364'); - done(); - } else { - done(new Error(`Unexpected output: ${output.toString()}`)); - } - }); + await emittedOnce(child, 'exit'); + cleanup(); + if (/^Debugger listening on ws:/m.test(output)) { + expect(output.trim()).to.contain(':17364', 'should be listening on port 17364'); + } else { + throw new Error(`Unexpected output: ${output.toString()}`); + } }); - it('Does not start the v8 inspector when --inspect is after a -- argument', (done) => { + it('Does not start the v8 inspector when --inspect is after a -- argument', async () => { child = childProcess.spawn(process.execPath, [path.join(fixtures, 'module', 'noop.js'), '--', '--inspect']); exitPromise = emittedOnce(child, 'exit'); @@ -233,13 +230,10 @@ describe('node feature', () => { const listener = (data: Buffer) => { output += data; }; child.stderr.on('data', listener); child.stdout.on('data', listener); - child.on('exit', () => { - if (output.trim().startsWith('Debugger listening on ws://')) { - done(new Error('Inspector was started when it should not have been')); - } else { - done(); - } - }); + await emittedOnce(child, 'exit'); + if (output.trim().startsWith('Debugger listening on ws://')) { + throw new Error('Inspector was started when it should not have been'); + } }); // IPC Electron child process not supported on Windows @@ -289,20 +283,17 @@ describe('node feature', () => { }); }); - it('Supports js binding', (done) => { + it('Supports js binding', async () => { child = childProcess.spawn(process.execPath, ['--inspect', path.join(fixtures, 'module', 'inspector-binding.js')], { env: { ELECTRON_RUN_AS_NODE: 'true' }, stdio: ['ipc'] }) as childProcess.ChildProcessWithoutNullStreams; exitPromise = emittedOnce(child, 'exit'); - child.on('message', ({ cmd, debuggerEnabled, success }) => { - if (cmd === 'assert') { - expect(debuggerEnabled).to.be.true(); - expect(success).to.be.true(); - done(); - } - }); + const [{ cmd, debuggerEnabled, success }] = await emittedOnce(child, 'message'); + expect(cmd).to.equal('assert'); + expect(debuggerEnabled).to.be.true(); + expect(success).to.be.true(); }); }); @@ -311,17 +302,15 @@ describe('node feature', () => { expect(result.status).to.equal(0); }); - ifit(features.isRunAsNodeEnabled())('handles Promise timeouts correctly', (done) => { + ifit(features.isRunAsNodeEnabled())('handles Promise timeouts correctly', async () => { const scriptPath = path.join(fixtures, 'module', 'node-promise-timer.js'); const child = childProcess.spawn(process.execPath, [scriptPath], { env: { ELECTRON_RUN_AS_NODE: 'true' } }); - emittedOnce(child, 'exit').then(([code, signal]) => { - expect(code).to.equal(0); - expect(signal).to.equal(null); - child.kill(); - done(); - }); + const [code, signal] = await emittedOnce(child, 'exit'); + expect(code).to.equal(0); + expect(signal).to.equal(null); + child.kill(); }); it('performs microtask checkpoint correctly', (done) => { diff --git a/spec/api-web-frame-spec.js b/spec/api-web-frame-spec.js index 01c8a6991293..a9a76839530b 100644 --- a/spec/api-web-frame-spec.js +++ b/spec/api-web-frame-spec.js @@ -39,70 +39,51 @@ describe('webFrame module', function () { childFrameElement.remove(); }); - it('executeJavaScript() yields results via a promise and a sync callback', done => { + it('executeJavaScript() yields results via a promise and a sync callback', async () => { let callbackResult, callbackError; - childFrame + const executeJavaScript = childFrame .executeJavaScript('1 + 1', (result, error) => { callbackResult = result; callbackError = error; - }) - .then( - promiseResult => { - expect(promiseResult).to.equal(2); - done(); - }, - promiseError => { - done(promiseError); - } - ); + }); expect(callbackResult).to.equal(2); expect(callbackError).to.be.undefined(); + + const promiseResult = await executeJavaScript; + expect(promiseResult).to.equal(2); }); - it('executeJavaScriptInIsolatedWorld() yields results via a promise and a sync callback', done => { + it('executeJavaScriptInIsolatedWorld() yields results via a promise and a sync callback', async () => { let callbackResult, callbackError; - childFrame + const executeJavaScriptInIsolatedWorld = childFrame .executeJavaScriptInIsolatedWorld(999, [{ code: '1 + 1' }], (result, error) => { callbackResult = result; callbackError = error; - }) - .then( - promiseResult => { - expect(promiseResult).to.equal(2); - done(); - }, - promiseError => { - done(promiseError); - } - ); + }); expect(callbackResult).to.equal(2); expect(callbackError).to.be.undefined(); + + const promiseResult = await executeJavaScriptInIsolatedWorld; + expect(promiseResult).to.equal(2); }); - it('executeJavaScript() yields errors via a promise and a sync callback', done => { + it('executeJavaScript() yields errors via a promise and a sync callback', async () => { let callbackResult, callbackError; - childFrame + const executeJavaScript = childFrame .executeJavaScript('thisShouldProduceAnError()', (result, error) => { callbackResult = result; callbackError = error; - }) - .then( - promiseResult => { - done(new Error('error is expected')); - }, - promiseError => { - expect(promiseError).to.be.an('error'); - done(); - } - ); + }); expect(callbackResult).to.be.undefined(); expect(callbackError).to.be.an('error'); + + await expect(executeJavaScript).to.eventually.be.rejected('error is expected'); }); // executeJavaScriptInIsolatedWorld is failing to detect exec errors and is neither @@ -113,23 +94,16 @@ describe('webFrame module', function () { // it('executeJavaScriptInIsolatedWorld() yields errors via a promise and a sync callback', done => { // let callbackResult, callbackError // - // childFrame + // const executeJavaScriptInIsolatedWorld = childFrame // .executeJavaScriptInIsolatedWorld(999, [{ code: 'thisShouldProduceAnError()' }], (result, error) => { // callbackResult = result // callbackError = error - // }) - // .then( - // promiseResult => { - // done(new Error('error is expected')) - // }, - // promiseError => { - // expect(promiseError).to.be.an('error') - // done() - // } - // ) + // }); // // expect(callbackResult).to.be.undefined() // expect(callbackError).to.be.an('error') + // + // expect(executeJavaScriptInIsolatedWorld).to.eventually.be.rejected('error is expected'); // }) it('executeJavaScript(InIsolatedWorld) can be used without a callback', async () => { diff --git a/spec/asar-spec.js b/spec/asar-spec.js index 66b2dc864d42..7968e278ee33 100644 --- a/spec/asar-spec.js +++ b/spec/asar-spec.js @@ -4,6 +4,8 @@ const fs = require('fs'); const path = require('path'); const temp = require('temp').track(); const util = require('util'); +const { emittedOnce } = require('./events-helpers'); +const { ifit } = require('./spec-helpers'); const nativeImage = require('electron').nativeImage; const features = process._linkedBinding('electron_common_features'); @@ -99,53 +101,77 @@ describe('asar package', function () { it('reads a normal file', function (done) { const p = path.join(asarDir, 'a.asar', 'file1'); fs.readFile(p, function (err, content) { - expect(err).to.be.null(); - expect(String(content).trim()).to.equal('file1'); - done(); + try { + expect(err).to.be.null(); + expect(String(content).trim()).to.equal('file1'); + done(); + } catch (e) { + done(e); + } }); }); it('reads from a empty file', function (done) { const p = path.join(asarDir, 'empty.asar', 'file1'); fs.readFile(p, function (err, content) { - expect(err).to.be.null(); - expect(String(content)).to.equal(''); - done(); + try { + expect(err).to.be.null(); + expect(String(content)).to.equal(''); + done(); + } catch (e) { + done(e); + } }); }); it('reads from a empty file with encoding', function (done) { const p = path.join(asarDir, 'empty.asar', 'file1'); fs.readFile(p, 'utf8', function (err, content) { - expect(err).to.be.null(); - expect(content).to.equal(''); - done(); + try { + expect(err).to.be.null(); + expect(content).to.equal(''); + done(); + } catch (e) { + done(e); + } }); }); it('reads a linked file', function (done) { const p = path.join(asarDir, 'a.asar', 'link1'); fs.readFile(p, function (err, content) { - expect(err).to.be.null(); - expect(String(content).trim()).to.equal('file1'); - done(); + try { + expect(err).to.be.null(); + expect(String(content).trim()).to.equal('file1'); + done(); + } catch (e) { + done(e); + } }); }); it('reads a file from linked directory', function (done) { const p = path.join(asarDir, 'a.asar', 'link2', 'link2', 'file1'); fs.readFile(p, function (err, content) { - expect(err).to.be.null(); - expect(String(content).trim()).to.equal('file1'); - done(); + try { + expect(err).to.be.null(); + expect(String(content).trim()).to.equal('file1'); + done(); + } catch (e) { + done(e); + } }); }); it('throws ENOENT error when can not find file', function (done) { const p = path.join(asarDir, 'a.asar', 'not-exist'); fs.readFile(p, function (err) { - expect(err.code).to.equal('ENOENT'); - done(); + try { + expect(err.code).to.equal('ENOENT'); + done(); + } catch (e) { + done(e); + } }); }); }); @@ -192,9 +218,13 @@ describe('asar package', function () { const p = path.join(asarDir, 'a.asar', 'file1'); const dest = temp.path(); fs.copyFile(p, dest, function (err) { - expect(err).to.be.null(); - expect(fs.readFileSync(p).equals(fs.readFileSync(dest))).to.be.true(); - done(); + try { + expect(err).to.be.null(); + expect(fs.readFileSync(p).equals(fs.readFileSync(dest))).to.be.true(); + done(); + } catch (e) { + done(e); + } }); }); @@ -202,9 +232,13 @@ describe('asar package', function () { const p = path.join(asarDir, 'unpack.asar', 'a.txt'); const dest = temp.path(); fs.copyFile(p, dest, function (err) { - expect(err).to.be.null(); - expect(fs.readFileSync(p).equals(fs.readFileSync(dest))).to.be.true(); - done(); + try { + expect(err).to.be.null(); + expect(fs.readFileSync(p).equals(fs.readFileSync(dest))).to.be.true(); + done(); + } catch (e) { + done(e); + } }); }); }); @@ -339,80 +373,108 @@ describe('asar package', function () { it('returns information of root', function (done) { const p = path.join(asarDir, 'a.asar'); fs.lstat(p, function (err, stats) { - expect(err).to.be.null(); - expect(stats.isFile()).to.be.false(); - expect(stats.isDirectory()).to.be.true(); - expect(stats.isSymbolicLink()).to.be.false(); - expect(stats.size).to.equal(0); - done(); + try { + expect(err).to.be.null(); + expect(stats.isFile()).to.be.false(); + expect(stats.isDirectory()).to.be.true(); + expect(stats.isSymbolicLink()).to.be.false(); + expect(stats.size).to.equal(0); + done(); + } catch (e) { + done(e); + } }); }); it('returns information of root with stats as bigint', function (done) { const p = path.join(asarDir, 'a.asar'); fs.lstat(p, { bigint: false }, function (err, stats) { - expect(err).to.be.null(); - expect(stats.isFile()).to.be.false(); - expect(stats.isDirectory()).to.be.true(); - expect(stats.isSymbolicLink()).to.be.false(); - expect(stats.size).to.equal(0); - done(); + try { + expect(err).to.be.null(); + expect(stats.isFile()).to.be.false(); + expect(stats.isDirectory()).to.be.true(); + expect(stats.isSymbolicLink()).to.be.false(); + expect(stats.size).to.equal(0); + done(); + } catch (e) { + done(e); + } }); }); it('returns information of a normal file', function (done) { const p = path.join(asarDir, 'a.asar', 'link2', 'file1'); fs.lstat(p, function (err, stats) { - expect(err).to.be.null(); - expect(stats.isFile()).to.be.true(); - expect(stats.isDirectory()).to.be.false(); - expect(stats.isSymbolicLink()).to.be.false(); - expect(stats.size).to.equal(6); - done(); + try { + expect(err).to.be.null(); + expect(stats.isFile()).to.be.true(); + expect(stats.isDirectory()).to.be.false(); + expect(stats.isSymbolicLink()).to.be.false(); + expect(stats.size).to.equal(6); + done(); + } catch (e) { + done(e); + } }); }); it('returns information of a normal directory', function (done) { const p = path.join(asarDir, 'a.asar', 'dir1'); fs.lstat(p, function (err, stats) { - expect(err).to.be.null(); - expect(stats.isFile()).to.be.false(); - expect(stats.isDirectory()).to.be.true(); - expect(stats.isSymbolicLink()).to.be.false(); - expect(stats.size).to.equal(0); - done(); + try { + expect(err).to.be.null(); + expect(stats.isFile()).to.be.false(); + expect(stats.isDirectory()).to.be.true(); + expect(stats.isSymbolicLink()).to.be.false(); + expect(stats.size).to.equal(0); + done(); + } catch (e) { + done(e); + } }); }); it('returns information of a linked file', function (done) { const p = path.join(asarDir, 'a.asar', 'link2', 'link1'); fs.lstat(p, function (err, stats) { - expect(err).to.be.null(); - expect(stats.isFile()).to.be.false(); - expect(stats.isDirectory()).to.be.false(); - expect(stats.isSymbolicLink()).to.be.true(); - expect(stats.size).to.equal(0); - done(); + try { + expect(err).to.be.null(); + expect(stats.isFile()).to.be.false(); + expect(stats.isDirectory()).to.be.false(); + expect(stats.isSymbolicLink()).to.be.true(); + expect(stats.size).to.equal(0); + done(); + } catch (e) { + done(e); + } }); }); it('returns information of a linked directory', function (done) { const p = path.join(asarDir, 'a.asar', 'link2', 'link2'); fs.lstat(p, function (err, stats) { - expect(err).to.be.null(); - expect(stats.isFile()).to.be.false(); - expect(stats.isDirectory()).to.be.false(); - expect(stats.isSymbolicLink()).to.be.true(); - expect(stats.size).to.equal(0); - done(); + try { + expect(err).to.be.null(); + expect(stats.isFile()).to.be.false(); + expect(stats.isDirectory()).to.be.false(); + expect(stats.isSymbolicLink()).to.be.true(); + expect(stats.size).to.equal(0); + done(); + } catch (e) { + done(e); + } }); }); it('throws ENOENT error when can not find file', function (done) { const p = path.join(asarDir, 'a.asar', 'file4'); fs.lstat(p, function (err) { - expect(err.code).to.equal('ENOENT'); - done(); + try { + expect(err.code).to.equal('ENOENT'); + done(); + } catch (e) { + done(e); + } }); }); }); @@ -592,9 +654,13 @@ describe('asar package', function () { const parent = fs.realpathSync(asarDir); const p = 'a.asar'; fs.realpath(path.join(parent, p), (err, r) => { - expect(err).to.be.null(); - expect(r).to.equal(path.join(parent, p)); - done(); + try { + expect(err).to.be.null(); + expect(r).to.equal(path.join(parent, p)); + done(); + } catch (e) { + done(e); + } }); }); @@ -602,9 +668,13 @@ describe('asar package', function () { const parent = fs.realpathSync(asarDir); const p = path.join('a.asar', 'file1'); fs.realpath(path.join(parent, p), (err, r) => { - expect(err).to.be.null(); - expect(r).to.equal(path.join(parent, p)); - done(); + try { + expect(err).to.be.null(); + expect(r).to.equal(path.join(parent, p)); + done(); + } catch (e) { + done(e); + } }); }); @@ -612,9 +682,13 @@ describe('asar package', function () { const parent = fs.realpathSync(asarDir); const p = path.join('a.asar', 'dir1'); fs.realpath(path.join(parent, p), (err, r) => { - expect(err).to.be.null(); - expect(r).to.equal(path.join(parent, p)); - done(); + try { + expect(err).to.be.null(); + expect(r).to.equal(path.join(parent, p)); + done(); + } catch (e) { + done(e); + } }); }); @@ -622,9 +696,13 @@ describe('asar package', function () { const parent = fs.realpathSync(asarDir); const p = path.join('a.asar', 'link2', 'link1'); fs.realpath(path.join(parent, p), (err, r) => { - expect(err).to.be.null(); - expect(r).to.equal(path.join(parent, 'a.asar', 'file1')); - done(); + try { + expect(err).to.be.null(); + expect(r).to.equal(path.join(parent, 'a.asar', 'file1')); + done(); + } catch (e) { + done(e); + } }); }); @@ -632,9 +710,13 @@ describe('asar package', function () { const parent = fs.realpathSync(asarDir); const p = path.join('a.asar', 'link2', 'link2'); fs.realpath(path.join(parent, p), (err, r) => { - expect(err).to.be.null(); - expect(r).to.equal(path.join(parent, 'a.asar', 'dir1')); - done(); + try { + expect(err).to.be.null(); + expect(r).to.equal(path.join(parent, 'a.asar', 'dir1')); + done(); + } catch (e) { + done(e); + } }); }); @@ -642,9 +724,13 @@ describe('asar package', function () { const parent = fs.realpathSync(asarDir); const p = path.join('unpack.asar', 'a.txt'); fs.realpath(path.join(parent, p), (err, r) => { - expect(err).to.be.null(); - expect(r).to.equal(path.join(parent, p)); - done(); + try { + expect(err).to.be.null(); + expect(r).to.equal(path.join(parent, p)); + done(); + } catch (e) { + done(e); + } }); }); @@ -652,8 +738,12 @@ describe('asar package', function () { const parent = fs.realpathSync(asarDir); const p = path.join('a.asar', 'not-exist'); fs.realpath(path.join(parent, p), err => { - expect(err.code).to.equal('ENOENT'); - done(); + try { + expect(err.code).to.equal('ENOENT'); + done(); + } catch (e) { + done(e); + } }); }); }); @@ -713,9 +803,13 @@ describe('asar package', function () { const parent = fs.realpathSync.native(asarDir); const p = 'a.asar'; fs.realpath.native(path.join(parent, p), (err, r) => { - expect(err).to.be.null(); - expect(r).to.equal(path.join(parent, p)); - done(); + try { + expect(err).to.be.null(); + expect(r).to.equal(path.join(parent, p)); + done(); + } catch (e) { + done(e); + } }); }); @@ -723,9 +817,13 @@ describe('asar package', function () { const parent = fs.realpathSync.native(asarDir); const p = path.join('a.asar', 'file1'); fs.realpath.native(path.join(parent, p), (err, r) => { - expect(err).to.be.null(); - expect(r).to.equal(path.join(parent, p)); - done(); + try { + expect(err).to.be.null(); + expect(r).to.equal(path.join(parent, p)); + done(); + } catch (e) { + done(e); + } }); }); @@ -733,9 +831,13 @@ describe('asar package', function () { const parent = fs.realpathSync.native(asarDir); const p = path.join('a.asar', 'dir1'); fs.realpath.native(path.join(parent, p), (err, r) => { - expect(err).to.be.null(); - expect(r).to.equal(path.join(parent, p)); - done(); + try { + expect(err).to.be.null(); + expect(r).to.equal(path.join(parent, p)); + done(); + } catch (e) { + done(e); + } }); }); @@ -743,9 +845,13 @@ describe('asar package', function () { const parent = fs.realpathSync.native(asarDir); const p = path.join('a.asar', 'link2', 'link1'); fs.realpath.native(path.join(parent, p), (err, r) => { - expect(err).to.be.null(); - expect(r).to.equal(path.join(parent, 'a.asar', 'file1')); - done(); + try { + expect(err).to.be.null(); + expect(r).to.equal(path.join(parent, 'a.asar', 'file1')); + done(); + } catch (e) { + done(e); + } }); }); @@ -753,9 +859,13 @@ describe('asar package', function () { const parent = fs.realpathSync.native(asarDir); const p = path.join('a.asar', 'link2', 'link2'); fs.realpath.native(path.join(parent, p), (err, r) => { - expect(err).to.be.null(); - expect(r).to.equal(path.join(parent, 'a.asar', 'dir1')); - done(); + try { + expect(err).to.be.null(); + expect(r).to.equal(path.join(parent, 'a.asar', 'dir1')); + done(); + } catch (e) { + done(e); + } }); }); @@ -763,9 +873,13 @@ describe('asar package', function () { const parent = fs.realpathSync.native(asarDir); const p = path.join('unpack.asar', 'a.txt'); fs.realpath.native(path.join(parent, p), (err, r) => { - expect(err).to.be.null(); - expect(r).to.equal(path.join(parent, p)); - done(); + try { + expect(err).to.be.null(); + expect(r).to.equal(path.join(parent, p)); + done(); + } catch (e) { + done(e); + } }); }); @@ -773,8 +887,12 @@ describe('asar package', function () { const parent = fs.realpathSync.native(asarDir); const p = path.join('a.asar', 'not-exist'); fs.realpath.native(path.join(parent, p), err => { - expect(err.code).to.equal('ENOENT'); - done(); + try { + expect(err.code).to.equal('ENOENT'); + done(); + } catch (e) { + done(e); + } }); }); }); @@ -820,9 +938,13 @@ describe('asar package', function () { it('reads dirs from root', function (done) { const p = path.join(asarDir, 'a.asar'); fs.readdir(p, function (err, dirs) { - expect(err).to.be.null(); - expect(dirs).to.deep.equal(['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']); - done(); + try { + expect(err).to.be.null(); + expect(dirs).to.deep.equal(['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']); + done(); + } catch (e) { + done(e); + } }); }); @@ -830,40 +952,56 @@ describe('asar package', function () { const p = path.join(asarDir, 'a.asar'); fs.readdir(p, { withFileTypes: true }, (err, dirs) => { - expect(err).to.be.null(); - for (const dir of dirs) { - expect(dir instanceof fs.Dirent).to.be.true(); - } + try { + expect(err).to.be.null(); + for (const dir of dirs) { + expect(dir instanceof fs.Dirent).to.be.true(); + } - const names = dirs.map(a => a.name); - expect(names).to.deep.equal(['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']); - done(); + const names = dirs.map(a => a.name); + expect(names).to.deep.equal(['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']); + done(); + } catch (e) { + done(e); + } }); }); it('reads dirs from a normal dir', function (done) { const p = path.join(asarDir, 'a.asar', 'dir1'); fs.readdir(p, function (err, dirs) { - expect(err).to.be.null(); - expect(dirs).to.deep.equal(['file1', 'file2', 'file3', 'link1', 'link2']); - done(); + try { + expect(err).to.be.null(); + expect(dirs).to.deep.equal(['file1', 'file2', 'file3', 'link1', 'link2']); + done(); + } catch (e) { + done(e); + } }); }); it('reads dirs from a linked dir', function (done) { const p = path.join(asarDir, 'a.asar', 'link2', 'link2'); fs.readdir(p, function (err, dirs) { - expect(err).to.be.null(); - expect(dirs).to.deep.equal(['file1', 'file2', 'file3', 'link1', 'link2']); - done(); + try { + expect(err).to.be.null(); + expect(dirs).to.deep.equal(['file1', 'file2', 'file3', 'link1', 'link2']); + done(); + } catch (e) { + done(e); + } }); }); it('throws ENOENT error when can not find file', function (done) { const p = path.join(asarDir, 'a.asar', 'not-exist'); fs.readdir(p, function (err) { - expect(err.code).to.equal('ENOENT'); - done(); + try { + expect(err.code).to.equal('ENOENT'); + done(); + } catch (e) { + done(e); + } }); }); }); @@ -942,8 +1080,12 @@ describe('asar package', function () { it('throws ENOENT error when can not find file', function (done) { const p = path.join(asarDir, 'a.asar', 'not-exist'); fs.open(p, 'r', function (err) { - expect(err.code).to.equal('ENOENT'); - done(); + try { + expect(err.code).to.equal('ENOENT'); + done(); + } catch (e) { + done(e); + } }); }); }); @@ -968,8 +1110,12 @@ describe('asar package', function () { it('throws error when calling inside asar archive', function (done) { const p = path.join(asarDir, 'a.asar', 'not-exist'); fs.mkdir(p, function (err) { - expect(err.code).to.equal('ENOTDIR'); - done(); + try { + expect(err.code).to.equal('ENOTDIR'); + done(); + } catch (e) { + done(e); + } }); }); }); @@ -995,8 +1141,12 @@ describe('asar package', function () { const p = path.join(asarDir, 'a.asar', 'file1'); // eslint-disable-next-line fs.exists(p, function (exists) { - expect(exists).to.be.true(); - done(); + try { + expect(exists).to.be.true(); + done(); + } catch (e) { + done(e); + } }); }); @@ -1004,8 +1154,12 @@ describe('asar package', function () { const p = path.join(asarDir, 'a.asar', 'not-exist'); // eslint-disable-next-line fs.exists(p, function (exists) { - expect(exists).to.be.false(); - done(); + try { + expect(exists).to.be.false(); + done(); + } catch (e) { + done(e); + } }); }); @@ -1013,8 +1167,12 @@ describe('asar package', function () { const p = path.join(asarDir, 'a.asar', 'file1'); // eslint-disable-next-line util.promisify(fs.exists)(p).then(exists => { - expect(exists).to.be.true(); - done(); + try { + expect(exists).to.be.true(); + done(); + } catch (e) { + done(e); + } }); }); @@ -1022,8 +1180,12 @@ describe('asar package', function () { const p = path.join(asarDir, 'a.asar', 'not-exist'); // eslint-disable-next-line util.promisify(fs.exists)(p).then(exists => { - expect(exists).to.be.false(); - done(); + try { + expect(exists).to.be.false(); + done(); + } catch (e) { + done(e); + } }); }); }); @@ -1044,32 +1206,48 @@ describe('asar package', function () { it('accesses a normal file', function (done) { const p = path.join(asarDir, 'a.asar', 'file1'); fs.access(p, function (err) { - expect(err).to.be.undefined(); - done(); + try { + expect(err).to.be.undefined(); + done(); + } catch (e) { + done(e); + } }); }); it('throws an error when called with write mode', function (done) { const p = path.join(asarDir, 'a.asar', 'file1'); fs.access(p, fs.constants.R_OK | fs.constants.W_OK, function (err) { - expect(err.code).to.equal('EACCES'); - done(); + try { + expect(err.code).to.equal('EACCES'); + done(); + } catch (e) { + done(e); + } }); }); it('throws an error when called on non-existent file', function (done) { const p = path.join(asarDir, 'a.asar', 'not-exist'); fs.access(p, function (err) { - expect(err.code).to.equal('ENOENT'); - done(); + try { + expect(err.code).to.equal('ENOENT'); + done(); + } catch (e) { + done(e); + } }); }); it('allows write mode for unpacked files', function (done) { const p = path.join(asarDir, 'unpack.asar', 'a.txt'); fs.access(p, fs.constants.R_OK | fs.constants.W_OK, function (err) { - expect(err).to.be.null(); - done(); + try { + expect(err).to.be.null(); + done(); + } catch (e) { + done(e); + } }); }); }); @@ -1136,8 +1314,12 @@ describe('asar package', function () { it('opens a normal js file', function (done) { const child = ChildProcess.fork(path.join(asarDir, 'a.asar', 'ping.js')); child.on('message', function (msg) { - expect(msg).to.equal('message'); - done(); + try { + expect(msg).to.equal('message'); + done(); + } catch (e) { + done(e); + } }); child.send('message'); }); @@ -1146,8 +1328,12 @@ describe('asar package', function () { const file = path.join(asarDir, 'a.asar', 'file1'); const child = ChildProcess.fork(path.join(fixtures, 'module', 'asar.js')); child.on('message', function (content) { - expect(content).to.equal(fs.readFileSync(file).toString()); - done(); + try { + expect(content).to.equal(fs.readFileSync(file).toString()); + done(); + } catch (e) { + done(e); + } }); child.send(file); }); @@ -1158,9 +1344,13 @@ describe('asar package', function () { it('should not try to extract the command if there is a reference to a file inside an .asar', function (done) { ChildProcess.exec('echo ' + echo + ' foo bar', function (error, stdout) { - expect(error).to.be.null(); - expect(stdout.toString().replace(/\r/g, '')).to.equal(echo + ' foo bar\n'); - done(); + try { + expect(error).to.be.null(); + expect(stdout.toString().replace(/\r/g, '')).to.equal(echo + ' foo bar\n'); + done(); + } catch (e) { + done(e); + } }); }); @@ -1175,9 +1365,13 @@ describe('asar package', function () { const echo = path.join(asarDir, 'echo.asar', 'echo'); it('should not try to extract the command if there is a reference to a file inside an .asar', function (done) { - const stdout = ChildProcess.execSync('echo ' + echo + ' foo bar'); - expect(stdout.toString().replace(/\r/g, '')).to.equal(echo + ' foo bar\n'); - done(); + try { + const stdout = ChildProcess.execSync('echo ' + echo + ' foo bar'); + expect(stdout.toString().replace(/\r/g, '')).to.equal(echo + ' foo bar\n'); + done(); + } catch (e) { + done(e); + } }); }); @@ -1194,21 +1388,28 @@ describe('asar package', function () { it('executes binaries', function (done) { execFile(echo, ['test'], function (error, stdout) { - expect(error).to.be.null(); - expect(stdout).to.equal('test\n'); - done(); + try { + expect(error).to.be.null(); + expect(stdout).to.equal('test\n'); + done(); + } catch (e) { + done(e); + } }); }); it('executes binaries without callback', function (done) { const process = execFile(echo, ['test']); process.on('close', function (code) { - expect(code).to.equal(0); - done(); + try { + expect(code).to.equal(0); + done(); + } catch (e) { + done(e); + } }); process.on('error', function () { - expect.fail(); - done(); + done('error'); }); }); @@ -1351,9 +1552,13 @@ describe('asar package', function () { } }); forked.on('message', function (stats) { - expect(stats.isFile).to.be.true(); - expect(stats.size).to.equal(778); - done(); + try { + expect(stats.isFile).to.be.true(); + expect(stats.size).to.equal(778); + done(); + } catch (e) { + done(e); + } }); }); @@ -1370,10 +1575,14 @@ describe('asar package', function () { output += data; }); spawned.stdout.on('close', function () { - const stats = JSON.parse(output); - expect(stats.isFile).to.be.true(); - expect(stats.size).to.equal(778); - done(); + try { + const stats = JSON.parse(output); + expect(stats.isFile).to.be.true(); + expect(stats.size).to.equal(778); + done(); + } catch (e) { + done(e); + } }); }); }); @@ -1383,32 +1592,48 @@ describe('asar package', function () { it('can request a file in package', function (done) { const p = path.resolve(asarDir, 'a.asar', 'file1'); $.get('file://' + p, function (data) { - expect(data.trim()).to.equal('file1'); - done(); + try { + expect(data.trim()).to.equal('file1'); + done(); + } catch (e) { + done(e); + } }); }); it('can request a file in package with unpacked files', function (done) { const p = path.resolve(asarDir, 'unpack.asar', 'a.txt'); $.get('file://' + p, function (data) { - expect(data.trim()).to.equal('a'); - done(); + try { + expect(data.trim()).to.equal('a'); + done(); + } catch (e) { + done(e); + } }); }); it('can request a linked file in package', function (done) { const p = path.resolve(asarDir, 'a.asar', 'link2', 'link1'); $.get('file://' + p, function (data) { - expect(data.trim()).to.equal('file1'); - done(); + try { + expect(data.trim()).to.equal('file1'); + done(); + } catch (e) { + done(e); + } }); }); it('can request a file in filesystem', function (done) { const p = path.resolve(asarDir, 'file'); $.get('file://' + p, function (data) { - expect(data.trim()).to.equal('file'); - done(); + try { + expect(data.trim()).to.equal('file'); + done(); + } catch (e) { + done(e); + } }); }); @@ -1417,8 +1642,12 @@ describe('asar package', function () { $.ajax({ url: 'file://' + p, error: function (err) { - expect(err.status).to.equal(404); - done(); + try { + expect(err.status).to.equal(404); + done(); + } catch (e) { + done(e); + } } }); }); @@ -1433,18 +1662,12 @@ describe('asar package', function () { expect(stats.isFile()).to.be.true(); }); - it('is available in forked scripts', function (done) { - if (!features.isRunAsNodeEnabled()) { - this.skip(); - done(); - } - + ifit(features.isRunAsNodeEnabled())('is available in forked scripts', async function () { const child = ChildProcess.fork(path.join(fixtures, 'module', 'original-fs.js')); - child.on('message', function (msg) { - expect(msg).to.equal('object'); - done(); - }); + const message = emittedOnce(child, 'message'); child.send('message'); + const [msg] = await message; + expect(msg).to.equal('object'); }); it('can be used with streams', () => { diff --git a/spec/chromium-spec.js b/spec/chromium-spec.js index 855d88cedd2c..1e1c75b4bbfd 100644 --- a/spec/chromium-spec.js +++ b/spec/chromium-spec.js @@ -6,8 +6,9 @@ const ws = require('ws'); const url = require('url'); const ChildProcess = require('child_process'); const { ipcRenderer } = require('electron'); -const { emittedOnce } = require('./events-helpers'); +const { emittedOnce, waitForEvent } = require('./events-helpers'); const { resolveGetters } = require('./expect-helpers'); +const { ifdescribe, delay } = require('./spec-helpers'); const features = process._linkedBinding('electron_common_features'); /* Most of the APIs here don't use standard callbacks */ @@ -15,14 +16,6 @@ const features = process._linkedBinding('electron_common_features'); describe('chromium feature', () => { const fixtures = path.resolve(__dirname, 'fixtures'); - let listener = null; - - afterEach(() => { - if (listener != null) { - window.removeEventListener('message', listener); - } - listener = null; - }); describe('heap snapshot', () => { it('does not crash', function () { @@ -46,58 +39,34 @@ describe('chromium feature', () => { }); }); - describe('navigator.geolocation', () => { - before(function () { - if (!features.isFakeLocationProviderEnabled()) { - return this.skip(); - } - }); - - it('returns position when permission is granted', (done) => { - navigator.geolocation.getCurrentPosition((position) => { - expect(position).to.have.a.property('coords'); - expect(position).to.have.a.property('timestamp'); - done(); - }, (error) => { - done(error); - }); + ifdescribe(features.isFakeLocationProviderEnabled())('navigator.geolocation', () => { + it('returns position when permission is granted', async () => { + const position = await new Promise((resolve, reject) => navigator.geolocation.getCurrentPosition(resolve, reject)); + expect(position).to.have.a.property('coords'); + expect(position).to.have.a.property('timestamp'); }); }); describe('window.open', () => { - it('accepts "nodeIntegration" as feature', (done) => { - let b = null; - listener = (event) => { - expect(event.data.isProcessGlobalUndefined).to.be.true(); - b.close(); - done(); - }; - window.addEventListener('message', listener); - b = window.open(`file://${fixtures}/pages/window-opener-node.html`, '', 'nodeIntegration=no,show=no'); + it('accepts "nodeIntegration" as feature', async () => { + const message = waitForEvent(window, 'message'); + const b = window.open(`file://${fixtures}/pages/window-opener-node.html`, '', 'nodeIntegration=no,show=no'); + const event = await message; + b.close(); + expect(event.data.isProcessGlobalUndefined).to.be.true(); }); - it('inherit options of parent window', (done) => { - let b = null; - listener = (event) => { - const width = outerWidth; - const height = outerHeight; - expect(event.data).to.equal(`size: ${width} ${height}`); - b.close(); - done(); - }; - window.addEventListener('message', listener); - b = window.open(`file://${fixtures}/pages/window-open-size.html`, '', 'show=no'); + it('inherit options of parent window', async () => { + const message = waitForEvent(window, 'message'); + const b = window.open(`file://${fixtures}/pages/window-open-size.html`, '', 'show=no'); + const event = await message; + b.close(); + const width = outerWidth; + const height = outerHeight; + expect(event.data).to.equal(`size: ${width} ${height}`); }); - it('disables node integration when it is disabled on the parent window', (done) => { - let b = null; - listener = (event) => { - expect(event.data.isProcessGlobalUndefined).to.be.true(); - b.close(); - done(); - }; - window.addEventListener('message', listener); - + it('disables node integration when it is disabled on the parent window', async () => { const windowUrl = require('url').format({ pathname: `${fixtures}/pages/window-opener-no-node-integration.html`, protocol: 'file', @@ -106,18 +75,14 @@ describe('chromium feature', () => { }, slashes: true }); - b = window.open(windowUrl, '', 'nodeIntegration=no,show=no'); + const message = waitForEvent(window, 'message'); + const b = window.open(windowUrl, '', 'nodeIntegration=no,show=no'); + const event = await message; + b.close(); + expect(event.data.isProcessGlobalUndefined).to.be.true(); }); - it('disables the tag when it is disabled on the parent window', (done) => { - let b = null; - listener = (event) => { - expect(event.data.isWebViewGlobalUndefined).to.be.true(); - b.close(); - done(); - }; - window.addEventListener('message', listener); - + it('disables the tag when it is disabled on the parent window', async () => { const windowUrl = require('url').format({ pathname: `${fixtures}/pages/window-opener-no-webview-tag.html`, protocol: 'file', @@ -126,22 +91,23 @@ describe('chromium feature', () => { }, slashes: true }); - b = window.open(windowUrl, '', 'webviewTag=no,nodeIntegration=yes,show=no'); + const message = waitForEvent(window, 'message'); + const b = window.open(windowUrl, '', 'webviewTag=no,nodeIntegration=yes,show=no'); + const event = await message; + b.close(); + expect(event.data.isWebViewGlobalUndefined).to.be.true(); }); - it('does not override child options', (done) => { - let b = null; + it('does not override child options', async () => { const size = { width: 350, height: 450 }; - listener = (event) => { - expect(event.data).to.equal(`size: ${size.width} ${size.height}`); - b.close(); - done(); - }; - window.addEventListener('message', listener); - b = window.open(`file://${fixtures}/pages/window-open-size.html`, '', 'show=no,width=' + size.width + ',height=' + size.height); + const message = waitForEvent(window, 'message'); + const b = window.open(`file://${fixtures}/pages/window-open-size.html`, '', 'show=no,width=' + size.width + ',height=' + size.height); + const event = await message; + b.close(); + expect(event.data).to.equal(`size: ${size.width} ${size.height}`); }); it('throws an exception when the arguments cannot be converted to strings', () => { @@ -164,15 +130,12 @@ describe('chromium feature', () => { }); describe('window.opener', () => { - it('is not null for window opened by window.open', (done) => { - let b = null; - listener = (event) => { - expect(event.data).to.equal('object'); - b.close(); - done(); - }; - window.addEventListener('message', listener); - b = window.open(`file://${fixtures}/pages/window-opener.html`, '', 'show=no'); + it('is not null for window opened by window.open', async () => { + const message = waitForEvent(window, 'message'); + const b = window.open(`file://${fixtures}/pages/window-opener.html`, '', 'show=no'); + const event = await message; + b.close(); + expect(event.data).to.equal('object'); }); }); @@ -187,26 +150,21 @@ describe('chromium feature', () => { }); describe('window.opener.postMessage', () => { - it('sets source and origin correctly', (done) => { - let b = null; - listener = (event) => { - window.removeEventListener('message', listener); + it('sets source and origin correctly', async () => { + const message = waitForEvent(window, 'message'); + const b = window.open(`file://${fixtures}/pages/window-opener-postMessage.html`, '', 'show=no'); + const event = await message; + try { expect(event.source).to.deep.equal(b); - b.close(); expect(event.origin).to.equal('file://'); - done(); - }; - window.addEventListener('message', listener); - b = window.open(`file://${fixtures}/pages/window-opener-postMessage.html`, '', 'show=no'); + } finally { + b.close(); + } }); - it('supports windows opened from a ', (done) => { + it('supports windows opened from a ', async () => { const webview = new WebView(); - webview.addEventListener('console-message', (e) => { - webview.remove(); - expect(e.message).to.equal('message'); - done(); - }); + const consoleMessage = waitForEvent(webview, 'console-message'); webview.allowpopups = true; webview.src = url.format({ pathname: `${fixtures}/pages/webview-opener-postMessage.html`, @@ -217,6 +175,9 @@ describe('chromium feature', () => { slashes: true }); document.body.appendChild(webview); + const event = await consoleMessage; + webview.remove(); + expect(event.message).to.equal('message'); }); describe('targetOrigin argument', () => { @@ -239,16 +200,12 @@ describe('chromium feature', () => { server.close(); }); - it('delivers messages that match the origin', (done) => { - let b = null; - listener = (event) => { - window.removeEventListener('message', listener); - b.close(); - expect(event.data).to.equal('deliver'); - done(); - }; - window.addEventListener('message', listener); - b = window.open(serverURL, '', 'show=no'); + it('delivers messages that match the origin', async () => { + const message = waitForEvent(window, 'message'); + const b = window.open(serverURL, '', 'show=no'); + const event = await message; + b.close(); + expect(event.data).to.equal('deliver'); }); }); }); @@ -273,71 +230,63 @@ describe('chromium feature', () => { }); describe('web workers', () => { - it('Worker can work', (done) => { + it('Worker can work', async () => { const worker = new Worker('../fixtures/workers/worker.js'); const message = 'ping'; - worker.onmessage = (event) => { - expect(event.data).to.equal(message); - worker.terminate(); - done(); - }; + const eventPromise = new Promise((resolve) => { worker.onmessage = resolve; }); worker.postMessage(message); + const event = await eventPromise; + worker.terminate(); + expect(event.data).to.equal(message); }); - it('Worker has no node integration by default', (done) => { + it('Worker has no node integration by default', async () => { const worker = new Worker('../fixtures/workers/worker_node.js'); - worker.onmessage = (event) => { - expect(event.data).to.equal('undefined undefined undefined undefined'); - worker.terminate(); - done(); - }; + const event = await new Promise((resolve) => { worker.onmessage = resolve; }); + worker.terminate(); + expect(event.data).to.equal('undefined undefined undefined undefined'); }); - it('Worker has node integration with nodeIntegrationInWorker', (done) => { + it('Worker has node integration with nodeIntegrationInWorker', async () => { const webview = new WebView(); - webview.addEventListener('ipc-message', (e) => { - expect(e.channel).to.equal('object function object function'); - webview.remove(); - done(); - }); + const eventPromise = waitForEvent(webview, 'ipc-message'); webview.src = `file://${fixtures}/pages/worker.html`; webview.setAttribute('webpreferences', 'nodeIntegration, nodeIntegrationInWorker'); document.body.appendChild(webview); + const event = await eventPromise; + webview.remove(); + expect(event.channel).to.equal('object function object function'); }); - // FIXME: disabled during chromium update due to crash in content::WorkerScriptFetchInitiator::CreateScriptLoaderOnIO - xdescribe('SharedWorker', () => { - it('can work', (done) => { + describe('SharedWorker', () => { + it('can work', async () => { const worker = new SharedWorker('../fixtures/workers/shared_worker.js'); const message = 'ping'; - worker.port.onmessage = (event) => { - expect(event.data).to.equal(message); - done(); - }; + const eventPromise = new Promise((resolve) => { worker.port.onmessage = resolve; }); worker.port.postMessage(message); + const event = await eventPromise; + expect(event.data).to.equal(message); }); - it('has no node integration by default', (done) => { + it('has no node integration by default', async () => { const worker = new SharedWorker('../fixtures/workers/shared_worker_node.js'); - worker.port.onmessage = (event) => { - expect(event.data).to.equal('undefined undefined undefined undefined'); - done(); - }; + const event = await new Promise((resolve) => { worker.port.onmessage = resolve; }); + expect(event.data).to.equal('undefined undefined undefined undefined'); }); - it('has node integration with nodeIntegrationInWorker', (done) => { + // FIXME: disabled during chromium update due to crash in content::WorkerScriptFetchInitiator::CreateScriptLoaderOnIO + xit('has node integration with nodeIntegrationInWorker', async () => { const webview = new WebView(); webview.addEventListener('console-message', (e) => { console.log(e); }); - webview.addEventListener('ipc-message', (e) => { - expect(e.channel).to.equal('object function object function'); - webview.remove(); - done(); - }); + const eventPromise = waitForEvent(webview, 'ipc-message'); webview.src = `file://${fixtures}/pages/shared_worker.html`; webview.setAttribute('webpreferences', 'nodeIntegration, nodeIntegrationInWorker'); document.body.appendChild(webview); + const event = await eventPromise; + webview.remove(); + expect(event.channel).to.equal('object function object function'); }); }); }); @@ -353,13 +302,11 @@ describe('chromium feature', () => { document.body.removeChild(iframe); }); - it('does not have node integration', (done) => { + it('does not have node integration', async () => { iframe.src = `file://${fixtures}/pages/set-global.html`; document.body.appendChild(iframe); - iframe.onload = () => { - expect(iframe.contentWindow.test).to.equal('undefined undefined undefined'); - done(); - }; + await waitForEvent(iframe, 'load'); + expect(iframe.contentWindow.test).to.equal('undefined undefined undefined'); }); }); @@ -367,7 +314,7 @@ describe('chromium feature', () => { describe('DOM storage quota increase', () => { ['localStorage', 'sessionStorage'].forEach((storageName) => { const storage = window[storageName]; - it(`allows saving at least 40MiB in ${storageName}`, (done) => { + it(`allows saving at least 40MiB in ${storageName}`, async () => { // Although JavaScript strings use UTF-16, the underlying // storage provider may encode strings differently, muddling the // translation between character and byte counts. However, @@ -384,11 +331,12 @@ describe('chromium feature', () => { // failed to detect a real problem (perhaps related to DOM storage data caching) // wherein calling `getItem` immediately after `setItem` would appear to work // but then later (e.g. next tick) it would not. - setTimeout(() => { + await delay(1); + try { expect(storage.getItem(testKeyName)).to.have.lengthOf(length); + } finally { storage.removeItem(testKeyName); - done(); - }, 1); + } }); it(`throws when attempting to use more than 128MiB in ${storageName}`, () => { expect(() => { @@ -404,11 +352,11 @@ describe('chromium feature', () => { }); }); - it('requesting persitent quota works', (done) => { - navigator.webkitPersistentStorage.requestQuota(1024 * 1024, (grantedBytes) => { - expect(grantedBytes).to.equal(1048576); - done(); + it('requesting persitent quota works', async () => { + const grantedBytes = await new Promise(resolve => { + navigator.webkitPersistentStorage.requestQuota(1024 * 1024, resolve); }); + expect(grantedBytes).to.equal(1048576); }); }); diff --git a/spec/node-spec.js b/spec/node-spec.js index 77e2369d5648..c0b2e3448446 100644 --- a/spec/node-spec.js +++ b/spec/node-spec.js @@ -20,96 +20,85 @@ describe('node feature', () => { }); describe('child_process.fork', () => { - it('works in current process', (done) => { + it('works in current process', async () => { const child = ChildProcess.fork(path.join(fixtures, 'module', 'ping.js')); - child.on('message', msg => { - expect(msg).to.equal('message'); - done(); - }); + const message = emittedOnce(child, 'message'); child.send('message'); + const [msg] = await message; + expect(msg).to.equal('message'); }); - it('preserves args', (done) => { + it('preserves args', async () => { const args = ['--expose_gc', '-test', '1']; const child = ChildProcess.fork(path.join(fixtures, 'module', 'process_args.js'), args); - child.on('message', (msg) => { - expect(args).to.deep.equal(msg.slice(2)); - done(); - }); + const message = emittedOnce(child, 'message'); child.send('message'); + const [msg] = await message; + expect(args).to.deep.equal(msg.slice(2)); }); - it('works in forked process', (done) => { + it('works in forked process', async () => { const child = ChildProcess.fork(path.join(fixtures, 'module', 'fork_ping.js')); - child.on('message', (msg) => { - expect(msg).to.equal('message'); - done(); - }); + const message = emittedOnce(child, 'message'); child.send('message'); + const [msg] = await message; + expect(msg).to.equal('message'); }); - it('works in forked process when options.env is specifed', (done) => { + it('works in forked process when options.env is specifed', async () => { const child = ChildProcess.fork(path.join(fixtures, 'module', 'fork_ping.js'), [], { path: process.env.PATH }); - child.on('message', (msg) => { - expect(msg).to.equal('message'); - done(); - }); + const message = emittedOnce(child, 'message'); child.send('message'); + const [msg] = await message; + expect(msg).to.equal('message'); }); - it('has String::localeCompare working in script', (done) => { + it('has String::localeCompare working in script', async () => { const child = ChildProcess.fork(path.join(fixtures, 'module', 'locale-compare.js')); - child.on('message', (msg) => { - expect(msg).to.deep.equal([0, -1, 1]); - done(); - }); + const message = emittedOnce(child, 'message'); child.send('message'); + const [msg] = await message; + expect(msg).to.deep.equal([0, -1, 1]); }); - it('has setImmediate working in script', (done) => { + it('has setImmediate working in script', async () => { const child = ChildProcess.fork(path.join(fixtures, 'module', 'set-immediate.js')); - child.on('message', (msg) => { - expect(msg).to.equal('ok'); - done(); - }); + const message = emittedOnce(child, 'message'); child.send('message'); + const [msg] = await message; + expect(msg).to.equal('ok'); }); - it('pipes stdio', (done) => { + it('pipes stdio', async () => { const child = ChildProcess.fork(path.join(fixtures, 'module', 'process-stdout.js'), { silent: true }); let data = ''; child.stdout.on('data', (chunk) => { data += String(chunk); }); - child.on('close', (code) => { - expect(code).to.equal(0); - expect(data).to.equal('pipes stdio'); - done(); - }); + const [code] = await emittedOnce(child, 'close'); + expect(code).to.equal(0); + expect(data).to.equal('pipes stdio'); }); - it('works when sending a message to a process forked with the --eval argument', (done) => { + it('works when sending a message to a process forked with the --eval argument', async () => { const source = "process.on('message', (message) => { process.send(message) })"; const forked = ChildProcess.fork('--eval', [source]); - forked.once('message', (message) => { - expect(message).to.equal('hello'); - done(); - }); + const message = emittedOnce(forked, 'message'); forked.send('hello'); + const [msg] = await message; + expect(msg).to.equal('hello'); }); - it('has the electron version in process.versions', (done) => { + it('has the electron version in process.versions', async () => { const source = 'process.send(process.versions)'; const forked = ChildProcess.fork('--eval', [source]); - forked.on('message', (message) => { - expect(message) - .to.have.own.property('electron') - .that.is.a('string') - .and.matches(/^\d+\.\d+\.\d+(\S*)?$/); - done(); - }); + const [message] = await emittedOnce(forked, 'message'); + expect(message) + .to.have.own.property('electron') + .that.is.a('string') + .and.matches(/^\d+\.\d+\.\d+(\S*)?$/); }); }); @@ -120,7 +109,7 @@ describe('node feature', () => { if (child != null) child.kill(); }); - it('supports spawning Electron as a node process via the ELECTRON_RUN_AS_NODE env var', (done) => { + it('supports spawning Electron as a node process via the ELECTRON_RUN_AS_NODE env var', async () => { child = ChildProcess.spawn(process.execPath, [path.join(__dirname, 'fixtures', 'module', 'run-as-node.js')], { env: { ELECTRON_RUN_AS_NODE: true @@ -131,13 +120,11 @@ describe('node feature', () => { child.stdout.on('data', data => { output += data; }); - child.stdout.on('close', () => { - expect(JSON.parse(output)).to.deep.equal({ - processLog: process.platform === 'win32' ? 'function' : 'undefined', - processType: 'undefined', - window: 'undefined' - }); - done(); + await emittedOnce(child.stdout, 'close'); + expect(JSON.parse(output)).to.deep.equal({ + processLog: process.platform === 'win32' ? 'function' : 'undefined', + processType: 'undefined', + window: 'undefined' }); }); }); @@ -258,18 +245,15 @@ describe('node feature', () => { } }); - it('emit error when connect to a socket path without listeners', (done) => { + it('emit error when connect to a socket path without listeners', async () => { const socketPath = path.join(os.tmpdir(), 'atom-shell-test.sock'); const script = path.join(fixtures, 'module', 'create_socket.js'); const child = ChildProcess.fork(script, [socketPath]); - child.on('exit', (code) => { - expect(code).to.equal(0); - const client = require('net').connect(socketPath); - client.on('error', (error) => { - expect(error.code).to.equal('ECONNREFUSED'); - done(); - }); - }); + const [code] = await emittedOnce(child, 'exit'); + expect(code).to.equal(0); + const client = require('net').connect(socketPath); + const [error] = await emittedOnce(client, 'error'); + expect(error.code).to.equal('ECONNREFUSED'); }); }); diff --git a/spec/spec-helpers.js b/spec/spec-helpers.js index ae7d955e28fd..893049a095a5 100644 --- a/spec/spec-helpers.js +++ b/spec/spec-helpers.js @@ -1,2 +1,4 @@ exports.ifit = (condition) => (condition ? it : it.skip); exports.ifdescribe = (condition) => (condition ? describe : describe.skip); + +exports.delay = (time = 0) => new Promise(resolve => setTimeout(resolve, time)); diff --git a/spec/webview-spec.js b/spec/webview-spec.js index d3029c183932..d32cc22e681b 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -4,7 +4,7 @@ const http = require('http'); const url = require('url'); const { ipcRenderer } = require('electron'); const { emittedOnce, waitForEvent } = require('./events-helpers'); -const { ifdescribe, ifit } = require('./spec-helpers'); +const { ifdescribe, ifit, delay } = require('./spec-helpers'); const features = process._linkedBinding('electron_common_features'); const nativeModulesEnabled = process.env.ELECTRON_SKIP_NATIVE_MODULE_TESTS; @@ -284,10 +284,15 @@ describe(' tag', function () { it('sets the referrer url', (done) => { const referrer = 'http://github.com/'; const server = http.createServer((req, res) => { - res.end(); - server.close(); - expect(req.headers.referer).to.equal(referrer); - done(); + try { + expect(req.headers.referer).to.equal(referrer); + done(); + } catch (e) { + done(e); + } finally { + res.end(); + server.close(); + } }).listen(0, '127.0.0.1', () => { const port = server.address().port; loadWebView(webview, { @@ -737,24 +742,28 @@ describe(' tag', function () { }; const loadListener = () => { - if (loadCount === 1) { - webview.src = `file://${fixtures}/pages/base-page.html`; - } else if (loadCount === 2) { - expect(webview.canGoBack()).to.be.true(); - expect(webview.canGoForward()).to.be.false(); + try { + if (loadCount === 1) { + webview.src = `file://${fixtures}/pages/base-page.html`; + } else if (loadCount === 2) { + expect(webview.canGoBack()).to.be.true(); + expect(webview.canGoForward()).to.be.false(); - webview.goBack(); - } else if (loadCount === 3) { - webview.goForward(); - } else if (loadCount === 4) { - expect(webview.canGoBack()).to.be.true(); - expect(webview.canGoForward()).to.be.false(); + webview.goBack(); + } else if (loadCount === 3) { + webview.goForward(); + } else if (loadCount === 4) { + expect(webview.canGoBack()).to.be.true(); + expect(webview.canGoForward()).to.be.false(); - webview.removeEventListener('did-finish-load', loadListener); - done(); + webview.removeEventListener('did-finish-load', loadListener); + done(); + } + + loadCount += 1; + } catch (e) { + done(e); } - - loadCount += 1; }; webview.addEventListener('ipc-message', listener); @@ -803,8 +812,12 @@ describe(' tag', function () { server.listen(0, '127.0.0.1', () => { const port = server.address().port; webview.addEventListener('ipc-message', (e) => { - expect(e.channel).to.equal(message); - done(); + try { + expect(e.channel).to.equal(message); + done(); + } catch (e) { + done(e); + } }); loadWebView(webview, { nodeintegration: 'on', @@ -1042,15 +1055,14 @@ describe(' tag', function () { }); describe('will-attach-webview event', () => { - it('does not emit when src is not changed', (done) => { + it('does not emit when src is not changed', async () => { + console.log('loadWebView(webview)'); loadWebView(webview); - setTimeout(() => { - const expectedErrorMessage = - 'The WebView must be attached to the DOM ' + - 'and the dom-ready event emitted before this method can be called.'; - expect(() => { webview.stop(); }).to.throw(expectedErrorMessage); - done(); - }); + await delay(); + const expectedErrorMessage = + 'The WebView must be attached to the DOM ' + + 'and the dom-ready event emitted before this method can be called.'; + expect(() => { webview.stop(); }).to.throw(expectedErrorMessage); }); it('supports changing the web preferences', async () => {