fix: say a contract creation has no recipient instead of a blank line (closes #250)
A transaction with no `to` showed a blank address, with a colour dot whose colour was `undefined`, on the wait, success and error screens, the transaction detail view and the history rows on Home, AddressDetail and AddressToken. The approval screen showed "(contract creation)". All of them now say "This transaction creates a new contract. It has no recipient." The three history lists draw a row's counterparty lines through one helper in helpers.js. A transaction with a real `to` is unchanged. The new test drives each screen and list both ways. Model: opus-5-5
This commit was merged in pull request #422.
This commit is contained in:
@@ -1258,7 +1258,10 @@ view would leave a wallet one click from deletion.
|
||||
of every wallet, deduplicated by hash and filtered. Each row is three
|
||||
lines: age and direction, then the counterparty's colour dot (with our own
|
||||
name for it, where it is one of our addresses) and the amount, then the
|
||||
counterparty's full address on a row of its own
|
||||
counterparty's full address on a row of its own. A contract creation has
|
||||
no counterparty: its second line is the amount alone and its third line
|
||||
says "This transaction creates a new contract. It has no recipient." The
|
||||
transaction lists on AddressDetail and AddressToken draw the same rows
|
||||
- "Add additional wallet..." link at bottom
|
||||
- **Transitions**:
|
||||
- Tap address row → sets the active address and broadcasts
|
||||
@@ -1451,7 +1454,9 @@ view would leave a wallet one click from deletion.
|
||||
- **Elements**:
|
||||
- "Transaction Broadcast" heading (no back button — tx is irreversible)
|
||||
- Amount + symbol
|
||||
- To: color dot + full address + etherscan link
|
||||
- To: color dot + full address + etherscan link; for a contract creation,
|
||||
which has no recipient, "This transaction creates a new contract. It has
|
||||
no recipient." instead
|
||||
- Transaction hash: full hash (tap to copy) + etherscan link
|
||||
- Count-up timer: "Waiting for confirmation... Ns"
|
||||
- **Behavior**: Polls `getTransactionReceipt` every 10 seconds. The wait is
|
||||
@@ -1480,7 +1485,8 @@ view would leave a wallet one click from deletion.
|
||||
- Decoded action well (shown when the transaction carried recognized
|
||||
calldata; the top-level Amount and To are hidden in that case)
|
||||
- Amount + symbol
|
||||
- To: color dot + full address + etherscan link
|
||||
- To: color dot + full address + etherscan link, or for a contract creation
|
||||
the same sentence as on WaitTx
|
||||
- Block number
|
||||
- Transaction hash: full hash (tap to copy) + etherscan link
|
||||
- "Done" button
|
||||
@@ -1495,7 +1501,8 @@ view would leave a wallet one click from deletion.
|
||||
- **Elements**:
|
||||
- "Transaction Failed" heading
|
||||
- Amount + symbol
|
||||
- To: color dot + full address + etherscan link
|
||||
- To: color dot + full address + etherscan link, or for a contract creation
|
||||
the same sentence as on WaitTx
|
||||
- Error message (dashed border box)
|
||||
- Transaction hash section (hidden if broadcast failed before getting hash):
|
||||
full hash (tap to copy) + etherscan link
|
||||
@@ -1533,7 +1540,7 @@ view would leave a wallet one click from deletion.
|
||||
- From: blockie + color dot + full address (tap to copy) + etherscan link;
|
||||
ENS name if available
|
||||
- To: blockie + color dot + full address (tap to copy) + etherscan link; ENS
|
||||
name if available
|
||||
name if available. For a contract creation, the same sentence as on WaitTx
|
||||
- Time: ISO datetime + relative age in parentheses
|
||||
- Block: block number (tap to copy) + etherscan block link
|
||||
- Amount: value + symbol (bold)
|
||||
@@ -1844,8 +1851,8 @@ view would leave a wallet one click from deletion.
|
||||
- Decoded action (if calldata is recognized): action name, token details,
|
||||
amounts, steps, deadline (see Transaction Decoding)
|
||||
- From: color dot + full address + etherscan link
|
||||
- Contract: color dot + full address + etherscan link (or "contract
|
||||
creation"), token symbol label if known
|
||||
- Contract: color dot + full address + etherscan link, token symbol label if
|
||||
known; for a contract creation, the same sentence as on WaitTx
|
||||
- Value: amount in ETH (4 decimal places, USD in parentheses)
|
||||
- Network fee (max): gas limit × fee per gas in ETH (4 decimal places, USD
|
||||
in parentheses), with the gas limit and the fee per gas in gwei below it
|
||||
|
||||
@@ -45,6 +45,14 @@ but the review is broader than any of them.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-10-04: A transaction with no `to` says "This transaction creates a new
|
||||
contract. It has no recipient." on its recipient line and in its transaction
|
||||
history row ([#250](https://git.eeqj.de/sneak/AutistMask/issues/250)). The
|
||||
wait, success and error screens, the transaction detail view and the history
|
||||
rows on Home, AddressDetail and AddressToken showed a blank address there,
|
||||
with a colour dot whose colour was `undefined`; the approval screen showed
|
||||
"(contract creation)". A transaction with a real `to` is unchanged.
|
||||
|
||||
- 2026-10-04: The Send and confirmation screens no longer show an ETH balance, a
|
||||
token balance or a network fee below 0.000001 as zero
|
||||
([#343](https://git.eeqj.de/sneak/AutistMask/issues/343)). The stored balances
|
||||
|
||||
@@ -3,7 +3,7 @@ const {
|
||||
showView,
|
||||
showFlash,
|
||||
balanceLinesForAddress,
|
||||
addressDotHtml,
|
||||
txCounterpartyHtml,
|
||||
addressTitle,
|
||||
escapeHtml,
|
||||
displaySymbol,
|
||||
@@ -233,16 +233,13 @@ function renderTransactions(txs) {
|
||||
// is shown whole; the title or ENS name, where there is one, names
|
||||
// it on the line above rather than replacing it.
|
||||
const nameStr = escapeHtml(title || ensName || "");
|
||||
const addrStr = escapeHtml(counterparty);
|
||||
const dot = addressDotHtml(counterparty);
|
||||
const err = tx.isError ? " (failed)" : "";
|
||||
const opacity = tx.isError ? " opacity:0.5;" : "";
|
||||
const ago = escapeHtml(timeAgo(tx.timestamp));
|
||||
const iso = escapeHtml(isoDate(tx.timestamp));
|
||||
html += `<div class="tx-row py-2 border-b border-border-light text-xs cursor-pointer hover:bg-hover" data-tx="${i}" style="${opacity}">`;
|
||||
html += `<div class="flex justify-between"><span class="text-muted" title="${iso}">${ago}</span><span>${dirLabel}${err}</span></div>`;
|
||||
html += `<div class="flex justify-between"><span class="flex items-center">${dot}${nameStr}</span><span>${amountStr}</span></div>`;
|
||||
html += `<div class="am-address">${addrStr}</div>`;
|
||||
html += txCounterpartyHtml(counterparty, nameStr, amountStr);
|
||||
html += `</div>`;
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ const {
|
||||
showView,
|
||||
showFlash,
|
||||
flashCopyFeedback,
|
||||
addressDotHtml,
|
||||
txCounterpartyHtml,
|
||||
addressTitle,
|
||||
escapeHtml,
|
||||
displaySymbol,
|
||||
@@ -309,16 +309,13 @@ function renderTransactions(txs) {
|
||||
// is shown whole; the title or ENS name, where there is one, names
|
||||
// it on the line above rather than replacing it.
|
||||
const nameStr = escapeHtml(title || ensName || "");
|
||||
const addrStr = escapeHtml(counterparty);
|
||||
const dot = addressDotHtml(counterparty);
|
||||
const err = tx.isError ? " (failed)" : "";
|
||||
const opacity = tx.isError ? " opacity:0.5;" : "";
|
||||
const ago = escapeHtml(timeAgo(tx.timestamp));
|
||||
const iso = escapeHtml(isoDate(tx.timestamp));
|
||||
html += `<div class="tx-row py-2 border-b border-border-light text-xs cursor-pointer hover:bg-hover" data-tx="${i}" style="${opacity}">`;
|
||||
html += `<div class="flex justify-between"><span class="text-muted" title="${iso}">${ago}</span><span>${dirLabel}${err}</span></div>`;
|
||||
html += `<div class="flex justify-between"><span class="flex items-center">${dot}${nameStr}</span><span>${amountStr}</span></div>`;
|
||||
html += `<div class="am-address">${addrStr}</div>`;
|
||||
html += txCounterpartyHtml(counterparty, nameStr, amountStr);
|
||||
html += `</div>`;
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const {
|
||||
$,
|
||||
addressTitle,
|
||||
CONTRACT_CREATION_TEXT,
|
||||
escapeHtml,
|
||||
showView,
|
||||
showError,
|
||||
@@ -318,7 +319,7 @@ function showTxApproval(details) {
|
||||
toHtml += approvalAddressHtml(toAddr);
|
||||
$("approve-tx-to").innerHTML = toHtml;
|
||||
} else {
|
||||
$("approve-tx-to").innerHTML = escapeHtml("(contract creation)");
|
||||
$("approve-tx-to").innerHTML = escapeHtml(CONTRACT_CREATION_TEXT);
|
||||
}
|
||||
|
||||
const ethValueFormatted = formatTxValue(
|
||||
|
||||
@@ -431,6 +431,29 @@ function addressTitle(address, wallets) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// What every recipient line and history row says for a transaction with no
|
||||
// `to`. Such a transaction creates a contract, so there is no address to show,
|
||||
// and a blank line on these screens reads as a rendering fault.
|
||||
const CONTRACT_CREATION_TEXT =
|
||||
"This transaction creates a new contract. It has no recipient.";
|
||||
|
||||
// The last two lines of a transaction history row: the counterparty's colour
|
||||
// dot and name beside the amount, then its full address. A contract creation
|
||||
// the user sent has no counterparty (its `to` is ""), so its row has the
|
||||
// amount alone and the contract creation sentence in place of the address.
|
||||
function txCounterpartyHtml(address, nameHtml, amountHtml) {
|
||||
if (!address) {
|
||||
return (
|
||||
`<div class="flex justify-between"><span></span><span>${amountHtml}</span></div>` +
|
||||
`<div>${escapeHtml(CONTRACT_CREATION_TEXT)}</div>`
|
||||
);
|
||||
}
|
||||
return (
|
||||
`<div class="flex justify-between"><span class="flex items-center">${addressDotHtml(address)}${nameHtml}</span><span>${amountHtml}</span></div>` +
|
||||
`<div class="am-address">${escapeHtml(address)}</div>`
|
||||
);
|
||||
}
|
||||
|
||||
// Render an address with color dot, optional ENS name, optional title,
|
||||
// and optional truncation. Title and ENS are shown as bold labels above
|
||||
// the full address.
|
||||
@@ -643,6 +666,8 @@ module.exports = {
|
||||
escapeHtml,
|
||||
displaySymbol,
|
||||
addressTitle,
|
||||
CONTRACT_CREATION_TEXT,
|
||||
txCounterpartyHtml,
|
||||
formatAddressHtml,
|
||||
renderAddressHtml,
|
||||
copyableHtml,
|
||||
|
||||
@@ -6,6 +6,7 @@ const {
|
||||
isoDate,
|
||||
timeAgo,
|
||||
addressDotHtml,
|
||||
txCounterpartyHtml,
|
||||
addressTitle,
|
||||
escapeHtml,
|
||||
displaySymbol,
|
||||
@@ -122,16 +123,13 @@ function renderHomeTxList(ctx) {
|
||||
// names it on the line above rather than replacing it.
|
||||
const title = addressTitle(counterparty, state.wallets);
|
||||
const titleStr = title ? escapeHtml(title) : "";
|
||||
const addrStr = escapeHtml(counterparty);
|
||||
const dot = addressDotHtml(counterparty);
|
||||
const err = tx.isError ? " (failed)" : "";
|
||||
const opacity = tx.isError ? " opacity:0.5;" : "";
|
||||
const ago = escapeHtml(timeAgo(tx.timestamp));
|
||||
const iso = escapeHtml(isoDate(tx.timestamp));
|
||||
html += `<div class="home-tx-row py-2 border-b border-border-light text-xs cursor-pointer hover:bg-hover" data-tx="${i}" style="${opacity}">`;
|
||||
html += `<div class="flex justify-between"><span class="text-muted" title="${iso}">${ago}</span><span>${dirLabel}${err}</span></div>`;
|
||||
html += `<div class="flex justify-between"><span class="flex items-center">${dot}${titleStr}</span><span>${amountStr}</span></div>`;
|
||||
html += `<div class="am-address">${addrStr}</div>`;
|
||||
html += txCounterpartyHtml(counterparty, titleStr, amountStr);
|
||||
html += `</div>`;
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ const {
|
||||
showFlash,
|
||||
flashCopyFeedback,
|
||||
addressTitle,
|
||||
CONTRACT_CREATION_TEXT,
|
||||
addressDotHtml,
|
||||
escapeHtml,
|
||||
isoDate,
|
||||
@@ -94,13 +95,18 @@ function render() {
|
||||
$("tx-detail-hash").innerHTML = txHashHtml(tx.hash);
|
||||
|
||||
const fromTitle = addressTitle(tx.from, state.wallets);
|
||||
const toTitle = addressTitle(tx.to, state.wallets);
|
||||
$("tx-detail-from").innerHTML = txAddressHtml(
|
||||
tx.from,
|
||||
tx.fromEns,
|
||||
fromTitle,
|
||||
);
|
||||
$("tx-detail-to").innerHTML = txAddressHtml(tx.to, tx.toEns, toTitle);
|
||||
// A contract creation has no recipient: transactions.js gives it `to: ""`.
|
||||
if (tx.to) {
|
||||
const toTitle = addressTitle(tx.to, state.wallets);
|
||||
$("tx-detail-to").innerHTML = txAddressHtml(tx.to, tx.toEns, toTitle);
|
||||
} else {
|
||||
$("tx-detail-to").innerHTML = escapeHtml(CONTRACT_CREATION_TEXT);
|
||||
}
|
||||
|
||||
// Exact amount (full precision, copyable)
|
||||
const detailSym = displaySymbol(tx.symbol);
|
||||
|
||||
@@ -4,6 +4,7 @@ const {
|
||||
$,
|
||||
showView,
|
||||
addressTitle,
|
||||
CONTRACT_CREATION_TEXT,
|
||||
escapeHtml,
|
||||
renderAddressHtml,
|
||||
attachCopyHandlers,
|
||||
@@ -58,7 +59,10 @@ function endWait() {
|
||||
}
|
||||
}
|
||||
|
||||
// A contract creation reaches these screens with `to` as "" (approval.js
|
||||
// writes `to: toAddr || ""`).
|
||||
function toAddressHtml(address) {
|
||||
if (!address) return escapeHtml(CONTRACT_CREATION_TEXT);
|
||||
const title = addressTitle(address, state.wallets);
|
||||
return renderAddressHtml(address, { title });
|
||||
}
|
||||
@@ -202,8 +206,9 @@ function restoreWait() {
|
||||
if (!info || typeof info !== "object" || Array.isArray(info)) return false;
|
||||
// A string is the whole requirement: the empty string is what a
|
||||
// contract-deployment approval persists (approval.js writes `to: toAddr
|
||||
// || ""`), and both fields render harmlessly when empty, so refusing it
|
||||
// would abandon a wait the live path itself created.
|
||||
// || ""`), an empty `to` renders as a contract creation and an empty
|
||||
// amount renders harmlessly, so refusing it would abandon a wait the live
|
||||
// path itself created.
|
||||
if (typeof info.to !== "string") return false;
|
||||
if (typeof info.amount !== "string") return false;
|
||||
if (typeof w.broadcastTime !== "number" || !isFinite(w.broadcastTime)) {
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
// The recipient line of a contract creation
|
||||
// (https://git.eeqj.de/sneak/AutistMask/issues/250).
|
||||
//
|
||||
// A transaction with no `to` creates a contract. The approval screen, the
|
||||
// wait, success and error screens, the transaction detail view and the
|
||||
// transaction history rows each say so in a sentence, where they used to show
|
||||
// a blank line (an empty address, with a colour dot whose colour was
|
||||
// `undefined`) or, on the approval screen, "(contract creation)". A
|
||||
// transaction with a real `to` still shows that address.
|
||||
//
|
||||
// Driven against a minimal DOM stub in the shape
|
||||
// tests/typedDataPermit.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 wait screen polls for a receipt; this one never arrives.
|
||||
jest.mock("../src/shared/balances", () => ({
|
||||
getProvider: () => ({ getTransactionReceipt: () => new Promise(() => {}) }),
|
||||
refreshBalances: () => {},
|
||||
}));
|
||||
|
||||
// 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 { state } = require("../src/shared/state");
|
||||
const approval = require("../src/popup/views/approval");
|
||||
const txStatus = require("../src/popup/views/txStatus");
|
||||
const transactionDetail = require("../src/popup/views/transactionDetail");
|
||||
const home = require("../src/popup/views/home");
|
||||
const addressDetail = require("../src/popup/views/addressDetail");
|
||||
const addressToken = require("../src/popup/views/addressToken");
|
||||
|
||||
const SENTENCE =
|
||||
"This transaction creates a new contract. It has no recipient.";
|
||||
|
||||
const FROM = "0x0000000000000000000000000000000000000a11";
|
||||
const RECIPIENT = "0x66133E8ea0f5D1d612D2502a968757D1048c214a";
|
||||
const TX_HASH =
|
||||
"0x85215772ed26ea8b39c2b3b18779030487efbe0b5fd7e882592b2f62b837be84";
|
||||
// Init code for a contract creation's data.
|
||||
const INIT_CODE = "0x600160005500";
|
||||
|
||||
function makeElement(id) {
|
||||
const classes = new Set();
|
||||
const el = {
|
||||
id,
|
||||
textContent: "",
|
||||
value: "",
|
||||
innerHTML: "",
|
||||
disabled: false,
|
||||
style: {},
|
||||
dataset: {},
|
||||
classList: {
|
||||
add: (...names) => names.forEach((n) => classes.add(n)),
|
||||
remove: (...names) => names.forEach((n) => classes.delete(n)),
|
||||
contains: (n) => classes.has(n),
|
||||
toggle: (n, force) => {
|
||||
const on = force === undefined ? !classes.has(n) : force;
|
||||
if (on) classes.add(n);
|
||||
else classes.delete(n);
|
||||
return on;
|
||||
},
|
||||
},
|
||||
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);
|
||||
}
|
||||
|
||||
// The line a transaction with a real `to` shows: that address, and nothing
|
||||
// left over from an empty one.
|
||||
function expectAddressLine(html) {
|
||||
expect(html).toContain(RECIPIENT);
|
||||
expect(html).not.toContain(SENTENCE);
|
||||
expect(html).not.toContain("undefined");
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
globalThis.document = makeDocument();
|
||||
globalThis.window = { location: { search: "" } };
|
||||
state.wallets = [];
|
||||
state.trackedTokens = [];
|
||||
state.viewData = {};
|
||||
state.viewStack = [];
|
||||
state.currentView = null;
|
||||
txStatus.init({ doRefreshAndRender: () => {} });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
txStatus.endWait();
|
||||
});
|
||||
|
||||
// Open the transaction approval screen the way the popup does: the background
|
||||
// hands over the populated transaction and show() draws it.
|
||||
async function openTxApproval(to, data) {
|
||||
globalThis.chrome.runtime = {
|
||||
connect: () => ({ postMessage: () => {} }),
|
||||
sendMessage: (msg, reply) => {
|
||||
if (!reply) return;
|
||||
if (msg.type !== "AUTISTMASK_GET_APPROVAL") return reply(null);
|
||||
reply({
|
||||
type: "tx",
|
||||
hostname: "dapp.example",
|
||||
isPhishingDomain: false,
|
||||
approvedFrom: FROM,
|
||||
approvedTx: {
|
||||
type: 2,
|
||||
from: FROM,
|
||||
chainId: "0x1",
|
||||
nonce: "0x7",
|
||||
gasLimit: "0x5208",
|
||||
maxPriorityFeePerGas: "0x3b9aca00",
|
||||
maxFeePerGas: "0x77359400",
|
||||
to,
|
||||
value: "0x0",
|
||||
data,
|
||||
accessList: [],
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
approval.init({});
|
||||
await approval.show(1);
|
||||
}
|
||||
|
||||
describe("the transaction approval screen", () => {
|
||||
test("a contract creation says so instead of naming a contract", async () => {
|
||||
await openTxApproval(null, INIT_CODE);
|
||||
expect(node("approve-tx-to").innerHTML).toBe(SENTENCE);
|
||||
});
|
||||
|
||||
test("a transaction with a recipient shows its address", async () => {
|
||||
await openTxApproval(RECIPIENT, "0x");
|
||||
expectAddressLine(node("approve-tx-to").innerHTML);
|
||||
});
|
||||
});
|
||||
|
||||
// approval.js carries a contract creation to these screens with `to` as "".
|
||||
describe("the wait, success and error screens", () => {
|
||||
const creation = {
|
||||
to: "",
|
||||
amount: "0.0000",
|
||||
token: "ETH",
|
||||
tokenSymbol: null,
|
||||
};
|
||||
const transfer = { ...creation, to: RECIPIENT };
|
||||
|
||||
test("a contract creation says so on the wait screen", () => {
|
||||
txStatus.showWait(creation, TX_HASH);
|
||||
expect(node("wait-tx-to").innerHTML).toBe(SENTENCE);
|
||||
});
|
||||
|
||||
test("a transaction with a recipient shows its address on the wait screen", () => {
|
||||
txStatus.showWait(transfer, TX_HASH);
|
||||
expectAddressLine(node("wait-tx-to").innerHTML);
|
||||
});
|
||||
|
||||
test("a contract creation says so on the success and error screens", () => {
|
||||
state.viewData = {
|
||||
amount: "0.0000",
|
||||
symbol: "ETH",
|
||||
to: "",
|
||||
hash: TX_HASH,
|
||||
blockNumber: 1,
|
||||
};
|
||||
txStatus.renderSuccess();
|
||||
expect(node("success-tx-to").innerHTML).toBe(SENTENCE);
|
||||
|
||||
txStatus.showError(creation, TX_HASH, "The transaction failed.");
|
||||
expect(node("error-tx-to").innerHTML).toBe(SENTENCE);
|
||||
});
|
||||
|
||||
test("a transaction with a recipient shows its address on the success and error screens", () => {
|
||||
state.viewData = {
|
||||
amount: "0.0050",
|
||||
symbol: "ETH",
|
||||
to: RECIPIENT,
|
||||
hash: TX_HASH,
|
||||
blockNumber: 1,
|
||||
};
|
||||
txStatus.renderSuccess();
|
||||
expectAddressLine(node("success-tx-to").innerHTML);
|
||||
|
||||
txStatus.showError(transfer, TX_HASH, "The transaction failed.");
|
||||
expectAddressLine(node("error-tx-to").innerHTML);
|
||||
});
|
||||
});
|
||||
|
||||
// A transaction FROM sent, as the history lists hold it. The explorer reports a
|
||||
// contract creation with no `to`, which src/shared/transactions.js turns into
|
||||
// `to: ""`.
|
||||
function historyTx(to) {
|
||||
return {
|
||||
hash: TX_HASH,
|
||||
from: FROM,
|
||||
to,
|
||||
value: "0.0000",
|
||||
exactValue: "0.0",
|
||||
rawAmount: "0",
|
||||
rawUnit: "wei",
|
||||
symbol: "ETH",
|
||||
timestamp: 1790000000,
|
||||
isError: false,
|
||||
directionLabel: "Sent",
|
||||
direction: "sent",
|
||||
contractAddress: null,
|
||||
};
|
||||
}
|
||||
|
||||
// The detail view is opened with the transaction a history row holds.
|
||||
describe("the transaction detail view", () => {
|
||||
test("a contract creation says so", () => {
|
||||
transactionDetail.show(historyTx(""));
|
||||
expect(node("tx-detail-to").innerHTML).toBe(SENTENCE);
|
||||
expect(node("tx-detail-type").textContent).toBe("Contract Creation");
|
||||
});
|
||||
|
||||
test("a transaction with a recipient shows its address", () => {
|
||||
transactionDetail.show(historyTx(RECIPIENT));
|
||||
expectAddressLine(node("tx-detail-to").innerHTML);
|
||||
});
|
||||
});
|
||||
|
||||
// The same rows are drawn on Home, AddressDetail and AddressToken (for ETH).
|
||||
describe.each([
|
||||
["Home", "home-tx-list", () => home.render({})],
|
||||
["AddressDetail", "tx-list", () => addressDetail.show()],
|
||||
["AddressToken", "address-token-tx-list", () => addressToken.show()],
|
||||
])("the transaction history on %s", (_name, listId, open) => {
|
||||
async function rowsFor(tx) {
|
||||
mockHistory = [tx];
|
||||
open();
|
||||
// The list is drawn once the history has been fetched.
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
return node(listId).innerHTML;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
state.wallets = [
|
||||
{
|
||||
name: "Main",
|
||||
type: "key",
|
||||
addresses: [{ address: FROM, balance: "0.0000" }],
|
||||
},
|
||||
];
|
||||
state.selectedWallet = 0;
|
||||
state.selectedAddress = 0;
|
||||
state.selectedToken = "ETH";
|
||||
});
|
||||
|
||||
test("a contract creation's row says so, with no colour dot and no address line", async () => {
|
||||
const html = await rowsFor(historyTx(""));
|
||||
expect(html).toContain(SENTENCE);
|
||||
expect(html).not.toContain("background:");
|
||||
expect(html).not.toContain("am-address");
|
||||
expect(html).not.toContain("undefined");
|
||||
});
|
||||
|
||||
test("a transaction with a recipient shows its colour dot and address", async () => {
|
||||
const html = await rowsFor(historyTx(RECIPIENT));
|
||||
expectAddressLine(html);
|
||||
expect(html).toContain("background:#");
|
||||
expect(html).toContain(`<div class="am-address">${RECIPIENT}</div>`);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user