// 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: () => {}, }); 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]); }); });