import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { z } from 'zod';
import { FormGenerator, useFormGenerator } from '../FormGenerator';
import { SchemaParser } from '../SchemaParser';
import '@testing-library/jest-dom';
jest.mock('framer-motion', () => ({
motion: {
div: ({ children, ...props }: any) =>
{children}
,
},
AnimatePresence: ({ children }: any) => children,
}));
describe('FormGenerator', () => {
describe('Basic form generation', () => {
it('should render a simple text field', () => {
const schema = z.object({
name: z.string()
});
render();
expect(screen.getByText('Test Form')).toBeInTheDocument();
expect(screen.getByLabelText('Name')).toBeInTheDocument();
});
it('should render multiple field types', () => {
const schema = z.object({
name: z.string(),
age: z.number(),
isActive: z.boolean(),
role: z.enum(['admin', 'user', 'guest'])
});
render();
expect(screen.getByLabelText('Name')).toBeInTheDocument();
expect(screen.getByLabelText('Age')).toBeInTheDocument();
expect(screen.getByLabelText('Is Active')).toBeInTheDocument();
expect(screen.getByText('Select an option')).toBeInTheDocument();
});
it('should handle optional fields', () => {
const schema = z.object({
name: z.string(),
nickname: z.string().optional(),
age: z.number().default(25)
});
render();
expect(screen.getByLabelText('Name')).toBeInTheDocument();
expect(screen.getByLabelText('Nickname')).toBeInTheDocument();
expect(screen.getByLabelText('Age')).toBeInTheDocument();
expect(screen.getByText('*')).toBeInTheDocument();
});
});
describe('Progressive disclosure', () => {
it('should support progressive disclosure with groups', () => {
const schema = z.object({
name: z.string(),
email: z.string().email(),
advancedOption: z.string().optional(),
expertSetting: z.number().optional()
});
const options = {
progressiveDisclosure: {
enabled: true,
groups: [
{ name: 'Basic Info', priority: 'essential' as const, defaultExpanded: true },
{ name: 'Advanced', priority: 'advanced' as const, collapsible: true },
{ name: 'Expert', priority: 'expert' as const, collapsible: true }
],
defaultExpanded: ['Basic Info'],
showFieldCount: true
}
};
render();
expect(screen.getByLabelText('Name')).toBeInTheDocument();
expect(screen.getByLabelText('Email')).toBeInTheDocument();
});
it('should expand and collapse groups', async () => {
const schema = z.object({
name: z.string(),
advancedOption: z.string().optional()
});
const options = {
progressiveDisclosure: {
enabled: true,
groups: [
{ name: 'Advanced', priority: 'advanced' as const, collapsible: true }
],
showFieldCount: true
}
};
render();
const advancedToggle = screen.getByText('Advanced');
fireEvent.click(advancedToggle);
expect(screen.getByText(/field/)).toBeInTheDocument();
});
});
describe('Form validation', () => {
it('should validate required fields', async () => {
const schema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Invalid email')
});
const onSubmit = jest.fn();
render();
const submitButton = screen.getByText('Submit');
fireEvent.click(submitButton);
await waitFor(() => {
expect(screen.getByText('Name is required')).toBeInTheDocument();
});
expect(onSubmit).not.toHaveBeenCalled();
});
it('should call onSubmit with valid data', async () => {
const schema = z.object({
name: z.string(),
age: z.number()
});
const onSubmit = jest.fn();
render();
const nameInput = screen.getByLabelText('Name');
const ageInput = screen.getByLabelText('Age');
fireEvent.change(nameInput, { target: { value: 'John Doe' } });
fireEvent.change(ageInput, { target: { value: '30' } });
const submitButton = screen.getByText('Submit');
fireEvent.click(submitButton);
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
name: 'John Doe',
age: 30
});
});
});
});
describe('Value changes', () => {
it('should call onValueChange when field values change', () => {
const schema = z.object({
name: z.string()
});
const onValueChange = jest.fn();
render();
const nameInput = screen.getByLabelText('Name');
fireEvent.change(nameInput, { target: { value: 'John' } });
expect(onValueChange).toHaveBeenCalledWith(['name'], 'John');
});
it('should update form state when values change', () => {
const schema = z.object({
name: z.string(),
age: z.number()
});
render();
const nameInput = screen.getByLabelText('Name');
fireEvent.change(nameInput, { target: { value: 'John' } });
expect(nameInput).toHaveValue('John');
});
});
describe('Initial values', () => {
it('should use provided initial values', () => {
const schema = z.object({
name: z.string(),
age: z.number()
});
const initialValues = { name: 'Initial Name', age: 25 };
render();
expect(screen.getByDisplayValue('Initial Name')).toBeInTheDocument();
expect(screen.getByDisplayValue('25')).toBeInTheDocument();
});
it('should use schema defaults when no initial values provided', () => {
const schema = z.object({
name: z.string().default('Default Name'),
age: z.number().default(30)
});
render();
expect(screen.getByDisplayValue('Default Name')).toBeInTheDocument();
expect(screen.getByDisplayValue('30')).toBeInTheDocument();
});
});
describe('Form actions', () => {
it('should reset form to initial values', () => {
const schema = z.object({
name: z.string().default('Default')
});
render();
const nameInput = screen.getByLabelText('Name');
fireEvent.change(nameInput, { target: { value: 'Changed' } });
expect(nameInput).toHaveValue('Changed');
const resetButton = screen.getByText('Reset');
fireEvent.click(resetButton);
expect(nameInput).toHaveValue('Default');
});
});
});
describe('SchemaParser', () => {
it('should parse simple object schema', () => {
const schema = z.object({
name: z.string(),
age: z.number(),
isActive: z.boolean()
});
const parser = new SchemaParser(schema);
const parsed = parser.parse();
expect(parsed.fields).toHaveProperty('name');
expect(parsed.fields).toHaveProperty('age');
expect(parsed.fields).toHaveProperty('isActive');
expect(parsed.fields.name.fieldType).toBe('text');
expect(parsed.fields.age.fieldType).toBe('number');
expect(parsed.fields.isActive.fieldType).toBe('checkbox');
});
it('should extract field metadata correctly', () => {
const schema = z.object({
email: z.string().email(),
age: z.number().min(18).max(120),
role: z.enum(['admin', 'user'])
});
const parser = new SchemaParser(schema);
const parsed = parser.parse();
expect(parsed.metadata.email.constraints).toHaveProperty('type', 'email');
expect(parsed.metadata.age.constraints).toHaveProperty('min', 18);
expect(parsed.metadata.age.constraints).toHaveProperty('max', 120);
expect(parsed.metadata.role.options).toHaveLength(2);
});
it('should generate default values', () => {
const schema = z.object({
name: z.string().default('John'),
age: z.number().default(25),
isActive: z.boolean().default(true)
});
const parser = new SchemaParser(schema);
const defaults = parser.getDefaultValues();
expect(defaults).toEqual({
name: 'John',
age: 25,
isActive: true
});
});
it('should validate values against schema', () => {
const schema = z.object({
name: z.string().min(1),
age: z.number().min(18)
});
const parser = new SchemaParser(schema);
const validValues = { name: 'John', age: 25 };
const validErrors = parser.validate(validValues);
expect(validErrors).toHaveLength(0);
const invalidValues = { name: '', age: 15 };
const invalidErrors = parser.validate(invalidValues);
expect(invalidErrors.length).toBeGreaterThan(0);
});
});
describe('useFormGenerator hook', () => {
it('should provide form state and actions', () => {
const TestComponent = () => {
const schema = z.object({ name: z.string() });
const { state, actions } = useFormGenerator(schema);
return (
{state.isDirty.toString()}
);
};
render();
expect(screen.getByTestId('isDirty')).toHaveTextContent('false');
fireEvent.click(screen.getByText('Set Name'));
expect(screen.getByTestId('isDirty')).toHaveTextContent('true');
});
});