test: the test suite exits on its own, without --forceExit (closes #585)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run

Some tests left timers running when they ended, and --forceExit hid them.

- Six background test files leave an approval pending, so the background's
  20-second timer ran forever. Each file now stubs that timer, as two blocks
  in tests/backgroundApproval.test.js already did.
- Five popup test files show a flash message, and its timer outlived the
  test. They now run on fake timers.

Both test scripts in package.json drop --forceExit.

Model: opus-5-5
This commit is contained in:
2026-10-27 23:33:22 +00:00
parent a33a40a6fa
commit e934ed19e7
12 changed files with 109 additions and 17 deletions
+2 -2
View File
@@ -6,8 +6,8 @@
"license": "GPL-3.0",
"private": true,
"scripts": {
"test": "jest --forceExit --maxWorkers=3",
"test:verbose": "jest --forceExit --maxWorkers=3 --verbose",
"test": "jest --maxWorkers=3",
"test:verbose": "jest --maxWorkers=3 --verbose",
"build": "node build.js",
"lint": "eslint . && prettier --check .",
"fmt": "prettier --write .",
+6 -1
View File
@@ -161,7 +161,7 @@ function click(element) {
// show() starts the fee estimate without awaiting it; this lets it settle.
async function settle() {
for (let i = 0; i < 10; i++) await new Promise((r) => setTimeout(r, 0));
for (let i = 0; i < 10; i++) await jest.advanceTimersByTimeAsync(0);
}
// ------------------------------------------------------------ harness
@@ -188,7 +188,11 @@ let sent;
// handler to the buttons it drew, as the browser's fresh list would have.
let plusButton;
// showFlash() starts a timer that clears the message one or two seconds later.
// Each test here runs on fake timers and drops them in afterEach, so that timer
// never outlives the test.
beforeEach(() => {
jest.useFakeTimers();
approvalDetails = null;
sent = [];
mockNode.broadcast = [];
@@ -239,6 +243,7 @@ beforeEach(() => {
});
afterEach(() => {
jest.useRealTimers();
jest.restoreAllMocks();
});
+11 -12
View File
@@ -537,7 +537,18 @@ async function settleIncludingRejections() {
await new Promise((resolve) => setImmediate(resolve));
}
// While an approval is pending, the background starts a 20-second timer to
// keep itself running. The timer is stubbed and never starts, so no test in
// this file leaves it running.
let keepRunningTimer;
beforeEach(() => {
keepRunningTimer = jest
.spyOn(global, "setInterval")
.mockImplementation(() => 1);
});
afterEach(() => {
keepRunningTimer.mockRestore();
delete global.chrome;
jest.resetModules();
});
@@ -1883,18 +1894,6 @@ describe("a closed approval window leaves the request pending", () => {
return bg.actionPopups[bg.actionPopups.length - 1];
}
// The timer that keeps the worker running while an approval is pending is
// stubbed, so no test here leaves that timer running when it ends.
let keepRunningTimer;
beforeEach(() => {
keepRunningTimer = jest
.spyOn(global, "setInterval")
.mockImplementation(() => 1);
});
afterEach(() => {
keepRunningTimer.mockRestore();
});
test("the toolbar popup opens a closed transaction approval, and approving it there sends", async () => {
const bg = loadBackground();
const pending = bg.requestTx();
+11
View File
@@ -103,7 +103,18 @@ function deferred() {
return { promise, resolve };
}
// While an approval is pending, the background starts a 20-second timer to
// keep itself running. The timer is stubbed and never starts, so no test in
// this file leaves it running.
let keepRunningTimer;
beforeEach(() => {
keepRunningTimer = jest
.spyOn(global, "setInterval")
.mockImplementation(() => 1);
});
afterEach(() => {
keepRunningTimer.mockRestore();
delete global.chrome;
});
+11
View File
@@ -56,7 +56,18 @@ async function settle() {
for (let i = 0; i < 50; i++) await Promise.resolve();
}
// While an approval is pending, the background starts a 20-second timer to
// keep itself running. The timer is stubbed and never starts, so no test in
// this file leaves it running.
let keepRunningTimer;
beforeEach(() => {
keepRunningTimer = jest
.spyOn(global, "setInterval")
.mockImplementation(() => 1);
});
afterEach(() => {
keepRunningTimer.mockRestore();
delete global.chrome;
});
+11
View File
@@ -64,7 +64,18 @@ async function settle() {
for (let i = 0; i < 50; i++) await Promise.resolve();
}
// While an approval is pending, the background starts a 20-second timer to
// keep itself running. The timer is stubbed and never starts, so no test in
// this file leaves it running.
let keepRunningTimer;
beforeEach(() => {
keepRunningTimer = jest
.spyOn(global, "setInterval")
.mockImplementation(() => 1);
});
afterEach(() => {
keepRunningTimer.mockRestore();
delete global.chrome;
});
+11
View File
@@ -76,7 +76,18 @@ async function settle() {
for (let i = 0; i < 60; i++) await Promise.resolve();
}
// While an approval is pending, the background starts a 20-second timer to
// keep itself running. The timer is stubbed and never starts, so no test in
// this file leaves it running.
let keepRunningTimer;
beforeEach(() => {
keepRunningTimer = jest
.spyOn(global, "setInterval")
.mockImplementation(() => 1);
});
afterEach(() => {
keepRunningTimer.mockRestore();
delete global.chrome;
});
+11
View File
@@ -57,7 +57,18 @@ async function settle() {
for (let i = 0; i < 50; i++) await Promise.resolve();
}
// While an approval is pending, the background starts a 20-second timer to
// keep itself running. The timer is stubbed and never starts, so no test in
// this file leaves it running.
let keepRunningTimer;
beforeEach(() => {
keepRunningTimer = jest
.spyOn(global, "setInterval")
.mockImplementation(() => 1);
});
afterEach(() => {
keepRunningTimer.mockRestore();
delete global.chrome;
jest.resetModules();
});
+11
View File
@@ -102,6 +102,17 @@ function makeDocument() {
// ------------------------------------------------------------ harness
// showFlash() starts a timer that clears the message two seconds later. Each
// test here runs on fake timers and drops them in afterEach, so that timer
// never outlives the test.
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
function wallet(name, secret, addresses) {
return {
type: "hd",
+9 -1
View File
@@ -131,10 +131,14 @@ async function review(amount, token) {
async function confirm(amount, token) {
const txInfo = await review("0.5", token);
confirmTx.show({ ...txInfo, amount });
for (let i = 0; i < 10; i++) await new Promise((r) => setTimeout(r, 0));
for (let i = 0; i < 10; i++) await jest.advanceTimersByTimeAsync(0);
}
// showFlash() starts a timer that clears the message two seconds later. Each
// test here runs on fake timers and drops them in afterEach, so that timer
// never outlives the test.
beforeEach(() => {
jest.useFakeTimers();
elements.clear();
state.wallets = [
{
@@ -167,6 +171,10 @@ beforeEach(() => {
state.currentView = null;
});
afterEach(() => {
jest.useRealTimers();
});
describe.each([
["1e-3", "ETH"],
["1e-3", "TOK"],
+9 -1
View File
@@ -277,14 +277,18 @@ async function review() {
// show() starts the fee estimate without awaiting it; this lets it settle.
async function settle() {
for (let i = 0; i < 10; i++) await new Promise((r) => setTimeout(r, 0));
for (let i = 0; i < 10; i++) await jest.advanceTimersByTimeAsync(0);
}
function canSend() {
return !el("btn-confirm-send").disabled;
}
// showFlash() starts a timer that clears the message two seconds later. Each
// test here runs on fake timers and drops them in afterEach, so that timer
// never outlives the test.
beforeEach(() => {
jest.useFakeTimers();
elements.clear();
log.errorf.mockClear();
pageClosed = new AbortController();
@@ -297,6 +301,10 @@ beforeEach(() => {
mockNode.broadcast = [];
});
afterEach(() => {
jest.useRealTimers();
});
describe("Max on an ETH send", () => {
test("fills in the exact balance minus the fee reserve", async () => {
await refreshWith(BALANCE_WEI);
+6
View File
@@ -357,6 +357,11 @@ describe("every way to a signature or the private key refuses a defective wallet
});
beforeEach(() => {
// showFlash() starts a timer that clears the message two seconds
// later. Each test here runs on fake timers and drops them in
// afterEach, so that timer never outlives the test.
jest.useFakeTimers();
const broken = brokenXprvWallet();
// A balance, so that no Send button's zero-balance refusal can stand
// in for the defect check.
@@ -398,6 +403,7 @@ describe("every way to a signature or the private key refuses a defective wallet
});
afterEach(() => {
jest.useRealTimers();
state.wallets = [];
state.activeAddress = null;
state.selectedWallet = null;