@tanstack/form-core 0.1.3 → 0.3.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/package.json +23 -12
- package/src/FieldApi.ts +53 -29
- package/src/FormApi.ts +14 -7
- package/src/tests/FieldApi.spec.ts +84 -17
- package/src/utils.ts +4 -0
- package/build/lib/FieldApi.cjs +0 -293
- package/build/lib/FieldApi.cjs.map +0 -1
- package/build/lib/FieldApi.d.ts +0 -95
- package/build/lib/FieldApi.d.ts.map +0 -1
- package/build/lib/FieldApi.js +0 -291
- package/build/lib/FieldApi.js.map +0 -1
- package/build/lib/FieldApi.legacy.cjs +0 -293
- package/build/lib/FieldApi.legacy.cjs.map +0 -1
- package/build/lib/FieldApi.legacy.js +0 -291
- package/build/lib/FieldApi.legacy.js.map +0 -1
- package/build/lib/FormApi.cjs +0 -239
- package/build/lib/FormApi.cjs.map +0 -1
- package/build/lib/FormApi.d.ts +0 -78
- package/build/lib/FormApi.d.ts.map +0 -1
- package/build/lib/FormApi.js +0 -237
- package/build/lib/FormApi.js.map +0 -1
- package/build/lib/FormApi.legacy.cjs +0 -239
- package/build/lib/FormApi.legacy.cjs.map +0 -1
- package/build/lib/FormApi.legacy.js +0 -237
- package/build/lib/FormApi.legacy.js.map +0 -1
- package/build/lib/_virtual/_rollupPluginBabelHelpers.cjs +0 -65
- package/build/lib/_virtual/_rollupPluginBabelHelpers.cjs.map +0 -1
- package/build/lib/_virtual/_rollupPluginBabelHelpers.js +0 -56
- package/build/lib/_virtual/_rollupPluginBabelHelpers.js.map +0 -1
- package/build/lib/_virtual/_rollupPluginBabelHelpers.legacy.cjs +0 -65
- package/build/lib/_virtual/_rollupPluginBabelHelpers.legacy.cjs.map +0 -1
- package/build/lib/_virtual/_rollupPluginBabelHelpers.legacy.js +0 -56
- package/build/lib/_virtual/_rollupPluginBabelHelpers.legacy.js.map +0 -1
- package/build/lib/index.cjs +0 -14
- package/build/lib/index.cjs.map +0 -1
- package/build/lib/index.d.ts +0 -4
- package/build/lib/index.d.ts.map +0 -1
- package/build/lib/index.js +0 -4
- package/build/lib/index.js.map +0 -1
- package/build/lib/index.legacy.cjs +0 -14
- package/build/lib/index.legacy.cjs.map +0 -1
- package/build/lib/index.legacy.js +0 -4
- package/build/lib/index.legacy.js.map +0 -1
- package/build/lib/tests/FieldApi.spec.d.ts +0 -2
- package/build/lib/tests/FieldApi.spec.d.ts.map +0 -1
- package/build/lib/tests/FieldApi.test-d.d.ts +0 -2
- package/build/lib/tests/FieldApi.test-d.d.ts.map +0 -1
- package/build/lib/tests/FormApi.spec.d.ts +0 -2
- package/build/lib/tests/FormApi.spec.d.ts.map +0 -1
- package/build/lib/tests/utils.d.ts +0 -2
- package/build/lib/tests/utils.d.ts.map +0 -1
- package/build/lib/utils.cjs +0 -81
- package/build/lib/utils.cjs.map +0 -1
- package/build/lib/utils.d.ts +0 -32
- package/build/lib/utils.d.ts.map +0 -1
- package/build/lib/utils.js +0 -77
- package/build/lib/utils.js.map +0 -1
- package/build/lib/utils.legacy.cjs +0 -81
- package/build/lib/utils.legacy.cjs.map +0 -1
- package/build/lib/utils.legacy.js +0 -77
- package/build/lib/utils.legacy.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/form-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Powerful, type-safe, framework agnostic forms.",
|
|
5
5
|
"author": "tannerlinsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,23 +11,36 @@
|
|
|
11
11
|
"url": "https://github.com/sponsors/tannerlinsley"
|
|
12
12
|
},
|
|
13
13
|
"type": "module",
|
|
14
|
-
"types": "build/
|
|
15
|
-
"main": "build/
|
|
16
|
-
"module": "build/
|
|
14
|
+
"types": "build/legacy/index.d.ts",
|
|
15
|
+
"main": "build/legacy/index.cjs",
|
|
16
|
+
"module": "build/legacy/index.js",
|
|
17
17
|
"exports": {
|
|
18
18
|
".": {
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
"import": {
|
|
20
|
+
"types": "./build/modern/index.d.ts",
|
|
21
|
+
"default": "./build/modern/index.js"
|
|
22
|
+
},
|
|
23
|
+
"require": {
|
|
24
|
+
"types": "./build/modern/index.d.cts",
|
|
25
|
+
"default": "./build/modern/index.cjs"
|
|
26
|
+
}
|
|
23
27
|
},
|
|
24
28
|
"./package.json": "./package.json"
|
|
25
29
|
},
|
|
26
30
|
"sideEffects": false,
|
|
27
31
|
"files": [
|
|
28
|
-
"build
|
|
32
|
+
"build",
|
|
29
33
|
"src"
|
|
30
34
|
],
|
|
35
|
+
"nx": {
|
|
36
|
+
"targets": {
|
|
37
|
+
"test:build": {
|
|
38
|
+
"dependsOn": [
|
|
39
|
+
"build"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
31
44
|
"dependencies": {
|
|
32
45
|
"@tanstack/store": "0.1.3"
|
|
33
46
|
},
|
|
@@ -38,8 +51,6 @@
|
|
|
38
51
|
"test:lib": "vitest run --coverage",
|
|
39
52
|
"test:lib:dev": "pnpm run test:lib --watch",
|
|
40
53
|
"test:build": "publint --strict",
|
|
41
|
-
"build": "
|
|
42
|
-
"build:rollup": "rollup --config rollup.config.js",
|
|
43
|
-
"build:types": "tsc --emitDeclarationOnly"
|
|
54
|
+
"build": "tsup"
|
|
44
55
|
}
|
|
45
56
|
}
|
package/src/FieldApi.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { FormApi, ValidationError } from './FormApi'
|
|
1
|
+
import { type DeepKeys, type DeepValue, type Updater } from './utils'
|
|
2
|
+
import type { FormApi, ValidationError, ValidationErrorMap } from './FormApi'
|
|
3
3
|
import { Store } from '@tanstack/store'
|
|
4
4
|
|
|
5
|
-
export type ValidationCause = 'change' | 'blur' | 'submit'
|
|
5
|
+
export type ValidationCause = 'change' | 'blur' | 'submit' | 'mount'
|
|
6
6
|
|
|
7
7
|
type ValidateFn<TData, TFormData> = (
|
|
8
8
|
value: TData,
|
|
@@ -52,8 +52,9 @@ export type FieldApiOptions<TData, TFormData> = FieldOptions<
|
|
|
52
52
|
|
|
53
53
|
export type FieldMeta = {
|
|
54
54
|
isTouched: boolean
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
touchedErrors: ValidationError[]
|
|
56
|
+
errors: ValidationError[]
|
|
57
|
+
errorMap: ValidationErrorMap
|
|
57
58
|
isValidating: boolean
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -110,6 +111,9 @@ export class FieldApi<TData, TFormData> {
|
|
|
110
111
|
meta: this._getMeta() ?? {
|
|
111
112
|
isValidating: false,
|
|
112
113
|
isTouched: false,
|
|
114
|
+
touchedErrors: [],
|
|
115
|
+
errors: [],
|
|
116
|
+
errorMap: {},
|
|
113
117
|
...opts.defaultMeta,
|
|
114
118
|
},
|
|
115
119
|
},
|
|
@@ -117,9 +121,9 @@ export class FieldApi<TData, TFormData> {
|
|
|
117
121
|
onUpdate: () => {
|
|
118
122
|
const state = this.store.state
|
|
119
123
|
|
|
120
|
-
state.meta.
|
|
121
|
-
? state.meta.
|
|
122
|
-
:
|
|
124
|
+
state.meta.touchedErrors = state.meta.isTouched
|
|
125
|
+
? state.meta.errors
|
|
126
|
+
: []
|
|
123
127
|
|
|
124
128
|
this.prevState = state
|
|
125
129
|
this.state = state
|
|
@@ -203,6 +207,9 @@ export class FieldApi<TData, TFormData> {
|
|
|
203
207
|
({
|
|
204
208
|
isValidating: false,
|
|
205
209
|
isTouched: false,
|
|
210
|
+
touchedErrors: [],
|
|
211
|
+
errors: [],
|
|
212
|
+
errorMap: {},
|
|
206
213
|
...this.options.defaultMeta,
|
|
207
214
|
} as FieldMeta)
|
|
208
215
|
|
|
@@ -239,7 +246,6 @@ export class FieldApi<TData, TFormData> {
|
|
|
239
246
|
const { onChange, onBlur } = this.options
|
|
240
247
|
const validate =
|
|
241
248
|
cause === 'submit' ? undefined : cause === 'change' ? onChange : onBlur
|
|
242
|
-
|
|
243
249
|
if (!validate) return
|
|
244
250
|
|
|
245
251
|
// Use the validationCount for all field instances to
|
|
@@ -247,16 +253,20 @@ export class FieldApi<TData, TFormData> {
|
|
|
247
253
|
const validationCount = (this.getInfo().validationCount || 0) + 1
|
|
248
254
|
this.getInfo().validationCount = validationCount
|
|
249
255
|
const error = normalizeError(validate(value as never, this as never))
|
|
250
|
-
|
|
251
|
-
if (this.state.meta.
|
|
256
|
+
const errorMapKey = getErrorMapKey(cause)
|
|
257
|
+
if (error && this.state.meta.errorMap[errorMapKey] !== error) {
|
|
252
258
|
this.setMeta((prev) => ({
|
|
253
259
|
...prev,
|
|
254
|
-
error,
|
|
260
|
+
errors: [...prev.errors, error],
|
|
261
|
+
errorMap: {
|
|
262
|
+
...prev.errorMap,
|
|
263
|
+
[getErrorMapKey(cause)]: error,
|
|
264
|
+
},
|
|
255
265
|
}))
|
|
256
266
|
}
|
|
257
267
|
|
|
258
|
-
// If a sync error is encountered, cancel any async validation
|
|
259
|
-
if (this.state.meta.
|
|
268
|
+
// If a sync error is encountered for the errorMapKey (eg. onChange), cancel any async validation
|
|
269
|
+
if (this.state.meta.errorMap[errorMapKey]) {
|
|
260
270
|
this.cancelValidateAsync()
|
|
261
271
|
}
|
|
262
272
|
}
|
|
@@ -293,9 +303,7 @@ export class FieldApi<TData, TFormData> {
|
|
|
293
303
|
: cause === 'submit'
|
|
294
304
|
? onSubmitAsync
|
|
295
305
|
: onBlurAsync
|
|
296
|
-
|
|
297
|
-
if (!validate) return
|
|
298
|
-
|
|
306
|
+
if (!validate) return []
|
|
299
307
|
const debounceMs =
|
|
300
308
|
cause === 'submit'
|
|
301
309
|
? 0
|
|
@@ -328,21 +336,25 @@ export class FieldApi<TData, TFormData> {
|
|
|
328
336
|
|
|
329
337
|
// Only kick off validation if this validation is the latest attempt
|
|
330
338
|
if (checkLatest()) {
|
|
339
|
+
const prevErrors = this.getMeta().errors
|
|
331
340
|
try {
|
|
332
341
|
const rawError = await validate(value as never, this as never)
|
|
333
|
-
|
|
334
342
|
if (checkLatest()) {
|
|
335
343
|
const error = normalizeError(rawError)
|
|
336
344
|
this.setMeta((prev) => ({
|
|
337
345
|
...prev,
|
|
338
346
|
isValidating: false,
|
|
339
|
-
error,
|
|
347
|
+
errors: [...prev.errors, error],
|
|
348
|
+
errorMap: {
|
|
349
|
+
...prev.errorMap,
|
|
350
|
+
[getErrorMapKey(cause)]: error,
|
|
351
|
+
},
|
|
340
352
|
}))
|
|
341
|
-
this.getInfo().validationResolve?.(error)
|
|
353
|
+
this.getInfo().validationResolve?.([...prevErrors, error])
|
|
342
354
|
}
|
|
343
355
|
} catch (error) {
|
|
344
356
|
if (checkLatest()) {
|
|
345
|
-
this.getInfo().validationReject?.(error)
|
|
357
|
+
this.getInfo().validationReject?.([...prevErrors, error])
|
|
346
358
|
throw error
|
|
347
359
|
}
|
|
348
360
|
} finally {
|
|
@@ -354,26 +366,25 @@ export class FieldApi<TData, TFormData> {
|
|
|
354
366
|
}
|
|
355
367
|
|
|
356
368
|
// Always return the latest validation promise to the caller
|
|
357
|
-
return this.getInfo().validationPromise
|
|
369
|
+
return this.getInfo().validationPromise ?? []
|
|
358
370
|
}
|
|
359
371
|
|
|
360
372
|
validate = (
|
|
361
373
|
cause: ValidationCause,
|
|
362
374
|
value?: typeof this._tdata,
|
|
363
|
-
): ValidationError | Promise<ValidationError> => {
|
|
375
|
+
): ValidationError[] | Promise<ValidationError[]> => {
|
|
364
376
|
// If the field is pristine and validatePristine is false, do not validate
|
|
365
|
-
if (!this.state.meta.isTouched) return
|
|
366
|
-
|
|
377
|
+
if (!this.state.meta.isTouched) return []
|
|
367
378
|
// Attempt to sync validate first
|
|
368
379
|
this.validateSync(value, cause)
|
|
369
380
|
|
|
370
|
-
|
|
371
|
-
|
|
381
|
+
const errorMapKey = getErrorMapKey(cause)
|
|
382
|
+
// If there is an error mapped to the errorMapKey (eg. onChange, onBlur, onSubmit), return the errors array, do not attempt async validation
|
|
383
|
+
if (this.getMeta().errorMap[errorMapKey]) {
|
|
372
384
|
if (!this.options.asyncAlways) {
|
|
373
|
-
return this.state.meta.
|
|
385
|
+
return this.state.meta.errors
|
|
374
386
|
}
|
|
375
387
|
}
|
|
376
|
-
|
|
377
388
|
// No error? Attempt async validation
|
|
378
389
|
return this.validateAsync(value, cause)
|
|
379
390
|
}
|
|
@@ -403,3 +414,16 @@ function normalizeError(rawError?: ValidationError) {
|
|
|
403
414
|
|
|
404
415
|
return undefined
|
|
405
416
|
}
|
|
417
|
+
|
|
418
|
+
function getErrorMapKey(cause: ValidationCause) {
|
|
419
|
+
switch (cause) {
|
|
420
|
+
case 'submit':
|
|
421
|
+
return 'onSubmit'
|
|
422
|
+
case 'change':
|
|
423
|
+
return 'onChange'
|
|
424
|
+
case 'blur':
|
|
425
|
+
return 'onBlur'
|
|
426
|
+
case 'mount':
|
|
427
|
+
return 'onMount'
|
|
428
|
+
}
|
|
429
|
+
}
|
package/src/FormApi.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Store } from '@tanstack/store'
|
|
2
2
|
//
|
|
3
3
|
import type { DeepKeys, DeepValue, Updater } from './utils'
|
|
4
|
-
import { functionalUpdate, getBy, setBy } from './utils'
|
|
4
|
+
import { functionalUpdate, getBy, isNonEmptyArray, setBy } from './utils'
|
|
5
5
|
import type { FieldApi, FieldMeta, ValidationCause } from './FieldApi'
|
|
6
6
|
|
|
7
7
|
export type FormOptions<TData> = {
|
|
@@ -37,13 +37,19 @@ export type FieldInfo<TFormData> = {
|
|
|
37
37
|
export type ValidationMeta = {
|
|
38
38
|
validationCount?: number
|
|
39
39
|
validationAsyncCount?: number
|
|
40
|
-
validationPromise?: Promise<ValidationError>
|
|
41
|
-
validationResolve?: (
|
|
42
|
-
validationReject?: (
|
|
40
|
+
validationPromise?: Promise<ValidationError[]>
|
|
41
|
+
validationResolve?: (errors: ValidationError[]) => void
|
|
42
|
+
validationReject?: (errors: unknown) => void
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export type ValidationError = undefined | false | null | string
|
|
46
46
|
|
|
47
|
+
export type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`
|
|
48
|
+
|
|
49
|
+
export type ValidationErrorMap = {
|
|
50
|
+
[K in ValidationErrorMapKeys]?: ValidationError
|
|
51
|
+
}
|
|
52
|
+
|
|
47
53
|
export type FormState<TData> = {
|
|
48
54
|
values: TData
|
|
49
55
|
// Form Validation
|
|
@@ -117,7 +123,9 @@ export class FormApi<TFormData> {
|
|
|
117
123
|
(field) => field?.isValidating,
|
|
118
124
|
)
|
|
119
125
|
|
|
120
|
-
const isFieldsValid = !fieldMetaValues.some((field) =>
|
|
126
|
+
const isFieldsValid = !fieldMetaValues.some((field) =>
|
|
127
|
+
isNonEmptyArray(field?.errors),
|
|
128
|
+
)
|
|
121
129
|
|
|
122
130
|
const isTouched = fieldMetaValues.some((field) => field?.isTouched)
|
|
123
131
|
|
|
@@ -192,8 +200,7 @@ export class FormApi<TFormData> {
|
|
|
192
200
|
)
|
|
193
201
|
|
|
194
202
|
validateAllFields = async (cause: ValidationCause) => {
|
|
195
|
-
const fieldValidationPromises: Promise<ValidationError>[] = [] as any
|
|
196
|
-
|
|
203
|
+
const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any
|
|
197
204
|
this.store.batch(() => {
|
|
198
205
|
void (Object.values(this.fieldInfo) as FieldInfo<any>[]).forEach(
|
|
199
206
|
(field) => {
|
|
@@ -30,6 +30,9 @@ describe('field api', () => {
|
|
|
30
30
|
expect(field.getMeta()).toEqual({
|
|
31
31
|
isTouched: false,
|
|
32
32
|
isValidating: false,
|
|
33
|
+
touchedErrors: [],
|
|
34
|
+
errors: [],
|
|
35
|
+
errorMap: {},
|
|
33
36
|
})
|
|
34
37
|
})
|
|
35
38
|
|
|
@@ -44,6 +47,9 @@ describe('field api', () => {
|
|
|
44
47
|
expect(field.getMeta()).toEqual({
|
|
45
48
|
isTouched: true,
|
|
46
49
|
isValidating: false,
|
|
50
|
+
touchedErrors: [],
|
|
51
|
+
errors: [],
|
|
52
|
+
errorMap: {},
|
|
47
53
|
})
|
|
48
54
|
})
|
|
49
55
|
|
|
@@ -193,9 +199,12 @@ describe('field api', () => {
|
|
|
193
199
|
|
|
194
200
|
field.mount()
|
|
195
201
|
|
|
196
|
-
expect(field.getMeta().
|
|
202
|
+
expect(field.getMeta().errors.length).toBe(0)
|
|
197
203
|
field.setValue('other', { touch: true })
|
|
198
|
-
expect(field.getMeta().
|
|
204
|
+
expect(field.getMeta().errors).toContain('Please enter a different value')
|
|
205
|
+
expect(field.getMeta().errorMap).toMatchObject({
|
|
206
|
+
onChange: 'Please enter a different value',
|
|
207
|
+
})
|
|
199
208
|
})
|
|
200
209
|
|
|
201
210
|
it('should run async validation onChange', async () => {
|
|
@@ -219,10 +228,13 @@ describe('field api', () => {
|
|
|
219
228
|
|
|
220
229
|
field.mount()
|
|
221
230
|
|
|
222
|
-
expect(field.getMeta().
|
|
231
|
+
expect(field.getMeta().errors.length).toBe(0)
|
|
223
232
|
field.setValue('other', { touch: true })
|
|
224
233
|
await vi.runAllTimersAsync()
|
|
225
|
-
expect(field.getMeta().
|
|
234
|
+
expect(field.getMeta().errors).toContain('Please enter a different value')
|
|
235
|
+
expect(field.getMeta().errorMap).toMatchObject({
|
|
236
|
+
onChange: 'Please enter a different value',
|
|
237
|
+
})
|
|
226
238
|
})
|
|
227
239
|
|
|
228
240
|
it('should run async validation onChange with debounce', async () => {
|
|
@@ -248,13 +260,16 @@ describe('field api', () => {
|
|
|
248
260
|
|
|
249
261
|
field.mount()
|
|
250
262
|
|
|
251
|
-
expect(field.getMeta().
|
|
263
|
+
expect(field.getMeta().errors.length).toBe(0)
|
|
252
264
|
field.setValue('other', { touch: true })
|
|
253
265
|
field.setValue('other')
|
|
254
266
|
await vi.runAllTimersAsync()
|
|
255
267
|
// sleepMock will have been called 2 times without onChangeAsyncDebounceMs
|
|
256
268
|
expect(sleepMock).toHaveBeenCalledTimes(1)
|
|
257
|
-
expect(field.getMeta().
|
|
269
|
+
expect(field.getMeta().errors).toContain('Please enter a different value')
|
|
270
|
+
expect(field.getMeta().errorMap).toMatchObject({
|
|
271
|
+
onChange: 'Please enter a different value',
|
|
272
|
+
})
|
|
258
273
|
})
|
|
259
274
|
|
|
260
275
|
it('should run async validation onChange with asyncDebounceMs', async () => {
|
|
@@ -280,13 +295,16 @@ describe('field api', () => {
|
|
|
280
295
|
|
|
281
296
|
field.mount()
|
|
282
297
|
|
|
283
|
-
expect(field.getMeta().
|
|
298
|
+
expect(field.getMeta().errors.length).toBe(0)
|
|
284
299
|
field.setValue('other', { touch: true })
|
|
285
300
|
field.setValue('other')
|
|
286
301
|
await vi.runAllTimersAsync()
|
|
287
302
|
// sleepMock will have been called 2 times without asyncDebounceMs
|
|
288
303
|
expect(sleepMock).toHaveBeenCalledTimes(1)
|
|
289
|
-
expect(field.getMeta().
|
|
304
|
+
expect(field.getMeta().errors).toContain('Please enter a different value')
|
|
305
|
+
expect(field.getMeta().errorMap).toMatchObject({
|
|
306
|
+
onChange: 'Please enter a different value',
|
|
307
|
+
})
|
|
290
308
|
})
|
|
291
309
|
|
|
292
310
|
it('should run validation onBlur', () => {
|
|
@@ -309,7 +327,10 @@ describe('field api', () => {
|
|
|
309
327
|
|
|
310
328
|
field.setValue('other', { touch: true })
|
|
311
329
|
field.validate('blur')
|
|
312
|
-
expect(field.getMeta().
|
|
330
|
+
expect(field.getMeta().errors).toContain('Please enter a different value')
|
|
331
|
+
expect(field.getMeta().errorMap).toMatchObject({
|
|
332
|
+
onBlur: 'Please enter a different value',
|
|
333
|
+
})
|
|
313
334
|
})
|
|
314
335
|
|
|
315
336
|
it('should run async validation onBlur', async () => {
|
|
@@ -333,11 +354,14 @@ describe('field api', () => {
|
|
|
333
354
|
|
|
334
355
|
field.mount()
|
|
335
356
|
|
|
336
|
-
expect(field.getMeta().
|
|
357
|
+
expect(field.getMeta().errors.length).toBe(0)
|
|
337
358
|
field.setValue('other', { touch: true })
|
|
338
359
|
field.validate('blur')
|
|
339
360
|
await vi.runAllTimersAsync()
|
|
340
|
-
expect(field.getMeta().
|
|
361
|
+
expect(field.getMeta().errors).toContain('Please enter a different value')
|
|
362
|
+
expect(field.getMeta().errorMap).toMatchObject({
|
|
363
|
+
onBlur: 'Please enter a different value',
|
|
364
|
+
})
|
|
341
365
|
})
|
|
342
366
|
|
|
343
367
|
it('should run async validation onBlur with debounce', async () => {
|
|
@@ -363,14 +387,17 @@ describe('field api', () => {
|
|
|
363
387
|
|
|
364
388
|
field.mount()
|
|
365
389
|
|
|
366
|
-
expect(field.getMeta().
|
|
390
|
+
expect(field.getMeta().errors.length).toBe(0)
|
|
367
391
|
field.setValue('other', { touch: true })
|
|
368
392
|
field.validate('blur')
|
|
369
393
|
field.validate('blur')
|
|
370
394
|
await vi.runAllTimersAsync()
|
|
371
395
|
// sleepMock will have been called 2 times without onBlurAsyncDebounceMs
|
|
372
396
|
expect(sleepMock).toHaveBeenCalledTimes(1)
|
|
373
|
-
expect(field.getMeta().
|
|
397
|
+
expect(field.getMeta().errors).toContain('Please enter a different value')
|
|
398
|
+
expect(field.getMeta().errorMap).toMatchObject({
|
|
399
|
+
onBlur: 'Please enter a different value',
|
|
400
|
+
})
|
|
374
401
|
})
|
|
375
402
|
|
|
376
403
|
it('should run async validation onBlur with asyncDebounceMs', async () => {
|
|
@@ -396,14 +423,17 @@ describe('field api', () => {
|
|
|
396
423
|
|
|
397
424
|
field.mount()
|
|
398
425
|
|
|
399
|
-
expect(field.getMeta().
|
|
426
|
+
expect(field.getMeta().errors.length).toBe(0)
|
|
400
427
|
field.setValue('other', { touch: true })
|
|
401
428
|
field.validate('blur')
|
|
402
429
|
field.validate('blur')
|
|
403
430
|
await vi.runAllTimersAsync()
|
|
404
431
|
// sleepMock will have been called 2 times without asyncDebounceMs
|
|
405
432
|
expect(sleepMock).toHaveBeenCalledTimes(1)
|
|
406
|
-
expect(field.getMeta().
|
|
433
|
+
expect(field.getMeta().errors).toContain('Please enter a different value')
|
|
434
|
+
expect(field.getMeta().errorMap).toMatchObject({
|
|
435
|
+
onBlur: 'Please enter a different value',
|
|
436
|
+
})
|
|
407
437
|
})
|
|
408
438
|
|
|
409
439
|
it('should run async validation onSubmit', async () => {
|
|
@@ -427,11 +457,48 @@ describe('field api', () => {
|
|
|
427
457
|
|
|
428
458
|
field.mount()
|
|
429
459
|
|
|
430
|
-
expect(field.getMeta().
|
|
460
|
+
expect(field.getMeta().errors.length).toBe(0)
|
|
431
461
|
field.setValue('other', { touch: true })
|
|
432
462
|
field.validate('submit')
|
|
433
463
|
await vi.runAllTimersAsync()
|
|
434
|
-
expect(field.getMeta().
|
|
464
|
+
expect(field.getMeta().errors).toContain('Please enter a different value')
|
|
465
|
+
expect(field.getMeta().errorMap).toMatchObject({
|
|
466
|
+
onSubmit: 'Please enter a different value',
|
|
467
|
+
})
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
it('should contain multiple errors when running validation onBlur and onChange', () => {
|
|
471
|
+
const form = new FormApi({
|
|
472
|
+
defaultValues: {
|
|
473
|
+
name: 'other',
|
|
474
|
+
},
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
const field = new FieldApi({
|
|
478
|
+
form,
|
|
479
|
+
name: 'name',
|
|
480
|
+
onBlur: (value) => {
|
|
481
|
+
if (value === 'other') return 'Please enter a different value'
|
|
482
|
+
return
|
|
483
|
+
},
|
|
484
|
+
onChange: (value) => {
|
|
485
|
+
if (value === 'other') return 'Please enter a different value'
|
|
486
|
+
return
|
|
487
|
+
},
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
field.mount()
|
|
491
|
+
|
|
492
|
+
field.setValue('other', { touch: true })
|
|
493
|
+
field.validate('blur')
|
|
494
|
+
expect(field.getMeta().errors).toStrictEqual([
|
|
495
|
+
'Please enter a different value',
|
|
496
|
+
'Please enter a different value',
|
|
497
|
+
])
|
|
498
|
+
expect(field.getMeta().errorMap).toEqual({
|
|
499
|
+
onBlur: 'Please enter a different value',
|
|
500
|
+
onChange: 'Please enter a different value',
|
|
501
|
+
})
|
|
435
502
|
})
|
|
436
503
|
|
|
437
504
|
it('should handle default value on field using state.value', async () => {
|
package/src/utils.ts
CHANGED
|
@@ -101,6 +101,10 @@ function makePathArray(str: string) {
|
|
|
101
101
|
})
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
export function isNonEmptyArray(obj: any) {
|
|
105
|
+
return !(Array.isArray(obj) && obj.length === 0)
|
|
106
|
+
}
|
|
107
|
+
|
|
104
108
|
export type RequiredByKey<T, K extends keyof T> = Omit<T, K> &
|
|
105
109
|
Required<Pick<T, K>>
|
|
106
110
|
|