1import { StrictMode, act } from "react";
2import { create, type ReactTestRenderer } from "react-test-renderer";
3import { afterEach, beforeEach, expect, it, vi } from "vite-plus/test";
4
5const state = vi.hoisted(() => ({
6 connect: vi.fn(),
7 connection: null,
8}));
9vi.mock("@effect/atom-react", () => ({ useAtomValue: () => ({ connection: state.connection }) }));
10vi.mock("../../connection/hub", () => ({
11 hubConnection: { stateValueAtom: {}, connectAuthenticated: state.connect },
12}));
13vi.mock("../../state/use-atom-command", () => ({ useAtomCommand: () => state.connect }));
14vi.mock("../../cloud/activation", () => ({ activateCloudAuthentication: vi.fn() }));
15vi.mock("../../cloud/publicConfig", () => ({
16 hasCloudPublicConfig: () => false,
17 resolveCloudPublicConfig: () => ({}),
18}));
19vi.mock("../../environments/primary/desktopAuth", () => ({
20 readDesktopPrimaryBearerToken: vi.fn(),
21}));
22vi.mock("../../environments/primary/target", () => ({ resolvePrimaryEnvironmentHttpUrl: vi.fn() }));
23
24vi.mock("./settingsLayout", () => ({ SettingsRow: () => null, SettingsSection: () => null }));
25
26import { HostedServerAutoConnect } from "./SelfHostedServer";
27
28let renderer: ReactTestRenderer | undefined;
29const server = "https://knife.example.test";
30
31beforeEach(() => {
32 vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
33 vi.stubGlobal("window", { location: { origin: server } });
34 state.connect.mockReset().mockResolvedValue({ _tag: "Success", value: undefined });
35});
36
37afterEach(async () => {
38 await act(() => renderer?.unmount());
39 renderer = undefined;
40 vi.unstubAllGlobals();
41});
42
43it("registers the authenticated web server after StrictMode replaces the first pending effect", async () => {
44 const configuration: Array<(response: Response) => void> = [];
45 const fetch = vi.fn((url: string) => {
46 if (url.endsWith("/oidc/config"))
47 return new Promise<Response>((resolve) => configuration.push(resolve));
48 return Promise.resolve(Response.json({ accountId: "owner", deviceId: "browser" }));
49 });
50 vi.stubGlobal("fetch", fetch);
51 await act(() => {
52 renderer = create(
53 <StrictMode>
54 <HostedServerAutoConnect />
55 </StrictMode>,
56 );
57 });
58 expect(configuration).toHaveLength(2);
59 await act(async () => {
60 for (const resolve of configuration) resolve(Response.json({ enabled: true }));
61 });
62 expect(state.connect.mock.calls).toEqual([[{ httpBaseUrl: server }]]);
63 expect(fetch.mock.calls.filter(([url]) => url.endsWith("/account"))).toHaveLength(1);
64});
65
66it("does not register a server when its account response arrives after unmount", async () => {
67 let accountResponse: ((response: Response) => void) | undefined;
68 vi.stubGlobal(
69 "fetch",
70 vi.fn((url: string) =>
71 url.endsWith("/oidc/config")
72 ? Promise.resolve(Response.json({ enabled: true }))
73 : new Promise<Response>((resolve) => {
74 accountResponse = resolve;
75 }),
76 ),
77 );
78 await act(() => {
79 renderer = create(<HostedServerAutoConnect />);
80 });
81 expect(accountResponse).toBeDefined();
82 await act(() => renderer?.unmount());
83 renderer = undefined;
84 await act(async () => {
85 accountResponse!(Response.json({ accountId: "owner" }));
86 });
87 expect(state.connect).not.toHaveBeenCalled();
88});