@tanstack/form-core 0.23.2 → 0.23.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.
@@ -1,1184 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest'
2
-
3
- import { FormApi } from '../FormApi'
4
- import { FieldApi } from '../FieldApi'
5
- import { sleep } from './utils'
6
-
7
- describe('field api', () => {
8
- it('should have an initial value', () => {
9
- const form = new FormApi({
10
- defaultValues: {
11
- name: 'test',
12
- },
13
- })
14
-
15
- const field = new FieldApi({
16
- form,
17
- name: 'name',
18
- })
19
-
20
- expect(field.getValue()).toBe('test')
21
- })
22
-
23
- it('should use field default value first', () => {
24
- const form = new FormApi({
25
- defaultValues: {
26
- name: 'test',
27
- },
28
- })
29
-
30
- const field = new FieldApi({
31
- form,
32
- defaultValue: 'other',
33
- name: 'name',
34
- })
35
-
36
- expect(field.getValue()).toBe('other')
37
- })
38
-
39
- it('should get default meta', () => {
40
- const form = new FormApi()
41
- const field = new FieldApi({
42
- form,
43
- name: 'name',
44
- })
45
-
46
- expect(field.getMeta()).toEqual({
47
- isTouched: false,
48
- isValidating: false,
49
- isPristine: true,
50
- isDirty: false,
51
- touchedErrors: [],
52
- errors: [],
53
- errorMap: {},
54
- })
55
- })
56
-
57
- it('should allow to set default meta', () => {
58
- const form = new FormApi()
59
- const field = new FieldApi({
60
- form,
61
- name: 'name',
62
- defaultMeta: { isTouched: true, isDirty: true, isPristine: false },
63
- })
64
-
65
- expect(field.getMeta()).toEqual({
66
- isTouched: true,
67
- isValidating: false,
68
- isDirty: true,
69
- isPristine: false,
70
- touchedErrors: [],
71
- errors: [],
72
- errorMap: {},
73
- })
74
- })
75
-
76
- it('should set a value correctly', () => {
77
- const form = new FormApi({
78
- defaultValues: {
79
- name: 'test',
80
- },
81
- })
82
-
83
- const field = new FieldApi({
84
- form,
85
- name: 'name',
86
- })
87
-
88
- field.setValue('other')
89
-
90
- expect(field.getValue()).toBe('other')
91
- })
92
-
93
- it('should push an array value correctly', () => {
94
- const form = new FormApi({
95
- defaultValues: {
96
- names: ['one'],
97
- },
98
- })
99
-
100
- const field = new FieldApi({
101
- form,
102
- name: 'names',
103
- })
104
-
105
- field.pushValue('other')
106
-
107
- expect(field.getValue()).toStrictEqual(['one', 'other'])
108
- })
109
-
110
- it('should run onChange validation when pushing an array fields value', async () => {
111
- const form = new FormApi({
112
- defaultValues: {
113
- names: ['test'],
114
- },
115
- })
116
- form.mount()
117
-
118
- const field = new FieldApi({
119
- form,
120
- name: 'names',
121
- validators: {
122
- onChange: ({ value }) => {
123
- if (value.length < 3) {
124
- return 'At least 3 names are required'
125
- }
126
- return
127
- },
128
- },
129
- })
130
- field.mount()
131
-
132
- field.pushValue('other')
133
-
134
- expect(field.getMeta().errors).toStrictEqual([
135
- 'At least 3 names are required',
136
- ])
137
- })
138
-
139
- it('should insert a value into an array value correctly', () => {
140
- const form = new FormApi({
141
- defaultValues: {
142
- names: ['one', 'two'],
143
- },
144
- })
145
-
146
- const field = new FieldApi({
147
- form,
148
- name: 'names',
149
- })
150
-
151
- field.insertValue(1, 'other')
152
-
153
- expect(field.getValue()).toStrictEqual(['one', 'other', 'two'])
154
- })
155
-
156
- it('should replace a value into an array correctly', () => {
157
- const form = new FormApi({
158
- defaultValues: {
159
- names: ['one', 'two', 'three'],
160
- },
161
- })
162
-
163
- const field = new FieldApi({
164
- form,
165
- name: 'names',
166
- })
167
-
168
- field.replaceValue(1, 'other')
169
-
170
- expect(field.getValue()).toStrictEqual(['one', 'other', 'three'])
171
- })
172
-
173
- it('should do nothing when replacing a value into an array at an index that does not exist', () => {
174
- const form = new FormApi({
175
- defaultValues: {
176
- names: ['one', 'two', 'three'],
177
- },
178
- })
179
-
180
- const field = new FieldApi({
181
- form,
182
- name: 'names',
183
- })
184
-
185
- field.replaceValue(10, 'other')
186
-
187
- expect(field.getValue()).toStrictEqual(['one', 'two', 'three'])
188
- })
189
-
190
- it('should run onChange validation when inserting an array fields value', () => {
191
- const form = new FormApi({
192
- defaultValues: {
193
- names: ['test'],
194
- },
195
- })
196
- form.mount()
197
-
198
- const field = new FieldApi({
199
- form,
200
- name: 'names',
201
- validators: {
202
- onChange: ({ value }) => {
203
- if (value.length < 3) {
204
- return 'At least 3 names are required'
205
- }
206
- return
207
- },
208
- },
209
- defaultMeta: {
210
- isTouched: true,
211
- },
212
- })
213
- field.mount()
214
-
215
- field.insertValue(1, 'other')
216
-
217
- expect(field.getMeta().errors).toStrictEqual([
218
- 'At least 3 names are required',
219
- ])
220
- })
221
-
222
- it('should remove a value from an array value correctly', () => {
223
- const form = new FormApi({
224
- defaultValues: {
225
- names: ['one', 'two'],
226
- },
227
- })
228
-
229
- const field = new FieldApi({
230
- form,
231
- name: 'names',
232
- })
233
-
234
- field.removeValue(1)
235
-
236
- expect(field.getValue()).toStrictEqual(['one'])
237
- })
238
-
239
- it('should run onChange validation when removing an array fields value', async () => {
240
- const form = new FormApi({
241
- defaultValues: {
242
- names: ['test'],
243
- },
244
- })
245
- form.mount()
246
-
247
- const field = new FieldApi({
248
- form,
249
- name: 'names',
250
- validators: {
251
- onChange: ({ value }) => {
252
- if (value.length < 3) {
253
- return 'At least 3 names are required'
254
- }
255
- return
256
- },
257
- },
258
- defaultMeta: {
259
- isTouched: true,
260
- },
261
- })
262
- field.mount()
263
-
264
- await field.removeValue(0)
265
-
266
- expect(field.getMeta().errors).toStrictEqual([
267
- 'At least 3 names are required',
268
- ])
269
- })
270
-
271
- it('should remove a subfield from an array field correctly', async () => {
272
- const form = new FormApi({
273
- defaultValues: {
274
- people: [] as Array<{ name: string }>,
275
- },
276
- })
277
-
278
- const field = new FieldApi({
279
- form,
280
- name: 'people',
281
- })
282
-
283
- const subFieldValidators = {
284
- onChange: ({ value }: { value: string }) =>
285
- value.length === 0 ? 'Required' : null,
286
- }
287
-
288
- const subField1 = new FieldApi({
289
- form: field.form,
290
- name: 'people[0].name',
291
- defaultValue: '',
292
- validators: subFieldValidators,
293
- })
294
-
295
- const subField2 = new FieldApi({
296
- form: field.form,
297
- name: 'people[1].name',
298
- defaultValue: 'hello',
299
- validators: subFieldValidators,
300
- })
301
-
302
- const subField3 = new FieldApi({
303
- form: field.form,
304
- name: 'people[2].name',
305
- defaultValue: '',
306
- validators: subFieldValidators,
307
- })
308
-
309
- const subField4 = new FieldApi({
310
- form: field.form,
311
- name: 'people[3].name',
312
- defaultValue: 'world',
313
- validators: subFieldValidators,
314
- })
315
-
316
- ;[form, field, subField1, subField2, subField3, subField4].forEach((f) =>
317
- f.mount(),
318
- )
319
-
320
- await form.handleSubmit()
321
-
322
- expect(subField1.state.meta.errorMap.onChange).toStrictEqual('Required')
323
- expect(subField2.state.meta.errorMap.onChange).toStrictEqual(undefined)
324
- expect(subField3.state.meta.errorMap.onChange).toStrictEqual('Required')
325
- expect(subField4.state.meta.errorMap.onChange).toStrictEqual(undefined)
326
-
327
- await field.removeValue(0 /* subField1 */, { touch: true })
328
-
329
- expect(subField1.state.value).toBe('hello')
330
- expect(subField1.state.meta.errorMap.onChange).toStrictEqual(undefined)
331
- expect(subField2.state.value).toBe('')
332
- expect(subField2.state.meta.errorMap.onChange).toStrictEqual('Required')
333
- expect(subField3.state.value).toBe('world')
334
- expect(subField3.state.meta.errorMap.onChange).toStrictEqual(undefined)
335
- expect(form.getFieldInfo('people[0].name').instance?.state.value).toBe(
336
- 'hello',
337
- )
338
- expect(form.getFieldInfo('people[1].name').instance?.state.value).toBe('')
339
- expect(form.getFieldInfo('people[2].name').instance?.state.value).toBe(
340
- 'world',
341
- )
342
- })
343
-
344
- it('should remove remove the last subfield from an array field correctly', async () => {
345
- const form = new FormApi({
346
- defaultValues: {
347
- people: [] as Array<{ name: string }>,
348
- },
349
- })
350
-
351
- const field = new FieldApi({
352
- form,
353
- name: 'people',
354
- })
355
-
356
- const subFieldValidators = {
357
- onChange: ({ value }: { value: string }) =>
358
- value.length === 0 ? 'Required' : null,
359
- }
360
-
361
- const subField1 = new FieldApi({
362
- form: field.form,
363
- name: 'people[0].name',
364
- defaultValue: '',
365
- validators: subFieldValidators,
366
- })
367
-
368
- ;[form, field, subField1].forEach((f) => f.mount())
369
-
370
- await form.handleSubmit()
371
-
372
- expect(subField1.state.meta.errorMap.onChange).toStrictEqual('Required')
373
-
374
- await field.removeValue(0 /* subField1 */, { touch: true })
375
-
376
- expect(subField1.state.value).toBe(undefined)
377
- expect(subField1.state.meta.errorMap.onChange).toStrictEqual(undefined)
378
-
379
- expect(form.state.canSubmit).toBe(true)
380
- })
381
-
382
- it('should swap a value from an array value correctly', () => {
383
- const form = new FormApi({
384
- defaultValues: {
385
- names: ['one', 'two'],
386
- },
387
- })
388
-
389
- const field = new FieldApi({
390
- form,
391
- name: 'names',
392
- })
393
-
394
- field.swapValues(0, 1)
395
-
396
- expect(field.getValue()).toStrictEqual(['two', 'one'])
397
- })
398
-
399
- it('should run onChange validation when swapping an array fields value', () => {
400
- const form = new FormApi({
401
- defaultValues: {
402
- names: ['test', 'test2'],
403
- },
404
- })
405
- form.mount()
406
-
407
- const field = new FieldApi({
408
- form,
409
- name: 'names',
410
- validators: {
411
- onChange: ({ value }) => {
412
- if (value.length < 3) {
413
- return 'At least 3 names are required'
414
- }
415
- return
416
- },
417
- },
418
- defaultMeta: {
419
- isTouched: true,
420
- },
421
- })
422
- field.mount()
423
-
424
- field.swapValues(0, 1)
425
-
426
- expect(field.getMeta().errors).toStrictEqual([
427
- 'At least 3 names are required',
428
- ])
429
- })
430
-
431
- it('should move a value from an array value correctly', () => {
432
- const form = new FormApi({
433
- defaultValues: {
434
- names: ['one', 'two', 'three', 'four'],
435
- },
436
- })
437
-
438
- const field = new FieldApi({
439
- form,
440
- name: 'names',
441
- })
442
-
443
- field.moveValue(2, 0)
444
-
445
- expect(field.getValue()).toStrictEqual(['three', 'one', 'two', 'four'])
446
- })
447
-
448
- it('should run onChange validation when moving an array fields value', () => {
449
- const form = new FormApi({
450
- defaultValues: {
451
- names: ['test', 'test2'],
452
- },
453
- })
454
- form.mount()
455
-
456
- const field = new FieldApi({
457
- form,
458
- name: 'names',
459
- validators: {
460
- onChange: ({ value }) => {
461
- if (value.length < 3) {
462
- return 'At least 3 names are required'
463
- }
464
- return
465
- },
466
- },
467
- defaultMeta: {
468
- isTouched: true,
469
- },
470
- })
471
- field.mount()
472
-
473
- field.moveValue(0, 1)
474
-
475
- expect(field.getMeta().errors).toStrictEqual([
476
- 'At least 3 names are required',
477
- ])
478
- })
479
-
480
- it('should not throw errors when no meta info is stored on a field and a form re-renders', async () => {
481
- const form = new FormApi({
482
- defaultValues: {
483
- name: 'test',
484
- },
485
- })
486
-
487
- const field = new FieldApi({
488
- form,
489
- name: 'name',
490
- })
491
-
492
- field.mount()
493
-
494
- expect(() =>
495
- form.update({
496
- defaultValues: {
497
- name: 'other',
498
- },
499
- }),
500
- ).not.toThrow()
501
- })
502
-
503
- it('should run validation onChange', () => {
504
- const form = new FormApi({
505
- defaultValues: {
506
- name: 'test',
507
- },
508
- })
509
-
510
- const field = new FieldApi({
511
- form,
512
- name: 'name',
513
- validators: {
514
- onChange: ({ value }) => {
515
- if (value === 'other') return 'Please enter a different value'
516
- return
517
- },
518
- },
519
- })
520
-
521
- field.mount()
522
-
523
- expect(field.getMeta().errors.length).toBe(0)
524
- field.setValue('other', { touch: true })
525
- expect(field.getMeta().errors).toContain('Please enter a different value')
526
- expect(field.getMeta().errorMap).toMatchObject({
527
- onChange: 'Please enter a different value',
528
- })
529
- })
530
-
531
- it('should run async validation onChange', async () => {
532
- vi.useFakeTimers()
533
-
534
- const form = new FormApi({
535
- defaultValues: {
536
- name: 'test',
537
- },
538
- })
539
-
540
- const field = new FieldApi({
541
- form,
542
- name: 'name',
543
- validators: {
544
- onChangeAsync: async ({ value }) => {
545
- await sleep(1000)
546
- if (value === 'other') return 'Please enter a different value'
547
- return
548
- },
549
- },
550
- })
551
-
552
- field.mount()
553
-
554
- expect(field.getMeta().errors.length).toBe(0)
555
- field.setValue('other', { touch: true })
556
- await vi.runAllTimersAsync()
557
- expect(field.getMeta().errors).toContain('Please enter a different value')
558
- expect(field.getMeta().errorMap).toMatchObject({
559
- onChange: 'Please enter a different value',
560
- })
561
- })
562
-
563
- it('should run async validation onChange with debounce', async () => {
564
- vi.useFakeTimers()
565
- const sleepMock = vi.fn().mockImplementation(sleep)
566
-
567
- const form = new FormApi({
568
- defaultValues: {
569
- name: 'test',
570
- },
571
- })
572
-
573
- const field = new FieldApi({
574
- form,
575
- name: 'name',
576
- validators: {
577
- onChangeAsyncDebounceMs: 1000,
578
- onChangeAsync: async ({ value }) => {
579
- await sleepMock(1000)
580
- if (value === 'other') return 'Please enter a different value'
581
- return
582
- },
583
- },
584
- })
585
-
586
- field.mount()
587
-
588
- expect(field.getMeta().errors.length).toBe(0)
589
- field.setValue('other', { touch: true })
590
- field.setValue('other')
591
- await vi.runAllTimersAsync()
592
- // sleepMock will have been called 2 times without onChangeAsyncDebounceMs
593
- expect(sleepMock).toHaveBeenCalledTimes(1)
594
- expect(field.getMeta().errors).toContain('Please enter a different value')
595
- expect(field.getMeta().errorMap).toMatchObject({
596
- onChange: 'Please enter a different value',
597
- })
598
- })
599
-
600
- it('should run async validation onChange with asyncDebounceMs', async () => {
601
- vi.useFakeTimers()
602
- const sleepMock = vi.fn().mockImplementation(sleep)
603
-
604
- const form = new FormApi({
605
- defaultValues: {
606
- name: 'test',
607
- },
608
- })
609
-
610
- const field = new FieldApi({
611
- form,
612
- name: 'name',
613
- asyncDebounceMs: 1000,
614
- validators: {
615
- onChangeAsync: async ({ value }) => {
616
- await sleepMock(1000)
617
- if (value === 'other') return 'Please enter a different value'
618
- return
619
- },
620
- },
621
- })
622
-
623
- field.mount()
624
-
625
- expect(field.getMeta().errors.length).toBe(0)
626
- field.setValue('other', { touch: true })
627
- field.setValue('other')
628
- await vi.runAllTimersAsync()
629
- // sleepMock will have been called 2 times without asyncDebounceMs
630
- expect(sleepMock).toHaveBeenCalledTimes(1)
631
- expect(field.getMeta().errors).toContain('Please enter a different value')
632
- expect(field.getMeta().errorMap).toMatchObject({
633
- onChange: 'Please enter a different value',
634
- })
635
- })
636
-
637
- it('should run validation onBlur', () => {
638
- const form = new FormApi({
639
- defaultValues: {
640
- name: 'other',
641
- },
642
- })
643
-
644
- const field = new FieldApi({
645
- form,
646
- name: 'name',
647
- validators: {
648
- onBlur: ({ value }) => {
649
- if (value === 'other') return 'Please enter a different value'
650
- return
651
- },
652
- },
653
- })
654
-
655
- field.mount()
656
-
657
- field.setValue('other', { touch: true })
658
- field.validate('blur')
659
- expect(field.getMeta().errors).toContain('Please enter a different value')
660
- expect(field.getMeta().errorMap).toMatchObject({
661
- onBlur: 'Please enter a different value',
662
- })
663
- })
664
-
665
- it('should run async validation onBlur', async () => {
666
- vi.useFakeTimers()
667
-
668
- const form = new FormApi({
669
- defaultValues: {
670
- name: 'test',
671
- },
672
- })
673
-
674
- const field = new FieldApi({
675
- form,
676
- name: 'name',
677
- validators: {
678
- onBlurAsync: async ({ value }) => {
679
- await sleep(1000)
680
- if (value === 'other') return 'Please enter a different value'
681
- return
682
- },
683
- },
684
- })
685
-
686
- field.mount()
687
-
688
- expect(field.getMeta().errors.length).toBe(0)
689
- field.setValue('other', { touch: true })
690
- field.validate('blur')
691
- await vi.runAllTimersAsync()
692
- expect(field.getMeta().errors).toContain('Please enter a different value')
693
- expect(field.getMeta().errorMap).toMatchObject({
694
- onBlur: 'Please enter a different value',
695
- })
696
- })
697
-
698
- it('should run async validation onBlur with debounce', async () => {
699
- vi.useFakeTimers()
700
- const sleepMock = vi.fn().mockImplementation(sleep)
701
-
702
- const form = new FormApi({
703
- defaultValues: {
704
- name: 'test',
705
- },
706
- })
707
-
708
- const field = new FieldApi({
709
- form,
710
- name: 'name',
711
- validators: {
712
- onBlurAsyncDebounceMs: 1000,
713
- onBlurAsync: async ({ value }) => {
714
- await sleepMock(10)
715
- if (value === 'other') return 'Please enter a different value'
716
- return
717
- },
718
- },
719
- })
720
-
721
- field.mount()
722
-
723
- expect(field.getMeta().errors.length).toBe(0)
724
- field.setValue('other', { touch: true })
725
- field.validate('blur')
726
- field.validate('blur')
727
- await vi.runAllTimersAsync()
728
- // sleepMock will have been called 2 times without onBlurAsyncDebounceMs
729
- expect(sleepMock).toHaveBeenCalledTimes(1)
730
- expect(field.getMeta().errors).toContain('Please enter a different value')
731
- expect(field.getMeta().errorMap).toMatchObject({
732
- onBlur: 'Please enter a different value',
733
- })
734
- })
735
-
736
- it('should run async validation onBlur with asyncDebounceMs', async () => {
737
- vi.useFakeTimers()
738
- const sleepMock = vi.fn().mockImplementation(sleep)
739
-
740
- const form = new FormApi({
741
- defaultValues: {
742
- name: 'test',
743
- },
744
- })
745
-
746
- const field = new FieldApi({
747
- form,
748
- name: 'name',
749
- asyncDebounceMs: 1000,
750
- validators: {
751
- onBlurAsync: async ({ value }) => {
752
- await sleepMock(10)
753
- if (value === 'other') return 'Please enter a different value'
754
- return
755
- },
756
- },
757
- })
758
-
759
- field.mount()
760
-
761
- expect(field.getMeta().errors.length).toBe(0)
762
- field.setValue('other', { touch: true })
763
- field.validate('blur')
764
- field.validate('blur')
765
- await vi.runAllTimersAsync()
766
- // sleepMock will have been called 2 times without asyncDebounceMs
767
- expect(sleepMock).toHaveBeenCalledTimes(1)
768
- expect(field.getMeta().errors).toContain('Please enter a different value')
769
- expect(field.getMeta().errorMap).toMatchObject({
770
- onBlur: 'Please enter a different value',
771
- })
772
- })
773
-
774
- it('should run async validation onSubmit', async () => {
775
- vi.useFakeTimers()
776
-
777
- const form = new FormApi({
778
- defaultValues: {
779
- name: 'test',
780
- },
781
- })
782
-
783
- const field = new FieldApi({
784
- form,
785
- name: 'name',
786
- validators: {
787
- onSubmitAsync: async ({ value }) => {
788
- await sleep(1000)
789
- if (value === 'other') return 'Please enter a different value'
790
- return
791
- },
792
- },
793
- })
794
-
795
- field.mount()
796
-
797
- expect(field.getMeta().errors.length).toBe(0)
798
- field.setValue('other', { touch: true })
799
- field.validate('submit')
800
- await vi.runAllTimersAsync()
801
- expect(field.getMeta().errors).toContain('Please enter a different value')
802
- expect(field.getMeta().errorMap).toMatchObject({
803
- onSubmit: 'Please enter a different value',
804
- })
805
- })
806
-
807
- it('should contain multiple errors when running validation onBlur and onChange', () => {
808
- const form = new FormApi({
809
- defaultValues: {
810
- name: 'other',
811
- },
812
- })
813
-
814
- const field = new FieldApi({
815
- form,
816
- name: 'name',
817
- validators: {
818
- onBlur: ({ value }) => {
819
- if (value === 'other') return 'Please enter a different value'
820
- return
821
- },
822
- onChange: ({ value }) => {
823
- if (value === 'other') return 'Please enter a different value'
824
- return
825
- },
826
- },
827
- })
828
-
829
- field.mount()
830
-
831
- field.setValue('other', { touch: true })
832
- field.validate('blur')
833
- expect(field.getMeta().errors).toStrictEqual([
834
- 'Please enter a different value',
835
- 'Please enter a different value',
836
- ])
837
- expect(field.getMeta().errorMap).toEqual({
838
- onBlur: 'Please enter a different value',
839
- onChange: 'Please enter a different value',
840
- })
841
- })
842
-
843
- it('should reset onChange errors when the issue is resolved', () => {
844
- const form = new FormApi({
845
- defaultValues: {
846
- name: 'other',
847
- },
848
- })
849
-
850
- const field = new FieldApi({
851
- form,
852
- name: 'name',
853
- validators: {
854
- onChange: ({ value }) => {
855
- if (value === 'other') return 'Please enter a different value'
856
- return
857
- },
858
- },
859
- })
860
-
861
- field.mount()
862
-
863
- field.setValue('other', { touch: true })
864
- expect(field.getMeta().errors).toStrictEqual([
865
- 'Please enter a different value',
866
- ])
867
- expect(field.getMeta().errorMap).toEqual({
868
- onChange: 'Please enter a different value',
869
- })
870
- field.setValue('test', { touch: true })
871
- expect(field.getMeta().errors).toStrictEqual([])
872
- expect(field.getMeta().errorMap).toEqual({})
873
- })
874
-
875
- it('should handle default value on field using state.value', async () => {
876
- interface Form {
877
- name: string
878
- }
879
- const form = new FormApi<Form>()
880
-
881
- const field = new FieldApi({
882
- form,
883
- name: 'name',
884
- defaultValue: 'test',
885
- })
886
-
887
- field.mount()
888
-
889
- expect(field.state.value).toBe('test')
890
- })
891
-
892
- // test the unmounting of the fieldAPI
893
- it('should preserve value on unmount', () => {
894
- const form = new FormApi({
895
- defaultValues: {
896
- name: 'test',
897
- },
898
- })
899
-
900
- const field = new FieldApi({
901
- form,
902
- name: 'name',
903
- preserveValue: true,
904
- })
905
-
906
- const unmount = field.mount()
907
- unmount()
908
- expect(form.getFieldInfo(field.name).instance).toBeDefined()
909
- expect(form.getFieldInfo(field.name)).toBeDefined()
910
- })
911
-
912
- it('should not preserve field value on ummount', () => {
913
- const form = new FormApi({
914
- defaultValues: {
915
- name: 'test',
916
- },
917
- })
918
-
919
- const field = new FieldApi({
920
- form,
921
- name: 'name',
922
- })
923
-
924
- const unmount = field.mount()
925
- const callback = vi.fn()
926
- const subscription = form.store.subscribe(callback)
927
- unmount()
928
- const info = form.getFieldInfo(field.name)
929
- subscription()
930
- expect(info.instance).toBeNull()
931
- expect(Object.keys(info.instance ?? {}).length).toBe(0)
932
-
933
- // Check that form store has been updated
934
- expect(callback).toHaveBeenCalledOnce()
935
-
936
- // Field should have been removed from the form as well
937
- expect(form.state.values.name).toBeUndefined()
938
- expect(form.state.fieldMeta.name).toBeUndefined()
939
- expect(form.store.state.values.name).toBeUndefined()
940
- expect(form.store.state.fieldMeta.name).toBeUndefined()
941
- })
942
-
943
- it('should show onSubmit errors', async () => {
944
- const form = new FormApi({
945
- defaultValues: {
946
- firstName: '',
947
- },
948
- })
949
-
950
- const field = new FieldApi({
951
- form,
952
- name: 'firstName',
953
- validators: {
954
- onSubmit: ({ value }) =>
955
- value.length > 0 ? undefined : 'first name is required',
956
- },
957
- })
958
-
959
- field.mount()
960
-
961
- await form.handleSubmit()
962
- expect(field.getMeta().errors).toStrictEqual(['first name is required'])
963
- })
964
-
965
- it('should show onMount errors', async () => {
966
- const form = new FormApi({
967
- defaultValues: {
968
- firstName: '',
969
- },
970
- })
971
-
972
- const field = new FieldApi({
973
- form,
974
- name: 'firstName',
975
- validators: {
976
- onMount: ({ value }) =>
977
- value.length > 0 ? undefined : 'first name is required',
978
- },
979
- })
980
-
981
- form.mount()
982
- field.mount()
983
-
984
- expect(field.getMeta().errors).toStrictEqual(['first name is required'])
985
- })
986
-
987
- it('should cancel previous functions from an async validator with an abort signal', async () => {
988
- vi.useRealTimers()
989
- const form = new FormApi({
990
- defaultValues: {
991
- firstName: '',
992
- },
993
- })
994
-
995
- let resolve!: () => void
996
- const promise = new Promise((r) => {
997
- resolve = r as never
998
- })
999
-
1000
- const fn = vi.fn()
1001
-
1002
- const field = new FieldApi({
1003
- form,
1004
- name: 'firstName',
1005
- validators: {
1006
- onChangeAsyncDebounceMs: 0,
1007
- onChangeAsync: async ({ signal }) => {
1008
- await promise
1009
- if (signal.aborted) return
1010
- fn()
1011
- return undefined
1012
- },
1013
- },
1014
- })
1015
-
1016
- field.mount()
1017
-
1018
- field.setValue('one', { touch: true })
1019
- // Allow for a micro-tick to allow the promise to resolve
1020
- await sleep(1)
1021
- field.setValue('two', { touch: true })
1022
- resolve()
1023
- await sleep(1)
1024
- expect(fn).toHaveBeenCalledTimes(1)
1025
- })
1026
-
1027
- it('should run onChange on a linked field', () => {
1028
- const form = new FormApi({
1029
- defaultValues: {
1030
- password: '',
1031
- confirm_password: '',
1032
- },
1033
- })
1034
-
1035
- const passField = new FieldApi({
1036
- form,
1037
- name: 'password',
1038
- })
1039
-
1040
- const passconfirmField = new FieldApi({
1041
- form,
1042
- name: 'confirm_password',
1043
- validators: {
1044
- onChangeListenTo: ['password'],
1045
- onChange: ({ value, fieldApi }) => {
1046
- if (value !== fieldApi.form.getFieldValue('password')) {
1047
- return 'Passwords do not match'
1048
- }
1049
- return undefined
1050
- },
1051
- },
1052
- })
1053
-
1054
- passField.mount()
1055
- passconfirmField.mount()
1056
-
1057
- passField.setValue('one', { touch: true })
1058
- expect(passconfirmField.state.meta.errors).toStrictEqual([
1059
- 'Passwords do not match',
1060
- ])
1061
- passconfirmField.setValue('one', { touch: true })
1062
- expect(passconfirmField.state.meta.errors).toStrictEqual([])
1063
- passField.setValue('two', { touch: true })
1064
- expect(passconfirmField.state.meta.errors).toStrictEqual([
1065
- 'Passwords do not match',
1066
- ])
1067
- })
1068
-
1069
- it('should run onBlur on a linked field', () => {
1070
- const form = new FormApi({
1071
- defaultValues: {
1072
- password: '',
1073
- confirm_password: '',
1074
- },
1075
- })
1076
-
1077
- const passField = new FieldApi({
1078
- form,
1079
- name: 'password',
1080
- })
1081
-
1082
- const passconfirmField = new FieldApi({
1083
- form,
1084
- name: 'confirm_password',
1085
- validators: {
1086
- onBlurListenTo: ['password'],
1087
- onBlur: ({ value, fieldApi }) => {
1088
- if (value !== fieldApi.form.getFieldValue('password')) {
1089
- return 'Passwords do not match'
1090
- }
1091
- return undefined
1092
- },
1093
- },
1094
- })
1095
-
1096
- passField.mount()
1097
- passconfirmField.mount()
1098
-
1099
- passField.setValue('one', { touch: true })
1100
- expect(passconfirmField.state.meta.errors).toStrictEqual([])
1101
- passField.handleBlur()
1102
- expect(passconfirmField.state.meta.errors).toStrictEqual([
1103
- 'Passwords do not match',
1104
- ])
1105
- passconfirmField.setValue('one', { touch: true })
1106
- expect(passconfirmField.state.meta.errors).toStrictEqual([
1107
- 'Passwords do not match',
1108
- ])
1109
- passField.handleBlur()
1110
- expect(passconfirmField.state.meta.errors).toStrictEqual([])
1111
- passField.setValue('two', { touch: true })
1112
- passField.handleBlur()
1113
- expect(passconfirmField.state.meta.errors).toStrictEqual([
1114
- 'Passwords do not match',
1115
- ])
1116
- })
1117
-
1118
- it('should run onChangeAsync on a linked field', async () => {
1119
- vi.useRealTimers()
1120
- let resolve!: () => void
1121
- let promise = new Promise((r) => {
1122
- resolve = r as never
1123
- })
1124
-
1125
- const fn = vi.fn()
1126
-
1127
- const form = new FormApi({
1128
- defaultValues: {
1129
- password: '',
1130
- confirm_password: '',
1131
- },
1132
- })
1133
-
1134
- const passField = new FieldApi({
1135
- form,
1136
- name: 'password',
1137
- })
1138
-
1139
- const passconfirmField = new FieldApi({
1140
- form,
1141
- name: 'confirm_password',
1142
- validators: {
1143
- onChangeListenTo: ['password'],
1144
- onChangeAsync: async ({ value, fieldApi }) => {
1145
- await promise
1146
- fn()
1147
- if (value !== fieldApi.form.getFieldValue('password')) {
1148
- return 'Passwords do not match'
1149
- }
1150
- return undefined
1151
- },
1152
- },
1153
- })
1154
-
1155
- passField.mount()
1156
- passconfirmField.mount()
1157
-
1158
- passField.setValue('one', { touch: true })
1159
- resolve()
1160
- // Allow for a micro-tick to allow the promise to resolve
1161
- await sleep(1)
1162
- expect(passconfirmField.state.meta.errors).toStrictEqual([
1163
- 'Passwords do not match',
1164
- ])
1165
- promise = new Promise((r) => {
1166
- resolve = r as never
1167
- })
1168
- passconfirmField.setValue('one', { touch: true })
1169
- resolve()
1170
- // Allow for a micro-tick to allow the promise to resolve
1171
- await sleep(1)
1172
- expect(passconfirmField.state.meta.errors).toStrictEqual([])
1173
- promise = new Promise((r) => {
1174
- resolve = r as never
1175
- })
1176
- passField.setValue('two', { touch: true })
1177
- resolve()
1178
- // Allow for a micro-tick to allow the promise to resolve
1179
- await sleep(1)
1180
- expect(passconfirmField.state.meta.errors).toStrictEqual([
1181
- 'Passwords do not match',
1182
- ])
1183
- })
1184
- })