import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, fireEvent, waitFor } from "@testing-library/react"; import { SetupWizardModal } from "../SetupWizardModal"; // Mock the API and utils const mockFetchFirstRunStatus = vi.fn(); const mockDetectProjects = vi.fn(); const mockRegisterProject = vi.fn(); vi.mock("../api", () => ({ fetchFirstRunStatus: (...args: unknown[]) => mockFetchFirstRunStatus(...args), detectProjects: (...args: unknown[]) => mockDetectProjects(...args), registerProject: (...args: unknown[]) => mockRegisterProject(...args), })); vi.mock("../utils/projectDetection", () => ({ scanForProjects: (...args: unknown[]) => mockDetectProjects(...args), suggestProjectName: (path: string) => path.split("/").pop() || "", isValidProjectName: (name: string) => /^[a-zA-Z0-9_-]+$/.test(name), })); // Mock lucide-react icons vi.mock("lucide-react", () => ({ X: () => ×, Loader2: () => , FolderPlus: () => 📁, Search: () => 🔍, CheckCircle: () => , ArrowRight: () => , ArrowLeft: () => , Folder: () => 📂, Check: () => , AlertCircle: () => , Pencil: () => , })); const noop = () => {}; describe("SetupWizardModal", () => { beforeEach(() => { vi.clearAllMocks(); localStorage.clear(); // Default: no projects, so wizard should auto-open mockFetchFirstRunStatus.mockResolvedValue({ hasProjects: false }); }); it("auto-opens when no projects exist", async () => { render( ); // Wait for the effect to run await waitFor(() => { expect(mockFetchFirstRunStatus).toHaveBeenCalled(); }); // Should show welcome screen await waitFor(() => { expect(screen.getByText("Welcome to kb")).toBeDefined(); }); }); it("does not auto-open when projects exist", async () => { mockFetchFirstRunStatus.mockResolvedValue({ hasProjects: true }); render( ); await waitFor(() => { expect(mockFetchFirstRunStatus).toHaveBeenCalled(); }); // Should not show welcome screen expect(screen.queryByText("Welcome to kb")).toBeNull(); }); it("shows welcome step with auto-detect and manual options", async () => { render( ); await waitFor(() => { expect(screen.getByText("Welcome to kb")).toBeDefined(); }); expect(screen.getByText("Auto-detect Projects")).toBeDefined(); expect(screen.getByText("Add Manually")).toBeDefined(); }); it("transitions to detecting step when auto-detect clicked", async () => { mockDetectProjects.mockResolvedValue({ projects: [] }); render( ); await waitFor(() => { expect(screen.getByText("Auto-detect Projects")).toBeDefined(); }); fireEvent.click(screen.getByText("Auto-detect Projects")); await waitFor(() => { expect(screen.getByText("Detecting Projects...")).toBeDefined(); }); }); it("shows review step with detected projects", async () => { mockDetectProjects.mockResolvedValue({ projects: [ { path: "/home/user/project1", suggestedName: "project1", existing: false }, { path: "/home/user/project2", suggestedName: "project2", existing: false }, ], }); render( ); await waitFor(() => { expect(screen.getByText("Auto-detect Projects")).toBeDefined(); }); fireEvent.click(screen.getByText("Auto-detect Projects")); await waitFor(() => { expect(screen.getByText("Review Detected Projects")).toBeDefined(); }); // Should show detected projects await waitFor(() => { expect(screen.getByText("project1")).toBeDefined(); expect(screen.getByText("project2")).toBeDefined(); }); }); it("transitions to manual step when manual option clicked", async () => { render( ); await waitFor(() => { expect(screen.getByText("Add Manually")).toBeDefined(); }); fireEvent.click(screen.getByText("Add Manually")); await waitFor(() => { expect(screen.getByText("Add Project Manually")).toBeDefined(); }); }); it("allows entering project details in manual step", async () => { render( ); await waitFor(() => { expect(screen.getByText("Add Manually")).toBeDefined(); }); fireEvent.click(screen.getByText("Add Manually")); await waitFor(() => { expect(screen.getByLabelText("Project Path")).toBeDefined(); }); const pathInput = screen.getByLabelText("Project Path"); fireEvent.change(pathInput, { target: { value: "/path/to/project" } }); expect(pathInput).toHaveValue("/path/to/project"); }); it("calls onProjectRegistered when manual registration succeeds", async () => { const onProjectRegistered = vi.fn(); mockRegisterProject.mockResolvedValue({ id: "proj_123", name: "Test Project", path: "/path/to/project", status: "active", isolationMode: "in-process", createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z", }); render( ); await waitFor(() => { expect(screen.getByText("Add Manually")).toBeDefined(); }); fireEvent.click(screen.getByText("Add Manually")); await waitFor(() => { expect(screen.getByLabelText("Project Path")).toBeDefined(); }); fireEvent.change(screen.getByLabelText("Project Path"), { target: { value: "/path/to/project" }, }); fireEvent.change(screen.getByLabelText("Project Name"), { target: { value: "test-project" }, }); fireEvent.click(screen.getByText("Register Project")); await waitFor(() => { expect(onProjectRegistered).toHaveBeenCalled(); }); }); it("persists wizard state to localStorage", async () => { mockDetectProjects.mockResolvedValue({ projects: [{ path: "/home/user/project1", suggestedName: "project1", existing: false }], }); render( ); await waitFor(() => { expect(screen.getByText("Auto-detect Projects")).toBeDefined(); }); fireEvent.click(screen.getByText("Auto-detect Projects")); await waitFor(() => { expect(screen.getByText("Review Detected Projects")).toBeDefined(); }); // Check localStorage was updated await waitFor(() => { const saved = localStorage.getItem("kb-setup-wizard-state"); expect(saved).toBeTruthy(); const parsed = JSON.parse(saved!); expect(parsed.inProgress).toBe(true); expect(parsed.step).toBe("review"); }); }); it("clears localStorage when closed", async () => { const onClose = vi.fn(); // Pre-populate localStorage localStorage.setItem( "kb-setup-wizard-state", JSON.stringify({ inProgress: true, step: "review", detectedProjects: [] }) ); mockFetchFirstRunStatus.mockResolvedValue({ hasProjects: false }); render( ); // Wait for modal to open await waitFor(() => { expect(screen.getByText("Review Detected Projects")).toBeDefined(); }); // Close the modal fireEvent.click(screen.getByLabelText("Close wizard")); expect(localStorage.getItem("kb-setup-wizard-state")).toBeNull(); expect(onClose).toHaveBeenCalled(); }); });