Compare commits
1
Commits
next
...
b6bce56b07
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6bce56b07 |
+17
-4
@@ -64,8 +64,8 @@ window.addEventListener("pagehide", () => pageClosed.abort());
|
|||||||
|
|
||||||
let refreshInFlight = false;
|
let refreshInFlight = false;
|
||||||
|
|
||||||
// The ten-second refresh init() starts, stopped when the popup moves to the
|
// The ten-second refresh startRefresh() starts, stopped when the popup moves to
|
||||||
// recovery screen: there is no profile left to refresh.
|
// the recovery screen: there is no profile left to refresh.
|
||||||
let refreshTimer = null;
|
let refreshTimer = null;
|
||||||
|
|
||||||
async function doRefreshAndRender() {
|
async function doRefreshAndRender() {
|
||||||
@@ -99,9 +99,23 @@ async function doRefreshAndRender() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refreshes now and every ten seconds from then on. Called once a wallet
|
||||||
|
// exists: by init() when the popup opens with one, and by AddWallet when it
|
||||||
|
// adds one, which in a popup that opened on Welcome is the first
|
||||||
|
// (https://git.eeqj.de/sneak/AutistMask/issues/530). With the refresh already
|
||||||
|
// running it starts no second one, and on the recovery screen it does nothing.
|
||||||
|
function startRefresh() {
|
||||||
|
if (!$("view-state-recovery").classList.contains("hidden")) return;
|
||||||
|
doRefreshAndRender();
|
||||||
|
if (refreshTimer === null) {
|
||||||
|
refreshTimer = setInterval(doRefreshAndRender, 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ctx = {
|
const ctx = {
|
||||||
renderWalletList,
|
renderWalletList,
|
||||||
doRefreshAndRender,
|
doRefreshAndRender,
|
||||||
|
startRefresh,
|
||||||
pageClosed: pageClosed.signal,
|
pageClosed: pageClosed.signal,
|
||||||
showAddWalletView: () => {
|
showAddWalletView: () => {
|
||||||
pushCurrentView();
|
pushCurrentView();
|
||||||
@@ -318,8 +332,7 @@ async function init() {
|
|||||||
} else {
|
} else {
|
||||||
renderWalletList();
|
renderWalletList();
|
||||||
restoreView();
|
restoreView();
|
||||||
doRefreshAndRender();
|
startRefresh();
|
||||||
refreshTimer = setInterval(doRefreshAndRender, 10000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ async function importMnemonic(ctx) {
|
|||||||
showFlash("Ready.", 1000);
|
showFlash("Ready.", 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.doRefreshAndRender();
|
ctx.startRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function importPrivateKey(ctx) {
|
async function importPrivateKey(ctx) {
|
||||||
@@ -274,7 +274,7 @@ async function importPrivateKey(ctx) {
|
|||||||
ctx.renderWalletList();
|
ctx.renderWalletList();
|
||||||
showView("main");
|
showView("main");
|
||||||
|
|
||||||
ctx.doRefreshAndRender();
|
ctx.startRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function importXprvKey(ctx) {
|
async function importXprvKey(ctx) {
|
||||||
@@ -368,7 +368,7 @@ async function importXprvKey(ctx) {
|
|||||||
showFlash("Ready.", 1000);
|
showFlash("Ready.", 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.doRefreshAndRender();
|
ctx.startRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(ctx) {
|
function init(ctx) {
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ beforeEach(() => {
|
|||||||
|
|
||||||
addWallet.init({
|
addWallet.init({
|
||||||
renderWalletList: () => {},
|
renderWalletList: () => {},
|
||||||
doRefreshAndRender: () => {},
|
startRefresh: () => {},
|
||||||
pageClosed: new AbortController().signal,
|
pageClosed: new AbortController().signal,
|
||||||
});
|
});
|
||||||
confirmTx.init({});
|
confirmTx.init({});
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ describe.each([
|
|||||||
async function create(pageClosed) {
|
async function create(pageClosed) {
|
||||||
addWallet.init({
|
addWallet.init({
|
||||||
renderWalletList: () => {},
|
renderWalletList: () => {},
|
||||||
doRefreshAndRender: () => {},
|
startRefresh: () => {},
|
||||||
pageClosed,
|
pageClosed,
|
||||||
});
|
});
|
||||||
element("tab-" + mode).listeners.click();
|
element("tab-" + mode).listeners.click();
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
// A popup that opened with no wallet never started the ten-second refresh, so
|
||||||
|
// after the first wallet was added Home went on showing the balance it had then
|
||||||
|
// until the popup was reopened
|
||||||
|
// (https://git.eeqj.de/sneak/AutistMask/issues/530). Each test boots the real
|
||||||
|
// popup, adds wallets through AddWallet, and runs the intervals the popup set
|
||||||
|
// with tick(). The balance stand-in writes what the chain holds onto every
|
||||||
|
// address, as the real refreshBalances() does.
|
||||||
|
|
||||||
|
const { HDNodeWallet, Mnemonic } = require("ethers");
|
||||||
|
|
||||||
|
const {
|
||||||
|
bootPopup,
|
||||||
|
cleanupPopup,
|
||||||
|
unversionedValidProfile,
|
||||||
|
ADDRESS,
|
||||||
|
} = require("./support/popupBoot");
|
||||||
|
|
||||||
|
const PHRASE =
|
||||||
|
"abandon abandon abandon abandon abandon abandon " +
|
||||||
|
"abandon abandon abandon abandon abandon about";
|
||||||
|
const PRIVATE_KEY = "0x" + "11".repeat(32);
|
||||||
|
const XPRV = HDNodeWallet.fromSeed(
|
||||||
|
Mnemonic.fromPhrase(PHRASE).computeSeed(),
|
||||||
|
).extendedKey;
|
||||||
|
const PASSWORD = "correct horse battery staple";
|
||||||
|
|
||||||
|
const TABS = [
|
||||||
|
{ tab: "tab-mnemonic", field: "wallet-mnemonic", secret: PHRASE },
|
||||||
|
{ tab: "tab-privkey", field: "import-private-key", secret: PRIVATE_KEY },
|
||||||
|
{ tab: "tab-xprv", field: "import-xprv-key", secret: XPRV },
|
||||||
|
];
|
||||||
|
|
||||||
|
// A record this build cannot read: its wallets are a string.
|
||||||
|
const UNREADABLE = {
|
||||||
|
hasWallet: true,
|
||||||
|
wallets: ADDRESS,
|
||||||
|
activeAddress: ADDRESS,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The ETH balance the chain holds for every address, in ether.
|
||||||
|
let chainBalance = "0.0";
|
||||||
|
|
||||||
|
// Boots the popup over `stored`, with the balance stand-in reading the chain.
|
||||||
|
async function boot(stored) {
|
||||||
|
const page = await bootPopup(stored);
|
||||||
|
const { refreshBalances } = require("../src/shared/balances");
|
||||||
|
refreshBalances.mockImplementation(async (wallets) => {
|
||||||
|
for (const wallet of wallets) {
|
||||||
|
for (const addr of wallet.addresses) addr.balance = chainBalance;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { page, refreshBalances };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fills in the open AddWallet screen and presses "Import".
|
||||||
|
async function importOn(page, { tab, field, secret }) {
|
||||||
|
await page.click(tab);
|
||||||
|
page.node(field).value = secret;
|
||||||
|
page.node("add-wallet-password").value = PASSWORD;
|
||||||
|
page.node("add-wallet-password-confirm").value = PASSWORD;
|
||||||
|
await page.click("btn-add-wallet-confirm");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fake timers hold the timer of the flash message an import ends with, which
|
||||||
|
// would otherwise fire after cleanupPopup() has removed the page and throw.
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
chainBalance = "0.0";
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupPopup();
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("a popup that opened on Welcome", () => {
|
||||||
|
test.each(TABS)(
|
||||||
|
"after the first wallet is added from $tab, funds that arrive show on Home",
|
||||||
|
async (entry) => {
|
||||||
|
const { page } = await boot(undefined);
|
||||||
|
expect(page.pageErrors).toEqual([]);
|
||||||
|
await page.click("btn-welcome-add");
|
||||||
|
await importOn(page, entry);
|
||||||
|
expect(page.visibleViews()).toEqual(["main"]);
|
||||||
|
expect(page.text("total-value")).toBe("0.0000 ETH");
|
||||||
|
|
||||||
|
chainBalance = "2.0";
|
||||||
|
await page.tick();
|
||||||
|
|
||||||
|
expect(page.text("total-value")).toBe("2.0000 ETH");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test("a second wallet starts no second refresh", async () => {
|
||||||
|
const { page, refreshBalances } = await boot(undefined);
|
||||||
|
await page.click("btn-welcome-add");
|
||||||
|
await importOn(page, TABS[1]);
|
||||||
|
await page.click("btn-add-wallet-bottom");
|
||||||
|
await importOn(page, TABS[0]);
|
||||||
|
const calls = refreshBalances.mock.calls.length;
|
||||||
|
|
||||||
|
await page.tick();
|
||||||
|
|
||||||
|
expect(refreshBalances).toHaveBeenCalledTimes(calls + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a wallet added as the recovery screen goes up starts no refresh", async () => {
|
||||||
|
const { page, refreshBalances } = await boot(undefined);
|
||||||
|
// The stored record becomes unreadable during the address scan that
|
||||||
|
// follows the import, and a save finds it.
|
||||||
|
const { scanForAddresses } = require("../src/shared/balances");
|
||||||
|
const { saveState } = require("../src/shared/state");
|
||||||
|
scanForAddresses.mockImplementationOnce(async () => {
|
||||||
|
page.storage.write("autistmask", UNREADABLE);
|
||||||
|
await saveState().catch(() => {});
|
||||||
|
return { addresses: [], nextIndex: 1 };
|
||||||
|
});
|
||||||
|
await page.click("btn-welcome-add");
|
||||||
|
await importOn(page, TABS[0]);
|
||||||
|
expect(page.visibleViews()).toEqual(["state-recovery"]);
|
||||||
|
|
||||||
|
await page.tick();
|
||||||
|
|
||||||
|
expect(refreshBalances).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("a popup that opened with a wallet", () => {
|
||||||
|
test("adding a wallet starts no second refresh", async () => {
|
||||||
|
const { page, refreshBalances } = await boot(unversionedValidProfile());
|
||||||
|
await page.click("btn-add-wallet-bottom");
|
||||||
|
await importOn(page, TABS[1]);
|
||||||
|
const calls = refreshBalances.mock.calls.length;
|
||||||
|
|
||||||
|
await page.tick();
|
||||||
|
|
||||||
|
expect(refreshBalances).toHaveBeenCalledTimes(calls + 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user