From 6b5f20596583cbdb2d03afbfc5d26d6cd4126c2a Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 19 Oct 2026 05:23:14 +0000 Subject: [PATCH] test: the popup boot in the jest suite sends no request to the internet (closes #559) bootPopup() in tests/support/popupBoot.js mocked three of the modules that reach the network, but a boot onto the transaction screen still asked Blockscout for its transaction, and a boot onto the confirmation screen had src/shared/etherscanLabels.js look the recipient up on Etherscan, both through the real fetch(). tests/persistedFieldContract.test.js boots onto both, so every run sent those requests and logged their answers after its tests had finished. bootPopup() now replaces fetch() with the throwing stub other test files use, and cleanupPopup() puts it back. tests/popupBoot.test.js checks that both requests reach the stub. Model: opus-5-5 --- tests/popupBoot.test.js | 45 ++++++++++++++++++++++++++++++++++++++ tests/support/popupBoot.js | 18 +++++++++++++-- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/tests/popupBoot.test.js b/tests/popupBoot.test.js index bbc0067..5c12d5c 100644 --- a/tests/popupBoot.test.js +++ b/tests/popupBoot.test.js @@ -9,6 +9,7 @@ const { bootPopup, cleanupPopup, unversionedValidProfile, + ADDRESS, } = require("./support/popupBoot"); const FLASH_MS = 10; @@ -40,3 +41,47 @@ test("a flash message's timer does not fire after cleanupPopup()", async () => { await wait(FLASH_MS * 5); }); + +// A boot onto a screen that sends a request no mocked module answers: that +// request reaches the boot's fetch() stub, never the internet +// (https://git.eeqj.de/sneak/AutistMask/issues/559). +function restoringOnto(view, viewData) { + return unversionedValidProfile({ + currentView: view, + selectedWallet: 0, + selectedAddress: 0, + viewStack: ["main"], + viewData, + }); +} + +test("the transaction screen's Blockscout request reaches the stub", async () => { + const page = await bootPopup( + restoringOnto("transaction", { + tx: { hash: "0x1", from: ADDRESS, to: ADDRESS }, + }), + ); + + expect(page.visibleViews()).toEqual(["transaction"]); + expect(globalThis.fetch.mock.calls.map((call) => call[0])).toEqual([ + "https://eth.blockscout.com/api/v2/transactions/0x1", + ]); +}); + +test("the confirmation screen's Etherscan lookup reaches the stub", async () => { + const page = await bootPopup( + restoringOnto("confirm-tx", { + pendingTx: { + token: "ETH", + from: ADDRESS, + to: ADDRESS, + amount: "1", + }, + }), + ); + + expect(page.visibleViews()).toEqual(["confirm-tx"]); + expect(globalThis.fetch.mock.calls.map((call) => call[0])).toEqual([ + "https://etherscan.io/address/" + ADDRESS, + ]); +}); diff --git a/tests/support/popupBoot.js b/tests/support/popupBoot.js index 02f98d0..294f338 100644 --- a/tests/support/popupBoot.js +++ b/tests/support/popupBoot.js @@ -45,6 +45,7 @@ const realSetInterval = globalThis.setInterval; const realClearInterval = globalThis.clearInterval; const realSetTimeout = globalThis.setTimeout; const realClearTimeout = globalThis.clearTimeout; +const realFetch = globalThis.fetch; // Every timeout set since the last cleanupPopup() that has neither fired nor // been cleared. cleanupPopup() clears them all. @@ -260,8 +261,14 @@ async function settle() { async function bootPopup(stored, options) { jest.resetModules(); - // The three modules that reach the network. None is on the path under - // test; all would make the suite hit the internet. + // The RPC requests made through src/shared/balances.js go through Node's + // own http and https modules, not fetch(), so the fetch() stub below would + // not stop them, which is why that module stays mocked. + // src/shared/prices.js and src/shared/transactions.js send their requests + // through fetch(), and their mocks answer with no prices and no + // transactions. Every request the page still sends, such as the + // transaction screen's Blockscout lookup and the Etherscan lookup in + // src/shared/etherscanLabels.js, meets the fetch() stub. jest.doMock("../../src/shared/prices", () => ({ prices: {}, refreshPrices: jest.fn(async () => {}), @@ -334,6 +341,12 @@ async function bootPopup(stored, options) { windowListeners[name].push(fn); }, }; + // Every fetch() the page makes fails, as it does elsewhere in the suite, + // instead of reaching the internet. A new stub per boot, so the calls it + // records start with that boot. Put back by cleanupPopup(). + globalThis.fetch = jest.fn(() => { + throw new Error("tests must not perform network requests"); + }); // The ten-second refresh init() starts, and a transaction wait's timers, // would outlive the test. So every interval is recorded rather than // started, clearInterval() removes it as a browser would, and tick() below @@ -435,6 +448,7 @@ function cleanupPopup() { pendingTimeouts.clear(); globalThis.setTimeout = realSetTimeout; globalThis.clearTimeout = realClearTimeout; + globalThis.fetch = realFetch; } module.exports = { -- 2.54.0