// Tests for the pricing picker's pure helpers. node home/test/fx.test.mjs import assert from "node:assert/strict"; import { BANDS, bandName, bandsOffered, tierForBand, regionCurrency, approxLocal, capNote } from "../web/static/fx.js"; let failures = 0; async function test(name, fn) { try { await fn(); console.log("PASS " + name); } catch (e) { console.log("FAIL " + name + " — " + (e && e.message)); failures++; } } const tiers = [ { key: "family", name: "Family", label: "€5/year", eur: 5 }, { key: "friends", name: "Friends", label: "€4/month", eur: 4, bands: { reduced: { label: "€2/month", eur: 2 }, supporter: { label: "€5.50/month", eur: 5.5 } } }, ]; await test("band vocabulary is the doc's four, in display order", () => { assert.deepEqual(BANDS.map((b) => b.key), ["supporter", "base", "reduced", "solidarity"]); assert.equal(bandName("base"), "Standard"); // The messaging rules: aggregate cross-subsidy only, never "discount"/"eligible". for (const b of BANDS) { assert.ok(!/discount|eligible/i.test(b.line), b.key + " line breaks the messaging rules"); } }); await test("bandsOffered: base always; others once any tier defines them", () => { assert.deepEqual(bandsOffered([tiers[0]]), ["base"]); assert.deepEqual(bandsOffered(tiers), ["supporter", "base", "reduced"]); assert.deepEqual(bandsOffered([]), ["base"]); }); await test("tierForBand: band label when offered, base flagged when not", () => { assert.deepEqual(tierForBand(tiers[1], "reduced"), { label: "€2/month", eur: 2, band: "reduced", offered: true }); assert.deepEqual(tierForBand(tiers[1], "base"), { label: "€4/month", eur: 4, band: "base", offered: true }); // family has no bands: a reduced selection falls back to base, UNOFFERED — // the UI says "standard price" instead of implying a reduction. assert.deepEqual(tierForBand(tiers[0], "reduced"), { label: "€5/year", eur: 5, band: "base", offered: false }); }); await test("regionCurrency: locale region → ECB currency; euro area and unknown → none", () => { assert.equal(regionCurrency("en-US"), "USD"); assert.equal(regionCurrency("en-GB"), "GBP"); assert.equal(regionCurrency("ja"), "JPY"); // maximize() infers the region assert.equal(regionCurrency("de-DE"), ""); // euro area: billed in EUR already assert.equal(regionCurrency("fr"), ""); assert.equal(regionCurrency(""), ""); assert.equal(regionCurrency("not a locale"), ""); }); await test("approxLocal: converts and formats; silent when it can't be honest", () => { const rates = { USD: 1.1, JPY: 170 }; assert.equal(approxLocal(5, "USD", rates, "en"), "≈ $5.50"); // Larger amounts drop the cents (an estimate pretending precision reads wrong). assert.equal(approxLocal(999, "USD", rates, "en"), "≈ $1,099"); // Unknown currency / missing rate / zero amount → no figure at all. assert.equal(approxLocal(5, "CHF", rates, "en"), ""); assert.equal(approxLocal(0, "USD", rates, "en"), ""); assert.equal(approxLocal(5, "", rates, "en"), ""); assert.equal(approxLocal(5, "USD", null, "en"), ""); }); await test("capNote: the 15% promise in euros, not percent", () => { assert.equal(capNote(5, "en"), "never more than €0.75 more at renewal without your say-so"); assert.equal(capNote(0, "en"), ""); }); console.log(failures === 0 ? "\nALL PASSED" : `\n${failures} FAILED`); process.exit(failures === 0 ? 0 : 1);