fix: a saved wait in another shape reaches SuccessTx when its receipt arrives (closes #734) #739
@@ -787,7 +787,7 @@ Shown after a broadcast. The screen holds:
|
|||||||
- the heading "Transaction Broadcast", with no "Back" button;
|
- the heading "Transaction Broadcast", with no "Back" button;
|
||||||
- the amount and symbol;
|
- the amount and symbol;
|
||||||
- each `Moved from the sender` amount of a swap;
|
- each `Moved from the sender` amount of a swap;
|
||||||
- each `Permit2 allowance` amount of a swap;
|
- each `Permit2 allowance` amount of a swap, with its `Allowance token` line;
|
||||||
- the recipient;
|
- the recipient;
|
||||||
- the transaction hash, with an explorer link;
|
- the transaction hash, with an explorer link;
|
||||||
- the counter "Waiting for confirmation... Ns".
|
- the counter "Waiting for confirmation... Ns".
|
||||||
@@ -827,7 +827,7 @@ The screen holds:
|
|||||||
- the heading "Transaction Failed";
|
- the heading "Transaction Failed";
|
||||||
- the amount;
|
- the amount;
|
||||||
- each `Moved from the sender` amount of a swap;
|
- each `Moved from the sender` amount of a swap;
|
||||||
- each `Permit2 allowance` amount of a swap;
|
- each `Permit2 allowance` amount of a swap, with its `Allowance token` line;
|
||||||
- the recipient;
|
- the recipient;
|
||||||
- the error message, in a dashed box;
|
- the error message, in a dashed box;
|
||||||
- the transaction hash, when one exists;
|
- the transaction hash, when one exists;
|
||||||
|
|||||||
@@ -810,7 +810,10 @@
|
|||||||
<h2 class="font-bold mb-2">Transaction Broadcast</h2>
|
<h2 class="font-bold mb-2">Transaction Broadcast</h2>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="text-xs text-muted mb-1">Amount</div>
|
<div class="text-xs text-muted mb-1">Amount</div>
|
||||||
<div id="wait-tx-summary" class="font-bold break-all"></div>
|
<div
|
||||||
|
id="wait-tx-summary"
|
||||||
|
class="font-bold break-words"
|
||||||
|
></div>
|
||||||
<div id="wait-tx-token-note"></div>
|
<div id="wait-tx-token-note"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -859,7 +862,7 @@
|
|||||||
<div class="text-xs text-muted mb-1">Amount</div>
|
<div class="text-xs text-muted mb-1">Amount</div>
|
||||||
<div
|
<div
|
||||||
id="error-tx-summary"
|
id="error-tx-summary"
|
||||||
class="font-bold break-all"
|
class="font-bold break-words"
|
||||||
></div>
|
></div>
|
||||||
<div id="error-tx-token-note"></div>
|
<div id="error-tx-token-note"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+54
-30
@@ -73,25 +73,53 @@ function toAddressHtml(address, chainId) {
|
|||||||
return renderAddressHtml(address, { title, chainId });
|
return renderAddressHtml(address, { title, chainId });
|
||||||
}
|
}
|
||||||
|
|
||||||
// The amount of each `Moved from the sender` and `Permit2 allowance` line of a
|
// A decoded record can come back from storage in any shape, so these screens
|
||||||
// decoded swap, for the wait and error screens to show under the Amount figure.
|
// show only the lines whose label and value are text. An address that is not
|
||||||
// That figure can leave these amounts out, so alone it can understate what can
|
// text would throw in toAddressHtml().
|
||||||
// leave the wallet. `decoded` can come back from storage in any shape, so this
|
function isReadableLine(d) {
|
||||||
// shows only the lines whose value is text.
|
return (
|
||||||
function movedOrAllowedHtml(decoded) {
|
d &&
|
||||||
|
typeof d.label === "string" &&
|
||||||
|
typeof d.value === "string" &&
|
||||||
|
(!d.address || typeof d.address === "string")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// What a decoded line with an address shows: the address, under the token's
|
||||||
|
// symbol when the line names a token.
|
||||||
|
function decodedAddressHtml(d, chainId) {
|
||||||
|
if (!d.isToken) return toAddressHtml(d.address, chainId);
|
||||||
|
const sym = tokenLabel(d.address, chainId) || "Unknown token";
|
||||||
|
return (
|
||||||
|
`<div class="font-bold">${escapeHtml(sym)}</div>` +
|
||||||
|
blockscoutOffTokenNoteHtml(d.address, chainId) +
|
||||||
|
toAddressHtml(d.address, chainId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each `Moved from the sender`, `Permit2 allowance` and `Allowance token` line
|
||||||
|
// of a decoded swap, for the wait and error screens to show under the Amount
|
||||||
|
// figure. That figure can leave these amounts out, so alone it can understate
|
||||||
|
// what can leave the wallet. The token line says which token an allowance
|
||||||
|
// covers, which `Unlimited` alone does not.
|
||||||
|
function movedOrAllowedHtml(decoded, chainId) {
|
||||||
if (!decoded || !Array.isArray(decoded.details)) return "";
|
if (!decoded || !Array.isArray(decoded.details)) return "";
|
||||||
let html = "";
|
let html = "";
|
||||||
for (const d of decoded.details) {
|
for (const d of decoded.details) {
|
||||||
if (
|
if (
|
||||||
!d ||
|
!isReadableLine(d) ||
|
||||||
(d.label !== "Moved from the sender" &&
|
(d.label !== "Moved from the sender" &&
|
||||||
d.label !== "Permit2 allowance") ||
|
d.label !== "Permit2 allowance" &&
|
||||||
typeof d.value !== "string"
|
d.label !== "Allowance token")
|
||||||
)
|
)
|
||||||
continue;
|
continue;
|
||||||
html += `<div class="mt-2">`;
|
html += `<div class="mt-2">`;
|
||||||
html += `<div class="text-xs text-muted mb-1">${escapeHtml(d.label)}</div>`;
|
html += `<div class="text-xs text-muted mb-1">${escapeHtml(d.label)}</div>`;
|
||||||
html += `<div class="font-bold break-all">${escapeHtml(d.value)}</div>`;
|
if (d.address) {
|
||||||
|
html += `<div class="text-xs">${decodedAddressHtml(d, chainId)}</div>`;
|
||||||
|
} else {
|
||||||
|
html += `<div class="font-bold break-words">${escapeHtml(d.value)}</div>`;
|
||||||
|
}
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
}
|
}
|
||||||
return html;
|
return html;
|
||||||
@@ -135,7 +163,7 @@ function startWait(txInfo, txHash, broadcastTime, pollNow) {
|
|||||||
txInfo.amount + (symbol ? " " + symbol : "");
|
txInfo.amount + (symbol ? " " + symbol : "");
|
||||||
$("wait-tx-token-note").innerHTML =
|
$("wait-tx-token-note").innerHTML =
|
||||||
blockscoutOffTokenNoteHtml(txInfo.token, txInfo.chainId) +
|
blockscoutOffTokenNoteHtml(txInfo.token, txInfo.chainId) +
|
||||||
movedOrAllowedHtml(txInfo.decoded);
|
movedOrAllowedHtml(txInfo.decoded, txInfo.chainId);
|
||||||
$("wait-tx-to").innerHTML = toAddressHtml(txInfo.to, txInfo.chainId);
|
$("wait-tx-to").innerHTML = toAddressHtml(txInfo.to, txInfo.chainId);
|
||||||
$("wait-tx-hash").innerHTML = txHashHtml(txHash, txInfo.chainId);
|
$("wait-tx-hash").innerHTML = txHashHtml(txHash, txInfo.chainId);
|
||||||
attachCopyHandlers("view-wait-tx");
|
attachCopyHandlers("view-wait-tx");
|
||||||
@@ -246,7 +274,7 @@ function showWait(txInfo, txHash) {
|
|||||||
// to name a network. txInfo.token and txInfo.tokenSymbol are not checked here.
|
// to name a network. txInfo.token and txInfo.tokenSymbol are not checked here.
|
||||||
// blockscoutOffTokenNoteHtml() shows no line for a token that is not text.
|
// blockscoutOffTokenNoteHtml() shows no line for a token that is not text.
|
||||||
// Everything else only compares and coalesces them, and tokenSymbol is null for
|
// Everything else only compares and coalesces them, and tokenSymbol is null for
|
||||||
// ETH. txInfo.decoded is not checked either: see movedOrAllowedHtml().
|
// ETH. txInfo.decoded is not checked either: see isReadableLine().
|
||||||
function restoreWait() {
|
function restoreWait() {
|
||||||
const d = state.viewData;
|
const d = state.viewData;
|
||||||
if (!d || !d.pendingWait) return false;
|
if (!d || !d.pendingWait) return false;
|
||||||
@@ -293,29 +321,25 @@ function showSuccess(txInfo, txHash, blockNumber) {
|
|||||||
ctx.doRefreshAndRender();
|
ctx.doRefreshAndRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The decoded action, or "" when no line of it can be read.
|
||||||
function decodedDetailsHtml(decoded, chainId) {
|
function decodedDetailsHtml(decoded, chainId) {
|
||||||
if (!decoded || !decoded.details) return "";
|
if (!decoded || !Array.isArray(decoded.details)) return "";
|
||||||
|
const lines = decoded.details.filter(isReadableLine);
|
||||||
|
if (lines.length === 0) return "";
|
||||||
let html = `<div class="border border-border border-dashed p-2 mb-3">`;
|
let html = `<div class="border border-border border-dashed p-2 mb-3">`;
|
||||||
if (decoded.name) {
|
if (decoded.name && typeof decoded.name === "string") {
|
||||||
html += `<div class="mb-2"><div class="text-xs text-muted mb-1">Action</div>`;
|
html += `<div class="mb-2"><div class="text-xs text-muted mb-1">Action</div>`;
|
||||||
html += `<div class="font-bold">${escapeHtml(decoded.name)}</div></div>`;
|
html += `<div class="font-bold">${escapeHtml(decoded.name)}</div></div>`;
|
||||||
}
|
}
|
||||||
if (decoded.description) {
|
if (decoded.description && typeof decoded.description === "string") {
|
||||||
html += `<div class="mb-2"><div class="text-xs text-muted mb-1">Description</div>`;
|
html += `<div class="mb-2"><div class="text-xs text-muted mb-1">Description</div>`;
|
||||||
html += `<div>${escapeHtml(decoded.description)}</div></div>`;
|
html += `<div>${escapeHtml(decoded.description)}</div></div>`;
|
||||||
}
|
}
|
||||||
for (const d of decoded.details) {
|
for (const d of lines) {
|
||||||
html += `<div class="mb-2">`;
|
html += `<div class="mb-2">`;
|
||||||
html += `<div class="text-xs text-muted mb-1">${escapeHtml(d.label)}</div>`;
|
html += `<div class="text-xs text-muted mb-1">${escapeHtml(d.label)}</div>`;
|
||||||
if (d.address) {
|
if (d.address) {
|
||||||
if (d.isToken) {
|
html += decodedAddressHtml(d, chainId);
|
||||||
const sym = tokenLabel(d.address, chainId) || "Unknown token";
|
|
||||||
html += `<div class="font-bold">${escapeHtml(sym)}</div>`;
|
|
||||||
html += blockscoutOffTokenNoteHtml(d.address, chainId);
|
|
||||||
html += toAddressHtml(d.address, chainId);
|
|
||||||
} else {
|
|
||||||
html += toAddressHtml(d.address, chainId);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
html += `<div class="font-bold">${escapeHtml(d.value)}</div>`;
|
html += `<div class="font-bold">${escapeHtml(d.value)}</div>`;
|
||||||
}
|
}
|
||||||
@@ -329,13 +353,13 @@ function renderSuccess() {
|
|||||||
const d = state.viewData;
|
const d = state.viewData;
|
||||||
if (!d || !d.hash) return;
|
if (!d || !d.hash) return;
|
||||||
|
|
||||||
const hasDecoded = d.decoded && d.decoded.details;
|
const decodedHtml = decodedDetailsHtml(d.decoded, d.chainId);
|
||||||
|
|
||||||
// When decoded details are present, the Amount and To are already
|
// When decoded details are present, the Amount and To are already
|
||||||
// shown inside the decoded well — hide the top-level duplicates.
|
// shown inside the decoded well — hide the top-level duplicates.
|
||||||
const summarySection = $("success-tx-summary").parentElement;
|
const summarySection = $("success-tx-summary").parentElement;
|
||||||
const toSection = $("success-tx-to").parentElement;
|
const toSection = $("success-tx-to").parentElement;
|
||||||
if (hasDecoded) {
|
if (decodedHtml) {
|
||||||
summarySection.classList.add("hidden");
|
summarySection.classList.add("hidden");
|
||||||
toSection.classList.add("hidden");
|
toSection.classList.add("hidden");
|
||||||
} else {
|
} else {
|
||||||
@@ -351,8 +375,8 @@ function renderSuccess() {
|
|||||||
|
|
||||||
// Show decoded calldata details if present
|
// Show decoded calldata details if present
|
||||||
const decodedEl = $("success-tx-decoded");
|
const decodedEl = $("success-tx-decoded");
|
||||||
if (decodedEl && hasDecoded) {
|
if (decodedEl && decodedHtml) {
|
||||||
decodedEl.innerHTML = decodedDetailsHtml(d.decoded, d.chainId);
|
decodedEl.innerHTML = decodedHtml;
|
||||||
decodedEl.classList.remove("hidden");
|
decodedEl.classList.remove("hidden");
|
||||||
} else if (decodedEl) {
|
} else if (decodedEl) {
|
||||||
decodedEl.classList.add("hidden");
|
decodedEl.classList.add("hidden");
|
||||||
@@ -392,18 +416,18 @@ function renderError() {
|
|||||||
d.amount + (d.symbol ? " " + d.symbol : "");
|
d.amount + (d.symbol ? " " + d.symbol : "");
|
||||||
$("error-tx-token-note").innerHTML =
|
$("error-tx-token-note").innerHTML =
|
||||||
blockscoutOffTokenNoteHtml(d.token, d.chainId) +
|
blockscoutOffTokenNoteHtml(d.token, d.chainId) +
|
||||||
movedOrAllowedHtml(d.decoded);
|
movedOrAllowedHtml(d.decoded, d.chainId);
|
||||||
$("error-tx-to").innerHTML = toAddressHtml(d.to, d.chainId);
|
$("error-tx-to").innerHTML = toAddressHtml(d.to, d.chainId);
|
||||||
$("error-tx-message").textContent = d.message;
|
$("error-tx-message").textContent = d.message;
|
||||||
|
|
||||||
if (d.hash) {
|
if (d.hash) {
|
||||||
$("error-tx-hash").innerHTML = txHashHtml(d.hash, d.chainId);
|
$("error-tx-hash").innerHTML = txHashHtml(d.hash, d.chainId);
|
||||||
$("error-tx-hash-section").classList.remove("hidden");
|
$("error-tx-hash-section").classList.remove("hidden");
|
||||||
attachCopyHandlers("view-error-tx");
|
|
||||||
} else {
|
} else {
|
||||||
$("error-tx-hash-section").classList.add("hidden");
|
$("error-tx-hash-section").classList.add("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attachCopyHandlers("view-error-tx");
|
||||||
showView("error-tx");
|
showView("error-tx");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -458,6 +458,9 @@ describe("a swap that gives another spender an allowance", () => {
|
|||||||
const html = node(id).innerHTML;
|
const html = node(id).innerHTML;
|
||||||
expect(html).toContain("Permit2 allowance");
|
expect(html).toContain("Permit2 allowance");
|
||||||
expect(html).toContain("Unlimited");
|
expect(html).toContain("Unlimited");
|
||||||
|
expect(html).toContain("Allowance token");
|
||||||
|
expect(html).toContain('<div class="font-bold">USDC</div>');
|
||||||
|
expect(html).toContain(`data-copy="${USDC}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
test("the wait screen shows the allowance under the Amount figure", async () => {
|
test("the wait screen shows the allowance under the Amount figure", async () => {
|
||||||
|
|||||||
+158
-10
@@ -617,8 +617,8 @@ describe("SuccessTx and ErrorTx for a Sepolia transaction with mainnet active",
|
|||||||
const decoded = {
|
const decoded = {
|
||||||
name: "Transfer",
|
name: "Transfer",
|
||||||
details: [
|
details: [
|
||||||
{ label: "Token", address: TOKEN, isToken: true },
|
{ label: "Token", value: TOKEN, address: TOKEN, isToken: true },
|
||||||
{ label: "Recipient", address: RECIPIENT },
|
{ label: "Recipient", value: RECIPIENT, address: RECIPIENT },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
mockReceiptLookup.mockResolvedValue({ blockNumber: 21000000 });
|
mockReceiptLookup.mockResolvedValue({ blockNumber: 21000000 });
|
||||||
@@ -644,7 +644,9 @@ describe("SuccessTx and ErrorTx for a Sepolia transaction with mainnet active",
|
|||||||
};
|
};
|
||||||
const decoded = {
|
const decoded = {
|
||||||
name: "Transfer",
|
name: "Transfer",
|
||||||
details: [{ label: "Token", address: TOKEN, isToken: true }],
|
details: [
|
||||||
|
{ label: "Token", value: TOKEN, address: TOKEN, isToken: true },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
mockReceiptLookup.mockResolvedValue({ blockNumber: 21000000 });
|
mockReceiptLookup.mockResolvedValue({ blockNumber: 21000000 });
|
||||||
txStatus.showWait({ ...sepoliaTx, decoded }, TX_HASH);
|
txStatus.showWait({ ...sepoliaTx, decoded }, TX_HASH);
|
||||||
@@ -747,6 +749,21 @@ describe("the Moved from the sender lines of a swap on WaitTx and ErrorTx", () =
|
|||||||
{ details: [moved(7)] },
|
{ details: [moved(7)] },
|
||||||
{ details: [moved({ toString: 1 })] },
|
{ details: [moved({ toString: 1 })] },
|
||||||
{ details: [{ label: "Permit2 allowance", value: 7 }] },
|
{ details: [{ label: "Permit2 allowance", value: 7 }] },
|
||||||
|
{
|
||||||
|
details: [
|
||||||
|
{ label: "Permit2 allowance", value: "text", address: 7 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
details: [
|
||||||
|
{
|
||||||
|
label: "Allowance token",
|
||||||
|
value: "text",
|
||||||
|
address: 7,
|
||||||
|
isToken: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
]) {
|
]) {
|
||||||
expect(noteOnReopen(saved)).toBe("");
|
expect(noteOnReopen(saved)).toBe("");
|
||||||
}
|
}
|
||||||
@@ -757,11 +774,22 @@ describe("the Moved from the sender lines of a swap on WaitTx and ErrorTx", () =
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("the Permit2 allowance lines of a swap on WaitTx and ErrorTx", () => {
|
describe("the Permit2 allowance lines of a swap on WaitTx and ErrorTx", () => {
|
||||||
|
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
|
||||||
const allowance = (value) => ({ label: "Permit2 allowance", value });
|
const allowance = (value) => ({ label: "Permit2 allowance", value });
|
||||||
const decoded = {
|
const decoded = {
|
||||||
details: [
|
details: [
|
||||||
allowance("Unlimited"),
|
allowance("Unlimited"),
|
||||||
{ label: "Allowance token", value: "USDC" },
|
{
|
||||||
|
label: "Allowance token",
|
||||||
|
value: "USDC (" + USDC + ")",
|
||||||
|
address: USDC,
|
||||||
|
isToken: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Allowance spender",
|
||||||
|
value: RECIPIENT,
|
||||||
|
address: RECIPIENT,
|
||||||
|
},
|
||||||
allowance("<b>400.0000</b> DAI"),
|
allowance("<b>400.0000</b> DAI"),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -771,9 +799,53 @@ describe("the Permit2 allowance lines of a swap on WaitTx and ErrorTx", () => {
|
|||||||
expect(html).toContain("Permit2 allowance");
|
expect(html).toContain("Permit2 allowance");
|
||||||
expect(html).toContain("Unlimited");
|
expect(html).toContain("Unlimited");
|
||||||
expect(html).toContain("<b>400.0000</b> DAI");
|
expect(html).toContain("<b>400.0000</b> DAI");
|
||||||
expect(html).not.toContain("Allowance token");
|
expect(html).not.toContain("Allowance spender");
|
||||||
|
expect(html).not.toContain(RECIPIENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `Unlimited` alone does not say which token it covers. The address gets
|
||||||
|
// the row of its own that every address gets, in the transaction's network.
|
||||||
|
function expectTokenLine(id) {
|
||||||
|
const html = getElement(id).innerHTML;
|
||||||
|
expect(html.indexOf("Unlimited")).toBeLessThan(
|
||||||
|
html.indexOf("Allowance token"),
|
||||||
|
);
|
||||||
|
expect(html).toContain('<div class="font-bold">USDC</div>');
|
||||||
|
expect(html).toContain(`data-copy="${USDC}"`);
|
||||||
|
expect(html).toContain(`href="https://etherscan.io/address/${USDC}"`);
|
||||||
|
expect(html).toMatch(/<div class="text-xs">.*class="am-address"/);
|
||||||
|
}
|
||||||
|
|
||||||
|
test("WaitTx says which token an allowance covers", () => {
|
||||||
|
mockReceiptLookup.mockResolvedValue(null);
|
||||||
|
txStatus.showWait({ ...TX_INFO, decoded }, TX_HASH);
|
||||||
|
expectTokenLine("wait-tx-token-note");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ErrorTx says which token an allowance covers", () => {
|
||||||
|
txStatus.showError({ ...TX_INFO, decoded }, TX_HASH, "It failed.");
|
||||||
|
expectTokenLine("error-tx-token-note");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the token's address links to the transaction's network", () => {
|
||||||
|
const sepoliaTx = {
|
||||||
|
...TX_INFO,
|
||||||
|
chainId: NETWORKS.sepolia.chainId,
|
||||||
|
decoded,
|
||||||
|
};
|
||||||
|
txStatus.showError(sepoliaTx, TX_HASH, "It failed.");
|
||||||
|
expect(getElement("error-tx-token-note").innerHTML).toContain(
|
||||||
|
`href="https://sepolia.etherscan.io/address/${USDC}"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the token's address can be copied on an ErrorTx with no hash", () => {
|
||||||
|
const view = getElement("view-error-tx");
|
||||||
|
view.querySelectorAll = jest.fn(() => []);
|
||||||
|
txStatus.showError({ ...TX_INFO, decoded }, null, "It failed.");
|
||||||
|
expect(view.querySelectorAll).toHaveBeenCalledWith("[data-copy]");
|
||||||
|
});
|
||||||
|
|
||||||
test("WaitTx shows each line's amount, escaped", () => {
|
test("WaitTx shows each line's amount, escaped", () => {
|
||||||
mockReceiptLookup.mockResolvedValue(null);
|
mockReceiptLookup.mockResolvedValue(null);
|
||||||
txStatus.showWait({ ...TX_INFO, decoded }, TX_HASH);
|
txStatus.showWait({ ...TX_INFO, decoded }, TX_HASH);
|
||||||
@@ -795,7 +867,8 @@ describe("the Permit2 allowance lines of a swap on WaitTx and ErrorTx", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// An amount of about 47 digits or more is wider than the screen. Without the
|
// An amount of about 47 digits or more is wider than the screen. Without the
|
||||||
// break-all class its last digits are cut off at the edge.
|
// break-words class its last digits are cut off at the edge. That class keeps
|
||||||
|
// the words of a line such as `(decimals unknown)` whole.
|
||||||
describe("a long amount on WaitTx and ErrorTx", () => {
|
describe("a long amount on WaitTx and ErrorTx", () => {
|
||||||
test("each amount under the Amount figure wraps", () => {
|
test("each amount under the Amount figure wraps", () => {
|
||||||
const decoded = {
|
const decoded = {
|
||||||
@@ -806,8 +879,10 @@ describe("a long amount on WaitTx and ErrorTx", () => {
|
|||||||
};
|
};
|
||||||
txStatus.showError({ ...TX_INFO, decoded }, TX_HASH, "It failed.");
|
txStatus.showError({ ...TX_INFO, decoded }, TX_HASH, "It failed.");
|
||||||
const html = getElement("error-tx-token-note").innerHTML;
|
const html = getElement("error-tx-token-note").innerHTML;
|
||||||
expect(html).toContain('<div class="font-bold break-all">1.0000 ETH<');
|
expect(html).toContain(
|
||||||
expect(html).toContain('<div class="font-bold break-all">Unlimited<');
|
'<div class="font-bold break-words">1.0000 ETH<',
|
||||||
|
);
|
||||||
|
expect(html).toContain('<div class="font-bold break-words">Unlimited<');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("the Amount figure wraps", () => {
|
test("the Amount figure wraps", () => {
|
||||||
@@ -816,14 +891,87 @@ describe("a long amount on WaitTx and ErrorTx", () => {
|
|||||||
"utf8",
|
"utf8",
|
||||||
);
|
);
|
||||||
expect(html).toMatch(
|
expect(html).toMatch(
|
||||||
/id="wait-tx-summary"\s+class="font-bold break-all"/,
|
/id="wait-tx-summary"\s+class="font-bold break-words"/,
|
||||||
);
|
);
|
||||||
expect(html).toMatch(
|
expect(html).toMatch(
|
||||||
/id="error-tx-summary"\s+class="font-bold break-all"/,
|
/id="error-tx-summary"\s+class="font-bold break-words"/,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// A saved wait comes back from storage, in whatever shape it is in. A throw when
|
||||||
|
// its receipt arrives would leave the popup on WaitTx with the counter stopped.
|
||||||
|
describe("a saved wait in another shape when its receipt arrives", () => {
|
||||||
|
const LINE = { label: "Steps", value: "V3_SWAP_EXACT_IN" };
|
||||||
|
|
||||||
|
async function receiptForSavedWait(decoded) {
|
||||||
|
mockReceiptLookup.mockResolvedValue({ blockNumber: 21000000 });
|
||||||
|
state.viewData = {
|
||||||
|
pendingWait: {
|
||||||
|
hash: TX_HASH,
|
||||||
|
txInfo: { ...TX_INFO, decoded },
|
||||||
|
broadcastTime: Date.now(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(txStatus.restoreWait()).toBe(true);
|
||||||
|
await jest.advanceTimersByTimeAsync(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
{ details: 7 },
|
||||||
|
{ details: "text" },
|
||||||
|
{ details: { label: "Steps", value: "text" } },
|
||||||
|
{ details: [null] },
|
||||||
|
{ details: [7] },
|
||||||
|
{ details: ["text"] },
|
||||||
|
{ details: [{ value: "text" }] },
|
||||||
|
{ details: [{ label: 7, value: "text" }] },
|
||||||
|
{ details: [{ label: { toString: 1 }, value: "text" }] },
|
||||||
|
{ details: [{ label: "Steps" }] },
|
||||||
|
{ details: [{ label: "Steps", value: 7 }] },
|
||||||
|
{ details: [{ label: "Steps", value: { toString: 1 } }] },
|
||||||
|
{ details: [{ label: "Steps", address: RECIPIENT }] },
|
||||||
|
{ details: [{ label: "Steps", value: "text", address: 7 }] },
|
||||||
|
{
|
||||||
|
details: [
|
||||||
|
{ label: "Steps", value: "text", address: 7, isToken: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: "Swap", description: "A swap", details: [null] },
|
||||||
|
])("%j shows SuccessTx with the amount and recipient", async (decoded) => {
|
||||||
|
await receiptForSavedWait(decoded);
|
||||||
|
expect(state.currentView).toBe("success-tx");
|
||||||
|
expect(
|
||||||
|
getElement("success-tx-decoded").classList.contains("hidden"),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
getElement("success-tx-summary-parent").classList.contains(
|
||||||
|
"hidden",
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
expect(getElement("success-tx-summary").textContent).toBe("0.0050 ETH");
|
||||||
|
expect(
|
||||||
|
getElement("success-tx-to-parent").classList.contains("hidden"),
|
||||||
|
).toBe(false);
|
||||||
|
expect(getElement("success-tx-to").innerHTML).toContain(RECIPIENT);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
{ details: [null, LINE, 7] },
|
||||||
|
{ name: 7, details: [LINE] },
|
||||||
|
{ name: { toString: 1 }, details: [LINE] },
|
||||||
|
{ description: 7, details: [LINE] },
|
||||||
|
{ description: { toString: 1 }, details: [LINE] },
|
||||||
|
])("%j shows SuccessTx with the line it can read", async (decoded) => {
|
||||||
|
await receiptForSavedWait(decoded);
|
||||||
|
expect(state.currentView).toBe("success-tx");
|
||||||
|
const html = getElement("success-tx-decoded").innerHTML;
|
||||||
|
expect(html).toContain("V3_SWAP_EXACT_IN");
|
||||||
|
expect(html).not.toContain("Action");
|
||||||
|
expect(html).not.toContain("Description");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("wait-tx is a view the popup may reopen onto", () => {
|
describe("wait-tx is a view the popup may reopen onto", () => {
|
||||||
// The resume feature is wired through RESTORABLE_VIEWS: restoreView()
|
// The resume feature is wired through RESTORABLE_VIEWS: restoreView()
|
||||||
// refuses any view not in the set, so dropping "wait-tx" from it kills
|
// refuses any view not in the set, so dropping "wait-tx" from it kills
|
||||||
|
|||||||
Reference in New Issue
Block a user