1import { RegistryContext } from "@effect/atom-react";
2import {
3 AuthOrchestrationOperateScope,
4 EnvironmentId,
5 type AuthEnvironmentScope,
6 type AuthSessionState,
7} from "@t3tools/contracts";
8import { AsyncResult, Atom, AtomRegistry } from "effect/reactivity";
9import { act, create, type ReactTestRenderer } from "react-test-renderer";
10import { afterEach, beforeEach, expect, it, vi } from "vite-plus/test";
11
12type SessionResult = AsyncResult.AsyncResult<AuthSessionState, Error>;
13const state = vi.hoisted(() => ({
14 registry: null as AtomRegistry.AtomRegistry | null,
15 sessions: new Map<EnvironmentId, Atom.Writable<SessionResult>>(),
16 run: vi.fn(),
17 toast: vi.fn(),
18}));
19
20vi.mock("../connection/runtime", () => ({ connectionAtomRuntime: undefined }));
21vi.mock("@t3tools/client-runtime/state/session", () => ({
22 createEnvironmentSessionAtoms: () => ({
23 sessionStateAtom: (id: EnvironmentId) => state.sessions.get(id)!,
24 }),
25}));
26vi.mock("../rpc/atomRegistry", () => ({
27 get appAtomRegistry() {
28 return state.registry;
29 },
30}));
31vi.mock("../state/shell", () => ({ shellEnvironment: { openInEditor: "openInEditor" } }));
32vi.mock("../state/use-atom-command", () => ({ useAtomCommand: () => state.run }));
33vi.mock("../hooks/useLocalStorage", () => ({
34 getLocalStorageItem: () => "vscode",
35 setLocalStorageItem: vi.fn(),
36 useLocalStorage: vi.fn(),
37}));
38vi.mock("./ui/button", () => ({ Button: "button" }));
39vi.mock("./ui/toast", () => ({
40 toastManager: { add: state.toast },
41 stackedThreadToast: (value: unknown) => value,
42}));
43
44import { KeybindingsConfigWarning } from "./KeybindingsConfigWarning";
45
46const environmentId = EnvironmentId.make("warning-environment");
47let renderer: ReactTestRenderer | undefined;
48const session = (scopes: readonly AuthEnvironmentScope[]): AuthSessionState => ({
49 authenticated: true,
50 scopes,
51 auth: {
52 policy: "remote-reachable",
53 bootstrapMethods: ["one-time-token"],
54 sessionMethods: ["bearer-access-token"],
55 sessionCookieName: "t3_session",
56 },
57});
58
59beforeEach(() => {
60 vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
61 state.registry = AtomRegistry.make();
62 state.sessions.set(
63 environmentId,
64 Atom.make<SessionResult>(AsyncResult.success(session([AuthOrchestrationOperateScope]))).pipe(
65 Atom.keepAlive,
66 ),
67 );
68 state.run.mockReset().mockResolvedValue(AsyncResult.success(undefined));
69 state.toast.mockReset();
70});
71
72afterEach(async () => {
73 await act(async () => renderer?.unmount());
74 renderer = undefined;
75 state.registry?.dispose();
76 state.sessions.clear();
77 vi.unstubAllGlobals();
78});
79
80it("updates a visible warning on revocation and blocks its retained action until regrant", async () => {
81 await act(async () => {
82 renderer = create(
83 <RegistryContext.Provider value={state.registry!}>
84 <KeybindingsConfigWarning
85 environmentId={environmentId}
86 configPath="/t3/keybindings.json"
87 availableEditors={["vscode"]}
88 message="Invalid shortcut"
89 />
90 </RegistryContext.Provider>,
91 );
92 });
93 const open = renderer!.root.findByType("button").props.onClick;
94 expect(renderer!.root.findByType("button").props.disabled).toBe(false);
95 await act(async () => {
96 state.registry!.set(state.sessions.get(environmentId)!, AsyncResult.success(session([])));
97 await open();
98 });
99 expect(renderer!.root.findByType("button").props.disabled).toBe(true);
100 expect(state.run).not.toHaveBeenCalled();
101 expect(state.toast).toHaveBeenCalledWith(
102 expect.objectContaining({
103 title: "Unable to open keybindings file",
104 }),
105 );
106
107 await act(async () => {
108 state.registry!.set(
109 state.sessions.get(environmentId)!,
110 AsyncResult.success(session([AuthOrchestrationOperateScope])),
111 );
112 });
113 expect(renderer!.root.findByType("button").props.disabled).toBe(false);
114 await act(async () => open());
115 expect(state.run).toHaveBeenCalledExactlyOnceWith({
116 environmentId,
117 input: { cwd: "/t3/keybindings.json", editor: "vscode" },
118 });
119});