From c150b83322e950d17b10be067fbc0e8a97a3a7df Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Tue, 3 Nov 2026 16:09:42 +0100 Subject: [PATCH] fix: Home's Send and Receive clear the selected token (closes #753) Three routes reach Home with a token still selected. Send used that token whatever its dropdown showed, and Receive warned about it. Both now clear it when they open, and Send's dropdown starts on ETH. Model: opus-5-5 --- README.md | 5 +- src/popup/views/home.js | 7 ++ tests/homeSelectedToken.test.js | 185 ++++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 tests/homeSelectedToken.test.js diff --git a/README.md b/README.md index fa3c430..6bb4aa9 100644 --- a/README.md +++ b/README.md @@ -581,8 +581,9 @@ Transitions: - "+": derives the next address in place. - `[info]`: AddressDetail. - `[x]`: DeleteAddress. -- "Send": Send. Refused with a flash message when the active address has a zero - ETH balance or belongs to a wallet carrying the `walletDefects.js` notice. +- "Send": Send, with its dropdown on ETH. Refused with a flash message when the + active address has a zero ETH balance or belongs to a wallet carrying the + `walletDefects.js` notice. - "Receive": Receive. - Tap a transaction row: TransactionDetail. - "Add additional wallet...": AddWallet. diff --git a/src/popup/views/home.js b/src/popup/views/home.js index c6c880f..e9cc2b2 100644 --- a/src/popup/views/home.js +++ b/src/popup/views/home.js @@ -510,11 +510,15 @@ function init(ctx) { showFlash("Cannot send \u2014 zero balance."); return; } + // Send uses a selected token ahead of its dropdown, and AddressToken + // can leave one selected on the way here. + state.selectedToken = null; $("send-to").value = ""; $("send-amount").value = ""; $("send-token-dropdown").classList.remove("hidden"); $("send-token-static").classList.add("hidden"); renderSendTokenSelect(addr); + $("send-token").value = "ETH"; updateSendBalance(); resetSendValidation(); pushCurrentView(); @@ -526,6 +530,9 @@ function init(ctx) { showFlash("No active address selected."); return; } + // Receive warns about a selected token, and AddressToken can leave one + // selected on the way here. + state.selectedToken = null; ctx.showReceive(); }); } diff --git a/tests/homeSelectedToken.test.js b/tests/homeSelectedToken.test.js new file mode 100644 index 0000000..3ad4d4e --- /dev/null +++ b/tests/homeSelectedToken.test.js @@ -0,0 +1,185 @@ +// Home's "Send" and "Receive" open with no token selected. Three routes reach +// Home with a token still selected, Send reads that token ahead of its dropdown, +// and Receive warns about it +// (https://git.eeqj.de/sneak/AutistMask/issues/753). Driven through the real +// popup over a stored profile. + +const { + bootPopup, + cleanupPopup, + unversionedValidProfile, + TOKEN_ADDRESS, +} = require("./support/popupBoot"); + +const RECIPIENT = "0x711e691962E5c054b78071C789A0C8CE657377aD"; +const OTHER_WALLET_ADDRESS = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; +const PRIVATE_KEY = "0x" + "11".repeat(32); +const PASSWORD = "correct horse battery staple"; +const TOKEN = { + address: TOKEN_ADDRESS, + symbol: "TST", + decimals: 18, + name: "Test Token", +}; + +afterEach(() => { + cleanupPopup(); +}); + +// AddressToken opened from AddressDetail, as tapping the token's row does. +async function openToken(page) { + const { state } = require("../src/shared/state"); + state.selectedToken = TOKEN_ADDRESS; + require("../src/popup/views/helpers").pushCurrentView(); + require("../src/popup/views/addressToken").show(); + await page.settle(); + expect(page.visibleViews()).toEqual(["address-token"]); +} + +// The popup open on AddressToken, at an address holding 1.5 ETH and 2.5 TST. +// A second wallet is there for the route that deletes one. +async function bootOnToken() { + const profile = unversionedValidProfile({ + currentView: "address", + viewStack: ["main"], + selectedWallet: 0, + selectedAddress: 0, + trackedTokens: [TOKEN], + }); + profile.wallets[0].addresses[0].tokenBalances = [ + { ...TOKEN, balance: "2.5" }, + ]; + profile.wallets.push({ + type: "hd", + name: "Wallet 2", + xpub: "xpub-wallet-2", + encryptedSecret: "encrypted-secret-2", + nextIndex: 1, + addresses: [ + { + address: OTHER_WALLET_ADDRESS, + balance: "0", + tokenBalances: [], + }, + ], + }); + const page = await bootPopup(profile); + await openToken(page); + return page; +} + +async function reopenAfterTokenSend() { + const closed = await bootOnToken(); + await closed.click("btn-address-token-send"); + cleanupPopup(); + return bootPopup(undefined, { storage: closed.storage }); +} + +async function addWalletFromTokenSettings() { + const page = await bootOnToken(); + await page.click("btn-settings"); + await page.click("btn-main-add-wallet"); + await page.click("tab-privkey"); + page.node("import-private-key").value = PRIVATE_KEY; + page.node("add-wallet-password").value = PASSWORD; + page.node("add-wallet-password-confirm").value = PASSWORD; + await page.click("btn-add-wallet-confirm"); + return page; +} + +// Deletes through the lost-password screen. It asks for the wallet's name, and +// the stored wallets here have no real password. +async function deleteWalletFromTokenSettings() { + const page = await bootOnToken(); + await page.click("btn-settings"); + // What the second wallet's [x] button in Settings does. + require("../src/popup/views/helpers").pushCurrentView(); + require("../src/popup/views/deleteWallet").show(1); + await page.click("btn-delete-wallet-lost-password"); + page.node("delete-wallet-lost-name-input").value = "Wallet 2"; + await page.click("btn-delete-wallet-lost-confirm"); + await page.click("btn-settings-back"); + return page; +} + +const ROUTES = [ + ["reopening the popup after AddressToken's Send", reopenAfterTokenSend], + [ + "the Settings gear on AddressToken, then adding a wallet", + addWalletFromTokenSettings, + ], + [ + "the Settings gear on AddressToken, then deleting another wallet, then Back", + deleteWalletFromTokenSettings, + ], +]; + +// A route that stopped reaching Home with the token selected would prove nothing +// in the tests below, so it fails here. +async function arriveOnHome(route) { + const page = await route(); + const { state } = require("../src/shared/state"); + expect(page.visibleViews()).toEqual(["main"]); + expect(state.selectedToken).toBe(TOKEN_ADDRESS); + return page; +} + +// ETH chosen in Send's dropdown, as the user does. +async function chooseEth(page) { + const dropdown = page.node("send-token"); + dropdown.value = "ETH"; + for (const fn of dropdown.listeners.change) await fn(); +} + +// Review for 1 of the token Send has, to RECIPIENT. +async function review(page) { + page.node("send-to").value = RECIPIENT; + page.node("send-amount").value = "1"; + await page.click("btn-send-review"); + expect(page.visibleViews()).toEqual(["confirm-tx"]); +} + +describe.each(ROUTES)("after %s", (_name, route) => { + test("choosing ETH on Home's Send reviews ETH", async () => { + const page = await arriveOnHome(route); + + await page.click("btn-main-send"); + await chooseEth(page); + await review(page); + + expect(page.text("confirm-type")).toBe("Native ETH transfer"); + expect(page.storage.read("autistmask").selectedToken).toBeNull(); + }); + + test("Home's Send starts on ETH", async () => { + const page = await arriveOnHome(route); + + await page.click("btn-main-send"); + + expect(page.text("send-balance")).toBe("Current balance: 1.5000 ETH"); + expect(page.value("send-token")).toBe("ETH"); + }); + + test("Home's Receive shows no token warning", async () => { + const page = await arriveOnHome(route); + + await page.click("btn-main-receive"); + + expect(page.visibleViews()).toEqual(["receive"]); + expect(page.text("receive-erc20-warning")).toBe(""); + expect(page.storage.read("autistmask").selectedToken).toBeNull(); + }); +}); + +test("AddressToken's Send still sends the token after Home's Send has opened", async () => { + const page = await arriveOnHome(deleteWalletFromTokenSettings); + await page.click("btn-main-send"); + await page.click("btn-send-back"); + await openToken(page); + + await page.click("btn-address-token-send"); + + expect(page.text("send-balance")).toBe("Current balance: 2.5000 TST"); + await review(page); + expect(page.text("confirm-type")).toBe("ERC-20 token transfer (TST)"); +});