| 1 | // @vitest-environment jsdom |
| 2 | import { act, useEffect, type ReactNode } from "react"; |
| 3 | import { createRoot, type Root } from "react-dom/client"; |
| 4 | import { afterEach, beforeEach, expect, it, vi } from "vite-plus/test"; |
| 5 | |
| 6 | const mocks = vi.hoisted(() => ({ loads: 0, mounts: 0 })); |
| 7 | vi.mock("../env", () => ({ isElectron: false })); |
| 8 | vi.mock("./publicConfig", () => ({ |
| 9 | hasCloudPublicConfig: () => true, |
| 10 | resolveCloudPublicConfig: () => ({ clerkPublishableKey: "test-key" }), |
| 11 | })); |
| 12 | vi.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 | |
| 24 | let root: Root; |
| 25 | let container: HTMLDivElement; |
| 26 | |
| 27 | beforeEach(() => { |
| 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 | }); |
| 36 | afterEach(async () => { |
| 37 | await act(async () => root.unmount()); |
| 38 | container.remove(); |
| 39 | }); |
| 40 | |
| 41 | it("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 | |
| 72 | it("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 | |
| 83 | it("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 | }); |