Files
AutistMask/tests/balanceRefreshCancelled.test.js
T
clawbot b4ec0a60db
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
fix: a popup reload no longer logs the requests it cancels, except on the transaction detail and confirmation screens (closes #475)
The transaction lists and ENS name lookups on the address and token
screens, the address scan after a wallet is created, the endpoint checks
in Settings, the wait screen's receipt check, the Send screen's Max fee
estimate and the token lookup on both add-token screens now check the
signal the popup aborts on pagehide before reporting a failed request.
scanForAddresses(), resolveEnsNames() and lookupTokenInfo() take the
signal.

End-to-end tests reload the popup on the address screen and during the
address scan with their requests held. Jest tests show each of these
reports a real failure and stays silent once the popup has closed. The
transaction detail and confirmation screens are left out: they discard
the popup context that carries the signal.

Model: opus-5-5
2026-10-06 17:58:58 +00:00

96 lines
3.1 KiB
JavaScript

// The balance refresh, and the address scan after a wallet is created, do not
// report a request the popup's own closing cancelled, and still report one
// that failed while the popup was open
// (https://git.eeqj.de/sneak/AutistMask/issues/218,
// https://git.eeqj.de/sneak/AutistMask/issues/475).
//
// 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, scanForAddresses } = require("../src/shared/balances");
const {
generateMnemonic,
hdWalletFromMnemonic,
} = require("../src/shared/wallet");
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 scan's balance and
// transaction count) 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([]);
});
function scan(signal) {
// What the scan found is logged at info level, which is not under test.
jest.spyOn(console, "log").mockImplementation(() => {});
const { xpub } = hdWalletFromMnemonic(generateMnemonic());
return scanForAddresses(xpub, RPC_URL, "mainnet", signal);
}
test("a scan failure while the popup is open is reported", async () => {
await scan(new AbortController().signal);
expect(
logged.some((line) => line.includes("scanForAddresses check failed")),
).toBe(true);
});
test("a scan failure once the popup has closed is not", async () => {
const pageClosed = new AbortController();
pageClosed.abort();
await scan(pageClosed.signal);
expect(logged).toEqual([]);
});