import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, fireEvent, waitFor } from "@testing-library/react"; import { SetupWizardModal } from "../SetupWizardModal"; // Mock lucide-react vi.mock("lucide-react", async () => { const actual = await vi.importActual("lucide-react"); return { ...actual, X: ({ size, ...props }: any) => ×, Loader2: ({ size, ...props }: any) => , Sparkles: ({ size, ...props }: any) => , CheckCircle: ({ size, ...props }: any) => , Folder: ({ size, ...props }: any) => 📁, FolderOpen: ({ size, ...props }: any) => 📂, ChevronRight: ({ size, ...props }: any) => , ChevronUp: ({ size, ...props }: any) => , Eye: ({ size, ...props }: any) => 👁, EyeOff: ({ size, ...props }: any) => 🙈, AlertCircle: ({ size, ...props }: any) => , }; }); // Mock api module vi.mock("../../api", () => ({ registerProject: vi.fn(), browseDirectory: vi.fn().mockResolvedValue({ currentPath: "/home/user", parentPath: "/home", entries: [], }), })); import { registerProject } from "../../api"; const mockRegisterProject = vi.mocked(registerProject); describe("SetupWizardModal", () => { beforeEach(() => { vi.clearAllMocks(); }); it("renders with welcome message", () => { render( ); expect(screen.getByText("Welcome to kb")).toBeDefined(); expect(screen.getByText(/Let's set up your first project/)).toBeDefined(); }); it("has DirectoryPicker for path selection", () => { render( ); // DirectoryPicker renders an input and a Browse button expect(screen.getByPlaceholderText("/path/to/your/project")).toBeDefined(); expect(screen.getByText("Browse")).toBeDefined(); }); it("auto-populates project name from selected directory path", () => { render( ); // Type a path in the DirectoryPicker input const pathInput = screen.getByPlaceholderText("/path/to/your/project"); fireEvent.change(pathInput, { target: { value: "/home/user/my-awesome-project" } }); const nameInput = screen.getByPlaceholderText("my-project") as HTMLInputElement; expect(nameInput.value).toBe("my-awesome-project"); }); it("register button is disabled when required fields are empty", () => { render( ); const registerBtn = screen.getByText("Register Project").closest("button")!; expect(registerBtn.disabled).toBe(true); }); it("register button is enabled when path and name are provided", () => { render( ); fireEvent.change(screen.getByPlaceholderText("/path/to/your/project"), { target: { value: "/home/user/project" }, }); // Name auto-populates; ensure it's not empty const nameInput = screen.getByPlaceholderText("my-project") as HTMLInputElement; expect(nameInput.value).toBe("project"); const registerBtn = screen.getByText("Register Project").closest("button")!; expect(registerBtn.disabled).toBe(false); }); it("shows error state on registration failure", async () => { mockRegisterProject.mockRejectedValueOnce(new Error("Path does not exist")); render( ); fireEvent.change(screen.getByPlaceholderText("/path/to/your/project"), { target: { value: "/bad/path" }, }); fireEvent.change(screen.getByPlaceholderText("my-project"), { target: { value: "test-project" }, }); fireEvent.click(screen.getByText("Register Project")); await waitFor(() => { expect(screen.getByText("Path does not exist")).toBeDefined(); }); }); it("shows completion state after successful registration", async () => { const mockProject = { id: "proj_123", name: "test-project", path: "/home/user/project", status: "active" as const, isolationMode: "in-process" as const, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; mockRegisterProject.mockResolvedValueOnce(mockProject); const onProjectRegistered = vi.fn(); render( ); fireEvent.change(screen.getByPlaceholderText("/path/to/your/project"), { target: { value: "/home/user/project" }, }); fireEvent.change(screen.getByPlaceholderText("my-project"), { target: { value: "test-project" }, }); fireEvent.click(screen.getByText("Register Project")); await waitFor(() => { expect(screen.getByText("All Set!")).toBeDefined(); expect(screen.getByText("Get Started")).toBeDefined(); }); expect(onProjectRegistered).toHaveBeenCalledWith(mockProject); }); it("close button calls onClose", () => { const onClose = vi.fn(); render( ); fireEvent.click(screen.getByLabelText("Close wizard")); expect(onClose).toHaveBeenCalled(); }); it("renders isolation mode radio cards", () => { render( ); expect(screen.getByText("In-Process")).toBeDefined(); expect(screen.getByText("Child-Process")).toBeDefined(); expect(screen.getByText("Recommended")).toBeDefined(); }); it("can switch isolation mode", () => { render( ); // Initially in-process is selected const inProcessRadio = screen.getByDisplayValue("in-process") as HTMLInputElement; const childProcessRadio = screen.getByDisplayValue("child-process") as HTMLInputElement; expect(inProcessRadio.checked).toBe(true); expect(childProcessRadio.checked).toBe(false); fireEvent.click(childProcessRadio); expect(childProcessRadio.checked).toBe(true); }); });