// A popup that opened with no wallet never started the ten-second refresh, so // after the first wallet was added Home went on showing the balance it had then // until the popup was reopened // (https://git.eeqj.de/sneak/AutistMask/issues/530). Each test boots the real // popup, adds wallets through AddWallet, and runs the intervals the popup set // with tick(). The balance stand-in writes what the chain holds onto every // address, as the real refreshBalances() does. const { HDNodeWallet, Mnemonic } = require("ethers"); const { bootPopup, cleanupPopup, unversionedValidProfile, ADDRESS, } = require("./support/popupBoot"); const PHRASE = "abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon about"; const PRIVATE_KEY = "0x" + "11".repeat(32); const XPRV = HDNodeWallet.fromSeed( Mnemonic.fromPhrase(PHRASE).computeSeed(), ).extendedKey; const PASSWORD = "correct horse battery staple"; const TABS = [ { tab: "tab-mnemonic", field: "wallet-mnemonic", secret: PHRASE }, { tab: "tab-privkey", field: "import-private-key", secret: PRIVATE_KEY }, { tab: "tab-xprv", field: "import-xprv-key", secret: XPRV }, ]; // A record this build cannot read: its wallets are a string. const UNREADABLE = { hasWallet: true, wallets: ADDRESS, activeAddress: ADDRESS, }; // The ETH balance the chain holds for every address, in ether. let chainBalance = "0.0"; // Boots the popup over `stored`, with the balance stand-in reading the chain. async function boot(stored) { const page = await bootPopup(stored); const { refreshBalances } = require("../src/shared/balances"); refreshBalances.mockImplementation(async (wallets) => { for (const wallet of wallets) { for (const addr of wallet.addresses) addr.balance = chainBalance; } }); return { page, refreshBalances }; } // Fills in the open AddWallet screen and presses "Import". async function importOn(page, { tab, field, secret }) { await page.click(tab); page.node(field).value = secret; page.node("add-wallet-password").value = PASSWORD; page.node("add-wallet-password-confirm").value = PASSWORD; await page.click("btn-add-wallet-confirm"); } // Fake timers hold the timer of the flash message an import ends with, which // would otherwise fire after cleanupPopup() has removed the page and throw. beforeEach(() => { jest.useFakeTimers(); chainBalance = "0.0"; }); afterEach(() => { cleanupPopup(); jest.useRealTimers(); }); describe("a popup that opened on Welcome", () => { test.each(TABS)( "after the first wallet is added from $tab, funds that arrive show on Home", async (entry) => { const { page } = await boot(undefined); expect(page.pageErrors).toEqual([]); await page.click("btn-welcome-add"); await importOn(page, entry); expect(page.visibleViews()).toEqual(["main"]); expect(page.text("total-value")).toBe("0.0000 ETH"); chainBalance = "2.0"; await page.tick(); expect(page.text("total-value")).toBe("2.0000 ETH"); }, ); test("a second wallet starts no second refresh", async () => { const { page, refreshBalances } = await boot(undefined); await page.click("btn-welcome-add"); await importOn(page, TABS[1]); await page.click("btn-add-wallet-bottom"); await importOn(page, TABS[0]); const calls = refreshBalances.mock.calls.length; await page.tick(); expect(refreshBalances).toHaveBeenCalledTimes(calls + 1); }); test("a wallet added as the recovery screen goes up starts no refresh", async () => { const { page, refreshBalances } = await boot(undefined); // The stored record becomes unreadable during the address scan that // follows the import, and a save finds it. const { scanForAddresses } = require("../src/shared/balances"); const { saveState } = require("../src/shared/state"); scanForAddresses.mockImplementationOnce(async () => { page.storage.write("autistmask", UNREADABLE); await saveState().catch(() => {}); return { addresses: [], nextIndex: 1 }; }); await page.click("btn-welcome-add"); await importOn(page, TABS[0]); expect(page.visibleViews()).toEqual(["state-recovery"]); await page.tick(); expect(refreshBalances).not.toHaveBeenCalled(); }); }); describe("a popup that opened with a wallet", () => { test("adding a wallet starts no second refresh", async () => { const { page, refreshBalances } = await boot(unversionedValidProfile()); await page.click("btn-add-wallet-bottom"); await importOn(page, TABS[1]); const calls = refreshBalances.mock.calls.length; await page.tick(); expect(refreshBalances).toHaveBeenCalledTimes(calls + 1); }); });