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
168 lines
5.3 KiB
JavaScript
168 lines
5.3 KiB
JavaScript
// The two add-token screens do not report a token lookup the popup's own
|
|
// closing cancelled, and still report one that failed while the popup was open
|
|
// (https://git.eeqj.de/sneak/AutistMask/issues/475).
|
|
//
|
|
// In the popup a cancelled request fails with the same "Failed to fetch" as a
|
|
// server that cannot be reached, so every RPC request here fails that way, and
|
|
// only the signal the popup aborts on pagehide tells the two cases apart. The
|
|
// real lookupTokenInfo() runs, which logs a failed lookup itself before the
|
|
// screen does. Driven against the fake elements tests/flashLine.test.js uses.
|
|
// The last tests call lookupTokenInfo() directly with symbol() answered, so
|
|
// that its later calls are the ones that fail.
|
|
|
|
const {
|
|
FetchRequest,
|
|
Interface,
|
|
toUtf8Bytes,
|
|
toUtf8String,
|
|
} = require("ethers");
|
|
const { ERC20_ABI } = require("../src/shared/constants");
|
|
|
|
const ADDRESS = "0x1111111111111111111111111111111111111111";
|
|
const RPC_URL = "https://rpc.example.invalid";
|
|
|
|
let elements;
|
|
|
|
function fakeElement() {
|
|
return {
|
|
value: "",
|
|
textContent: "",
|
|
style: {},
|
|
listeners: {},
|
|
addEventListener(event, handler) {
|
|
this.listeners[event] = handler;
|
|
},
|
|
};
|
|
}
|
|
|
|
// Stands in for document.getElementById(): one fake element per id.
|
|
function element(id) {
|
|
return (elements[id] ||= fakeElement());
|
|
}
|
|
|
|
jest.doMock("../src/popup/views/helpers", () => ({
|
|
$: element,
|
|
showView: () => {},
|
|
showFlash: () => {},
|
|
escapeHtml: (s) => s,
|
|
goBack: () => {},
|
|
}));
|
|
|
|
// state.js reads chrome.storage.local at load.
|
|
globalThis.chrome = {
|
|
storage: { local: { get: async () => ({}), set: async () => {} } },
|
|
};
|
|
|
|
const { state } = require("../src/shared/state");
|
|
const { lookupTokenInfo } = require("../src/shared/balances");
|
|
|
|
let logged;
|
|
|
|
beforeEach(() => {
|
|
elements = {};
|
|
logged = [];
|
|
state.trackedTokens = [];
|
|
for (const method of ["warn", "error"]) {
|
|
jest.spyOn(console, method).mockImplementation((...args) => {
|
|
logged.push(args.map(String).join(" "));
|
|
});
|
|
}
|
|
FetchRequest.registerGetUrl(async () => {
|
|
throw new TypeError("Failed to fetch");
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
FetchRequest.registerGetUrl(FetchRequest.createGetUrlFunc());
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
describe.each([
|
|
["addToken", "add-token-address", "btn-add-token-confirm"],
|
|
[
|
|
"settingsAddToken",
|
|
"settings-addtoken-address",
|
|
"btn-settings-addtoken-manual",
|
|
],
|
|
])("looking up a token on %s", (view, field, button) => {
|
|
// Clicks the screen's add button for ADDRESS and waits for the lookup.
|
|
async function add(pageClosed) {
|
|
require("../src/popup/views/" + view).init({
|
|
doRefreshAndRender: () => {},
|
|
pageClosed,
|
|
});
|
|
element(field).value = ADDRESS;
|
|
await element(button).listeners.click();
|
|
}
|
|
|
|
test("a failure while the popup is open is reported", async () => {
|
|
await add(new AbortController().signal);
|
|
expect(logged).toHaveLength(2);
|
|
expect(logged[0]).toContain("symbol() failed:");
|
|
expect(logged[1]).toBe(
|
|
"[AutistMask] Adding token failed for " +
|
|
ADDRESS +
|
|
" Not a valid ERC-20 token (symbol() failed).",
|
|
);
|
|
});
|
|
|
|
test("a failure once the popup has closed is not", async () => {
|
|
const pageClosed = new AbortController();
|
|
pageClosed.abort();
|
|
await add(pageClosed.signal);
|
|
expect(logged).toEqual([]);
|
|
});
|
|
});
|
|
|
|
// Answers the contract call for each function named in `answers` with its
|
|
// value, and fails every other request as above.
|
|
function answer(answers) {
|
|
const erc20 = new Interface(ERC20_ABI);
|
|
FetchRequest.registerGetUrl(async (req) => {
|
|
const { id, params } = JSON.parse(toUtf8String(req.body));
|
|
const { name } = erc20.parseTransaction({ data: params[0].data });
|
|
if (!(name in answers)) {
|
|
throw new TypeError("Failed to fetch");
|
|
}
|
|
const result = erc20.encodeFunctionResult(name, [answers[name]]);
|
|
return {
|
|
statusCode: 200,
|
|
statusMessage: "OK",
|
|
headers: {},
|
|
body: toUtf8Bytes(JSON.stringify({ jsonrpc: "2.0", id, result })),
|
|
};
|
|
});
|
|
}
|
|
|
|
describe.each([
|
|
["decimals() fails", { symbol: "TKN" }, "decimals() failed:"],
|
|
[
|
|
"name() fails",
|
|
{ symbol: "TKN", decimals: 18 },
|
|
"name() failed, using symbol as name:",
|
|
],
|
|
])("a token lookup where %s", (_, answers, report) => {
|
|
async function lookUp(pageClosed) {
|
|
// A token found is logged at info level, which is not under test.
|
|
jest.spyOn(console, "log").mockImplementation(() => {});
|
|
answer(answers);
|
|
// A failed decimals() also fails the lookup, which the screens report
|
|
// as tested above.
|
|
await lookupTokenInfo(ADDRESS, RPC_URL, "mainnet", pageClosed).catch(
|
|
() => {},
|
|
);
|
|
}
|
|
|
|
test("is reported while the popup is open", async () => {
|
|
await lookUp(new AbortController().signal);
|
|
expect(logged).toEqual([expect.stringContaining(report)]);
|
|
});
|
|
|
|
test("is not once the popup has closed", async () => {
|
|
const pageClosed = new AbortController();
|
|
pageClosed.abort();
|
|
await lookUp(pageClosed.signal);
|
|
expect(logged).toEqual([]);
|
|
});
|
|
});
|