1import { EnvironmentId } from "@t3tools/contracts";
2import { beforeEach, expect, it, vi } from "vite-plus/test";
3
4const state = vi.hoisted(() => ({
5 values: [] as unknown[],
6 cursor: 0,
7 presentations: new Map(),
8 refreshProviders: vi.fn(),
9 autoRefresh: async () => {},
10 refreshingRef: { current: false },
11}));
12vi.mock("../../state/session", () => ({
13 useEnvironmentScope: () => true,
14 readEnvironmentScope: () => true,
15}));
16vi.mock("react", () => ({
17 useState: (initial: unknown) => {
18 const index = state.cursor++;
19 if (!(index in state.values)) {
20 state.values[index] = typeof initial === "function" ? initial() : initial;
21 }
22 return [
23 state.values[index],
24 (next: unknown) => {
25 state.values[index] = typeof next === "function" ? next(state.values[index]) : next;
26 },
27 ];
28 },
29 useRef: () => state.refreshingRef,
30 useEffect: () => {},
31 useEffectEvent: (callback: () => Promise<void>) => {
32 state.autoRefresh = callback;
33 return callback;
34 },
35}));
36vi.mock("@effect/atom-react", () => ({ useAtomValue: () => state.presentations }));
37vi.mock("react-native", () => ({ Alert: {}, Pressable: "button", View: "div" }));
38vi.mock("../../components/AppText", () => ({ AppText: "span" }));
39vi.mock("../../components/ProviderIcon", () => ({ ProviderIcon: () => null }));
40vi.mock("./usageProviders", () => ({ useProviderColors: () => ({}) }));
41vi.mock("../../state/presentation", () => ({
42 environmentPresentations: { presentationsAtom: null },
43}));
44vi.mock("../../state/server", () => ({ serverEnvironment: { refreshProviders: null } }));
45vi.mock("../../state/use-atom-command", () => ({ useAtomCommand: () => state.refreshProviders }));
46
47import { useRefreshLimits } from "./UsageLimitsSection";
48import { refreshUsageLimits } from "@t3tools/client-runtime/state/usage";
49
50beforeEach(() => {
51 state.values = [];
52 state.cursor = 0;
53 state.refreshingRef.current = false;
54 state.refreshProviders.mockReset();
55});
56
57it("keeps a newer environment failure when an older refresh finishes", async () => {
58 const a = EnvironmentId.make("mobile-limits-a");
59 const b = EnvironmentId.make("mobile-limits-b");
60 const pending = Promise.withResolvers<{ _tag: string }>();
61 const read = () => {
62 state.cursor = 0;
63 return useRefreshLimits();
64 };
65 const presentation = (label: string) => ({
66 connection: { phase: "connected" },
67 entry: { target: { label } },
68 });
69 state.presentations = new Map([[a, presentation("A")]]);
70 state.refreshProviders.mockImplementation(({ environmentId }) =>
71 environmentId === a ? pending.promise : Promise.resolve({ _tag: "Failure" }),
72 );
73 const first = read().refresh();
74 state.presentations = new Map([
75 [a, presentation("A")],
76 [b, presentation("B")],
77 ]);
78 read();
79 await state.autoRefresh();
80 expect(read().failedLabels).toEqual(["B"]);
81 pending.resolve({ _tag: "Success" });
82 await first;
83 expect(read().failedLabels).toEqual(["B"]);
84});
85
86it("does not let an older multi-environment batch clear a newer failure for the same environment", async () => {
87 const a = EnvironmentId.make("mobile-limits-race-a");
88 const b = EnvironmentId.make("mobile-limits-race-b");
89 const aFirst = Promise.withResolvers<{ _tag: string }>();
90 const bFirst = Promise.withResolvers<{ _tag: string }>();
91 const read = (selected: ReadonlySet<EnvironmentId> | null = null) => {
92 state.cursor = 0;
93 return useRefreshLimits(selected);
94 };
95 const presentation = (label: string) => ({
96 connection: { phase: "connected" },
97 entry: { target: { label } },
98 });
99 state.presentations = new Map([
100 [a, presentation("A")],
101 [b, presentation("B")],
102 ]);
103 let aCalls = 0;
104 state.refreshProviders.mockImplementation(({ environmentId }) =>
105 environmentId === b
106 ? bFirst.promise
107 : ++aCalls === 1
108 ? aFirst.promise
109 : Promise.resolve({ _tag: "Failure" }),
110 );
111 read();
112 const older = state.autoRefresh();
113 aFirst.resolve({ _tag: "Success" });
114 await refreshUsageLimits(a, () => aFirst.promise);
115 const selected = new Set([a]);
116 await read(selected).refresh();
117 expect(read(selected).failedLabels).toEqual(["A"]);
118 bFirst.resolve({ _tag: "Success" });
119 await older;
120 expect(read(selected).failedLabels).toEqual(["A"]);
121});