1import {
2 USAGE_CONTRACT_VERSION,
3 UsageDay,
4 type UsageProviderKind,
5 type UsageSummary,
6} from "@t3tools/contracts";
7import { describe, expect, it } from "vite-plus/test";
8
9import { usageEnvironmentProgress, usageLoadingState, usageProgress } from "./usageProgress.ts";
10
11function summary(refreshing: readonly UsageProviderKind[] = []): UsageSummary {
12 return {
13 contractVersion: USAGE_CONTRACT_VERSION,
14 readAt: "2026-09-05T12:00:00Z",
15 timeZone: "UTC",
16 sinceDay: UsageDay.make("2026-09-05"),
17 untilDay: UsageDay.make("2026-09-05"),
18 buckets: [],
19 sources: (["codex", ...refreshing] as const).map((provider) => ({
20 fingerprint: { hostId: "host", provider, resolvedHomePath: "/home", volumeId: "1:1" },
21 status: "ok" as const,
22 scannedFiles: 1,
23 skippedFiles: 0,
24 malformedRecords: 0,
25 distinctSessions: 1,
26 message: null,
27 ...(provider === "codex" ? {} : { refreshing: true as const }),
28 })),
29 pricing: { status: "fresh", source: "test", fetchedAt: null, knownModels: 1 },
30 scanDurationMs: 1,
31 };
32}
33
34function environment(
35 label: string,
36 overrides: Partial<{
37 isConnected: boolean;
38 isPending: boolean;
39 error: string | null;
40 summary: UsageSummary | null;
41 }> = {},
42) {
43 return {
44 label,
45 isConnected: true,
46 isPending: false,
47 error: null as string | null,
48 summary: summary(),
49 ...overrides,
50 };
51}
52
53const options = { providerLabel: (provider: UsageProviderKind) => `${provider} label` };
54
55describe("usageProgress", () => {
56 it("dims a lone environment while it refetches over its old totals", () => {
57 expect(usageProgress([environment("a", { isPending: true })], options)).toEqual({
58 dimmed: true,
59 label: "Updating…",
60 });
61 expect(usageProgress([environment("a")], options)).toEqual({ dimmed: false, label: null });
62 });
63
64 it("undims once one device answers and names the slow one", () => {
65 const waiting = [environment("a", { isPending: true }), environment("b", { isPending: true })];
66 expect(usageProgress(waiting, options)).toEqual({
67 dimmed: true,
68 label: "Updating 2 environments…",
69 });
70 expect(
71 usageProgress([environment("a"), ...waiting.slice(1), environment("c")], options),
72 ).toEqual({ dimmed: false, label: "Updating b…" });
73 expect(
74 usageProgress(
75 [environment("a"), environment("b", { summary: null, isPending: true })],
76 options,
77 ),
78 ).toEqual({ dimmed: false, label: "Updating b…" });
79 });
80
81 it("names a slow source without dimming once its environment answers from cache", () => {
82 const partway = environment("a", { isPending: true, summary: summary(["cursor"]) });
83 expect(usageEnvironmentProgress(partway)).toEqual({ phase: "partway", providers: ["cursor"] });
84 expect(usageProgress([partway, environment("b")], options)).toEqual({
85 dimmed: false,
86 label: "Updating cursor label…",
87 });
88 expect(
89 usageProgress(
90 [partway, environment("b", { isPending: true, summary: summary(["cursor", "grok"]) })],
91 options,
92 ),
93 ).toEqual({ dimmed: false, label: "Updating 2 providers…" });
94 // Without a follow-up in flight, a leftover refreshing source is not waited on.
95 expect(usageProgress([{ ...partway, isPending: false }], options)).toEqual({
96 dimmed: false,
97 label: null,
98 });
99 });
100
101 it("ignores reconnecting and failed environments", () => {
102 const ignored = [
103 environment("reconnecting", { isPending: true, isConnected: false }),
104 environment("failed", { error: "Offline", summary: null }),
105 ];
106 expect(usageProgress([environment("a"), ...ignored], options)).toEqual({
107 dimmed: false,
108 label: null,
109 });
110 expect(
111 usageProgress(
112 [environment("a"), ...ignored, environment("slow", { summary: null, isPending: true })],
113 options,
114 ),
115 ).toEqual({ dimmed: false, label: "Updating slow…" });
116 expect(usageEnvironmentProgress(ignored[0]!)).toEqual({ phase: "inactive" });
117 });
118
119 it("dims every answered environment while a manual refresh runs", () => {
120 // Pricing refreshes first, so neither query is pending yet.
121 expect(
122 usageProgress([environment("a"), environment("b")], { ...options, refreshing: true }),
123 ).toEqual({ dimmed: true, label: "Updating 2 environments…" });
124 });
125});
126
127describe("usageLoadingState", () => {
128 it("marks only refreshing providers while the rest have answered", () => {
129 const state = usageLoadingState([
130 environment("a", { isPending: true, summary: summary(["cursor"]) }),
131 environment("b"),
132 ]);
133 expect(state).toEqual({ partial: true, everyProvider: false, providers: new Set(["cursor"]) });
134 });
135
136 it("marks every provider while an environment has not answered", () => {
137 const state = usageLoadingState([environment("a"), environment("b", { summary: null })]);
138 expect(state.everyProvider).toBe(true);
139 expect(state.partial).toBe(true);
140 });
141
142 it("ignores environments that cannot answer and settles once all have", () => {
143 const state = usageLoadingState([
144 environment("a"),
145 environment("b", { summary: null, isConnected: false }),
146 environment("c", { summary: null, error: "Offline" }),
147 ]);
148 expect(state).toEqual({ partial: false, everyProvider: false, providers: new Set() });
149 });
150});