// Tracked tokens are kept per network, and the two add-token screens offer the // bundled token list, which holds Ethereum Mainnet contracts, on Ethereum // Mainnet only (https://git.eeqj.de/sneak/AutistMask/issues/523), including on // the screen "Back" lands on after a switch made from the Settings gear. Driven // through the real popup over a stored profile; the same switch made by the // background is in tests/coldWorkerChainSwitch.test.js. const { bootPopup, cleanupPopup, unversionedValidProfile, TOKEN_ADDRESS, } = require("./support/popupBoot"); const SEPOLIA_TOKEN = { address: TOKEN_ADDRESS, symbol: "TST", decimals: 18, name: "Test Token", }; const MAINNET_TOKEN = { address: "0x6B175474E89094C44Da98b954EedeAC495271d0F", symbol: "DAI", decimals: 18, name: "Dai Stablecoin", }; // Fake timers hold the timer of the flash message a switch ends with, which // would otherwise fire after cleanupPopup() has removed the page and throw. beforeEach(() => { jest.useFakeTimers(); }); afterEach(() => { cleanupPopup(); jest.useRealTimers(); }); // The popup reopened onto SettingsAddToken, on `networkId`. function bootOnAddToken(networkId, extra) { return bootPopup( unversionedValidProfile({ networkId, currentView: "settings-addtoken", ...extra, }), ); } // The popup reopened onto AddressDetail on Ethereum Mainnet, with DAI tracked // there and TST tracked on Sepolia. function bootOnAddress() { return bootPopup( unversionedValidProfile({ currentView: "address", selectedWallet: 0, selectedAddress: 0, trackedTokens: [MAINNET_TOKEN], networkTrackedTokens: { sepolia: [SEPOLIA_TOKEN] }, }), ); } // Picks `networkId` in the Settings network selector, as the user does. async function chooseNetwork(boot, networkId) { const select = boot.node("settings-network"); select.value = networkId; for (const fn of select.listeners.change) await fn(); await boot.settle(); } // The Settings gear, a switch to Sepolia, and Back. async function gearSwitchBack(boot) { await boot.click("btn-settings"); expect(boot.visibleViews()).toEqual(["settings"]); await chooseNetwork(boot, "sepolia"); await boot.click("btn-settings-back"); } describe.each([ ["mainnet", false], ["sepolia", true], ])("on %s", (networkId, hidden) => { test(`the common tokens are hidden: ${hidden}`, async () => { const boot = await bootOnAddToken(networkId); expect(boot.pageErrors).toEqual([]); expect(boot.visibleViews()).toEqual(["settings-addtoken"]); expect(boot.hidden("settings-addtoken-common")).toBe(hidden); require("../src/popup/views/addToken").show(); expect(boot.visibleViews()).toEqual(["add-token"]); expect(boot.hidden("add-token-common")).toBe(hidden); }); }); test("a token added by contract address on Sepolia is tracked on Sepolia only", async () => { const boot = await bootOnAddToken("sepolia", { networkTrackedTokens: { mainnet: [MAINNET_TOKEN] }, }); const stored = () => boot.storage.read("autistmask"); const { lookupTokenInfo } = require("../src/shared/balances"); lookupTokenInfo.mockResolvedValueOnce({ symbol: SEPOLIA_TOKEN.symbol, decimals: SEPOLIA_TOKEN.decimals, name: SEPOLIA_TOKEN.name, }); boot.node("settings-addtoken-address").value = TOKEN_ADDRESS; await boot.click("btn-settings-addtoken-manual"); expect(lookupTokenInfo).toHaveBeenCalledWith( TOKEN_ADDRESS, expect.any(String), "sepolia", expect.anything(), ); expect(stored().trackedTokens).toEqual([SEPOLIA_TOKEN]); await chooseNetwork(boot, "mainnet"); expect(stored().networkId).toBe("mainnet"); expect(stored().trackedTokens).toEqual([MAINNET_TOKEN]); const onMainnet = boot.node("settings-tracked-tokens").innerHTML; expect(onMainnet).toContain("DAI"); expect(onMainnet).not.toContain("TST"); await chooseNetwork(boot, "sepolia"); expect(stored().networkId).toBe("sepolia"); expect(stored().trackedTokens).toEqual([SEPOLIA_TOKEN]); const onSepolia = boot.node("settings-tracked-tokens").innerHTML; expect(onSepolia).toContain("TST"); expect(onSepolia).not.toContain("DAI"); }); describe("Back after a switch from the Settings gear", () => { test("SettingsAddToken no longer offers the common tokens", async () => { const boot = await bootOnAddToken("mainnet"); expect(boot.hidden("settings-addtoken-common")).toBe(false); await gearSwitchBack(boot); expect(boot.visibleViews()).toEqual(["settings-addtoken"]); expect(boot.hidden("settings-addtoken-common")).toBe(true); }); test("AddressDetail lists the new network's tracked tokens", async () => { const boot = await bootOnAddress(); expect(boot.node("address-balances").innerHTML).toContain("DAI"); await gearSwitchBack(boot); expect(boot.visibleViews()).toEqual(["address"]); const balances = boot.node("address-balances").innerHTML; expect(balances).toContain("TST"); expect(balances).not.toContain("DAI"); }); // Neither can be rendered from saved state, so neither is landed on. test.each([ ["btn-add-token", "add-token"], ["btn-send", "send"], ])("from over %s, it lands on AddressDetail", async (button, view) => { const boot = await bootOnAddress(); await boot.click(button); expect(boot.visibleViews()).toEqual([view]); await gearSwitchBack(boot); expect(boot.visibleViews()).toEqual(["address"]); expect(boot.node("address-balances").innerHTML).not.toContain("DAI"); }); test("without a wallet, it lands on Welcome", async () => { const boot = await bootPopup(undefined); expect(boot.visibleViews()).toEqual(["welcome"]); await gearSwitchBack(boot); expect(boot.visibleViews()).toEqual(["welcome"]); }); test("a later Back onto Settings keeps what was typed there", async () => { const boot = await bootOnAddress(); await boot.click("btn-settings"); await chooseNetwork(boot, "sepolia"); boot.node("settings-rpc").value = "https://typed.example"; await boot.click("btn-settings-add-token"); expect(boot.visibleViews()).toEqual(["settings-addtoken"]); await boot.click("btn-settings-addtoken-back"); expect(boot.visibleViews()).toEqual(["settings"]); expect(boot.value("settings-rpc")).toBe("https://typed.example"); }); });