@simplybusiness/mobius 6.4.3 → 6.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/cjs/index.js +255 -188
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/index.js +171 -99
- package/dist/types/src/components/MaskedField/MaskedField.d.ts +22 -9
- package/package.json +1 -1
- package/src/components/MaskedField/MaskedField.tsx +289 -118
- package/src/components/NumberField/NumberField.test.tsx +73 -28
- package/src/components/NumberField/NumberField.tsx +77 -21
- package/src/components/Select/Select.css +13 -1
- package/src/components/Select/Select.tsx +4 -3
- package/dist/types/src/components/MaskedField/MaskedField.test.d.ts +0 -1
- package/src/components/MaskedField/MaskedField.test.tsx +0 -687
|
@@ -1,687 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { render, screen } from "@testing-library/react";
|
|
3
|
-
import userEvent from "@testing-library/user-event";
|
|
4
|
-
import { MaskedField } from ".";
|
|
5
|
-
import type { ReactMaskOpts } from "react-imask";
|
|
6
|
-
|
|
7
|
-
const commaSeparatedNumberMask = {
|
|
8
|
-
mask: Number,
|
|
9
|
-
thousandsSeparator: ",",
|
|
10
|
-
scale: 0,
|
|
11
|
-
signed: false,
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const usPhoneNumberMask = {
|
|
15
|
-
mask: "(000) 000-0000",
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const WRAPPER_CLASS_NAME = "mobius-text-field";
|
|
19
|
-
const INPUT_CLASS_NAME = "mobius-text-field__input";
|
|
20
|
-
|
|
21
|
-
// Helper component for controlled component tests
|
|
22
|
-
const TestComponent = ({
|
|
23
|
-
value,
|
|
24
|
-
mask = usPhoneNumberMask,
|
|
25
|
-
useMaskedValue,
|
|
26
|
-
label = "Phone Number",
|
|
27
|
-
...props
|
|
28
|
-
}: {
|
|
29
|
-
value: string;
|
|
30
|
-
mask?: ReactMaskOpts;
|
|
31
|
-
useMaskedValue?: boolean;
|
|
32
|
-
label?: string;
|
|
33
|
-
[key: string]: unknown;
|
|
34
|
-
}) => (
|
|
35
|
-
<MaskedField
|
|
36
|
-
mask={mask}
|
|
37
|
-
label={label}
|
|
38
|
-
data-testid="masked-field"
|
|
39
|
-
value={value}
|
|
40
|
-
useMaskedValue={useMaskedValue}
|
|
41
|
-
{...props}
|
|
42
|
-
/>
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
describe("MaskedField", () => {
|
|
46
|
-
it("should render without errors", () => {
|
|
47
|
-
render(
|
|
48
|
-
<MaskedField
|
|
49
|
-
mask={usPhoneNumberMask}
|
|
50
|
-
label="Phone Number"
|
|
51
|
-
data-testid="masked-field"
|
|
52
|
-
/>,
|
|
53
|
-
);
|
|
54
|
-
expect(screen.getByTestId("masked-field")).toBeInTheDocument();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("should render with correct base class names", () => {
|
|
58
|
-
const { container } = render(
|
|
59
|
-
<MaskedField
|
|
60
|
-
mask={usPhoneNumberMask}
|
|
61
|
-
label="Phone Number"
|
|
62
|
-
data-testid="masked-field"
|
|
63
|
-
/>,
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
expect(container.firstChild).toHaveClass("mobius");
|
|
67
|
-
expect(container.firstChild).toHaveClass(WRAPPER_CLASS_NAME);
|
|
68
|
-
expect(screen.getByTestId("masked-field")).toHaveClass("mobius");
|
|
69
|
-
expect(screen.getByTestId("masked-field")).toHaveClass(INPUT_CLASS_NAME);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("should apply phone number mask correctly", async () => {
|
|
73
|
-
const user = userEvent.setup();
|
|
74
|
-
|
|
75
|
-
render(
|
|
76
|
-
<MaskedField
|
|
77
|
-
mask={usPhoneNumberMask}
|
|
78
|
-
label="Phone Number"
|
|
79
|
-
data-testid="masked-field"
|
|
80
|
-
/>,
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const input = screen.getByTestId("masked-field");
|
|
84
|
-
|
|
85
|
-
await user.type(input, "1234567890");
|
|
86
|
-
|
|
87
|
-
expect(input).toHaveValue("(123) 456-7890");
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("should apply comma-separated number mask correctly", async () => {
|
|
91
|
-
const user = userEvent.setup();
|
|
92
|
-
|
|
93
|
-
render(
|
|
94
|
-
<MaskedField
|
|
95
|
-
mask={commaSeparatedNumberMask}
|
|
96
|
-
label="Number"
|
|
97
|
-
data-testid="masked-field"
|
|
98
|
-
/>,
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
const input = screen.getByTestId("masked-field");
|
|
102
|
-
|
|
103
|
-
await user.type(input, "1234567");
|
|
104
|
-
|
|
105
|
-
expect(input).toHaveValue("1,234,567");
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe("onChange behavior", () => {
|
|
109
|
-
it("should call onChange with unmasked value by default", async () => {
|
|
110
|
-
const user = userEvent.setup();
|
|
111
|
-
const mockOnChange = jest.fn();
|
|
112
|
-
|
|
113
|
-
render(
|
|
114
|
-
<MaskedField
|
|
115
|
-
mask={usPhoneNumberMask}
|
|
116
|
-
label="Phone Number"
|
|
117
|
-
data-testid="masked-field"
|
|
118
|
-
onChange={mockOnChange}
|
|
119
|
-
/>,
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
const input = screen.getByTestId("masked-field");
|
|
123
|
-
|
|
124
|
-
// Type a simple input to test the unmasked value behavior
|
|
125
|
-
await user.type(input, "12345");
|
|
126
|
-
|
|
127
|
-
// Should be called multiple times, check that onChange is called
|
|
128
|
-
expect(mockOnChange).toHaveBeenCalled();
|
|
129
|
-
|
|
130
|
-
// The onChange should provide unmasked value (not masked)
|
|
131
|
-
const lastCall =
|
|
132
|
-
mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1][0];
|
|
133
|
-
// Should have numeric digits only (unmasked)
|
|
134
|
-
expect(lastCall.target.value).toMatch(/^\d+$/);
|
|
135
|
-
expect(lastCall.target.value).toBe("1234");
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it("should call onChange with masked value when useMaskedValue is true", async () => {
|
|
139
|
-
const user = userEvent.setup();
|
|
140
|
-
const mockOnChange = jest.fn();
|
|
141
|
-
|
|
142
|
-
render(
|
|
143
|
-
<MaskedField
|
|
144
|
-
mask={usPhoneNumberMask}
|
|
145
|
-
label="Phone Number"
|
|
146
|
-
data-testid="masked-field"
|
|
147
|
-
onChange={mockOnChange}
|
|
148
|
-
useMaskedValue={true}
|
|
149
|
-
/>,
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
const input = screen.getByTestId("masked-field");
|
|
153
|
-
|
|
154
|
-
// Type a simple input to test the masked value behavior
|
|
155
|
-
await user.type(input, "12345");
|
|
156
|
-
|
|
157
|
-
// Should be called multiple times, check that onChange is called
|
|
158
|
-
expect(mockOnChange).toHaveBeenCalled();
|
|
159
|
-
|
|
160
|
-
// The onChange should provide masked value (formatted)
|
|
161
|
-
const lastCall =
|
|
162
|
-
mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1][0];
|
|
163
|
-
// Should have formatting characters (masked)
|
|
164
|
-
expect(lastCall.target.value).toMatch(/^\(\d{3}\) \d+/);
|
|
165
|
-
expect(lastCall.target.value).toBe("(123) 4");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("should include name in onChange event when provided", async () => {
|
|
169
|
-
const user = userEvent.setup();
|
|
170
|
-
const mockOnChange = jest.fn();
|
|
171
|
-
|
|
172
|
-
render(
|
|
173
|
-
<MaskedField
|
|
174
|
-
mask={usPhoneNumberMask}
|
|
175
|
-
label="Phone Number"
|
|
176
|
-
data-testid="masked-field"
|
|
177
|
-
name="phoneNumber"
|
|
178
|
-
onChange={mockOnChange}
|
|
179
|
-
/>,
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
const input = screen.getByTestId("masked-field");
|
|
183
|
-
|
|
184
|
-
await user.type(input, "123");
|
|
185
|
-
|
|
186
|
-
expect(mockOnChange).toHaveBeenLastCalledWith(
|
|
187
|
-
expect.objectContaining({
|
|
188
|
-
target: expect.objectContaining({
|
|
189
|
-
name: "phoneNumber",
|
|
190
|
-
}),
|
|
191
|
-
currentTarget: expect.objectContaining({
|
|
192
|
-
name: "phoneNumber",
|
|
193
|
-
}),
|
|
194
|
-
}),
|
|
195
|
-
);
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
describe("onBlur behavior", () => {
|
|
200
|
-
it("should call onBlur with unmasked value by default", async () => {
|
|
201
|
-
const user = userEvent.setup();
|
|
202
|
-
const mockOnBlur = jest.fn();
|
|
203
|
-
|
|
204
|
-
render(
|
|
205
|
-
<MaskedField
|
|
206
|
-
mask={usPhoneNumberMask}
|
|
207
|
-
label="Phone Number"
|
|
208
|
-
data-testid="masked-field"
|
|
209
|
-
onBlur={mockOnBlur}
|
|
210
|
-
/>,
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
const input = screen.getByTestId("masked-field");
|
|
214
|
-
|
|
215
|
-
await user.type(input, "1234567890");
|
|
216
|
-
await user.tab();
|
|
217
|
-
|
|
218
|
-
expect(mockOnBlur).toHaveBeenCalledWith(
|
|
219
|
-
expect.objectContaining({
|
|
220
|
-
target: expect.objectContaining({
|
|
221
|
-
value: "1234567890",
|
|
222
|
-
}),
|
|
223
|
-
}),
|
|
224
|
-
);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("should call onBlur with masked value when useMaskedValue is true", async () => {
|
|
228
|
-
const user = userEvent.setup();
|
|
229
|
-
const mockOnBlur = jest.fn();
|
|
230
|
-
|
|
231
|
-
render(
|
|
232
|
-
<MaskedField
|
|
233
|
-
mask={usPhoneNumberMask}
|
|
234
|
-
label="Phone Number"
|
|
235
|
-
data-testid="masked-field"
|
|
236
|
-
onBlur={mockOnBlur}
|
|
237
|
-
useMaskedValue={true}
|
|
238
|
-
/>,
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
const input = screen.getByTestId("masked-field");
|
|
242
|
-
|
|
243
|
-
await user.type(input, "1234567890");
|
|
244
|
-
await user.tab();
|
|
245
|
-
|
|
246
|
-
expect(mockOnBlur).toHaveBeenCalledWith(
|
|
247
|
-
expect.objectContaining({
|
|
248
|
-
target: expect.objectContaining({
|
|
249
|
-
value: "(123) 456-7890",
|
|
250
|
-
}),
|
|
251
|
-
}),
|
|
252
|
-
);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it("should include name in onBlur event when provided", async () => {
|
|
256
|
-
const user = userEvent.setup();
|
|
257
|
-
const mockOnBlur = jest.fn();
|
|
258
|
-
|
|
259
|
-
render(
|
|
260
|
-
<MaskedField
|
|
261
|
-
mask={usPhoneNumberMask}
|
|
262
|
-
label="Phone Number"
|
|
263
|
-
data-testid="masked-field"
|
|
264
|
-
name="phoneNumber"
|
|
265
|
-
onBlur={mockOnBlur}
|
|
266
|
-
/>,
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
const input = screen.getByTestId("masked-field");
|
|
270
|
-
|
|
271
|
-
await user.type(input, "123");
|
|
272
|
-
await user.tab();
|
|
273
|
-
|
|
274
|
-
expect(mockOnBlur).toHaveBeenCalledWith(
|
|
275
|
-
expect.objectContaining({
|
|
276
|
-
target: expect.objectContaining({
|
|
277
|
-
name: "phoneNumber",
|
|
278
|
-
}),
|
|
279
|
-
currentTarget: expect.objectContaining({
|
|
280
|
-
name: "phoneNumber",
|
|
281
|
-
}),
|
|
282
|
-
}),
|
|
283
|
-
);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it("should not call onBlur when no onBlur handler is provided", async () => {
|
|
287
|
-
const user = userEvent.setup();
|
|
288
|
-
|
|
289
|
-
render(
|
|
290
|
-
<MaskedField
|
|
291
|
-
mask={usPhoneNumberMask}
|
|
292
|
-
label="Phone Number"
|
|
293
|
-
data-testid="masked-field"
|
|
294
|
-
/>,
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
const input = screen.getByTestId("masked-field");
|
|
298
|
-
|
|
299
|
-
await user.type(input, "123");
|
|
300
|
-
|
|
301
|
-
// Should not throw an error
|
|
302
|
-
await user.tab();
|
|
303
|
-
|
|
304
|
-
expect(input).not.toHaveFocus();
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
describe("event consistency", () => {
|
|
309
|
-
it("should provide consistent event structure for both onChange and onBlur", async () => {
|
|
310
|
-
const user = userEvent.setup();
|
|
311
|
-
const mockOnChange = jest.fn();
|
|
312
|
-
const mockOnBlur = jest.fn();
|
|
313
|
-
|
|
314
|
-
render(
|
|
315
|
-
<MaskedField
|
|
316
|
-
mask={usPhoneNumberMask}
|
|
317
|
-
label="Phone Number"
|
|
318
|
-
data-testid="masked-field"
|
|
319
|
-
name="phoneNumber"
|
|
320
|
-
onChange={mockOnChange}
|
|
321
|
-
onBlur={mockOnBlur}
|
|
322
|
-
/>,
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
const input = screen.getByTestId("masked-field");
|
|
326
|
-
|
|
327
|
-
await user.type(input, "123");
|
|
328
|
-
await user.tab();
|
|
329
|
-
|
|
330
|
-
// Both should have consistent event structure with target and currentTarget
|
|
331
|
-
const changeCall =
|
|
332
|
-
mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1][0];
|
|
333
|
-
const blurCall = mockOnBlur.mock.calls[0][0];
|
|
334
|
-
|
|
335
|
-
expect(changeCall).toHaveProperty("target");
|
|
336
|
-
expect(changeCall).toHaveProperty("currentTarget");
|
|
337
|
-
expect(blurCall).toHaveProperty("target");
|
|
338
|
-
expect(blurCall).toHaveProperty("currentTarget");
|
|
339
|
-
|
|
340
|
-
// Both should have the same name property
|
|
341
|
-
expect(changeCall.target.name).toBe("phoneNumber");
|
|
342
|
-
expect(changeCall.currentTarget.name).toBe("phoneNumber");
|
|
343
|
-
expect(blurCall.target.name).toBe("phoneNumber");
|
|
344
|
-
expect(blurCall.currentTarget.name).toBe("phoneNumber");
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
it("should handle both onChange and onBlur with number masks", async () => {
|
|
348
|
-
const user = userEvent.setup();
|
|
349
|
-
const mockOnChange = jest.fn();
|
|
350
|
-
const mockOnBlur = jest.fn();
|
|
351
|
-
|
|
352
|
-
render(
|
|
353
|
-
<MaskedField
|
|
354
|
-
mask={commaSeparatedNumberMask}
|
|
355
|
-
label="Number"
|
|
356
|
-
data-testid="masked-field"
|
|
357
|
-
onChange={mockOnChange}
|
|
358
|
-
onBlur={mockOnBlur}
|
|
359
|
-
/>,
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
const input = screen.getByTestId("masked-field");
|
|
363
|
-
|
|
364
|
-
await user.type(input, "1234");
|
|
365
|
-
await user.tab();
|
|
366
|
-
|
|
367
|
-
// onChange should be called with unmasked value during typing (last keystroke)
|
|
368
|
-
expect(mockOnChange).toHaveBeenCalled();
|
|
369
|
-
const lastChangeCall =
|
|
370
|
-
mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1][0];
|
|
371
|
-
expect(lastChangeCall.target.value).toBe("12");
|
|
372
|
-
|
|
373
|
-
// onBlur should provide the complete unmasked value
|
|
374
|
-
expect(mockOnBlur).toHaveBeenCalled();
|
|
375
|
-
const blurCall = mockOnBlur.mock.calls[0][0];
|
|
376
|
-
|
|
377
|
-
// The blur event should provide the complete unmasked value (numbers only, no comma formatting)
|
|
378
|
-
expect(typeof blurCall.target.value).toBe("string");
|
|
379
|
-
expect(blurCall.target.value).toMatch(/^\d+$/); // Should be digits only (unmasked)
|
|
380
|
-
expect(blurCall.target.value).toBe("1234"); // Complete unmasked value
|
|
381
|
-
|
|
382
|
-
// Verify the input shows the formatted (masked) version
|
|
383
|
-
expect(input).toHaveValue("1,234");
|
|
384
|
-
});
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
describe("controlled component behavior", () => {
|
|
388
|
-
it("should accept value prop without errors", () => {
|
|
389
|
-
// This test verifies that the component can accept a value prop
|
|
390
|
-
// without throwing errors. The component now properly handles
|
|
391
|
-
// controlled behavior through useIMask
|
|
392
|
-
render(
|
|
393
|
-
<MaskedField
|
|
394
|
-
mask={usPhoneNumberMask}
|
|
395
|
-
label="Phone Number"
|
|
396
|
-
data-testid="masked-field"
|
|
397
|
-
value="9876543210"
|
|
398
|
-
/>,
|
|
399
|
-
);
|
|
400
|
-
|
|
401
|
-
const input = screen.getByTestId("masked-field");
|
|
402
|
-
expect(input).toBeInTheDocument();
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
it("should work with defaultValue prop", () => {
|
|
406
|
-
render(
|
|
407
|
-
<MaskedField
|
|
408
|
-
mask={usPhoneNumberMask}
|
|
409
|
-
label="Phone Number"
|
|
410
|
-
data-testid="masked-field"
|
|
411
|
-
defaultValue="1234567890"
|
|
412
|
-
/>,
|
|
413
|
-
);
|
|
414
|
-
|
|
415
|
-
const input = screen.getByTestId("masked-field");
|
|
416
|
-
expect(input).toHaveValue("(123) 456-7890");
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
it("should update when value prop changes (controlled behavior)", () => {
|
|
420
|
-
const { rerender } = render(<TestComponent value="1234567890" />);
|
|
421
|
-
|
|
422
|
-
const input = screen.getByTestId("masked-field");
|
|
423
|
-
expect(input).toHaveValue("(123) 456-7890");
|
|
424
|
-
|
|
425
|
-
// Change the value prop to simulate server-side update
|
|
426
|
-
rerender(<TestComponent value="9876543210" />);
|
|
427
|
-
|
|
428
|
-
// The input should reflect the new value
|
|
429
|
-
expect(input).toHaveValue("(987) 654-3210");
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
it("should handle masked value input when useMaskedValue is true", () => {
|
|
433
|
-
const { rerender } = render(
|
|
434
|
-
<TestComponent value="(123) 456-7890" useMaskedValue={true} />,
|
|
435
|
-
);
|
|
436
|
-
|
|
437
|
-
const input = screen.getByTestId("masked-field");
|
|
438
|
-
expect(input).toHaveValue("(123) 456-7890");
|
|
439
|
-
|
|
440
|
-
// Change to a different masked value
|
|
441
|
-
rerender(<TestComponent value="(987) 654-3210" />);
|
|
442
|
-
|
|
443
|
-
// The input should reflect the new masked value
|
|
444
|
-
expect(input).toHaveValue("(987) 654-3210");
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
it("should handle unmasked value input when useMaskedValue is false", () => {
|
|
448
|
-
const { rerender } = render(
|
|
449
|
-
<TestComponent value="1234567890" useMaskedValue={false} />,
|
|
450
|
-
);
|
|
451
|
-
|
|
452
|
-
const input = screen.getByTestId("masked-field");
|
|
453
|
-
expect(input).toHaveValue("(123) 456-7890");
|
|
454
|
-
|
|
455
|
-
// Change to a different unmasked value
|
|
456
|
-
rerender(<TestComponent value="9876543210" />);
|
|
457
|
-
|
|
458
|
-
// The input should reflect the new value (displayed as masked)
|
|
459
|
-
expect(input).toHaveValue("(987) 654-3210");
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
it("should not update when value prop matches current internal state", () => {
|
|
463
|
-
const setValue = jest.fn();
|
|
464
|
-
|
|
465
|
-
// Mock useIMask to spy on setValue calls
|
|
466
|
-
const originalUseIMask = jest.requireActual("react-imask").useIMask;
|
|
467
|
-
const reactIMask = jest.requireActual("react-imask");
|
|
468
|
-
jest.spyOn(reactIMask, "useIMask").mockImplementation((mask, options) => {
|
|
469
|
-
const result = originalUseIMask(mask, options);
|
|
470
|
-
return {
|
|
471
|
-
...result,
|
|
472
|
-
setValue: setValue,
|
|
473
|
-
};
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
const { rerender } = render(<TestComponent value="1234567890" />);
|
|
477
|
-
|
|
478
|
-
// Clear any initial setValue calls
|
|
479
|
-
setValue.mockClear();
|
|
480
|
-
|
|
481
|
-
// Re-render with the same value
|
|
482
|
-
rerender(<TestComponent value="1234567890" />);
|
|
483
|
-
|
|
484
|
-
// setValue should not be called since the value hasn't actually changed
|
|
485
|
-
expect(setValue).not.toHaveBeenCalled();
|
|
486
|
-
|
|
487
|
-
// Restore original implementation
|
|
488
|
-
jest.restoreAllMocks();
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
it("should handle partial phone number updates", () => {
|
|
492
|
-
const { rerender } = render(<TestComponent value="123" />);
|
|
493
|
-
|
|
494
|
-
const input = screen.getByTestId("masked-field");
|
|
495
|
-
expect(input).toHaveValue("(123");
|
|
496
|
-
|
|
497
|
-
// Update to a longer partial number
|
|
498
|
-
rerender(<TestComponent value="12345" />);
|
|
499
|
-
|
|
500
|
-
expect(input).toHaveValue("(123) 45");
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
it("should handle empty value updates", () => {
|
|
504
|
-
const { rerender } = render(<TestComponent value="1234567890" />);
|
|
505
|
-
|
|
506
|
-
const input = screen.getByTestId("masked-field");
|
|
507
|
-
expect(input).toHaveValue("(123) 456-7890");
|
|
508
|
-
|
|
509
|
-
// Clear the value
|
|
510
|
-
rerender(<TestComponent value="" />);
|
|
511
|
-
|
|
512
|
-
expect(input).toHaveValue("");
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
it("should work with number masks and controlled values", () => {
|
|
516
|
-
const { rerender } = render(
|
|
517
|
-
<TestComponent
|
|
518
|
-
value="1234567"
|
|
519
|
-
mask={commaSeparatedNumberMask}
|
|
520
|
-
label="Number"
|
|
521
|
-
/>,
|
|
522
|
-
);
|
|
523
|
-
|
|
524
|
-
const input = screen.getByTestId("masked-field");
|
|
525
|
-
expect(input).toHaveValue("1,234,567");
|
|
526
|
-
|
|
527
|
-
// Update to different number
|
|
528
|
-
rerender(
|
|
529
|
-
<TestComponent
|
|
530
|
-
value="9876543"
|
|
531
|
-
mask={commaSeparatedNumberMask}
|
|
532
|
-
label="Number"
|
|
533
|
-
/>,
|
|
534
|
-
);
|
|
535
|
-
|
|
536
|
-
expect(input).toHaveValue("9,876,543");
|
|
537
|
-
});
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
describe("accessibility", () => {
|
|
541
|
-
it("should forward aria-label correctly", () => {
|
|
542
|
-
render(
|
|
543
|
-
<MaskedField
|
|
544
|
-
mask={usPhoneNumberMask}
|
|
545
|
-
label="Phone Number"
|
|
546
|
-
data-testid="masked-field"
|
|
547
|
-
aria-label="Custom phone number label"
|
|
548
|
-
/>,
|
|
549
|
-
);
|
|
550
|
-
|
|
551
|
-
const input = screen.getByTestId("masked-field");
|
|
552
|
-
expect(input).toHaveAttribute("aria-label", "Custom phone number label");
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
it("should forward aria-describedby correctly", () => {
|
|
556
|
-
render(
|
|
557
|
-
<MaskedField
|
|
558
|
-
mask={usPhoneNumberMask}
|
|
559
|
-
label="Phone Number"
|
|
560
|
-
data-testid="masked-field"
|
|
561
|
-
aria-describedby="phone-help"
|
|
562
|
-
/>,
|
|
563
|
-
);
|
|
564
|
-
|
|
565
|
-
const input = screen.getByTestId("masked-field");
|
|
566
|
-
expect(input).toHaveAttribute("aria-describedby", "phone-help");
|
|
567
|
-
});
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
describe("ref forwarding", () => {
|
|
571
|
-
it("should forward ref to the input element", () => {
|
|
572
|
-
let inputRef: HTMLInputElement | null = null;
|
|
573
|
-
|
|
574
|
-
render(
|
|
575
|
-
<MaskedField
|
|
576
|
-
mask={usPhoneNumberMask}
|
|
577
|
-
label="Phone Number"
|
|
578
|
-
data-testid="masked-field"
|
|
579
|
-
ref={ref => {
|
|
580
|
-
inputRef = ref;
|
|
581
|
-
}}
|
|
582
|
-
/>,
|
|
583
|
-
);
|
|
584
|
-
|
|
585
|
-
const input = screen.getByTestId("masked-field");
|
|
586
|
-
expect(inputRef).toBe(input);
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
it("should work with useRef", () => {
|
|
590
|
-
const TestComponent = () => {
|
|
591
|
-
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
592
|
-
|
|
593
|
-
return (
|
|
594
|
-
<MaskedField
|
|
595
|
-
mask={usPhoneNumberMask}
|
|
596
|
-
label="Phone Number"
|
|
597
|
-
data-testid="masked-field"
|
|
598
|
-
ref={inputRef}
|
|
599
|
-
/>
|
|
600
|
-
);
|
|
601
|
-
};
|
|
602
|
-
|
|
603
|
-
render(<TestComponent />);
|
|
604
|
-
|
|
605
|
-
const input = screen.getByTestId("masked-field");
|
|
606
|
-
expect(input).toBeInTheDocument();
|
|
607
|
-
});
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
describe("custom mask configurations", () => {
|
|
611
|
-
it("should work with custom mask objects", async () => {
|
|
612
|
-
const user = userEvent.setup();
|
|
613
|
-
const customMask = {
|
|
614
|
-
mask: "000-000",
|
|
615
|
-
definitions: {
|
|
616
|
-
"0": /[0-9]/,
|
|
617
|
-
},
|
|
618
|
-
};
|
|
619
|
-
|
|
620
|
-
render(
|
|
621
|
-
<MaskedField
|
|
622
|
-
mask={customMask}
|
|
623
|
-
label="Custom Code"
|
|
624
|
-
data-testid="masked-field"
|
|
625
|
-
/>,
|
|
626
|
-
);
|
|
627
|
-
|
|
628
|
-
const input = screen.getByTestId("masked-field");
|
|
629
|
-
|
|
630
|
-
await user.type(input, "123456");
|
|
631
|
-
|
|
632
|
-
expect(input).toHaveValue("123-456");
|
|
633
|
-
});
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
describe("edge cases", () => {
|
|
637
|
-
it("should handle empty input gracefully", () => {
|
|
638
|
-
render(
|
|
639
|
-
<MaskedField
|
|
640
|
-
mask={usPhoneNumberMask}
|
|
641
|
-
label="Phone Number"
|
|
642
|
-
data-testid="masked-field"
|
|
643
|
-
/>,
|
|
644
|
-
);
|
|
645
|
-
|
|
646
|
-
const input = screen.getByTestId("masked-field");
|
|
647
|
-
expect(input).toHaveValue("");
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
it("should not call onChange when no onChange handler is provided", async () => {
|
|
651
|
-
const user = userEvent.setup();
|
|
652
|
-
|
|
653
|
-
render(
|
|
654
|
-
<MaskedField
|
|
655
|
-
mask={usPhoneNumberMask}
|
|
656
|
-
label="Phone Number"
|
|
657
|
-
data-testid="masked-field"
|
|
658
|
-
/>,
|
|
659
|
-
);
|
|
660
|
-
|
|
661
|
-
const input = screen.getByTestId("masked-field");
|
|
662
|
-
|
|
663
|
-
// Should not throw an error
|
|
664
|
-
await user.type(input, "123");
|
|
665
|
-
|
|
666
|
-
expect(input).toHaveValue("(123");
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
it("should handle partial input correctly", async () => {
|
|
670
|
-
const user = userEvent.setup();
|
|
671
|
-
|
|
672
|
-
render(
|
|
673
|
-
<MaskedField
|
|
674
|
-
mask={usPhoneNumberMask}
|
|
675
|
-
label="Phone Number"
|
|
676
|
-
data-testid="masked-field"
|
|
677
|
-
/>,
|
|
678
|
-
);
|
|
679
|
-
|
|
680
|
-
const input = screen.getByTestId("masked-field");
|
|
681
|
-
|
|
682
|
-
await user.type(input, "123");
|
|
683
|
-
|
|
684
|
-
expect(input).toHaveValue("(123");
|
|
685
|
-
});
|
|
686
|
-
});
|
|
687
|
-
});
|