/** * ProjectWizard Component Tests * * Tests for the main project creation wizard component. */ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ProjectWizard } from '@/components/projects/wizard/ProjectWizard'; // Mock step components jest.mock('@/components/projects/wizard/steps', () => ({ BasicInfoStep: jest.fn(({ updateState }) => (
)), ComplexityStep: jest.fn(({ updateState }) => (
)), ClientModeStep: jest.fn(() =>
), AutonomyStep: jest.fn(() =>
), AgentChatStep: jest.fn(() =>
), ReviewStep: jest.fn(({ state }) => (
Project: {state.projectName}
)), })); // Mock StepIndicator jest.mock('@/components/projects/wizard/StepIndicator', () => ({ StepIndicator: jest.fn(({ currentStep, isScriptMode }) => (
Step {currentStep} {isScriptMode && '(script mode)'}
)), })); // Mock useWizardState hook const mockUpdateState = jest.fn(); const mockResetState = jest.fn(); const mockGoNext = jest.fn(); const mockGoBack = jest.fn(); const mockGetProjectData = jest.fn(() => ({ name: 'Test Project', slug: 'test-project', description: 'Test description', autonomy_level: 'milestone', settings: {}, })); let mockWizardState = { step: 1 as 1 | 2 | 3 | 4 | 5 | 6, projectName: 'Test Project', description: '', repoUrl: '', complexity: null as string | null, clientMode: null as string | null, autonomyLevel: null as string | null, }; jest.mock('@/components/projects/wizard/useWizardState', () => ({ useWizardState: jest.fn(() => ({ state: mockWizardState, updateState: mockUpdateState, resetState: mockResetState, isScriptMode: mockWizardState.complexity === 'script', canProceed: mockWizardState.projectName.length > 0, goNext: mockGoNext, goBack: mockGoBack, getProjectData: mockGetProjectData, })), })); // Mock router const mockPush = jest.fn(); jest.mock('next/navigation', () => ({ useRouter: () => ({ push: mockPush, }), })); // Mock API client const mockPost = jest.fn(); jest.mock('@/lib/api/client', () => ({ apiClient: { instance: { post: jest.fn((url, data) => mockPost(url, data)), }, }, })); function createWrapper() { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); return ({ children }: { children: React.ReactNode }) => ( {children} ); } describe('ProjectWizard', () => { beforeEach(() => { jest.clearAllMocks(); mockWizardState = { step: 1, projectName: 'Test Project', description: '', repoUrl: '', complexity: null, clientMode: null, autonomyLevel: null, }; mockPost.mockResolvedValue({ data: { id: 'project-123', name: 'Test Project', slug: 'test-project', }, }); }); describe('Rendering', () => { it('renders the step indicator', () => { render(, { wrapper: createWrapper() }); expect(screen.getByTestId('step-indicator')).toBeInTheDocument(); }); it('renders BasicInfoStep on step 1', () => { mockWizardState.step = 1; render(, { wrapper: createWrapper() }); expect(screen.getByTestId('basic-info-step')).toBeInTheDocument(); }); it('renders ComplexityStep on step 2', () => { mockWizardState.step = 2; render(, { wrapper: createWrapper() }); expect(screen.getByTestId('complexity-step')).toBeInTheDocument(); }); it('renders AgentChatStep on step 5', () => { mockWizardState.step = 5; render(, { wrapper: createWrapper() }); expect(screen.getByTestId('agent-chat-step')).toBeInTheDocument(); }); it('renders ReviewStep on step 6', () => { mockWizardState.step = 6; render(, { wrapper: createWrapper() }); expect(screen.getByTestId('review-step')).toBeInTheDocument(); }); it('applies custom className', () => { const { container } = render(, { wrapper: createWrapper(), }); expect(container.firstChild).toHaveClass('custom-class'); }); }); describe('Navigation', () => { it('calls goNext when Next button is clicked', async () => { const user = userEvent.setup(); mockWizardState.step = 1; render(, { wrapper: createWrapper() }); await user.click(screen.getByRole('button', { name: /next/i })); expect(mockGoNext).toHaveBeenCalled(); }); it('calls goBack when Back button is clicked', async () => { const user = userEvent.setup(); mockWizardState.step = 2; render(, { wrapper: createWrapper() }); await user.click(screen.getByRole('button', { name: /back/i })); expect(mockGoBack).toHaveBeenCalled(); }); it('hides Back button on step 1', () => { mockWizardState.step = 1; render(, { wrapper: createWrapper() }); const backButton = screen.getByRole('button', { name: /back/i }); expect(backButton).toHaveClass('invisible'); }); it('shows Back button visible on step 2', () => { mockWizardState.step = 2; render(, { wrapper: createWrapper() }); const backButton = screen.getByRole('button', { name: /back/i }); expect(backButton).not.toHaveClass('invisible'); }); it('shows Create Project button on review step', () => { mockWizardState.step = 6; render(, { wrapper: createWrapper() }); expect(screen.getByRole('button', { name: /create project/i })).toBeInTheDocument(); }); }); describe('Script Mode', () => { it('skips client mode step in script mode', () => { mockWizardState.step = 3; mockWizardState.complexity = 'script'; render(, { wrapper: createWrapper() }); // ClientModeStep should not render for script mode expect(screen.queryByTestId('client-mode-step')).not.toBeInTheDocument(); }); it('skips autonomy step in script mode', () => { mockWizardState.step = 4; mockWizardState.complexity = 'script'; render(, { wrapper: createWrapper() }); // AutonomyStep should not render for script mode expect(screen.queryByTestId('autonomy-step')).not.toBeInTheDocument(); }); it('shows script mode indicator', () => { mockWizardState.complexity = 'script'; render(, { wrapper: createWrapper() }); expect(screen.getByText(/script mode/i)).toBeInTheDocument(); }); }); describe('Project Creation', () => { it('shows success screen after creation', async () => { const user = userEvent.setup(); mockWizardState.step = 6; render(, { wrapper: createWrapper() }); await user.click(screen.getByRole('button', { name: /create project/i })); await waitFor(() => { expect(screen.getByText(/project created successfully/i)).toBeInTheDocument(); }); }); it('displays project name in success message', async () => { const user = userEvent.setup(); mockWizardState.step = 6; render(, { wrapper: createWrapper() }); await user.click(screen.getByRole('button', { name: /create project/i })); await waitFor(() => { expect(screen.getByText(/test project/i)).toBeInTheDocument(); }); }); it('navigates to project dashboard on success', async () => { const user = userEvent.setup(); mockWizardState.step = 6; render(, { wrapper: createWrapper() }); await user.click(screen.getByRole('button', { name: /create project/i })); await waitFor(() => { expect(screen.getByRole('button', { name: /go to project dashboard/i })).toBeInTheDocument(); }); await user.click(screen.getByRole('button', { name: /go to project dashboard/i })); expect(mockPush).toHaveBeenCalledWith('/en/projects/test-project'); }); it('allows creating another project', async () => { const user = userEvent.setup(); mockWizardState.step = 6; render(, { wrapper: createWrapper() }); await user.click(screen.getByRole('button', { name: /create project/i })); await waitFor(() => { expect(screen.getByRole('button', { name: /create another project/i })).toBeInTheDocument(); }); await user.click(screen.getByRole('button', { name: /create another project/i })); expect(mockResetState).toHaveBeenCalled(); }); }); describe('Error Handling', () => { it('shows error message on creation failure', async () => { mockPost.mockRejectedValue(new Error('Network error')); const user = userEvent.setup(); mockWizardState.step = 6; render(, { wrapper: createWrapper() }); await user.click(screen.getByRole('button', { name: /create project/i })); await waitFor(() => { expect(screen.getByText(/failed to create project/i)).toBeInTheDocument(); }); }); }); describe('Button States', () => { it('disables Next button when cannot proceed', () => { mockWizardState.projectName = ''; render(, { wrapper: createWrapper() }); expect(screen.getByRole('button', { name: /next/i })).toBeDisabled(); }); it('enables Next button when can proceed', () => { mockWizardState.projectName = 'Valid Name'; render(, { wrapper: createWrapper() }); expect(screen.getByRole('button', { name: /next/i })).not.toBeDisabled(); }); it('shows loading state during creation', async () => { mockPost.mockImplementation( () => new Promise((resolve) => setTimeout(() => resolve({ data: {} }), 1000)) ); const user = userEvent.setup(); mockWizardState.step = 6; render(, { wrapper: createWrapper() }); await user.click(screen.getByRole('button', { name: /create project/i })); expect(screen.getByText(/creating/i)).toBeInTheDocument(); }); }); });