fix: a save merges this page's tracked token change into the stored list (closes #717)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run

A save wrote `trackedTokens` whole, so two pages on one network dropped each
other's adds and brought back each other's removals.

A save that keeps the network now merges this page's change into the stored
list by contract address. A token both lists hold keeps this page's record.
Two pages that add one token can hold different records for it, and
`mergeListByIdentity()` alone would store both.

A save that switches networks is unchanged.

Model: opus-5-5
This commit is contained in:
2026-11-03 04:02:17 +00:00
parent 076726b13d
commit 798a1a36e7
3 changed files with 158 additions and 19 deletions
+102
View File
@@ -117,6 +117,17 @@ async function chooseNetwork(boot, networkId) {
await boot.settle();
}
// Adds NEW_TOKEN_ADDRESS by contract address on SettingsAddToken, as the user
// does. Returns the record the add tracks.
async function addNewToken(boot) {
const { lookupTokenInfo } = require("../src/shared/balances");
const info = { symbol: "NEW", decimals: 18, name: "New Token" };
lookupTokenInfo.mockResolvedValueOnce(info);
boot.node("settings-addtoken-address").value = NEW_TOKEN_ADDRESS;
await boot.click("btn-settings-addtoken-manual");
return { address: NEW_TOKEN_ADDRESS, ...info, balanceNotFetched: true };
}
// The Settings gear, a switch to Sepolia, and Back.
async function gearSwitchBack(boot) {
await boot.click("btn-settings");
@@ -685,6 +696,97 @@ test("a token removed while this page waits to take another page's switch is gon
expect(stored.networkTrackedTokens.mainnet).toEqual([]);
});
// Another popup page tracks DAI after this page's last save. This page then
// adds a token. Its save keeps DAI.
test("a token another page added stays when this page adds a token", async () => {
const boot = await bootOnAddToken("mainnet");
expect(boot.pageErrors).toEqual([]);
boot.storage.write("autistmask", {
...boot.storage.read("autistmask"),
trackedTokens: [MAINNET_TOKEN],
});
const added = await addNewToken(boot);
expect(boot.storage.read("autistmask").trackedTokens).toEqual([
MAINNET_TOKEN,
added,
]);
});
// Another popup page tracks a new token after this page's last save. This page
// then removes DAI, as the `[x]` on a tracked token in Settings does. Its save
// keeps the new token.
test("a token another page added stays when this page removes a token", async () => {
const boot = await bootPopup(
unversionedValidProfile({ trackedTokens: [MAINNET_TOKEN] }),
);
expect(boot.pageErrors).toEqual([]);
const otherPageToken = {
address: NEW_TOKEN_ADDRESS,
symbol: "NEW",
decimals: 18,
name: "New Token",
};
boot.storage.write("autistmask", {
...boot.storage.read("autistmask"),
trackedTokens: [MAINNET_TOKEN, otherPageToken],
});
const { state, saveState } = require("../src/shared/state");
state.trackedTokens.splice(0, 1);
await saveState();
expect(boot.storage.read("autistmask").trackedTokens).toEqual([
otherPageToken,
]);
});
// Another popup page stops tracking DAI after this page's last save. This page
// still holds DAI and adds a token. Its save does not store DAI again.
test("a token another page removed stays removed when this page adds a token", async () => {
const boot = await bootOnAddToken("mainnet", {
trackedTokens: [MAINNET_TOKEN],
});
expect(boot.pageErrors).toEqual([]);
boot.storage.write("autistmask", {
...boot.storage.read("autistmask"),
trackedTokens: [],
});
const added = await addNewToken(boot);
expect(boot.storage.read("autistmask").trackedTokens).toEqual([added]);
});
// Another popup page tracks DAI and the new token after this page's last save,
// and its refresh has read the new token's balance. This page adds the new token
// too, so the two pages hold different records for it. The save stores one
// record for the new token, this page's, and keeps DAI.
test("a token both pages added is stored once, with this page's record", async () => {
const boot = await bootOnAddToken("mainnet");
expect(boot.pageErrors).toEqual([]);
boot.storage.write("autistmask", {
...boot.storage.read("autistmask"),
trackedTokens: [
MAINNET_TOKEN,
{
address: NEW_TOKEN_ADDRESS,
symbol: "NEW",
decimals: 18,
name: "New Token",
},
],
});
const added = await addNewToken(boot);
expect(boot.storage.read("autistmask").trackedTokens).toEqual([
MAINNET_TOKEN,
added,
]);
});
describe("Back after a switch from the Settings gear", () => {
test("SettingsAddToken no longer offers the common tokens", async () => {
const boot = await bootOnAddToken("mainnet");