Skip to content
Source
Sign inSign up
Open History to see the last commit

useSubscriptionUsage.test.ts

128 lines4.5 KB

1import { EnvironmentId } from "@t3tools/contracts";
2import * as Option from "effect/Option";
3import { Atom, AtomRegistry } from "effect/reactivity";
4import { describe, expect, it, vi } from "vite-plus/test";
5​
6const fixture = vi.hoisted(() => ({
7 appState: "active",
8 appStateListeners: new Set<() => void>(),
9 refreshed: [] as string[],
10}));
11​
12vi.mock("react-native", () => ({
13 AppState: {
14 get currentState() {
15 return fixture.appState;
16 },
17 addEventListener: (_event: string, listener: () => void) => {
18 fixture.appStateListeners.add(listener);
19 return { remove: () => fixture.appStateListeners.delete(listener) };
20 },
21 },
22 AccessibilityInfo: { addEventListener: vi.fn(), isReduceMotionEnabled: vi.fn() },
23}));
24​
25vi.mock("../state/atom-registry", async () => {
26 const { AtomRegistry } = await import("effect/reactivity");
27 return { appAtomRegistry: AtomRegistry.make() };
28});
29​
30vi.mock("../connection/catalog", async () => {
31 const { Atom } = await import("effect/reactivity");
32 return { environmentCatalog: { catalogValueAtom: Atom.make({ isReady: false }) } };
33});
34​
35vi.mock("../state/presentation", async () => {
36 const { Atom } = await import("effect/reactivity");
37 return {
38 environmentPresentations: { presentationsAtom: Atom.make(new Map<string, unknown>()) },
39 };
40});
41​
42vi.mock("../state/shell", async () => {
43 const { Atom } = await import("effect/reactivity");
44 const Option = await import("effect/Option");
45 return {
46 environmentShell: {
47 stateValueAtom: Atom.family((_id: string) =>
48 Atom.make({ status: "synchronizing", error: Option.none<string>() }),
49 ),
50 },
51 };
52});
53​
54vi.mock("../state/server", () => ({
55 serverEnvironment: { refreshProviders: { label: "Refresh providers" } },
56}));
57​
58vi.mock("@t3tools/client-runtime/state/runtime", () => ({
59 runAtomCommand: (
60 _registry: unknown,
61 _command: unknown,
62 input: { readonly environmentId: string },
63 ) => {
64 fixture.refreshed.push(input.environmentId);
65 return Promise.resolve();
66 },
67}));
68​
69import { environmentCatalog } from "../connection/catalog";
70import { appAtomRegistry } from "../state/atom-registry";
71import { environmentPresentations } from "../state/presentation";
72import { environmentShell } from "../state/shell";
73import { installSubscriptionUsageRefresh } from "./useSubscriptionUsage";
74​
75const registry = appAtomRegistry as AtomRegistry.AtomRegistry;
76const catalogAtom = environmentCatalog.catalogValueAtom as unknown as Atom.Writable<{
77 isReady: boolean;
78}>;
79const presentationsAtom = environmentPresentations.presentationsAtom as unknown as Atom.Writable<
80 Map<string, { connection: { phase: string } }>
81>;
82​
83const shellAtom = (environmentId: EnvironmentId) =>
84 environmentShell.stateValueAtom(environmentId) as unknown as Atom.Writable<{
85 status: string;
86 error: Option.Option<string>;
87 }>;
88​
89const connected = EnvironmentId.make("env-connected");
90const connecting = EnvironmentId.make("env-connecting");
91const syncing = EnvironmentId.make("env-syncing");
92const failed = EnvironmentId.make("env-failed");
93​
94describe("installSubscriptionUsageRefresh", () => {
95 it("refreshes connected environments once the catalog is ready and the app is active", () => {
96 fixture.appState = "background";
97 fixture.refreshed = [];
98 registry.set(catalogAtom, { isReady: false });
99 registry.set(
100 presentationsAtom,
101 new Map([
102 [connected, { connection: { phase: "connected" } }],
103 [connecting, { connection: { phase: "connecting" } }],
104 [syncing, { connection: { phase: "connected" } }],
105 [failed, { connection: { phase: "connected" } }],
106 ]),
107 );
108 registry.set(shellAtom(connected), { status: "live", error: Option.none() });
109 // A shell that failed never goes live; its quotas still refresh.
110 registry.set(shellAtom(failed), { status: "cached", error: Option.some("Shell failed") });
111​
112 const dispose = installSubscriptionUsageRefresh();
113 registry.set(catalogAtom, { isReady: true });
114 // Backgrounded: nothing refreshes yet.
115 expect(fixture.refreshed).toEqual([]);
116​
117 fixture.appState = "active";
118 for (const listener of fixture.appStateListeners) listener();
119 expect(fixture.refreshed).toEqual([connected, failed]);
120​
121 // The refresh waits for the thread list, so it never competes with it on a cold start.
122 registry.set(shellAtom(syncing), { status: "live", error: Option.none() });
123 expect(fixture.refreshed).toEqual([connected, failed, syncing]);
124​
125 dispose();
126 expect(fixture.appStateListeners.size).toBe(0);
127 });
128});