@@ -44,7 +44,7 @@ jest.mock('../ProgressBar', () => {
};
});
-jest.mock('../PhaseIndicator', () => {
+jest.mock('@/components/PhaseIndicator', () => {
return function MockPhaseIndicator({ phase }: { phase: string }) {
return
{phase};
};
diff --git a/web-ui/src/components/PRDModal.test.tsx b/web-ui/__tests__/components/PRDModal.test.tsx
similarity index 98%
rename from web-ui/src/components/PRDModal.test.tsx
rename to web-ui/__tests__/components/PRDModal.test.tsx
index ecb95b51..0c0fe43b 100644
--- a/web-ui/src/components/PRDModal.test.tsx
+++ b/web-ui/__tests__/components/PRDModal.test.tsx
@@ -1,11 +1,11 @@
/**
* Tests for PRDModal Component
- * TDD: RED phase - These tests should fail initially
+ * Migrated from src/components/PRDModal.test.tsx
*/
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
-import PRDModal from './PRDModal';
+import PRDModal from '@/components/PRDModal';
import type { PRDResponse } from '@/types/api';
describe('PRDModal', () => {
diff --git a/web-ui/src/components/__tests__/PhaseIndicator.test.tsx b/web-ui/__tests__/components/PhaseIndicator.test.tsx
similarity index 98%
rename from web-ui/src/components/__tests__/PhaseIndicator.test.tsx
rename to web-ui/__tests__/components/PhaseIndicator.test.tsx
index dce427cd..21996df9 100644
--- a/web-ui/src/components/__tests__/PhaseIndicator.test.tsx
+++ b/web-ui/__tests__/components/PhaseIndicator.test.tsx
@@ -1,10 +1,10 @@
/**
* Tests for PhaseIndicator Component (cf-17.2)
- * TDD RED Phase - Write tests first
+ * Migrated from src/components/__tests__/PhaseIndicator.test.tsx
*/
import { render, screen } from '@testing-library/react';
-import PhaseIndicator from '../PhaseIndicator';
+import PhaseIndicator from '@/components/PhaseIndicator';
describe('PhaseIndicator Component', () => {
describe('Phase Text Display', () => {
diff --git a/web-ui/src/components/__tests__/ProgressBar.test.tsx b/web-ui/__tests__/components/ProgressBar.test.tsx
similarity index 98%
rename from web-ui/src/components/__tests__/ProgressBar.test.tsx
rename to web-ui/__tests__/components/ProgressBar.test.tsx
index e6a8d5fc..bac26c19 100644
--- a/web-ui/src/components/__tests__/ProgressBar.test.tsx
+++ b/web-ui/__tests__/components/ProgressBar.test.tsx
@@ -1,10 +1,10 @@
/**
* Tests for ProgressBar Component (cf-17.2)
- * TDD RED Phase - Write tests first
+ * Migrated from src/components/__tests__/ProgressBar.test.tsx
*/
import { render, screen } from '@testing-library/react';
-import ProgressBar from '../ProgressBar';
+import ProgressBar from '@/components/ProgressBar';
describe('ProgressBar Component', () => {
describe('Rendering and Width', () => {
diff --git a/web-ui/src/components/__tests__/ProjectCreationForm.test.tsx b/web-ui/__tests__/components/ProjectCreationForm.test.tsx
similarity index 99%
rename from web-ui/src/components/__tests__/ProjectCreationForm.test.tsx
rename to web-ui/__tests__/components/ProjectCreationForm.test.tsx
index 2e970cf6..6495d3f5 100644
--- a/web-ui/src/components/__tests__/ProjectCreationForm.test.tsx
+++ b/web-ui/__tests__/components/ProjectCreationForm.test.tsx
@@ -1,13 +1,13 @@
/**
* Tests for ProjectCreationForm Component
* Feature: 011-project-creation-flow
- * Sprint: 9.5 - Critical UX Fixes
+ * Migrated from src/components/__tests__/ProjectCreationForm.test.tsx
*/
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
-import ProjectCreationForm from '../ProjectCreationForm';
+import ProjectCreationForm from '@/components/ProjectCreationForm';
// Create mock functions
const mockPost = jest.fn();
diff --git a/web-ui/src/components/__tests__/Spinner.test.tsx b/web-ui/__tests__/components/Spinner.test.tsx
similarity index 94%
rename from web-ui/src/components/__tests__/Spinner.test.tsx
rename to web-ui/__tests__/components/Spinner.test.tsx
index 9de2882a..ea2a2985 100644
--- a/web-ui/src/components/__tests__/Spinner.test.tsx
+++ b/web-ui/__tests__/components/Spinner.test.tsx
@@ -1,12 +1,12 @@
/**
* Tests for Spinner Component
* Feature: 011-project-creation-flow (User Story 5)
- * Sprint: 9.5 - Critical UX Fixes
+ * Migrated from src/components/__tests__/Spinner.test.tsx
*/
import React from 'react';
import { render, screen } from '@testing-library/react';
-import { Spinner } from '../Spinner';
+import { Spinner } from '@/components/Spinner';
describe('Spinner', () => {
test('renders with default medium size', () => {
diff --git a/web-ui/src/components/TaskTreeView.test.tsx b/web-ui/__tests__/components/TaskTreeView.test.tsx
similarity index 99%
rename from web-ui/src/components/TaskTreeView.test.tsx
rename to web-ui/__tests__/components/TaskTreeView.test.tsx
index fc09ef93..b3d340b3 100644
--- a/web-ui/src/components/TaskTreeView.test.tsx
+++ b/web-ui/__tests__/components/TaskTreeView.test.tsx
@@ -1,15 +1,15 @@
/**
* Tests for TaskTreeView Component
- * TDD: RED phase - These tests should fail initially
+ * Migrated from src/components/TaskTreeView.test.tsx
*/
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
-import TaskTreeView from './TaskTreeView';
+import TaskTreeView from '@/components/TaskTreeView';
import type { Issue, Task } from '@/types/api';
// Mock QualityGateStatus component to avoid async issues in tests
-jest.mock('./quality-gates/QualityGateStatus', () => {
+jest.mock('@/components/quality-gates/QualityGateStatus', () => {
return function QualityGateStatus() {
return 'Quality Gate Status Mock';
};
@@ -208,7 +208,7 @@ describe('TaskTreeView', () => {
// Use getAllByText since task numbers appear multiple times
const task001Elements = screen.getAllByText(/T-001/i);
expect(task001Elements.length).toBeGreaterThan(0);
-
+
const task002Elements = screen.getAllByText(/T-002/i);
expect(task002Elements.length).toBeGreaterThan(0);
});
@@ -678,4 +678,4 @@ describe('TaskTreeView', () => {
expect(blockedBadges.length).toBe(0);
});
});
-});
\ No newline at end of file
+});
diff --git a/web-ui/src/lib/__tests__/api.test.ts b/web-ui/__tests__/lib/api.test.ts
similarity index 99%
rename from web-ui/src/lib/__tests__/api.test.ts
rename to web-ui/__tests__/lib/api.test.ts
index 3714e876..d1f222da 100644
--- a/web-ui/src/lib/__tests__/api.test.ts
+++ b/web-ui/__tests__/lib/api.test.ts
@@ -1,6 +1,7 @@
/**
* Tests for API client methods
* Following TDD methodology - tests written BEFORE implementation
+ * Migrated from src/lib/__tests__/api.test.ts
*/
import type { ProjectResponse, StartProjectResponse } from '@/types';
@@ -40,7 +41,7 @@ jest.mock('axios', () => {
});
// Now import the API module after mocking axios
-import { projectsApi, blockersApi } from '../api';
+import { projectsApi, blockersApi } from '@/lib/api';
beforeEach(() => {
jest.clearAllMocks();
diff --git a/web-ui/src/app/__tests__/page.test.tsx b/web-ui/src/app/__tests__/page.test.tsx
deleted file mode 100644
index 9ed2ba98..00000000
--- a/web-ui/src/app/__tests__/page.test.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * Tests for HomePage Component
- * Feature: 011-project-creation-flow
- *
- * Tests the home page which displays the project list.
- */
-
-import React from 'react';
-import { render, screen } from '@testing-library/react';
-import { useRouter } from 'next/navigation';
-import HomePage from '../page';
-
-// Mock Next.js router
-jest.mock('next/navigation', () => ({
- useRouter: jest.fn(),
-}));
-
-// Mock ProtectedRoute to pass through children
-jest.mock('@/components/auth/ProtectedRoute', () => ({
- ProtectedRoute: ({ children }: { children: React.ReactNode }) => (
-
{children}
- ),
-}));
-
-// Mock ProjectList component
-jest.mock('@/components/ProjectList', () => {
- return function MockProjectList() {
- return
ProjectList Component
;
- };
-});
-
-describe('HomePage', () => {
- const mockPush = jest.fn();
-
- beforeEach(() => {
- mockPush.mockClear();
- (useRouter as jest.Mock).mockReturnValue({
- push: mockPush,
- });
- });
-
- describe('Page Structure', () => {
- test('renders wrapped in ProtectedRoute', () => {
- render(
);
-
- expect(screen.getByTestId('protected-route')).toBeInTheDocument();
- });
-
- test('renders page heading', () => {
- render(
);
-
- expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Your Projects');
- });
-
- test('renders tagline', () => {
- render(
);
-
- expect(screen.getByText(/ai coding agents that work autonomously while you sleep/i)).toBeInTheDocument();
- });
-
- test('renders ProjectList component', () => {
- render(
);
-
- expect(screen.getByTestId('project-list')).toBeInTheDocument();
- });
- });
-
- describe('Responsive Layout', () => {
- test('has responsive container classes', () => {
- const { container } = render(
);
-
- const mainElement = container.querySelector('main');
- expect(mainElement).toHaveClass('min-h-screen', 'bg-background');
- });
-
- test('has max-width container', () => {
- const { container } = render(
);
-
- const contentDiv = container.querySelector('.max-w-7xl');
- expect(contentDiv).toBeInTheDocument();
- });
- });
-});
diff --git a/web-ui/src/components/AgentCard.test.tsx b/web-ui/src/components/AgentCard.test.tsx
deleted file mode 100644
index 2b622eb9..00000000
--- a/web-ui/src/components/AgentCard.test.tsx
+++ /dev/null
@@ -1,406 +0,0 @@
-/**
- * Tests for AgentCard component (cf-8ip: Phase 5.1)
- */
-
-import React from 'react';
-import { render, screen, fireEvent } from '@testing-library/react';
-import '@testing-library/jest-dom';
-import { AgentCard, Agent } from './AgentCard';
-
-describe('AgentCard Component', () => {
- const mockOnAgentClick = jest.fn();
-
- beforeEach(() => {
- mockOnAgentClick.mockClear();
- });
-
- describe('Display Agent Information', () => {
- it('should display agent ID', () => {
- const agent: Agent = {
- id: 'backend-worker-001',
- type: 'backend-worker',
- status: 'idle',
- tasksCompleted: 0,
- };
-
- render(
);
-
- expect(screen.getByText('backend-worker-001')).toBeInTheDocument();
- });
-
- it('should display formatted agent type', () => {
- const agent: Agent = {
- id: 'frontend-specialist-001',
- type: 'frontend-specialist',
- status: 'idle',
- tasksCompleted: 5,
- };
-
- render(
);
-
- expect(screen.getByText('Frontend Specialist')).toBeInTheDocument();
- });
-
- it('should display tasks completed counter', () => {
- const agent: Agent = {
- id: 'test-engineer-001',
- type: 'test-engineer',
- status: 'busy',
- currentTask: 15,
- tasksCompleted: 7,
- };
-
- render(
);
-
- expect(screen.getByText('Tasks Completed')).toBeInTheDocument();
- expect(screen.getByText('7')).toBeInTheDocument();
- });
- });
-
- describe('Status Colors', () => {
- it('should show green for idle status', () => {
- const agent: Agent = {
- id: 'agent-001',
- type: 'backend',
- status: 'idle',
- tasksCompleted: 0,
- };
-
- const { container } = render(
);
-
- const card = container.firstChild as HTMLElement;
- expect(card).toHaveClass('bg-secondary', 'border-border', 'text-secondary-foreground');
- expect(screen.getByText('Idle')).toBeInTheDocument();
- });
-
- it('should show yellow for busy status', () => {
- const agent: Agent = {
- id: 'agent-002',
- type: 'backend',
- status: 'busy',
- currentTask: 10,
- tasksCompleted: 3,
- };
-
- const { container } = render(
);
-
- const card = container.firstChild as HTMLElement;
- expect(card).toHaveClass('bg-primary/20', 'border-border', 'text-foreground');
- expect(screen.getByText('Working')).toBeInTheDocument();
- });
-
- it('should show red for blocked status', () => {
- const agent: Agent = {
- id: 'agent-003',
- type: 'backend',
- status: 'blocked',
- blockedBy: [5, 6],
- tasksCompleted: 1,
- };
-
- const { container } = render(
);
-
- const card = container.firstChild as HTMLElement;
- expect(card).toHaveClass('bg-destructive/10', 'border-destructive', 'text-destructive-foreground');
- expect(screen.getByText('Blocked')).toBeInTheDocument();
- });
- });
-
- describe('Current Task Display', () => {
- it('should show current task when agent is busy', () => {
- const agent: Agent = {
- id: 'agent-004',
- type: 'backend-worker',
- status: 'busy',
- currentTask: 42,
- tasksCompleted: 5,
- };
-
- render(
);
-
- expect(screen.getByText('Current Task:')).toBeInTheDocument();
- expect(screen.getByText('Task #42')).toBeInTheDocument();
- });
-
- it('should NOT show current task when agent is idle', () => {
- const agent: Agent = {
- id: 'agent-005',
- type: 'backend',
- status: 'idle',
- tasksCompleted: 2,
- };
-
- render(
);
-
- expect(screen.queryByText('Current Task:')).not.toBeInTheDocument();
- expect(screen.getByText('Ready for work')).toBeInTheDocument();
- });
-
- it('should NOT show current task when agent is blocked', () => {
- const agent: Agent = {
- id: 'agent-006',
- type: 'frontend',
- status: 'blocked',
- blockedBy: [10],
- tasksCompleted: 3,
- };
-
- render(
);
-
- expect(screen.queryByText('Current Task:')).not.toBeInTheDocument();
- });
- });
-
- describe('Blocked Status Display', () => {
- it('should show blocked by information when blocked by single task', () => {
- const agent: Agent = {
- id: 'agent-007',
- type: 'test',
- status: 'blocked',
- blockedBy: [15],
- tasksCompleted: 1,
- };
-
- render(
);
-
- expect(screen.getByText('Blocked By:')).toBeInTheDocument();
- expect(screen.getByText('Task #15')).toBeInTheDocument();
- });
-
- it('should show count when blocked by multiple tasks', () => {
- const agent: Agent = {
- id: 'agent-008',
- type: 'backend',
- status: 'blocked',
- blockedBy: [10, 11, 12],
- tasksCompleted: 2,
- };
-
- render(
);
-
- expect(screen.getByText('Blocked By:')).toBeInTheDocument();
- expect(screen.getByText('3 tasks')).toBeInTheDocument();
- });
-
- it('should NOT show blocked by section when not blocked', () => {
- const agent: Agent = {
- id: 'agent-009',
- type: 'backend',
- status: 'idle',
- tasksCompleted: 0,
- };
-
- render(
);
-
- expect(screen.queryByText('Blocked By:')).not.toBeInTheDocument();
- });
- });
-
- describe('Agent Type Badges', () => {
- it('should show backend badge with correct icon', () => {
- const agent: Agent = {
- id: 'agent-010',
- type: 'backend-worker',
- status: 'idle',
- tasksCompleted: 0,
- };
-
- render(
);
-
- const badge = screen.getByText('Backend Worker').parentElement;
- expect(badge).toHaveClass('bg-primary/10', 'text-primary-foreground');
- expect(screen.getByText('โ๏ธ')).toBeInTheDocument();
- });
-
- it('should show frontend badge with correct icon', () => {
- const agent: Agent = {
- id: 'agent-011',
- type: 'frontend-specialist',
- status: 'idle',
- tasksCompleted: 0,
- };
-
- render(
);
-
- const badge = screen.getByText('Frontend Specialist').parentElement;
- expect(badge).toHaveClass('bg-secondary', 'text-secondary-foreground');
- expect(screen.getByText('๐จ')).toBeInTheDocument();
- });
-
- it('should show test badge with correct icon', () => {
- const agent: Agent = {
- id: 'agent-012',
- type: 'test-engineer',
- status: 'idle',
- tasksCompleted: 0,
- };
-
- render(
);
-
- const badge = screen.getByText('Test Engineer').parentElement;
- expect(badge).toHaveClass('bg-secondary', 'text-secondary-foreground');
- expect(screen.getByText('๐งช')).toBeInTheDocument();
- });
-
- it('should show default badge for unknown agent type', () => {
- const agent: Agent = {
- id: 'agent-013',
- type: 'custom-agent',
- status: 'idle',
- tasksCompleted: 0,
- };
-
- render(
);
-
- const badge = screen.getByText('Custom Agent').parentElement;
- expect(badge).toHaveClass('bg-muted', 'text-foreground');
- expect(screen.getByText('๐ค')).toBeInTheDocument();
- });
- });
-
- describe('Click Interaction', () => {
- it('should call onAgentClick when card is clicked', () => {
- const agent: Agent = {
- id: 'agent-014',
- type: 'backend',
- status: 'idle',
- tasksCompleted: 0,
- };
-
- const { container } = render(
);
-
- const card = container.firstChild as HTMLElement;
- fireEvent.click(card);
-
- expect(mockOnAgentClick).toHaveBeenCalledTimes(1);
- expect(mockOnAgentClick).toHaveBeenCalledWith('agent-014');
- });
-
- it('should handle optional onAgentClick callback', () => {
- const agent: Agent = {
- id: 'agent-015',
- type: 'backend',
- status: 'idle',
- tasksCompleted: 0,
- };
-
- const { container } = render(
);
-
- const card = container.firstChild as HTMLElement;
- // Should not throw error when onAgentClick is undefined
- expect(() => fireEvent.click(card)).not.toThrow();
- });
- });
-
- describe('Status Indicator', () => {
- it('should show animated pulse dot for all statuses', () => {
- const statuses: Array<'idle' | 'busy' | 'blocked'> = ['idle', 'busy', 'blocked'];
-
- statuses.forEach((status) => {
- const agent: Agent = {
- id: `agent-${status}`,
- type: 'backend',
- status,
- tasksCompleted: 0,
- };
-
- const { container } = render(
);
-
- const dot = container.querySelector('.animate-pulse');
- expect(dot).toBeInTheDocument();
- expect(dot).toHaveClass('w-3', 'h-3', 'rounded-full');
- });
- });
- });
-
- describe('Responsive Design', () => {
- it('should have proper styling classes for responsiveness', () => {
- const agent: Agent = {
- id: 'agent-016',
- type: 'backend',
- status: 'idle',
- tasksCompleted: 0,
- };
-
- const { container } = render(
);
-
- const card = container.firstChild as HTMLElement;
- expect(card).toHaveClass('rounded-lg', 'border-2', 'p-4');
- expect(card).toHaveClass('transition-all', 'duration-200');
- expect(card).toHaveClass('hover:shadow-sm', 'cursor-pointer');
- });
-
- it('should truncate long agent IDs', () => {
- const agent: Agent = {
- id: 'very-long-agent-identifier-that-should-be-truncated',
- type: 'backend',
- status: 'idle',
- tasksCompleted: 0,
- };
-
- render(
);
-
- const agentIdElement = screen.getByText(agent.id);
- expect(agentIdElement).toHaveClass('truncate', 'max-w-[150px]');
- expect(agentIdElement).toHaveAttribute('title', agent.id);
- });
- });
-
- describe('Edge Cases', () => {
- it('should handle zero tasks completed', () => {
- const agent: Agent = {
- id: 'agent-017',
- type: 'backend',
- status: 'idle',
- tasksCompleted: 0,
- };
-
- render(
);
-
- expect(screen.getByText('0')).toBeInTheDocument();
- });
-
- it('should handle large tasks completed number', () => {
- const agent: Agent = {
- id: 'agent-018',
- type: 'backend',
- status: 'idle',
- tasksCompleted: 9999,
- };
-
- render(
);
-
- expect(screen.getByText('9999')).toBeInTheDocument();
- });
-
- it('should handle undefined currentTask gracefully', () => {
- const agent: Agent = {
- id: 'agent-019',
- type: 'backend',
- status: 'busy',
- tasksCompleted: 5,
- // currentTask is undefined
- };
-
- render(
);
-
- // Should not show current task section when undefined
- expect(screen.queryByText('Current Task:')).not.toBeInTheDocument();
- });
-
- it('should handle empty blockedBy array', () => {
- const agent: Agent = {
- id: 'agent-020',
- type: 'backend',
- status: 'blocked',
- blockedBy: [],
- tasksCompleted: 2,
- };
-
- render(
);
-
- // Should not show blocked by section when array is empty
- expect(screen.queryByText('Blocked By:')).not.toBeInTheDocument();
- });
- });
-});
diff --git a/web-ui/src/components/__tests__/ProjectList.test.tsx b/web-ui/src/components/__tests__/ProjectList.test.tsx
deleted file mode 100644
index da803381..00000000
--- a/web-ui/src/components/__tests__/ProjectList.test.tsx
+++ /dev/null
@@ -1,293 +0,0 @@
-import { render, screen, waitFor } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import { SWRConfig } from 'swr';
-import { useRouter } from 'next/navigation';
-import ProjectList from '@/components/ProjectList';
-import { projectsApi } from '@/lib/api';
-
-// Mock Next.js router
-jest.mock('next/navigation', () => ({
- useRouter: jest.fn(),
-}));
-
-// Mock projectsApi
-jest.mock('@/lib/api', () => ({
- projectsApi: {
- list: jest.fn(),
- startProject: jest.fn(),
- },
-}));
-
-// Mock ProjectCreationForm component
-jest.mock('@/components/ProjectCreationForm', () => {
- return function MockProjectCreationForm({
- onSuccess,
- onSubmit,
- }: {
- onSuccess: (projectId: number) => void;
- onSubmit?: () => void;
- onError?: () => void;
- }) {
- return (
-
-
-
- );
- };
-});
-
-// Mock Spinner component
-jest.mock('@/components/Spinner', () => ({
- Spinner: ({ size }: { size: string }) => (
-
Loading...
- ),
-}));
-
-// Mock Hugeicons
-jest.mock('@hugeicons/react', () => ({
- Add01Icon: ({ className }: { className?: string }) => (
-
- ),
-}));
-
-// Helper to render with SWR wrapper
-const renderWithSWR = (component: React.ReactElement) => {
- return render(
-
new Map(), dedupingInterval: 0 }}>
- {component}
-
- );
-};
-
-describe('ProjectList', () => {
- const mockPush = jest.fn();
-
- beforeEach(() => {
- jest.clearAllMocks();
- (useRouter as jest.Mock).mockReturnValue({
- push: mockPush,
- });
- (projectsApi.startProject as jest.Mock).mockResolvedValue({});
- });
-
- test('shows loading state while fetching projects', () => {
- (projectsApi.list as jest.Mock).mockImplementation(
- () => new Promise(() => {}) // Never resolves
- );
-
- renderWithSWR(
);
-
- expect(screen.getByText(/loading/i)).toBeInTheDocument();
- });
-
- test('renders project cards with correct data (name, status, phase, date)', async () => {
- const mockProjects = [
- {
- id: 1,
- name: 'Project A',
- status: 'init',
- phase: 'discovery',
- created_at: '2025-01-15T10:00:00Z',
- },
- {
- id: 2,
- name: 'Project B',
- status: 'running',
- phase: 'planning',
- created_at: '2025-01-14T09:00:00Z',
- },
- ];
-
- (projectsApi.list as jest.Mock).mockResolvedValue({
- data: { projects: mockProjects },
- });
-
- renderWithSWR(
);
-
- await waitFor(() => {
- expect(screen.getByText('Project A')).toBeInTheDocument();
- });
-
- expect(screen.getByText('Project B')).toBeInTheDocument();
- // Check for status and phase values (text is split across elements)
- const allText = screen.getByText('Project A').closest('div')!.textContent;
- expect(allText).toContain('Status:');
- expect(allText).toContain('init');
- expect(allText).toContain('Phase:');
- expect(allText).toContain('discovery');
-
- const projectBText = screen.getByText('Project B').closest('div')!.textContent;
- expect(projectBText).toContain('running');
- expect(projectBText).toContain('planning');
- });
-
- test('navigates to project page when card is clicked', async () => {
- const mockProjects = [
- {
- id: 1,
- name: 'Project A',
- status: 'init',
- phase: 'discovery',
- created_at: '2025-01-15T10:00:00Z',
- },
- ];
-
- (projectsApi.list as jest.Mock).mockResolvedValue({
- data: { projects: mockProjects },
- });
-
- renderWithSWR(
);
-
- await waitFor(() => {
- expect(screen.getByText('Project A')).toBeInTheDocument();
- });
-
- const projectCard = screen.getByText('Project A').closest('div');
- await userEvent.click(projectCard!);
-
- expect(mockPush).toHaveBeenCalledWith('/projects/1');
- });
-
- test('shows empty state when no projects exist', async () => {
- (projectsApi.list as jest.Mock).mockResolvedValue({
- data: { projects: [] },
- });
-
- renderWithSWR(
);
-
- await waitFor(() => {
- expect(screen.getByText(/No projects yet/i)).toBeInTheDocument();
- });
-
- // Also check for the CTA text
- expect(screen.getByText(/Create your first project/i)).toBeInTheDocument();
- });
-
- test('shows "Create New Project" button', async () => {
- (projectsApi.list as jest.Mock).mockResolvedValue({
- data: { projects: [] },
- });
-
- renderWithSWR(
);
-
- await waitFor(() => {
- expect(screen.getByTestId('create-project-button')).toBeInTheDocument();
- });
- });
-
- test('shows ProjectCreationForm when Create button is clicked', async () => {
- (projectsApi.list as jest.Mock).mockResolvedValue({
- data: { projects: [] },
- });
-
- renderWithSWR(
);
-
- await waitFor(() => {
- expect(screen.getByTestId('create-project-button')).toBeInTheDocument();
- });
-
- const createButton = screen.getByTestId('create-project-button');
- await userEvent.click(createButton);
-
- expect(screen.getByTestId('project-creation-form')).toBeInTheDocument();
- });
-
- test('navigates to project dashboard after creation', async () => {
- (projectsApi.list as jest.Mock).mockResolvedValue({
- data: { projects: [] },
- });
-
- renderWithSWR(
);
-
- await waitFor(() => {
- expect(screen.getByTestId('create-project-button')).toBeInTheDocument();
- });
-
- // Show form
- const createButton = screen.getByTestId('create-project-button');
- await userEvent.click(createButton);
-
- expect(screen.getByTestId('project-creation-form')).toBeInTheDocument();
-
- // Submit form
- const submitButton = screen.getByText('Submit Form');
- await userEvent.click(submitButton);
-
- // Should navigate to project dashboard
- await waitFor(() => {
- expect(mockPush).toHaveBeenCalledWith('/projects/3');
- });
- });
-
- test('starts discovery after project creation', async () => {
- (projectsApi.list as jest.Mock).mockResolvedValue({
- data: { projects: [] },
- });
-
- renderWithSWR(
);
-
- await waitFor(() => {
- expect(screen.getByTestId('create-project-button')).toBeInTheDocument();
- });
-
- // Show form and submit
- const createButton = screen.getByTestId('create-project-button');
- await userEvent.click(createButton);
-
- const submitButton = screen.getByText('Submit Form');
- await userEvent.click(submitButton);
-
- // Should call startProject
- await waitFor(() => {
- expect(projectsApi.startProject).toHaveBeenCalledWith(3);
- });
- });
-
- test('shows error state if fetch fails', async () => {
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
-
- (projectsApi.list as jest.Mock).mockRejectedValue(
- new Error('Failed to fetch projects')
- );
-
- renderWithSWR(
);
-
- await waitFor(() => {
- expect(
- screen.getByText(/Failed to load projects/i)
- ).toBeInTheDocument();
- });
-
- consoleErrorSpy.mockRestore();
- });
-
- test('formats created_at date in readable format', async () => {
- const mockProjects = [
- {
- id: 1,
- name: 'Project A',
- status: 'init',
- phase: 'discovery',
- created_at: '2025-01-15T10:00:00Z',
- },
- ];
-
- (projectsApi.list as jest.Mock).mockResolvedValue({
- data: { projects: mockProjects },
- });
-
- renderWithSWR(
);
-
- await waitFor(() => {
- expect(screen.getByText(/January 15, 2025/)).toBeInTheDocument();
- });
- });
-});