@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.
@@ -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
  })