test: cover Add Token's confirm, TransactionDetail from the token screen, and the token explorer link #466
@@ -45,6 +45,17 @@ but the review is broader than any of them.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-10-05: The Chrome end-to-end suite covers the last of the
|
||||
[#150](https://git.eeqj.de/sneak/AutistMask/issues/150) and
|
||||
[#151](https://git.eeqj.de/sneak/AutistMask/issues/151) items
|
||||
([#295](https://git.eeqj.de/sneak/AutistMask/issues/295)): a token added on
|
||||
Add Token by its contract address is listed on the address screen;
|
||||
TransactionDetail opened from the token screen leaves that screen on the
|
||||
persisted navigation stack, and Back returns to it; and the token contract row
|
||||
links to the explorer's token page, read off the link rather than followed.
|
||||
The network stub answers `symbol()` and `name()` for the stub token, which
|
||||
adding it reads.
|
||||
|
||||
- 2026-10-05: Each control that leads to a signature or to the private key has a
|
||||
test that it refuses a defective wallet before asking for a password
|
||||
([#254](https://git.eeqj.de/sneak/AutistMask/issues/254)): Send on the main,
|
||||
|
||||
+18
-6
@@ -22,7 +22,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Transaction } = require("ethers");
|
||||
const { AbiCoder, Transaction } = require("ethers");
|
||||
|
||||
// Fictional ERC-20 used to seed the transaction-detail test. The symbol
|
||||
// must not collide with any entry in src/shared/tokenList.js, or
|
||||
@@ -244,12 +244,16 @@ function latestBlock() {
|
||||
};
|
||||
}
|
||||
|
||||
// keccak("decimals()")[0:4].
|
||||
// keccak("decimals()")[0:4], and the same for symbol() and name().
|
||||
const SELECTOR_DECIMALS = "0x313ce567";
|
||||
const SELECTOR_SYMBOL = "0x95d89b41";
|
||||
const SELECTOR_NAME = "0x06fdde03";
|
||||
|
||||
// Every eth_call still answers with a zero word except decimals() on the
|
||||
// stub token, which the wallet reads back at signing time to compare with
|
||||
// the scale the confirmation screen rendered (issue #305).
|
||||
// Every eth_call still answers with a zero word except decimals(), symbol()
|
||||
// and name() on the stub token. The wallet reads decimals() back at signing
|
||||
// time to compare with the scale the confirmation screen rendered (issue
|
||||
// #305). Adding the token by its contract address reads all three (issue
|
||||
// #295); symbol() and name() answer what the explorer reports for it.
|
||||
//
|
||||
// opts.tokenDecimalsOverride is the lying contract: set it and decimals()
|
||||
// answers something other than the value this same fixture reports through
|
||||
@@ -263,9 +267,17 @@ function ethCallResult(req, opts) {
|
||||
if (!call || typeof call !== "object") return ZERO_WORD;
|
||||
const data = String(call.data || call.input || "").toLowerCase();
|
||||
const to = String(call.to || "").toLowerCase();
|
||||
if (data.startsWith(SELECTOR_DECIMALS) && to === STUB_TOKEN.address) {
|
||||
if (to !== STUB_TOKEN.address) return ZERO_WORD;
|
||||
if (data.startsWith(SELECTOR_DECIMALS)) {
|
||||
return word(opts.tokenDecimalsOverride ?? STUB_TOKEN.decimals);
|
||||
}
|
||||
const abi = AbiCoder.defaultAbiCoder();
|
||||
if (data.startsWith(SELECTOR_SYMBOL)) {
|
||||
return abi.encode(["string"], [tokenObject(opts).symbol]);
|
||||
}
|
||||
if (data.startsWith(SELECTOR_NAME)) {
|
||||
return abi.encode(["string"], [tokenObject(opts).name]);
|
||||
}
|
||||
return ZERO_WORD;
|
||||
}
|
||||
|
||||
|
||||
@@ -596,6 +596,106 @@ test("tap-to-copy on the transaction detail screen copies the address (#151)", a
|
||||
);
|
||||
});
|
||||
|
||||
// ------------------------- the last of the #150 and #151 items (#295)
|
||||
//
|
||||
// Add Token's confirm button, TransactionDetail opened from the token screen
|
||||
// and Back from it, and the explorer link on the token contract row.
|
||||
|
||||
// The stub token stays tracked for the rest of the run: the next test reaches
|
||||
// its token screen through the balance row this one adds.
|
||||
test("a token added by its contract address is listed on the address screen (#150)", async (env) => {
|
||||
await leaveTransactionDetail(env.page);
|
||||
await env.page.click("#btn-add-token");
|
||||
await visible(env.page, "#view-add-token");
|
||||
|
||||
await env.page.fill("#add-token-address", STUB_TOKEN.address);
|
||||
await env.page.click("#btn-add-token-confirm");
|
||||
await visible(env.page, "#view-address");
|
||||
|
||||
// No wait: the confirm renders the balance list before it shows the
|
||||
// screen, and nothing renders the list again while the screen is up.
|
||||
const row = env.page.locator(
|
||||
'#address-balances [data-token="' + STUB_TOKEN.address + '"]',
|
||||
{ hasText: STUB_TOKEN.symbol },
|
||||
);
|
||||
const balances = await env.page.locator("#address-balances").innerText();
|
||||
assert(
|
||||
(await row.count()) === 1,
|
||||
"the balance list has no " +
|
||||
STUB_TOKEN.symbol +
|
||||
" row for the token just added: " +
|
||||
JSON.stringify(balances),
|
||||
);
|
||||
});
|
||||
|
||||
// TransactionDetail looks the same from either entry point. Only the
|
||||
// persisted stack says which one opened it, so that is what is asserted: from
|
||||
// the token screen it ends in "address-token", and Back has to land there
|
||||
// rather than on the address screen beneath it.
|
||||
test("transaction detail opened from the token screen goes Back to it (#151)", async (env) => {
|
||||
await goHome(env.page);
|
||||
const base = await persistedViewStack(env.page);
|
||||
|
||||
await env.page.locator("#wallet-list .btn-addr-info").first().click();
|
||||
await visible(env.page, "#view-address");
|
||||
await env.page
|
||||
.locator('#address-balances [data-token="' + STUB_TOKEN.address + '"]')
|
||||
.click();
|
||||
await visible(env.page, "#view-address-token");
|
||||
|
||||
const row = env.page.locator("#address-token-tx-list .tx-row").first();
|
||||
await row.waitFor({ state: "visible", timeout: 30000 });
|
||||
await row.click();
|
||||
await visible(env.page, "#view-transaction");
|
||||
await waitForPersisted(
|
||||
env.page,
|
||||
"viewStack",
|
||||
base.concat("main", "address", "address-token"),
|
||||
"on transaction detail opened from the token screen",
|
||||
);
|
||||
|
||||
// The stack is checked before the screen, so a Back that lands on the
|
||||
// wrong screen fails by saying what the stack holds.
|
||||
await env.page.click("#btn-tx-back");
|
||||
await waitForPersisted(
|
||||
env.page,
|
||||
"viewStack",
|
||||
base.concat("main", "address"),
|
||||
"after Back from transaction detail",
|
||||
);
|
||||
await visible(env.page, "#view-address-token");
|
||||
|
||||
// Onto the address screen, which the next test starts from.
|
||||
await env.page.click("#btn-address-token-back");
|
||||
await visible(env.page, "#view-address");
|
||||
});
|
||||
|
||||
// Read off the anchor rather than followed: where it points is all the popup
|
||||
// decides, and following it would only load the explorer's page. The suite is
|
||||
// on mainnet until the Settings section.
|
||||
test("the token contract row links to the explorer's token page (#151)", async (env) => {
|
||||
await leaveTransactionDetail(env.page);
|
||||
const row = env.page
|
||||
.locator("#tx-list .tx-row")
|
||||
.filter({ hasText: STUB_TOKEN.symbol });
|
||||
await row.waitFor({ state: "visible", timeout: 30000 });
|
||||
await row.click();
|
||||
await visible(env.page, "#view-transaction");
|
||||
await visible(env.page, "#tx-detail-token-contract-section");
|
||||
|
||||
const href = await env.page
|
||||
.locator("#tx-detail-token-contract a")
|
||||
.getAttribute("href");
|
||||
const expected = "https://etherscan.io/token/" + STUB_TOKEN.address;
|
||||
assert(
|
||||
href === expected,
|
||||
"the token contract row links to " +
|
||||
JSON.stringify(href) +
|
||||
", expected " +
|
||||
expected,
|
||||
);
|
||||
});
|
||||
|
||||
// -------------------------------------------- recovery phrase (#161)
|
||||
|
||||
// The gear toggles, so pressing it while Settings is already up leaves it.
|
||||
|
||||
Reference in New Issue
Block a user