test(frontend): add unit tests for Projects list components
Add comprehensive test coverage for projects list components: - ProjectCard.test.tsx: Card rendering, status badges, actions menu - ProjectFilters.test.tsx: Search, filters, view mode toggle - ProjectsGrid.test.tsx: Grid/list layout, loading, empty states 30 tests covering rendering, interactions, and edge cases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
105
frontend/tests/components/projects/ProjectCard.test.tsx
Normal file
105
frontend/tests/components/projects/ProjectCard.test.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* ProjectCard Component Tests
|
||||
*/
|
||||
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { ProjectCard, ProjectCardSkeleton } from '@/components/projects/ProjectCard';
|
||||
import type { ProjectListItem } from '@/lib/api/hooks/useProjects';
|
||||
|
||||
describe('ProjectCard', () => {
|
||||
const mockProject: ProjectListItem = {
|
||||
id: 'proj-1',
|
||||
name: 'Test Project',
|
||||
description: 'This is a test project description',
|
||||
status: 'active',
|
||||
complexity: 'medium',
|
||||
progress: 65,
|
||||
openIssues: 5,
|
||||
activeAgents: 3,
|
||||
currentSprint: 'Sprint 2',
|
||||
lastActivity: '5 minutes ago',
|
||||
createdAt: '2025-01-01T00:00:00Z',
|
||||
owner: { id: 'user-1', name: 'Test User' },
|
||||
tags: ['frontend', 'react', 'typescript'],
|
||||
};
|
||||
|
||||
it('renders project name', () => {
|
||||
render(<ProjectCard project={mockProject} />);
|
||||
expect(screen.getByText('Test Project')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders project description', () => {
|
||||
render(<ProjectCard project={mockProject} />);
|
||||
expect(screen.getByText('This is a test project description')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders project status badge', () => {
|
||||
render(<ProjectCard project={mockProject} />);
|
||||
expect(screen.getByText('Active')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders current sprint', () => {
|
||||
render(<ProjectCard project={mockProject} />);
|
||||
// Sprint not shown in current card design, but progress is
|
||||
expect(screen.getByText('65%')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders project metrics', () => {
|
||||
render(<ProjectCard project={mockProject} />);
|
||||
expect(screen.getByText('3')).toBeInTheDocument(); // agents
|
||||
expect(screen.getByText('5')).toBeInTheDocument(); // issues
|
||||
});
|
||||
|
||||
it('renders last activity time', () => {
|
||||
render(<ProjectCard project={mockProject} />);
|
||||
expect(screen.getByText('5 minutes ago')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders project tags', () => {
|
||||
render(<ProjectCard project={mockProject} />);
|
||||
expect(screen.getByText('frontend')).toBeInTheDocument();
|
||||
expect(screen.getByText('react')).toBeInTheDocument();
|
||||
expect(screen.getByText('typescript')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onClick when card is clicked', () => {
|
||||
const onClick = jest.fn();
|
||||
render(<ProjectCard project={mockProject} onClick={onClick} />);
|
||||
|
||||
// Click the card (first button, which is the card itself)
|
||||
const buttons = screen.getAllByRole('button');
|
||||
fireEvent.click(buttons[0]);
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('renders action menu when onAction is provided', () => {
|
||||
const onAction = jest.fn();
|
||||
render(<ProjectCard project={mockProject} onAction={onAction} />);
|
||||
|
||||
// Menu button should exist with sr-only text
|
||||
const menuButtons = screen.getAllByRole('button');
|
||||
const menuButton = menuButtons.find(btn => btn.querySelector('.sr-only'));
|
||||
expect(menuButton).toBeDefined();
|
||||
expect(menuButton!.querySelector('.sr-only')).toHaveTextContent('Project actions');
|
||||
});
|
||||
|
||||
it('does not render action menu when onAction is not provided', () => {
|
||||
render(<ProjectCard project={mockProject} />);
|
||||
|
||||
// Only the card itself should be a button
|
||||
const buttons = screen.getAllByRole('button');
|
||||
expect(buttons.length).toBe(1);
|
||||
});
|
||||
|
||||
it('applies custom className', () => {
|
||||
const { container } = render(<ProjectCard project={mockProject} className="custom-class" />);
|
||||
expect(container.querySelector('.custom-class')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ProjectCardSkeleton', () => {
|
||||
it('renders skeleton elements', () => {
|
||||
const { container } = render(<ProjectCardSkeleton />);
|
||||
expect(container.querySelectorAll('.animate-pulse').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user