// @vitest-environment jsdom import { webcrypto } from "node:crypto"; import type { ReactNode } from "react"; import { createRoot, type Root } from "react-dom/client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { AgentRunnerChoice, CompanyPortabilityImportResult, CompanyPortabilityPreviewResult } from "@paperclipai/shared"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { ApiError } from "../api/client"; import type { CompanyImportJobAccepted } from "../api/companies"; import { CompanyImport } from "./CompanyImport"; // jsdom's crypto has no SubtleCrypto; the chunked transfer path hashes parts // with WebCrypto, so back the global with Node's implementation. if (!globalThis.crypto?.subtle) { vi.stubGlobal("crypto", webcrypto); } const mockCompaniesApi = vi.hoisted(() => ({ importPreview: vi.fn(), importPreviewPackage: vi.fn(), importBundle: vi.fn(), importBundleAsync: vi.fn(), importBundlePackageAsync: vi.fn(), importTransferCreate: vi.fn(), importTransferUploadPart: vi.fn(), importTransferStatus: vi.fn(), importTransferPreview: vi.fn(), importTransferApply: vi.fn(), getImportJob: vi.fn(), get: vi.fn(), })); const mockAgentsApi = vi.hoisted(() => ({ list: vi.fn(), resume: vi.fn(), })); const mockAdaptersApi = vi.hoisted(() => ({ list: vi.fn(), })); const mockInstanceSettingsApi = vi.hoisted(() => ({ getExperimental: vi.fn(), getGeneral: vi.fn(), get: vi.fn(), })); const mockRoutinesApi = vi.hoisted(() => ({ update: vi.fn(), })); const mockAuthApi = vi.hoisted(() => ({ getSession: vi.fn(), })); const mockSidebarPreferencesApi = vi.hoisted(() => ({ updateProjectOrder: vi.fn(), })); const mockPushToast = vi.hoisted(() => vi.fn()); const mockSetSelectedCompanyId = vi.hoisted(() => vi.fn()); const mockOptionalCompany = vi.hoisted(() => vi.fn()); const mockReadZipArchive = vi.hoisted(() => vi.fn()); vi.mock("../api/companies", () => ({ companiesApi: mockCompaniesApi, })); // Keep the real sessionStorage bookkeeping so reload-resume is exercised, but // make the poll delay instant so tests never wait the real 3s interval. vi.mock("../lib/import-job-watch", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, waitForNextImportJobPoll: () => Promise.resolve() }; }); vi.mock("../lib/zip", () => ({ readZipArchive: mockReadZipArchive, })); vi.mock("../api/agents", () => ({ agentsApi: mockAgentsApi, })); vi.mock("../api/adapters", () => ({ adaptersApi: mockAdaptersApi, })); vi.mock("../api/instanceSettings", () => ({ instanceSettingsApi: mockInstanceSettingsApi, })); vi.mock("../api/routines", () => ({ routinesApi: mockRoutinesApi, })); vi.mock("../api/auth", () => ({ authApi: mockAuthApi, })); vi.mock("../api/sidebarPreferences", () => ({ sidebarPreferencesApi: mockSidebarPreferencesApi, })); vi.mock("../context/BreadcrumbContext", () => ({ useBreadcrumbs: () => ({ setBreadcrumbs: vi.fn() }), })); vi.mock("../context/CompanyContext", () => ({ useCompany: () => ({ selectedCompanyId: "company-1", selectedCompany: { id: "company-1", name: "Paperclip" }, setSelectedCompanyId: mockSetSelectedCompanyId, }), useOptionalCompany: mockOptionalCompany, })); vi.mock("../context/ToastContext", () => ({ useToastActions: () => ({ pushToast: mockPushToast }), useOptionalToastActions: () => ({ pushToast: mockPushToast }), })); vi.mock("@/components/ui/tooltip", () => ({ TooltipProvider: ({ children }: { children: ReactNode }) => <>{children}, Tooltip: ({ children }: { children: ReactNode }) => <>{children}, TooltipContent: ({ children }: { children: ReactNode }) => {children}, TooltipTrigger: ({ children }: { children: ReactNode }) => <>{children}, })); vi.mock("../components/MarkdownBody", () => ({ MarkdownBody: ({ children }: { children: ReactNode }) =>
{children}
, })); // MarkdownEditor pulls in @mdxeditor/editor, whose sandpack dependency inserts // CSS rules jsdom cannot parse; the editor is never exercised by these tests. vi.mock("../components/MarkdownEditor", () => ({ MarkdownEditor: ({ value }: { value?: string }) =>