veza/apps/web/src/components/forms/FormBuilder.test.tsx
2025-12-12 21:34:34 -05:00

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();
});
});