1import { describe, expect, it, vi } from "vite-plus/test";
2
3vi.mock("@expo/ui/jetpack-compose", () => ({
4 Button: "Button",
5 Column: "Column",
6 LinearProgressIndicator: "LinearProgressIndicator",
7 Text: "Text",
8 getMaterialColors: ({ scheme }: { scheme: string }) => ({
9 surface: `${scheme}-surface`,
10 onSurface: `${scheme}-onSurface`,
11 onSurfaceVariant: `${scheme}-onSurfaceVariant`,
12 surfaceVariant: `${scheme}-surfaceVariant`,
13 primary: `${scheme}-primary`,
14 error: `${scheme}-error`,
15 }),
16}));
17
18vi.mock("@expo/ui/jetpack-compose/modifiers", () => ({
19 fillMaxSize: () => "fillMaxSize",
20 fillMaxWidth: () => "fillMaxWidth",
21 height: (value: number) => ({ height: value }),
22 padding: (...values: number[]) => ({ padding: values }),
23 paddingAll: (value: number) => ({ paddingAll: value }),
24}));
25
26vi.mock("expo-widgets", () => ({
27 createWidget: vi.fn((name: string, layout: unknown) => ({ layout, name })),
28}));
29
30import { SubscriptionUsage } from "./SubscriptionUsage.android";
31import type { SubscriptionUsageSnapshot } from "./subscriptionUsageSnapshot";
32
33const now = Date.parse("2026-09-05T12:00:00.000Z");
34const provider = {
35 name: "Codex",
36 detail: "Subscription remaining",
37 windows: [
38 { kind: "session", label: "5 hours", remaining: 60, reset: "Next reset Sep 5, 5:00 PM" },
39 { kind: "weekly", label: "Weekly", remaining: 8, reset: "Next reset Sep 9, 9:00 AM" },
40 ],
41 expiresAt: now + 60_000,
42 totalWindows: 2,
43} satisfies SubscriptionUsageSnapshot["providers"][number];
44const snapshot = {
45 checkedAt: now,
46 url: "t3code-dev://settings/usage?tab=limits",
47 providers: [provider, { ...provider, name: "Claude" }],
48} satisfies SubscriptionUsageSnapshot;
49
50function render(props: SubscriptionUsageSnapshot, colorScheme: "light" | "dark" = "dark") {
51 vi.setSystemTime(now);
52 return JSON.stringify(SubscriptionUsage(props, { colorScheme, configuration: undefined }));
53}
54
55describe("SubscriptionUsage Android layout", () => {
56 it("renders each window with its remaining share, bar, and reset", () => {
57 const tree = render(snapshot);
58 expect(tree).toContain("5 hours · 60% left");
59 expect(tree).toContain('"progress":0.6');
60 expect(tree).toContain("Next reset Sep 5, 5:00 PM");
61 expect(tree).toContain('"progress":0.08');
62 expect(tree).toContain('"color":"dark-error"');
63 expect(tree).toContain("As of ");
64 });
65
66 it("drops the bars and asks for a refresh once the snapshot expires", () => {
67 const tree = render({
68 ...snapshot,
69 providers: [{ ...provider, expiresAt: now - 1 }],
70 });
71 expect(tree).toContain("Open T3 to refresh");
72 expect(tree).not.toContain("LinearProgressIndicator");
73 expect(tree).not.toContain("more in T3");
74 });
75
76 it("counts the quotas that did not fit", () => {
77 const tree = render({ ...snapshot, providers: [{ ...provider, totalWindows: 5 }] });
78 expect(tree).toContain("3 more in T3");
79 });
80
81 it("keeps quotas without an expiry deadline visible", () => {
82 const tree = render({ ...snapshot, providers: [{ ...provider, expiresAt: 0 }] });
83 expect(tree).toContain("5 hours · 60% left");
84 expect(tree).not.toContain("Open T3 to refresh");
85 });
86
87 it("invites connecting when nothing has been checked", () => {
88 const tree = render({ checkedAt: 0, providers: [] }, "light");
89 expect(tree).toContain("Tap to connect in T3");
90 expect(tree).not.toContain("As of ");
91 expect(tree).toContain('"containerColor":"light-surface"');
92 });
93});