@tanstack/form-core 0.20.2 → 0.20.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.
@@ -107,6 +107,35 @@ describe('field api', () => {
107
107
  expect(field.getValue()).toStrictEqual(['one', 'other'])
108
108
  })
109
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
+
110
139
  it('should insert a value into an array value correctly', () => {
111
140
  const form = new FormApi({
112
141
  defaultValues: {
@@ -124,6 +153,38 @@ describe('field api', () => {
124
153
  expect(field.getValue()).toStrictEqual(['one', 'other'])
125
154
  })
126
155
 
156
+ it('should run onChange validation when inserting an array fields value', () => {
157
+ const form = new FormApi({
158
+ defaultValues: {
159
+ names: ['test'],
160
+ },
161
+ })
162
+ form.mount()
163
+
164
+ const field = new FieldApi({
165
+ form,
166
+ name: 'names',
167
+ validators: {
168
+ onChange: ({ value }) => {
169
+ if (value.length < 3) {
170
+ return 'At least 3 names are required'
171
+ }
172
+ return
173
+ },
174
+ },
175
+ defaultMeta: {
176
+ isTouched: true,
177
+ },
178
+ })
179
+ field.mount()
180
+
181
+ field.insertValue(1, 'other')
182
+
183
+ expect(field.getMeta().errors).toStrictEqual([
184
+ 'At least 3 names are required',
185
+ ])
186
+ })
187
+
127
188
  it('should remove a value from an array value correctly', () => {
128
189
  const form = new FormApi({
129
190
  defaultValues: {
@@ -141,6 +202,38 @@ describe('field api', () => {
141
202
  expect(field.getValue()).toStrictEqual(['one'])
142
203
  })
143
204
 
205
+ it('should run onChange validation when removing an array fields value', async () => {
206
+ const form = new FormApi({
207
+ defaultValues: {
208
+ names: ['test'],
209
+ },
210
+ })
211
+ form.mount()
212
+
213
+ const field = new FieldApi({
214
+ form,
215
+ name: 'names',
216
+ validators: {
217
+ onChange: ({ value }) => {
218
+ if (value.length < 3) {
219
+ return 'At least 3 names are required'
220
+ }
221
+ return
222
+ },
223
+ },
224
+ defaultMeta: {
225
+ isTouched: true,
226
+ },
227
+ })
228
+ field.mount()
229
+
230
+ await field.removeValue(0)
231
+
232
+ expect(field.getMeta().errors).toStrictEqual([
233
+ 'At least 3 names are required',
234
+ ])
235
+ })
236
+
144
237
  it('should remove a subfield from an array field correctly', async () => {
145
238
  const form = new FormApi({
146
239
  defaultValues: {
@@ -269,6 +362,38 @@ describe('field api', () => {
269
362
  expect(field.getValue()).toStrictEqual(['two', 'one'])
270
363
  })
271
364
 
365
+ it('should run onChange validation when swapping an array fields value', () => {
366
+ const form = new FormApi({
367
+ defaultValues: {
368
+ names: ['test', 'test2'],
369
+ },
370
+ })
371
+ form.mount()
372
+
373
+ const field = new FieldApi({
374
+ form,
375
+ name: 'names',
376
+ validators: {
377
+ onChange: ({ value }) => {
378
+ if (value.length < 3) {
379
+ return 'At least 3 names are required'
380
+ }
381
+ return
382
+ },
383
+ },
384
+ defaultMeta: {
385
+ isTouched: true,
386
+ },
387
+ })
388
+ field.mount()
389
+
390
+ field.swapValues(0, 1)
391
+
392
+ expect(field.getMeta().errors).toStrictEqual([
393
+ 'At least 3 names are required',
394
+ ])
395
+ })
396
+
272
397
  it('should move a value from an array value correctly', () => {
273
398
  const form = new FormApi({
274
399
  defaultValues: {
@@ -286,6 +411,38 @@ describe('field api', () => {
286
411
  expect(field.getValue()).toStrictEqual(['three', 'one', 'two', 'four'])
287
412
  })
288
413
 
414
+ it('should run onChange validation when moving an array fields value', () => {
415
+ const form = new FormApi({
416
+ defaultValues: {
417
+ names: ['test', 'test2'],
418
+ },
419
+ })
420
+ form.mount()
421
+
422
+ const field = new FieldApi({
423
+ form,
424
+ name: 'names',
425
+ validators: {
426
+ onChange: ({ value }) => {
427
+ if (value.length < 3) {
428
+ return 'At least 3 names are required'
429
+ }
430
+ return
431
+ },
432
+ },
433
+ defaultMeta: {
434
+ isTouched: true,
435
+ },
436
+ })
437
+ field.mount()
438
+
439
+ field.moveValue(0, 1)
440
+
441
+ expect(field.getMeta().errors).toStrictEqual([
442
+ 'At least 3 names are required',
443
+ ])
444
+ })
445
+
289
446
  it('should not throw errors when no meta info is stored on a field and a form re-renders', async () => {
290
447
  const form = new FormApi({
291
448
  defaultValues: {
@@ -324,6 +324,25 @@ describe('form api', () => {
324
324
  expect(form.getFieldValue('names')).toStrictEqual(['test', 'other'])
325
325
  })
326
326
 
327
+ it("should run onChange validation when pushing an array field's value", () => {
328
+ const form = new FormApi({
329
+ defaultValues: {
330
+ names: ['test'],
331
+ },
332
+ validators: {
333
+ onChange: ({ value }) =>
334
+ value.names.length > 3 ? undefined : 'At least 3 names are required',
335
+ },
336
+ })
337
+ form.mount()
338
+ // Since validation runs through the field, a field must be mounted for that array
339
+ new FieldApi({ form, name: 'names' }).mount()
340
+
341
+ form.pushFieldValue('names', 'other')
342
+
343
+ expect(form.state.errors).toStrictEqual(['At least 3 names are required'])
344
+ })
345
+
327
346
  it("should insert an array field's value", () => {
328
347
  const form = new FormApi({
329
348
  defaultValues: {
@@ -336,6 +355,56 @@ describe('form api', () => {
336
355
  expect(form.getFieldValue('names')).toStrictEqual(['one', 'other', 'three'])
337
356
  })
338
357
 
358
+ it("should run onChange validation when inserting an array field's value", () => {
359
+ const form = new FormApi({
360
+ defaultValues: {
361
+ names: ['test'],
362
+ },
363
+ validators: {
364
+ onChange: ({ value }) =>
365
+ value.names.length > 3 ? undefined : 'At least 3 names are required',
366
+ },
367
+ })
368
+ form.mount()
369
+ // Since validation runs through the field, a field must be mounted for that array
370
+ new FieldApi({ form, name: 'names' }).mount()
371
+
372
+ form.insertFieldValue('names', 1, 'other')
373
+
374
+ expect(form.state.errors).toStrictEqual(['At least 3 names are required'])
375
+ })
376
+
377
+ it("should validate all shifted fields when inserting an array field's value", async () => {
378
+ const form = new FormApi({
379
+ defaultValues: {
380
+ names: [{ first: 'test' }, { first: 'test2' }],
381
+ },
382
+ validators: {
383
+ onChange: ({ value }) =>
384
+ value.names.length > 3 ? undefined : 'At least 3 names are required',
385
+ },
386
+ })
387
+ form.mount()
388
+ // Since validation runs through the field, a field must be mounted for that array
389
+ new FieldApi({ form, name: 'names' }).mount()
390
+
391
+ const field1 = new FieldApi({
392
+ form,
393
+ name: 'names[0].first',
394
+ defaultValue: 'test',
395
+ validators: {
396
+ onChange: ({ value }) => value !== 'test' && 'Invalid value',
397
+ },
398
+ })
399
+ field1.mount()
400
+
401
+ expect(field1.state.meta.errors).toStrictEqual([])
402
+
403
+ await form.insertFieldValue('names', 0, { first: 'other' })
404
+
405
+ expect(field1.state.meta.errors).toStrictEqual(['Invalid value'])
406
+ })
407
+
339
408
  it("should remove an array field's value", () => {
340
409
  const form = new FormApi({
341
410
  defaultValues: {
@@ -348,6 +417,79 @@ describe('form api', () => {
348
417
  expect(form.getFieldValue('names')).toStrictEqual(['one', 'three'])
349
418
  })
350
419
 
420
+ it("should run onChange validation when removing an array field's value", () => {
421
+ const form = new FormApi({
422
+ defaultValues: {
423
+ names: ['test'],
424
+ },
425
+ validators: {
426
+ onChange: ({ value }) =>
427
+ value.names.length > 1 ? undefined : 'At least 1 name is required',
428
+ },
429
+ })
430
+ form.mount()
431
+ // Since validation runs through the field, a field must be mounted for that array
432
+ new FieldApi({ form, name: 'names' }).mount()
433
+
434
+ form.removeFieldValue('names', 0)
435
+
436
+ expect(form.state.errors).toStrictEqual(['At least 1 name is required'])
437
+ })
438
+
439
+ it("should validate following fields when removing an array field's value", async () => {
440
+ const form = new FormApi({
441
+ defaultValues: {
442
+ names: ['test', 'test2', 'test3'],
443
+ },
444
+ validators: {
445
+ onChange: ({ value }) =>
446
+ value.names.length > 1 ? undefined : 'At least 1 name is required',
447
+ },
448
+ })
449
+ form.mount()
450
+ // Since validation runs through the field, a field must be mounted for that array
451
+ new FieldApi({ form, name: 'names' }).mount()
452
+
453
+ const field1 = new FieldApi({
454
+ form,
455
+ name: 'names[0]',
456
+ defaultValue: 'test',
457
+ validators: {
458
+ onChange: ({ value }) => value !== 'test' && 'Invalid value',
459
+ },
460
+ })
461
+ field1.mount()
462
+ const field2 = new FieldApi({
463
+ form,
464
+ name: 'names[1]',
465
+ defaultValue: 'test2',
466
+ validators: {
467
+ onChange: ({ value }) => value !== 'test2' && 'Invalid value',
468
+ },
469
+ })
470
+ field2.mount()
471
+ const field3 = new FieldApi({
472
+ form,
473
+ name: 'names[2]',
474
+ defaultValue: 'test3',
475
+ validators: {
476
+ onChange: ({ value }) => value !== 'test3' && 'Invalid value',
477
+ },
478
+ })
479
+ field3.mount()
480
+
481
+ expect(field1.state.meta.errors).toStrictEqual([])
482
+ expect(field2.state.meta.errors).toStrictEqual([])
483
+ expect(field3.state.meta.errors).toStrictEqual([])
484
+
485
+ await form.removeFieldValue('names', 1)
486
+
487
+ expect(field1.state.meta.errors).toStrictEqual([])
488
+ expect(field2.state.meta.errors).toStrictEqual(['Invalid value'])
489
+ // This field does not exist anymore. Therefore, its validation should also not run
490
+ expect(field3.state.meta.errors).toStrictEqual([])
491
+ })
492
+
351
493
  it("should swap an array field's value", () => {
352
494
  const form = new FormApi({
353
495
  defaultValues: {
@@ -355,11 +497,146 @@ describe('form api', () => {
355
497
  },
356
498
  })
357
499
  form.mount()
500
+ // Since validation runs through the field, a field must be mounted for that array
501
+ new FieldApi({ form, name: 'names' }).mount()
502
+
358
503
  form.swapFieldValues('names', 1, 2)
359
504
 
360
505
  expect(form.getFieldValue('names')).toStrictEqual(['one', 'three', 'two'])
361
506
  })
362
507
 
508
+ it("should run onChange validation when swapping an array field's value", () => {
509
+ const form = new FormApi({
510
+ defaultValues: {
511
+ names: ['test', 'test2'],
512
+ },
513
+ validators: {
514
+ onChange: ({ value }) =>
515
+ value.names.length > 3 ? undefined : 'At least 3 names are required',
516
+ },
517
+ })
518
+ form.mount()
519
+ // Since validation runs through the field, a field must be mounted for that array
520
+ new FieldApi({ form, name: 'names' }).mount()
521
+ expect(form.state.errors).toStrictEqual([])
522
+
523
+ form.swapFieldValues('names', 1, 2)
524
+
525
+ expect(form.state.errors).toStrictEqual(['At least 3 names are required'])
526
+ })
527
+
528
+ it('should run validation on swapped fields', () => {
529
+ const form = new FormApi({
530
+ defaultValues: {
531
+ names: ['test', 'test2'],
532
+ },
533
+ validators: {
534
+ onChange: ({ value }) =>
535
+ value.names.length > 3 ? undefined : 'At least 3 names are required',
536
+ },
537
+ })
538
+ form.mount()
539
+ // Since validation runs through the field, a field must be mounted for that array
540
+ new FieldApi({ form, name: 'names' }).mount()
541
+
542
+ const field1 = new FieldApi({
543
+ form,
544
+ name: 'names[0]',
545
+ defaultValue: 'test',
546
+ validators: {
547
+ onChange: ({ value }) => value !== 'test' && 'Invalid value',
548
+ },
549
+ })
550
+ field1.mount()
551
+
552
+ const field2 = new FieldApi({
553
+ form,
554
+ name: 'names[1]',
555
+ defaultValue: 'test2',
556
+ })
557
+ field2.mount()
558
+
559
+ expect(field1.state.meta.errors).toStrictEqual([])
560
+ expect(field2.state.meta.errors).toStrictEqual([])
561
+
562
+ form.swapFieldValues('names', 0, 1)
563
+
564
+ expect(field1.state.meta.errors).toStrictEqual(['Invalid value'])
565
+ expect(field2.state.meta.errors).toStrictEqual([])
566
+ })
567
+
568
+ it("should move an array field's value", () => {
569
+ const form = new FormApi({
570
+ defaultValues: {
571
+ names: ['one', 'two', 'three'],
572
+ },
573
+ })
574
+ form.mount()
575
+ form.moveFieldValues('names', 1, 2)
576
+
577
+ expect(form.getFieldValue('names')).toStrictEqual(['one', 'three', 'two'])
578
+ })
579
+
580
+ it("should run onChange validation when moving an array field's value", () => {
581
+ const form = new FormApi({
582
+ defaultValues: {
583
+ names: ['test', 'test2'],
584
+ },
585
+ validators: {
586
+ onChange: ({ value }) =>
587
+ value.names.length > 3 ? undefined : 'At least 3 names are required',
588
+ },
589
+ })
590
+ form.mount()
591
+ // Since validation runs through the field, a field must be mounted for that array
592
+ new FieldApi({ form, name: 'names' }).mount()
593
+
594
+ expect(form.state.errors).toStrictEqual([])
595
+ form.moveFieldValues('names', 0, 1)
596
+
597
+ expect(form.state.errors).toStrictEqual(['At least 3 names are required'])
598
+ })
599
+
600
+ it('should run validation on moved fields', () => {
601
+ const form = new FormApi({
602
+ defaultValues: {
603
+ names: ['test', 'test2'],
604
+ },
605
+ validators: {
606
+ onChange: ({ value }) =>
607
+ value.names.length > 3 ? undefined : 'At least 3 names are required',
608
+ },
609
+ })
610
+ form.mount()
611
+ // Since validation runs through the field, a field must be mounted for that array
612
+ new FieldApi({ form, name: 'names' }).mount()
613
+
614
+ const field1 = new FieldApi({
615
+ form,
616
+ name: 'names[0]',
617
+ defaultValue: 'test',
618
+ validators: {
619
+ onChange: ({ value }) => value !== 'test' && 'Invalid value',
620
+ },
621
+ })
622
+ field1.mount()
623
+
624
+ const field2 = new FieldApi({
625
+ form,
626
+ name: 'names[1]',
627
+ defaultValue: 'test2',
628
+ })
629
+ field2.mount()
630
+
631
+ expect(field1.state.meta.errors).toStrictEqual([])
632
+ expect(field2.state.meta.errors).toStrictEqual([])
633
+
634
+ form.swapFieldValues('names', 0, 1)
635
+
636
+ expect(field1.state.meta.errors).toStrictEqual(['Invalid value'])
637
+ expect(field2.state.meta.errors).toStrictEqual([])
638
+ })
639
+
363
640
  it('should handle fields inside an array', async () => {
364
641
  interface Employee {
365
642
  firstName: string
@@ -1052,6 +1329,35 @@ describe('form api', () => {
1052
1329
  expect(field.getMeta().errorMap.onChange).toEqual('first name is required')
1053
1330
  })
1054
1331
 
1332
+ it('should validate a single field consistently if touched', async () => {
1333
+ const form = new FormApi({
1334
+ defaultValues: {
1335
+ firstName: '',
1336
+ lastName: '',
1337
+ },
1338
+ })
1339
+
1340
+ const field = new FieldApi({
1341
+ form,
1342
+ name: 'firstName',
1343
+ validators: {
1344
+ onChange: ({ value }) =>
1345
+ value.length > 0 ? undefined : 'first name is required',
1346
+ },
1347
+ defaultMeta: {
1348
+ isTouched: true,
1349
+ },
1350
+ })
1351
+
1352
+ field.mount()
1353
+ form.mount()
1354
+
1355
+ await form.validateField('firstName', 'change')
1356
+ expect(field.getMeta().errorMap.onChange).toEqual('first name is required')
1357
+ await form.validateField('firstName', 'change')
1358
+ expect(field.getMeta().errorMap.onChange).toEqual('first name is required')
1359
+ })
1360
+
1055
1361
  it('should show onSubmit errors', async () => {
1056
1362
  const form = new FormApi({
1057
1363
  defaultValues: {