import { describe, expect, it } from "vitest"; import { formatBytes, formatDate, formatDurationMs, formatNumber, formatRelativeTime, initials, truncate, } from "./formatters.js"; describe("formatBytes", () => { it("handles the spec edge cases", () => { expect(formatBytes(0)).toBe("0 B"); expect(formatBytes(500)).toBe("500 B"); expect(formatBytes(1)).toBe("1 B"); expect(formatBytes(1023)).toBe("1023 B"); expect(formatBytes(1024)).toBe("1 KB"); expect(formatBytes(1536)).toBe("1.5 KB"); expect(formatBytes(1048576)).toBe("1 MB"); }); it("uses 1024 steps with 1 decimal when <10", () => { expect(formatBytes(1.5 * 1024 * 1024)).toBe("1.5 MB"); expect(formatBytes(9.9 * 1024)).toBe("9.9 KB"); expect(formatBytes(10 * 1024)).toBe("10 KB"); expect(formatBytes(1.2 * 1024 * 1024 * 1024)).toBe("1.2 GB"); expect(formatBytes(2.5 * 1024 * 1024 * 1024)).toBe("2.5 GB"); expect(formatBytes(1500 * 1024)).toBe("1.5 MB"); // 1500 KB → 1.4648 MB → 1.5 MB }); it("clamps invalid input", () => { expect(formatBytes(-5)).toBe("0 B"); expect(formatBytes(NaN)).toBe("0 B"); expect(formatBytes(Infinity)).toBe("0 B"); }); }); describe("formatNumber", () => { it("formats with the injected locale", () => { expect(formatNumber(1234567, "en-US")).toBe("1,234,567"); expect(formatNumber(1234567, "de-DE")).toBe("1.234.567"); expect(formatNumber(1.5, "en-US")).toBe("1.5"); }); }); describe("formatRelativeTime (fixed now)", () => { const now = Date.parse("2025-06-01T12:00:00.000Z"); it("just now / minutes / hours / days / date", () => { expect(formatRelativeTime("2025-06-01T11:59:30.000Z", now)).toBe("just now"); expect(formatRelativeTime("2025-06-01T11:45:00.000Z", now)).toBe("15m ago"); expect(formatRelativeTime("2025-06-01T11:55:00.000Z", now)).toBe("5m ago"); expect(formatRelativeTime("2025-06-01T10:00:00.000Z", now)).toBe("2h ago"); expect(formatRelativeTime("2025-05-29T12:00:00.000Z", now)).toBe("3d ago"); // one ms short of 4 full days → still "3d ago" (floor) expect(formatRelativeTime("2025-05-28T12:00:00.001Z", now)).toBe("3d ago"); // beyond 7 days → locale date, never "… ago" const out8d = formatRelativeTime("2025-05-24T12:00:00.000Z", now); expect(out8d.includes("ago")).toBe(false); expect(out8d.includes("2025")).toBe(true); }); it("handles invalid ISO", () => { expect(formatRelativeTime("not-a-date", now)).toBe(""); }); }); describe("formatDurationMs", () => { it("formats seconds and minutes", () => { expect(formatDurationMs(1200)).toBe("1.2s"); expect(formatDurationMs(0)).toBe("0.0s"); expect(formatDurationMs(1500)).toBe("1.5s"); expect(formatDurationMs(12000)).toBe("12s"); expect(formatDurationMs(184000)).toBe("3m 04s"); expect(formatDurationMs(60_000)).toBe("1m 00s"); }); it("formats hours", () => { expect(formatDurationMs(3_723_000)).toBe("1h 02m 03s"); }); it("clamps invalid input", () => { expect(formatDurationMs(-100)).toBe("0s"); expect(formatDurationMs(NaN)).toBe("0s"); }); }); describe("initials", () => { it("two words → first letters", () => { expect(initials("Ada Lovelace")).toBe("AL"); expect(initials("Ada Lovelace")).toBe("AL"); }); it("single word → first two chars uppercased", () => { expect(initials("Grace")).toBe("GR"); expect(initials("a")).toBe("A"); }); it("many words → only first two", () => { expect(initials("Ada King Olivia Lovelace")).toBe("AK"); }); it("empty / whitespace → empty string", () => { expect(initials("")).toBe(""); expect(initials(" ")).toBe(""); }); }); describe("truncate", () => { it("leaves short strings alone", () => { expect(truncate("abc", 5)).toBe("abc"); expect(truncate("abc", 3)).toBe("abc"); }); it("adds ellipsis when longer", () => { expect(truncate("Hello world", 5)).toBe("Hello…"); expect(truncate("abc", 0)).toBe("…"); }); }); describe("formatDate", () => { it("includes all three parts and honors the injected locale", () => { const en = formatDate("2025-06-01T00:00:00.000Z", { locale: "en-US" }); const de = formatDate("2025-06-01T00:00:00.000Z", { locale: "de-DE" }); // locale-agnostic: day 1, June/06, year 2025 must all appear for (const out of [en, de]) { expect(out).toContain("2025"); expect(out).toMatch(/1/); expect(out).toMatch(/(Jun|06)/); } expect(en).not.toBe(de); // locales actually differ }); it("invalid → empty", () => { expect(formatDate("nope", { locale: "en-US" })).toBe(""); }); });