1import { describe, expect, it } from "vitest";
2import {
3 formatBytes,
4 formatDate,
5 formatDurationMs,
6 formatNumber,
7 formatRelativeTime,
8 initials,
9 truncate,
10} from "./formatters.js";
11
12describe("formatBytes", () => {
13 it("handles the spec edge cases", () => {
14 expect(formatBytes(0)).toBe("0 B");
15 expect(formatBytes(500)).toBe("500 B");
16 expect(formatBytes(1)).toBe("1 B");
17 expect(formatBytes(1023)).toBe("1023 B");
18 expect(formatBytes(1024)).toBe("1 KB");
19 expect(formatBytes(1536)).toBe("1.5 KB");
20 expect(formatBytes(1048576)).toBe("1 MB");
21 });
22
23 it("uses 1024 steps with 1 decimal when <10", () => {
24 expect(formatBytes(1.5 * 1024 * 1024)).toBe("1.5 MB");
25 expect(formatBytes(9.9 * 1024)).toBe("9.9 KB");
26 expect(formatBytes(10 * 1024)).toBe("10 KB");
27 expect(formatBytes(1.2 * 1024 * 1024 * 1024)).toBe("1.2 GB");
28 expect(formatBytes(2.5 * 1024 * 1024 * 1024)).toBe("2.5 GB");
29 expect(formatBytes(1500 * 1024)).toBe("1.5 MB");
30 });
31
32 it("clamps invalid input", () => {
33 expect(formatBytes(-5)).toBe("0 B");
34 expect(formatBytes(NaN)).toBe("0 B");
35 expect(formatBytes(Infinity)).toBe("0 B");
36 });
37});
38
39describe("formatNumber", () => {
40 it("formats with the injected locale", () => {
41 expect(formatNumber(1234567, "en-US")).toBe("1,234,567");
42 expect(formatNumber(1234567, "de-DE")).toBe("1.234.567");
43 expect(formatNumber(1.5, "en-US")).toBe("1.5");
44 });
45});
46
47describe("formatRelativeTime (fixed now)", () => {
48 const now = Date.parse("2025-06-01T12:00:00.000Z");
49
50 it("just now / minutes / hours / days / date", () => {
51 expect(formatRelativeTime("2025-06-01T11:59:30.000Z", now)).toBe("just now");
52 expect(formatRelativeTime("2025-06-01T11:45:00.000Z", now)).toBe("15m ago");
53 expect(formatRelativeTime("2025-06-01T11:55:00.000Z", now)).toBe("5m ago");
54 expect(formatRelativeTime("2025-06-01T10:00:00.000Z", now)).toBe("2h ago");
55 expect(formatRelativeTime("2025-05-29T12:00:00.000Z", now)).toBe("3d ago");
56
57 expect(formatRelativeTime("2025-05-28T12:00:00.001Z", now)).toBe("3d ago");
58
59 const out8d = formatRelativeTime("2025-05-24T12:00:00.000Z", now);
60 expect(out8d.includes("ago")).toBe(false);
61 expect(out8d.includes("2025")).toBe(true);
62 });
63
64 it("handles invalid ISO", () => {
65 expect(formatRelativeTime("not-a-date", now)).toBe("");
66 });
67});
68
69describe("formatDurationMs", () => {
70 it("formats seconds and minutes", () => {
71 expect(formatDurationMs(1200)).toBe("1.2s");
72 expect(formatDurationMs(0)).toBe("0.0s");
73 expect(formatDurationMs(1500)).toBe("1.5s");
74 expect(formatDurationMs(12000)).toBe("12s");
75 expect(formatDurationMs(184000)).toBe("3m 04s");
76 expect(formatDurationMs(60_000)).toBe("1m 00s");
77 });
78
79 it("formats hours", () => {
80 expect(formatDurationMs(3_723_000)).toBe("1h 02m 03s");
81 });
82
83 it("clamps invalid input", () => {
84 expect(formatDurationMs(-100)).toBe("0s");
85 expect(formatDurationMs(NaN)).toBe("0s");
86 });
87});
88
89describe("initials", () => {
90 it("two words → first letters", () => {
91 expect(initials("Ada Lovelace")).toBe("AL");
92 expect(initials("Ada Lovelace")).toBe("AL");
93 });
94
95 it("single word → first two chars uppercased", () => {
96 expect(initials("Grace")).toBe("GR");
97 expect(initials("a")).toBe("A");
98 });
99
100 it("many words → only first two", () => {
101 expect(initials("Ada King Olivia Lovelace")).toBe("AK");
102 });
103
104 it("empty / whitespace → empty string", () => {
105 expect(initials("")).toBe("");
106 expect(initials(" ")).toBe("");
107 });
108});
109
110describe("truncate", () => {
111 it("leaves short strings alone", () => {
112 expect(truncate("abc", 5)).toBe("abc");
113 expect(truncate("abc", 3)).toBe("abc");
114 });
115 it("adds ellipsis when longer", () => {
116 expect(truncate("Hello world", 5)).toBe("Hello…");
117 expect(truncate("abc", 0)).toBe("…");
118 });
119});
120
121describe("formatDate", () => {
122 it("includes all three parts and honors the injected locale", () => {
123 const en = formatDate("2025-06-01T00:00:00.000Z", { locale: "en-US" });
124 const de = formatDate("2025-06-01T00:00:00.000Z", { locale: "de-DE" });
125
126 for (const out of [en, de]) {
127 expect(out).toContain("2025");
128 expect(out).toMatch(/1/);
129 expect(out).toMatch(/(Jun|06)/);
130 }
131 expect(en).not.toBe(de);
132 });
133 it("invalid → empty", () => {
134 expect(formatDate("nope", { locale: "en-US" })).toBe("");
135 });
136});
137