@khanacademy/wonder-blocks-form 4.9.1 → 4.9.3

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/components/checkbox-core.d.ts +2 -2
  3. package/dist/components/checkbox.d.ts +2 -2
  4. package/dist/components/choice-internal.d.ts +2 -2
  5. package/dist/components/choice.d.ts +2 -2
  6. package/dist/components/radio-core.d.ts +2 -2
  7. package/dist/components/radio.d.ts +2 -2
  8. package/dist/components/text-area.d.ts +2 -2
  9. package/dist/components/text-field.d.ts +4 -1
  10. package/dist/es/index.js +31 -78
  11. package/dist/index.js +31 -78
  12. package/package.json +7 -7
  13. package/src/__tests__/__snapshots__/custom-snapshot.test.tsx.snap +0 -247
  14. package/src/__tests__/custom-snapshot.test.tsx +0 -48
  15. package/src/components/__tests__/checkbox-group.test.tsx +0 -162
  16. package/src/components/__tests__/checkbox.test.tsx +0 -138
  17. package/src/components/__tests__/field-heading.test.tsx +0 -225
  18. package/src/components/__tests__/labeled-text-field.test.tsx +0 -727
  19. package/src/components/__tests__/radio-group.test.tsx +0 -182
  20. package/src/components/__tests__/text-area.test.tsx +0 -1286
  21. package/src/components/__tests__/text-field.test.tsx +0 -562
  22. package/src/components/checkbox-core.tsx +0 -239
  23. package/src/components/checkbox-group.tsx +0 -174
  24. package/src/components/checkbox.tsx +0 -99
  25. package/src/components/choice-internal.tsx +0 -184
  26. package/src/components/choice.tsx +0 -157
  27. package/src/components/field-heading.tsx +0 -169
  28. package/src/components/group-styles.ts +0 -33
  29. package/src/components/labeled-text-field.tsx +0 -317
  30. package/src/components/radio-core.tsx +0 -171
  31. package/src/components/radio-group.tsx +0 -159
  32. package/src/components/radio.tsx +0 -82
  33. package/src/components/text-area.tsx +0 -430
  34. package/src/components/text-field.tsx +0 -437
  35. package/src/index.ts +0 -17
  36. package/src/util/types.ts +0 -85
  37. package/tsconfig-build.json +0 -19
  38. package/tsconfig-build.tsbuildinfo +0 -1
@@ -1,1286 +0,0 @@
1
- /* eslint-disable max-lines */
2
- import * as React from "react";
3
- import {render, screen} from "@testing-library/react";
4
-
5
- import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
6
- import {userEvent} from "@testing-library/user-event";
7
- import TextArea from "../text-area";
8
-
9
- const defaultOptions = {
10
- wrapper: RenderStateRoot,
11
- };
12
-
13
- const wrapOptions: Array<"soft" | "hard" | "off"> = ["soft", "hard", "off"];
14
-
15
- describe("TextArea", () => {
16
- describe("Attributes", () => {
17
- it("should use the id prop for the textarea", async () => {
18
- // Arrange
19
- const testId = "test-id";
20
- // Act
21
- render(
22
- <TextArea id={testId} value="" onChange={() => {}} />,
23
- defaultOptions,
24
- );
25
-
26
- // Assert
27
- const textArea = await screen.findByRole("textbox");
28
- expect(textArea).toHaveAttribute("id", testId);
29
- });
30
-
31
- it("should use an auto-generated id for the textarea when id prop is not set", async () => {
32
- // Arrange
33
-
34
- // Act
35
- render(<TextArea value="" onChange={() => {}} />, defaultOptions);
36
-
37
- // Assert
38
- // Since the generated id is unique, we cannot know what it will be. We
39
- // only test if the id attribute starts with "uid-", then followed by
40
- // "text-field-" as the scope assigned to IDProvider.
41
- const textArea = await screen.findByRole("textbox");
42
- expect(textArea.getAttribute("id")).toMatch(/uid-text-area-.*$/);
43
- });
44
-
45
- it("should use the testId prop for the textarea element", async () => {
46
- // Arrange
47
- const testId = "test-id";
48
- render(
49
- <TextArea value="Text" onChange={() => {}} testId={testId} />,
50
- defaultOptions,
51
- );
52
-
53
- // Act
54
-
55
- // Assert
56
- const textArea = await screen.findByRole("textbox");
57
- expect(textArea).toHaveAttribute("data-testid", testId);
58
- });
59
-
60
- it("should set the placeholder when the prop when provided", async () => {
61
- // Arrange
62
- const placeholder = "Test placeholder";
63
- render(
64
- <TextArea
65
- placeholder={placeholder}
66
- value="Text"
67
- onChange={() => {}}
68
- />,
69
- defaultOptions,
70
- );
71
-
72
- // Act
73
-
74
- // Assert
75
- const textArea = await screen.findByRole("textbox");
76
- expect(textArea).toHaveAttribute("placeholder", placeholder);
77
- });
78
-
79
- it("should set the aria-disabled attribute when the disabled prop is true", async () => {
80
- // Arrange
81
- render(
82
- <TextArea disabled={true} value="Text" onChange={() => {}} />,
83
- defaultOptions,
84
- );
85
-
86
- // Act
87
-
88
- // Assert
89
- const textArea = await screen.findByRole("textbox");
90
- expect(textArea).toHaveAttribute("aria-disabled", "true");
91
- });
92
-
93
- it("should set the aria-disabled attribute when the disabled prop is false", async () => {
94
- // Arrange
95
- render(
96
- <TextArea disabled={false} value="Text" onChange={() => {}} />,
97
- defaultOptions,
98
- );
99
-
100
- // Act
101
-
102
- // Assert
103
- const textArea = await screen.findByRole("textbox");
104
- expect(textArea).toHaveAttribute("aria-disabled", "false");
105
- });
106
-
107
- it("should not set the aria-disabled attribute if the disabled prop is not provided", async () => {
108
- // Arrange
109
- render(
110
- <TextArea value="Text" onChange={() => {}} />,
111
- defaultOptions,
112
- );
113
-
114
- // Act
115
-
116
- // Assert
117
- const textArea = await screen.findByRole("textbox");
118
- expect(textArea).not.toHaveAttribute("aria-disabled");
119
- });
120
-
121
- it("should not set the disabled attribute when the disabled prop is true", async () => {
122
- // Arrange
123
- render(
124
- <TextArea disabled={true} value="Text" onChange={() => {}} />,
125
- defaultOptions,
126
- );
127
-
128
- // Act
129
-
130
- // Assert
131
- const textArea = await screen.findByRole("textbox");
132
- expect(textArea).not.toHaveAttribute("disabled");
133
- });
134
-
135
- it("should set the readonly attribute when the readOnly prop is provided", async () => {
136
- // Arrange
137
- render(
138
- <TextArea value="Text" onChange={() => {}} readOnly={true} />,
139
- defaultOptions,
140
- );
141
-
142
- // Act
143
-
144
- // Assert
145
- const textArea = await screen.findByRole("textbox");
146
- expect(textArea).toHaveAttribute("readonly");
147
- });
148
-
149
- it("should set the readonly attribute if the disabled prop is true", async () => {
150
- // Arrange
151
- render(
152
- <TextArea value="Text" onChange={() => {}} disabled={true} />,
153
- defaultOptions,
154
- );
155
-
156
- // Act
157
-
158
- // Assert
159
- const textArea = await screen.findByRole("textbox");
160
- expect(textArea).toHaveAttribute("readonly");
161
- });
162
-
163
- it("should set the autocomplete attribute when the autoComplete prop is provided", async () => {
164
- // Arrange
165
- render(
166
- <TextArea value="Text" onChange={() => {}} autoComplete="on" />,
167
- defaultOptions,
168
- );
169
-
170
- // Act
171
-
172
- // Assert
173
- const textArea = await screen.findByRole("textbox");
174
- expect(textArea).toHaveAttribute("autocomplete", "on");
175
- });
176
-
177
- it("should set the name attribute when the name prop is provided", async () => {
178
- // Arrange
179
- const name = "Test name";
180
- render(
181
- <TextArea value="Text" onChange={() => {}} name={name} />,
182
- defaultOptions,
183
- );
184
-
185
- // Act
186
-
187
- // Assert
188
- const textArea = await screen.findByRole("textbox");
189
- expect(textArea).toHaveAttribute("name", name);
190
- });
191
-
192
- it("should set the class when the className prop is provided", async () => {
193
- // Arrange
194
- const className = "Test class name";
195
- render(
196
- <TextArea
197
- value="Text"
198
- onChange={() => {}}
199
- className={className}
200
- />,
201
- defaultOptions,
202
- );
203
-
204
- // Act
205
-
206
- // Assert
207
- const textArea = await screen.findByRole("textbox");
208
- expect(textArea).toHaveClass(className);
209
- });
210
-
211
- it("should set the rows attribute when the rows prop is provided", async () => {
212
- // Arrange
213
- const rows = 10;
214
- render(
215
- <TextArea value="Text" onChange={() => {}} rows={rows} />,
216
- defaultOptions,
217
- );
218
-
219
- // Act
220
-
221
- // Assert
222
- const textArea = await screen.findByRole("textbox");
223
- expect(textArea).toHaveAttribute("rows", `${rows}`);
224
- });
225
-
226
- it("should set the spellcheck attribute when spellCheck prop is set to true", async () => {
227
- // Arrange
228
- render(
229
- <TextArea value="Text" onChange={() => {}} spellCheck={true} />,
230
- defaultOptions,
231
- );
232
-
233
- // Act
234
-
235
- // Assert
236
- const textArea = await screen.findByRole("textbox");
237
- expect(textArea).toHaveAttribute("spellcheck", "true");
238
- });
239
-
240
- it("should set the spellcheck attribute when spellCheck prop is set to false", async () => {
241
- // Arrange
242
- render(
243
- <TextArea
244
- value="Text"
245
- onChange={() => {}}
246
- spellCheck={false}
247
- />,
248
- defaultOptions,
249
- );
250
-
251
- // Act
252
-
253
- // Assert
254
- const textArea = await screen.findByRole("textbox");
255
- expect(textArea).toHaveAttribute("spellcheck", "false");
256
- });
257
-
258
- it.each(wrapOptions)(
259
- "should set the wrap attribute when the spellCheck prop is set to '%s' ",
260
- async (wrap) => {
261
- // Arrange
262
- render(
263
- <TextArea value="Text" onChange={() => {}} wrap={wrap} />,
264
- defaultOptions,
265
- );
266
- // Act
267
-
268
- // Assert
269
- const textArea = await screen.findByRole("textbox");
270
- expect(textArea).toHaveAttribute("wrap", wrap);
271
- },
272
- );
273
-
274
- it("should set the minlength attribute when the minLength prop is used", async () => {
275
- // Arrange
276
- const minLength = 3;
277
- render(
278
- <TextArea
279
- value="Text"
280
- onChange={() => {}}
281
- minLength={minLength}
282
- />,
283
- defaultOptions,
284
- );
285
-
286
- // Act
287
-
288
- // Assert
289
- const textArea = await screen.findByRole("textbox");
290
- expect(textArea).toHaveAttribute("minlength", `${minLength}`);
291
- });
292
-
293
- it("should set the maxlength attribute when the maxLength prop is used", async () => {
294
- // Arrange
295
- const maxLength = 3;
296
- render(
297
- <TextArea
298
- value="Text"
299
- onChange={() => {}}
300
- maxLength={maxLength}
301
- />,
302
- defaultOptions,
303
- );
304
-
305
- // Act
306
-
307
- // Assert
308
- const textArea = await screen.findByRole("textbox");
309
- expect(textArea).toHaveAttribute("maxlength", `${maxLength}`);
310
- });
311
-
312
- it("should set the required attribute when the required prop is used", async () => {
313
- // Arrange
314
- render(
315
- <TextArea value="Text" onChange={() => {}} required={true} />,
316
- defaultOptions,
317
- );
318
-
319
- // Act
320
-
321
- // Assert
322
- const textArea = await screen.findByRole("textbox");
323
- expect(textArea).toHaveAttribute("required");
324
- });
325
- });
326
-
327
- it("should use the value prop", async () => {
328
- // Arrange
329
- const testValue = "test value";
330
- render(
331
- <TextArea value={testValue} onChange={() => {}} />,
332
- defaultOptions,
333
- );
334
-
335
- // Act
336
-
337
- // Assert
338
- const textArea = await screen.findByRole("textbox");
339
- expect(textArea).toHaveValue(testValue);
340
- });
341
-
342
- it("should forward the ref to the textarea element", async () => {
343
- // Arrange
344
- const ref = React.createRef<HTMLTextAreaElement>();
345
- render(
346
- <TextArea value="Text" onChange={() => {}} ref={ref} />,
347
- defaultOptions,
348
- );
349
-
350
- // Act
351
-
352
- // Assert
353
- expect(ref.current).toBeInstanceOf(HTMLTextAreaElement);
354
- expect(await screen.findByRole("textbox")).toBe(ref.current);
355
- });
356
-
357
- describe("Event Handlers", () => {
358
- it("should call the onChange prop when the textarea value changes", async () => {
359
- // Arrange
360
- const onChangeMock = jest.fn();
361
- render(
362
- <TextArea value="" onChange={onChangeMock} />,
363
- defaultOptions,
364
- );
365
-
366
- // Act
367
- // Type one letter
368
- const letterToType = "X";
369
- await userEvent.type(
370
- await screen.findByRole("textbox"),
371
- letterToType,
372
- );
373
-
374
- // Assert
375
- expect(onChangeMock).toHaveBeenCalledExactlyOnceWith(letterToType);
376
- });
377
-
378
- it("should not call the onChange prop when the textarea value changes and it is disabled", async () => {
379
- // Arrange
380
- const onChangeMock = jest.fn();
381
- render(
382
- <TextArea value="" onChange={onChangeMock} disabled={true} />,
383
- defaultOptions,
384
- );
385
-
386
- // Act
387
- // Type one letter
388
- const letterToType = "X";
389
- await userEvent.type(
390
- await screen.findByRole("textbox"),
391
- letterToType,
392
- );
393
-
394
- // Assert
395
- expect(onChangeMock).not.toHaveBeenCalled();
396
- });
397
-
398
- it("should call the onClick prop when the textarea is clicked", async () => {
399
- // Arrange
400
- const onClickMock = jest.fn();
401
- render(
402
- <TextArea value="" onChange={() => {}} onClick={onClickMock} />,
403
- defaultOptions,
404
- );
405
-
406
- // Act
407
- await userEvent.click(await screen.findByRole("textbox"));
408
-
409
- // Assert
410
- expect(onClickMock).toHaveBeenCalledOnce();
411
- });
412
-
413
- it("should not call the onClick prop when the textarea is clicked and it is disabled", async () => {
414
- // Arrange
415
- const onClickMock = jest.fn();
416
- render(
417
- <TextArea
418
- value=""
419
- onChange={() => {}}
420
- onClick={onClickMock}
421
- disabled={true}
422
- />,
423
- defaultOptions,
424
- );
425
-
426
- // Act
427
- await userEvent.click(await screen.findByRole("textbox"));
428
-
429
- // Assert
430
- expect(onClickMock).not.toHaveBeenCalled();
431
- });
432
-
433
- it("should call the onKeyDown prop when a key is typed in the textarea", async () => {
434
- // Arrange
435
- const handleOnKeyDown = jest.fn(
436
- (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
437
- return event.key;
438
- },
439
- );
440
-
441
- render(
442
- <TextArea
443
- value=""
444
- onChange={() => {}}
445
- onKeyDown={handleOnKeyDown}
446
- />,
447
- defaultOptions,
448
- );
449
-
450
- // Act
451
- await userEvent.type(await screen.findByRole("textbox"), "{enter}");
452
-
453
- // Assert
454
- expect(handleOnKeyDown).toHaveReturnedWith("Enter");
455
- });
456
-
457
- it("should not call the onKeyDown prop when a key is typed in the textarea and it is disabled", async () => {
458
- // Arrange
459
- const handleOnKeyDown = jest.fn();
460
-
461
- render(
462
- <TextArea
463
- value=""
464
- onChange={() => {}}
465
- onKeyDown={handleOnKeyDown}
466
- disabled={true}
467
- />,
468
- defaultOptions,
469
- );
470
-
471
- // Act
472
- await userEvent.type(await screen.findByRole("textbox"), "{enter}");
473
-
474
- // Assert
475
- expect(handleOnKeyDown).not.toHaveBeenCalled();
476
- });
477
-
478
- it("should call the onKeyUp prop when a key is typed in the textarea", async () => {
479
- // Arrange
480
- const handleOnKeyUp = jest.fn(
481
- (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
482
- return event.key;
483
- },
484
- );
485
-
486
- render(
487
- <TextArea
488
- value=""
489
- onChange={() => {}}
490
- onKeyUp={handleOnKeyUp}
491
- />,
492
- defaultOptions,
493
- );
494
-
495
- // Act
496
- await userEvent.type(await screen.findByRole("textbox"), "{enter}");
497
-
498
- // Assert
499
- expect(handleOnKeyUp).toHaveReturnedWith("Enter");
500
- });
501
-
502
- it("should not call the onKeyUp prop when a key is typed in the textarea and it is disabled", async () => {
503
- // Arrange
504
- const handleOnKeyUp = jest.fn();
505
-
506
- render(
507
- <TextArea
508
- value=""
509
- onChange={() => {}}
510
- onKeyUp={handleOnKeyUp}
511
- disabled={true}
512
- />,
513
- defaultOptions,
514
- );
515
-
516
- // Act
517
- await userEvent.type(await screen.findByRole("textbox"), "{enter}");
518
-
519
- // Assert
520
- expect(handleOnKeyUp).not.toHaveBeenCalled();
521
- });
522
-
523
- it("should call the onFocus prop when the textarea is focused", async () => {
524
- // Arrange
525
- const handleOnFocus = jest.fn();
526
-
527
- render(
528
- <TextArea
529
- value=""
530
- onChange={() => {}}
531
- onFocus={handleOnFocus}
532
- />,
533
- defaultOptions,
534
- );
535
-
536
- // Act
537
- await userEvent.tab();
538
-
539
- // Assert
540
- expect(handleOnFocus).toHaveBeenCalledOnce();
541
- });
542
-
543
- it("should continue to call the onFocus prop when the textarea is focused and it is disabled", async () => {
544
- // Arrange
545
- const handleOnFocus = jest.fn();
546
-
547
- render(
548
- <TextArea
549
- value=""
550
- onChange={() => {}}
551
- onFocus={handleOnFocus}
552
- disabled={true}
553
- />,
554
- defaultOptions,
555
- );
556
-
557
- // Act
558
- await userEvent.tab();
559
-
560
- // Assert
561
- expect(handleOnFocus).toHaveBeenCalledOnce();
562
- });
563
-
564
- it("should call the onBlur prop when the textarea is blurred", async () => {
565
- // Arrange
566
- const handleOnBlur = jest.fn();
567
-
568
- render(
569
- <TextArea value="" onChange={() => {}} onBlur={handleOnBlur} />,
570
- defaultOptions,
571
- );
572
- // Tab to focus on textarea
573
- await userEvent.tab();
574
-
575
- // Act
576
- // Tab to move focus away
577
- await userEvent.tab();
578
-
579
- // Assert
580
- expect(handleOnBlur).toHaveBeenCalledOnce();
581
- });
582
-
583
- it("should continue to call the onBlur prop when the textarea is blurred and it is disabled", async () => {
584
- // Arrange
585
- const handleOnBlur = jest.fn();
586
-
587
- render(
588
- <TextArea
589
- value=""
590
- onChange={() => {}}
591
- onBlur={handleOnBlur}
592
- disabled={true}
593
- />,
594
- defaultOptions,
595
- );
596
- // Tab to focus on textarea
597
- await userEvent.tab();
598
-
599
- // Act
600
- // Tab to move focus away
601
- await userEvent.tab();
602
-
603
- // Assert
604
- expect(handleOnBlur).toHaveBeenCalledOnce();
605
- });
606
- });
607
-
608
- describe("Accessibility", () => {
609
- describe("Axe", () => {
610
- test("has no accessibility violations", async () => {
611
- // Arrange
612
- // Use with label to demonstrate how it should be used with the
613
- // TextArea component
614
- const {container} = render(
615
- <>
616
- <label htmlFor="text-area">Test label</label>
617
- <TextArea
618
- value="Text"
619
- onChange={() => {}}
620
- id="text-area"
621
- />
622
- </>,
623
- defaultOptions,
624
- );
625
- // Act
626
-
627
- // Assert
628
- await expect(container).toHaveNoA11yViolations();
629
- });
630
- });
631
- describe("Focus", () => {
632
- it("should focus on the textarea by default when the autoFocus prop is provided", async () => {
633
- // Arrange
634
- render(
635
- <TextArea
636
- value="Text"
637
- onChange={() => {}}
638
- autoFocus={true}
639
- />,
640
- defaultOptions,
641
- );
642
-
643
- // Act
644
-
645
- // Assert
646
- const textArea = await screen.findByRole("textbox");
647
- expect(textArea).toHaveFocus();
648
- });
649
-
650
- it("should be focusable", async () => {
651
- // Arrange
652
- render(
653
- <TextArea value="Text" onChange={() => {}} />,
654
- defaultOptions,
655
- );
656
-
657
- // Act
658
- await userEvent.tab();
659
-
660
- // Assert
661
- const textArea = await screen.findByRole("textbox");
662
- expect(textArea).toHaveFocus();
663
- });
664
-
665
- it("should be focusable if it is disabled", async () => {
666
- // Arrange
667
- render(
668
- <TextArea
669
- value="Text"
670
- onChange={() => {}}
671
- disabled={true}
672
- />,
673
- defaultOptions,
674
- );
675
-
676
- // Act
677
- await userEvent.tab();
678
-
679
- // Assert
680
- const textArea = await screen.findByRole("textbox");
681
- expect(textArea).toHaveFocus();
682
- });
683
- });
684
-
685
- describe("ARIA", () => {
686
- it("should set the aria-label attribute when provided", async () => {
687
- // Arrange
688
- const ariaLabel = "Test Aria Label";
689
- render(
690
- <TextArea
691
- value="Text"
692
- onChange={() => {}}
693
- aria-label={ariaLabel}
694
- />,
695
- defaultOptions,
696
- );
697
- // Act
698
-
699
- // Assert
700
- const textArea = await screen.findByRole("textbox");
701
- expect(textArea).toHaveAttribute("aria-label", ariaLabel);
702
- });
703
-
704
- it("should set the aria-labelledby attribute when provided", async () => {
705
- // Arrange
706
- const ariaLabelledBy = "test-label-id";
707
- render(
708
- <TextArea
709
- value="Text"
710
- onChange={() => {}}
711
- aria-labelledby={ariaLabelledBy}
712
- />,
713
- defaultOptions,
714
- );
715
- // Act
716
-
717
- // Assert
718
- const textArea = await screen.findByRole("textbox");
719
- expect(textArea).toHaveAttribute(
720
- "aria-labelledby",
721
- ariaLabelledBy,
722
- );
723
- });
724
-
725
- it("should set the aria-describedby attribute when provided", async () => {
726
- // Arrange
727
- const ariaDescribedBy = "test-label-id";
728
- render(
729
- <TextArea
730
- value="Text"
731
- onChange={() => {}}
732
- aria-describedby={ariaDescribedBy}
733
- />,
734
- defaultOptions,
735
- );
736
- // Act
737
-
738
- // Assert
739
- const textArea = await screen.findByRole("textbox");
740
- expect(textArea).toHaveAttribute(
741
- "aria-describedby",
742
- ariaDescribedBy,
743
- );
744
- });
745
-
746
- it("should set the aria-details attribute when provided", async () => {
747
- // Arrange
748
- const ariaDetails = "details-id";
749
- render(
750
- <TextArea
751
- value="Text"
752
- onChange={() => {}}
753
- aria-details={ariaDetails}
754
- />,
755
- defaultOptions,
756
- );
757
- // Act
758
-
759
- // Assert
760
- const textArea = await screen.findByRole("textbox");
761
- expect(textArea).toHaveAttribute(
762
- "aria-details",
763
- `${ariaDetails}`,
764
- );
765
- });
766
-
767
- it("should set aria-invalid to true if the validate prop returns an error message", async () => {
768
- // Arrange
769
- render(
770
- <TextArea
771
- value="text"
772
- onChange={() => {}}
773
- // If the validate function returns a string or true,
774
- // then the text area is in an error state. For this
775
- // test, we always return a string upon validation
776
- // to trigger the error state. Since the textarea is
777
- // being mounted with a non-empty value, it is validated
778
- // on initial render. Because the text area is in
779
- // an error state, it will have aria-invalid=true
780
- validate={() => "Error"}
781
- />,
782
- defaultOptions,
783
- );
784
-
785
- // Act
786
-
787
- // Assert
788
- const textArea = await screen.findByRole("textbox");
789
- expect(textArea).toHaveAttribute("aria-invalid", "true");
790
- });
791
- it("should set aria-invalid to true if the validate prop returns an error message", async () => {
792
- // Arrange
793
- render(
794
- <TextArea
795
- value="text"
796
- onChange={() => {}}
797
- validate={() => null}
798
- />,
799
- defaultOptions,
800
- );
801
-
802
- // Act
803
-
804
- // Assert
805
- const textArea = await screen.findByRole("textbox");
806
- expect(textArea).toHaveAttribute("aria-invalid", "false");
807
- });
808
- });
809
- });
810
-
811
- describe("Validation", () => {
812
- describe("validate prop", () => {
813
- it("should be in an error state if the initial value is not empty and not valid", async () => {
814
- // Arrange
815
- render(
816
- <TextArea
817
- value="tooShort"
818
- onChange={() => {}}
819
- validate={(value) => {
820
- if (value.length < 10) {
821
- return "Error: value should be >= 10";
822
- }
823
- }}
824
- />,
825
- defaultOptions,
826
- );
827
- // Act
828
-
829
- // Assert
830
- const textArea = await screen.findByRole("textbox");
831
- expect(textArea).toHaveAttribute("aria-invalid", "true");
832
- });
833
-
834
- it("should not be in an error state if the initial value is empty and not valid", async () => {
835
- // Arrange
836
- render(
837
- <TextArea
838
- value=""
839
- onChange={() => {}}
840
- validate={(value) => {
841
- if (value.length < 10) {
842
- return "Error: value should be >= 10";
843
- }
844
- }}
845
- />,
846
- defaultOptions,
847
- );
848
- // Act
849
-
850
- // Assert
851
- const textArea = await screen.findByRole("textbox");
852
- expect(textArea).toHaveAttribute("aria-invalid", "false");
853
- });
854
-
855
- it("should not be in an error state if the initial value is valid", async () => {
856
- // Arrange
857
- render(
858
- <TextArea
859
- value="LongerThan10"
860
- onChange={() => {}}
861
- validate={(value) => {
862
- if (value.length < 10) {
863
- return "Error: value should be >= 10";
864
- }
865
- }}
866
- />,
867
- defaultOptions,
868
- );
869
- // Act
870
-
871
- // Assert
872
- const textArea = await screen.findByRole("textbox");
873
- expect(textArea).toHaveAttribute("aria-invalid", "false");
874
- });
875
-
876
- it("should be able to change from a valid state to an error state", async () => {
877
- // Arrange
878
- const Controlled = () => {
879
- const [value, setValue] = React.useState("text");
880
- return (
881
- <TextArea
882
- value={value}
883
- onChange={setValue}
884
- validate={(value) => {
885
- if (value.length > 4) {
886
- return "Error";
887
- }
888
- }}
889
- />
890
- );
891
- };
892
- render(<Controlled />, defaultOptions);
893
-
894
- // Act
895
- // Add a character to make it longer than the validation limit
896
- await userEvent.type(await screen.findByRole("textbox"), "s");
897
-
898
- // Assert
899
- const textArea = await screen.findByRole("textbox");
900
- expect(textArea).toHaveAttribute("aria-invalid", "true");
901
- });
902
-
903
- it("should be able to change from an error state to a valid state", async () => {
904
- // Arrange
905
- const Controlled = () => {
906
- const [value, setValue] = React.useState("texts");
907
- return (
908
- <TextArea
909
- value={value}
910
- onChange={setValue}
911
- validate={(value) => {
912
- if (value.length > 4) {
913
- return "Error";
914
- }
915
- }}
916
- />
917
- );
918
- };
919
- render(<Controlled />, defaultOptions);
920
-
921
- // Act
922
- // Remove a character to make it within the validation limit
923
- await userEvent.type(
924
- await screen.findByRole("textbox"),
925
- "{backspace}",
926
- );
927
-
928
- // Assert
929
- const textArea = await screen.findByRole("textbox");
930
- expect(textArea).toHaveAttribute("aria-invalid", "false");
931
- });
932
-
933
- it("should call the validate function when it is first rendered", async () => {
934
- // Arrange
935
- const validate = jest.fn();
936
- render(
937
- <TextArea
938
- value="text"
939
- onChange={() => {}}
940
- validate={validate}
941
- />,
942
- defaultOptions,
943
- );
944
- // Act
945
-
946
- // Assert
947
- expect(validate).toHaveBeenCalledExactlyOnceWith("text");
948
- });
949
-
950
- it("should not call the validate function when it is first rendered if the value is empty", async () => {
951
- // Arrange
952
- const validate = jest.fn();
953
- render(
954
- <TextArea
955
- value=""
956
- onChange={() => {}}
957
- validate={validate}
958
- />,
959
- defaultOptions,
960
- );
961
- // Act
962
-
963
- // Assert
964
- expect(validate).not.toHaveBeenCalled();
965
- });
966
-
967
- it("should call the validate function when the value is updated", async () => {
968
- // Arrange
969
- const validate = jest.fn();
970
- const Controlled = () => {
971
- const [value, setValue] = React.useState("text");
972
- return (
973
- <TextArea
974
- value={value}
975
- onChange={setValue}
976
- validate={validate}
977
- />
978
- );
979
- };
980
- render(<Controlled />, defaultOptions);
981
- // Reset mock after initial render
982
- validate.mockReset();
983
-
984
- // Act
985
- // Update value
986
- await userEvent.type(await screen.findByRole("textbox"), "s");
987
-
988
- // Assert
989
- expect(validate).toHaveBeenCalledExactlyOnceWith("texts");
990
- });
991
-
992
- it("should call the validate function when the value is updated to an empty string", async () => {
993
- // Arrange
994
- const validate = jest.fn();
995
- const Controlled = () => {
996
- const [value, setValue] = React.useState("t");
997
- return (
998
- <TextArea
999
- value={value}
1000
- onChange={setValue}
1001
- validate={validate}
1002
- />
1003
- );
1004
- };
1005
- render(<Controlled />, defaultOptions);
1006
- // Reset mock after initial render
1007
- validate.mockReset();
1008
-
1009
- // Act
1010
- // Erase value
1011
- await userEvent.type(
1012
- await screen.findByRole("textbox"),
1013
- "{backspace}",
1014
- );
1015
-
1016
- // Assert
1017
- expect(validate).toHaveBeenCalledExactlyOnceWith("");
1018
- });
1019
- });
1020
- describe("onValidate prop", () => {
1021
- it("should call the onValidate prop with the error message when the textarea is validated", () => {
1022
- // Arrange
1023
- const handleValidate = jest.fn();
1024
- const errorMsg = "error message";
1025
- render(
1026
- <TextArea
1027
- value="text"
1028
- onChange={() => {}}
1029
- validate={() => errorMsg}
1030
- onValidate={handleValidate}
1031
- />,
1032
- defaultOptions,
1033
- );
1034
- // Act
1035
-
1036
- // Assert
1037
- expect(handleValidate).toHaveBeenCalledExactlyOnceWith(
1038
- errorMsg,
1039
- );
1040
- });
1041
-
1042
- it("should call the onValidate prop with null if the validate prop returns null", () => {
1043
- // Arrange
1044
- const handleValidate = jest.fn();
1045
- render(
1046
- <TextArea
1047
- value="text"
1048
- onChange={() => {}}
1049
- validate={() => null}
1050
- onValidate={handleValidate}
1051
- />,
1052
- defaultOptions,
1053
- );
1054
- // Act
1055
-
1056
- // Assert
1057
- expect(handleValidate).toHaveBeenCalledExactlyOnceWith(null);
1058
- });
1059
-
1060
- it("should call the onValidate prop with null if the validate prop is a void function", () => {
1061
- // Arrange
1062
- const handleValidate = jest.fn();
1063
- render(
1064
- <TextArea
1065
- value="text"
1066
- onChange={() => {}}
1067
- validate={() => {}}
1068
- onValidate={handleValidate}
1069
- />,
1070
- defaultOptions,
1071
- );
1072
- // Act
1073
-
1074
- // Assert
1075
- expect(handleValidate).toHaveBeenCalledExactlyOnceWith(null);
1076
- });
1077
- });
1078
-
1079
- describe("required prop", () => {
1080
- it("should initially render with no error if it is required and the value is empty", async () => {
1081
- // Arrange
1082
- render(
1083
- <TextArea
1084
- value=""
1085
- onChange={() => {}}
1086
- required="Required"
1087
- />,
1088
- defaultOptions,
1089
- );
1090
-
1091
- // Act
1092
-
1093
- // Assert
1094
- const textArea = await screen.findByRole("textbox");
1095
- expect(textArea).toHaveAttribute("aria-invalid", "false");
1096
- });
1097
-
1098
- it("should initially render with no error if it is required and the value is not empty", async () => {
1099
- // Arrange
1100
- render(
1101
- <TextArea
1102
- value="Text"
1103
- onChange={() => {}}
1104
- required="Required"
1105
- />,
1106
- defaultOptions,
1107
- );
1108
-
1109
- // Act
1110
-
1111
- // Assert
1112
- const textArea = await screen.findByRole("textbox");
1113
- expect(textArea).toHaveAttribute("aria-invalid", "false");
1114
- });
1115
-
1116
- it("shound update with error if it is required and the value changes to an empty string", async () => {
1117
- // Arrange
1118
- render(
1119
- <TextArea
1120
- value="T"
1121
- onChange={() => {}}
1122
- required="Required"
1123
- />,
1124
- defaultOptions,
1125
- );
1126
-
1127
- // Act
1128
- await userEvent.type(
1129
- await screen.findByRole("textbox"),
1130
- "{backspace}",
1131
- );
1132
- // Assert
1133
- const textArea = await screen.findByRole("textbox");
1134
- expect(textArea).toHaveAttribute("aria-invalid", "true");
1135
- });
1136
-
1137
- it("should not call onValidate on first render if the value is empty and required prop is used", async () => {
1138
- // Arrange
1139
- const handleValidate = jest.fn();
1140
- render(
1141
- <TextArea
1142
- value=""
1143
- onChange={() => {}}
1144
- required="Required"
1145
- onValidate={handleValidate}
1146
- />,
1147
- defaultOptions,
1148
- );
1149
-
1150
- // Act
1151
-
1152
- // Assert
1153
- expect(handleValidate).not.toHaveBeenCalled();
1154
- });
1155
-
1156
- it("should call onValidate with no error message on first render if the value is not empty and required prop is used", async () => {
1157
- // Arrange
1158
- const handleValidate = jest.fn();
1159
- render(
1160
- <TextArea
1161
- value="Text"
1162
- onChange={() => {}}
1163
- required="Required"
1164
- onValidate={handleValidate}
1165
- />,
1166
- defaultOptions,
1167
- );
1168
-
1169
- // Act
1170
-
1171
- // Assert
1172
- expect(handleValidate).toHaveBeenCalledExactlyOnceWith(null);
1173
- });
1174
-
1175
- it("should call onValidate when the value is cleared", async () => {
1176
- // Arrange
1177
- const handleValidate = jest.fn();
1178
- render(
1179
- <TextArea
1180
- value="T"
1181
- onChange={() => {}}
1182
- required="Required"
1183
- onValidate={handleValidate}
1184
- />,
1185
- defaultOptions,
1186
- );
1187
- // Reset mock after initial render
1188
- handleValidate.mockReset();
1189
-
1190
- // Act
1191
- await userEvent.type(
1192
- await screen.findByRole("textbox"),
1193
- "{backspace}",
1194
- );
1195
-
1196
- // Assert
1197
- expect(handleValidate).toHaveBeenCalledOnce();
1198
- });
1199
-
1200
- it("should call onValidate with the custom error message from the required prop when it is a string", async () => {
1201
- // Arrange
1202
- const requiredErrorMsg = "Custom required error message";
1203
- const handleValidate = jest.fn();
1204
- render(
1205
- <TextArea
1206
- value="T"
1207
- onChange={() => {}}
1208
- required={requiredErrorMsg}
1209
- onValidate={handleValidate}
1210
- />,
1211
- defaultOptions,
1212
- );
1213
- // Reset mock after initial render
1214
- handleValidate.mockReset();
1215
-
1216
- // Act
1217
- await userEvent.type(
1218
- await screen.findByRole("textbox"),
1219
- "{backspace}",
1220
- );
1221
-
1222
- // Assert
1223
- expect(handleValidate).toHaveBeenCalledExactlyOnceWith(
1224
- requiredErrorMsg,
1225
- );
1226
- });
1227
-
1228
- it("should call onValidate with a default error message if required is not a string", async () => {
1229
- // Arrange
1230
- const handleValidate = jest.fn();
1231
- render(
1232
- <TextArea
1233
- value="T"
1234
- onChange={() => {}}
1235
- required={true}
1236
- onValidate={handleValidate}
1237
- />,
1238
- defaultOptions,
1239
- );
1240
- // Reset mock after initial render
1241
- handleValidate.mockReset();
1242
-
1243
- // Act
1244
- await userEvent.type(
1245
- await screen.findByRole("textbox"),
1246
- "{backspace}",
1247
- );
1248
-
1249
- // Assert
1250
- expect(handleValidate).toHaveBeenCalledExactlyOnceWith(
1251
- "This field is required.",
1252
- );
1253
- });
1254
-
1255
- it("should prioritize validate prop over required prop if both are provided", async () => {
1256
- // Arrange
1257
- const handleValidate = jest.fn();
1258
- const requiredErrorMessage = "Error because it is required";
1259
- const validateErrorMessage = "Error because of validation";
1260
- render(
1261
- <TextArea
1262
- value="T"
1263
- onChange={() => {}}
1264
- required={requiredErrorMessage}
1265
- onValidate={handleValidate}
1266
- validate={() => validateErrorMessage}
1267
- />,
1268
- defaultOptions,
1269
- );
1270
- // Reset mock after initial render
1271
- handleValidate.mockReset();
1272
-
1273
- // Act
1274
- await userEvent.type(
1275
- await screen.findByRole("textbox"),
1276
- "{backspace}",
1277
- );
1278
-
1279
- // Assert
1280
- expect(handleValidate).toHaveBeenCalledExactlyOnceWith(
1281
- validateErrorMessage,
1282
- );
1283
- });
1284
- });
1285
- });
1286
- });