Out of the box the wallet sent the user's addresses to Blockscout. It sent each recipient to Etherscan for its label. Two settings, "Use Blockscout" and "Check recipients on Etherscan", now gate those requests. Both are off in a new install and offered unticked on Welcome and in Settings. While one is off, the screens that relied on it say so in a plain line. Each gated request reads its setting from storage before it asks and when the answer lands. An answer that lands after the setting went off is dropped. An open popup takes a change another page saved and draws its screens again. While "Use Blockscout" is off, no address keeps an untracked token. ENS lookups ask only the RPC endpoint. Model: opus-5-5
162 lines
5.3 KiB
JavaScript
162 lines
5.3 KiB
JavaScript
// A transaction's time is written by isoDate() and timeAgo() in
|
|
// src/popup/views/helpers.js on every screen that shows one (README, Display
|
|
// Consistency; https://git.eeqj.de/sneak/AutistMask/issues/168). AddressDetail
|
|
// and AddressToken used to define their own copies, so a fix to the shared pair
|
|
// would not have reached them.
|
|
//
|
|
// The pair is replaced before the views are loaded, because a view takes it
|
|
// when it loads. A view that writes the time with a copy of its own shows the
|
|
// real time instead of the replacement.
|
|
//
|
|
// Driven against a minimal DOM stub in the shape
|
|
// tests/contractCreation.test.js uses.
|
|
|
|
jest.mock("../src/shared/log", () => ({
|
|
log: {
|
|
debugf: () => {},
|
|
infof: () => {},
|
|
warnf: () => {},
|
|
errorf: () => {},
|
|
},
|
|
// The transaction detail view fetches on-chain details after drawing; an
|
|
// answer that is not ok leaves the drawn lines as they are.
|
|
debugFetch: async () => ({ ok: false }),
|
|
setRuntimeDebug: () => {},
|
|
isDebug: () => false,
|
|
}));
|
|
|
|
// The history lists ask the explorer for their transactions and resolve ENS
|
|
// names for them; here the explorer answers with mockHistory and no name
|
|
// resolves.
|
|
let mockHistory = [];
|
|
jest.mock("../src/shared/transactions", () => ({
|
|
...jest.requireActual("../src/shared/transactions"),
|
|
fetchRecentTransactions: async () => mockHistory,
|
|
}));
|
|
jest.mock("../src/shared/ens", () => ({
|
|
...jest.requireActual("../src/shared/ens"),
|
|
resolveEnsNames: async () => new Map(),
|
|
}));
|
|
|
|
globalThis.chrome = {
|
|
storage: { local: { get: async () => ({}), set: async () => {} } },
|
|
};
|
|
|
|
const helpers = require("../src/popup/views/helpers");
|
|
jest.spyOn(helpers, "isoDate").mockReturnValue("SHARED-ISO-DATE");
|
|
jest.spyOn(helpers, "timeAgo").mockReturnValue("SHARED-TIME-AGO");
|
|
|
|
const { state } = require("../src/shared/state");
|
|
const addressDetail = require("../src/popup/views/addressDetail");
|
|
const addressToken = require("../src/popup/views/addressToken");
|
|
const transactionDetail = require("../src/popup/views/transactionDetail");
|
|
|
|
const FROM = "0x0000000000000000000000000000000000000a11";
|
|
const RECIPIENT = "0x66133E8ea0f5D1d612D2502a968757D1048c214a";
|
|
|
|
function makeElement(id) {
|
|
const el = {
|
|
id,
|
|
textContent: "",
|
|
value: "",
|
|
innerHTML: "",
|
|
style: {},
|
|
dataset: {},
|
|
classList: {
|
|
add: () => {},
|
|
remove: () => {},
|
|
contains: () => false,
|
|
toggle: () => false,
|
|
},
|
|
addEventListener: () => {},
|
|
querySelectorAll: () => [],
|
|
appendChild: () => {},
|
|
};
|
|
// Views reach for .parentElement to hide whole sections.
|
|
Object.defineProperty(el, "parentElement", {
|
|
get: () => node(id + "-parent"),
|
|
});
|
|
return el;
|
|
}
|
|
|
|
function makeDocument() {
|
|
const els = new Map();
|
|
return {
|
|
getElementById(id) {
|
|
// The debug banner is created on demand by helpers.js; absent
|
|
// is the state a non-debug, non-testnet popup is in.
|
|
if (id === "debug-banner") return null;
|
|
if (!els.has(id)) els.set(id, makeElement(id));
|
|
return els.get(id);
|
|
},
|
|
createElement: () => makeElement("created"),
|
|
body: { prepend: () => {} },
|
|
};
|
|
}
|
|
|
|
function node(id) {
|
|
return globalThis.document.getElementById(id);
|
|
}
|
|
|
|
// A transaction FROM sent, as the history lists hold it.
|
|
function historyTx() {
|
|
return {
|
|
hash: "0x85215772ed26ea8b39c2b3b18779030487efbe0b5fd7e882592b2f62b837be84",
|
|
from: FROM,
|
|
to: RECIPIENT,
|
|
value: "0.0000",
|
|
exactValue: "0.0",
|
|
rawAmount: "0",
|
|
rawUnit: "wei",
|
|
symbol: "ETH",
|
|
timestamp: 1790000000,
|
|
isError: false,
|
|
directionLabel: "Sent",
|
|
direction: "sent",
|
|
contractAddress: null,
|
|
};
|
|
}
|
|
|
|
beforeEach(() => {
|
|
globalThis.document = makeDocument();
|
|
globalThis.window = { location: { search: "" } };
|
|
state.wallets = [
|
|
{
|
|
name: "Main",
|
|
type: "key",
|
|
addresses: [{ address: FROM, balance: "0.0000" }],
|
|
},
|
|
];
|
|
state.trackedTokens = [];
|
|
state.viewData = {};
|
|
state.viewStack = [];
|
|
state.currentView = null;
|
|
state.selectedWallet = 0;
|
|
state.selectedAddress = 0;
|
|
state.selectedToken = "ETH";
|
|
// The history is Blockscout's.
|
|
state.useBlockscout = true;
|
|
});
|
|
|
|
describe.each([
|
|
["AddressDetail", "tx-list", () => addressDetail.show()],
|
|
["AddressToken", "address-token-tx-list", () => addressToken.show()],
|
|
])("a transaction history row on %s", (_name, listId, open) => {
|
|
test("shows the time written by the shared isoDate() and timeAgo()", async () => {
|
|
mockHistory = [historyTx()];
|
|
open();
|
|
// The list is drawn once the history has been fetched.
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
const html = node(listId).innerHTML;
|
|
expect(html).toContain('title="SHARED-ISO-DATE"');
|
|
expect(html).toContain(">SHARED-TIME-AGO<");
|
|
});
|
|
});
|
|
|
|
test("the transaction detail view shows the time written by the shared isoDate() and timeAgo()", () => {
|
|
transactionDetail.show(historyTx());
|
|
const html = node("tx-detail-time").innerHTML;
|
|
expect(html).toContain("SHARED-ISO-DATE");
|
|
expect(html).toContain("(SHARED-TIME-AGO)");
|
|
});
|