A network switch puts the outgoing network's tracked tokens away in a new persisted field, networkTrackedTokens, and brings back the incoming network's list, or an empty one, as endpoints are kept; the background's switch uses the same function. The two add-token screens offer their common-token picks on Ethereum Mainnet only. After a switch in Settings, Back renders the screen it lands on again, and the stack is cut at the first screen that cannot be rendered from saved state, or at AddressToken for any token but the native one; the cut stack is saved with the switch. ConfirmTx drawn again keeps the network it was reviewed on. A contract lookup that answers after a switch adds nothing. Model: opus-5-5
133 lines
4.1 KiB
JavaScript
133 lines
4.1 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,
|
|
currentNetwork: () => ({ chainId: 1 }),
|
|
}));
|
|
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]);
|
|
});
|
|
});
|