From e934ed19e7c93a42eaf0e3db2e4d049c57272bf8 Mon Sep 17 00:00:00 2001 From: sneak Date: Tue, 27 Oct 2026 23:33:21 +0000 Subject: [PATCH] test: the test suite exits on its own, without --forceExit (closes #585) 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 --- package.json | 4 ++-- tests/addressKey.test.js | 7 ++++++- tests/backgroundApproval.test.js | 23 +++++++++++------------ tests/backgroundStateIsolation.test.js | 11 +++++++++++ tests/chainSwitchGate.test.js | 11 +++++++++++ tests/coldWorkerChainSwitch.test.js | 11 +++++++++++ tests/coldWorkerSendTransaction.test.js | 11 +++++++++++ tests/connectedSitesWorkerRestart.test.js | 11 +++++++++++ tests/deleteWalletLostPassword.test.js | 11 +++++++++++ tests/sendAmountRule.test.js | 10 +++++++++- tests/sendMax.test.js | 10 +++++++++- tests/walletDefects.test.js | 6 ++++++ 12 files changed, 109 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index a8ca0a0..b61275e 100644 --- a/package.json +++ b/package.json @@ -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 .", diff --git a/tests/addressKey.test.js b/tests/addressKey.test.js index 322b87c..efd8133 100644 --- a/tests/addressKey.test.js +++ b/tests/addressKey.test.js @@ -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(); }); diff --git a/tests/backgroundApproval.test.js b/tests/backgroundApproval.test.js index 1c110cc..3e62f6f 100644 --- a/tests/backgroundApproval.test.js +++ b/tests/backgroundApproval.test.js @@ -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(); diff --git a/tests/backgroundStateIsolation.test.js b/tests/backgroundStateIsolation.test.js index 327bcda..3ea75c9 100644 --- a/tests/backgroundStateIsolation.test.js +++ b/tests/backgroundStateIsolation.test.js @@ -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; }); diff --git a/tests/chainSwitchGate.test.js b/tests/chainSwitchGate.test.js index 9abbbec..da0a097 100644 --- a/tests/chainSwitchGate.test.js +++ b/tests/chainSwitchGate.test.js @@ -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; }); diff --git a/tests/coldWorkerChainSwitch.test.js b/tests/coldWorkerChainSwitch.test.js index 06425ab..1ed03e1 100644 --- a/tests/coldWorkerChainSwitch.test.js +++ b/tests/coldWorkerChainSwitch.test.js @@ -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; }); diff --git a/tests/coldWorkerSendTransaction.test.js b/tests/coldWorkerSendTransaction.test.js index 1344807..21df1f9 100644 --- a/tests/coldWorkerSendTransaction.test.js +++ b/tests/coldWorkerSendTransaction.test.js @@ -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; }); diff --git a/tests/connectedSitesWorkerRestart.test.js b/tests/connectedSitesWorkerRestart.test.js index 25dc1f8..f7ff68d 100644 --- a/tests/connectedSitesWorkerRestart.test.js +++ b/tests/connectedSitesWorkerRestart.test.js @@ -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(); }); diff --git a/tests/deleteWalletLostPassword.test.js b/tests/deleteWalletLostPassword.test.js index 9b5b20c..1837299 100644 --- a/tests/deleteWalletLostPassword.test.js +++ b/tests/deleteWalletLostPassword.test.js @@ -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", diff --git a/tests/sendAmountRule.test.js b/tests/sendAmountRule.test.js index 98f43fb..fbc181f 100644 --- a/tests/sendAmountRule.test.js +++ b/tests/sendAmountRule.test.js @@ -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"], diff --git a/tests/sendMax.test.js b/tests/sendMax.test.js index 1c14a76..cd64adb 100644 --- a/tests/sendMax.test.js +++ b/tests/sendMax.test.js @@ -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); diff --git a/tests/walletDefects.test.js b/tests/walletDefects.test.js index fe1c389..346cb5d 100644 --- a/tests/walletDefects.test.js +++ b/tests/walletDefects.test.js @@ -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;