test/shared/currency.test.ts
Rama main · réplica de lectura del repositorio original.
import { assertStringIncludes } from "@std/assert";
import { expect } from "@std/expect";
import { afterEach, beforeEach, describe, it as test } from "@std/testing/bdd";
import { ALL_SETTINGS_KEYS, settings } from "#db/settings.ts";
import {
formatCurrency,
formatSignedCurrency,
getDecimalPlaces,
toMajorUnits,
toMinorUnits,
} from "#shared/currency.ts";
import { expectIncludesAll } from "#test-utils/assertions.ts";
import { createTestDbWithSetup, resetDb } from "#test-utils/db.ts";
import { setupTestEncryptionKey } from "#test-utils/env.ts";
import { testWithSetting } from "#test-utils/settings.ts";
describe("currency", () => {
describe("getDecimalPlaces", () => {
test("returns 2 for GBP", () => {
expect(getDecimalPlaces("GBP")).toBe(2);
});
test("returns 2 for USD", () => {
expect(getDecimalPlaces("USD")).toBe(2);
});
test("returns 2 for EUR", () => {
expect(getDecimalPlaces("EUR")).toBe(2);
});
test("returns 0 for JPY", () => {
expect(getDecimalPlaces("JPY")).toBe(0);
});
test("returns 3 for KWD", () => {
expect(getDecimalPlaces("KWD")).toBe(3);
});
test("throws when Intl omits its guaranteed decimal places", () => {
const original = Intl.NumberFormat.prototype.resolvedOptions;
Intl.NumberFormat.prototype.resolvedOptions = function () {
return {
...original.call(this),
minimumFractionDigits: undefined as unknown as number,
};
};
try {
expect(() => getDecimalPlaces("CAD")).toThrow(
"Intl omitted currency decimal places for CAD",
);
} finally {
Intl.NumberFormat.prototype.resolvedOptions = original;
}
});
});
describe("formatCurrency", () => {
testWithSetting(
"formats GBP with pound symbol",
{ currency: "GBP" },
() => {
expect(formatCurrency(1050)).toBe("£10.50");
},
);
testWithSetting("formats GBP zero amount", { currency: "GBP" }, () => {
expect(formatCurrency(0)).toBe("£0");
});
testWithSetting(
"does not sign a negative zero",
{ currency: "GBP" },
() => {
expect(formatCurrency(-0)).toBe("£0");
},
);
testWithSetting(
"formats USD with dollar symbol",
{ currency: "USD" },
() => {
expect(formatCurrency(1050)).toBe("$10.50");
},
);
testWithSetting("formats EUR with euro symbol", { currency: "EUR" }, () => {
const result = formatCurrency(1050);
expectIncludesAll(result, ["10.50", "€"]);
});
testWithSetting(
"formats JPY without decimal places",
{ currency: "JPY" },
() => {
expect(formatCurrency(1050)).toBe("¥1,050");
},
);
testWithSetting(
"formats KWD with 3 decimal places",
{ currency: "KWD" },
() => {
const result = formatCurrency(1050);
assertStringIncludes(result, "1.050");
},
);
testWithSetting("accepts string input", { currency: "GBP" }, () => {
expect(formatCurrency("1050")).toBe("£10.50");
});
test("falls back to GBP when no currency loaded", () => {
expect(formatCurrency(1050)).toBe("£10.50");
});
});
describe("formatSignedCurrency", () => {
testWithSetting(
"uses one sign style and leaves zero unsigned",
{ currency: "GBP" },
() => {
expect(formatSignedCurrency(1250)).toBe("+£12.50");
expect(formatSignedCurrency(-1250)).toBe("−£12.50");
expect(formatSignedCurrency(0)).toBe("£0");
// The sign follows the value's own sign, not whether it reaches a
// whole minor unit, so a sliver still reads as added or removed.
expect(formatSignedCurrency(0.5)).toBe("+£0.01");
expect(formatSignedCurrency(-0.5)).toBe("−£0.01");
expect(formatSignedCurrency(1250, false)).toBe("£12.50");
expect(formatSignedCurrency(-1250, false)).toBe("−£12.50");
},
);
});
describe("toMinorUnits", () => {
testWithSetting(
"converts GBP major to minor units",
{ currency: "GBP" },
() => {
expect(toMinorUnits(10.5)).toBe(1050);
},
);
testWithSetting("converts whole number", { currency: "GBP" }, () => {
expect(toMinorUnits(25)).toBe(2500);
});
testWithSetting("rounds to nearest integer", { currency: "GBP" }, () => {
expect(toMinorUnits(10.999)).toBe(1100);
});
testWithSetting("converts JPY (no decimals)", { currency: "JPY" }, () => {
expect(toMinorUnits(1050)).toBe(1050);
});
testWithSetting("converts KWD (3 decimals)", { currency: "KWD" }, () => {
expect(toMinorUnits(1.05)).toBe(1050);
});
});
describe("toMajorUnits", () => {
testWithSetting(
"converts GBP minor to major units string",
{ currency: "GBP" },
() => {
expect(toMajorUnits(1050)).toBe("10.50");
},
);
testWithSetting("converts zero", { currency: "GBP" }, () => {
expect(toMajorUnits(0)).toBe("0.00");
});
testWithSetting("converts JPY (no decimals)", { currency: "JPY" }, () => {
expect(toMajorUnits(1050)).toBe("1050");
});
testWithSetting("converts KWD (3 decimals)", { currency: "KWD" }, () => {
expect(toMajorUnits(1050)).toBe("1.050");
});
});
describe("settings.currency integration", () => {
beforeEach(async () => {
setupTestEncryptionKey();
await createTestDbWithSetup("US");
await settings.loadKeys(ALL_SETTINGS_KEYS);
});
afterEach(() => {
resetDb();
});
test("reads currency from settings", () => {
expect(settings.currency).toBe("USD");
});
test("uses settings currency for formatting", () => {
expect(formatCurrency(1050)).toBe("$10.50");
});
});
});