chore: migrate api-browser-window-spec.ts to vitest

This commit is contained in:
Samuel Attard
2026-04-11 22:57:38 -07:00
parent d64680ff0d
commit 93d9fe675d

View File

@@ -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('<a target=_top href="/">navigate _top</a>');
@@ -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('<a target=_top href="/">navigate _top</a>');
@@ -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,<iframe src="http://127.0.0.1:${(server.address() as AddressInfo).port}/navigate-iframe-immediately"></iframe>`
);
});
}
});
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,<iframe src="http://127.0.0.1:${(server.address() as AddressInfo).port}/navigate-iframe-immediately"></iframe>`
);
})
);
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('<a href="/">navigate</a>');
@@ -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', () => {