-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathphone-input.test.tsx
More file actions
187 lines (152 loc) · 6.37 KB
/
phone-input.test.tsx
File metadata and controls
187 lines (152 loc) · 6.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import { zodResolver } from '@hookform/resolvers/zod';
import { PhoneInput } from '@lambdacurry/forms';
import { Button } from '@lambdacurry/forms/ui/button';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useFetcher } from 'react-router';
import { RemixFormProvider, useRemixForm } from 'remix-hook-form';
import { z } from 'zod';
import type { ElementType, PropsWithChildren } from 'react';
// Mock useFetcher
jest.mock('react-router', () => ({
useFetcher: jest.fn(),
}));
const mockUseFetcher = useFetcher as jest.MockedFunction<typeof useFetcher>;
// Test form schema
const testSchema = z.object({
usaPhone: z.string().min(1, 'USA phone number is required'),
internationalPhone: z.string().min(1, 'International phone number is required'),
});
type TestFormData = z.infer<typeof testSchema>;
// Test component wrapper
const TestPhoneInputForm = ({
initialErrors = {},
customComponents = {},
}: {
initialErrors?: Record<string, { message: string }>;
customComponents?: { FormMessage?: React.ComponentType<PropsWithChildren<Record<string, unknown>>> };
}) => {
const mockFetcher = {
data: { errors: initialErrors },
state: 'idle' as const,
submit: jest.fn(),
Form: 'form' as ElementType,
};
mockUseFetcher.mockReturnValue(mockFetcher);
const methods = useRemixForm<TestFormData>({
resolver: zodResolver(testSchema),
defaultValues: { usaPhone: '', internationalPhone: '' },
fetcher: mockFetcher,
submitConfig: { action: '/test', method: 'post' },
});
return (
<RemixFormProvider {...methods}>
<form onSubmit={methods.handleSubmit}>
<PhoneInput
name="usaPhone"
label="USA Phone Number"
description="Enter a US phone number"
components={customComponents}
/>
<PhoneInput
name="internationalPhone"
label="International Phone Number"
description="Enter an international phone number"
isInternational
components={customComponents}
/>
<Button type="submit">Submit</Button>
</form>
</RemixFormProvider>
);
};
describe('PhoneInput Component', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Basic Functionality', () => {
it('renders phone input fields with labels and descriptions', () => {
render(<TestPhoneInputForm />);
// Check for labels
expect(screen.getByLabelText('USA Phone Number')).toBeInTheDocument();
expect(screen.getByLabelText('International Phone Number')).toBeInTheDocument();
// Check for descriptions
expect(screen.getByText('Enter a US phone number')).toBeInTheDocument();
expect(screen.getByText('Enter an international phone number')).toBeInTheDocument();
});
it('displays validation errors when provided', async () => {
const errors = {
usaPhone: { message: 'USA phone number is required' },
internationalPhone: { message: 'International phone number is required' },
};
render(<TestPhoneInputForm initialErrors={errors} />);
// Check for error messages
expect(screen.getByText('USA phone number is required')).toBeInTheDocument();
expect(screen.getByText('International phone number is required')).toBeInTheDocument();
});
});
describe('Input Behavior', () => {
it('formats and caps US number at 10 digits', async () => {
const user = userEvent.setup();
render(<TestPhoneInputForm />);
const usaPhoneInput = screen.getByLabelText('USA Phone Number') as HTMLInputElement;
// Type more than 10 digits
await user.type(usaPhoneInput, '2025550123456');
// Display should be formatted and capped: (202) 555-0123
await waitFor(() => {
expect(usaPhoneInput.value).toBe('(202) 555-0123');
});
});
it('handles 11-digit US numbers with leading 1 (autofill case)', async () => {
const user = userEvent.setup();
render(<TestPhoneInputForm />);
const usaPhoneInput = screen.getByLabelText('USA Phone Number') as HTMLInputElement;
// Simulate autofill with 11 digits starting with 1
await user.type(usaPhoneInput, '12025550123');
// Should format correctly by removing the leading 1
await waitFor(() => {
expect(usaPhoneInput.value).toBe('(202) 555-0123');
});
});
it('accepts international number with + and inserts spaces', async () => {
const user = userEvent.setup();
render(<TestPhoneInputForm />);
const intlInput = screen.getByLabelText('International Phone Number') as HTMLInputElement;
// Type digits without +; component should normalize to + and format
await user.type(intlInput, '7911123456');
await waitFor(() => {
expect(intlInput.value.startsWith('+')).toBe(true);
// Digits (without non-digits) should match what was typed with leading country code
const digitsOnly = intlInput.value.replace(/\D/g, '');
expect(digitsOnly.endsWith('7911123456')).toBe(true);
});
});
});
describe('Component Customization', () => {
it('uses custom FormMessage component when provided', () => {
const CustomFormMessage = ({ children, ...props }: PropsWithChildren<Record<string, unknown>>) => (
<div data-testid="custom-form-message" className="custom-message" {...props}>
Custom: {children}
</div>
);
const errors = {
usaPhone: { message: 'Test error' },
};
render(<TestPhoneInputForm initialErrors={errors} customComponents={{ FormMessage: CustomFormMessage }} />);
expect(screen.getByTestId('custom-form-message')).toBeInTheDocument();
expect(screen.getByText('Custom: Test error')).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('has proper label associations for screen readers', () => {
render(<TestPhoneInputForm />);
const usaPhoneLabel = screen.getByText('USA Phone Number');
const internationalPhoneLabel = screen.getByText('International Phone Number');
expect(usaPhoneLabel).toBeInTheDocument();
expect(internationalPhoneLabel).toBeInTheDocument();
// Verify labels are properly associated with inputs
expect(screen.getByLabelText('USA Phone Number')).toBeInTheDocument();
expect(screen.getByLabelText('International Phone Number')).toBeInTheDocument();
});
});
});