From 87aed2556cb7a415344cfece4584525742ef9b30 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 5 Oct 2026 05:51:46 +0000 Subject: [PATCH] chore: remove dead exports and share copied view helpers (closes #168) AddressDetail and AddressToken defined their own isoDate() and timeAgo(), hiding the shared pair in helpers.js, so a fix there would not have reached them. The copies were identical and are deleted; blockieHtml() and tokenLabel(), each defined twice, move to helpers.js. A new test shows the history rows and the transaction detail view write the time with the shared pair. Deleted as never called: explorerLink(), ETHEREUM_SEPOLIA_CHAIN_ID, getWalletValue() and getTotalValue() with their tests. Home's "Total:" is the active address's total, as README.md already says. addressColor() and etherscanAddressUrl() are no longer exported. Model: opus-5-5 --- README.md | 8 +- TODO.md | 13 +++ src/popup/views/addressDetail.js | 58 +--------- src/popup/views/addressToken.js | 58 +--------- src/popup/views/approval.js | 12 +- src/popup/views/confirmTx.js | 7 +- src/popup/views/helpers.js | 25 ++++- src/popup/views/transactionDetail.js | 7 +- src/popup/views/txStatus.js | 13 +-- src/shared/constants.js | 2 - src/shared/networks.js | 7 -- src/shared/prices.js | 23 ---- tests/addressValue.test.js | 13 --- tests/support/popupBoot.js | 2 - tests/timestampDisplay.test.js | 159 +++++++++++++++++++++++++++ 15 files changed, 208 insertions(+), 199 deletions(-) create mode 100644 tests/timestampDisplay.test.js diff --git a/README.md b/README.md index 1d19c85..08456a8 100644 --- a/README.md +++ b/README.md @@ -842,9 +842,11 @@ The same data must be formatted identically everywhere it appears. Token and ETH amounts are displayed with exactly 4 decimal places (e.g. "1.0500 ETH", "17.1900 USDT") in balance lists, transaction lists, send confirmations, and approval screens. Timestamps include both an ISO datetime and a humanized relative age -wherever shown. If a formatting rule applies in one place, it applies in every -place. Users should never see the same value rendered differently on two -screens. +wherever shown, written by `isoDate()` and `timeAgo()` in +`src/popup/views/helpers.js` on every screen; the ISO datetime is in UTC when +the UTC Timestamps setting is on. If a formatting rule applies in one place, it +applies in every place. Users should never see the same value rendered +differently on two screens. The native token's label is a network's `nativeCurrency` in `src/shared/networks.js`: `ETH` on mainnet, `SepoliaETH` on Sepolia. The diff --git a/TODO.md b/TODO.md index e354a4a..1ddac09 100644 --- a/TODO.md +++ b/TODO.md @@ -45,6 +45,19 @@ but the review is broader than any of them. # Completed Steps +- 2026-10-05: Dead code removed and copied view helpers shared + ([#168](https://git.eeqj.de/sneak/AutistMask/issues/168)). AddressDetail and + AddressToken each defined their own `isoDate()` and `timeAgo()` in place of + the ones in `src/popup/views/helpers.js`, so a fix to the shared pair would + not have reached them. The copies were identical; every screen now uses the + shared pair. `blockieHtml()` and `tokenLabel()`, each defined twice, live in + `helpers.js` too. Removed as never called: `explorerLink()` (the views build + explorer links with `explorerUrl()`), `ETHEREUM_SEPOLIA_CHAIN_ID` (the chain + id lives in `src/shared/networks.js`), and `getWalletValue()` and + `getTotalValue()`: Home's "Total:" is the active address's total, as + `README.md` says. `addressColor()` and `etherscanAddressUrl()` are no longer + exported. Nothing the user sees changed. + - 2026-10-05: The Send screen has a "Max" button ([#198](https://git.eeqj.de/sneak/AutistMask/issues/198)). Emptying an ETH address took guessing an amount and being refused by the confirmation screen's diff --git a/src/popup/views/addressDetail.js b/src/popup/views/addressDetail.js index 5ea6225..39f1f6e 100644 --- a/src/popup/views/addressDetail.js +++ b/src/popup/views/addressDetail.js @@ -11,6 +11,8 @@ const { attachCopyHandlers, goBack, pushCurrentView, + isoDate, + timeAgo, } = require("./helpers"); const { state, saveState, currentNetwork } = require("../../shared/state"); const { formatAddressTotal, getAddressValue } = require("../../shared/prices"); @@ -88,62 +90,6 @@ function show() { loadTransactions(addr.address); } -function isoDate(timestamp) { - const d = new Date(timestamp * 1000); - const pad = (n) => String(n).padStart(2, "0"); - if (state.utcTimestamps) { - return ( - d.getUTCFullYear() + - "-" + - pad(d.getUTCMonth() + 1) + - "-" + - pad(d.getUTCDate()) + - "T" + - pad(d.getUTCHours()) + - ":" + - pad(d.getUTCMinutes()) + - ":" + - pad(d.getUTCSeconds()) + - "Z" - ); - } - const offsetMin = -d.getTimezoneOffset(); - const sign = offsetMin >= 0 ? "+" : "-"; - const absOff = Math.abs(offsetMin); - const tzStr = sign + pad(Math.floor(absOff / 60)) + ":" + pad(absOff % 60); - return ( - d.getFullYear() + - "-" + - pad(d.getMonth() + 1) + - "-" + - pad(d.getDate()) + - "T" + - pad(d.getHours()) + - ":" + - pad(d.getMinutes()) + - ":" + - pad(d.getSeconds()) + - tzStr - ); -} - -function timeAgo(timestamp) { - const seconds = Math.floor(Date.now() / 1000 - timestamp); - if (seconds < 60) return seconds + " seconds ago"; - const minutes = Math.floor(seconds / 60); - if (minutes < 60) - return minutes + " minute" + (minutes !== 1 ? "s" : "") + " ago"; - const hours = Math.floor(minutes / 60); - if (hours < 24) return hours + " hour" + (hours !== 1 ? "s" : "") + " ago"; - const days = Math.floor(hours / 24); - if (days < 30) return days + " day" + (days !== 1 ? "s" : "") + " ago"; - const months = Math.floor(days / 30); - if (months < 12) - return months + " month" + (months !== 1 ? "s" : "") + " ago"; - const years = Math.floor(days / 365); - return years + " year" + (years !== 1 ? "s" : "") + " ago"; -} - let loadedTxs = []; let ensNameMap = new Map(); diff --git a/src/popup/views/addressToken.js b/src/popup/views/addressToken.js index 1ba8907..451e58b 100644 --- a/src/popup/views/addressToken.js +++ b/src/popup/views/addressToken.js @@ -17,6 +17,8 @@ const { attachCopyHandlers, goBack, pushCurrentView, + isoDate, + timeAgo, } = require("./helpers"); const { state, saveState, currentNetwork } = require("../../shared/state"); const { TOKEN_BY_ADDRESS, resolveSymbol } = require("../../shared/tokenList"); @@ -37,62 +39,6 @@ const { walletDefect } = require("../../shared/walletDefects"); let ctx; -function isoDate(timestamp) { - const d = new Date(timestamp * 1000); - const pad = (n) => String(n).padStart(2, "0"); - if (state.utcTimestamps) { - return ( - d.getUTCFullYear() + - "-" + - pad(d.getUTCMonth() + 1) + - "-" + - pad(d.getUTCDate()) + - "T" + - pad(d.getUTCHours()) + - ":" + - pad(d.getUTCMinutes()) + - ":" + - pad(d.getUTCSeconds()) + - "Z" - ); - } - const offsetMin = -d.getTimezoneOffset(); - const sign = offsetMin >= 0 ? "+" : "-"; - const absOff = Math.abs(offsetMin); - const tzStr = sign + pad(Math.floor(absOff / 60)) + ":" + pad(absOff % 60); - return ( - d.getFullYear() + - "-" + - pad(d.getMonth() + 1) + - "-" + - pad(d.getDate()) + - "T" + - pad(d.getHours()) + - ":" + - pad(d.getMinutes()) + - ":" + - pad(d.getSeconds()) + - tzStr - ); -} - -function timeAgo(timestamp) { - const seconds = Math.floor(Date.now() / 1000 - timestamp); - if (seconds < 60) return seconds + " seconds ago"; - const minutes = Math.floor(seconds / 60); - if (minutes < 60) - return minutes + " minute" + (minutes !== 1 ? "s" : "") + " ago"; - const hours = Math.floor(minutes / 60); - if (hours < 24) return hours + " hour" + (hours !== 1 ? "s" : "") + " ago"; - const days = Math.floor(hours / 24); - if (days < 30) return days + " day" + (days !== 1 ? "s" : "") + " ago"; - const months = Math.floor(days / 30); - if (months < 12) - return months + " month" + (months !== 1 ? "s" : "") + " ago"; - const years = Math.floor(days / 365); - return years + " year" + (years !== 1 ? "s" : "") + " ago"; -} - let loadedTxs = []; let ensNameMap = new Map(); let currentSymbol = null; diff --git a/src/popup/views/approval.js b/src/popup/views/approval.js index d4e9e5d..3f8dddf 100644 --- a/src/popup/views/approval.js +++ b/src/popup/views/approval.js @@ -10,6 +10,7 @@ const { attachCopyHandlers, onViewLeave, formatFee, + tokenLabel, } = require("./helpers"); const { state, saveState } = require("../../shared/state"); const { @@ -73,17 +74,6 @@ function tokenAmountText(rawAmount, decimals, symbol) { }; } -// The symbol shown for a token line, resolved from the bundled list, the -// tokens the user tracks, and the explorer's report — the same chain the -// amount line's scale comes from. Null when no source names one, so the token -// lines keep saying `Unknown token` for a token nothing knows. -function tokenLabel(address) { - return resolveTokenSymbol(address, { - trackedTokens: state.trackedTokens, - wallets: state.wallets, - }); -} - // Try to decode calldata using known ABIs. // Returns { name, description, details } or null. function decodeCalldata(data, toAddress) { diff --git a/src/popup/views/confirmTx.js b/src/popup/views/confirmTx.js index 4752419..942e951 100644 --- a/src/popup/views/confirmTx.js +++ b/src/popup/views/confirmTx.js @@ -13,6 +13,7 @@ const { displaySymbol, nativeCurrency, renderAddressHtml, + blockieHtml, attachCopyHandlers, goBack, onViewLeave, @@ -47,7 +48,6 @@ const { validateTransfer, } = require("../../shared/txValidation"); const { log } = require("../../shared/log"); -const makeBlockie = require("ethereum-blockies-base64"); const txStatus = require("./txStatus"); let pendingTx = null; @@ -67,11 +67,6 @@ function restore() { } } -function blockieHtml(address) { - const src = makeBlockie(address); - return ``; -} - function confirmAddressHtml(address, ensName, title) { const blockie = blockieHtml(address); return ( diff --git a/src/popup/views/helpers.js b/src/popup/views/helpers.js index a285d24..bf44d49 100644 --- a/src/popup/views/helpers.js +++ b/src/popup/views/helpers.js @@ -13,10 +13,12 @@ // reasoning behind it are; it is re-exported below so views keep importing // it from here. const { formatEther } = require("ethers"); +const makeBlockie = require("ethereum-blockies-base64"); const { truncateAmountNeverZero, isBelowOneMillionth, } = require("../../shared/amountDisplay"); +const { resolveTokenSymbol } = require("../../shared/approvalAmount"); const { DEBUG } = require("../../shared/constants"); const { escapeHtml } = require("../../shared/html"); const { isDebug } = require("../../shared/log"); @@ -278,6 +280,17 @@ function nativeCurrency() { return currentNetwork().nativeCurrency; } +// The symbol shown for a token line, resolved from the bundled list, the +// tokens the user tracks, and the explorer's report — the same chain the +// amount line's scale comes from. Null when no source names one, so the token +// lines keep saying `Unknown token` for a token nothing knows. +function tokenLabel(address) { + return resolveTokenSymbol(address, { + trackedTokens: state.trackedTokens, + wallets: state.wallets, + }); +} + // A network fee in wei as the confirmation and approval screens both show it: // the ETH figure through truncateAmountNeverZero() and labelled `symbol`, the // native currency of the network the fee is paid on, then its USD value when @@ -446,6 +459,11 @@ function addressDotHtml(address) { return ``; } +function blockieHtml(address) { + const src = makeBlockie(address); + return ``; +} + // Look up an address across all wallets and return its title // (e.g. "Address 1.2") or null if it's not one of ours. function addressTitle(address, wallets) { @@ -492,6 +510,9 @@ function formatAddressHtml(address, ensName, maxLen, title) { return renderAddressHtml(address, { title, ensName, maxLen }); } +// A transaction's time as every screen shows it (README, Display +// Consistency): the ISO datetime, in UTC when the UTC Timestamps setting is +// on, and the relative age. Views import these two; they keep no copies. function isoDate(timestamp) { const d = new Date(timestamp * 1000); const pad = (n) => String(n).padStart(2, "0"); @@ -691,9 +712,10 @@ module.exports = { addressHoldsFunds, unknownableAmount, nativeCurrency, + tokenLabel, formatFee, - addressColor, addressDotHtml, + blockieHtml, escapeHtml, displaySymbol, addressTitle, @@ -703,7 +725,6 @@ module.exports = { renderAddressHtml, copyableHtml, attachCopyHandlers, - etherscanAddressUrl, etherscanLinkHtml, explorerUrl, EXT_ICON, diff --git a/src/popup/views/transactionDetail.js b/src/popup/views/transactionDetail.js index 871aeff..11c800b 100644 --- a/src/popup/views/transactionDetail.js +++ b/src/popup/views/transactionDetail.js @@ -13,6 +13,7 @@ const { isoDate, timeAgo, renderAddressHtml, + blockieHtml, attachCopyHandlers, copyableHtml, etherscanLinkHtml, @@ -23,7 +24,6 @@ const { const { state } = require("../../shared/state"); const { nativeCurrencyByChainId } = require("../../shared/networks"); const { formatEther, formatUnits } = require("ethers"); -const makeBlockie = require("ethereum-blockies-base64"); const { log, debugFetch } = require("../../shared/log"); const { decodeCalldata } = require("./approval"); @@ -48,11 +48,6 @@ function getTransactionType(tx) { return "Native " + nativeCurrencyByChainId(tx.chainId) + " Transfer"; } -function blockieHtml(address) { - const src = makeBlockie(address); - return ``; -} - function txAddressHtml(address, ensName, title) { const blockie = blockieHtml(address); return ( diff --git a/src/popup/views/txStatus.js b/src/popup/views/txStatus.js index 0ee254d..cc65a87 100644 --- a/src/popup/views/txStatus.js +++ b/src/popup/views/txStatus.js @@ -13,8 +13,8 @@ const { explorerUrl, displaySymbol, clearViewStack, + tokenLabel, } = require("./helpers"); -const { resolveTokenSymbol } = require("../../shared/approvalAmount"); const { state } = require("../../shared/state"); const { nativeCurrencyByChainId } = require("../../shared/networks"); const { getProvider } = require("../../shared/balances"); @@ -243,17 +243,6 @@ function showSuccess(txInfo, txHash, blockNumber) { ctx.doRefreshAndRender(); } -// The symbol shown for a decoded token line, resolved from the bundled list, -// the tokens the user tracks, and the explorer's report — the same chain the -// approval screen uses. Null when no source names one, so the line keeps -// saying `Unknown token`. -function tokenLabel(address) { - return resolveTokenSymbol(address, { - trackedTokens: state.trackedTokens, - wallets: state.wallets, - }); -} - function decodedDetailsHtml(decoded) { if (!decoded || !decoded.details) return ""; let html = `
`; diff --git a/src/shared/constants.js b/src/shared/constants.js index 6a36273..32497ca 100644 --- a/src/shared/constants.js +++ b/src/shared/constants.js @@ -33,7 +33,6 @@ const DEBUG_MNEMONIC = DEBUG : null; const ETHEREUM_MAINNET_CHAIN_ID = "0x1"; -const ETHEREUM_SEPOLIA_CHAIN_ID = "0xaa36a7"; const DEFAULT_RPC_URL = "https://ethereum-rpc.publicnode.com"; @@ -69,7 +68,6 @@ module.exports = { BUILD_DEBUG_MARKER, DEBUG_MNEMONIC, ETHEREUM_MAINNET_CHAIN_ID, - ETHEREUM_SEPOLIA_CHAIN_ID, DEFAULT_RPC_URL, DEFAULT_BLOCKSCOUT_URL, BIP44_ETH_PATH, diff --git a/src/shared/networks.js b/src/shared/networks.js index 591f965..17dda34 100644 --- a/src/shared/networks.js +++ b/src/shared/networks.js @@ -85,12 +85,6 @@ function nativeCurrencyByChainId(chainId) { return network ? network.nativeCurrency : "ETH"; } -// Build a block explorer link for the given path type and value. -// type: "address" | "tx" | "token" | "block" -function explorerLink(network, type, value) { - return `${network.explorerUrl}/${type}/${value}`; -} - module.exports = { NETWORKS, SUPPORTED_CHAIN_IDS, @@ -99,5 +93,4 @@ module.exports = { networkById, networkByChainId, nativeCurrencyByChainId, - explorerLink, }; diff --git a/src/shared/prices.js b/src/shared/prices.js index d6166ad..ebc1944 100644 --- a/src/shared/prices.js +++ b/src/shared/prices.js @@ -104,27 +104,6 @@ function getAddressValue(addr) { return { usd, partial }; } -// The same pair for a whole wallet, and for every wallet at once. One -// unpriced holding anywhere makes the sum a floor, so partial carries up. -function getWalletValue(wallet) { - return sumValues(wallet.addresses.map(getAddressValue)); -} - -function getTotalValue(wallets) { - return sumValues(wallets.map(getWalletValue)); -} - -function sumValues(values) { - let usd = null; - let partial = false; - for (const value of values) { - if (value.usd === null) continue; - usd = (usd === null ? 0 : usd) + value.usd; - partial = partial || value.partial; - } - return { usd, partial }; -} - // The one rendering of an address total, so no screen says it differently. // // A partial total is shown and named as partial: the figure is the ETH and @@ -149,6 +128,4 @@ module.exports = { formatUsd, formatAddressTotal, getAddressValue, - getWalletValue, - getTotalValue, }; diff --git a/tests/addressValue.test.js b/tests/addressValue.test.js index 44de5d4..3d6851d 100644 --- a/tests/addressValue.test.js +++ b/tests/addressValue.test.js @@ -22,8 +22,6 @@ const { prices, clearPrices, getAddressValue, - getWalletValue, - getTotalValue, formatAddressTotal, } = require("../src/shared/prices"); const { state } = require("../src/shared/state"); @@ -136,17 +134,6 @@ describe("the value of an address, and whether it is the whole value", () => { partial: false, }); }); - - test("one unpriced holding makes a wallet and the grand total partial", () => { - const wallet = { addresses: [FULLY_PRICED, UNPRICED_ONLY] }; - expect(getWalletValue(wallet)).toEqual({ usd: 5500, partial: true }); - expect(getTotalValue([wallet])).toEqual({ usd: 5500, partial: true }); - }); - - test("a wallet of fully priced addresses stays complete", () => { - const wallet = { addresses: [FULLY_PRICED, EMPTY] }; - expect(getWalletValue(wallet)).toEqual({ usd: 5500, partial: false }); - }); }); describe("how that value is written on screen", () => { diff --git a/tests/support/popupBoot.js b/tests/support/popupBoot.js index 5f76bd8..664a496 100644 --- a/tests/support/popupBoot.js +++ b/tests/support/popupBoot.js @@ -253,8 +253,6 @@ async function bootPopup(stored, options) { formatUsd: () => "", formatAddressTotal: () => "", getAddressValue: () => ({ usd: null, partial: false }), - getWalletValue: () => ({ usd: null, partial: false }), - getTotalValue: () => ({ usd: null, partial: false }), })); jest.doMock("../../src/shared/balances", () => ({ fetchTokenBalances: jest.fn(async () => []), diff --git a/tests/timestampDisplay.test.js b/tests/timestampDisplay.test.js new file mode 100644 index 0000000..bdf3bd4 --- /dev/null +++ b/tests/timestampDisplay.test.js @@ -0,0 +1,159 @@ +// 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"; +}); + +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)"); +});