// @vitest-environment jsdom import { act } from "react"; import type { ReactNode } from "react"; import { createRoot } from "react-dom/client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { NewAgentDialog } from "./NewAgentDialog"; const createCompanyInviteMock = vi.hoisted(() => vi.fn()); const getInviteOnboardingMock = vi.hoisted(() => vi.fn()); const listAgentsMock = vi.hoisted(() => vi.fn()); const listAdaptersMock = vi.hoisted(() => vi.fn()); const navigateMock = vi.hoisted(() => vi.fn()); const closeNewAgentMock = vi.hoisted(() => vi.fn()); const openNewIssueMock = vi.hoisted(() => vi.fn()); const pushToastMock = vi.hoisted(() => vi.fn()); const clipboardWriteTextMock = vi.hoisted(() => vi.fn()); vi.mock("@/lib/router", () => ({ useNavigate: () => navigateMock, })); vi.mock("../context/DialogContext", () => ({ useDialog: () => ({ newAgentOpen: true, closeNewAgent: closeNewAgentMock, openNewIssue: openNewIssueMock, }), })); vi.mock("../context/CompanyContext", () => ({ useCompany: () => ({ selectedCompanyId: "company-1", }), })); vi.mock("../context/ToastContext", () => ({ useToast: () => ({ pushToast: pushToastMock }), })); vi.mock("../api/access", () => ({ accessApi: { createCompanyInvite: (companyId: string, input: unknown) => createCompanyInviteMock(companyId, input), getInviteOnboarding: (token: string) => getInviteOnboardingMock(token), }, })); vi.mock("../api/agents", () => ({ agentsApi: { list: (companyId: string) => listAgentsMock(companyId), }, })); vi.mock("../api/adapters", () => ({ adaptersApi: { list: () => listAdaptersMock(), }, })); vi.mock("../adapters", () => ({ listUIAdapters: () => [{ type: "claude_local" }, { type: "openclaw_gateway" }], })); vi.mock("../adapters/metadata", () => ({ isVisualAdapterChoice: (type: string) => type !== "openclaw_gateway", })); vi.mock("../adapters/use-disabled-adapters", () => ({ useDisabledAdaptersSync: () => new Set(), })); vi.mock("@/components/ui/dialog", () => ({ Dialog: ({ open, children }: { open: boolean; children: ReactNode }) => open ?
{children}
: null, DialogContent: ({ children, className }: { children: ReactNode; className?: string }) => (
{children}
), })); // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; async function flushReact() { await act(async () => { await Promise.resolve(); await new Promise((resolve) => window.setTimeout(resolve, 0)); }); } describe("NewAgentDialog", () => { let container: HTMLDivElement; beforeEach(() => { container = document.createElement("div"); document.body.appendChild(container); listAgentsMock.mockResolvedValue([ { id: "agent-ceo", role: "ceo" }, ]); listAdaptersMock.mockResolvedValue([]); createCompanyInviteMock.mockResolvedValue({ id: "invite-1", token: "agent-token", inviteUrl: "https://paperclip.local/invite/agent-token", expiresAt: "2026-04-20T00:00:00.000Z", allowedJoinTypes: "agent", humanRole: null, onboardingTextUrl: "https://paperclip.local/api/invites/agent-token/onboarding.txt", onboardingTextPath: "/api/invites/agent-token/onboarding.txt", }); getInviteOnboardingMock.mockResolvedValue({ onboarding: { connectivity: { connectionCandidates: ["https://paperclip.local"], testResolutionEndpoint: { url: "https://paperclip.local/api/invites/agent-token/test-resolution", }, }, }, }); Object.defineProperty(globalThis.navigator, "clipboard", { configurable: true, value: { writeText: clipboardWriteTextMock }, }); }); afterEach(() => { container.remove(); document.body.innerHTML = ""; vi.clearAllMocks(); }); it("generates an external agent onboarding prompt inside the add-agent modal", async () => { const root = createRoot(container); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, }); await act(async () => { root.render( , ); }); await flushReact(); expect(container.textContent).toContain("Add a new agent"); expect(container.textContent).toContain("Invite an external agent"); const inviteButton = Array.from(container.querySelectorAll("button")).find( (button) => button.textContent?.startsWith("Invite an external agent"), ); await act(async () => { inviteButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); expect(container.textContent).toContain("Generate a one-time onboarding prompt"); expect(container.textContent).not.toContain("Company Invites"); const generateButton = Array.from(container.querySelectorAll("button")).find( (button) => button.textContent === "Generate onboarding prompt", ); await act(async () => { generateButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await flushReact(); await flushReact(); expect(createCompanyInviteMock).toHaveBeenCalledWith("company-1", { allowedJoinTypes: "agent", humanRole: null, agentMessage: null, }); expect(getInviteOnboardingMock).toHaveBeenCalledWith("agent-token"); expect(clipboardWriteTextMock).toHaveBeenCalledWith( expect.stringContaining("You're invited to join a Paperclip company as an agent."), ); expect(container.textContent).toContain("Agent onboarding prompt"); expect(container.textContent).toContain("Send this prompt to the external agent"); expect(container.textContent).not.toContain("Optional message for the agent"); expect(container.textContent).not.toContain("Generate onboarding prompt"); expect(pushToastMock).toHaveBeenCalledWith({ title: "Agent invite created", body: "Agent onboarding prompt ready below and copied to clipboard.", tone: "success", }); const backButton = Array.from(container.querySelectorAll("button")).find( (button) => button.textContent === "Back", ); await act(async () => { backButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await flushReact(); expect(container.textContent).toContain("Optional message for the agent"); expect(container.textContent).toContain("Generate onboarding prompt"); await act(async () => { root.unmount(); }); }); });