1// @effect-diagnostics globalDate:off -- A fixed instant keeps calendar-window assertions deterministic.
2import { describe, expect, it, vi } from "vite-plus/test";
3
4import {
5 enumerateHourStarts,
6 formatDateTimeShort,
7 formatHourShort,
8 formatPercent,
9 formatRelativeHourShort,
10 makeWindow,
11} from "./usageFormat.ts";
12
13describe("formatPercent", () => {
14 it("distinguishes a small positive share from zero", () => {
15 expect(formatPercent(0)).toBe("0.0%");
16 expect(formatPercent(0.0004)).toBe("<0.1%");
17 expect(formatPercent(0.0009)).toBe("<0.1%");
18 expect(formatPercent(0.001)).toBe("0.1%");
19 expect(formatPercent(0.023)).toBe("2.3%");
20 expect(formatPercent(0.00004, 2)).toBe("<0.01%");
21 });
22});
23
24describe("hourly usage formatting", () => {
25 it("keeps requested zones separate when formatting repeated calls", () => {
26 const instant = "2026-08-11T12:37:00.000Z";
27 for (const zone of ["UTC", "America/New_York", "Asia/Kathmandu", "UTC"]) {
28 expect(formatHourShort(instant, zone)).toBe(
29 new Intl.DateTimeFormat("en-US", { timeZone: zone, hour: "numeric" }).format(
30 new Date(instant),
31 ),
32 );
33 }
34 expect(() => formatHourShort(instant, "Etc/Unknown")).toThrow(RangeError);
35 expect(formatHourShort("invalid", "UTC")).toBe("invalid");
36 });
37
38 it("uses the current system zone when no zone is supplied", () => {
39 try {
40 vi.stubEnv("TZ", "UTC");
41 expect(formatHourShort("2026-08-11T12:37:00.000Z")).toBe("12 PM");
42 vi.stubEnv("TZ", "America/New_York");
43 expect(formatHourShort("2026-08-11T12:37:00.000Z")).toBe("8 AM");
44 } finally {
45 vi.unstubAllEnvs();
46 }
47 });
48
49 it("enumerates 24 fixed buckets across a rolling window", () => {
50 const hours = enumerateHourStarts("2026-08-10T12:37:00.000Z", "2026-08-11T12:37:00.000Z");
51
52 expect(hours).toHaveLength(24);
53 expect(hours[0]).toBe("2026-08-10T12:37:00.000Z");
54 expect(hours[23]).toBe("2026-08-11T11:37:00.000Z");
55 });
56
57 it("formats rolling instants in the requested time zone", () => {
58 expect(formatHourShort("2026-08-11T00:37:00.000Z", "UTC")).toBe("12 AM");
59 expect(formatHourShort("2026-08-11T12:37:00.000Z", "UTC")).toBe("12 PM");
60 expect(formatDateTimeShort("2026-08-11T17:37:00.000Z", "UTC")).toBe("Aug 11, 5 PM");
61 });
62
63 it("disambiguates repeated hours during a fall-back transition", () => {
64 expect(formatHourShort("2026-11-01T05:37:00.000Z", "America/New_York")).toBe("1 AM EDT");
65 expect(formatHourShort("2026-11-01T06:37:00.000Z", "America/New_York")).toBe("1 AM EST");
66 });
67
68 it("makes hourly tooltip dates relative to the window in its requested time zone", () => {
69 const windowEnd = "2026-08-11T14:37:00.000Z";
70
71 expect(formatRelativeHourShort("2026-08-10T17:37:00.000Z", windowEnd, "UTC")).toBe(
72 "5 PM yesterday",
73 );
74 expect(formatRelativeHourShort("2026-08-11T14:37:00.000Z", windowEnd, "UTC")).toBe(
75 "2 PM today",
76 );
77 expect(
78 formatRelativeHourShort(
79 "2026-08-11T01:37:00.000Z",
80 "2026-08-11T10:37:00.000Z",
81 "America/Los_Angeles",
82 ),
83 ).toBe("6 PM yesterday");
84 });
85
86 it("builds an exact minute-aligned 24-hour request", () => {
87 const window = makeWindow(1, new Date("2026-08-11T12:37:42.123Z"), "hour");
88
89 expect(window.resolution).toBe("hour");
90 expect(window.sinceTime).toBe("2026-08-10T12:37:00.000Z");
91 expect(window.untilTime).toBe("2026-08-11T12:37:00.000Z");
92 });
93
94 it("degrades an unknown resolved zone to UTC instead of crashing", () => {
95 const resolved = new Intl.DateTimeFormat().resolvedOptions();
96 const resolvedOptions = vi
97 .spyOn(Intl.DateTimeFormat.prototype, "resolvedOptions")
98 .mockReturnValue({ ...resolved, timeZone: "Etc/Unknown" });
99
100 try {
101 const now = new Date("2026-08-11T12:37:42.123Z");
102
103 expect(makeWindow(1, now, "hour").timeZone).toBe("UTC");
104 expect(makeWindow(30, now).timeZone).toBe("UTC");
105 } finally {
106 resolvedOptions.mockRestore();
107 }
108 });
109});