Out of the box the wallet sent the user's addresses to Blockscout for history and token lookups, and each recipient to Etherscan for its label. Two settings, "Use Blockscout" and "Check recipients on Etherscan", now gate those requests where they are made. Both are off in a new profile and offered unticked on Welcome and in Settings. While one is off, the screens that relied on it say so in a plain line naming the setting. README and the user guide say what is contacted by default and what each setting adds. Unit tests show each request skipped while its setting is off and made while it is on; the Chrome suite watches every request from a fresh install, then turns each setting on. 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)");
|
|
});
|