1// @vitest-environment jsdom
2import { act, useEffect, type ReactNode } from "react";
3import { createRoot, type Root } from "react-dom/client";
4import { afterEach, beforeEach, expect, it, vi } from "vite-plus/test";
5
6const mocks = vi.hoisted(() => ({ loads: 0, mounts: 0 }));
7vi.mock("../env", () => ({ isElectron: false }));
8vi.mock("./publicConfig", () => ({
9 hasCloudPublicConfig: () => true,
10 resolveCloudPublicConfig: () => ({ clerkPublishableKey: "test-key" }),
11}));
12vi.mock("../components/clerk/BrowserManagedAuthShell", () => {
13 mocks.loads += 1;
14 return {
15 default: ({ children }: { children: ReactNode }) => {
16 useEffect(() => {
17 mocks.mounts += 1;
18 }, []);
19 return children;
20 },
21 };
22});
23
24let root: Root;
25let container: HTMLDivElement;
26
27beforeEach(() => {
28 localStorage.clear();
29 vi.resetModules();
30 mocks.loads = 0;
31 mocks.mounts = 0;
32 container = document.createElement("div");
33 document.body.append(container);
34 root = createRoot(container);
35});
36afterEach(async () => {
37 await act(async () => root.unmount());
38 container.remove();
39});
40
41it("leaves Clerk dormant for local and self-hosted use and preserves the editor on activation", async () => {
42 const { CloudAuthBoundary } = await import("./CloudAuthBoundary");
43 const { activateCloudAuthentication } = await import("./activation");
44 await act(async () =>
45 root.render(
46 <>
47 <input aria-label="Draft" defaultValue="" />
48 <CloudAuthBoundary fallback={<button>Use T3 Connect</button>}>
49 <p>Cloud account</p>
50 </CloudAuthBoundary>
51 </>,
52 ),
53 );
54 const editor = container.querySelector("input")!;
55 editor.value = "Unsent draft";
56 editor.focus();
57 expect(mocks.loads).toBe(0);
58 expect(mocks.mounts).toBe(0);
59 expect(container.textContent).toContain("Use T3 Connect");
60 await act(async () => {
61 activateCloudAuthentication();
62 await vi.dynamicImportSettled();
63 });
64 expect(mocks.loads).toBe(1);
65 expect(mocks.mounts).toBe(1);
66 expect(container.textContent).toContain("Cloud account");
67 expect(container.querySelector("input")).toBe(editor);
68 expect(editor.value).toBe("Unsent draft");
69 expect(document.activeElement).toBe(editor);
70});
71
72it("restores an explicit Connect opt-in without a new sign-in request", async () => {
73 localStorage.setItem("knife:t3-connect-enabled", "true");
74 const { useCloudAuthentication } = await import("./activation");
75 function State() {
76 const state = useCloudAuthentication();
77 return <p>{JSON.stringify(state)}</p>;
78 }
79 await act(async () => root.render(<State />));
80 expect(container.textContent).toBe('{"enabled":true,"signInRequested":false}');
81});
82
83it("forgetting Connect opt-in returns cloud fragments to fallback without touching the editor", async () => {
84 const { CloudAuthBoundary } = await import("./CloudAuthBoundary");
85 const { activateCloudAuthentication, deactivateCloudAuthentication } =
86 await import("./activation");
87 await act(async () => {
88 activateCloudAuthentication();
89 root.render(
90 <>
91 <input defaultValue="unsent" />
92 <CloudAuthBoundary fallback={<p>Sign in</p>}>
93 <p>Account</p>
94 </CloudAuthBoundary>
95 </>,
96 );
97 await vi.dynamicImportSettled();
98 });
99 const editor = container.querySelector("input")!;
100 await act(async () => deactivateCloudAuthentication());
101 expect(localStorage.getItem("knife:t3-connect-enabled")).toBeNull();
102 expect(container.textContent).toBe("Sign in");
103 expect(container.querySelector("input")).toBe(editor);
104 expect(editor.value).toBe("unsent");
105});