1import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
2
3import { readUsagePagePreferences, saveUsagePagePreferences } from "./usagePagePreferences";
4
5const key = "t3code:usage-page-preferences:v1";
6let values: Map<string, string>;
7let storage: Pick<Storage, "getItem" | "setItem">;
8
9beforeEach(() => {
10 values = new Map();
11 storage = {
12 getItem: (key) => values.get(key) ?? null,
13 setItem: (key, value) => {
14 values.set(key, value);
15 },
16 };
17 vi.stubGlobal("window", { localStorage: storage });
18 vi.spyOn(console, "error").mockImplementation(() => {});
19});
20
21afterEach(() => {
22 vi.restoreAllMocks();
23 vi.unstubAllGlobals();
24});
25
26describe("Usage page preferences", () => {
27 it("uses defaults when no preference has been saved", () => {
28 expect(readUsagePagePreferences()).toEqual({ metric: "cost", windowDays: 30 });
29 });
30
31 it.each([1, 7, 30, 90] as const)("round-trips every metric with a %i-day range", (windowDays) => {
32 for (const metric of ["cost", "tokens"] as const) {
33 saveUsagePagePreferences({ metric, windowDays });
34 expect(readUsagePagePreferences()).toEqual({ metric, windowDays });
35 }
36 });
37
38 it("opens saved limits preferences as cost without discarding filters", () => {
39 values.set(
40 key,
41 JSON.stringify({ metric: "limits", windowDays: 7, hiddenProviders: ["claude"] }),
42 );
43 expect(readUsagePagePreferences()).toEqual({
44 metric: "cost",
45 windowDays: 7,
46 hiddenProviders: ["claude"],
47 });
48 });
49
50 it("round-trips hidden providers and reads older saves without them", () => {
51 saveUsagePagePreferences({ metric: "cost", windowDays: 7, hiddenProviders: ["codex"] });
52 expect(readUsagePagePreferences().hiddenProviders).toEqual(["codex"]);
53
54 values.set(key, '{"metric":"tokens","windowDays":30}');
55 expect(readUsagePagePreferences()).toEqual({ metric: "tokens", windowDays: 30 });
56 });
57
58 it.each([
59 "not-json",
60 '{"metric":"unknown","windowDays":7}',
61 '{"metric":"cost","windowDays":365}',
62 ])("replaces invalid preferences on the next save: %s", (value) => {
63 values.set(key, value);
64 expect(readUsagePagePreferences()).toEqual({ metric: "cost", windowDays: 30 });
65 saveUsagePagePreferences({ metric: "tokens", windowDays: 7 });
66 expect(readUsagePagePreferences()).toEqual({ metric: "tokens", windowDays: 7 });
67 });
68
69 it("contains write failures and can save again after storage recovers", () => {
70 saveUsagePagePreferences({ metric: "cost", windowDays: 30 });
71 const write = vi.spyOn(storage, "setItem").mockImplementation(() => {
72 throw new Error("QuotaExceededError");
73 });
74 expect(() => saveUsagePagePreferences({ metric: "tokens", windowDays: 7 })).not.toThrow();
75 expect(readUsagePagePreferences()).toEqual({ metric: "cost", windowDays: 30 });
76 write.mockRestore();
77 saveUsagePagePreferences({ metric: "cost", windowDays: 7 });
78 expect(readUsagePagePreferences()).toEqual({ metric: "cost", windowDays: 7 });
79 });
80
81 it("contains failures when the browser blocks storage access", () => {
82 vi.stubGlobal("window", {
83 get localStorage() {
84 throw new Error("SecurityError");
85 },
86 });
87 expect(readUsagePagePreferences()).toEqual({ metric: "cost", windowDays: 30 });
88 expect(() => saveUsagePagePreferences({ metric: "tokens", windowDays: 7 })).not.toThrow();
89 });
90});