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 = {