A balance refresh that returns now draws the balances on AddressDetail or AddressToken again when that screen is up, not only the wallet list. Their transaction lists are left alone. Every add path on AddToken and SettingsAddToken tracks a token through trackToken(), which gives the tracked record `balanceNotFetched: true`. While the field is set, the token's line reads `quantity unknown` instead of 0.0000, and Send reads its balance as unknown. refreshBalances() now resolves with the tracked tokens whose balance it read at every address. The popup's refresh deletes the field from those tokens in the stored record and leaves the rest of the stored list as it is. Removing the token removes the field, and each network keeps its own records. Model: opus-5-5
139 lines
4.9 KiB
JavaScript
139 lines
4.9 KiB
JavaScript
// A contract lookup on AddToken can answer after the user has left AddToken.
|
|
// The token is still added, and the screen the user is on does not change
|
|
// (https://git.eeqj.de/sneak/AutistMask/issues/603). An answer while AddToken
|
|
// still shows goes to AddressDetail. Driven through the real popup over a
|
|
// stored profile.
|
|
|
|
const {
|
|
bootPopup,
|
|
cleanupPopup,
|
|
unversionedValidProfile,
|
|
TOKEN_ADDRESS,
|
|
} = require("./support/popupBoot");
|
|
|
|
const TOKEN = {
|
|
address: TOKEN_ADDRESS,
|
|
symbol: "TST",
|
|
decimals: 18,
|
|
name: "Test Token",
|
|
balanceNotFetched: true,
|
|
};
|
|
|
|
// A second contract address, typed after AddToken was opened again.
|
|
const OTHER_TOKEN_ADDRESS = "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
|
|
|
|
afterEach(() => {
|
|
cleanupPopup();
|
|
});
|
|
|
|
// The popup reopened onto AddressDetail, then AddToken opened from it with
|
|
// TOKEN's contract address typed in.
|
|
async function openAddToken() {
|
|
const boot = await bootPopup(
|
|
unversionedValidProfile({
|
|
currentView: "address",
|
|
selectedWallet: 0,
|
|
selectedAddress: 0,
|
|
}),
|
|
);
|
|
expect(boot.pageErrors).toEqual([]);
|
|
await boot.click("btn-add-token");
|
|
expect(boot.visibleViews()).toEqual(["add-token"]);
|
|
boot.node("add-token-address").value = TOKEN.address;
|
|
return boot;
|
|
}
|
|
|
|
// Presses Add with a contract lookup that answers only when the function
|
|
// returned here is called. That function waits for Add to finish.
|
|
function addWithPendingLookup(boot) {
|
|
const { lookupTokenInfo } = require("../src/shared/balances");
|
|
let answer;
|
|
lookupTokenInfo.mockImplementationOnce(
|
|
() =>
|
|
new Promise((resolve) => {
|
|
answer = resolve;
|
|
}),
|
|
);
|
|
const adding = boot.click("btn-add-token-confirm");
|
|
return async () => {
|
|
answer({
|
|
symbol: TOKEN.symbol,
|
|
decimals: TOKEN.decimals,
|
|
name: TOKEN.name,
|
|
});
|
|
await adding;
|
|
};
|
|
}
|
|
|
|
test("an answer after the user opened Settings adds the token and Settings stays", async () => {
|
|
const boot = await openAddToken();
|
|
const answerLookup = addWithPendingLookup(boot);
|
|
await boot.click("btn-settings");
|
|
expect(boot.visibleViews()).toEqual(["settings"]);
|
|
|
|
await answerLookup();
|
|
expect(boot.visibleViews()).toEqual(["settings"]);
|
|
expect(boot.storage.read("autistmask").trackedTokens).toEqual([TOKEN]);
|
|
});
|
|
|
|
test("Back from Settings onto AddToken after the lookup answered shows no lookup status", async () => {
|
|
const boot = await openAddToken();
|
|
const answerLookup = addWithPendingLookup(boot);
|
|
expect(boot.text("add-token-info")).toBe("Looking up token...");
|
|
await boot.click("btn-settings");
|
|
await answerLookup();
|
|
|
|
await boot.click("btn-settings-back");
|
|
expect(boot.visibleViews()).toEqual(["add-token"]);
|
|
expect(boot.text("add-token-info")).toBe("");
|
|
expect(boot.node("add-token-info").style.visibility).toBe("hidden");
|
|
});
|
|
|
|
test("an answer after AddToken was opened again adds the token and leaves AddToken and its status line as they are", async () => {
|
|
const boot = await openAddToken();
|
|
const answerFirstLookup = addWithPendingLookup(boot);
|
|
await boot.click("btn-add-token-back");
|
|
await boot.click("btn-add-token");
|
|
expect(boot.visibleViews()).toEqual(["add-token"]);
|
|
boot.node("add-token-address").value = OTHER_TOKEN_ADDRESS;
|
|
// The second lookup is never answered.
|
|
addWithPendingLookup(boot);
|
|
|
|
await answerFirstLookup();
|
|
expect(boot.visibleViews()).toEqual(["add-token"]);
|
|
expect(boot.text("add-token-info")).toBe("Looking up token...");
|
|
expect(boot.storage.read("autistmask").trackedTokens).toEqual([TOKEN]);
|
|
});
|
|
|
|
test("a lookup that fails after AddToken was opened again leaves the status line as it is", async () => {
|
|
const boot = await openAddToken();
|
|
const { lookupTokenInfo } = require("../src/shared/balances");
|
|
let failFirstLookup;
|
|
lookupTokenInfo.mockImplementationOnce(
|
|
() =>
|
|
new Promise((_, reject) => {
|
|
failFirstLookup = reject;
|
|
}),
|
|
);
|
|
const firstAdd = boot.click("btn-add-token-confirm");
|
|
await boot.click("btn-add-token-back");
|
|
await boot.click("btn-add-token");
|
|
boot.node("add-token-address").value = OTHER_TOKEN_ADDRESS;
|
|
// The second lookup is never answered.
|
|
addWithPendingLookup(boot);
|
|
|
|
failFirstLookup(new Error("lookup failed"));
|
|
await firstAdd;
|
|
expect(boot.text("add-token-info")).toBe("Looking up token...");
|
|
});
|
|
|
|
test("an answer while AddToken still shows goes to AddressDetail with the token listed", async () => {
|
|
const boot = await openAddToken();
|
|
const answerLookup = addWithPendingLookup(boot);
|
|
|
|
await answerLookup();
|
|
expect(boot.visibleViews()).toEqual(["address"]);
|
|
expect(boot.node("address-balances").innerHTML).toContain("TST");
|
|
expect(boot.storage.read("autistmask").trackedTokens).toEqual([TOKEN]);
|
|
});
|