forked from cardosofelipe/fast-next-template
Standardize multiline formatting across components, tests, and API hooks for better consistency and clarity: - Adjusted function and object property indentation. - Updated tests and components to align with clean coding practices.
106 lines
3.6 KiB
TypeScript
106 lines
3.6 KiB
TypeScript
/**
|
|
* 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);
|
|
});
|
|
});
|