1// @vitest-environment jsdom
2import {
3 EnvironmentId,
4 ProviderDriverKind,
5 ProviderInstanceId,
6 type ServerProvider,
7} from "@t3tools/contracts";
8import { AsyncResult, Atom } from "effect/reactivity";
9import { act } from "react";
10import { createRoot, type Root } from "react-dom/client";
11import { afterEach, beforeEach, expect, it, vi } from "vite-plus/test";
12
13import { appAtomRegistry, AppAtomRegistryProvider } from "~/rpc/atomRegistry";
14
15const mocks = vi.hoisted(() => ({
16 environments: [] as Array<{
17 environmentId: EnvironmentId;
18 label: string;
19 connection: { phase: "connected" };
20 serverConfig: { providers: ServerProvider[] };
21 }>,
22 permissionAtom: vi.fn(),
23 update: vi.fn(),
24}));
25vi.mock("~/state/environments", () => ({
26 useEnvironments: () => ({ environments: mocks.environments }),
27}));
28vi.mock("~/state/server", () => ({
29 serverEnvironment: { updateProvider: { permissionAtom: mocks.permissionAtom } },
30}));
31vi.mock("~/state/use-atom-command", () => ({ useAtomCommand: () => mocks.update }));
32vi.mock("./ui/toast", () => ({ toastManager: { add: vi.fn() }, stackedThreadToast: vi.fn() }));
33
34import { useProviderUpdates } from "./ProviderUpdatesAction";
35
36const home = EnvironmentId.make("home");
37const work = EnvironmentId.make("work");
38const homeAllowed = Atom.make(false).pipe(Atom.keepAlive);
39const workAllowed = Atom.make(false).pipe(Atom.keepAlive);
40let root: Root;
41let container: HTMLDivElement;
42
43function Updates() {
44 const { machines, updateAll } = useProviderUpdates();
45 return machines.length > 0 ? (
46 <button onClick={() => void updateAll({ quiet: true })}>Update all</button>
47 ) : null;
48}
49
50beforeEach(() => {
51 const provider: ServerProvider = {
52 instanceId: ProviderInstanceId.make("codex-home"),
53 driver: ProviderDriverKind.make("codex"),
54 enabled: true,
55 installed: true,
56 version: "1.0.0",
57 status: "ready",
58 auth: { status: "authenticated" },
59 checkedAt: "2026-10-15T12:00:00Z",
60 models: [],
61 slashCommands: [],
62 skills: [],
63 versionAdvisory: {
64 status: "behind_latest",
65 currentVersion: "1.0.0",
66 latestVersion: "1.1.0",
67 updateCommand: "npm install -g provider",
68 canUpdate: true,
69 checkedAt: "2026-10-15T12:00:00Z",
70 message: "Update available.",
71 },
72 };
73 mocks.environments = [home, work].map((environmentId) => ({
74 environmentId,
75 label: environmentId,
76 connection: { phase: "connected" },
77 serverConfig: { providers: [provider] },
78 }));
79 mocks.permissionAtom.mockImplementation((id: EnvironmentId) =>
80 id === home ? homeAllowed : workAllowed,
81 );
82 mocks.update.mockReset().mockResolvedValue(AsyncResult.success(undefined));
83 appAtomRegistry.set(homeAllowed, false);
84 appAtomRegistry.set(workAllowed, false);
85 container = document.createElement("div");
86 document.body.append(container);
87 root = createRoot(container);
88 Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
89});
90
91afterEach(async () => {
92 await act(async () => root.unmount());
93 container.remove();
94});
95
96it("offers and updates only permitted machines, reacting to permission changes", async () => {
97 await act(async () =>
98 root.render(
99 <AppAtomRegistryProvider>
100 <Updates />
101 </AppAtomRegistryProvider>,
102 ),
103 );
104 expect(container.querySelector("button")).toBeNull();
105
106 await act(async () => appAtomRegistry.set(workAllowed, true));
107 expect(container.querySelector("button")).not.toBeNull();
108 await act(async () => container.querySelector("button")?.click());
109 expect(mocks.update).toHaveBeenCalledExactlyOnceWith({
110 environmentId: work,
111 input: { provider: "codex", instanceId: "codex-home" },
112 });
113
114 await act(async () => appAtomRegistry.set(workAllowed, false));
115 expect(container.querySelector("button")).toBeNull();
116});