- 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>
95 lines
3.0 KiB
TypeScript
95 lines
3.0 KiB
TypeScript
import React from 'react';
|
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
import { Button } from '../Button';
|
|
|
|
describe('Button', () => {
|
|
it('renders children correctly', () => {
|
|
render(<Button>Click me</Button>);
|
|
expect(screen.getByText('Click me')).toBeInTheDocument();
|
|
});
|
|
|
|
it('applies primary variant by default', () => {
|
|
render(<Button>Primary</Button>);
|
|
const button = screen.getByRole('button');
|
|
expect(button).toHaveClass('bg-primary');
|
|
});
|
|
|
|
it('applies secondary variant when specified', () => {
|
|
render(<Button variant="secondary">Secondary</Button>);
|
|
const button = screen.getByRole('button');
|
|
expect(button).toHaveClass('bg-secondary');
|
|
});
|
|
|
|
it('applies outline variant when specified', () => {
|
|
render(<Button variant="outline">Outline</Button>);
|
|
const button = screen.getByRole('button');
|
|
expect(button).toHaveClass('border-2');
|
|
expect(button).toHaveClass('border-primary');
|
|
});
|
|
|
|
it('applies danger variant when specified', () => {
|
|
render(<Button variant="danger">Danger</Button>);
|
|
const button = screen.getByRole('button');
|
|
expect(button).toHaveClass('bg-error');
|
|
});
|
|
|
|
it('applies small size correctly', () => {
|
|
render(<Button size="sm">Small</Button>);
|
|
const button = screen.getByRole('button');
|
|
expect(button).toHaveClass('px-3');
|
|
expect(button).toHaveClass('py-1.5');
|
|
});
|
|
|
|
it('applies large size correctly', () => {
|
|
render(<Button size="lg">Large</Button>);
|
|
const button = screen.getByRole('button');
|
|
expect(button).toHaveClass('px-6');
|
|
expect(button).toHaveClass('py-3');
|
|
});
|
|
|
|
it('applies full width when specified', () => {
|
|
render(<Button fullWidth>Full Width</Button>);
|
|
const button = screen.getByRole('button');
|
|
expect(button).toHaveClass('w-full');
|
|
});
|
|
|
|
it('disables button when disabled prop is true', () => {
|
|
render(<Button disabled>Disabled</Button>);
|
|
const button = screen.getByRole('button');
|
|
expect(button).toBeDisabled();
|
|
});
|
|
|
|
it('shows loading spinner when loading', () => {
|
|
render(<Button loading>Loading</Button>);
|
|
const button = screen.getByRole('button');
|
|
expect(button).toBeDisabled();
|
|
expect(button.querySelector('svg')).toBeInTheDocument();
|
|
});
|
|
|
|
it('calls onClick when clicked', () => {
|
|
const handleClick = jest.fn();
|
|
render(<Button onClick={handleClick}>Click me</Button>);
|
|
const button = screen.getByRole('button');
|
|
fireEvent.click(button);
|
|
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('does not call onClick when disabled', () => {
|
|
const handleClick = jest.fn();
|
|
render(
|
|
<Button disabled onClick={handleClick}>
|
|
Disabled
|
|
</Button>
|
|
);
|
|
const button = screen.getByRole('button');
|
|
fireEvent.click(button);
|
|
expect(handleClick).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('applies custom className', () => {
|
|
render(<Button className="custom-class">Custom</Button>);
|
|
const button = screen.getByRole('button');
|
|
expect(button).toHaveClass('custom-class');
|
|
});
|
|
});
|