zod-form-kit
Version:
UI-agnostic form generation library based on Zod schemas with extensible adapter pattern
155 lines (154 loc) • 7.36 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { z } from 'zod';
import { ZodForm } from '../ZodForm';
// Mock FieldRenderer to test ZodForm-specific logic
vi.mock('../FieldRenderer', () => ({
FieldRenderer: ({ schema: _schema, form, path }) => {
return (_jsx(form.Field, { name: path || 'testField', children: (field) => (_jsxs("div", { children: [_jsx("input", { "aria-label": path || 'testField', value: field.state.value || '', onChange: (e) => field.handleChange(e.target.value) }), field.state.meta.errors.length > 0 && (_jsx("span", { "data-testid": "field-error", children: field.state.meta.errors[0] }))] })) }));
},
}));
describe('ZodForm Component', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('Integration with TanStack Form', () => {
it('should pass correct form instance to FieldRenderer', () => {
const schema = z.object({
name: z.string(),
});
const onSubmit = vi.fn();
render(_jsx(ZodForm, { schema: schema, onSubmit: onSubmit }));
// Verify form is rendered and field is accessible with mock
expect(screen.getByLabelText('testField')).toBeInTheDocument();
});
it('should handle form state through TanStack Form Subscribe', async () => {
const user = userEvent.setup();
const schema = z.object({
name: z.string(),
});
let resolveSubmit;
const submitPromise = new Promise((resolve) => {
resolveSubmit = resolve;
});
const onSubmit = vi.fn().mockReturnValue(submitPromise);
render(_jsx(ZodForm, { schema: schema, onSubmit: onSubmit }));
const submitButton = screen.getByRole('button', { name: /submit/i });
// Initially enabled
expect(submitButton).not.toBeDisabled();
expect(submitButton).toHaveTextContent('Submit');
// Fill field and submit
await user.type(screen.getByLabelText('testField'), 'Test');
await user.click(submitButton);
// Should show submitting state
await waitFor(() => {
expect(submitButton).toHaveTextContent('Submitting...');
expect(submitButton).toBeDisabled();
});
// Resolve and check state returns to normal
resolveSubmit();
await waitFor(() => {
expect(submitButton).toHaveTextContent('Submit');
expect(submitButton).not.toBeDisabled();
});
});
});
describe('Schema Processing', () => {
it('should extract and merge default values correctly', () => {
const schema = z.object({
name: z.string().default('Default Name'),
email: z.string(),
count: z.number().default(5),
});
const defaultValues = { email: 'test@example.com', count: 10 };
const onSubmit = vi.fn();
render(_jsx(ZodForm, { schema: schema, onSubmit: onSubmit, defaultValues: defaultValues }));
// With the mock, just verify the form renders with testField
const field = screen.getByLabelText('testField');
expect(field).toBeInTheDocument();
});
it('should handle empty default extraction gracefully', () => {
const schema = z.object({
name: z.string(),
});
const onSubmit = vi.fn();
// Should not throw when no defaults are available
expect(() => {
render(_jsx(ZodForm, { schema: schema, onSubmit: onSubmit }));
}).not.toThrow();
});
});
describe('Form Submission Handling', () => {
it('should validate data before calling onSubmit', async () => {
const user = userEvent.setup();
const schema = z.object({
name: z.string(),
});
const onSubmit = vi.fn();
render(_jsx(ZodForm, { schema: schema, onSubmit: onSubmit }));
// Submit with simple string data
await user.type(screen.getByLabelText('testField'), 'test value');
await user.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalled();
});
});
it('should handle Zod validation errors during submission', async () => {
const user = userEvent.setup();
const schema = z.object({
age: z.number().min(18, 'Must be 18 or older'),
});
const onSubmit = vi.fn();
render(_jsx(ZodForm, { schema: schema, onSubmit: onSubmit }));
// This will trigger a validation error during submission
await user.type(screen.getByLabelText('testField'), '15');
await user.click(screen.getByRole('button', { name: /submit/i }));
// onSubmit should not be called due to validation error
expect(onSubmit).not.toHaveBeenCalled();
});
});
describe('Form Element Properties', () => {
it('should apply correct form attributes', () => {
const schema = z.object({
name: z.string(),
});
const onSubmit = vi.fn();
render(_jsx(ZodForm, { schema: schema, onSubmit: onSubmit, className: "custom-class" }));
const form = screen.getByRole('form');
expect(form).toHaveClass('form-generator', 'custom-class');
expect(form).toHaveAttribute('role', 'form');
});
it('should prevent default form submission behavior', async () => {
const schema = z.object({
name: z.string(),
});
const onSubmit = vi.fn();
render(_jsx(ZodForm, { schema: schema, onSubmit: onSubmit }));
const form = screen.getByRole('form');
const preventDefault = vi.fn();
const stopPropagation = vi.fn();
// Simulate form submission event
const submitEvent = new Event('submit', { bubbles: true, cancelable: true });
Object.defineProperty(submitEvent, 'preventDefault', { value: preventDefault });
Object.defineProperty(submitEvent, 'stopPropagation', { value: stopPropagation });
form.dispatchEvent(submitEvent);
expect(preventDefault).toHaveBeenCalled();
expect(stopPropagation).toHaveBeenCalled();
});
});
describe('Error Recovery', () => {
it('should handle schema parsing errors gracefully', () => {
// Create a schema that might cause parsing issues
const schema = z.object({
complexField: z.union([z.string(), z.number()]),
});
const onSubmit = vi.fn();
// Should not throw during render
expect(() => {
render(_jsx(ZodForm, { schema: schema, onSubmit: onSubmit }));
}).not.toThrow();
});
});
});