@tanstack/form-core 0.7.1 → 0.8.0
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/build/legacy/FieldApi.cjs +5 -1
- package/build/legacy/FieldApi.cjs.map +1 -1
- package/build/legacy/FieldApi.d.cts +1 -1
- package/build/legacy/FieldApi.d.ts +1 -1
- package/build/legacy/FieldApi.js +5 -1
- package/build/legacy/FieldApi.js.map +1 -1
- package/build/legacy/FormApi.cjs +176 -11
- package/build/legacy/FormApi.cjs.map +1 -1
- package/build/legacy/FormApi.js +176 -11
- package/build/legacy/FormApi.js.map +1 -1
- package/build/legacy/index.d.cts +11 -4
- package/build/legacy/index.d.ts +11 -4
- package/build/modern/FieldApi.cjs +5 -1
- package/build/modern/FieldApi.cjs.map +1 -1
- package/build/modern/FieldApi.d.cts +1 -1
- package/build/modern/FieldApi.d.ts +1 -1
- package/build/modern/FieldApi.js +5 -1
- package/build/modern/FieldApi.js.map +1 -1
- package/build/modern/FormApi.cjs +175 -11
- package/build/modern/FormApi.cjs.map +1 -1
- package/build/modern/FormApi.js +175 -11
- package/build/modern/FormApi.js.map +1 -1
- package/build/modern/index.d.cts +11 -4
- package/build/modern/index.d.ts +11 -4
- package/package.json +1 -1
- package/src/FieldApi.ts +8 -4
- package/src/FormApi.ts +229 -14
- package/src/tests/FieldApi.spec.ts +7 -1
- package/src/tests/FormApi.spec.ts +352 -0
|
@@ -2,6 +2,7 @@ import { expect } from 'vitest'
|
|
|
2
2
|
|
|
3
3
|
import { FormApi } from '../FormApi'
|
|
4
4
|
import { FieldApi } from '../FieldApi'
|
|
5
|
+
import { sleep } from './utils'
|
|
5
6
|
|
|
6
7
|
describe('form api', () => {
|
|
7
8
|
it('should get default form state', () => {
|
|
@@ -16,6 +17,8 @@ describe('form api', () => {
|
|
|
16
17
|
isFormValid: true,
|
|
17
18
|
isFormValidating: false,
|
|
18
19
|
isSubmitted: false,
|
|
20
|
+
errors: [],
|
|
21
|
+
errorMap: {},
|
|
19
22
|
isSubmitting: false,
|
|
20
23
|
isTouched: false,
|
|
21
24
|
isValid: true,
|
|
@@ -39,6 +42,8 @@ describe('form api', () => {
|
|
|
39
42
|
fieldMeta: {},
|
|
40
43
|
canSubmit: true,
|
|
41
44
|
isFieldsValid: true,
|
|
45
|
+
errors: [],
|
|
46
|
+
errorMap: {},
|
|
42
47
|
isFieldsValidating: false,
|
|
43
48
|
isFormValid: true,
|
|
44
49
|
isFormValidating: false,
|
|
@@ -62,6 +67,8 @@ describe('form api', () => {
|
|
|
62
67
|
expect(form.state).toEqual({
|
|
63
68
|
values: {},
|
|
64
69
|
fieldMeta: {},
|
|
70
|
+
errors: [],
|
|
71
|
+
errorMap: {},
|
|
65
72
|
canSubmit: true,
|
|
66
73
|
isFieldsValid: true,
|
|
67
74
|
isFieldsValidating: false,
|
|
@@ -97,6 +104,8 @@ describe('form api', () => {
|
|
|
97
104
|
values: {
|
|
98
105
|
name: 'other',
|
|
99
106
|
},
|
|
107
|
+
errors: [],
|
|
108
|
+
errorMap: {},
|
|
100
109
|
fieldMeta: {},
|
|
101
110
|
canSubmit: true,
|
|
102
111
|
isFieldsValid: true,
|
|
@@ -129,6 +138,8 @@ describe('form api', () => {
|
|
|
129
138
|
values: {
|
|
130
139
|
name: 'test',
|
|
131
140
|
},
|
|
141
|
+
errors: [],
|
|
142
|
+
errorMap: {},
|
|
132
143
|
fieldMeta: {},
|
|
133
144
|
canSubmit: true,
|
|
134
145
|
isFieldsValid: true,
|
|
@@ -316,4 +327,345 @@ describe('form api', () => {
|
|
|
316
327
|
expect(form.state.isFieldsValid).toEqual(true)
|
|
317
328
|
expect(form.state.canSubmit).toEqual(true)
|
|
318
329
|
})
|
|
330
|
+
|
|
331
|
+
it('should run validation onChange', () => {
|
|
332
|
+
const form = new FormApi({
|
|
333
|
+
defaultValues: {
|
|
334
|
+
name: 'test',
|
|
335
|
+
},
|
|
336
|
+
onChange: (value) => {
|
|
337
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
338
|
+
return
|
|
339
|
+
},
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
const field = new FieldApi({
|
|
343
|
+
form,
|
|
344
|
+
name: 'name',
|
|
345
|
+
})
|
|
346
|
+
form.mount()
|
|
347
|
+
field.mount()
|
|
348
|
+
|
|
349
|
+
expect(form.state.errors.length).toBe(0)
|
|
350
|
+
field.setValue('other', { touch: true })
|
|
351
|
+
expect(form.state.errors).toContain('Please enter a different value')
|
|
352
|
+
expect(form.state.errorMap).toMatchObject({
|
|
353
|
+
onChange: 'Please enter a different value',
|
|
354
|
+
})
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
it('should run async validation onChange', async () => {
|
|
358
|
+
vi.useFakeTimers()
|
|
359
|
+
|
|
360
|
+
const form = new FormApi({
|
|
361
|
+
defaultValues: {
|
|
362
|
+
name: 'test',
|
|
363
|
+
},
|
|
364
|
+
onChangeAsync: async (value) => {
|
|
365
|
+
await sleep(1000)
|
|
366
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
367
|
+
return
|
|
368
|
+
},
|
|
369
|
+
})
|
|
370
|
+
const field = new FieldApi({
|
|
371
|
+
form,
|
|
372
|
+
name: 'name',
|
|
373
|
+
})
|
|
374
|
+
form.mount()
|
|
375
|
+
|
|
376
|
+
field.mount()
|
|
377
|
+
|
|
378
|
+
expect(form.state.errors.length).toBe(0)
|
|
379
|
+
field.setValue('other', { touch: true })
|
|
380
|
+
await vi.runAllTimersAsync()
|
|
381
|
+
expect(form.state.errors).toContain('Please enter a different value')
|
|
382
|
+
expect(form.state.errorMap).toMatchObject({
|
|
383
|
+
onChange: 'Please enter a different value',
|
|
384
|
+
})
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
it('should run async validation onChange with debounce', async () => {
|
|
388
|
+
vi.useFakeTimers()
|
|
389
|
+
const sleepMock = vi.fn().mockImplementation(sleep)
|
|
390
|
+
|
|
391
|
+
const form = new FormApi({
|
|
392
|
+
defaultValues: {
|
|
393
|
+
name: 'test',
|
|
394
|
+
},
|
|
395
|
+
onChangeAsyncDebounceMs: 1000,
|
|
396
|
+
onChangeAsync: async (value) => {
|
|
397
|
+
await sleepMock(1000)
|
|
398
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
399
|
+
return
|
|
400
|
+
},
|
|
401
|
+
})
|
|
402
|
+
const field = new FieldApi({
|
|
403
|
+
form,
|
|
404
|
+
name: 'name',
|
|
405
|
+
})
|
|
406
|
+
form.mount()
|
|
407
|
+
|
|
408
|
+
field.mount()
|
|
409
|
+
|
|
410
|
+
expect(form.state.errors.length).toBe(0)
|
|
411
|
+
field.setValue('other', { touch: true })
|
|
412
|
+
field.setValue('other')
|
|
413
|
+
await vi.runAllTimersAsync()
|
|
414
|
+
// sleepMock will have been called 2 times without onChangeAsyncDebounceMs
|
|
415
|
+
expect(sleepMock).toHaveBeenCalledTimes(1)
|
|
416
|
+
expect(form.state.errors).toContain('Please enter a different value')
|
|
417
|
+
expect(form.state.errorMap).toMatchObject({
|
|
418
|
+
onChange: 'Please enter a different value',
|
|
419
|
+
})
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
it('should run async validation onChange with asyncDebounceMs', async () => {
|
|
423
|
+
vi.useFakeTimers()
|
|
424
|
+
const sleepMock = vi.fn().mockImplementation(sleep)
|
|
425
|
+
|
|
426
|
+
const form = new FormApi({
|
|
427
|
+
defaultValues: {
|
|
428
|
+
name: 'test',
|
|
429
|
+
},
|
|
430
|
+
asyncDebounceMs: 1000,
|
|
431
|
+
onChangeAsync: async (value) => {
|
|
432
|
+
await sleepMock(1000)
|
|
433
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
434
|
+
return
|
|
435
|
+
},
|
|
436
|
+
})
|
|
437
|
+
const field = new FieldApi({
|
|
438
|
+
form,
|
|
439
|
+
name: 'name',
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
form.mount()
|
|
443
|
+
field.mount()
|
|
444
|
+
|
|
445
|
+
expect(form.state.errors.length).toBe(0)
|
|
446
|
+
field.setValue('other', { touch: true })
|
|
447
|
+
field.setValue('other')
|
|
448
|
+
await vi.runAllTimersAsync()
|
|
449
|
+
// sleepMock will have been called 2 times without asyncDebounceMs
|
|
450
|
+
expect(sleepMock).toHaveBeenCalledTimes(1)
|
|
451
|
+
expect(form.state.errors).toContain('Please enter a different value')
|
|
452
|
+
expect(form.state.errorMap).toMatchObject({
|
|
453
|
+
onChange: 'Please enter a different value',
|
|
454
|
+
})
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
it('should run validation onBlur', () => {
|
|
458
|
+
const form = new FormApi({
|
|
459
|
+
defaultValues: {
|
|
460
|
+
name: 'other',
|
|
461
|
+
},
|
|
462
|
+
onBlur: (value) => {
|
|
463
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
464
|
+
return
|
|
465
|
+
},
|
|
466
|
+
})
|
|
467
|
+
const field = new FieldApi({
|
|
468
|
+
form,
|
|
469
|
+
name: 'name',
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
form.mount()
|
|
473
|
+
field.mount()
|
|
474
|
+
|
|
475
|
+
field.setValue('other', { touch: true })
|
|
476
|
+
field.validate('blur')
|
|
477
|
+
expect(form.state.errors).toContain('Please enter a different value')
|
|
478
|
+
expect(form.state.errorMap).toMatchObject({
|
|
479
|
+
onBlur: 'Please enter a different value',
|
|
480
|
+
})
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
it('should run async validation onBlur', async () => {
|
|
484
|
+
vi.useFakeTimers()
|
|
485
|
+
|
|
486
|
+
const form = new FormApi({
|
|
487
|
+
defaultValues: {
|
|
488
|
+
name: 'test',
|
|
489
|
+
},
|
|
490
|
+
onBlurAsync: async (value) => {
|
|
491
|
+
await sleep(1000)
|
|
492
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
493
|
+
return
|
|
494
|
+
},
|
|
495
|
+
})
|
|
496
|
+
const field = new FieldApi({
|
|
497
|
+
form,
|
|
498
|
+
name: 'name',
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
form.mount()
|
|
502
|
+
field.mount()
|
|
503
|
+
|
|
504
|
+
expect(form.state.errors.length).toBe(0)
|
|
505
|
+
field.setValue('other', { touch: true })
|
|
506
|
+
field.validate('blur')
|
|
507
|
+
await vi.runAllTimersAsync()
|
|
508
|
+
expect(form.state.errors).toContain('Please enter a different value')
|
|
509
|
+
expect(form.state.errorMap).toMatchObject({
|
|
510
|
+
onBlur: 'Please enter a different value',
|
|
511
|
+
})
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
it('should run async validation onBlur with debounce', async () => {
|
|
515
|
+
vi.useFakeTimers()
|
|
516
|
+
const sleepMock = vi.fn().mockImplementation(sleep)
|
|
517
|
+
|
|
518
|
+
const form = new FormApi({
|
|
519
|
+
defaultValues: {
|
|
520
|
+
name: 'test',
|
|
521
|
+
},
|
|
522
|
+
onBlurAsyncDebounceMs: 1000,
|
|
523
|
+
onBlurAsync: async (value) => {
|
|
524
|
+
await sleepMock(10)
|
|
525
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
526
|
+
return
|
|
527
|
+
},
|
|
528
|
+
})
|
|
529
|
+
const field = new FieldApi({
|
|
530
|
+
form,
|
|
531
|
+
name: 'name',
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
form.mount()
|
|
535
|
+
field.mount()
|
|
536
|
+
|
|
537
|
+
expect(form.state.errors.length).toBe(0)
|
|
538
|
+
field.setValue('other', { touch: true })
|
|
539
|
+
field.validate('blur')
|
|
540
|
+
field.validate('blur')
|
|
541
|
+
await vi.runAllTimersAsync()
|
|
542
|
+
// sleepMock will have been called 2 times without onBlurAsyncDebounceMs
|
|
543
|
+
expect(sleepMock).toHaveBeenCalledTimes(1)
|
|
544
|
+
expect(form.state.errors).toContain('Please enter a different value')
|
|
545
|
+
expect(form.state.errorMap).toMatchObject({
|
|
546
|
+
onBlur: 'Please enter a different value',
|
|
547
|
+
})
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
it('should run async validation onBlur with asyncDebounceMs', async () => {
|
|
551
|
+
vi.useFakeTimers()
|
|
552
|
+
const sleepMock = vi.fn().mockImplementation(sleep)
|
|
553
|
+
|
|
554
|
+
const form = new FormApi({
|
|
555
|
+
defaultValues: {
|
|
556
|
+
name: 'test',
|
|
557
|
+
},
|
|
558
|
+
asyncDebounceMs: 1000,
|
|
559
|
+
onBlurAsync: async (value) => {
|
|
560
|
+
await sleepMock(10)
|
|
561
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
562
|
+
return
|
|
563
|
+
},
|
|
564
|
+
})
|
|
565
|
+
const field = new FieldApi({
|
|
566
|
+
form,
|
|
567
|
+
name: 'name',
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
form.mount()
|
|
571
|
+
field.mount()
|
|
572
|
+
|
|
573
|
+
expect(form.state.errors.length).toBe(0)
|
|
574
|
+
field.setValue('other', { touch: true })
|
|
575
|
+
field.validate('blur')
|
|
576
|
+
field.validate('blur')
|
|
577
|
+
await vi.runAllTimersAsync()
|
|
578
|
+
// sleepMock will have been called 2 times without asyncDebounceMs
|
|
579
|
+
expect(sleepMock).toHaveBeenCalledTimes(1)
|
|
580
|
+
expect(form.state.errors).toContain('Please enter a different value')
|
|
581
|
+
expect(form.state.errorMap).toMatchObject({
|
|
582
|
+
onBlur: 'Please enter a different value',
|
|
583
|
+
})
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
it('should contain multiple errors when running validation onBlur and onChange', () => {
|
|
587
|
+
const form = new FormApi({
|
|
588
|
+
defaultValues: {
|
|
589
|
+
name: 'other',
|
|
590
|
+
},
|
|
591
|
+
onBlur: (value) => {
|
|
592
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
593
|
+
return
|
|
594
|
+
},
|
|
595
|
+
onChange: (value) => {
|
|
596
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
597
|
+
return
|
|
598
|
+
},
|
|
599
|
+
})
|
|
600
|
+
const field = new FieldApi({
|
|
601
|
+
form,
|
|
602
|
+
name: 'name',
|
|
603
|
+
})
|
|
604
|
+
|
|
605
|
+
form.mount()
|
|
606
|
+
field.mount()
|
|
607
|
+
|
|
608
|
+
field.setValue('other', { touch: true })
|
|
609
|
+
field.validate('blur')
|
|
610
|
+
expect(form.state.errors).toStrictEqual([
|
|
611
|
+
'Please enter a different value',
|
|
612
|
+
'Please enter a different value',
|
|
613
|
+
])
|
|
614
|
+
expect(form.state.errorMap).toEqual({
|
|
615
|
+
onBlur: 'Please enter a different value',
|
|
616
|
+
onChange: 'Please enter a different value',
|
|
617
|
+
})
|
|
618
|
+
})
|
|
619
|
+
|
|
620
|
+
it('should reset onChange errors when the issue is resolved', () => {
|
|
621
|
+
const form = new FormApi({
|
|
622
|
+
defaultValues: {
|
|
623
|
+
name: 'other',
|
|
624
|
+
},
|
|
625
|
+
onChange: (value) => {
|
|
626
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
627
|
+
return
|
|
628
|
+
},
|
|
629
|
+
})
|
|
630
|
+
const field = new FieldApi({
|
|
631
|
+
form,
|
|
632
|
+
name: 'name',
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
form.mount()
|
|
636
|
+
field.mount()
|
|
637
|
+
|
|
638
|
+
field.setValue('other', { touch: true })
|
|
639
|
+
expect(form.state.errors).toStrictEqual(['Please enter a different value'])
|
|
640
|
+
expect(form.state.errorMap).toEqual({
|
|
641
|
+
onChange: 'Please enter a different value',
|
|
642
|
+
})
|
|
643
|
+
field.setValue('test', { touch: true })
|
|
644
|
+
expect(form.state.errors).toStrictEqual([])
|
|
645
|
+
expect(form.state.errorMap).toEqual({})
|
|
646
|
+
})
|
|
647
|
+
|
|
648
|
+
it('should return error onMount', () => {
|
|
649
|
+
const form = new FormApi({
|
|
650
|
+
defaultValues: {
|
|
651
|
+
name: 'other',
|
|
652
|
+
},
|
|
653
|
+
onMount: (value) => {
|
|
654
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
655
|
+
return
|
|
656
|
+
},
|
|
657
|
+
})
|
|
658
|
+
const field = new FieldApi({
|
|
659
|
+
form,
|
|
660
|
+
name: 'name',
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
form.mount()
|
|
664
|
+
field.mount()
|
|
665
|
+
|
|
666
|
+
expect(form.state.errors).toStrictEqual(['Please enter a different value'])
|
|
667
|
+
expect(form.state.errorMap).toEqual({
|
|
668
|
+
onMount: 'Please enter a different value',
|
|
669
|
+
})
|
|
670
|
+
})
|
|
319
671
|
})
|