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:
@@ -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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user