fix: a refresh asked for while another runs is no longer dropped (closes #581)
doRefreshAndRender() returned at once when a refresh was already running. An import asks for a refresh after its address scan, so the addresses the scan found showed 0.0000 until the next ten-second refresh. A request made while a refresh runs now sets a flag. One more refresh runs once the running one ends. Several requests in that time lead to one more refresh, not several. A tick of the ten-second timer during a refresh counts as such a request too. Model: opus-5-5
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
// A refresh asked for while another runs is not dropped. One more refresh runs
|
||||
// once the running one ends, however many were asked for in that time
|
||||
// (https://git.eeqj.de/sneak/AutistMask/issues/581). Each test boots the real
|
||||
// popup over a stored profile and holds the ten-second refresh open. The
|
||||
// balance stand-in writes what the chain holds onto every address, as the real
|
||||
// refreshBalances() does.
|
||||
|
||||
const {
|
||||
bootPopup,
|
||||
cleanupPopup,
|
||||
unversionedValidProfile,
|
||||
ADDRESS,
|
||||
} = require("./support/popupBoot");
|
||||
const {
|
||||
hdWalletFromMnemonic,
|
||||
deriveAddressFromXpub,
|
||||
} = require("../src/shared/wallet");
|
||||
|
||||
const PHRASE =
|
||||
"abandon abandon abandon abandon abandon abandon " +
|
||||
"abandon abandon abandon abandon abandon about";
|
||||
const PASSWORD = "correct horse battery staple";
|
||||
|
||||
// The first two addresses of PHRASE, both used on the chain.
|
||||
const { xpub, firstAddress } = hdWalletFromMnemonic(PHRASE);
|
||||
const SECOND_ADDRESS = deriveAddressFromXpub(xpub, 1);
|
||||
|
||||
// The ETH balance the chain holds for each address, in ether.
|
||||
const CHAIN = {
|
||||
[ADDRESS]: "1.5",
|
||||
[firstAddress]: "2.0",
|
||||
[SECOND_ADDRESS]: "3.0",
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
cleanupPopup();
|
||||
});
|
||||
|
||||
// The balance stand-in for a refresh that returns.
|
||||
async function readChain(wallets) {
|
||||
for (const wallet of wallets) {
|
||||
for (const addr of wallet.addresses) {
|
||||
addr.balance = CHAIN[addr.address];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Boots the popup over a profile with one wallet. From then on, a refresh the
|
||||
// test does not answer never returns. So a refresh that starts another on its
|
||||
// own is counted and stops, rather than running forever.
|
||||
async function boot() {
|
||||
const page = await bootPopup(unversionedValidProfile());
|
||||
expect(page.pageErrors).toEqual([]);
|
||||
const { refreshBalances } = require("../src/shared/balances");
|
||||
refreshBalances.mockImplementation(() => new Promise(() => {}));
|
||||
return { page, refreshBalances };
|
||||
}
|
||||
|
||||
// Starts the ten-second refresh and holds it open until finish() is called.
|
||||
// The held refresh then writes the chain's balances. finish() resolves once
|
||||
// the held refresh has saved them and drawn Home. The one refresh after the
|
||||
// held one also returns.
|
||||
async function holdRefresh(page) {
|
||||
const { refreshBalances } = require("../src/shared/balances");
|
||||
let answer;
|
||||
refreshBalances
|
||||
.mockImplementationOnce(async (wallets) => {
|
||||
await new Promise((resolve) => {
|
||||
answer = resolve;
|
||||
});
|
||||
await readChain(wallets);
|
||||
})
|
||||
.mockImplementationOnce(readChain);
|
||||
const ticking = page.tick();
|
||||
await page.settle();
|
||||
return {
|
||||
finish: async () => {
|
||||
answer();
|
||||
await ticking;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Imports PHRASE on AddWallet. Its address scan finds the phrase's first two
|
||||
// addresses.
|
||||
async function importPhrase(page) {
|
||||
const { scanForAddresses } = require("../src/shared/balances");
|
||||
scanForAddresses.mockImplementationOnce(async () => ({
|
||||
addresses: [
|
||||
{ address: firstAddress, index: 0 },
|
||||
{ address: SECOND_ADDRESS, index: 1 },
|
||||
],
|
||||
nextIndex: 2,
|
||||
}));
|
||||
await page.click("btn-add-wallet-bottom");
|
||||
await page.click("tab-mnemonic");
|
||||
page.node("wallet-mnemonic").value = PHRASE;
|
||||
page.node("add-wallet-password").value = PASSWORD;
|
||||
page.node("add-wallet-password-confirm").value = PASSWORD;
|
||||
await page.click("btn-add-wallet-confirm");
|
||||
}
|
||||
|
||||
test("addresses an import finds while a refresh runs show their balances once one more refresh returns", async () => {
|
||||
const { page } = await boot();
|
||||
const refresh = await holdRefresh(page);
|
||||
|
||||
await importPhrase(page);
|
||||
expect(page.visibleViews()).toEqual(["main"]);
|
||||
expect(page.text("flash-msg")).toBe("Found 2 addresses.");
|
||||
|
||||
await refresh.finish();
|
||||
await page.settle();
|
||||
|
||||
const list = page.node("wallet-list").innerHTML;
|
||||
expect(list).toContain("<span>2.0000</span>");
|
||||
expect(list).toContain("<span>3.0000</span>");
|
||||
});
|
||||
|
||||
test("several refreshes asked for while one runs lead to exactly one more", async () => {
|
||||
const { page, refreshBalances } = await boot();
|
||||
const refresh = await holdRefresh(page);
|
||||
const calls = refreshBalances.mock.calls.length;
|
||||
|
||||
await page.tick();
|
||||
await page.tick();
|
||||
await importPhrase(page);
|
||||
expect(refreshBalances).toHaveBeenCalledTimes(calls);
|
||||
|
||||
await refresh.finish();
|
||||
await page.settle();
|
||||
|
||||
expect(refreshBalances).toHaveBeenCalledTimes(calls + 1);
|
||||
});
|
||||
Reference in New Issue
Block a user