@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.
- package/dist/cjs/FieldApi.cjs +17 -0
- package/dist/cjs/FieldApi.cjs.map +1 -1
- package/dist/cjs/FieldApi.d.cts +220 -1
- package/dist/cjs/FormApi.cjs +14 -0
- package/dist/cjs/FormApi.cjs.map +1 -1
- package/dist/cjs/FormApi.d.cts +238 -0
- package/dist/cjs/mergeForm.cjs.map +1 -1
- package/dist/cjs/mergeForm.d.cts +3 -0
- package/dist/cjs/types.d.cts +14 -0
- package/dist/cjs/util-types.d.cts +18 -3
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +24 -0
- package/dist/esm/FieldApi.d.ts +220 -1
- package/dist/esm/FieldApi.js +17 -0
- package/dist/esm/FieldApi.js.map +1 -1
- package/dist/esm/FormApi.d.ts +238 -0
- package/dist/esm/FormApi.js +14 -0
- package/dist/esm/FormApi.js.map +1 -1
- package/dist/esm/mergeForm.d.ts +3 -0
- package/dist/esm/mergeForm.js.map +1 -1
- package/dist/esm/types.d.ts +14 -0
- package/dist/esm/util-types.d.ts +18 -3
- package/dist/esm/utils.d.ts +24 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/FieldApi.ts +249 -4
- package/src/FormApi.ts +249 -12
- package/src/mergeForm.ts +3 -0
- package/src/types.ts +14 -2
- package/src/util-types.ts +18 -4
- package/src/utils.ts +24 -0
- package/src/tests/FieldApi.spec.ts +0 -1184
- package/src/tests/FieldApi.test-d.ts +0 -149
- package/src/tests/FormApi.spec.ts +0 -1523
- package/src/tests/formOptions.test.ts +0 -25
- package/src/tests/mutateMergeDeep.spec.ts +0 -32
- package/src/tests/util-types.test-d.ts +0 -171
- package/src/tests/utils.spec.ts +0 -131
- package/src/tests/utils.ts +0 -5
|
@@ -1,1523 +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('form api', () => {
|
|
8
|
-
it('should get default form state', () => {
|
|
9
|
-
const form = new FormApi()
|
|
10
|
-
form.mount()
|
|
11
|
-
expect(form.state).toEqual({
|
|
12
|
-
values: {},
|
|
13
|
-
fieldMeta: {},
|
|
14
|
-
canSubmit: true,
|
|
15
|
-
isFieldsValid: true,
|
|
16
|
-
isFieldsValidating: false,
|
|
17
|
-
isFormValid: true,
|
|
18
|
-
isFormValidating: false,
|
|
19
|
-
isSubmitted: false,
|
|
20
|
-
errors: [],
|
|
21
|
-
errorMap: {},
|
|
22
|
-
isSubmitting: false,
|
|
23
|
-
isTouched: false,
|
|
24
|
-
isPristine: true,
|
|
25
|
-
isDirty: false,
|
|
26
|
-
isValid: true,
|
|
27
|
-
isValidating: false,
|
|
28
|
-
submissionAttempts: 0,
|
|
29
|
-
validationMetaMap: {
|
|
30
|
-
onChange: undefined,
|
|
31
|
-
onBlur: undefined,
|
|
32
|
-
onSubmit: undefined,
|
|
33
|
-
onMount: undefined,
|
|
34
|
-
},
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('should get default form state when default values are passed', () => {
|
|
39
|
-
const form = new FormApi({
|
|
40
|
-
defaultValues: {
|
|
41
|
-
name: 'test',
|
|
42
|
-
},
|
|
43
|
-
})
|
|
44
|
-
form.mount()
|
|
45
|
-
expect(form.state).toEqual({
|
|
46
|
-
values: {
|
|
47
|
-
name: 'test',
|
|
48
|
-
},
|
|
49
|
-
fieldMeta: {},
|
|
50
|
-
canSubmit: true,
|
|
51
|
-
isFieldsValid: true,
|
|
52
|
-
errors: [],
|
|
53
|
-
errorMap: {},
|
|
54
|
-
isFieldsValidating: false,
|
|
55
|
-
isFormValid: true,
|
|
56
|
-
isFormValidating: false,
|
|
57
|
-
isSubmitted: false,
|
|
58
|
-
isSubmitting: false,
|
|
59
|
-
isTouched: false,
|
|
60
|
-
isPristine: true,
|
|
61
|
-
isDirty: false,
|
|
62
|
-
isValid: true,
|
|
63
|
-
isValidating: false,
|
|
64
|
-
submissionAttempts: 0,
|
|
65
|
-
validationMetaMap: {
|
|
66
|
-
onChange: undefined,
|
|
67
|
-
onBlur: undefined,
|
|
68
|
-
onSubmit: undefined,
|
|
69
|
-
onMount: undefined,
|
|
70
|
-
},
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('should get default form state when default state is passed', () => {
|
|
75
|
-
const form = new FormApi({
|
|
76
|
-
defaultState: {
|
|
77
|
-
submissionAttempts: 30,
|
|
78
|
-
},
|
|
79
|
-
})
|
|
80
|
-
form.mount()
|
|
81
|
-
expect(form.state).toEqual({
|
|
82
|
-
values: {},
|
|
83
|
-
fieldMeta: {},
|
|
84
|
-
errors: [],
|
|
85
|
-
errorMap: {},
|
|
86
|
-
canSubmit: true,
|
|
87
|
-
isFieldsValid: true,
|
|
88
|
-
isFieldsValidating: false,
|
|
89
|
-
isFormValid: true,
|
|
90
|
-
isFormValidating: false,
|
|
91
|
-
isSubmitted: false,
|
|
92
|
-
isSubmitting: false,
|
|
93
|
-
isTouched: false,
|
|
94
|
-
isPristine: true,
|
|
95
|
-
isDirty: false,
|
|
96
|
-
isValid: true,
|
|
97
|
-
isValidating: false,
|
|
98
|
-
submissionAttempts: 30,
|
|
99
|
-
validationMetaMap: {
|
|
100
|
-
onChange: undefined,
|
|
101
|
-
onBlur: undefined,
|
|
102
|
-
onSubmit: undefined,
|
|
103
|
-
onMount: undefined,
|
|
104
|
-
},
|
|
105
|
-
})
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('should handle updating form state', () => {
|
|
109
|
-
const form = new FormApi({
|
|
110
|
-
defaultValues: {
|
|
111
|
-
name: 'test',
|
|
112
|
-
},
|
|
113
|
-
})
|
|
114
|
-
form.mount()
|
|
115
|
-
form.update({
|
|
116
|
-
defaultValues: {
|
|
117
|
-
name: 'other',
|
|
118
|
-
},
|
|
119
|
-
defaultState: {
|
|
120
|
-
submissionAttempts: 300,
|
|
121
|
-
},
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
expect(form.state).toEqual({
|
|
125
|
-
values: {
|
|
126
|
-
name: 'other',
|
|
127
|
-
},
|
|
128
|
-
errors: [],
|
|
129
|
-
errorMap: {},
|
|
130
|
-
fieldMeta: {},
|
|
131
|
-
canSubmit: true,
|
|
132
|
-
isFieldsValid: true,
|
|
133
|
-
isFieldsValidating: false,
|
|
134
|
-
isFormValid: true,
|
|
135
|
-
isFormValidating: false,
|
|
136
|
-
isSubmitted: false,
|
|
137
|
-
isSubmitting: false,
|
|
138
|
-
isTouched: false,
|
|
139
|
-
isPristine: true,
|
|
140
|
-
isDirty: false,
|
|
141
|
-
isValid: true,
|
|
142
|
-
isValidating: false,
|
|
143
|
-
submissionAttempts: 300,
|
|
144
|
-
validationMetaMap: {
|
|
145
|
-
onChange: undefined,
|
|
146
|
-
onBlur: undefined,
|
|
147
|
-
onSubmit: undefined,
|
|
148
|
-
onMount: undefined,
|
|
149
|
-
onServer: undefined,
|
|
150
|
-
},
|
|
151
|
-
})
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('should reset the form state properly', () => {
|
|
155
|
-
const form = new FormApi({
|
|
156
|
-
defaultValues: {
|
|
157
|
-
name: 'test',
|
|
158
|
-
},
|
|
159
|
-
})
|
|
160
|
-
form.mount()
|
|
161
|
-
form.setFieldValue('name', 'other')
|
|
162
|
-
form.state.submissionAttempts = 300
|
|
163
|
-
|
|
164
|
-
form.reset()
|
|
165
|
-
|
|
166
|
-
expect(form.state).toEqual({
|
|
167
|
-
values: {
|
|
168
|
-
name: 'test',
|
|
169
|
-
},
|
|
170
|
-
errors: [],
|
|
171
|
-
errorMap: {},
|
|
172
|
-
fieldMeta: {},
|
|
173
|
-
canSubmit: true,
|
|
174
|
-
isFieldsValid: true,
|
|
175
|
-
isFieldsValidating: false,
|
|
176
|
-
isFormValid: true,
|
|
177
|
-
isFormValidating: false,
|
|
178
|
-
isSubmitted: false,
|
|
179
|
-
isSubmitting: false,
|
|
180
|
-
isTouched: false,
|
|
181
|
-
isPristine: true,
|
|
182
|
-
isDirty: false,
|
|
183
|
-
isValid: true,
|
|
184
|
-
isValidating: false,
|
|
185
|
-
submissionAttempts: 0,
|
|
186
|
-
validationMetaMap: {
|
|
187
|
-
onChange: undefined,
|
|
188
|
-
onBlur: undefined,
|
|
189
|
-
onSubmit: undefined,
|
|
190
|
-
onMount: undefined,
|
|
191
|
-
},
|
|
192
|
-
})
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
it('should not wipe validators when resetting', () => {
|
|
196
|
-
const form = new FormApi({
|
|
197
|
-
defaultValues: {
|
|
198
|
-
name: 'test',
|
|
199
|
-
},
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
const field = new FieldApi({
|
|
203
|
-
form,
|
|
204
|
-
name: 'name',
|
|
205
|
-
validators: {
|
|
206
|
-
onChange: ({ value }) => (value.length > 0 ? undefined : 'required'),
|
|
207
|
-
},
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
form.mount()
|
|
211
|
-
|
|
212
|
-
field.mount()
|
|
213
|
-
|
|
214
|
-
field.handleChange('')
|
|
215
|
-
|
|
216
|
-
expect(form.state.isFieldsValid).toEqual(false)
|
|
217
|
-
expect(form.state.canSubmit).toEqual(false)
|
|
218
|
-
|
|
219
|
-
form.reset()
|
|
220
|
-
|
|
221
|
-
expect(form.state).toEqual({
|
|
222
|
-
values: { name: 'test' },
|
|
223
|
-
errors: [],
|
|
224
|
-
errorMap: {},
|
|
225
|
-
fieldMeta: {
|
|
226
|
-
name: {
|
|
227
|
-
isValidating: false,
|
|
228
|
-
isTouched: false,
|
|
229
|
-
isDirty: false,
|
|
230
|
-
isPristine: true,
|
|
231
|
-
touchedErrors: [],
|
|
232
|
-
errors: [],
|
|
233
|
-
errorMap: {},
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
canSubmit: true,
|
|
237
|
-
isFieldsValid: true,
|
|
238
|
-
isFieldsValidating: false,
|
|
239
|
-
isFormValid: true,
|
|
240
|
-
isFormValidating: false,
|
|
241
|
-
isSubmitted: false,
|
|
242
|
-
isSubmitting: false,
|
|
243
|
-
isTouched: false,
|
|
244
|
-
isPristine: true,
|
|
245
|
-
isDirty: false,
|
|
246
|
-
isValid: true,
|
|
247
|
-
isValidating: false,
|
|
248
|
-
submissionAttempts: 0,
|
|
249
|
-
validationMetaMap: {
|
|
250
|
-
onChange: undefined,
|
|
251
|
-
onBlur: undefined,
|
|
252
|
-
onSubmit: undefined,
|
|
253
|
-
onMount: undefined,
|
|
254
|
-
onServer: undefined,
|
|
255
|
-
},
|
|
256
|
-
})
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
it("should get a field's value", () => {
|
|
260
|
-
const form = new FormApi({
|
|
261
|
-
defaultValues: {
|
|
262
|
-
name: 'test',
|
|
263
|
-
},
|
|
264
|
-
})
|
|
265
|
-
form.mount()
|
|
266
|
-
expect(form.getFieldValue('name')).toEqual('test')
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
it("should set a field's value", () => {
|
|
270
|
-
const form = new FormApi({
|
|
271
|
-
defaultValues: {
|
|
272
|
-
name: 'test',
|
|
273
|
-
},
|
|
274
|
-
})
|
|
275
|
-
form.mount()
|
|
276
|
-
form.setFieldValue('name', 'other')
|
|
277
|
-
|
|
278
|
-
expect(form.getFieldValue('name')).toEqual('other')
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
it("should be dirty after a field's value has been set", () => {
|
|
282
|
-
const form = new FormApi({
|
|
283
|
-
defaultValues: {
|
|
284
|
-
name: 'test',
|
|
285
|
-
},
|
|
286
|
-
})
|
|
287
|
-
form.mount()
|
|
288
|
-
|
|
289
|
-
form.setFieldValue('name', 'other', { touch: true })
|
|
290
|
-
|
|
291
|
-
expect(form.state.isDirty).toBe(true)
|
|
292
|
-
expect(form.state.isPristine).toBe(false)
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
it('should be clean again after being reset from a dirty state', () => {
|
|
296
|
-
const form = new FormApi({
|
|
297
|
-
defaultValues: {
|
|
298
|
-
name: 'test',
|
|
299
|
-
},
|
|
300
|
-
})
|
|
301
|
-
form.mount()
|
|
302
|
-
|
|
303
|
-
form.setFieldMeta('name', (meta) => ({
|
|
304
|
-
...meta,
|
|
305
|
-
isDirty: true,
|
|
306
|
-
isPristine: false,
|
|
307
|
-
}))
|
|
308
|
-
|
|
309
|
-
form.reset()
|
|
310
|
-
|
|
311
|
-
expect(form.state.isDirty).toBe(false)
|
|
312
|
-
expect(form.state.isPristine).toBe(true)
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
it("should push an array field's value", () => {
|
|
316
|
-
const form = new FormApi({
|
|
317
|
-
defaultValues: {
|
|
318
|
-
names: ['test'],
|
|
319
|
-
},
|
|
320
|
-
})
|
|
321
|
-
form.mount()
|
|
322
|
-
form.pushFieldValue('names', 'other')
|
|
323
|
-
|
|
324
|
-
expect(form.getFieldValue('names')).toStrictEqual(['test', 'other'])
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
it("should insert an array field's value as first element", () => {
|
|
328
|
-
const form = new FormApi({
|
|
329
|
-
defaultValues: {
|
|
330
|
-
names: ['one', 'two', 'three'],
|
|
331
|
-
},
|
|
332
|
-
})
|
|
333
|
-
form.mount()
|
|
334
|
-
form.insertFieldValue('names', 0, 'other')
|
|
335
|
-
|
|
336
|
-
expect(form.getFieldValue('names')).toStrictEqual([
|
|
337
|
-
'other',
|
|
338
|
-
'one',
|
|
339
|
-
'two',
|
|
340
|
-
'three',
|
|
341
|
-
])
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
it("should run onChange validation when pushing an array field's value", () => {
|
|
345
|
-
const form = new FormApi({
|
|
346
|
-
defaultValues: {
|
|
347
|
-
names: ['test'],
|
|
348
|
-
},
|
|
349
|
-
validators: {
|
|
350
|
-
onChange: ({ value }) =>
|
|
351
|
-
value.names.length > 3 ? undefined : 'At least 3 names are required',
|
|
352
|
-
},
|
|
353
|
-
})
|
|
354
|
-
form.mount()
|
|
355
|
-
// Since validation runs through the field, a field must be mounted for that array
|
|
356
|
-
new FieldApi({ form, name: 'names' }).mount()
|
|
357
|
-
|
|
358
|
-
form.pushFieldValue('names', 'other')
|
|
359
|
-
|
|
360
|
-
expect(form.state.errors).toStrictEqual(['At least 3 names are required'])
|
|
361
|
-
})
|
|
362
|
-
|
|
363
|
-
it("should insert an array field's value", () => {
|
|
364
|
-
const form = new FormApi({
|
|
365
|
-
defaultValues: {
|
|
366
|
-
names: ['one', 'two', 'three'],
|
|
367
|
-
},
|
|
368
|
-
})
|
|
369
|
-
form.mount()
|
|
370
|
-
form.insertFieldValue('names', 1, 'other')
|
|
371
|
-
|
|
372
|
-
expect(form.getFieldValue('names')).toStrictEqual([
|
|
373
|
-
'one',
|
|
374
|
-
'other',
|
|
375
|
-
'two',
|
|
376
|
-
'three',
|
|
377
|
-
])
|
|
378
|
-
})
|
|
379
|
-
|
|
380
|
-
it("should insert an array field's value at the end if the index is higher than the length", () => {
|
|
381
|
-
const form = new FormApi({
|
|
382
|
-
defaultValues: {
|
|
383
|
-
names: ['one', 'two', 'three'],
|
|
384
|
-
},
|
|
385
|
-
})
|
|
386
|
-
form.mount()
|
|
387
|
-
form.insertFieldValue('names', 10, 'other')
|
|
388
|
-
|
|
389
|
-
expect(form.getFieldValue('names')).toStrictEqual([
|
|
390
|
-
'one',
|
|
391
|
-
'two',
|
|
392
|
-
'three',
|
|
393
|
-
'other',
|
|
394
|
-
])
|
|
395
|
-
})
|
|
396
|
-
|
|
397
|
-
it("should replace an array field's value", () => {
|
|
398
|
-
const form = new FormApi({
|
|
399
|
-
defaultValues: {
|
|
400
|
-
names: ['one', 'two', 'three'],
|
|
401
|
-
},
|
|
402
|
-
})
|
|
403
|
-
form.mount()
|
|
404
|
-
form.replaceFieldValue('names', 1, 'other')
|
|
405
|
-
|
|
406
|
-
expect(form.getFieldValue('names')).toStrictEqual(['one', 'other', 'three'])
|
|
407
|
-
})
|
|
408
|
-
|
|
409
|
-
it("should do nothing when replacing an array field's value with an index that doesn't exist", () => {
|
|
410
|
-
const form = new FormApi({
|
|
411
|
-
defaultValues: {
|
|
412
|
-
names: ['one', 'two', 'three'],
|
|
413
|
-
},
|
|
414
|
-
})
|
|
415
|
-
form.mount()
|
|
416
|
-
form.replaceFieldValue('names', 10, 'other')
|
|
417
|
-
|
|
418
|
-
expect(form.getFieldValue('names')).toStrictEqual(['one', 'two', 'three'])
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
it("should run onChange validation when inserting an array field's value", () => {
|
|
422
|
-
const form = new FormApi({
|
|
423
|
-
defaultValues: {
|
|
424
|
-
names: ['test'],
|
|
425
|
-
},
|
|
426
|
-
validators: {
|
|
427
|
-
onChange: ({ value }) =>
|
|
428
|
-
value.names.length > 3 ? undefined : 'At least 3 names are required',
|
|
429
|
-
},
|
|
430
|
-
})
|
|
431
|
-
form.mount()
|
|
432
|
-
// Since validation runs through the field, a field must be mounted for that array
|
|
433
|
-
new FieldApi({ form, name: 'names' }).mount()
|
|
434
|
-
|
|
435
|
-
form.insertFieldValue('names', 1, 'other')
|
|
436
|
-
|
|
437
|
-
expect(form.state.errors).toStrictEqual(['At least 3 names are required'])
|
|
438
|
-
})
|
|
439
|
-
|
|
440
|
-
it("should validate all shifted fields when inserting an array field's value", async () => {
|
|
441
|
-
const form = new FormApi({
|
|
442
|
-
defaultValues: {
|
|
443
|
-
names: [{ first: 'test' }, { first: 'test2' }],
|
|
444
|
-
},
|
|
445
|
-
validators: {
|
|
446
|
-
onChange: ({ value }) =>
|
|
447
|
-
value.names.length > 3 ? undefined : 'At least 3 names are required',
|
|
448
|
-
},
|
|
449
|
-
})
|
|
450
|
-
form.mount()
|
|
451
|
-
// Since validation runs through the field, a field must be mounted for that array
|
|
452
|
-
new FieldApi({ form, name: 'names' }).mount()
|
|
453
|
-
|
|
454
|
-
const field1 = new FieldApi({
|
|
455
|
-
form,
|
|
456
|
-
name: 'names[0].first',
|
|
457
|
-
defaultValue: 'test',
|
|
458
|
-
validators: {
|
|
459
|
-
onChange: ({ value }) => value !== 'test' && 'Invalid value',
|
|
460
|
-
},
|
|
461
|
-
})
|
|
462
|
-
field1.mount()
|
|
463
|
-
|
|
464
|
-
expect(field1.state.meta.errors).toStrictEqual([])
|
|
465
|
-
|
|
466
|
-
await form.replaceFieldValue('names', 0, { first: 'other' })
|
|
467
|
-
|
|
468
|
-
expect(field1.state.meta.errors).toStrictEqual(['Invalid value'])
|
|
469
|
-
})
|
|
470
|
-
|
|
471
|
-
it("should remove an array field's value", () => {
|
|
472
|
-
const form = new FormApi({
|
|
473
|
-
defaultValues: {
|
|
474
|
-
names: ['one', 'two', 'three'],
|
|
475
|
-
},
|
|
476
|
-
})
|
|
477
|
-
form.mount()
|
|
478
|
-
form.removeFieldValue('names', 1)
|
|
479
|
-
|
|
480
|
-
expect(form.getFieldValue('names')).toStrictEqual(['one', 'three'])
|
|
481
|
-
})
|
|
482
|
-
|
|
483
|
-
it("should run onChange validation when removing an array field's value", () => {
|
|
484
|
-
const form = new FormApi({
|
|
485
|
-
defaultValues: {
|
|
486
|
-
names: ['test'],
|
|
487
|
-
},
|
|
488
|
-
validators: {
|
|
489
|
-
onChange: ({ value }) =>
|
|
490
|
-
value.names.length > 1 ? undefined : 'At least 1 name is required',
|
|
491
|
-
},
|
|
492
|
-
})
|
|
493
|
-
form.mount()
|
|
494
|
-
// Since validation runs through the field, a field must be mounted for that array
|
|
495
|
-
new FieldApi({ form, name: 'names' }).mount()
|
|
496
|
-
|
|
497
|
-
form.removeFieldValue('names', 0)
|
|
498
|
-
|
|
499
|
-
expect(form.state.errors).toStrictEqual(['At least 1 name is required'])
|
|
500
|
-
})
|
|
501
|
-
|
|
502
|
-
it("should validate following fields when removing an array field's value", async () => {
|
|
503
|
-
const form = new FormApi({
|
|
504
|
-
defaultValues: {
|
|
505
|
-
names: ['test', 'test2', 'test3'],
|
|
506
|
-
},
|
|
507
|
-
validators: {
|
|
508
|
-
onChange: ({ value }) =>
|
|
509
|
-
value.names.length > 1 ? undefined : 'At least 1 name is required',
|
|
510
|
-
},
|
|
511
|
-
})
|
|
512
|
-
form.mount()
|
|
513
|
-
// Since validation runs through the field, a field must be mounted for that array
|
|
514
|
-
new FieldApi({ form, name: 'names' }).mount()
|
|
515
|
-
|
|
516
|
-
const field1 = new FieldApi({
|
|
517
|
-
form,
|
|
518
|
-
name: 'names[0]',
|
|
519
|
-
defaultValue: 'test',
|
|
520
|
-
validators: {
|
|
521
|
-
onChange: ({ value }) => value !== 'test' && 'Invalid value',
|
|
522
|
-
},
|
|
523
|
-
})
|
|
524
|
-
field1.mount()
|
|
525
|
-
const field2 = new FieldApi({
|
|
526
|
-
form,
|
|
527
|
-
name: 'names[1]',
|
|
528
|
-
defaultValue: 'test2',
|
|
529
|
-
validators: {
|
|
530
|
-
onChange: ({ value }) => value !== 'test2' && 'Invalid value',
|
|
531
|
-
},
|
|
532
|
-
})
|
|
533
|
-
field2.mount()
|
|
534
|
-
const field3 = new FieldApi({
|
|
535
|
-
form,
|
|
536
|
-
name: 'names[2]',
|
|
537
|
-
defaultValue: 'test3',
|
|
538
|
-
validators: {
|
|
539
|
-
onChange: ({ value }) => value !== 'test3' && 'Invalid value',
|
|
540
|
-
},
|
|
541
|
-
})
|
|
542
|
-
field3.mount()
|
|
543
|
-
|
|
544
|
-
expect(field1.state.meta.errors).toStrictEqual([])
|
|
545
|
-
expect(field2.state.meta.errors).toStrictEqual([])
|
|
546
|
-
expect(field3.state.meta.errors).toStrictEqual([])
|
|
547
|
-
|
|
548
|
-
await form.removeFieldValue('names', 1)
|
|
549
|
-
|
|
550
|
-
expect(field1.state.meta.errors).toStrictEqual([])
|
|
551
|
-
expect(field2.state.meta.errors).toStrictEqual(['Invalid value'])
|
|
552
|
-
// This field does not exist anymore. Therefore, its validation should also not run
|
|
553
|
-
expect(field3.state.meta.errors).toStrictEqual([])
|
|
554
|
-
})
|
|
555
|
-
|
|
556
|
-
it("should swap an array field's value", () => {
|
|
557
|
-
const form = new FormApi({
|
|
558
|
-
defaultValues: {
|
|
559
|
-
names: ['one', 'two', 'three'],
|
|
560
|
-
},
|
|
561
|
-
})
|
|
562
|
-
form.mount()
|
|
563
|
-
// Since validation runs through the field, a field must be mounted for that array
|
|
564
|
-
new FieldApi({ form, name: 'names' }).mount()
|
|
565
|
-
|
|
566
|
-
form.swapFieldValues('names', 1, 2)
|
|
567
|
-
|
|
568
|
-
expect(form.getFieldValue('names')).toStrictEqual(['one', 'three', 'two'])
|
|
569
|
-
})
|
|
570
|
-
|
|
571
|
-
it("should run onChange validation when swapping an array field's value", () => {
|
|
572
|
-
const form = new FormApi({
|
|
573
|
-
defaultValues: {
|
|
574
|
-
names: ['test', 'test2'],
|
|
575
|
-
},
|
|
576
|
-
validators: {
|
|
577
|
-
onChange: ({ value }) =>
|
|
578
|
-
value.names.length > 3 ? undefined : 'At least 3 names are required',
|
|
579
|
-
},
|
|
580
|
-
})
|
|
581
|
-
form.mount()
|
|
582
|
-
// Since validation runs through the field, a field must be mounted for that array
|
|
583
|
-
new FieldApi({ form, name: 'names' }).mount()
|
|
584
|
-
expect(form.state.errors).toStrictEqual([])
|
|
585
|
-
|
|
586
|
-
form.swapFieldValues('names', 1, 2)
|
|
587
|
-
|
|
588
|
-
expect(form.state.errors).toStrictEqual(['At least 3 names are required'])
|
|
589
|
-
})
|
|
590
|
-
|
|
591
|
-
it('should run validation on swapped fields', () => {
|
|
592
|
-
const form = new FormApi({
|
|
593
|
-
defaultValues: {
|
|
594
|
-
names: ['test', 'test2'],
|
|
595
|
-
},
|
|
596
|
-
validators: {
|
|
597
|
-
onChange: ({ value }) =>
|
|
598
|
-
value.names.length > 3 ? undefined : 'At least 3 names are required',
|
|
599
|
-
},
|
|
600
|
-
})
|
|
601
|
-
form.mount()
|
|
602
|
-
// Since validation runs through the field, a field must be mounted for that array
|
|
603
|
-
new FieldApi({ form, name: 'names' }).mount()
|
|
604
|
-
|
|
605
|
-
const field1 = new FieldApi({
|
|
606
|
-
form,
|
|
607
|
-
name: 'names[0]',
|
|
608
|
-
defaultValue: 'test',
|
|
609
|
-
validators: {
|
|
610
|
-
onChange: ({ value }) => value !== 'test' && 'Invalid value',
|
|
611
|
-
},
|
|
612
|
-
})
|
|
613
|
-
field1.mount()
|
|
614
|
-
|
|
615
|
-
const field2 = new FieldApi({
|
|
616
|
-
form,
|
|
617
|
-
name: 'names[1]',
|
|
618
|
-
defaultValue: 'test2',
|
|
619
|
-
})
|
|
620
|
-
field2.mount()
|
|
621
|
-
|
|
622
|
-
expect(field1.state.meta.errors).toStrictEqual([])
|
|
623
|
-
expect(field2.state.meta.errors).toStrictEqual([])
|
|
624
|
-
|
|
625
|
-
form.swapFieldValues('names', 0, 1)
|
|
626
|
-
|
|
627
|
-
expect(field1.state.meta.errors).toStrictEqual(['Invalid value'])
|
|
628
|
-
expect(field2.state.meta.errors).toStrictEqual([])
|
|
629
|
-
})
|
|
630
|
-
|
|
631
|
-
it("should move an array field's value", () => {
|
|
632
|
-
const form = new FormApi({
|
|
633
|
-
defaultValues: {
|
|
634
|
-
names: ['one', 'two', 'three'],
|
|
635
|
-
},
|
|
636
|
-
})
|
|
637
|
-
form.mount()
|
|
638
|
-
form.moveFieldValues('names', 1, 2)
|
|
639
|
-
|
|
640
|
-
expect(form.getFieldValue('names')).toStrictEqual(['one', 'three', 'two'])
|
|
641
|
-
})
|
|
642
|
-
|
|
643
|
-
it("should run onChange validation when moving an array field's value", () => {
|
|
644
|
-
const form = new FormApi({
|
|
645
|
-
defaultValues: {
|
|
646
|
-
names: ['test', 'test2'],
|
|
647
|
-
},
|
|
648
|
-
validators: {
|
|
649
|
-
onChange: ({ value }) =>
|
|
650
|
-
value.names.length > 3 ? undefined : 'At least 3 names are required',
|
|
651
|
-
},
|
|
652
|
-
})
|
|
653
|
-
form.mount()
|
|
654
|
-
// Since validation runs through the field, a field must be mounted for that array
|
|
655
|
-
new FieldApi({ form, name: 'names' }).mount()
|
|
656
|
-
|
|
657
|
-
expect(form.state.errors).toStrictEqual([])
|
|
658
|
-
form.moveFieldValues('names', 0, 1)
|
|
659
|
-
|
|
660
|
-
expect(form.state.errors).toStrictEqual(['At least 3 names are required'])
|
|
661
|
-
})
|
|
662
|
-
|
|
663
|
-
it('should run validation on moved fields', () => {
|
|
664
|
-
const form = new FormApi({
|
|
665
|
-
defaultValues: {
|
|
666
|
-
names: ['test', 'test2'],
|
|
667
|
-
},
|
|
668
|
-
validators: {
|
|
669
|
-
onChange: ({ value }) =>
|
|
670
|
-
value.names.length > 3 ? undefined : 'At least 3 names are required',
|
|
671
|
-
},
|
|
672
|
-
})
|
|
673
|
-
form.mount()
|
|
674
|
-
// Since validation runs through the field, a field must be mounted for that array
|
|
675
|
-
new FieldApi({ form, name: 'names' }).mount()
|
|
676
|
-
|
|
677
|
-
const field1 = new FieldApi({
|
|
678
|
-
form,
|
|
679
|
-
name: 'names[0]',
|
|
680
|
-
defaultValue: 'test',
|
|
681
|
-
validators: {
|
|
682
|
-
onChange: ({ value }) => value !== 'test' && 'Invalid value',
|
|
683
|
-
},
|
|
684
|
-
})
|
|
685
|
-
field1.mount()
|
|
686
|
-
|
|
687
|
-
const field2 = new FieldApi({
|
|
688
|
-
form,
|
|
689
|
-
name: 'names[1]',
|
|
690
|
-
defaultValue: 'test2',
|
|
691
|
-
})
|
|
692
|
-
field2.mount()
|
|
693
|
-
|
|
694
|
-
expect(field1.state.meta.errors).toStrictEqual([])
|
|
695
|
-
expect(field2.state.meta.errors).toStrictEqual([])
|
|
696
|
-
|
|
697
|
-
form.swapFieldValues('names', 0, 1)
|
|
698
|
-
|
|
699
|
-
expect(field1.state.meta.errors).toStrictEqual(['Invalid value'])
|
|
700
|
-
expect(field2.state.meta.errors).toStrictEqual([])
|
|
701
|
-
})
|
|
702
|
-
|
|
703
|
-
it('should handle fields inside an array', async () => {
|
|
704
|
-
interface Employee {
|
|
705
|
-
firstName: string
|
|
706
|
-
}
|
|
707
|
-
interface Form {
|
|
708
|
-
employees: Partial<Employee>[]
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
const form = new FormApi<Form>()
|
|
712
|
-
|
|
713
|
-
const field = new FieldApi({
|
|
714
|
-
form,
|
|
715
|
-
name: 'employees',
|
|
716
|
-
defaultValue: [],
|
|
717
|
-
})
|
|
718
|
-
|
|
719
|
-
field.mount()
|
|
720
|
-
|
|
721
|
-
const fieldInArray = new FieldApi({
|
|
722
|
-
form,
|
|
723
|
-
name: `employees[0].firstName`,
|
|
724
|
-
defaultValue: 'Darcy',
|
|
725
|
-
})
|
|
726
|
-
fieldInArray.mount()
|
|
727
|
-
expect(field.state.value.length).toBe(1)
|
|
728
|
-
expect(fieldInArray.getValue()).toBe('Darcy')
|
|
729
|
-
})
|
|
730
|
-
|
|
731
|
-
it('should handle deleting fields in an array', async () => {
|
|
732
|
-
interface Employee {
|
|
733
|
-
firstName: string
|
|
734
|
-
}
|
|
735
|
-
interface Form {
|
|
736
|
-
employees: Partial<Employee>[]
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
const form = new FormApi<Form>()
|
|
740
|
-
|
|
741
|
-
const field = new FieldApi({
|
|
742
|
-
form,
|
|
743
|
-
name: 'employees',
|
|
744
|
-
defaultValue: [],
|
|
745
|
-
})
|
|
746
|
-
|
|
747
|
-
field.mount()
|
|
748
|
-
|
|
749
|
-
const fieldInArray = new FieldApi({
|
|
750
|
-
form,
|
|
751
|
-
name: `employees[0].firstName`,
|
|
752
|
-
defaultValue: 'Darcy',
|
|
753
|
-
})
|
|
754
|
-
fieldInArray.mount()
|
|
755
|
-
form.deleteField(`employees[0].firstName`)
|
|
756
|
-
expect(field.state.value.length).toBe(1)
|
|
757
|
-
expect(Object.keys(field.state.value[0]!).length).toBe(0)
|
|
758
|
-
})
|
|
759
|
-
|
|
760
|
-
it('should not wipe values when updating', () => {
|
|
761
|
-
const form = new FormApi({
|
|
762
|
-
defaultValues: {
|
|
763
|
-
name: 'test',
|
|
764
|
-
},
|
|
765
|
-
})
|
|
766
|
-
form.mount()
|
|
767
|
-
form.setFieldValue('name', 'other')
|
|
768
|
-
|
|
769
|
-
expect(form.getFieldValue('name')).toEqual('other')
|
|
770
|
-
|
|
771
|
-
form.update()
|
|
772
|
-
|
|
773
|
-
expect(form.getFieldValue('name')).toEqual('other')
|
|
774
|
-
})
|
|
775
|
-
|
|
776
|
-
it('should wipe default values when not touched', () => {
|
|
777
|
-
const form = new FormApi({
|
|
778
|
-
defaultValues: {
|
|
779
|
-
name: 'test',
|
|
780
|
-
},
|
|
781
|
-
})
|
|
782
|
-
form.mount()
|
|
783
|
-
expect(form.getFieldValue('name')).toEqual('test')
|
|
784
|
-
|
|
785
|
-
form.update({
|
|
786
|
-
defaultValues: {
|
|
787
|
-
name: 'other',
|
|
788
|
-
},
|
|
789
|
-
})
|
|
790
|
-
|
|
791
|
-
expect(form.getFieldValue('name')).toEqual('other')
|
|
792
|
-
})
|
|
793
|
-
|
|
794
|
-
it('should not wipe default values when touched', () => {
|
|
795
|
-
const form = new FormApi({
|
|
796
|
-
defaultValues: {
|
|
797
|
-
name: 'one',
|
|
798
|
-
},
|
|
799
|
-
})
|
|
800
|
-
form.mount()
|
|
801
|
-
expect(form.getFieldValue('name')).toEqual('one')
|
|
802
|
-
|
|
803
|
-
form.setFieldValue('name', 'two', { touch: true })
|
|
804
|
-
|
|
805
|
-
form.update({
|
|
806
|
-
defaultValues: {
|
|
807
|
-
name: 'three',
|
|
808
|
-
},
|
|
809
|
-
})
|
|
810
|
-
|
|
811
|
-
expect(form.getFieldValue('name')).toEqual('two')
|
|
812
|
-
})
|
|
813
|
-
|
|
814
|
-
it('should delete field from the form', () => {
|
|
815
|
-
const form = new FormApi({
|
|
816
|
-
defaultValues: {
|
|
817
|
-
names: 'kittu',
|
|
818
|
-
age: 4,
|
|
819
|
-
},
|
|
820
|
-
})
|
|
821
|
-
|
|
822
|
-
form.deleteField('names')
|
|
823
|
-
|
|
824
|
-
expect(form.getFieldValue('age')).toStrictEqual(4)
|
|
825
|
-
expect(form.getFieldValue('names')).toStrictEqual(undefined)
|
|
826
|
-
expect(form.getFieldMeta('names')).toStrictEqual(undefined)
|
|
827
|
-
})
|
|
828
|
-
|
|
829
|
-
it("form's valid state should be work fine", () => {
|
|
830
|
-
const form = new FormApi({
|
|
831
|
-
defaultValues: {
|
|
832
|
-
name: '',
|
|
833
|
-
},
|
|
834
|
-
})
|
|
835
|
-
|
|
836
|
-
const field = new FieldApi({
|
|
837
|
-
form,
|
|
838
|
-
name: 'name',
|
|
839
|
-
validators: {
|
|
840
|
-
onChange: ({ value }) => (value.length > 0 ? undefined : 'required'),
|
|
841
|
-
},
|
|
842
|
-
})
|
|
843
|
-
|
|
844
|
-
form.mount()
|
|
845
|
-
|
|
846
|
-
field.mount()
|
|
847
|
-
|
|
848
|
-
field.handleChange('one')
|
|
849
|
-
|
|
850
|
-
expect(form.state.isFieldsValid).toEqual(true)
|
|
851
|
-
expect(form.state.canSubmit).toEqual(true)
|
|
852
|
-
|
|
853
|
-
field.handleChange('')
|
|
854
|
-
|
|
855
|
-
expect(form.state.isFieldsValid).toEqual(false)
|
|
856
|
-
expect(form.state.canSubmit).toEqual(false)
|
|
857
|
-
|
|
858
|
-
field.handleChange('two')
|
|
859
|
-
|
|
860
|
-
expect(form.state.isFieldsValid).toEqual(true)
|
|
861
|
-
expect(form.state.canSubmit).toEqual(true)
|
|
862
|
-
})
|
|
863
|
-
|
|
864
|
-
it('should run validation onChange', () => {
|
|
865
|
-
const form = new FormApi({
|
|
866
|
-
defaultValues: {
|
|
867
|
-
name: 'test',
|
|
868
|
-
},
|
|
869
|
-
validators: {
|
|
870
|
-
onChange: ({ value }) => {
|
|
871
|
-
if (value.name === 'other') return 'Please enter a different value'
|
|
872
|
-
return
|
|
873
|
-
},
|
|
874
|
-
},
|
|
875
|
-
})
|
|
876
|
-
|
|
877
|
-
const field = new FieldApi({
|
|
878
|
-
form,
|
|
879
|
-
name: 'name',
|
|
880
|
-
})
|
|
881
|
-
form.mount()
|
|
882
|
-
field.mount()
|
|
883
|
-
|
|
884
|
-
expect(form.state.errors.length).toBe(0)
|
|
885
|
-
field.setValue('other', { touch: true })
|
|
886
|
-
expect(form.state.errors).toContain('Please enter a different value')
|
|
887
|
-
expect(form.state.errorMap).toMatchObject({
|
|
888
|
-
onChange: 'Please enter a different value',
|
|
889
|
-
})
|
|
890
|
-
})
|
|
891
|
-
|
|
892
|
-
it('should run async validation onChange', async () => {
|
|
893
|
-
vi.useFakeTimers()
|
|
894
|
-
|
|
895
|
-
const form = new FormApi({
|
|
896
|
-
defaultValues: {
|
|
897
|
-
name: 'test',
|
|
898
|
-
},
|
|
899
|
-
validators: {
|
|
900
|
-
onChangeAsync: async ({ value }) => {
|
|
901
|
-
await sleep(1000)
|
|
902
|
-
if (value.name === 'other') return 'Please enter a different value'
|
|
903
|
-
return
|
|
904
|
-
},
|
|
905
|
-
},
|
|
906
|
-
})
|
|
907
|
-
const field = new FieldApi({
|
|
908
|
-
form,
|
|
909
|
-
name: 'name',
|
|
910
|
-
})
|
|
911
|
-
form.mount()
|
|
912
|
-
|
|
913
|
-
field.mount()
|
|
914
|
-
|
|
915
|
-
expect(form.state.errors.length).toBe(0)
|
|
916
|
-
field.setValue('other', { touch: true })
|
|
917
|
-
await vi.runAllTimersAsync()
|
|
918
|
-
expect(form.state.errors).toContain('Please enter a different value')
|
|
919
|
-
expect(form.state.errorMap).toMatchObject({
|
|
920
|
-
onChange: 'Please enter a different value',
|
|
921
|
-
})
|
|
922
|
-
})
|
|
923
|
-
|
|
924
|
-
it('should run async validation onChange with debounce', async () => {
|
|
925
|
-
vi.useFakeTimers()
|
|
926
|
-
const sleepMock = vi.fn().mockImplementation(sleep)
|
|
927
|
-
|
|
928
|
-
const form = new FormApi({
|
|
929
|
-
defaultValues: {
|
|
930
|
-
name: 'test',
|
|
931
|
-
},
|
|
932
|
-
validators: {
|
|
933
|
-
onChangeAsyncDebounceMs: 1000,
|
|
934
|
-
onChangeAsync: async ({ value }) => {
|
|
935
|
-
await sleepMock(1000)
|
|
936
|
-
if (value.name === 'other') return 'Please enter a different value'
|
|
937
|
-
return
|
|
938
|
-
},
|
|
939
|
-
},
|
|
940
|
-
})
|
|
941
|
-
const field = new FieldApi({
|
|
942
|
-
form,
|
|
943
|
-
name: 'name',
|
|
944
|
-
})
|
|
945
|
-
form.mount()
|
|
946
|
-
|
|
947
|
-
field.mount()
|
|
948
|
-
|
|
949
|
-
expect(form.state.errors.length).toBe(0)
|
|
950
|
-
field.setValue('other', { touch: true })
|
|
951
|
-
field.setValue('other')
|
|
952
|
-
await vi.runAllTimersAsync()
|
|
953
|
-
// sleepMock will have been called 2 times without onChangeAsyncDebounceMs
|
|
954
|
-
expect(sleepMock).toHaveBeenCalledTimes(1)
|
|
955
|
-
expect(form.state.errors).toContain('Please enter a different value')
|
|
956
|
-
expect(form.state.errorMap).toMatchObject({
|
|
957
|
-
onChange: 'Please enter a different value',
|
|
958
|
-
})
|
|
959
|
-
})
|
|
960
|
-
|
|
961
|
-
it('should run async validation onChange with asyncDebounceMs', async () => {
|
|
962
|
-
vi.useFakeTimers()
|
|
963
|
-
const sleepMock = vi.fn().mockImplementation(sleep)
|
|
964
|
-
|
|
965
|
-
const form = new FormApi({
|
|
966
|
-
defaultValues: {
|
|
967
|
-
name: 'test',
|
|
968
|
-
},
|
|
969
|
-
asyncDebounceMs: 1000,
|
|
970
|
-
validators: {
|
|
971
|
-
onChangeAsync: async ({ value }) => {
|
|
972
|
-
await sleepMock(1000)
|
|
973
|
-
if (value.name === 'other') return 'Please enter a different value'
|
|
974
|
-
return
|
|
975
|
-
},
|
|
976
|
-
},
|
|
977
|
-
})
|
|
978
|
-
const field = new FieldApi({
|
|
979
|
-
form,
|
|
980
|
-
name: 'name',
|
|
981
|
-
})
|
|
982
|
-
|
|
983
|
-
form.mount()
|
|
984
|
-
field.mount()
|
|
985
|
-
|
|
986
|
-
expect(form.state.errors.length).toBe(0)
|
|
987
|
-
field.setValue('other', { touch: true })
|
|
988
|
-
field.setValue('other')
|
|
989
|
-
await vi.runAllTimersAsync()
|
|
990
|
-
// sleepMock will have been called 2 times without asyncDebounceMs
|
|
991
|
-
expect(sleepMock).toHaveBeenCalledTimes(1)
|
|
992
|
-
expect(form.state.errors).toContain('Please enter a different value')
|
|
993
|
-
expect(form.state.errorMap).toMatchObject({
|
|
994
|
-
onChange: 'Please enter a different value',
|
|
995
|
-
})
|
|
996
|
-
})
|
|
997
|
-
|
|
998
|
-
it('should run validation onBlur', () => {
|
|
999
|
-
const form = new FormApi({
|
|
1000
|
-
defaultValues: {
|
|
1001
|
-
name: 'other',
|
|
1002
|
-
},
|
|
1003
|
-
validators: {
|
|
1004
|
-
onBlur: ({ value }) => {
|
|
1005
|
-
if (value.name === 'other') return 'Please enter a different value'
|
|
1006
|
-
return
|
|
1007
|
-
},
|
|
1008
|
-
},
|
|
1009
|
-
})
|
|
1010
|
-
const field = new FieldApi({
|
|
1011
|
-
form,
|
|
1012
|
-
name: 'name',
|
|
1013
|
-
})
|
|
1014
|
-
|
|
1015
|
-
form.mount()
|
|
1016
|
-
field.mount()
|
|
1017
|
-
|
|
1018
|
-
field.setValue('other', { touch: true })
|
|
1019
|
-
field.validate('blur')
|
|
1020
|
-
expect(form.state.errors).toContain('Please enter a different value')
|
|
1021
|
-
expect(form.state.errorMap).toMatchObject({
|
|
1022
|
-
onBlur: 'Please enter a different value',
|
|
1023
|
-
})
|
|
1024
|
-
})
|
|
1025
|
-
|
|
1026
|
-
it('should run async validation onBlur', async () => {
|
|
1027
|
-
vi.useFakeTimers()
|
|
1028
|
-
|
|
1029
|
-
const form = new FormApi({
|
|
1030
|
-
defaultValues: {
|
|
1031
|
-
name: 'test',
|
|
1032
|
-
},
|
|
1033
|
-
validators: {
|
|
1034
|
-
onBlurAsync: async ({ value }) => {
|
|
1035
|
-
await sleep(1000)
|
|
1036
|
-
if (value.name === 'other') return 'Please enter a different value'
|
|
1037
|
-
return
|
|
1038
|
-
},
|
|
1039
|
-
},
|
|
1040
|
-
})
|
|
1041
|
-
const field = new FieldApi({
|
|
1042
|
-
form,
|
|
1043
|
-
name: 'name',
|
|
1044
|
-
})
|
|
1045
|
-
|
|
1046
|
-
form.mount()
|
|
1047
|
-
field.mount()
|
|
1048
|
-
expect(form.state.errors.length).toBe(0)
|
|
1049
|
-
field.setValue('other', { touch: true })
|
|
1050
|
-
field.validate('blur')
|
|
1051
|
-
await vi.runAllTimersAsync()
|
|
1052
|
-
expect(form.state.errors).toContain('Please enter a different value')
|
|
1053
|
-
expect(form.state.errorMap).toMatchObject({
|
|
1054
|
-
onBlur: 'Please enter a different value',
|
|
1055
|
-
})
|
|
1056
|
-
})
|
|
1057
|
-
|
|
1058
|
-
it('should run async validation onBlur with debounce', async () => {
|
|
1059
|
-
vi.useFakeTimers()
|
|
1060
|
-
const sleepMock = vi.fn().mockImplementation(sleep)
|
|
1061
|
-
|
|
1062
|
-
const form = new FormApi({
|
|
1063
|
-
defaultValues: {
|
|
1064
|
-
name: 'test',
|
|
1065
|
-
},
|
|
1066
|
-
validators: {
|
|
1067
|
-
onBlurAsyncDebounceMs: 1000,
|
|
1068
|
-
onBlurAsync: async ({ value }) => {
|
|
1069
|
-
await sleepMock(10)
|
|
1070
|
-
if (value.name === 'other') return 'Please enter a different value'
|
|
1071
|
-
return
|
|
1072
|
-
},
|
|
1073
|
-
},
|
|
1074
|
-
})
|
|
1075
|
-
const field = new FieldApi({
|
|
1076
|
-
form,
|
|
1077
|
-
name: 'name',
|
|
1078
|
-
})
|
|
1079
|
-
|
|
1080
|
-
form.mount()
|
|
1081
|
-
field.mount()
|
|
1082
|
-
|
|
1083
|
-
expect(form.state.errors.length).toBe(0)
|
|
1084
|
-
field.setValue('other', { touch: true })
|
|
1085
|
-
field.validate('blur')
|
|
1086
|
-
field.validate('blur')
|
|
1087
|
-
await vi.runAllTimersAsync()
|
|
1088
|
-
// sleepMock will have been called 2 times without onBlurAsyncDebounceMs
|
|
1089
|
-
expect(sleepMock).toHaveBeenCalledTimes(1)
|
|
1090
|
-
expect(form.state.errors).toContain('Please enter a different value')
|
|
1091
|
-
expect(form.state.errorMap).toMatchObject({
|
|
1092
|
-
onBlur: 'Please enter a different value',
|
|
1093
|
-
})
|
|
1094
|
-
})
|
|
1095
|
-
|
|
1096
|
-
it('should run async validation onBlur with asyncDebounceMs', async () => {
|
|
1097
|
-
vi.useFakeTimers()
|
|
1098
|
-
const sleepMock = vi.fn().mockImplementation(sleep)
|
|
1099
|
-
|
|
1100
|
-
const form = new FormApi({
|
|
1101
|
-
defaultValues: {
|
|
1102
|
-
name: 'test',
|
|
1103
|
-
},
|
|
1104
|
-
asyncDebounceMs: 1000,
|
|
1105
|
-
validators: {
|
|
1106
|
-
onBlurAsync: async ({ value }) => {
|
|
1107
|
-
await sleepMock(10)
|
|
1108
|
-
if (value.name === 'other') return 'Please enter a different value'
|
|
1109
|
-
return
|
|
1110
|
-
},
|
|
1111
|
-
},
|
|
1112
|
-
})
|
|
1113
|
-
const field = new FieldApi({
|
|
1114
|
-
form,
|
|
1115
|
-
name: 'name',
|
|
1116
|
-
})
|
|
1117
|
-
|
|
1118
|
-
form.mount()
|
|
1119
|
-
field.mount()
|
|
1120
|
-
|
|
1121
|
-
expect(form.state.errors.length).toBe(0)
|
|
1122
|
-
field.setValue('other', { touch: true })
|
|
1123
|
-
field.validate('blur')
|
|
1124
|
-
field.validate('blur')
|
|
1125
|
-
await vi.runAllTimersAsync()
|
|
1126
|
-
// sleepMock will have been called 2 times without asyncDebounceMs
|
|
1127
|
-
expect(sleepMock).toHaveBeenCalledTimes(1)
|
|
1128
|
-
expect(form.state.errors).toContain('Please enter a different value')
|
|
1129
|
-
expect(form.state.errorMap).toMatchObject({
|
|
1130
|
-
onBlur: 'Please enter a different value',
|
|
1131
|
-
})
|
|
1132
|
-
})
|
|
1133
|
-
|
|
1134
|
-
it('should contain multiple errors when running validation onBlur and onChange', () => {
|
|
1135
|
-
const form = new FormApi({
|
|
1136
|
-
defaultValues: {
|
|
1137
|
-
name: 'other',
|
|
1138
|
-
},
|
|
1139
|
-
validators: {
|
|
1140
|
-
onBlur: ({ value }) => {
|
|
1141
|
-
if (value.name === 'other') return 'Please enter a different value'
|
|
1142
|
-
return
|
|
1143
|
-
},
|
|
1144
|
-
onChange: ({ value }) => {
|
|
1145
|
-
if (value.name === 'other') return 'Please enter a different value'
|
|
1146
|
-
return
|
|
1147
|
-
},
|
|
1148
|
-
},
|
|
1149
|
-
})
|
|
1150
|
-
const field = new FieldApi({
|
|
1151
|
-
form,
|
|
1152
|
-
name: 'name',
|
|
1153
|
-
})
|
|
1154
|
-
|
|
1155
|
-
form.mount()
|
|
1156
|
-
field.mount()
|
|
1157
|
-
|
|
1158
|
-
field.setValue('other', { touch: true })
|
|
1159
|
-
field.validate('blur')
|
|
1160
|
-
expect(form.state.errors).toStrictEqual([
|
|
1161
|
-
'Please enter a different value',
|
|
1162
|
-
'Please enter a different value',
|
|
1163
|
-
])
|
|
1164
|
-
expect(form.state.errorMap).toEqual({
|
|
1165
|
-
onBlur: 'Please enter a different value',
|
|
1166
|
-
onChange: 'Please enter a different value',
|
|
1167
|
-
})
|
|
1168
|
-
})
|
|
1169
|
-
|
|
1170
|
-
it('should reset onChange errors when the issue is resolved', () => {
|
|
1171
|
-
const form = new FormApi({
|
|
1172
|
-
defaultValues: {
|
|
1173
|
-
name: 'other',
|
|
1174
|
-
},
|
|
1175
|
-
validators: {
|
|
1176
|
-
onChange: ({ value }) => {
|
|
1177
|
-
if (value.name === 'other') return 'Please enter a different value'
|
|
1178
|
-
return
|
|
1179
|
-
},
|
|
1180
|
-
},
|
|
1181
|
-
})
|
|
1182
|
-
const field = new FieldApi({
|
|
1183
|
-
form,
|
|
1184
|
-
name: 'name',
|
|
1185
|
-
})
|
|
1186
|
-
|
|
1187
|
-
form.mount()
|
|
1188
|
-
field.mount()
|
|
1189
|
-
|
|
1190
|
-
field.setValue('other', { touch: true })
|
|
1191
|
-
expect(form.state.errors).toStrictEqual(['Please enter a different value'])
|
|
1192
|
-
expect(form.state.errorMap).toEqual({
|
|
1193
|
-
onChange: 'Please enter a different value',
|
|
1194
|
-
})
|
|
1195
|
-
field.setValue('test', { touch: true })
|
|
1196
|
-
expect(form.state.errors).toStrictEqual([])
|
|
1197
|
-
expect(form.state.errorMap).toEqual({})
|
|
1198
|
-
})
|
|
1199
|
-
|
|
1200
|
-
it('should return error onMount', () => {
|
|
1201
|
-
const form = new FormApi({
|
|
1202
|
-
defaultValues: {
|
|
1203
|
-
name: 'other',
|
|
1204
|
-
},
|
|
1205
|
-
validators: {
|
|
1206
|
-
onMount: ({ value }) => {
|
|
1207
|
-
if (value.name === 'other') return 'Please enter a different value'
|
|
1208
|
-
return
|
|
1209
|
-
},
|
|
1210
|
-
},
|
|
1211
|
-
})
|
|
1212
|
-
const field = new FieldApi({
|
|
1213
|
-
form,
|
|
1214
|
-
name: 'name',
|
|
1215
|
-
})
|
|
1216
|
-
|
|
1217
|
-
form.mount()
|
|
1218
|
-
field.mount()
|
|
1219
|
-
|
|
1220
|
-
expect(form.state.errors).toStrictEqual(['Please enter a different value'])
|
|
1221
|
-
expect(form.state.errorMap).toEqual({
|
|
1222
|
-
onMount: 'Please enter a different value',
|
|
1223
|
-
})
|
|
1224
|
-
})
|
|
1225
|
-
|
|
1226
|
-
it('should validate fields during submit', async () => {
|
|
1227
|
-
const form = new FormApi({
|
|
1228
|
-
defaultValues: {
|
|
1229
|
-
firstName: '',
|
|
1230
|
-
lastName: '',
|
|
1231
|
-
},
|
|
1232
|
-
})
|
|
1233
|
-
|
|
1234
|
-
const field = new FieldApi({
|
|
1235
|
-
form,
|
|
1236
|
-
name: 'firstName',
|
|
1237
|
-
validators: {
|
|
1238
|
-
onChange: ({ value }) =>
|
|
1239
|
-
value.length > 0 ? undefined : 'first name is required',
|
|
1240
|
-
},
|
|
1241
|
-
})
|
|
1242
|
-
|
|
1243
|
-
const lastNameField = new FieldApi({
|
|
1244
|
-
form,
|
|
1245
|
-
name: 'lastName',
|
|
1246
|
-
validators: {
|
|
1247
|
-
onChange: ({ value }) =>
|
|
1248
|
-
value.length > 0 ? undefined : 'last name is required',
|
|
1249
|
-
},
|
|
1250
|
-
})
|
|
1251
|
-
|
|
1252
|
-
field.mount()
|
|
1253
|
-
lastNameField.mount()
|
|
1254
|
-
|
|
1255
|
-
await form.handleSubmit()
|
|
1256
|
-
expect(form.state.isFieldsValid).toEqual(false)
|
|
1257
|
-
expect(form.state.canSubmit).toEqual(false)
|
|
1258
|
-
expect(form.state.fieldMeta['firstName'].errors).toEqual([
|
|
1259
|
-
'first name is required',
|
|
1260
|
-
])
|
|
1261
|
-
expect(form.state.fieldMeta['lastName'].errors).toEqual([
|
|
1262
|
-
'last name is required',
|
|
1263
|
-
])
|
|
1264
|
-
})
|
|
1265
|
-
|
|
1266
|
-
it('should validate optional object fields during submit', async () => {
|
|
1267
|
-
const form = new FormApi({
|
|
1268
|
-
defaultValues: {
|
|
1269
|
-
person: null,
|
|
1270
|
-
} as { person: { firstName: string; lastName: string } | null },
|
|
1271
|
-
})
|
|
1272
|
-
|
|
1273
|
-
const field = new FieldApi({
|
|
1274
|
-
form,
|
|
1275
|
-
name: 'person.firstName',
|
|
1276
|
-
validators: {
|
|
1277
|
-
onChange: ({ value }) =>
|
|
1278
|
-
value && value.length > 0 ? undefined : 'first name is required',
|
|
1279
|
-
},
|
|
1280
|
-
})
|
|
1281
|
-
|
|
1282
|
-
const lastNameField = new FieldApi({
|
|
1283
|
-
form,
|
|
1284
|
-
name: 'person.lastName',
|
|
1285
|
-
validators: {
|
|
1286
|
-
onChange: ({ value }) =>
|
|
1287
|
-
value && value.length > 0 ? undefined : 'last name is required',
|
|
1288
|
-
},
|
|
1289
|
-
})
|
|
1290
|
-
|
|
1291
|
-
field.mount()
|
|
1292
|
-
lastNameField.mount()
|
|
1293
|
-
|
|
1294
|
-
await form.handleSubmit()
|
|
1295
|
-
expect(form.state.isFieldsValid).toEqual(false)
|
|
1296
|
-
expect(form.state.canSubmit).toEqual(false)
|
|
1297
|
-
expect(form.state.fieldMeta['person.firstName'].errors).toEqual([
|
|
1298
|
-
'first name is required',
|
|
1299
|
-
])
|
|
1300
|
-
expect(form.state.fieldMeta['person.lastName'].errors).toEqual([
|
|
1301
|
-
'last name is required',
|
|
1302
|
-
])
|
|
1303
|
-
})
|
|
1304
|
-
|
|
1305
|
-
it('should run all types of validation on fields during submit', async () => {
|
|
1306
|
-
const form = new FormApi({
|
|
1307
|
-
defaultValues: {
|
|
1308
|
-
firstName: '',
|
|
1309
|
-
lastName: '',
|
|
1310
|
-
},
|
|
1311
|
-
})
|
|
1312
|
-
|
|
1313
|
-
const field = new FieldApi({
|
|
1314
|
-
form,
|
|
1315
|
-
name: 'firstName',
|
|
1316
|
-
validators: {
|
|
1317
|
-
onChange: ({ value }) =>
|
|
1318
|
-
value.length > 0 ? undefined : 'first name is required',
|
|
1319
|
-
onBlur: ({ value }) =>
|
|
1320
|
-
value.length > 3
|
|
1321
|
-
? undefined
|
|
1322
|
-
: 'first name must be longer than 3 characters',
|
|
1323
|
-
},
|
|
1324
|
-
})
|
|
1325
|
-
|
|
1326
|
-
field.mount()
|
|
1327
|
-
|
|
1328
|
-
await form.handleSubmit()
|
|
1329
|
-
expect(form.state.isFieldsValid).toEqual(false)
|
|
1330
|
-
expect(form.state.canSubmit).toEqual(false)
|
|
1331
|
-
expect(form.state.fieldMeta['firstName'].errors).toEqual([
|
|
1332
|
-
'first name is required',
|
|
1333
|
-
'first name must be longer than 3 characters',
|
|
1334
|
-
])
|
|
1335
|
-
})
|
|
1336
|
-
|
|
1337
|
-
it('should clear onSubmit error when a valid value is entered', async () => {
|
|
1338
|
-
const form = new FormApi({
|
|
1339
|
-
defaultValues: {
|
|
1340
|
-
firstName: '',
|
|
1341
|
-
},
|
|
1342
|
-
})
|
|
1343
|
-
|
|
1344
|
-
const field = new FieldApi({
|
|
1345
|
-
form,
|
|
1346
|
-
name: 'firstName',
|
|
1347
|
-
validators: {
|
|
1348
|
-
onSubmit: ({ value }) =>
|
|
1349
|
-
value.length > 0 ? undefined : 'first name is required',
|
|
1350
|
-
},
|
|
1351
|
-
})
|
|
1352
|
-
|
|
1353
|
-
field.mount()
|
|
1354
|
-
|
|
1355
|
-
await form.handleSubmit()
|
|
1356
|
-
expect(form.state.isFieldsValid).toEqual(false)
|
|
1357
|
-
expect(form.state.canSubmit).toEqual(false)
|
|
1358
|
-
expect(form.state.fieldMeta['firstName'].errorMap['onSubmit']).toEqual(
|
|
1359
|
-
'first name is required',
|
|
1360
|
-
)
|
|
1361
|
-
field.handleChange('test')
|
|
1362
|
-
expect(form.state.isFieldsValid).toEqual(true)
|
|
1363
|
-
expect(form.state.canSubmit).toEqual(true)
|
|
1364
|
-
expect(
|
|
1365
|
-
form.state.fieldMeta['firstName'].errorMap['onSubmit'],
|
|
1366
|
-
).toBeUndefined()
|
|
1367
|
-
})
|
|
1368
|
-
|
|
1369
|
-
it('should validate all fields consistently', async () => {
|
|
1370
|
-
const form = new FormApi({
|
|
1371
|
-
defaultValues: {
|
|
1372
|
-
firstName: '',
|
|
1373
|
-
lastName: '',
|
|
1374
|
-
},
|
|
1375
|
-
})
|
|
1376
|
-
|
|
1377
|
-
const field = new FieldApi({
|
|
1378
|
-
form,
|
|
1379
|
-
name: 'firstName',
|
|
1380
|
-
validators: {
|
|
1381
|
-
onChange: ({ value }) =>
|
|
1382
|
-
value.length > 0 ? undefined : 'first name is required',
|
|
1383
|
-
},
|
|
1384
|
-
})
|
|
1385
|
-
|
|
1386
|
-
field.mount()
|
|
1387
|
-
form.mount()
|
|
1388
|
-
|
|
1389
|
-
await form.validateAllFields('change')
|
|
1390
|
-
expect(field.getMeta().errorMap.onChange).toEqual('first name is required')
|
|
1391
|
-
await form.validateAllFields('change')
|
|
1392
|
-
expect(field.getMeta().errorMap.onChange).toEqual('first name is required')
|
|
1393
|
-
})
|
|
1394
|
-
|
|
1395
|
-
it('should validate a single field consistently if touched', async () => {
|
|
1396
|
-
const form = new FormApi({
|
|
1397
|
-
defaultValues: {
|
|
1398
|
-
firstName: '',
|
|
1399
|
-
lastName: '',
|
|
1400
|
-
},
|
|
1401
|
-
})
|
|
1402
|
-
|
|
1403
|
-
const field = new FieldApi({
|
|
1404
|
-
form,
|
|
1405
|
-
name: 'firstName',
|
|
1406
|
-
validators: {
|
|
1407
|
-
onChange: ({ value }) =>
|
|
1408
|
-
value.length > 0 ? undefined : 'first name is required',
|
|
1409
|
-
},
|
|
1410
|
-
defaultMeta: {
|
|
1411
|
-
isTouched: true,
|
|
1412
|
-
},
|
|
1413
|
-
})
|
|
1414
|
-
|
|
1415
|
-
field.mount()
|
|
1416
|
-
form.mount()
|
|
1417
|
-
|
|
1418
|
-
await form.validateField('firstName', 'change')
|
|
1419
|
-
expect(field.getMeta().errorMap.onChange).toEqual('first name is required')
|
|
1420
|
-
await form.validateField('firstName', 'change')
|
|
1421
|
-
expect(field.getMeta().errorMap.onChange).toEqual('first name is required')
|
|
1422
|
-
})
|
|
1423
|
-
|
|
1424
|
-
it('should show onSubmit errors', async () => {
|
|
1425
|
-
const form = new FormApi({
|
|
1426
|
-
defaultValues: {
|
|
1427
|
-
firstName: '',
|
|
1428
|
-
},
|
|
1429
|
-
validators: {
|
|
1430
|
-
onSubmit: ({ value }) =>
|
|
1431
|
-
value.firstName.length > 0 ? undefined : 'first name is required',
|
|
1432
|
-
},
|
|
1433
|
-
})
|
|
1434
|
-
|
|
1435
|
-
const field = new FieldApi({
|
|
1436
|
-
form,
|
|
1437
|
-
name: 'firstName',
|
|
1438
|
-
})
|
|
1439
|
-
|
|
1440
|
-
field.mount()
|
|
1441
|
-
|
|
1442
|
-
await form.handleSubmit()
|
|
1443
|
-
expect(form.state.errors).toStrictEqual(['first name is required'])
|
|
1444
|
-
})
|
|
1445
|
-
|
|
1446
|
-
it('should run onChange validation during submit', async () => {
|
|
1447
|
-
const form = new FormApi({
|
|
1448
|
-
defaultValues: {
|
|
1449
|
-
firstName: '',
|
|
1450
|
-
},
|
|
1451
|
-
validators: {
|
|
1452
|
-
onChange: ({ value }) =>
|
|
1453
|
-
value.firstName.length > 0 ? undefined : 'first name is required',
|
|
1454
|
-
},
|
|
1455
|
-
})
|
|
1456
|
-
|
|
1457
|
-
const field = new FieldApi({
|
|
1458
|
-
form,
|
|
1459
|
-
name: 'firstName',
|
|
1460
|
-
})
|
|
1461
|
-
|
|
1462
|
-
field.mount()
|
|
1463
|
-
|
|
1464
|
-
await form.handleSubmit()
|
|
1465
|
-
expect(form.state.errors).toStrictEqual(['first name is required'])
|
|
1466
|
-
})
|
|
1467
|
-
|
|
1468
|
-
it('should update a nullable object', async () => {
|
|
1469
|
-
const form = new FormApi({
|
|
1470
|
-
defaultValues: {
|
|
1471
|
-
person: null,
|
|
1472
|
-
} as { person: { firstName: string } | null },
|
|
1473
|
-
})
|
|
1474
|
-
|
|
1475
|
-
const field = new FieldApi({
|
|
1476
|
-
form,
|
|
1477
|
-
name: 'person.firstName',
|
|
1478
|
-
})
|
|
1479
|
-
|
|
1480
|
-
field.mount()
|
|
1481
|
-
|
|
1482
|
-
field.setValue('firstName')
|
|
1483
|
-
expect(form.state.values.person?.firstName).toStrictEqual('firstName')
|
|
1484
|
-
})
|
|
1485
|
-
|
|
1486
|
-
it('should update a deep nullable object', async () => {
|
|
1487
|
-
const form = new FormApi({
|
|
1488
|
-
defaultValues: {
|
|
1489
|
-
person: null,
|
|
1490
|
-
} as { person: { nameInfo: { first: string } | null } | null },
|
|
1491
|
-
})
|
|
1492
|
-
|
|
1493
|
-
const field = new FieldApi({
|
|
1494
|
-
form,
|
|
1495
|
-
name: 'person.nameInfo.first',
|
|
1496
|
-
})
|
|
1497
|
-
|
|
1498
|
-
field.mount()
|
|
1499
|
-
|
|
1500
|
-
field.setValue('firstName')
|
|
1501
|
-
expect(form.state.values.person?.nameInfo?.first).toStrictEqual('firstName')
|
|
1502
|
-
})
|
|
1503
|
-
|
|
1504
|
-
it('should update a nullable array', async () => {
|
|
1505
|
-
const form = new FormApi({
|
|
1506
|
-
defaultValues: {
|
|
1507
|
-
persons: null,
|
|
1508
|
-
} as { persons: Array<{ nameInfo: { first: string } }> | null },
|
|
1509
|
-
})
|
|
1510
|
-
|
|
1511
|
-
const field = new FieldApi({
|
|
1512
|
-
form,
|
|
1513
|
-
name: 'persons',
|
|
1514
|
-
})
|
|
1515
|
-
|
|
1516
|
-
field.mount()
|
|
1517
|
-
|
|
1518
|
-
field.pushValue({ nameInfo: { first: 'firstName' } })
|
|
1519
|
-
expect(form.state.values.persons).toStrictEqual([
|
|
1520
|
-
{ nameInfo: { first: 'firstName' } },
|
|
1521
|
-
])
|
|
1522
|
-
})
|
|
1523
|
-
})
|