From 01c58643822134db1bb168af21f63fafb4f5537e Mon Sep 17 00:00:00 2001 From: Jeppe B <2jepp9350@gmail.com> Date: Thu, 13 Aug 2026 12:43:29 +0200 Subject: [PATCH] test(services): add unit tests for localeFormatting helpers (#298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- tests/unit/locale-formatting.spec.js | 143 +++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 tests/unit/locale-formatting.spec.js diff --git a/tests/unit/locale-formatting.spec.js b/tests/unit/locale-formatting.spec.js new file mode 100644 index 00000000..6d5a615b --- /dev/null +++ b/tests/unit/locale-formatting.spec.js @@ -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"); + }); +});