Files
fusion/packages/dashboard/app/components/__tests__/SetupWizard.test.tsx
gsxdsm 51e0883708 feat(KB-503): merge kb/kb-503
- feat(KB-503): complete Step 5 other command project support
- fix(KB-503): align task resolution with shared project context
- fix(KB-503): preserve local task command fallback
- test(KB-503): complete Step 4 task command coverage
- test(KB-503): harden Step 3 CLI parser coverage
- fix(KB-503): keep settings set scope explicit
- fix(KB-503): allow implicit project resolution for project settings
- fix(KB-503): enforce explicit settings scope rules
- fix(KB-503): restore shared settings resolution behavior
- fix(KB-503): align settings scope and project flag behavior
- feat(KB-503): complete Step 5 — add project-aware settings, git, and backup behavior
- fix(KB-503): enforce settings scope for global and project modes
- fix(KB-503): preserve project-scoped settings and cwd-aware git helpers
- fix(KB-503): add scoped settings behavior and git project tests
- feat(KB-503): complete Step 5 — add project-aware git and backup coverage
- test(KB-503): cover remaining project-aware task command paths
- fix(KB-503): use shared resolution flow for all task commands
- fix(KB-503): restore legacy task fallback and correct task mocks
- fix(KB-503): align task resolution order and kb storage paths
- fix(KB-503): restore local task store fallback without project flag
- test(KB-503): cover remaining project-aware task handlers
- fix(KB-503): preserve branch naming and avoid duplicate log resolution
- test(KB-503): expand project-aware task command coverage
- fix(KB-503): restore cwd fallback for path-based task commands
- fix(KB-503): add project-aware task output and tests
- fix(KB-503): harden project command output and coverage
- fix(KB-503): preserve legacy task resolution without project flag
- test(KB-503): add bin routing coverage for project flag parsing
- feat(KB-503): complete Step 3 — CLI argument parsing updates
- fix(KB-503): add defaultProjectId to GlobalSettings type and fix TypeScript errors
- test(KB-503): fix resolveProject mock in task tests for runTaskLogs follow mode
- docs(KB-503): add multi-project CLI documentation and changeset
- test(KB-503): add project-context mock to task tests
- test(KB-503): update git tests for new cwd parameter
- feat(KB-503): Step 3 — CLI argument parsing updates with --project flag support
- feat(KB-503): Steps 1-2 — project context utilities and project subcommands
2026-04-01 15:06:58 -07:00

285 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { SetupWizard } from "../SetupWizard";
import type { ProjectInfo, ProjectCreateInput } from "../../api";
// Mock lucide-react
vi.mock("lucide-react", async () => {
const actual = await vi.importActual("lucide-react");
return {
...actual,
X: () => <span data-testid="close-icon">×</span>,
ChevronRight: () => <span data-testid="next-icon"></span>,
ChevronLeft: () => <span data-testid="back-icon"></span>,
Folder: () => <span data-testid="folder-icon">📁</span>,
Check: () => <span data-testid="check-icon"></span>,
Loader2: () => <span data-testid="loader-icon"></span>,
AlertCircle: () => <span data-testid="alert-icon"></span>,
};
});
describe("SetupWizard", () => {
it("does not render when isOpen is false", () => {
render(
<SetupWizard
isOpen={false}
onClose={vi.fn()}
onProjectCreated={vi.fn()}
/>
);
expect(screen.queryByText("Add New Project")).toBeNull();
});
it("renders when isOpen is true", () => {
render(
<SetupWizard
isOpen={true}
onClose={vi.fn()}
onProjectCreated={vi.fn()}
/>
);
expect(screen.getByText("Add New Project")).toBeDefined();
});
it("starts at directory step", () => {
render(
<SetupWizard
isOpen={true}
onClose={vi.fn()}
onProjectCreated={vi.fn()}
/>
);
expect(screen.getByText("Select Project Directory")).toBeDefined();
});
it("shows step indicator with 5 steps", () => {
render(
<SetupWizard
isOpen={true}
onClose={vi.fn()}
onProjectCreated={vi.fn()}
/>
);
expect(screen.getByText("Directory")).toBeDefined();
expect(screen.getByText("Name")).toBeDefined();
expect(screen.getByText("Mode")).toBeDefined();
expect(screen.getByText("Validate")).toBeDefined();
expect(screen.getByText("Confirm")).toBeDefined();
});
it("disables Next button when directory is empty", () => {
render(
<SetupWizard
isOpen={true}
onClose={vi.fn()}
onProjectCreated={vi.fn()}
/>
);
const nextButton = screen.getByRole("button", { name: /Next/i });
expect(nextButton).toBeDisabled();
});
it("enables Next button when directory is filled", () => {
render(
<SetupWizard
isOpen={true}
onClose={vi.fn()}
onProjectCreated={vi.fn()}
/>
);
const input = screen.getByPlaceholderText("/path/to/your/project");
fireEvent.change(input, { target: { value: "/home/user/project" } });
const nextButton = screen.getByRole("button", { name: /Next/i });
expect(nextButton).not.toBeDisabled();
});
it("navigates to next step when Next is clicked", () => {
render(
<SetupWizard
isOpen={true}
onClose={vi.fn()}
onProjectCreated={vi.fn()}
/>
);
const input = screen.getByPlaceholderText("/path/to/your/project");
fireEvent.change(input, { target: { value: "/home/user/project" } });
// Find the primary button (Next) in the actions area
const nextButton = screen.getByRole("button", { name: /Next/i });
fireEvent.click(nextButton);
expect(screen.getByText("Project Name")).toBeDefined();
});
it("auto-suggests name from directory path", () => {
render(
<SetupWizard
isOpen={true}
onClose={vi.fn()}
onProjectCreated={vi.fn()}
/>
);
const input = screen.getByPlaceholderText("/path/to/your/project");
fireEvent.change(input, { target: { value: "/home/user/my-awesome-project" } });
const nextButton = screen.getByRole("button", { name: /Next/i });
fireEvent.click(nextButton);
const nameInput = screen.getByPlaceholderText("My Project") as HTMLInputElement;
expect(nameInput.value).toBe("my-awesome-project");
});
it("allows navigation back to previous step", () => {
render(
<SetupWizard
isOpen={true}
onClose={vi.fn()}
onProjectCreated={vi.fn()}
/>
);
// Go to step 2
const input = screen.getByPlaceholderText("/path/to/your/project");
fireEvent.change(input, { target: { value: "/home/user/project" } });
const nextButton = screen.getByRole("button", { name: /Next/i });
fireEvent.click(nextButton);
// Go back
const backButton = screen.getByRole("button", { name: /Back/i });
fireEvent.click(backButton);
expect(screen.getByText("Select Project Directory")).toBeDefined();
});
it("shows isolation mode options", () => {
render(
<SetupWizard
isOpen={true}
onClose={vi.fn()}
onProjectCreated={vi.fn()}
/>
);
// Navigate to step 3 (isolation)
const dirInput = screen.getByPlaceholderText("/path/to/your/project");
fireEvent.change(dirInput, { target: { value: "/home/user/project" } });
// Go to name step
fireEvent.click(screen.getByRole("button", { name: /Next/i }));
// Go to isolation step
fireEvent.click(screen.getByRole("button", { name: /Next/i }));
expect(screen.getByText("In-Process (Default)")).toBeDefined();
expect(screen.getByText("Child Process (Isolated)")).toBeDefined();
});
it("calls onClose when Cancel is clicked", () => {
const onClose = vi.fn();
render(
<SetupWizard
isOpen={true}
onClose={onClose}
onProjectCreated={vi.fn()}
/>
);
const cancelButton = screen.getByRole("button", { name: /Cancel/i });
fireEvent.click(cancelButton);
expect(onClose).toHaveBeenCalled();
});
it("calls onClose when close icon is clicked", () => {
const onClose = vi.fn();
render(
<SetupWizard
isOpen={true}
onClose={onClose}
onProjectCreated={vi.fn()}
/>
);
const closeButton = screen.getByLabelText("Close");
fireEvent.click(closeButton);
expect(onClose).toHaveBeenCalled();
});
it("submits project data when created", async () => {
const mockRegisterProject = vi.fn().mockResolvedValue({
id: "proj_123",
name: "My Project",
path: "/home/user/project",
status: "active",
isolationMode: "in-process",
} as ProjectInfo);
const onProjectCreated = vi.fn();
render(
<SetupWizard
isOpen={true}
onClose={vi.fn()}
onProjectCreated={onProjectCreated}
onRegisterProject={mockRegisterProject}
/>
);
// Fill directory
const dirInput = screen.getByPlaceholderText("/path/to/your/project");
fireEvent.change(dirInput, { target: { value: "/home/user/project" } });
// The wizard should be in directory step with a Next button
expect(screen.getByRole("button", { name: /Next/i })).toBeDefined();
// Note: Full wizard flow testing would require more complex setup
// including mocking the validation API call
});
it("resets state when reopened", () => {
const { rerender } = render(
<SetupWizard
isOpen={true}
onClose={vi.fn()}
onProjectCreated={vi.fn()}
/>
);
// Fill some data
const input = screen.getByPlaceholderText("/path/to/your/project");
fireEvent.change(input, { target: { value: "/home/user/project" } });
// Close and reopen
rerender(
<SetupWizard
isOpen={false}
onClose={vi.fn()}
onProjectCreated={vi.fn()}
/>
);
rerender(
<SetupWizard
isOpen={true}
onClose={vi.fn()}
onProjectCreated={vi.fn()}
/>
);
// Should be back at step 1 with empty fields
expect(screen.getByText("Select Project Directory")).toBeDefined();
const newInput = screen.getByPlaceholderText("/path/to/your/project") as HTMLInputElement;
expect(newInput.value).toBe("");
});
});