- Add extended error types with severity levels, retry policies, and contextual information (types/errors.ts) - Create centralized error handler service with user-friendly message translation and classification (services/errorHandler.ts) - Add ErrorContext for global error state management with auto-dismiss and error queue support (contexts/ErrorContext.tsx) - Create error UI components: ErrorToast, FieldError, FieldErrorSummary, FullScreenError, EmptyState, OfflineState - Add useError hook with retry strategies and API response handling - Add useAsync hook for async operations with comprehensive state - Create error message utilities with validation helpers - Add tests for errorHandler and errorMessages (88 tests) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
227 lines
7.7 KiB
TypeScript
227 lines
7.7 KiB
TypeScript
/**
|
|
* Tests for Error Message Utilities
|
|
*/
|
|
|
|
import {
|
|
getErrorMessage,
|
|
getActionHint,
|
|
validateEmail,
|
|
validatePhone,
|
|
validateRequired,
|
|
validateMinLength,
|
|
validateMaxLength,
|
|
validateOtp,
|
|
formatErrorMessage,
|
|
combineFieldErrors,
|
|
getErrorCountText,
|
|
ErrorMessages,
|
|
} from '@/utils/errorMessages';
|
|
import { ErrorCodes } from '@/types/errors';
|
|
|
|
describe('errorMessages', () => {
|
|
describe('getErrorMessage', () => {
|
|
it('should return network error message', () => {
|
|
const message = getErrorMessage(ErrorCodes.NETWORK_ERROR);
|
|
expect(message).toBe(ErrorMessages.networkError);
|
|
});
|
|
|
|
it('should return session expired message for unauthorized', () => {
|
|
const message = getErrorMessage(ErrorCodes.UNAUTHORIZED);
|
|
expect(message).toBe(ErrorMessages.sessionExpired);
|
|
});
|
|
|
|
it('should return fallback for unknown code', () => {
|
|
const message = getErrorMessage('UNKNOWN_CODE', 'Custom fallback');
|
|
expect(message).toBe('Custom fallback');
|
|
});
|
|
|
|
it('should return generic error for unknown code without fallback', () => {
|
|
const message = getErrorMessage('UNKNOWN_CODE');
|
|
expect(message).toBe(ErrorMessages.genericError);
|
|
});
|
|
});
|
|
|
|
describe('getActionHint', () => {
|
|
it('should return hint for network error', () => {
|
|
const hint = getActionHint(ErrorCodes.NETWORK_ERROR);
|
|
expect(hint).toContain('connection');
|
|
});
|
|
|
|
it('should return hint for BLE error', () => {
|
|
const hint = getActionHint(ErrorCodes.BLE_NOT_ENABLED);
|
|
expect(hint).toContain('Bluetooth');
|
|
});
|
|
|
|
it('should return undefined for code without hint', () => {
|
|
const hint = getActionHint('UNKNOWN_CODE');
|
|
expect(hint).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('validateEmail', () => {
|
|
it('should return null for valid email', () => {
|
|
expect(validateEmail('test@example.com')).toBeNull();
|
|
expect(validateEmail('user.name@domain.co.uk')).toBeNull();
|
|
expect(validateEmail('user+tag@example.org')).toBeNull();
|
|
});
|
|
|
|
it('should return error for empty email', () => {
|
|
expect(validateEmail('')).toBe(ErrorMessages.required);
|
|
expect(validateEmail(' ')).toBe(ErrorMessages.required);
|
|
});
|
|
|
|
it('should return error for invalid email', () => {
|
|
expect(validateEmail('notanemail')).toBe(ErrorMessages.invalidEmail);
|
|
expect(validateEmail('missing@domain')).toBe(ErrorMessages.invalidEmail);
|
|
expect(validateEmail('@nodomain.com')).toBe(ErrorMessages.invalidEmail);
|
|
expect(validateEmail('spaces in@email.com')).toBe(ErrorMessages.invalidEmail);
|
|
});
|
|
});
|
|
|
|
describe('validatePhone', () => {
|
|
it('should return null for valid phone numbers', () => {
|
|
expect(validatePhone('+14155551234')).toBeNull();
|
|
expect(validatePhone('14155551234')).toBeNull();
|
|
expect(validatePhone('+7 (999) 123-45-67')).toBeNull();
|
|
expect(validatePhone('999.123.4567')).toBeNull();
|
|
});
|
|
|
|
it('should return null for empty phone (optional)', () => {
|
|
expect(validatePhone('')).toBeNull();
|
|
expect(validatePhone(' ')).toBeNull();
|
|
});
|
|
|
|
it('should return error for invalid phone', () => {
|
|
expect(validatePhone('123')).toBe(ErrorMessages.invalidPhone);
|
|
expect(validatePhone('abcdefghij')).toBe(ErrorMessages.invalidPhone);
|
|
});
|
|
});
|
|
|
|
describe('validateRequired', () => {
|
|
it('should return null for non-empty value', () => {
|
|
expect(validateRequired('test')).toBeNull();
|
|
expect(validateRequired(' test ')).toBeNull();
|
|
});
|
|
|
|
it('should return error for empty value', () => {
|
|
expect(validateRequired('')).toBe(ErrorMessages.required);
|
|
expect(validateRequired(' ')).toBe(ErrorMessages.required);
|
|
expect(validateRequired(null)).toBe(ErrorMessages.required);
|
|
expect(validateRequired(undefined)).toBe(ErrorMessages.required);
|
|
});
|
|
|
|
it('should include field name in error when provided', () => {
|
|
const error = validateRequired('', 'Email');
|
|
expect(error).toBe('Email is required.');
|
|
});
|
|
});
|
|
|
|
describe('validateMinLength', () => {
|
|
it('should return null when value meets minimum', () => {
|
|
expect(validateMinLength('abc', 3)).toBeNull();
|
|
expect(validateMinLength('abcde', 3)).toBeNull();
|
|
});
|
|
|
|
it('should return error when value is too short', () => {
|
|
expect(validateMinLength('ab', 3)).toBe(ErrorMessages.tooShort(3));
|
|
expect(validateMinLength('', 1)).toBe(ErrorMessages.tooShort(1));
|
|
});
|
|
});
|
|
|
|
describe('validateMaxLength', () => {
|
|
it('should return null when value meets maximum', () => {
|
|
expect(validateMaxLength('abc', 3)).toBeNull();
|
|
expect(validateMaxLength('ab', 3)).toBeNull();
|
|
});
|
|
|
|
it('should return error when value is too long', () => {
|
|
expect(validateMaxLength('abcd', 3)).toBe(ErrorMessages.tooLong(3));
|
|
});
|
|
});
|
|
|
|
describe('validateOtp', () => {
|
|
it('should return null for valid OTP', () => {
|
|
expect(validateOtp('123456')).toBeNull();
|
|
expect(validateOtp('000000')).toBeNull();
|
|
});
|
|
|
|
it('should return error for empty OTP', () => {
|
|
expect(validateOtp('')).toBe(ErrorMessages.required);
|
|
expect(validateOtp(' ')).toBe(ErrorMessages.required);
|
|
});
|
|
|
|
it('should return error for invalid OTP', () => {
|
|
expect(validateOtp('12345')).toBe(ErrorMessages.invalidOtp); // Too short
|
|
expect(validateOtp('1234567')).toBe(ErrorMessages.invalidOtp); // Too long
|
|
expect(validateOtp('12345a')).toBe(ErrorMessages.invalidOtp); // Contains letter
|
|
expect(validateOtp('12 345')).toBe(ErrorMessages.invalidOtp); // Contains space
|
|
});
|
|
|
|
it('should respect custom length', () => {
|
|
expect(validateOtp('1234', 4)).toBeNull();
|
|
expect(validateOtp('123456', 4)).toBe(ErrorMessages.invalidOtp);
|
|
});
|
|
});
|
|
|
|
describe('formatErrorMessage', () => {
|
|
it('should capitalize first letter', () => {
|
|
expect(formatErrorMessage('error message')).toBe('Error message.');
|
|
});
|
|
|
|
it('should add period if missing', () => {
|
|
expect(formatErrorMessage('Error message')).toBe('Error message.');
|
|
});
|
|
|
|
it('should not add period if already ends with punctuation', () => {
|
|
expect(formatErrorMessage('Error message.')).toBe('Error message.');
|
|
expect(formatErrorMessage('Error message!')).toBe('Error message!');
|
|
expect(formatErrorMessage('Error message?')).toBe('Error message?');
|
|
});
|
|
|
|
it('should return generic error for empty input', () => {
|
|
expect(formatErrorMessage('')).toBe(ErrorMessages.genericError);
|
|
});
|
|
|
|
it('should trim whitespace', () => {
|
|
expect(formatErrorMessage(' error ')).toBe('Error.');
|
|
});
|
|
});
|
|
|
|
describe('combineFieldErrors', () => {
|
|
it('should return empty string for no errors', () => {
|
|
expect(combineFieldErrors([])).toBe('');
|
|
});
|
|
|
|
it('should return single message for one error', () => {
|
|
const errors = [{ field: 'email', message: 'Invalid email' }];
|
|
expect(combineFieldErrors(errors)).toBe('Invalid email');
|
|
});
|
|
|
|
it('should combine multiple errors', () => {
|
|
const errors = [
|
|
{ field: 'email', message: 'Invalid email' },
|
|
{ field: 'phone', message: 'Invalid phone' },
|
|
];
|
|
const result = combineFieldErrors(errors);
|
|
expect(result).toContain('2 errors');
|
|
expect(result).toContain('Invalid email');
|
|
expect(result).toContain('Invalid phone');
|
|
});
|
|
});
|
|
|
|
describe('getErrorCountText', () => {
|
|
it('should return "No errors" for 0', () => {
|
|
expect(getErrorCountText(0)).toBe('No errors');
|
|
});
|
|
|
|
it('should return "1 error" for 1', () => {
|
|
expect(getErrorCountText(1)).toBe('1 error');
|
|
});
|
|
|
|
it('should return plural for multiple errors', () => {
|
|
expect(getErrorCountText(2)).toBe('2 errors');
|
|
expect(getErrorCountText(5)).toBe('5 errors');
|
|
});
|
|
});
|
|
});
|