// A balance refresh the popup started on one network, and that returns after // the network was switched to another, writes nothing: the switch cleared every // address's balances, and the previous network's must not come back over them // (https://git.eeqj.de/sneak/AutistMask/issues/577). The switch is made either // by the user in this popup's Settings, or by the background after the user // approved a site's request, which changes only the stored record. Driven // through the real popup over a stored profile on Ethereum Mainnet. The same // refresh in the background is in tests/backgroundStateIsolation.test.js. const { bootPopup, cleanupPopup, unversionedValidProfile, TOKEN_ADDRESS, } = require("./support/popupBoot"); const { applyChainSwitchFields } = require("../src/shared/chainSwitchFields"); afterEach(() => { cleanupPopup(); }); // Starts the ten-second refresh and holds it open. The balance stand-in writes // Ethereum Mainnet's balances in place, on the records handed in, as the real // refreshBalances() does, once the returned function is called. Resolves with // that function and the refresh still running. async function holdRefresh(page) { const { refreshBalances } = require("../src/shared/balances"); let answer; refreshBalances.mockImplementationOnce(async (wallets) => { await new Promise((resolve) => { answer = resolve; }); wallets[0].addresses[0].balance = "7.0"; wallets[0].addresses[0].tokenBalances = [ { address: TOKEN_ADDRESS, name: "Test Token", symbol: "TST", decimals: 18, balance: "5.0", holders: null, }, ]; }); const ticking = page.tick(); await page.settle(); return { finish: async () => { answer(); await ticking; }, }; } // Picks `networkId` in the Settings network selector, as the user does. async function chooseNetwork(page, networkId) { const select = page.node("settings-network"); select.value = networkId; for (const fn of select.listeners.change) await fn(); await page.settle(); } // The stored record holds none of Ethereum Mainnet's balances, and no refresh // recorded as done. function expectNothingStored(page) { const stored = page.storage.read("autistmask"); expect(stored.networkId).toBe("sepolia"); expect(stored.wallets[0].addresses[0].balance).toBe("0"); expect(stored.wallets[0].addresses[0].tokenBalances).toEqual([]); expect(stored.lastBalanceRefresh).toBe(0); } test("a refresh that returns after a switch in Settings writes nothing, and the next one runs on the new network", async () => { const page = await bootPopup(unversionedValidProfile()); expect(page.pageErrors).toEqual([]); const { refreshBalances } = require("../src/shared/balances"); const { state } = require("../src/shared/state"); const refresh = await holdRefresh(page); await page.click("btn-settings"); await chooseNetwork(page, "sepolia"); await refresh.finish(); expect(state.wallets[0].addresses[0].balance).toBe("0"); expect(state.wallets[0].addresses[0].tokenBalances).toEqual([]); expectNothingStored(page); await page.click("btn-settings-back"); expect(page.visibleViews()).toEqual(["main"]); expect(page.text("total-value")).toBe("0.0000 SepoliaETH"); await page.tick(); expect(refreshBalances.mock.lastCall[4]).toBe("sepolia"); expect(page.storage.read("autistmask").lastBalanceRefresh).toBeGreaterThan( 0, ); }); test("a refresh that returns after the background switched the network writes nothing", async () => { const page = await bootPopup(unversionedValidProfile()); expect(page.pageErrors).toEqual([]); const { saveState } = require("../src/shared/state"); const refresh = await holdRefresh(page); // The background writes the switch to Sepolia, as its updateState() does // once the user approves a site's request. This page is not told. const record = page.storage.read("autistmask"); applyChainSwitchFields(record, "sepolia"); page.storage.write("autistmask", record); await refresh.finish(); expectNothingStored(page); // Nor does a later save by this page, such as every navigation makes. await saveState(); expectNothingStored(page); });