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(); expect(screen.getByLabelText(/Name/)).toBeInTheDocument(); expect(screen.getByText('Submit')).toBeInTheDocument(); }); it('uses custom submit label', () => { render( , ); expect(screen.getByText('Save')).toBeInTheDocument(); }); it('renders text input field', () => { render(); 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(); 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(); 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(); const input = screen.getByLabelText('Age'); expect(input).toHaveAttribute('type', 'number'); }); it('renders textarea field', () => { const fields: FormField[] = [ { name: 'description', type: 'textarea', label: 'Description', }, ]; render(); 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(); expect(screen.getByText(/Country/)).toBeInTheDocument(); }); it('renders date picker field', () => { const fields: FormField[] = [ { name: 'birthday', type: 'date', label: 'Birthday', }, ]; render(); expect(screen.getByText(/Birthday/)).toBeInTheDocument(); }); it('renders file upload field', () => { const fields: FormField[] = [ { name: 'avatar', type: 'file', label: 'Avatar', }, ]; render(); expect(screen.getByText(/Avatar/)).toBeInTheDocument(); }); it('shows required indicator', () => { render(); const label = screen.getByText(/Name/); expect(label.textContent).toContain('*'); }); it('updates form data when field value changes', async () => { const user = userEvent.setup(); render(); 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(); 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(); 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(); 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(); 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(); const input = screen.getByLabelText(/Name/); expect(input).toHaveValue('Default Name'); }); it('disables form when disabled prop is true', () => { render( , ); 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(); 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(); 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(); expect(screen.getByLabelText('First Name')).toBeInTheDocument(); expect(screen.getByLabelText('Last Name')).toBeInTheDocument(); expect(screen.getByLabelText('Email')).toBeInTheDocument(); }); });