import { act } from 'react'; import { describe, expect, it } from 'vitest'; import { useValidatedFields } from '../../src/hooks/useValidatedFields'; import { renderHook } from '../helpers/renderHook'; type FormValues = { password: string; confirmPassword: string; }; function validate(values: FormValues) { return { password: values.password.length < 3 ? 'Password too short' : undefined, confirmPassword: values.confirmPassword !== values.password ? 'Passwords do not match' : undefined, }; } describe('useValidatedFields', () => { it('initializes values and keeps errors hidden until fields are touched', () => { const { result } = renderHook(() => useValidatedFields({ initialValues: { password: '', confirmPassword: '' }, validate, }), ); expect(result.current.values).toEqual({ password: '', confirmPassword: '' }); expect(result.current.errors).toEqual({}); expect(result.current.isValid).toBe(false); }); it('setFieldValue touches and validates by default', () => { const { result } = renderHook(() => useValidatedFields({ initialValues: { password: '', confirmPassword: '' }, validate, }), ); act(() => { result.current.setFieldValue('password', 'ab'); }); expect(result.current.values.password).toBe('ab'); expect(result.current.errors.password).toBe('Password too short'); }); it('setFieldValue can skip touch and validation', () => { const { result } = renderHook(() => useValidatedFields({ initialValues: { password: '', confirmPassword: '' }, validate, }), ); act(() => { result.current.setFieldValue('password', 'ab', { touch: false, validate: false }); }); expect(result.current.values.password).toBe('ab'); expect(result.current.errors).toEqual({}); }); it('validateAll can avoid touching fields when requested', () => { const { result } = renderHook(() => useValidatedFields({ initialValues: { password: 'abcd', confirmPassword: 'abce' }, validate, }), ); let validationErrors: ReturnType | undefined; act(() => { validationErrors = result.current.validateAll({ touchAll: false }); }); expect(validationErrors).toEqual({ password: undefined, confirmPassword: 'Passwords do not match', }); expect(result.current.errors).toEqual({}); }); it('validateAll touches all fields by default', () => { const { result } = renderHook(() => useValidatedFields({ initialValues: { password: '', confirmPassword: '' }, validate, }), ); act(() => { result.current.validateAll(); }); expect(result.current.errors.password).toBe('Password too short'); expect(result.current.errors.confirmPassword).toBeUndefined(); }); it('supports setFieldError, setErrors and clearErrors', () => { const { result } = renderHook(() => useValidatedFields({ initialValues: { password: 'abcd', confirmPassword: 'abcd' }, validate, }), ); act(() => { result.current.setFieldError('password', 'Server-side password issue'); }); expect(result.current.errors.password).toBe('Server-side password issue'); act(() => { result.current.setErrors({ password: undefined, confirmPassword: 'Still invalid', }); }); expect(result.current.errors.confirmPassword).toBe('Still invalid'); act(() => { result.current.clearErrors(); }); expect(result.current.errors).toEqual({}); }); it('setValues with clearErrors resets touched state and revalidates', () => { const { result } = renderHook(() => useValidatedFields({ initialValues: { password: '', confirmPassword: '' }, validate, }), ); act(() => { result.current.setFieldValue('password', 'ab'); }); expect(result.current.errors.password).toBe('Password too short'); act(() => { result.current.setValues( { password: 'abcd', confirmPassword: 'abcd', }, { clearErrors: true }, ); }); expect(result.current.values).toEqual({ password: 'abcd', confirmPassword: 'abcd', }); expect(result.current.errors).toEqual({}); expect(result.current.isValid).toBe(true); }); });