fix: Home's list drops the previous network's rows after a network switch (closes #718)
A network switch left the previous network's rows or failure message in Home's list until the new network's answer landed. Home now replaces them with "Loading..." when it first draws on a new network. A redraw on the same network keeps its rows. Home, AddressDetail and AddressToken compared the page's network before reading storage. A switch made in Settings could change the page during that read, and the answer was still drawn. They now compare after the read, for an answer and for a failure. Model: opus-5-5
This commit was merged in pull request #738.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
// Home, AddressDetail and AddressToken drop a transaction history answer that
|
||||
// lands after a network switch. A dropped answer draws no rows and saves no
|
||||
// fraud contracts (https://git.eeqj.de/sneak/AutistMask/issues/657).
|
||||
// fraud contracts (https://git.eeqj.de/sneak/AutistMask/issues/657). Home's list
|
||||
// shows nothing of the previous network once it is drawn after a switch
|
||||
// (https://git.eeqj.de/sneak/AutistMask/issues/718).
|
||||
//
|
||||
// Most tests hold the history request open, switch the network or what the
|
||||
// screen shows, and then let the answer land. The views run against a minimal
|
||||
@@ -154,6 +156,21 @@ function holdLock() {
|
||||
};
|
||||
}
|
||||
|
||||
// Every read of storage waits until the returned function is called. Each read
|
||||
// then answers with what storage holds at that moment, the oldest read first.
|
||||
function holdReads() {
|
||||
const read = storage.local.get;
|
||||
const waiting = [];
|
||||
storage.local.get = (key) =>
|
||||
new Promise((resolve) => {
|
||||
waiting.push(() => resolve(read(key)));
|
||||
});
|
||||
return () => {
|
||||
storage.local.get = read;
|
||||
for (const answer of waiting) answer();
|
||||
};
|
||||
}
|
||||
|
||||
function makeElement(id) {
|
||||
const el = {
|
||||
id,
|
||||
@@ -409,6 +426,123 @@ describe.each([HOME, ADDRESS_DETAIL, ADDRESS_TOKEN])("$name", (screen) => {
|
||||
expect(drawn).toContain("tx-row");
|
||||
expect(node(screen.listId).innerHTML).toBe(drawn);
|
||||
});
|
||||
|
||||
// The user picks Sepolia in Settings, and the switch reads storage. The
|
||||
// answer lands during that read, so the screen's own read of storage still
|
||||
// finds Ethereum Mainnet. The switch changes the page before the screen's
|
||||
// read ends. The answer is the transfer of ETH alone, so nothing is saved,
|
||||
// and only a check after the screen's read can drop the answer.
|
||||
test("an answer that lands during the first read of a switch made in Settings adds nothing", async () => {
|
||||
const land = holdHistory(history().slice(0, 1));
|
||||
screen.open();
|
||||
await settle();
|
||||
const before = node(screen.listId).innerHTML;
|
||||
const answerReads = holdReads();
|
||||
const switching = onChainSwitch("sepolia");
|
||||
await settle();
|
||||
land();
|
||||
await settle();
|
||||
answerReads();
|
||||
await switching;
|
||||
await settle();
|
||||
|
||||
expectNothingAdded(screen, before);
|
||||
});
|
||||
|
||||
test("a request that fails during the first read of a switch made in Settings shows no message", async () => {
|
||||
const fail = holdFailure();
|
||||
screen.open();
|
||||
await settle();
|
||||
const before = node(screen.listId).innerHTML;
|
||||
const answerReads = holdReads();
|
||||
const switching = onChainSwitch("sepolia");
|
||||
await settle();
|
||||
fail();
|
||||
await settle();
|
||||
answerReads();
|
||||
await switching;
|
||||
await settle();
|
||||
|
||||
expect(node(screen.listId).innerHTML).toBe(before);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Home's list", () => {
|
||||
test.each([
|
||||
{ what: "rows", hold: holdHistory, shown: "tx-row" },
|
||||
{
|
||||
what: "failure message",
|
||||
hold: holdFailure,
|
||||
shown: "Failed to load transactions.",
|
||||
},
|
||||
])(
|
||||
"shows the previous network's $what neither before nor after the new network's answer lands",
|
||||
async ({ hold, shown }) => {
|
||||
const answer = hold();
|
||||
HOME.open();
|
||||
answer();
|
||||
await settle();
|
||||
expect(node(HOME.listId).innerHTML).toContain(shown);
|
||||
|
||||
await onChainSwitch("sepolia");
|
||||
const land = holdHistory([]);
|
||||
HOME.open();
|
||||
await settle();
|
||||
expect(node(HOME.listId).innerHTML).toContain("Loading...");
|
||||
|
||||
land();
|
||||
await settle();
|
||||
expect(node(HOME.listId).innerHTML).toContain(
|
||||
"No transactions found.",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// The user leaves Ethereum Mainnet with a request waiting, comes back, and
|
||||
// that request lands. The list then holds Ethereum Mainnet's answer, so the
|
||||
// next draw on Sepolia must replace it.
|
||||
test.each([
|
||||
{ what: "rows", hold: holdHistory },
|
||||
{ what: "failure message", hold: holdFailure },
|
||||
])(
|
||||
"shows no $what of an older request that lands after a switch back",
|
||||
async ({ hold }) => {
|
||||
const answer = hold();
|
||||
HOME.open();
|
||||
|
||||
await onChainSwitch("sepolia");
|
||||
holdHistory();
|
||||
HOME.open();
|
||||
|
||||
await onChainSwitch("mainnet");
|
||||
answer();
|
||||
await settle();
|
||||
|
||||
await onChainSwitch("sepolia");
|
||||
holdHistory();
|
||||
HOME.open();
|
||||
await settle();
|
||||
|
||||
const list = node(HOME.listId).innerHTML;
|
||||
expect(list).toContain("Loading...");
|
||||
expect(list).not.toContain("tx-row");
|
||||
expect(list).not.toContain("Failed to load transactions.");
|
||||
},
|
||||
);
|
||||
|
||||
// Each balance refresh draws Home again, so the rows must not blink.
|
||||
test("keeps its rows while a redraw on the same network waits for the answer", async () => {
|
||||
fetchRecentTransactions.mockImplementationOnce(async () => history());
|
||||
HOME.open();
|
||||
await settle();
|
||||
const drawn = node(HOME.listId).innerHTML;
|
||||
holdHistory();
|
||||
HOME.open();
|
||||
await settle();
|
||||
|
||||
expect(drawn).toContain("tx-row");
|
||||
expect(node(HOME.listId).innerHTML).toBe(drawn);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([ADDRESS_DETAIL, ADDRESS_TOKEN])("$name", (screen) => {
|
||||
|
||||
Reference in New Issue
Block a user