test(services): add unit tests for localeFormatting helpers (#298)

Adds 24 unit tests covering the five formatters exported by
`src/services/localeFormatting.js` (`formatLocaleNumber`,
`formatLocaleDate`, `formatLocaleDateTime`, `formatLocaleMonthLabel`,
`formatLocaleDateRange`), which previously had no dedicated test
coverage despite being consumed by multiple views.

The new spec follows the existing `tests/unit/**/*.spec.js` conventions:
- kebab-case file name (matches `date-only.spec.js` ↔ `dateOnly.js`)
- imports via the `@/` alias defined in `vitest.config.js`
- timezone-stable inputs (YYYY-MM-DD strings and `new Date(y, m, d)`
constructors)

Coverage added:
- `formatLocaleNumber`: locale-specific separators, fallback to `en` for
empty/null/undefined locale, whitespace trimming, NaN/non-numeric
coercion, currency option pass-through
- `formatLocaleDate`: YYYY-MM-DD and Date object inputs, custom option
merging, empty/invalid handling
- `formatLocaleDateTime`: hour/minute inclusion, default time fields,
custom option override, invalid input
- `formatLocaleMonthLabel`: long month + year, Date instance support,
invalid input
- `formatLocaleDateRange`: full range, single-date collapse,
missing/invalid start or end, both-empty, locale-aware output

This PR was created by an AI agent (OpenHands) on behalf of the user.

---------

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Jeppe B
2026-08-13 12:43:29 +02:00
committed by GitHub
co-authored by openhands
parent c01596aeb5
commit 01c5864382
+143
View File
@@ -0,0 +1,143 @@
import { describe, expect, it } from "vitest";
import {
formatLocaleDate,
formatLocaleDateRange,
formatLocaleDateTime,
formatLocaleMonthLabel,
formatLocaleNumber,
} from "@/services/localeFormatting.js";
describe("formatLocaleNumber", () => {
it("formats a plain number using the requested locale", () => {
expect(formatLocaleNumber(1234.5, "en-US")).toBe("1,234.5");
});
it("uses locale-specific decimal and grouping separators", () => {
expect(formatLocaleNumber(1234.5, "da-DK")).toBe("1.234,5");
});
it("falls back to the en locale when the locale argument is empty, null, or undefined", () => {
expect(formatLocaleNumber(1234.5, "")).toBe(formatLocaleNumber(1234.5, "en"));
expect(formatLocaleNumber(1234.5, null)).toBe(formatLocaleNumber(1234.5, "en"));
expect(formatLocaleNumber(1234.5, undefined)).toBe(formatLocaleNumber(1234.5, "en"));
});
it("trims surrounding whitespace around the locale argument", () => {
expect(formatLocaleNumber(42, " en-US ")).toBe(formatLocaleNumber(42, "en-US"));
});
it("coerces NaN and non-numeric values to 0", () => {
expect(formatLocaleNumber(NaN, "en-US")).toBe("0");
expect(formatLocaleNumber("not-a-number", "en-US")).toBe("0");
expect(formatLocaleNumber(null, "en-US")).toBe("0");
expect(formatLocaleNumber(undefined, "en-US")).toBe("0");
});
it("passes Intl options through (currency style)", () => {
expect(formatLocaleNumber(99.95, "en-US", { style: "currency", currency: "USD" })).toBe("$99.95");
});
it("formats integers without a fractional separator", () => {
expect(formatLocaleNumber(42, "en-US")).toBe("42");
});
});
describe("formatLocaleDate", () => {
it("formats a YYYY-MM-DD string with the locale's short month", () => {
expect(formatLocaleDate("2026-03-18", "en-US")).toBe("Mar 18, 2026");
expect(formatLocaleDate("2026-03-18", "da-DK")).toBe("18. mar. 2026");
});
it("formats a Date instance built from local-time components", () => {
const date = new Date(2026, 2, 18);
expect(formatLocaleDate(date, "en-US")).toBe("Mar 18, 2026");
});
it("merges custom Intl options over the defaults", () => {
expect(formatLocaleDate("2026-03-18", "en-US", { month: "long" })).toBe("March 18, 2026");
});
it("returns an empty string for invalid, empty, null, or undefined values", () => {
expect(formatLocaleDate("not-a-date", "en-US")).toBe("");
expect(formatLocaleDate("", "en-US")).toBe("");
expect(formatLocaleDate(" ", "en-US")).toBe("");
expect(formatLocaleDate(null, "en-US")).toBe("");
expect(formatLocaleDate(undefined, "en-US")).toBe("");
expect(formatLocaleDate(new Date("invalid"), "en-US")).toBe("");
});
});
describe("formatLocaleDateTime", () => {
it("includes hour and minute in the formatted output", () => {
const date = new Date(2026, 2, 18, 9, 30);
expect(formatLocaleDateTime(date, "en-US")).toBe("Mar 18, 2026, 09:30 AM");
});
it("formats a YYYY-MM-DD string with the locale's short month and time defaults", () => {
expect(formatLocaleDateTime("2026-03-18", "en-US")).toBe("Mar 18, 2026, 12:00 AM");
});
it("returns an empty string when the value cannot be parsed", () => {
expect(formatLocaleDateTime("not-a-date", "en-US")).toBe("");
expect(formatLocaleDateTime(null, "en-US")).toBe("");
});
it("honours custom Intl options for time and date parts", () => {
const date = new Date(2026, 2, 18, 9, 30);
expect(
formatLocaleDateTime(date, "en-US", {
hour: "2-digit",
minute: "2-digit",
year: undefined,
month: undefined,
day: undefined,
})
).toBe("09:30 AM");
});
});
describe("formatLocaleMonthLabel", () => {
it("returns the long month name paired with the year", () => {
expect(formatLocaleMonthLabel("2026-03-18", "en-US")).toBe("March 2026");
expect(formatLocaleMonthLabel("2026-03-18", "da-DK")).toBe("marts 2026");
});
it("formats a Date instance using only month and year", () => {
expect(formatLocaleMonthLabel(new Date(2026, 2, 18), "en-US")).toBe("March 2026");
});
it("returns an empty string for invalid input", () => {
expect(formatLocaleMonthLabel("not-a-date", "en-US")).toBe("");
expect(formatLocaleMonthLabel(null, "en-US")).toBe("");
});
});
describe("formatLocaleDateRange", () => {
it("joins the formatted from and to values with a dash", () => {
expect(formatLocaleDateRange("2026-03-01", "2026-03-31", "en-US")).toBe("Mar 1, 2026 - Mar 31, 2026");
});
it("returns the single date when both ends resolve to the same formatted value", () => {
expect(formatLocaleDateRange("2026-03-01", "2026-03-01", "en-US")).toBe("Mar 1, 2026");
});
it("returns only the start when the end is missing or invalid", () => {
expect(formatLocaleDateRange("2026-03-01", "", "en-US")).toBe("Mar 1, 2026");
expect(formatLocaleDateRange("2026-03-01", "not-a-date", "en-US")).toBe("Mar 1, 2026");
});
it("returns only the end when the start is missing or invalid", () => {
expect(formatLocaleDateRange("", "2026-03-31", "en-US")).toBe("Mar 31, 2026");
expect(formatLocaleDateRange("not-a-date", "2026-03-31", "en-US")).toBe("Mar 31, 2026");
});
it("returns an empty string when both ends are missing", () => {
expect(formatLocaleDateRange("", "", "en-US")).toBe("");
expect(formatLocaleDateRange(null, undefined, "en-US")).toBe("");
});
it("honours the requested locale for both ends", () => {
expect(formatLocaleDateRange("2026-03-01", "2026-03-31", "da-DK")).toBe("1. mar. 2026 - 31. mar. 2026");
});
});