forked from cardosofelipe/fast-next-template
Add tests for useFormError hook and FormField component
- Introduced `useFormError.test.tsx` to validate error handling, server error integration, and form behavior. - Added `FormField.test.tsx`, covering rendering, accessibility, error handling, and prop forwarding. - Updated Jest coverage exclusions to include `middleware.ts` (no logic to test).
This commit is contained in:
263
frontend/tests/components/forms/useFormError.test.tsx
Normal file
263
frontend/tests/components/forms/useFormError.test.tsx
Normal file
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* Tests for useFormError Hook
|
||||
* Verifies form error handling and API error integration
|
||||
*/
|
||||
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useFormError } from '@/components/forms/useFormError';
|
||||
|
||||
interface TestFormData {
|
||||
email: string;
|
||||
password: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
// Helper to render both hooks together in one scope
|
||||
function useTestForm(defaultValues?: Partial<TestFormData>) {
|
||||
const form = useForm<TestFormData>({
|
||||
defaultValues: defaultValues || {},
|
||||
});
|
||||
const formError = useFormError(form);
|
||||
|
||||
return { form, formError };
|
||||
}
|
||||
|
||||
describe('useFormError', () => {
|
||||
describe('Initial State', () => {
|
||||
it('initializes with null serverError', () => {
|
||||
const { result } = renderHook(() => useTestForm());
|
||||
|
||||
expect(result.current.formError.serverError).toBeNull();
|
||||
});
|
||||
|
||||
it('provides all expected functions', () => {
|
||||
const { result } = renderHook(() => useTestForm());
|
||||
|
||||
expect(typeof result.current.formError.setServerError).toBe('function');
|
||||
expect(typeof result.current.formError.handleFormError).toBe('function');
|
||||
expect(typeof result.current.formError.clearErrors).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setServerError', () => {
|
||||
it('sets server error message', () => {
|
||||
const { result } = renderHook(() => useTestForm());
|
||||
|
||||
act(() => {
|
||||
result.current.formError.setServerError('Custom error message');
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBe('Custom error message');
|
||||
});
|
||||
|
||||
it('clears server error when set to null', () => {
|
||||
const { result } = renderHook(() => useTestForm());
|
||||
|
||||
act(() => {
|
||||
result.current.formError.setServerError('Error message');
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.formError.setServerError(null);
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleFormError - API Error Array', () => {
|
||||
it('handles API error with general error message', () => {
|
||||
const { result } = renderHook(() => useTestForm());
|
||||
|
||||
const apiError = [
|
||||
{ code: 'AUTH_001', message: 'Invalid credentials' },
|
||||
];
|
||||
|
||||
act(() => {
|
||||
result.current.formError.handleFormError(apiError);
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBe('Invalid credentials');
|
||||
});
|
||||
|
||||
it('handles multiple general errors (takes first non-field error)', () => {
|
||||
const { result } = renderHook(() => useTestForm());
|
||||
|
||||
const apiError = [
|
||||
{ code: 'AUTH_001', message: 'Authentication failed' },
|
||||
{ code: 'AUTH_002', message: 'Account is inactive' },
|
||||
];
|
||||
|
||||
act(() => {
|
||||
result.current.formError.handleFormError(apiError);
|
||||
});
|
||||
|
||||
// Should take the first general error
|
||||
expect(result.current.formError.serverError).toBe('Authentication failed');
|
||||
});
|
||||
|
||||
it('handles API errors with field-specific errors without crashing', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTestForm({ email: '', password: '', username: '' })
|
||||
);
|
||||
|
||||
const apiError = [
|
||||
{ code: 'VAL_004', message: 'Email is required', field: 'email' },
|
||||
{ code: 'VAL_003', message: 'Password too short', field: 'password' },
|
||||
];
|
||||
|
||||
// Should not throw even though fields aren't registered
|
||||
expect(() => {
|
||||
act(() => {
|
||||
result.current.formError.handleFormError(apiError);
|
||||
});
|
||||
}).not.toThrow();
|
||||
|
||||
// No general error should be set (all are field errors)
|
||||
expect(result.current.formError.serverError).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleFormError - Non-API Errors', () => {
|
||||
it('handles unexpected error format', () => {
|
||||
const { result } = renderHook(() => useTestForm());
|
||||
|
||||
const unexpectedError = new Error('Network error');
|
||||
|
||||
act(() => {
|
||||
result.current.formError.handleFormError(unexpectedError);
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBe('An unexpected error occurred. Please try again.');
|
||||
});
|
||||
|
||||
it('handles string errors', () => {
|
||||
const { result } = renderHook(() => useTestForm());
|
||||
|
||||
act(() => {
|
||||
result.current.formError.handleFormError('Some error string');
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBe('An unexpected error occurred. Please try again.');
|
||||
});
|
||||
|
||||
it('handles null errors', () => {
|
||||
const { result } = renderHook(() => useTestForm());
|
||||
|
||||
act(() => {
|
||||
result.current.formError.handleFormError(null);
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBe('An unexpected error occurred. Please try again.');
|
||||
});
|
||||
|
||||
it('handles undefined errors', () => {
|
||||
const { result } = renderHook(() => useTestForm());
|
||||
|
||||
act(() => {
|
||||
result.current.formError.handleFormError(undefined);
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBe('An unexpected error occurred. Please try again.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearErrors', () => {
|
||||
it('clears server error', () => {
|
||||
const { result } = renderHook(() => useTestForm());
|
||||
|
||||
act(() => {
|
||||
result.current.formError.setServerError('Some error');
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.formError.clearErrors();
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBeNull();
|
||||
});
|
||||
|
||||
it('clears form errors', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTestForm({ email: '', password: '', username: '' })
|
||||
);
|
||||
|
||||
// Set field errors
|
||||
act(() => {
|
||||
result.current.form.setError('email', { message: 'Email error' });
|
||||
result.current.form.setError('password', { message: 'Password error' });
|
||||
});
|
||||
|
||||
// Clear all errors
|
||||
act(() => {
|
||||
result.current.formError.clearErrors();
|
||||
});
|
||||
|
||||
expect(result.current.form.formState.errors.email).toBeUndefined();
|
||||
expect(result.current.form.formState.errors.password).toBeUndefined();
|
||||
});
|
||||
|
||||
it('clears both server and form errors', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTestForm({ email: '', password: '', username: '' })
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.formError.setServerError('Server error');
|
||||
result.current.form.setError('email', { message: 'Email error' });
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.formError.clearErrors();
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBeNull();
|
||||
expect(result.current.form.formState.errors.email).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Integration Scenarios', () => {
|
||||
it('handles typical login flow with API error', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTestForm({ email: '', password: '', username: '' })
|
||||
);
|
||||
|
||||
// Simulate API error response
|
||||
const apiError = [
|
||||
{ code: 'AUTH_001', message: 'Invalid email or password' },
|
||||
];
|
||||
|
||||
act(() => {
|
||||
result.current.formError.handleFormError(apiError);
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBe('Invalid email or password');
|
||||
});
|
||||
|
||||
it('clears error state on retry', () => {
|
||||
const { result } = renderHook(() => useTestForm());
|
||||
|
||||
// First attempt - error
|
||||
act(() => {
|
||||
result.current.formError.setServerError('First error');
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBe('First error');
|
||||
|
||||
// Clear before retry
|
||||
act(() => {
|
||||
result.current.formError.clearErrors();
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBeNull();
|
||||
|
||||
// Second attempt - different error
|
||||
act(() => {
|
||||
result.current.formError.setServerError('Second error');
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBe('Second error');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user