From e0ccb1c8dec61cd03969a0bd24cf9fce558d29dc Mon Sep 17 00:00:00 2001 From: sneak Date: Tue, 3 Nov 2026 05:28:24 +0000 Subject: [PATCH] fix: a double click on a Top tokens button tracks the token once (closes #729) A "Top tokens:" button had no already-tracked check and stays enabled until its save ends, so a fast double click tracked the token twice in memory. Its handler now checks first. A second click shows "Already tracked." and tracks nothing. The top 100 dropdown already has this check, so it is unchanged. Model: opus-5-5 --- src/popup/views/settingsAddToken.js | 5 +++ tests/addTokenLateAnswer.test.js | 47 ++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/popup/views/settingsAddToken.js b/src/popup/views/settingsAddToken.js index 6a67947..2a39b39 100644 --- a/src/popup/views/settingsAddToken.js +++ b/src/popup/views/settingsAddToken.js @@ -45,6 +45,11 @@ function renderTop10() { el.querySelectorAll(".settings-addtoken-quick:not([disabled])").forEach( (btn) => { btn.addEventListener("click", async () => { + // The button stays enabled until the first click's save ends. + if (isTracked(btn.dataset.address)) { + showFlash("Already tracked."); + return; + } const token = { address: btn.dataset.address, symbol: btn.dataset.symbol, diff --git a/tests/addTokenLateAnswer.test.js b/tests/addTokenLateAnswer.test.js index b8123a6..b8db174 100644 --- a/tests/addTokenLateAnswer.test.js +++ b/tests/addTokenLateAnswer.test.js @@ -3,7 +3,9 @@ // (https://git.eeqj.de/sneak/AutistMask/issues/603). An answer while AddToken // still shows goes to AddressDetail. On either add-token screen, an answer for a // token tracked while the lookup waited tracks nothing -// (https://git.eeqj.de/sneak/AutistMask/issues/727). Driven through the real +// (https://git.eeqj.de/sneak/AutistMask/issues/727). A second click on a "Top +// tokens:" button tracks nothing +// (https://git.eeqj.de/sneak/AutistMask/issues/729). Driven through the real // popup over a stored profile. const { @@ -197,6 +199,8 @@ describe.each([ await answerSecondClick(); expect(boot.text("flash-msg")).toBe(alreadyTrackedFlash); expect(boot.storage.read("autistmask").trackedTokens).toEqual([TOKEN]); + const { state } = require("../src/shared/state"); + expect(state.trackedTokens).toEqual([TOKEN]); }); test("an answer for a token tracked while the lookup waited clears the status line", async () => { @@ -213,3 +217,44 @@ describe.each([ expect(state.trackedTokens).toEqual([TOKEN]); }); }); + +test('a double click on a "Top tokens:" button tracks the token once', async () => { + const boot = await bootPopup( + unversionedValidProfile({ + currentView: "address", + selectedWallet: 0, + selectedAddress: 0, + }), + ); + expect(boot.pageErrors).toEqual([]); + // The page stub does not turn the markup the screen draws into elements. + // So the list hands the screen a stand-in for TOKEN's button. + let press; + boot.node("settings-addtoken-top10").querySelectorAll = () => [ + { + dataset: { + address: TOKEN.address, + symbol: TOKEN.symbol, + decimals: String(TOKEN.decimals), + name: TOKEN.name, + }, + addEventListener: (name, fn) => { + press = fn; + }, + }, + ]; + await boot.click("btn-settings"); + await boot.click("btn-settings-add-token"); + + const firstClick = press(); + const secondClick = press(); + const { state } = require("../src/shared/state"); + expect(state.trackedTokens).toEqual([TOKEN]); + // Read before the first click's save ends, when "Added TST" replaces it. + expect(boot.text("flash-msg")).toBe("Already tracked."); + + await Promise.all([firstClick, secondClick]); + await boot.settle(); + expect(state.trackedTokens).toEqual([TOKEN]); + expect(boot.storage.read("autistmask").trackedTokens).toEqual([TOKEN]); +}); -- 2.54.0