From 93d9fe675d3ab3f1bbc76a4aefcc82d4b8d2f945 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Sat, 11 Apr 2026 22:57:38 -0700 Subject: [PATCH] chore: migrate api-browser-window-spec.ts to vitest --- ...dow-spec.ts => api-browser-window.spec.ts} | 569 ++++++++++-------- 1 file changed, 303 insertions(+), 266 deletions(-) rename spec/{api-browser-window-spec.ts => api-browser-window.spec.ts} (96%) mode change 100755 => 100644 diff --git a/spec/api-browser-window-spec.ts b/spec/api-browser-window.spec.ts old mode 100755 new mode 100644 similarity index 96% rename from spec/api-browser-window-spec.ts rename to spec/api-browser-window.spec.ts index 7f0bfec206..c7ad3f2403 --- a/spec/api-browser-window-spec.ts +++ b/spec/api-browser-window.spec.ts @@ -17,6 +17,7 @@ import { } from 'electron/main'; import { expect } from 'chai'; +import { afterAll, afterEach, beforeAll, beforeEach, describe, it } from 'vitest'; import * as childProcess from 'node:child_process'; import { once } from 'node:events'; @@ -33,7 +34,7 @@ import * as nodeUrl from 'node:url'; import { emittedUntil, emittedNTimes } from './lib/events-helpers'; import { randomString } from './lib/net-helpers'; import { HexColors, hasCapturableScreen, ScreenCapture } from './lib/screen-helpers'; -import { ifit, ifdescribe, defer, listen, waitUntil, isWayland } from './lib/spec-helpers'; +import { ifit, ifdescribe, defer, listen, waitUntil, isWayland, withDone } from './lib/spec-helpers'; import { closeWindow, closeAllWindows } from './lib/window-helpers'; const fixtures = path.resolve(__dirname, 'fixtures'); @@ -193,7 +194,7 @@ describe('BrowserWindow module', () => { let server: http.Server; let url: string; - before(async () => { + beforeAll(async () => { server = http.createServer((request, response) => { switch (request.url) { case '/net-error': @@ -220,7 +221,7 @@ describe('BrowserWindow module', () => { url = (await listen(server)).url; }); - after(() => { + afterAll(() => { server.close(); }); @@ -381,14 +382,14 @@ describe('BrowserWindow module', () => { let w: BrowserWindow; const scheme = 'other'; const srcPath = path.join(fixtures, 'api'); - before(() => { + beforeAll(() => { protocol.handle(scheme, (req) => { const reqURL = new URL(req.url); return net.fetch(nodeUrl.pathToFileURL(path.join(srcPath, reqURL.pathname)).toString()); }); }); - after(() => { + afterAll(() => { protocol.unhandle(scheme); }); @@ -402,7 +403,7 @@ describe('BrowserWindow module', () => { let server: http.Server; let url: string; let postData = null as any; - before(async () => { + beforeAll(async () => { const filePath = path.join(fixtures, 'pages', 'a.html'); const fileStats = fs.statSync(filePath); postData = [ @@ -448,7 +449,7 @@ describe('BrowserWindow module', () => { url = (await listen(server)).url; }); - after(() => { + afterAll(() => { server.close(); }); @@ -495,13 +496,16 @@ describe('BrowserWindow module', () => { 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', () => { + it( + 'does not crash in did-fail-provisional-load handler', + withDone((done) => { + w.webContents.once('did-fail-provisional-load', () => { + w.loadURL('http://127.0.0.1:11111'); + done(); + }); w.loadURL('http://127.0.0.1:11111'); - done(); - }); - w.loadURL('http://127.0.0.1:11111'); - }); + }) + ); it('should emit did-fail-load event for URL exceeding character limit', async () => { const data = Buffer.alloc(2 * 1024 * 1024).toString('base64'); const didFailLoad = once(w.webContents, 'did-fail-load'); @@ -665,7 +669,7 @@ describe('BrowserWindow module', () => { describe('will-navigate event', () => { let server: http.Server; let url: string; - before(async () => { + beforeAll(async () => { server = http.createServer((req, res) => { if (req.url === '/navigate-top') { res.end('navigate _top'); @@ -676,7 +680,7 @@ describe('BrowserWindow module', () => { url = (await listen(server)).url; }); - after(() => { + afterAll(() => { server.close(); }); @@ -687,25 +691,28 @@ describe('BrowserWindow module', () => { w.close(); }); - it('can be prevented', (done) => { - let willNavigate = false; - w.webContents.once('will-navigate', (e) => { - willNavigate = true; - e.preventDefault(); - }); - w.webContents.on('did-stop-loading', () => { - if (willNavigate) { - // i.e. it shouldn't have had '?navigated' appended to it. - try { - expect(w.webContents.getURL().endsWith('will-navigate.html')).to.be.true(); - done(); - } catch (e) { - done(e); + it( + 'can be prevented', + withDone((done) => { + let willNavigate = false; + w.webContents.once('will-navigate', (e) => { + willNavigate = true; + e.preventDefault(); + }); + w.webContents.on('did-stop-loading', () => { + if (willNavigate) { + // i.e. it shouldn't have had '?navigated' appended to it. + 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')); - }); + }); + w.loadFile(path.join(fixtures, 'pages', 'will-navigate.html')); + }) + ); it('is triggered when navigating from file: to http:', async () => { await w.loadFile(path.join(fixtures, 'api', 'blank.html')); @@ -773,7 +780,7 @@ describe('BrowserWindow module', () => { describe('will-frame-navigate event', () => { let server = null as unknown as http.Server; let url = null as unknown as string; - before(async () => { + beforeAll(async () => { server = http.createServer((req, res) => { if (req.url === '/navigate-top') { res.end('navigate _top'); @@ -796,69 +803,78 @@ describe('BrowserWindow module', () => { url = (await listen(server)).url; }); - after(() => { + afterAll(() => { server.close(); }); - it('allows the window to be closed from the event listener', (done) => { - w.webContents.once('will-frame-navigate', () => { - w.close(); - done(); - }); - w.loadFile(path.join(fixtures, 'pages', 'will-navigate.html')); - }); + it( + 'allows the window to be closed from the event listener', + withDone((done) => { + w.webContents.once('will-frame-navigate', () => { + w.close(); + done(); + }); + w.loadFile(path.join(fixtures, 'pages', 'will-navigate.html')); + }) + ); - it('can be prevented', (done) => { - let willNavigate = false; - w.webContents.once('will-frame-navigate', (e) => { - willNavigate = true; - e.preventDefault(); - }); - w.webContents.on('did-stop-loading', () => { - if (willNavigate) { - // i.e. it shouldn't have had '?navigated' appended to it. - 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')); - }); - - it('can be prevented when navigating subframe', (done) => { - let willNavigate = false; - w.webContents.on( - 'did-frame-navigate', - (_event, _url, _httpResponseCode, _httpStatusText, isMainFrame, frameProcessId, frameRoutingId) => { - if (isMainFrame) return; - - w.webContents.once('will-frame-navigate', (e) => { - willNavigate = true; - e.preventDefault(); - }); - - w.webContents.on('did-stop-loading', () => { - const frame = webFrameMain.fromId(frameProcessId, frameRoutingId); - expect(frame).to.not.be.undefined(); - if (willNavigate) { - // i.e. it shouldn't have had '?navigated' appended to it. - try { - expect(frame!.url.endsWith('/navigate-iframe-immediately')).to.be.true(); - done(); - } catch (e) { - done(e); - } + it( + 'can be prevented', + withDone((done) => { + let willNavigate = false; + w.webContents.once('will-frame-navigate', (e) => { + willNavigate = true; + e.preventDefault(); + }); + w.webContents.on('did-stop-loading', () => { + if (willNavigate) { + // i.e. it shouldn't have had '?navigated' appended to it. + try { + expect(w.webContents.getURL().endsWith('will-navigate.html')).to.be.true(); + done(); + } catch (e) { + done(e); } - }); - } - ); - w.loadURL( - `data:text/html,` - ); - }); + } + }); + w.loadFile(path.join(fixtures, 'pages', 'will-navigate.html')); + }) + ); + + it( + 'can be prevented when navigating subframe', + withDone((done) => { + let willNavigate = false; + w.webContents.on( + 'did-frame-navigate', + (_event, _url, _httpResponseCode, _httpStatusText, isMainFrame, frameProcessId, frameRoutingId) => { + if (isMainFrame) return; + + w.webContents.once('will-frame-navigate', (e) => { + willNavigate = true; + e.preventDefault(); + }); + + w.webContents.on('did-stop-loading', () => { + const frame = webFrameMain.fromId(frameProcessId, frameRoutingId); + expect(frame).to.not.be.undefined(); + if (willNavigate) { + // i.e. it shouldn't have had '?navigated' appended to it. + try { + expect(frame!.url.endsWith('/navigate-iframe-immediately')).to.be.true(); + done(); + } catch (e) { + done(e); + } + } + }); + } + ); + w.loadURL( + `data:text/html,` + ); + }) + ); it('is triggered when navigating from file: to http:', async () => { await w.loadFile(path.join(fixtures, 'api', 'blank.html')); @@ -992,7 +1008,7 @@ describe('BrowserWindow module', () => { describe('will-redirect event', () => { let server: http.Server; let url: string; - before(async () => { + beforeAll(async () => { server = http.createServer((req, res) => { if (req.url === '/302') { res.setHeader('Location', '/200'); @@ -1007,7 +1023,7 @@ describe('BrowserWindow module', () => { url = (await listen(server)).url; }); - after(() => { + afterAll(() => { server.close(); }); it('is emitted on redirects', async () => { @@ -1045,33 +1061,36 @@ describe('BrowserWindow module', () => { w.close(); }); - it('can be prevented', (done) => { - w.webContents.once('will-redirect', (event) => { - event.preventDefault(); - }); - w.webContents.on('will-navigate', (e, u) => { - expect(u).to.equal(`${url}/302`); - }); - w.webContents.on('did-stop-loading', () => { - 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) => { - try { - expect(u).to.equal(`${url}/200`); - } catch (e) { - done(e); - } - }); - w.loadURL(`${url}/navigate-302`); - }); + it( + 'can be prevented', + withDone((done) => { + w.webContents.once('will-redirect', (event) => { + event.preventDefault(); + }); + w.webContents.on('will-navigate', (e, u) => { + expect(u).to.equal(`${url}/302`); + }); + w.webContents.on('did-stop-loading', () => { + 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) => { + try { + expect(u).to.equal(`${url}/200`); + } catch (e) { + done(e); + } + }); + w.loadURL(`${url}/navigate-302`); + }) + ); }); describe('ordering', () => { @@ -1087,7 +1106,7 @@ describe('BrowserWindow module', () => { 'did-frame-navigate', 'did-navigate' ]; - before(async () => { + beforeAll(async () => { server = http.createServer((req, res) => { if (req.url === '/navigate') { res.end('navigate'); @@ -1105,7 +1124,7 @@ describe('BrowserWindow module', () => { }); url = (await listen(server)).url; }); - after(() => { + afterAll(() => { server.close(); }); it('for initial navigation, event order is consistent', async () => { @@ -2137,43 +2156,46 @@ describe('BrowserWindow module', () => { expect(w.isFullScreen()).to.equal(true); }); - it('does not crash if maximized, minimized, then restored to maximized state', (done) => { - w.destroy(); - w = new BrowserWindow({ show: false }); + it( + 'does not crash if maximized, minimized, then restored to maximized state', + withDone((done) => { + w.destroy(); + w = new BrowserWindow({ show: false }); - w.show(); + w.show(); - let count = 0; + let count = 0; - w.on('maximize', () => { - if (count === 0) { - syncSetTimeout(() => { - w.minimize(); - }); - } - count++; - }); + w.on('maximize', () => { + if (count === 0) { + syncSetTimeout(() => { + w.minimize(); + }); + } + count++; + }); - w.on('minimize', () => { - if (count === 1) { - syncSetTimeout(() => { - w.restore(); - }); - } - count++; - }); + w.on('minimize', () => { + if (count === 1) { + syncSetTimeout(() => { + w.restore(); + }); + } + count++; + }); - w.on('restore', () => { - try { - throw new Error('hey!'); - } catch (e: any) { - expect(e.message).to.equal('hey!'); - done(); - } - }); + w.on('restore', () => { + try { + throw new Error('hey!'); + } catch (e: any) { + expect(e.message).to.equal('hey!'); + done(); + } + }); - w.maximize(); - }); + w.maximize(); + }) + ); it('checks normal bounds for maximized transparent window', async () => { w.destroy(); @@ -2714,10 +2736,10 @@ describe('BrowserWindow module', () => { describe('BrowserWindow.setProgressBar(progress)', () => { let w: BrowserWindow; - before(() => { + beforeAll(() => { w = new BrowserWindow({ show: false }); }); - after(async () => { + afterAll(async () => { await closeWindow(w); w = null as unknown as BrowserWindow; }); @@ -3975,7 +3997,7 @@ describe('BrowserWindow module', () => { let server: http.Server; let serverUrl: string; - before(async () => { + beforeAll(async () => { server = http.createServer((request, response) => { switch (request.url) { case '/cross-site': @@ -3988,7 +4010,7 @@ describe('BrowserWindow module', () => { serverUrl = (await listen(server)).url; }); - after(() => { + afterAll(() => { server.close(); }); @@ -4928,95 +4950,104 @@ describe('BrowserWindow module', () => { }); describe('beginFrameSubscription method', () => { - it('does not crash when callback returns nothing', (done) => { - const w = new BrowserWindow({ show: false }); - let called = false; - w.loadFile(path.join(fixtures, 'api', 'frame-subscriber.html')); - w.webContents.on('dom-ready', async () => { - await showWindowForWayland(w); + it( + 'does not crash when callback returns nothing', + withDone((done) => { + const w = new BrowserWindow({ show: false }); + let called = false; + w.loadFile(path.join(fixtures, 'api', 'frame-subscriber.html')); + w.webContents.on('dom-ready', async () => { + await showWindowForWayland(w); - w.webContents.beginFrameSubscription(function () { - // This callback might be called twice. - if (called) return; - called = true; + w.webContents.beginFrameSubscription(function () { + // This callback might be called twice. + if (called) return; + called = true; - // Pending endFrameSubscription to next tick can reliably reproduce - // a crash which happens when nothing is returned in the callback. - setTimeout().then(() => { - w.webContents.endFrameSubscription(); - done(); + // Pending endFrameSubscription to next tick can reliably reproduce + // a crash which happens when nothing is returned in the callback. + setTimeout().then(() => { + w.webContents.endFrameSubscription(); + done(); + }); }); }); - }); - }); + }) + ); - it('subscribes to frame updates', (done) => { - const w = new BrowserWindow({ show: false }); - let called = false; - w.loadFile(path.join(fixtures, 'api', 'frame-subscriber.html')); - w.webContents.on('dom-ready', async () => { - await showWindowForWayland(w); + it( + 'subscribes to frame updates', + withDone((done) => { + const w = new BrowserWindow({ show: false }); + let called = false; + w.loadFile(path.join(fixtures, 'api', 'frame-subscriber.html')); + w.webContents.on('dom-ready', async () => { + await showWindowForWayland(w); - w.webContents.beginFrameSubscription(function (data) { - // This callback might be called twice. - if (called) return; - called = true; + w.webContents.beginFrameSubscription(function (data) { + // This callback might be called twice. + if (called) return; + called = true; - try { - expect(data.constructor.name).to.equal('NativeImage'); - expect(data.isEmpty()).to.be.false('data is empty'); - done(); - } catch (e) { - done(e); - } finally { - w.webContents.endFrameSubscription(); - } + try { + expect(data.constructor.name).to.equal('NativeImage'); + expect(data.isEmpty()).to.be.false('data is empty'); + done(); + } catch (e) { + done(e); + } finally { + w.webContents.endFrameSubscription(); + } + }); }); - }); - }); + }) + ); - it('subscribes to frame updates (only dirty rectangle)', (done) => { - const w = new BrowserWindow({ show: false }); - let called = false; - let gotInitialFullSizeFrame = false; - const [contentWidth, contentHeight] = w.getContentSize(); - w.webContents.on('did-finish-load', async () => { - await showWindowForWayland(w); + it( + 'subscribes to frame updates (only dirty rectangle)', + withDone((done) => { + const w = new BrowserWindow({ show: false }); + let called = false; + let gotInitialFullSizeFrame = false; + const [contentWidth, contentHeight] = w.getContentSize(); + w.webContents.on('did-finish-load', async () => { + await showWindowForWayland(w); - w.webContents.beginFrameSubscription(true, (image, rect) => { - if (image.isEmpty()) { - // Chromium sometimes sends a 0x0 frame at the beginning of the - // page load. - return; - } - if (rect.height === contentHeight && rect.width === contentWidth && !gotInitialFullSizeFrame) { - // The initial frame is full-size, but we're looking for a call - // with just the dirty-rect. The next frame should be a smaller - // rect. - gotInitialFullSizeFrame = true; - return; - } - // This callback might be called twice. - if (called) return; - // We asked for just the dirty rectangle, so we expect to receive a - // rect smaller than the full size. - // TODO(jeremy): this is failing on windows currently; investigate. - // assert(rect.width < contentWidth || rect.height < contentHeight) - called = true; + w.webContents.beginFrameSubscription(true, (image, rect) => { + if (image.isEmpty()) { + // Chromium sometimes sends a 0x0 frame at the beginning of the + // page load. + return; + } + if (rect.height === contentHeight && rect.width === contentWidth && !gotInitialFullSizeFrame) { + // The initial frame is full-size, but we're looking for a call + // with just the dirty-rect. The next frame should be a smaller + // rect. + gotInitialFullSizeFrame = true; + return; + } + // This callback might be called twice. + if (called) return; + // We asked for just the dirty rectangle, so we expect to receive a + // rect smaller than the full size. + // TODO(jeremy): this is failing on windows currently; investigate. + // assert(rect.width < contentWidth || rect.height < contentHeight) + called = true; - try { - const expectedSize = rect.width * rect.height * 4; - expect(image.toBitmap()).to.be.an.instanceOf(Buffer).with.lengthOf(expectedSize); - done(); - } catch (e) { - done(e); - } finally { - w.webContents.endFrameSubscription(); - } + try { + const expectedSize = rect.width * rect.height * 4; + expect(image.toBitmap()).to.be.an.instanceOf(Buffer).with.lengthOf(expectedSize); + done(); + } catch (e) { + done(e); + } finally { + w.webContents.endFrameSubscription(); + } + }); }); - }); - w.loadFile(path.join(fixtures, 'api', 'frame-subscriber.html')); - }); + w.loadFile(path.join(fixtures, 'api', 'frame-subscriber.html')); + }) + ); it('throws error when subscriber is not well defined', () => { const w = new BrowserWindow({ show: false }); @@ -5364,17 +5395,20 @@ describe('BrowserWindow module', () => { expect(w.getChildWindows().length).to.equal(0); }); - it('can handle parent window close with focus or blur events', (done) => { - const w = new BrowserWindow({ show: false }); - const c = new BrowserWindow({ show: false, parent: w }); + it( + 'can handle parent window close with focus or blur events', + withDone((done) => { + const w = new BrowserWindow({ show: false }); + const c = new BrowserWindow({ show: false, parent: w }); - c.on('closed', () => { - w.focus(); - done(); - }); + c.on('closed', () => { + w.focus(); + done(); + }); - w.close(); - }); + w.close(); + }) + ); ifit(process.platform === 'darwin')( 'only shows the intended window when a child with siblings is shown', @@ -5793,14 +5827,14 @@ describe('BrowserWindow module', () => { let server: http.Server; let serverUrl: string; - before(async () => { + beforeAll(async () => { server = http.createServer((request, response) => { response.end(); }); serverUrl = (await listen(server)).url; }); - after(() => { + afterAll(() => { server.close(); }); @@ -6980,28 +7014,31 @@ describe('BrowserWindow module', () => { } ); - it('reloading does not cause Node.js module API hangs after reload', (done) => { - const w = new BrowserWindow({ - show: false, - webPreferences: { - nodeIntegration: true, - contextIsolation: false - } - }); + it( + 'reloading does not cause Node.js module API hangs after reload', + withDone((done) => { + const w = new BrowserWindow({ + show: false, + webPreferences: { + nodeIntegration: true, + contextIsolation: false + } + }); - let count = 0; - ipcMain.on('async-node-api-done', () => { - if (count === 3) { - ipcMain.removeAllListeners('async-node-api-done'); - done(); - } else { - count++; - w.reload(); - } - }); + let count = 0; + ipcMain.on('async-node-api-done', () => { + if (count === 3) { + ipcMain.removeAllListeners('async-node-api-done'); + done(); + } else { + count++; + w.reload(); + } + }); - w.loadFile(path.join(fixtures, 'pages', 'send-after-node.html')); - }); + w.loadFile(path.join(fixtures, 'pages', 'send-after-node.html')); + }) + ); // TODO(codebytere): fix on Windows and Linux too ifdescribe(process.platform === 'darwin')('window.webContents initial paint', () => {