forked from cardosofelipe/fast-next-template
- Add comprehensive test coverage for FormTextarea and FormSelect components to validate rendering, accessibility, props forwarding, error handling, and behavior. - Introduced function-scoped fixtures in e2e tests to ensure test isolation and address event loop issues with pytest-asyncio and SQLAlchemy.
282 lines
8.7 KiB
TypeScript
282 lines
8.7 KiB
TypeScript
/**
|
|
* Tests for FormTextarea Component
|
|
* Verifies textarea field rendering, accessibility, and error handling
|
|
*/
|
|
|
|
import { render, screen } from '@testing-library/react';
|
|
import { FormTextarea } from '@/components/forms/FormTextarea';
|
|
import type { FieldError } from 'react-hook-form';
|
|
|
|
describe('FormTextarea', () => {
|
|
describe('Basic Rendering', () => {
|
|
it('renders with label and textarea', () => {
|
|
render(<FormTextarea label="Description" name="description" />);
|
|
|
|
expect(screen.getByLabelText('Description')).toBeInTheDocument();
|
|
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders with description', () => {
|
|
render(
|
|
<FormTextarea
|
|
label="Personality Prompt"
|
|
name="personality"
|
|
description="Define the agent's personality and behavior"
|
|
/>
|
|
);
|
|
|
|
expect(screen.getByText("Define the agent's personality and behavior")).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders description before textarea', () => {
|
|
const { container } = render(
|
|
<FormTextarea label="Description" name="description" description="Helper text" />
|
|
);
|
|
|
|
const description = container.querySelector('#description-description');
|
|
const textarea = container.querySelector('textarea');
|
|
|
|
// Get positions
|
|
const descriptionRect = description?.getBoundingClientRect();
|
|
const textareaRect = textarea?.getBoundingClientRect();
|
|
|
|
// Description should appear (both should exist)
|
|
expect(description).toBeInTheDocument();
|
|
expect(textarea).toBeInTheDocument();
|
|
|
|
// In the DOM order, description comes before textarea
|
|
expect(descriptionRect).toBeDefined();
|
|
expect(textareaRect).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('Required Field', () => {
|
|
it('shows asterisk when required is true', () => {
|
|
render(<FormTextarea label="Description" name="description" required />);
|
|
|
|
expect(screen.getByText('*')).toBeInTheDocument();
|
|
});
|
|
|
|
it('does not show asterisk when required is false', () => {
|
|
render(<FormTextarea label="Description" name="description" required={false} />);
|
|
|
|
expect(screen.queryByText('*')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Error Handling', () => {
|
|
it('displays error message when error prop is provided', () => {
|
|
const error: FieldError = {
|
|
type: 'required',
|
|
message: 'Description is required',
|
|
};
|
|
|
|
render(<FormTextarea label="Description" name="description" error={error} />);
|
|
|
|
expect(screen.getByText('Description is required')).toBeInTheDocument();
|
|
});
|
|
|
|
it('sets aria-invalid when error exists', () => {
|
|
const error: FieldError = {
|
|
type: 'required',
|
|
message: 'Description is required',
|
|
};
|
|
|
|
render(<FormTextarea label="Description" name="description" error={error} />);
|
|
|
|
const textarea = screen.getByRole('textbox');
|
|
expect(textarea).toHaveAttribute('aria-invalid', 'true');
|
|
});
|
|
|
|
it('sets aria-describedby with error ID when error exists', () => {
|
|
const error: FieldError = {
|
|
type: 'required',
|
|
message: 'Description is required',
|
|
};
|
|
|
|
render(<FormTextarea label="Description" name="description" error={error} />);
|
|
|
|
const textarea = screen.getByRole('textbox');
|
|
expect(textarea).toHaveAttribute('aria-describedby', 'description-error');
|
|
});
|
|
|
|
it('renders error with role="alert"', () => {
|
|
const error: FieldError = {
|
|
type: 'required',
|
|
message: 'Description is required',
|
|
};
|
|
|
|
render(<FormTextarea label="Description" name="description" error={error} />);
|
|
|
|
const errorElement = screen.getByRole('alert');
|
|
expect(errorElement).toHaveTextContent('Description is required');
|
|
});
|
|
});
|
|
|
|
describe('Accessibility', () => {
|
|
it('links label to textarea via htmlFor/id', () => {
|
|
render(<FormTextarea label="Description" name="description" />);
|
|
|
|
const label = screen.getByText('Description');
|
|
const textarea = screen.getByRole('textbox');
|
|
|
|
expect(label).toHaveAttribute('for', 'description');
|
|
expect(textarea).toHaveAttribute('id', 'description');
|
|
});
|
|
|
|
it('sets aria-describedby with description ID when description exists', () => {
|
|
render(
|
|
<FormTextarea
|
|
label="Description"
|
|
name="description"
|
|
description="Enter a detailed description"
|
|
/>
|
|
);
|
|
|
|
const textarea = screen.getByRole('textbox');
|
|
expect(textarea).toHaveAttribute('aria-describedby', 'description-description');
|
|
});
|
|
|
|
it('combines error and description IDs in aria-describedby', () => {
|
|
const error: FieldError = {
|
|
type: 'required',
|
|
message: 'Description is required',
|
|
};
|
|
|
|
render(
|
|
<FormTextarea
|
|
label="Description"
|
|
name="description"
|
|
description="Enter a detailed description"
|
|
error={error}
|
|
/>
|
|
);
|
|
|
|
const textarea = screen.getByRole('textbox');
|
|
expect(textarea).toHaveAttribute(
|
|
'aria-describedby',
|
|
'description-error description-description'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Textarea Props Forwarding', () => {
|
|
it('forwards textarea props correctly', () => {
|
|
render(
|
|
<FormTextarea
|
|
label="Description"
|
|
name="description"
|
|
placeholder="Enter description"
|
|
rows={5}
|
|
disabled
|
|
/>
|
|
);
|
|
|
|
const textarea = screen.getByRole('textbox');
|
|
expect(textarea).toHaveAttribute('placeholder', 'Enter description');
|
|
expect(textarea).toHaveAttribute('rows', '5');
|
|
expect(textarea).toBeDisabled();
|
|
});
|
|
|
|
it('accepts register() props via registration', () => {
|
|
const registerProps = {
|
|
name: 'description',
|
|
onChange: jest.fn(),
|
|
onBlur: jest.fn(),
|
|
ref: jest.fn(),
|
|
};
|
|
|
|
render(<FormTextarea label="Description" registration={registerProps} />);
|
|
|
|
const textarea = screen.getByRole('textbox');
|
|
expect(textarea).toBeInTheDocument();
|
|
expect(textarea).toHaveAttribute('id', 'description');
|
|
});
|
|
|
|
it('extracts name from spread props', () => {
|
|
const spreadProps = {
|
|
name: 'content',
|
|
onChange: jest.fn(),
|
|
};
|
|
|
|
render(<FormTextarea label="Content" {...spreadProps} />);
|
|
|
|
const textarea = screen.getByRole('textbox');
|
|
expect(textarea).toHaveAttribute('id', 'content');
|
|
});
|
|
});
|
|
|
|
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(<FormTextarea label="Description" />);
|
|
}).toThrow('FormTextarea: name must be provided either explicitly or via register()');
|
|
|
|
consoleError.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe('Layout and Styling', () => {
|
|
it('applies correct spacing classes', () => {
|
|
const { container } = render(<FormTextarea label="Description" name="description" />);
|
|
|
|
const wrapper = container.firstChild as HTMLElement;
|
|
expect(wrapper).toHaveClass('space-y-2');
|
|
});
|
|
|
|
it('applies correct error styling', () => {
|
|
const error: FieldError = {
|
|
type: 'required',
|
|
message: 'Description is required',
|
|
};
|
|
|
|
render(<FormTextarea label="Description" name="description" error={error} />);
|
|
|
|
const errorElement = screen.getByRole('alert');
|
|
expect(errorElement).toHaveClass('text-sm', 'text-destructive');
|
|
});
|
|
|
|
it('applies correct description styling', () => {
|
|
const { container } = render(
|
|
<FormTextarea label="Description" name="description" description="Helper text" />
|
|
);
|
|
|
|
const description = container.querySelector('#description-description');
|
|
expect(description).toHaveClass('text-sm', 'text-muted-foreground');
|
|
});
|
|
});
|
|
|
|
describe('Name Priority', () => {
|
|
it('uses explicit name over registration name', () => {
|
|
const registerProps = {
|
|
name: 'fromRegister',
|
|
onChange: jest.fn(),
|
|
onBlur: jest.fn(),
|
|
ref: jest.fn(),
|
|
};
|
|
|
|
render(<FormTextarea label="Content" name="explicit" registration={registerProps} />);
|
|
|
|
const textarea = screen.getByRole('textbox');
|
|
expect(textarea).toHaveAttribute('id', 'explicit');
|
|
});
|
|
|
|
it('uses registration name when explicit name not provided', () => {
|
|
const registerProps = {
|
|
name: 'fromRegister',
|
|
onChange: jest.fn(),
|
|
onBlur: jest.fn(),
|
|
ref: jest.fn(),
|
|
};
|
|
|
|
render(<FormTextarea label="Content" registration={registerProps} />);
|
|
|
|
const textarea = screen.getByRole('textbox');
|
|
expect(textarea).toHaveAttribute('id', 'fromRegister');
|
|
});
|
|
});
|
|
});
|