340 lines
8.3 KiB
TypeScript
340 lines
8.3 KiB
TypeScript
import { render, screen, waitFor } from '@testing-library/react';
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { FormBuilder, FormField } from './FormBuilder';
|
|
|
|
describe('FormBuilder Component', () => {
|
|
const mockOnSubmit = vi.fn();
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
const basicFields: FormField[] = [
|
|
{
|
|
name: 'name',
|
|
type: 'text',
|
|
label: 'Name',
|
|
required: true,
|
|
},
|
|
];
|
|
|
|
it('renders form with fields', () => {
|
|
render(<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} />);
|
|
|
|
expect(screen.getByLabelText(/Name/)).toBeInTheDocument();
|
|
expect(screen.getByText('Submit')).toBeInTheDocument();
|
|
});
|
|
|
|
it('uses custom submit label', () => {
|
|
render(
|
|
<FormBuilder
|
|
fields={basicFields}
|
|
onSubmit={mockOnSubmit}
|
|
submitLabel="Save"
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('Save')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders text input field', () => {
|
|
render(<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} />);
|
|
|
|
const input = screen.getByLabelText(/Name/);
|
|
expect(input).toHaveAttribute('type', 'text');
|
|
});
|
|
|
|
it('renders email input field', () => {
|
|
const fields: FormField[] = [
|
|
{
|
|
name: 'email',
|
|
type: 'email',
|
|
label: 'Email',
|
|
},
|
|
];
|
|
|
|
render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);
|
|
|
|
const input = screen.getByLabelText('Email');
|
|
expect(input).toHaveAttribute('type', 'email');
|
|
});
|
|
|
|
it('renders password input field', () => {
|
|
const fields: FormField[] = [
|
|
{
|
|
name: 'password',
|
|
type: 'password',
|
|
label: 'Password',
|
|
},
|
|
];
|
|
|
|
render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);
|
|
|
|
const input = screen.getByLabelText('Password');
|
|
expect(input).toHaveAttribute('type', 'password');
|
|
});
|
|
|
|
it('renders number input field', () => {
|
|
const fields: FormField[] = [
|
|
{
|
|
name: 'age',
|
|
type: 'number',
|
|
label: 'Age',
|
|
},
|
|
];
|
|
|
|
render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);
|
|
|
|
const input = screen.getByLabelText('Age');
|
|
expect(input).toHaveAttribute('type', 'number');
|
|
});
|
|
|
|
it('renders textarea field', () => {
|
|
const fields: FormField[] = [
|
|
{
|
|
name: 'description',
|
|
type: 'textarea',
|
|
label: 'Description',
|
|
},
|
|
];
|
|
|
|
render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);
|
|
|
|
const textarea = screen.getByLabelText('Description');
|
|
expect(textarea.tagName).toBe('TEXTAREA');
|
|
});
|
|
|
|
it('renders select field', () => {
|
|
const fields: FormField[] = [
|
|
{
|
|
name: 'country',
|
|
type: 'select',
|
|
label: 'Country',
|
|
options: [
|
|
{ value: 'us', label: 'United States' },
|
|
{ value: 'fr', label: 'France' },
|
|
],
|
|
},
|
|
];
|
|
|
|
render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);
|
|
|
|
expect(screen.getByText(/Country/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders date picker field', () => {
|
|
const fields: FormField[] = [
|
|
{
|
|
name: 'birthday',
|
|
type: 'date',
|
|
label: 'Birthday',
|
|
},
|
|
];
|
|
|
|
render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);
|
|
|
|
expect(screen.getByText(/Birthday/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders file upload field', () => {
|
|
const fields: FormField[] = [
|
|
{
|
|
name: 'avatar',
|
|
type: 'file',
|
|
label: 'Avatar',
|
|
},
|
|
];
|
|
|
|
render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);
|
|
|
|
expect(screen.getByText(/Avatar/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('shows required indicator', () => {
|
|
render(<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} />);
|
|
|
|
const label = screen.getByText(/Name/);
|
|
expect(label.textContent).toContain('*');
|
|
});
|
|
|
|
it('updates form data when field value changes', async () => {
|
|
const user = userEvent.setup();
|
|
render(<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} />);
|
|
|
|
const input = screen.getByLabelText(/Name/);
|
|
await user.type(input, 'John Doe');
|
|
|
|
expect(input).toHaveValue('John Doe');
|
|
});
|
|
|
|
it('validates required fields', async () => {
|
|
const user = userEvent.setup();
|
|
render(<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} />);
|
|
|
|
const submitButton = screen.getByText('Submit');
|
|
await user.click(submitButton);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/is required/)).toBeInTheDocument();
|
|
});
|
|
|
|
expect(mockOnSubmit).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('validates email format', async () => {
|
|
const user = userEvent.setup();
|
|
const fields: FormField[] = [
|
|
{
|
|
name: 'email',
|
|
type: 'email',
|
|
label: 'Email',
|
|
},
|
|
];
|
|
|
|
render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);
|
|
|
|
const input = screen.getByLabelText('Email');
|
|
await user.type(input, 'invalid-email');
|
|
|
|
await user.tab(); // Trigger blur
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/valid email address/)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('calls custom validation function', async () => {
|
|
const user = userEvent.setup();
|
|
const customValidation = vi.fn((value) => {
|
|
if (value.length < 5) {
|
|
return 'Must be at least 5 characters';
|
|
}
|
|
return null;
|
|
});
|
|
|
|
const fields: FormField[] = [
|
|
{
|
|
name: 'username',
|
|
type: 'text',
|
|
label: 'Username',
|
|
validation: customValidation,
|
|
},
|
|
];
|
|
|
|
render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);
|
|
|
|
const input = screen.getByLabelText('Username');
|
|
await user.type(input, 'abc');
|
|
|
|
await user.tab(); // Trigger blur
|
|
|
|
await waitFor(() => {
|
|
expect(customValidation).toHaveBeenCalled();
|
|
expect(screen.getByText(/at least 5 characters/)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('submits form with valid data', async () => {
|
|
const user = userEvent.setup();
|
|
render(<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} />);
|
|
|
|
const input = screen.getByLabelText(/Name/);
|
|
await user.type(input, 'John Doe');
|
|
|
|
const submitButton = screen.getByText('Submit');
|
|
await user.click(submitButton);
|
|
|
|
await waitFor(() => {
|
|
expect(mockOnSubmit).toHaveBeenCalledWith({
|
|
name: 'John Doe',
|
|
});
|
|
});
|
|
});
|
|
|
|
it('uses default values', () => {
|
|
const fields: FormField[] = [
|
|
{
|
|
name: 'name',
|
|
type: 'text',
|
|
label: 'Name',
|
|
defaultValue: 'Default Name',
|
|
},
|
|
];
|
|
|
|
render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);
|
|
|
|
const input = screen.getByLabelText(/Name/);
|
|
expect(input).toHaveValue('Default Name');
|
|
});
|
|
|
|
it('disables form when disabled prop is true', () => {
|
|
render(
|
|
<FormBuilder fields={basicFields} onSubmit={mockOnSubmit} disabled />,
|
|
);
|
|
|
|
const input = screen.getByLabelText(/Name/);
|
|
expect(input).toBeDisabled();
|
|
|
|
const submitButton = screen.getByText('Submit');
|
|
expect(submitButton).toBeDisabled();
|
|
});
|
|
|
|
it('disables individual fields', () => {
|
|
const fields: FormField[] = [
|
|
{
|
|
name: 'name',
|
|
type: 'text',
|
|
label: 'Name',
|
|
disabled: true,
|
|
},
|
|
];
|
|
|
|
render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);
|
|
|
|
const input = screen.getByLabelText(/Name/);
|
|
expect(input).toBeDisabled();
|
|
});
|
|
|
|
it('shows placeholder text', () => {
|
|
const fields: FormField[] = [
|
|
{
|
|
name: 'name',
|
|
type: 'text',
|
|
label: 'Name',
|
|
placeholder: 'Enter your name',
|
|
},
|
|
];
|
|
|
|
render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);
|
|
|
|
const input = screen.getByLabelText(/Name/);
|
|
expect(input).toHaveAttribute('placeholder', 'Enter your name');
|
|
});
|
|
|
|
it('handles multiple fields', () => {
|
|
const fields: FormField[] = [
|
|
{
|
|
name: 'firstName',
|
|
type: 'text',
|
|
label: 'First Name',
|
|
},
|
|
{
|
|
name: 'lastName',
|
|
type: 'text',
|
|
label: 'Last Name',
|
|
},
|
|
{
|
|
name: 'email',
|
|
type: 'email',
|
|
label: 'Email',
|
|
},
|
|
];
|
|
|
|
render(<FormBuilder fields={fields} onSubmit={mockOnSubmit} />);
|
|
|
|
expect(screen.getByLabelText('First Name')).toBeInTheDocument();
|
|
expect(screen.getByLabelText('Last Name')).toBeInTheDocument();
|
|
expect(screen.getByLabelText('Email')).toBeInTheDocument();
|
|
});
|
|
});
|