The "already tracked" check ran only before the contract lookup, so a second click while the first lookup waited tracked the token twice. AddToken and SettingsAddToken now check again when the lookup answers. An answer for a token tracked meanwhile shows the "already tracked" flash message and tracks nothing. Model: opus-5-5
216 lines
7.9 KiB
JavaScript
216 lines
7.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. On either add-token screen, an answer for a
|
|
// token tracked while the lookup waited tracks nothing
|
|
// (https://git.eeqj.de/sneak/AutistMask/issues/727). 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 `button` with a contract lookup that answers only when the function
|
|
// returned here is called. That function waits for the add to finish.
|
|
function addWithPendingLookup(boot, button = "btn-add-token-confirm") {
|
|
const { lookupTokenInfo } = require("../src/shared/balances");
|
|
let answer;
|
|
lookupTokenInfo.mockImplementationOnce(
|
|
() =>
|
|
new Promise((resolve) => {
|
|
answer = resolve;
|
|
}),
|
|
);
|
|
const adding = boot.click(button);
|
|
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]);
|
|
});
|
|
|
|
test("an answer for a token tracked after AddToken was opened again leaves the status line as it is", async () => {
|
|
const boot = await openAddToken();
|
|
const answerFirstLookup = addWithPendingLookup(boot);
|
|
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);
|
|
require("../src/popup/views/helpers").trackToken(TOKEN);
|
|
|
|
await answerFirstLookup();
|
|
expect(boot.text("flash-msg")).toBe("TST is already being tracked.");
|
|
expect(boot.text("add-token-info")).toBe("Looking up token...");
|
|
});
|
|
|
|
// Each screen is opened from AddressDetail by the buttons named, in order.
|
|
describe.each([
|
|
[
|
|
"AddToken",
|
|
["btn-add-token"],
|
|
"add-token-address",
|
|
"btn-add-token-confirm",
|
|
"add-token-info",
|
|
"TST is already being tracked.",
|
|
],
|
|
[
|
|
"SettingsAddToken",
|
|
["btn-settings", "btn-settings-add-token"],
|
|
"settings-addtoken-address",
|
|
"btn-settings-addtoken-manual",
|
|
"settings-addtoken-info",
|
|
"Already tracked.",
|
|
],
|
|
])("%s", (_, opening, field, button, statusLine, alreadyTrackedFlash) => {
|
|
// The screen open with TOKEN's contract address typed in.
|
|
async function openScreen() {
|
|
const boot = await bootPopup(
|
|
unversionedValidProfile({
|
|
currentView: "address",
|
|
selectedWallet: 0,
|
|
selectedAddress: 0,
|
|
}),
|
|
);
|
|
expect(boot.pageErrors).toEqual([]);
|
|
for (const id of opening) await boot.click(id);
|
|
boot.node(field).value = TOKEN.address;
|
|
return boot;
|
|
}
|
|
|
|
test("a second click on Add while the lookup waits tracks the token once", async () => {
|
|
const boot = await openScreen();
|
|
const answerFirstClick = addWithPendingLookup(boot, button);
|
|
const answerSecondClick = addWithPendingLookup(boot, button);
|
|
|
|
await answerFirstClick();
|
|
await answerSecondClick();
|
|
expect(boot.text("flash-msg")).toBe(alreadyTrackedFlash);
|
|
expect(boot.storage.read("autistmask").trackedTokens).toEqual([TOKEN]);
|
|
});
|
|
|
|
test("an answer for a token tracked while the lookup waited clears the status line", async () => {
|
|
const boot = await openScreen();
|
|
const answerLookup = addWithPendingLookup(boot, button);
|
|
expect(boot.text(statusLine)).toBe("Looking up token...");
|
|
require("../src/popup/views/helpers").trackToken(TOKEN);
|
|
|
|
await answerLookup();
|
|
expect(boot.text("flash-msg")).toBe(alreadyTrackedFlash);
|
|
expect(boot.text(statusLine)).toBe("");
|
|
expect(boot.node(statusLine).style.visibility).toBe("hidden");
|
|
const { state } = require("../src/shared/state");
|
|
expect(state.trackedTokens).toEqual([TOKEN]);
|
|
});
|
|
});
|