Sergei 4b60a92777 Add basic UI components for web application
- 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>
2026-01-31 18:11:31 -08:00

98 lines
3.2 KiB
TypeScript

import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Input } from '../Input';
describe('Input', () => {
it('renders input field correctly', () => {
render(<Input placeholder="Enter text" />);
expect(screen.getByPlaceholderText('Enter text')).toBeInTheDocument();
});
it('renders label when provided', () => {
render(<Input label="Email" placeholder="email@example.com" />);
expect(screen.getByText('Email')).toBeInTheDocument();
});
it('renders error message when error prop is provided', () => {
render(<Input error="This field is required" />);
expect(screen.getByText('This field is required')).toBeInTheDocument();
});
it('renders helper text when provided and no error', () => {
render(<Input helperText="Enter your email address" />);
expect(screen.getByText('Enter your email address')).toBeInTheDocument();
});
it('does not render helper text when error is present', () => {
render(
<Input
error="This field is required"
helperText="This should not be visible"
/>
);
expect(screen.queryByText('This should not be visible')).not.toBeInTheDocument();
});
it('applies error styling when error prop is provided', () => {
render(<Input error="Error" />);
const input = screen.getByRole('textbox');
expect(input).toHaveClass('border-error');
});
it('applies full width when specified', () => {
render(<Input fullWidth />);
const input = screen.getByRole('textbox');
expect(input).toHaveClass('w-full');
});
it('disables input when disabled prop is true', () => {
render(<Input disabled />);
const input = screen.getByRole('textbox');
expect(input).toBeDisabled();
});
it('accepts user input', async () => {
const user = userEvent.setup();
render(<Input placeholder="Type here" />);
const input = screen.getByPlaceholderText('Type here') as HTMLInputElement;
await user.type(input, 'Hello World');
expect(input.value).toBe('Hello World');
});
it('calls onChange when value changes', async () => {
const user = userEvent.setup();
const handleChange = jest.fn();
render(<Input onChange={handleChange} />);
const input = screen.getByRole('textbox');
await user.type(input, 'test');
expect(handleChange).toHaveBeenCalled();
});
it('applies custom className', () => {
render(<Input className="custom-input" />);
const input = screen.getByRole('textbox');
expect(input).toHaveClass('custom-input');
});
it('sets aria-invalid when error is present', () => {
render(<Input error="Error" id="test-input" />);
const input = screen.getByRole('textbox');
expect(input).toHaveAttribute('aria-invalid', 'true');
});
it('sets aria-describedby correctly for error', () => {
render(<Input error="Error message" id="test-input" />);
const input = screen.getByRole('textbox');
expect(input).toHaveAttribute('aria-describedby', 'test-input-error');
});
it('forwards ref correctly', () => {
const ref = React.createRef<HTMLInputElement>();
render(<Input ref={ref} />);
expect(ref.current).toBeInstanceOf(HTMLInputElement);
});
});