// The balance refresh does not report a request the popup's own closing // cancelled, and still reports one that failed while the popup was open // (https://git.eeqj.de/sneak/AutistMask/issues/218). // // In the popup a cancelled fetch() fails with the same "Failed to fetch" as a // server that cannot be reached, so every request here fails that way, and // only the signal the popup aborts on pagehide tells the two cases apart. const { FetchRequest } = require("ethers"); const { refreshBalances } = require("../src/shared/balances"); const RPC_URL = "https://rpc.example.invalid"; const EXPLORER_URL = "https://explorer.example.invalid/api/v2"; const ADDRESS = "0x1111111111111111111111111111111111111111"; const realFetch = globalThis.fetch; let logged; beforeEach(() => { logged = []; jest.spyOn(console, "error").mockImplementation((...args) => { logged.push(args.map(String).join(" ")); }); const failedToFetch = async () => { throw new TypeError("Failed to fetch"); }; // The RPC calls (ETH balance, ENS name) and the explorer request (token // balances) all fail the same way. FetchRequest.registerGetUrl(failedToFetch); globalThis.fetch = jest.fn(failedToFetch); }); afterEach(() => { FetchRequest.registerGetUrl(FetchRequest.createGetUrlFunc()); globalThis.fetch = realFetch; jest.restoreAllMocks(); }); function refresh(signal) { const wallets = [{ addresses: [{ address: ADDRESS }] }]; return refreshBalances( wallets, RPC_URL, EXPLORER_URL, [], "mainnet", signal, ); } test("a failure while the popup is open is reported", async () => { await refresh(new AbortController().signal); for (const label of [ "ETH balance failed", "ENS reverse failed", "fetchTokenBalances failed: Failed to fetch", ]) { expect(logged.some((line) => line.includes(label))).toBe(true); } }); test("a failure once the popup has closed is not", async () => { const pageClosed = new AbortController(); pageClosed.abort(); await refresh(pageClosed.signal); expect(logged).toEqual([]); });