/** * Tests for FormField Component * Verifies form field rendering, accessibility, and error handling */ import { render, screen } from '@testing-library/react'; import { FormField } from '@/components/forms/FormField'; import { FieldError } from 'react-hook-form'; describe('FormField', () => { describe('Basic Rendering', () => { it('renders with label and input', () => { render(); expect(screen.getByLabelText('Email')).toBeInTheDocument(); expect(screen.getByRole('textbox')).toBeInTheDocument(); }); it('renders with description', () => { render(); expect(screen.getByText('Choose a unique username')).toBeInTheDocument(); }); it('renders children content', () => { render( Password requirements: 8+ characters ); expect(screen.getByText(/Password requirements/)).toBeInTheDocument(); }); }); describe('Required Field', () => { it('shows asterisk when required is true', () => { render(); expect(screen.getByText('*')).toBeInTheDocument(); }); it('does not show asterisk when required is false', () => { render(); expect(screen.queryByText('*')).not.toBeInTheDocument(); }); }); describe('Error Handling', () => { it('displays error message when error prop is provided', () => { const error: FieldError = { type: 'required', message: 'Email is required', }; render(); expect(screen.getByText('Email is required')).toBeInTheDocument(); }); it('sets aria-invalid when error exists', () => { const error: FieldError = { type: 'required', message: 'Email is required', }; render(); const input = screen.getByRole('textbox'); expect(input).toHaveAttribute('aria-invalid', 'true'); }); it('sets aria-describedby with error ID when error exists', () => { const error: FieldError = { type: 'required', message: 'Email is required', }; render(); const input = screen.getByRole('textbox'); expect(input).toHaveAttribute('aria-describedby', 'email-error'); }); it('renders error with role="alert"', () => { const error: FieldError = { type: 'required', message: 'Email is required', }; render(); const errorElement = screen.getByRole('alert'); expect(errorElement).toHaveTextContent('Email is required'); }); }); describe('Accessibility', () => { it('links label to input via htmlFor/id', () => { render(); const label = screen.getByText('Email'); const input = screen.getByRole('textbox'); expect(label).toHaveAttribute('for', 'email'); expect(input).toHaveAttribute('id', 'email'); }); it('sets aria-describedby with description ID when description exists', () => { render(); const input = screen.getByRole('textbox'); expect(input).toHaveAttribute('aria-describedby', 'username-description'); }); it('combines error and description IDs in aria-describedby', () => { const error: FieldError = { type: 'required', message: 'Username is required', }; render( ); const input = screen.getByRole('textbox'); expect(input).toHaveAttribute('aria-describedby', 'username-error username-description'); }); }); describe('Input Props Forwarding', () => { it('forwards input props correctly', () => { render( ); const input = screen.getByRole('textbox'); expect(input).toHaveAttribute('type', 'email'); expect(input).toHaveAttribute('placeholder', 'Enter your email'); expect(input).toBeDisabled(); }); it('accepts register() props', () => { const registerProps = { name: 'email', onChange: jest.fn(), onBlur: jest.fn(), ref: jest.fn(), }; render(); const input = screen.getByRole('textbox'); expect(input).toBeInTheDocument(); // Input ID should match the name from register props expect(input).toHaveAttribute('id', 'email'); }); }); describe('Error Cases', () => { it('throws error when name is not provided', () => { // Suppress console.error for this test const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); expect(() => { render(); }).toThrow('FormField: name must be provided either explicitly or via register()'); consoleError.mockRestore(); }); }); describe('Layout and Styling', () => { it('applies correct spacing classes', () => { const { container } = render(); const wrapper = container.firstChild as HTMLElement; expect(wrapper).toHaveClass('space-y-2'); }); it('applies correct error styling', () => { const error: FieldError = { type: 'required', message: 'Email is required', }; render(); const errorElement = screen.getByRole('alert'); expect(errorElement).toHaveClass('text-sm', 'text-destructive'); }); it('applies correct description styling', () => { const { container } = render( ); const description = container.querySelector('#email-description'); expect(description).toHaveClass('text-sm', 'text-muted-foreground'); }); }); });
Password requirements: 8+ characters