Files
AutistMask/tests/balanceRefreshNetworkSwitch.test.js
T
sneak 0375f83470
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s
fix: a balance refresh that returns after a network switch writes nothing (closes #577)
The popup's doRefreshAndRender() and the background's backgroundRefresh()
each note the network a refresh starts on, and write its balances only if
that network is still the active one when the requests return. Otherwise
nothing is written, lastBalanceRefresh stays as the switch left it, and the
next refresh runs on the new network. The background checks inside
updateState(), against the record it is about to write. The popup refreshes
a copy of its wallets and copies the results onto its address records, by
address, after the check, because refreshBalances() writes into the records
it is given as each request returns.

Model: opus-5-5
2026-10-20 18:24:48 +00:00

80 lines
2.8 KiB
JavaScript

// A balance refresh the popup started on one network, and that returns after
// the user switched to another in Settings, 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). 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");
afterEach(() => {
cleanupPopup();
});
// 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();
}
test("a refresh that returns after a switch 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");
// The ten-second refresh, held open until answer() is called. It writes
// Ethereum Mainnet's balances in place, on the records handed in, as the
// real refreshBalances() does.
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();
await page.click("btn-settings");
await chooseNetwork(page, "sepolia");
answer();
await ticking;
expect(state.wallets[0].addresses[0].balance).toBe("0");
expect(state.wallets[0].addresses[0].tokenBalances).toEqual([]);
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);
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,
);
});