- Set up Tailwind CSS configuration for styling - Create Button component with variants (primary, secondary, outline, ghost, danger) - Create Input component with label, error, and helper text support - Create Card component with composable subcomponents (Header, Title, Description, Content, Footer) - Create LoadingSpinner component with size and fullscreen options - Create ErrorMessage component with retry and dismiss actions - Add comprehensive test suite using Jest and React Testing Library - Configure ESLint and Jest for quality assurance All components follow consistent design patterns from mobile app and include proper TypeScript types and accessibility features. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
142 lines
4.7 KiB
TypeScript
142 lines
4.7 KiB
TypeScript
import React from 'react';
|
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
import {
|
|
Card,
|
|
CardHeader,
|
|
CardTitle,
|
|
CardDescription,
|
|
CardContent,
|
|
CardFooter,
|
|
} from '../Card';
|
|
|
|
describe('Card', () => {
|
|
it('renders children correctly', () => {
|
|
render(<Card>Card content</Card>);
|
|
expect(screen.getByText('Card content')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders with base card styling', () => {
|
|
const { container } = render(<Card>Content</Card>);
|
|
const card = container.firstChild as HTMLElement;
|
|
expect(card.className).toContain('bg-white');
|
|
expect(card.className).toContain('rounded-lg');
|
|
});
|
|
|
|
it('applies padding classes based on padding prop', () => {
|
|
const { container: container1 } = render(<Card padding="sm">Content</Card>);
|
|
expect((container1.firstChild as HTMLElement).className).toContain('p-3');
|
|
|
|
const { container: container2 } = render(<Card padding="md">Content</Card>);
|
|
expect((container2.firstChild as HTMLElement).className).toContain('p-4');
|
|
|
|
const { container: container3 } = render(<Card padding="lg">Content</Card>);
|
|
expect((container3.firstChild as HTMLElement).className).toContain('p-6');
|
|
});
|
|
|
|
it('applies hover class when hover prop is true', () => {
|
|
const { container } = render(<Card hover>Hoverable card</Card>);
|
|
const card = container.firstChild as HTMLElement;
|
|
expect(card.className).toContain('hover:shadow-md');
|
|
});
|
|
|
|
it('renders as button role when onClick is provided', () => {
|
|
const handleClick = jest.fn();
|
|
render(<Card onClick={handleClick}>Clickable card</Card>);
|
|
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
});
|
|
|
|
it('calls onClick when clicked', () => {
|
|
const handleClick = jest.fn();
|
|
render(<Card onClick={handleClick}>Clickable card</Card>);
|
|
const card = screen.getByRole('button');
|
|
fireEvent.click(card);
|
|
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('applies custom className', () => {
|
|
const { container } = render(<Card className="custom-class">Content</Card>);
|
|
const card = container.firstChild as HTMLElement;
|
|
expect(card.className).toContain('custom-class');
|
|
});
|
|
});
|
|
|
|
describe('CardHeader', () => {
|
|
it('renders children correctly', () => {
|
|
render(<CardHeader>Header content</CardHeader>);
|
|
expect(screen.getByText('Header content')).toBeInTheDocument();
|
|
});
|
|
|
|
it('applies border bottom styling', () => {
|
|
const { container } = render(<CardHeader>Header</CardHeader>);
|
|
const header = container.firstChild as HTMLElement;
|
|
expect(header.className).toContain('border-b');
|
|
});
|
|
});
|
|
|
|
describe('CardTitle', () => {
|
|
it('renders title correctly', () => {
|
|
render(<CardTitle>Card Title</CardTitle>);
|
|
expect(screen.getByText('Card Title')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders as h3 element', () => {
|
|
render(<CardTitle>Title</CardTitle>);
|
|
const title = screen.getByText('Title');
|
|
expect(title.tagName).toBe('H3');
|
|
});
|
|
});
|
|
|
|
describe('CardDescription', () => {
|
|
it('renders description correctly', () => {
|
|
render(<CardDescription>Card description</CardDescription>);
|
|
expect(screen.getByText('Card description')).toBeInTheDocument();
|
|
});
|
|
|
|
it('applies text styling', () => {
|
|
render(<CardDescription>Description</CardDescription>);
|
|
const description = screen.getByText('Description');
|
|
expect(description).toHaveClass('text-sm');
|
|
expect(description).toHaveClass('text-textSecondary');
|
|
});
|
|
});
|
|
|
|
describe('CardContent', () => {
|
|
it('renders content correctly', () => {
|
|
render(<CardContent>Content area</CardContent>);
|
|
expect(screen.getByText('Content area')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('CardFooter', () => {
|
|
it('renders footer correctly', () => {
|
|
render(<CardFooter>Footer content</CardFooter>);
|
|
expect(screen.getByText('Footer content')).toBeInTheDocument();
|
|
});
|
|
|
|
it('applies border top styling', () => {
|
|
const { container } = render(<CardFooter>Footer</CardFooter>);
|
|
const footer = container.firstChild as HTMLElement;
|
|
expect(footer.className).toContain('border-t');
|
|
});
|
|
});
|
|
|
|
describe('Card composition', () => {
|
|
it('renders complete card with all components', () => {
|
|
render(
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Test Card</CardTitle>
|
|
<CardDescription>This is a test card</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>Main content</CardContent>
|
|
<CardFooter>Footer actions</CardFooter>
|
|
</Card>
|
|
);
|
|
|
|
expect(screen.getByText('Test Card')).toBeInTheDocument();
|
|
expect(screen.getByText('This is a test card')).toBeInTheDocument();
|
|
expect(screen.getByText('Main content')).toBeInTheDocument();
|
|
expect(screen.getByText('Footer actions')).toBeInTheDocument();
|
|
});
|
|
});
|