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
132 lines
4.0 KiB
JavaScript
132 lines
4.0 KiB
JavaScript
// The flash line (#252). #flash-msg reserves one line and cuts a message too
|
|
// long for it with an ellipsis; that is measured in a real browser by
|
|
// tests/e2e/run.js. Here: showFlash() keeps the whole message readable in the
|
|
// line's title, and the two add-token screens flash a fixed line, not the text
|
|
// of whatever error adding the token threw.
|
|
|
|
const ADDRESS = "0x1111111111111111111111111111111111111111";
|
|
|
|
let elements;
|
|
|
|
function fakeElement() {
|
|
return {
|
|
value: "",
|
|
textContent: "",
|
|
title: "",
|
|
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());
|
|
}
|
|
|
|
beforeEach(() => {
|
|
jest.resetModules();
|
|
elements = {};
|
|
globalThis.document = { getElementById: element };
|
|
// state.js reads chrome.storage.local at load.
|
|
globalThis.chrome = {
|
|
storage: { local: { get: async () => ({}), set: async () => {} } },
|
|
};
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.dontMock("../src/popup/views/helpers");
|
|
jest.dontMock("../src/shared/state");
|
|
jest.dontMock("../src/shared/balances");
|
|
jest.restoreAllMocks();
|
|
jest.useRealTimers();
|
|
delete globalThis.document;
|
|
delete globalThis.chrome;
|
|
});
|
|
|
|
test("showFlash() puts the whole message in the title, and clears both", () => {
|
|
jest.useFakeTimers();
|
|
const { showFlash } = require("../src/popup/views/helpers");
|
|
|
|
showFlash("Saved.");
|
|
expect(element("flash-msg").textContent).toBe("Saved.");
|
|
expect(element("flash-msg").title).toBe("Saved.");
|
|
|
|
jest.advanceTimersByTime(2000);
|
|
expect(element("flash-msg").textContent).toBe("");
|
|
expect(element("flash-msg").title).toBe("");
|
|
});
|
|
|
|
describe.each([
|
|
["addToken", "add-token-address", "btn-add-token-confirm"],
|
|
[
|
|
"settingsAddToken",
|
|
"settings-addtoken-address",
|
|
"btn-settings-addtoken-manual",
|
|
],
|
|
])("adding a token on %s", (view, field, button) => {
|
|
let flashes;
|
|
let errors;
|
|
|
|
// Clicks the screen's add button with lookupTokenInfo() and saveState()
|
|
// replaced by the given functions.
|
|
async function add(lookupTokenInfo, saveState) {
|
|
flashes = [];
|
|
errors = jest.spyOn(console, "error").mockImplementation(() => {});
|
|
jest.spyOn(console, "log").mockImplementation(() => {});
|
|
jest.doMock("../src/shared/balances", () => ({ lookupTokenInfo }));
|
|
jest.doMock("../src/shared/state", () => ({
|
|
state: { trackedTokens: [] },
|
|
saveState,
|
|
}));
|
|
jest.doMock("../src/popup/views/helpers", () => ({
|
|
$: element,
|
|
showView: () => {},
|
|
showFlash: (msg) => flashes.push(msg),
|
|
escapeHtml: (s) => s,
|
|
goBack: () => {},
|
|
}));
|
|
|
|
require("../src/popup/views/" + view).init({
|
|
doRefreshAndRender: () => {},
|
|
pageClosed: new AbortController().signal,
|
|
});
|
|
element(field).value = ADDRESS;
|
|
await element(button).listeners.click();
|
|
}
|
|
|
|
test("a failed save flashes a fixed line and logs the error", async () => {
|
|
const detail = "A sentence about the stored record. ".repeat(4);
|
|
|
|
await add(
|
|
async () => ({ symbol: "TKN", decimals: 18, name: "Token" }),
|
|
async () => {
|
|
throw new Error(detail);
|
|
},
|
|
);
|
|
|
|
expect(flashes).toEqual(["Could not add the token."]);
|
|
expect(errors).toHaveBeenCalledWith(
|
|
"[AutistMask]",
|
|
"Adding token failed for",
|
|
ADDRESS,
|
|
detail,
|
|
);
|
|
});
|
|
|
|
test("a contract that is not a token flashes the lookup message", async () => {
|
|
const detail = "Not a valid ERC-20 token (symbol() failed).";
|
|
|
|
await add(
|
|
async () => {
|
|
throw new Error(detail);
|
|
},
|
|
async () => {},
|
|
);
|
|
|
|
expect(flashes).toEqual([detail]);
|
|
});
|
|
});
|