diff --git a/ui/src/App.test.tsx b/ui/src/App.test.tsx index d1b14463b0b..ed6bba5d9e6 100644 --- a/ui/src/App.test.tsx +++ b/ui/src/App.test.tsx @@ -42,9 +42,6 @@ vi.mock("@/lib/router", () => ({ useParams: () => ({}), })); -// eslint-disable-next-line @typescript-eslint/no-explicit-any -(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; - async function flushReact() { await Promise.resolve(); await new Promise((resolve) => window.setTimeout(resolve, 0)); @@ -58,6 +55,29 @@ async function waitForText(container: HTMLElement, text: string) { expect(container.textContent).toContain(text); } +function renderGate(container: HTMLElement) { + const root = createRoot(container); + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }); + + flushSync(() => { + root.render( + + + , + ); + }); + + return root; +} + +function unmountRoot(root: ReturnType) { + flushSync(() => { + root.unmount(); + }); +} + describe("CloudAccessGate", () => { let container: HTMLDivElement; @@ -92,26 +112,13 @@ describe("CloudAccessGate", () => { keyId: null, }); - const root = createRoot(container); - const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, - }); - - flushSync(() => { - root.render( - - - , - ); - }); + const root = renderGate(container); await waitForText(container, "No company access"); expect(container.textContent).toContain("No company access"); expect(container.textContent).not.toContain("Outlet content"); - flushSync(() => { - root.unmount(); - }); + unmountRoot(root); }); it("allows authenticated users with company access through to the board", async () => { @@ -128,26 +135,13 @@ describe("CloudAccessGate", () => { keyId: null, }); - const root = createRoot(container); - const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, - }); - - flushSync(() => { - root.render( - - - , - ); - }); + const root = renderGate(container); await waitForText(container, "Outlet content"); expect(container.textContent).toContain("Outlet content"); expect(container.textContent).not.toContain("No company access"); - flushSync(() => { - root.unmount(); - }); + unmountRoot(root); }); it("shows browser sign-in setup for signed-out private bootstrap-pending instances", async () => { @@ -160,18 +154,7 @@ describe("CloudAccessGate", () => { }); mockAuthApi.getSession.mockResolvedValue(null); - const root = createRoot(container); - const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, - }); - - flushSync(() => { - root.render( - - - , - ); - }); + const root = renderGate(container); await waitForText(container, "Finish setting up this Paperclip"); expect(container.textContent).toContain("Finish setting up this Paperclip"); @@ -179,9 +162,7 @@ describe("CloudAccessGate", () => { expect(container.textContent).toContain("pnpm paperclipai auth bootstrap-ceo"); expect(mockAccessApi.getCurrentBoardAccess).not.toHaveBeenCalled(); - flushSync(() => { - root.unmount(); - }); + unmountRoot(root); }); it("shows the claim action for signed-in private bootstrap-pending instances", async () => { @@ -198,18 +179,7 @@ describe("CloudAccessGate", () => { }); mockAccessApi.claimBootstrapAdmin.mockResolvedValue({ claimed: true, userId: "user-1" }); - const root = createRoot(container); - const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, - }); - - flushSync(() => { - root.render( - - - , - ); - }); + const root = renderGate(container); await waitForText(container, "Claim this instance"); expect(container.textContent).toContain("Claim this instance"); @@ -227,10 +197,9 @@ describe("CloudAccessGate", () => { expect(mockAccessApi.claimBootstrapAdmin).toHaveBeenCalledTimes(1); expect(container.textContent).toContain("You're the instance admin"); + expect(container.textContent).toContain("Continue to dashboard"); - flushSync(() => { - root.unmount(); - }); + unmountRoot(root); }); it("keeps public bootstrap-pending instances invite-only", async () => { @@ -246,18 +215,7 @@ describe("CloudAccessGate", () => { user: { id: "user-1", email: "user@example.com", name: "User", image: null }, }); - const root = createRoot(container); - const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, - }); - - flushSync(() => { - root.render( - - - , - ); - }); + const root = renderGate(container); await waitForText(container, "This Paperclip is waiting on its first admin"); expect(container.textContent).toContain("This Paperclip is waiting on its first admin"); @@ -266,8 +224,6 @@ describe("CloudAccessGate", () => { expect(container.textContent).not.toContain("Sign in / Create account"); expect(mockAccessApi.claimBootstrapAdmin).not.toHaveBeenCalled(); - flushSync(() => { - root.unmount(); - }); + unmountRoot(root); }); }); diff --git a/ui/src/bootstrapSetup.ts b/ui/src/bootstrapSetup.ts new file mode 100644 index 00000000000..83ebeba84f9 --- /dev/null +++ b/ui/src/bootstrapSetup.ts @@ -0,0 +1 @@ +export const BOOTSTRAP_FALLBACK_COMMAND = "pnpm paperclipai auth bootstrap-ceo"; diff --git a/ui/src/components/BootstrapPendingPage.tsx b/ui/src/components/BootstrapPendingPage.tsx index ab3784d2b1a..659ad5723cd 100644 --- a/ui/src/components/BootstrapPendingPage.tsx +++ b/ui/src/components/BootstrapPendingPage.tsx @@ -2,10 +2,9 @@ import type { ReactNode } from "react"; import { Loader2, ShieldCheck, Terminal, TriangleAlert } from "lucide-react"; import { Link } from "@/lib/router"; import { Button } from "@/components/ui/button"; +import { BOOTSTRAP_FALLBACK_COMMAND } from "@/bootstrapSetup"; import type { AuthSession } from "@paperclipai/shared"; -const FALLBACK_COMMAND = "pnpm paperclipai auth bootstrap-ceo"; - type BootstrapPendingPageProps = { claimAvailable: boolean; hasActiveInvite?: boolean; @@ -28,7 +27,7 @@ function CliFallback({ hasActiveInvite = false }: { hasActiveInvite?: boolean }) : "Run this command on the host that runs Paperclip to print a one-time first-admin invite URL:"}

-{FALLBACK_COMMAND}
+{BOOTSTRAP_FALLBACK_COMMAND}
       
); @@ -108,6 +107,11 @@ export function BootstrapPendingPage({ Redirecting... +
+ +
); } diff --git a/ui/src/pages/BootstrapSetupUxLab.tsx b/ui/src/pages/BootstrapSetupUxLab.tsx index 22810dbadd3..35d49644c8b 100644 --- a/ui/src/pages/BootstrapSetupUxLab.tsx +++ b/ui/src/pages/BootstrapSetupUxLab.tsx @@ -1,5 +1,6 @@ import type { ReactElement, ReactNode } from "react"; import { Loader2, ShieldCheck, Terminal, TriangleAlert } from "lucide-react"; +import { BOOTSTRAP_FALLBACK_COMMAND } from "@/bootstrapSetup"; import { Button } from "@/components/ui/button"; type LabFixtureKey = @@ -28,8 +29,6 @@ const FIXTURE_ORDER: LabFixtureKey[] = [ "public-invite-only", ]; -const FALLBACK_COMMAND = "pnpm paperclipai auth bootstrap-ceo"; - function CliFallback({ hasActiveInvite }: { hasActiveInvite: boolean }) { return (
@@ -43,7 +42,7 @@ function CliFallback({ hasActiveInvite }: { hasActiveInvite: boolean }) { : "Run this command on the host that runs Paperclip to print a one‑time first‑admin invite URL:"}

-{FALLBACK_COMMAND}
+{BOOTSTRAP_FALLBACK_COMMAND}
       
); @@ -170,6 +169,11 @@ function ClaimSuccess() { Redirecting… +
+ +
); }