fix: Send's balance line is drawn again when a refresh returns (closes #748)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run

Send opened at an address no refresh had read kept reading
"Current balance: unknown" after a refresh had read the balance, until the
screen was opened again. A token just added read the same way.

A refresh that returns while Send is up now runs updateSendBalance(). The
token list is not rebuilt, so the recipient, amount and token stay as the
user left them. README says so.

Model: opus-5-5
This commit is contained in:
2026-11-03 13:07:37 +00:00
parent 39c1ad09c0
commit 7ba6b30350
3 changed files with 65 additions and 3 deletions
+3 -1
View File
@@ -471,7 +471,9 @@ When "Back" returns to a screen that this page load has not drawn yet,
Home is shown instead. "Back" onto a screen already drawn only unhides it, so
unsaved input in Settings survives. Home is drawn again on every "Back". When a
balance refresh returns while AddressDetail or AddressToken is showing, that
screen draws its balances again.
screen draws its balances again. When a refresh returns while Send is showing,
Send draws its From address and current balance again. Its inputs and dropdown
stay as the user left them.
A network switch, or a change to "Use Blockscout", cuts the stack at the first
screen that cannot be drawn from saved state. The cut also drops AddressToken
+5 -2
View File
@@ -107,8 +107,10 @@ let refreshTimer = null;
// tracked token list stays as it is.
//
// Once the save is done, the wallet list is drawn again. So are the balances
// on AddressDetail or AddressToken when that screen is up. No other screen is
// drawn again, DeleteAddress and its balance lines included.
// on AddressDetail or AddressToken when that screen is up. When Send is up, its
// From address and current balance are drawn again, and its inputs and dropdown
// stay as the user left them. No other screen is drawn again, DeleteAddress and
// its balance lines included.
async function doRefreshAndRender() {
if (refreshInFlight) {
refreshAgain = true;
@@ -164,6 +166,7 @@ async function doRefreshAndRender() {
renderWalletList();
if (state.currentView === "address") addressDetail.renderBalances();
if (state.currentView === "address-token") addressToken.renderBalance();
if (state.currentView === "send") send.updateSendBalance();
} catch (e) {
// No caller waits for this function, so it must never reject. An
// unhandled rejection reports nothing. The save inside it reports its
+57
View File
@@ -19,6 +19,10 @@
// its ETH line reads the same, and Send states it as unknown
// (https://git.eeqj.de/sneak/AutistMask/issues/736).
//
// A refresh that returns while Send is up draws Send's balance line again, and
// leaves what the user entered as it is
// (https://git.eeqj.de/sneak/AutistMask/issues/748).
//
// Driven through the real popup over a stored profile on Ethereum Mainnet,
// with "Show tracked tokens with zero balance" on, as it is by default.
@@ -431,6 +435,59 @@ test("Send for the new token reads its balance as unknown before any refresh has
await finish([MILLION]);
});
test("Send for the new token reads the fetched balance once the refresh returns, without being reopened", async () => {
const page = await bootOnAddress();
const finish = holdNextRefresh(page);
await addNewToken(page);
await openToken(page, NEW_TOKEN.toLowerCase());
await page.click("btn-address-token-send");
expect(page.text("send-balance")).toBe("Current balance: unknown (NEW)");
await finish([MILLION]);
expect(page.visibleViews()).toEqual(["send"]);
expect(page.text("send-balance")).toBe("Current balance: 1000000.0000 NEW");
});
test("a refresh that returns while Send is up draws an unread ETH balance again and leaves the inputs alone", async () => {
const page = await bootPopup(profileNotRead());
// The dropdown's first option, which a browser selects by itself.
page.node("send-token").value = "ETH";
await page.click("btn-main-send");
expect(page.text("send-balance")).toBe("Current balance: unknown (ETH)");
const recipient = "0x" + "dd".repeat(20);
page.node("send-to").value = recipient;
page.node("send-amount").value = "0.5";
// Stands for the options on the screen, which only opening Send replaces.
page.node("send-token").innerHTML = "options on the screen";
await refreshOnce(page, []);
expect(page.visibleViews()).toEqual(["send"]);
expect(page.text("send-balance")).toBe("Current balance: 2.0000 ETH");
expect(page.value("send-to")).toBe(recipient);
expect(page.value("send-amount")).toBe("0.5");
expect(page.value("send-token")).toBe("ETH");
expect(page.node("send-token").innerHTML).toBe("options on the screen");
});
test("a refresh that returns while Send is up keeps the token picked in the dropdown and draws its new balance", async () => {
const page = await bootOnDerivingWallet();
const dropdown = page.node("send-token");
dropdown.value = "ETH";
await page.click("btn-main-send");
dropdown.value = NEW_TOKEN;
for (const fn of dropdown.listeners.change) await fn();
expect(page.text("send-balance")).toBe("Current balance: 1000000.0000 NEW");
page.node("send-amount").value = "0.5";
await refreshOnce(page, [{ ...MILLION, balance: "3000000.0" }]);
expect(page.text("send-balance")).toBe("Current balance: 3000000.0000 NEW");
expect(page.value("send-token")).toBe(NEW_TOKEN);
expect(page.value("send-amount")).toBe("0.5");
});
test("a token added by contract address on SettingsAddToken reads quantity unknown until the refresh returns", async () => {
const page = await bootPopup(unversionedValidProfile());
expect(page.visibleViews()).toEqual(["main"]);