Adding a token by its contract address is checked against the address screen's balance list. TransactionDetail opened from the token screen is checked on the persisted navigation stack, on arrival and after Back, which is what tells it apart from the address screen's entry point. The token contract row's explorer link is read off the anchor, not followed, so it needs no network fixture. The network stub now answers symbol() and name() for the stub token, which Add Token reads; before, both decoded as empty strings. The token stays tracked for the rest of the run. Model: opus-5-5
This commit was merged in pull request #466.
This commit is contained in:
+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