remix-validated-form 4.6.12 → 5.0.1
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/.turbo/turbo-build.log +8 -8
- package/dist/index.cjs.js +178 -52
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +34 -12
- package/dist/index.esm.js +180 -54
- package/dist/index.esm.js.map +1 -1
- package/package.json +6 -5
- package/src/ValidatedForm.tsx +46 -16
- package/src/hooks.ts +5 -7
- package/src/internal/hooks.ts +5 -5
- package/src/internal/state/arrayUtil.ts +41 -3
- package/src/internal/state/createFormStore.ts +104 -27
- package/src/internal/state/fieldArray.tsx +56 -14
- package/src/server.ts +3 -0
- package/src/unreleased/formStateHooks.ts +8 -3
- package/src/validation/types.ts +4 -1
- package/tsconfig.json +1 -1
- package/src/validation/validation.test.ts +0 -330
@@ -1,4 +1,5 @@
|
|
1
|
-
import
|
1
|
+
import { nanoid } from "nanoid";
|
2
|
+
import React, { useMemo, useRef, useState } from "react";
|
2
3
|
import { useCallback } from "react";
|
3
4
|
import invariant from "tiny-invariant";
|
4
5
|
import { InternalFormContextValue } from "../formContext";
|
@@ -7,8 +8,9 @@ import {
|
|
7
8
|
useFieldError,
|
8
9
|
useInternalFormContext,
|
9
10
|
useInternalHasBeenSubmitted,
|
10
|
-
|
11
|
+
useSmartValidate,
|
11
12
|
} from "../hooks";
|
13
|
+
import * as arrayUtil from "./arrayUtil";
|
12
14
|
import { useRegisterControlledField } from "./controlledFields";
|
13
15
|
import { useFormStore } from "./storeHooks";
|
14
16
|
|
@@ -19,6 +21,19 @@ export type FieldArrayValidationBehaviorOptions = {
|
|
19
21
|
whenSubmitted: FieldArrayValidationBehavior;
|
20
22
|
};
|
21
23
|
|
24
|
+
export type FieldArrayItem<T> = {
|
25
|
+
/**
|
26
|
+
* The default value of the item.
|
27
|
+
* This does not update as the field is changed by the user.
|
28
|
+
*/
|
29
|
+
defaultValue: T;
|
30
|
+
/**
|
31
|
+
* A unique key for the item.
|
32
|
+
* Use this as the key prop when rendering the item.
|
33
|
+
*/
|
34
|
+
key: string;
|
35
|
+
};
|
36
|
+
|
22
37
|
const useInternalFieldArray = (
|
23
38
|
context: InternalFormContextValue,
|
24
39
|
field: string,
|
@@ -27,7 +42,7 @@ const useInternalFieldArray = (
|
|
27
42
|
const value = useFieldDefaultValue(field, context);
|
28
43
|
useRegisterControlledField(context, field);
|
29
44
|
const hasBeenSubmitted = useInternalHasBeenSubmitted(context.formId);
|
30
|
-
const validateField =
|
45
|
+
const validateField = useSmartValidate(context.formId);
|
31
46
|
const error = useFieldError(field, context);
|
32
47
|
|
33
48
|
const resolvedValidationBehavior: FieldArrayValidationBehaviorOptions = {
|
@@ -42,7 +57,7 @@ const useInternalFieldArray = (
|
|
42
57
|
|
43
58
|
const maybeValidate = useCallback(() => {
|
44
59
|
if (behavior === "onChange") {
|
45
|
-
validateField(field);
|
60
|
+
validateField({ alwaysIncludeErrorsFromFields: [field] });
|
46
61
|
}
|
47
62
|
}, [behavior, field, validateField]);
|
48
63
|
|
@@ -56,47 +71,74 @@ const useInternalFieldArray = (
|
|
56
71
|
(state) => state.controlledFields.array
|
57
72
|
);
|
58
73
|
|
74
|
+
const arrayValue = useMemo<unknown[]>(() => value ?? [], [value]);
|
75
|
+
const keyRef = useRef<string[]>([]);
|
76
|
+
|
77
|
+
// If the lengths don't match up it means one of two things
|
78
|
+
// 1. The array has been modified outside of this hook
|
79
|
+
// 2. We're initializing the array
|
80
|
+
if (keyRef.current.length !== arrayValue.length) {
|
81
|
+
keyRef.current = arrayValue.map(() => nanoid());
|
82
|
+
}
|
83
|
+
|
59
84
|
const helpers = useMemo(
|
60
85
|
() => ({
|
61
86
|
push: (item: any) => {
|
62
87
|
arr.push(field, item);
|
88
|
+
keyRef.current.push(nanoid());
|
63
89
|
maybeValidate();
|
64
90
|
},
|
65
91
|
swap: (indexA: number, indexB: number) => {
|
66
92
|
arr.swap(field, indexA, indexB);
|
93
|
+
arrayUtil.swap(keyRef.current, indexA, indexB);
|
67
94
|
maybeValidate();
|
68
95
|
},
|
69
96
|
move: (from: number, to: number) => {
|
70
97
|
arr.move(field, from, to);
|
98
|
+
arrayUtil.move(keyRef.current, from, to);
|
71
99
|
maybeValidate();
|
72
100
|
},
|
73
101
|
insert: (index: number, value: any) => {
|
74
102
|
arr.insert(field, index, value);
|
103
|
+
arrayUtil.insert(keyRef.current, index, nanoid());
|
75
104
|
maybeValidate();
|
76
105
|
},
|
77
106
|
unshift: (value: any) => {
|
78
107
|
arr.unshift(field, value);
|
108
|
+
keyRef.current.unshift(nanoid());
|
79
109
|
maybeValidate();
|
80
110
|
},
|
81
111
|
remove: (index: number) => {
|
82
112
|
arr.remove(field, index);
|
113
|
+
arrayUtil.remove(keyRef.current, index);
|
83
114
|
maybeValidate();
|
84
115
|
},
|
85
116
|
pop: () => {
|
86
117
|
arr.pop(field);
|
118
|
+
keyRef.current.pop();
|
87
119
|
maybeValidate();
|
88
120
|
},
|
89
121
|
replace: (index: number, value: any) => {
|
90
122
|
arr.replace(field, index, value);
|
123
|
+
keyRef.current[index] = nanoid();
|
91
124
|
maybeValidate();
|
92
125
|
},
|
93
126
|
}),
|
94
127
|
[arr, field, maybeValidate]
|
95
128
|
);
|
96
129
|
|
97
|
-
const
|
98
|
-
|
99
|
-
|
130
|
+
const valueWithKeys = useMemo(() => {
|
131
|
+
const result: { defaultValue: any; key: string }[] = [];
|
132
|
+
arrayValue.forEach((item, index) => {
|
133
|
+
result[index] = {
|
134
|
+
key: keyRef.current[index],
|
135
|
+
defaultValue: item,
|
136
|
+
};
|
137
|
+
});
|
138
|
+
return result;
|
139
|
+
}, [arrayValue]);
|
140
|
+
|
141
|
+
return [valueWithKeys, helpers, error] as const;
|
100
142
|
};
|
101
143
|
|
102
144
|
export type FieldArrayHelpers<Item = any> = {
|
@@ -122,29 +164,29 @@ export function useFieldArray<Item = any>(
|
|
122
164
|
const context = useInternalFormContext(formId, "FieldArray");
|
123
165
|
|
124
166
|
return useInternalFieldArray(context, name, validationBehavior) as [
|
125
|
-
|
167
|
+
items: FieldArrayItem<Item>[],
|
126
168
|
helpers: FieldArrayHelpers,
|
127
169
|
error: string | undefined
|
128
170
|
];
|
129
171
|
}
|
130
172
|
|
131
|
-
export type FieldArrayProps = {
|
173
|
+
export type FieldArrayProps<Item> = {
|
132
174
|
name: string;
|
133
175
|
children: (
|
134
|
-
|
135
|
-
helpers: FieldArrayHelpers
|
176
|
+
items: FieldArrayItem<Item>[],
|
177
|
+
helpers: FieldArrayHelpers<Item>,
|
136
178
|
error: string | undefined
|
137
179
|
) => React.ReactNode;
|
138
180
|
formId?: string;
|
139
181
|
validationBehavior?: FieldArrayValidationBehaviorOptions;
|
140
182
|
};
|
141
183
|
|
142
|
-
export
|
184
|
+
export function FieldArray<Item = any>({
|
143
185
|
name,
|
144
186
|
children,
|
145
187
|
formId,
|
146
188
|
validationBehavior,
|
147
|
-
}: FieldArrayProps)
|
189
|
+
}: FieldArrayProps<Item>) {
|
148
190
|
const context = useInternalFormContext(formId, "FieldArray");
|
149
191
|
const [value, helpers, error] = useInternalFieldArray(
|
150
192
|
context,
|
@@ -152,4 +194,4 @@ export const FieldArray = ({
|
|
152
194
|
validationBehavior
|
153
195
|
);
|
154
196
|
return <>{children(value, helpers, error)}</>;
|
155
|
-
}
|
197
|
+
}
|
package/src/server.ts
CHANGED
@@ -42,6 +42,9 @@ export type FormDefaults = {
|
|
42
42
|
[formDefaultsKey: `${typeof FORM_DEFAULTS_FIELD}_${string}`]: any;
|
43
43
|
};
|
44
44
|
|
45
|
+
// FIXME: Remove after https://github.com/egoist/tsup/issues/813 is fixed
|
46
|
+
export type internal_FORM_DEFAULTS_FIELD = typeof FORM_DEFAULTS_FIELD;
|
47
|
+
|
45
48
|
export const setFormDefaults = <DataType = any>(
|
46
49
|
formId: string,
|
47
50
|
defaultValues: Partial<DataType>
|
@@ -11,7 +11,6 @@ import {
|
|
11
11
|
useTouchedFields,
|
12
12
|
useInternalIsValid,
|
13
13
|
useFieldErrors,
|
14
|
-
useValidateField,
|
15
14
|
useValidate,
|
16
15
|
useSetFieldErrors,
|
17
16
|
useResetFormElement,
|
@@ -20,6 +19,7 @@ import {
|
|
20
19
|
useFormSubactionProp,
|
21
20
|
useSubmitForm,
|
22
21
|
useFormValues,
|
22
|
+
useSmartValidate,
|
23
23
|
} from "../internal/hooks";
|
24
24
|
import {
|
25
25
|
FieldErrors,
|
@@ -133,7 +133,7 @@ export type FormHelpers = {
|
|
133
133
|
export const useFormHelpers = (formId?: string): FormHelpers => {
|
134
134
|
const formContext = useInternalFormContext(formId, "useFormHelpers");
|
135
135
|
const setTouched = useSetTouched(formContext);
|
136
|
-
const validateField =
|
136
|
+
const validateField = useSmartValidate(formContext.formId);
|
137
137
|
const validate = useValidate(formContext.formId);
|
138
138
|
const clearError = useClearError(formContext);
|
139
139
|
const setFieldErrors = useSetFieldErrors(formContext.formId);
|
@@ -143,7 +143,12 @@ export const useFormHelpers = (formId?: string): FormHelpers => {
|
|
143
143
|
return useMemo(
|
144
144
|
() => ({
|
145
145
|
setTouched,
|
146
|
-
validateField
|
146
|
+
validateField: async (fieldName: string) => {
|
147
|
+
const res = await validateField({
|
148
|
+
alwaysIncludeErrorsFromFields: [fieldName],
|
149
|
+
});
|
150
|
+
return res.error?.fieldErrors[fieldName] ?? null;
|
151
|
+
},
|
147
152
|
clearError,
|
148
153
|
validate,
|
149
154
|
clearAllErrors: () => setFieldErrors({}),
|
package/src/validation/types.ts
CHANGED
@@ -44,7 +44,10 @@ export type Validator<DataType> = {
|
|
44
44
|
validate: (
|
45
45
|
unvalidatedData: GenericObject
|
46
46
|
) => Promise<ValidationResult<DataType>>;
|
47
|
-
|
47
|
+
/**
|
48
|
+
* @deprecated Will be removed in a future version of remix-validated-form
|
49
|
+
*/
|
50
|
+
validateField?: (
|
48
51
|
unvalidatedData: GenericObject,
|
49
52
|
field: string
|
50
53
|
) => Promise<ValidateFieldResult>;
|
package/tsconfig.json
CHANGED
@@ -1,330 +0,0 @@
|
|
1
|
-
import { anyString, TestFormData } from "@remix-validated-form/test-utils";
|
2
|
-
import { withYup } from "@remix-validated-form/with-yup/src";
|
3
|
-
import { withZod } from "@remix-validated-form/with-zod";
|
4
|
-
import * as R from "remeda";
|
5
|
-
import { Validator } from "remix-validated-form/src";
|
6
|
-
import { objectFromPathEntries } from "remix-validated-form/src/internal/flatten";
|
7
|
-
import { describe, it, expect } from "vitest";
|
8
|
-
import * as yup from "yup";
|
9
|
-
import { z } from "zod";
|
10
|
-
import { FORM_ID_FIELD } from "../internal/constants";
|
11
|
-
|
12
|
-
// If adding an adapter, write a validator that validates this shape
|
13
|
-
type Person = {
|
14
|
-
firstName: string;
|
15
|
-
lastName: string;
|
16
|
-
age?: number;
|
17
|
-
address: {
|
18
|
-
streetAddress: string;
|
19
|
-
city: string;
|
20
|
-
country: string;
|
21
|
-
};
|
22
|
-
pets?: {
|
23
|
-
animal: string;
|
24
|
-
name: string;
|
25
|
-
}[];
|
26
|
-
};
|
27
|
-
|
28
|
-
type ValidationTestCase = {
|
29
|
-
name: string;
|
30
|
-
validator: Validator<Person>;
|
31
|
-
};
|
32
|
-
|
33
|
-
const validationTestCases: ValidationTestCase[] = [
|
34
|
-
{
|
35
|
-
name: "yup",
|
36
|
-
validator: withYup(
|
37
|
-
yup.object({
|
38
|
-
firstName: yup.string().required(),
|
39
|
-
lastName: yup.string().required(),
|
40
|
-
age: yup.number(),
|
41
|
-
address: yup
|
42
|
-
.object({
|
43
|
-
streetAddress: yup.string().required(),
|
44
|
-
city: yup.string().required(),
|
45
|
-
country: yup.string().required(),
|
46
|
-
})
|
47
|
-
.required(),
|
48
|
-
pets: yup.array().of(
|
49
|
-
yup.object({
|
50
|
-
animal: yup.string().required(),
|
51
|
-
name: yup.string().required(),
|
52
|
-
})
|
53
|
-
),
|
54
|
-
})
|
55
|
-
),
|
56
|
-
},
|
57
|
-
{
|
58
|
-
name: "zod",
|
59
|
-
validator: withZod(
|
60
|
-
z.object({
|
61
|
-
firstName: z.string().min(1),
|
62
|
-
lastName: z.string().min(1),
|
63
|
-
age: z.optional(z.number()),
|
64
|
-
address: z.preprocess(
|
65
|
-
(value) => (value == null ? {} : value),
|
66
|
-
z.object({
|
67
|
-
streetAddress: z.string().min(1),
|
68
|
-
city: z.string().min(1),
|
69
|
-
country: z.string().min(1),
|
70
|
-
})
|
71
|
-
),
|
72
|
-
pets: z
|
73
|
-
.object({
|
74
|
-
animal: z.string().min(1),
|
75
|
-
name: z.string().min(1),
|
76
|
-
})
|
77
|
-
.array()
|
78
|
-
.optional(),
|
79
|
-
})
|
80
|
-
),
|
81
|
-
},
|
82
|
-
];
|
83
|
-
|
84
|
-
describe("Validation", () => {
|
85
|
-
describe.each(validationTestCases)("Adapter for $name", ({ validator }) => {
|
86
|
-
describe("validate", () => {
|
87
|
-
it("should return the data when valid", async () => {
|
88
|
-
const person: Person = {
|
89
|
-
firstName: "John",
|
90
|
-
lastName: "Doe",
|
91
|
-
age: 30,
|
92
|
-
address: {
|
93
|
-
streetAddress: "123 Main St",
|
94
|
-
city: "Anytown",
|
95
|
-
country: "USA",
|
96
|
-
},
|
97
|
-
pets: [{ animal: "dog", name: "Fido" }],
|
98
|
-
};
|
99
|
-
expect(await validator.validate(person)).toEqual({
|
100
|
-
data: person,
|
101
|
-
error: undefined,
|
102
|
-
submittedData: person,
|
103
|
-
});
|
104
|
-
});
|
105
|
-
|
106
|
-
it("should omit internal fields", async () => {
|
107
|
-
const person: Person = {
|
108
|
-
firstName: "John",
|
109
|
-
lastName: "Doe",
|
110
|
-
age: 30,
|
111
|
-
address: {
|
112
|
-
streetAddress: "123 Main St",
|
113
|
-
city: "Anytown",
|
114
|
-
country: "USA",
|
115
|
-
},
|
116
|
-
pets: [{ animal: "dog", name: "Fido" }],
|
117
|
-
|
118
|
-
// @ts-expect-error
|
119
|
-
// internal filed technically not part of person type
|
120
|
-
[FORM_ID_FIELD]: "something",
|
121
|
-
};
|
122
|
-
expect(await validator.validate(person)).toEqual({
|
123
|
-
data: R.omit(person as any, [FORM_ID_FIELD]),
|
124
|
-
error: undefined,
|
125
|
-
submittedData: person,
|
126
|
-
formId: "something",
|
127
|
-
});
|
128
|
-
});
|
129
|
-
|
130
|
-
it("should return field errors when invalid", async () => {
|
131
|
-
const obj = { age: "hi!", pets: [{ animal: "dog" }] };
|
132
|
-
expect(await validator.validate(obj)).toEqual({
|
133
|
-
data: undefined,
|
134
|
-
error: {
|
135
|
-
fieldErrors: {
|
136
|
-
firstName: anyString,
|
137
|
-
lastName: anyString,
|
138
|
-
age: anyString,
|
139
|
-
"address.city": anyString,
|
140
|
-
"address.country": anyString,
|
141
|
-
"address.streetAddress": anyString,
|
142
|
-
"pets[0].name": anyString,
|
143
|
-
},
|
144
|
-
subaction: undefined,
|
145
|
-
},
|
146
|
-
submittedData: obj,
|
147
|
-
});
|
148
|
-
});
|
149
|
-
|
150
|
-
it("should unflatten data when validating", async () => {
|
151
|
-
const data = {
|
152
|
-
firstName: "John",
|
153
|
-
lastName: "Doe",
|
154
|
-
age: 30,
|
155
|
-
"address.streetAddress": "123 Main St",
|
156
|
-
"address.city": "Anytown",
|
157
|
-
"address.country": "USA",
|
158
|
-
"pets[0].animal": "dog",
|
159
|
-
"pets[0].name": "Fido",
|
160
|
-
};
|
161
|
-
expect(await validator.validate(data)).toEqual({
|
162
|
-
data: {
|
163
|
-
firstName: "John",
|
164
|
-
lastName: "Doe",
|
165
|
-
age: 30,
|
166
|
-
address: {
|
167
|
-
streetAddress: "123 Main St",
|
168
|
-
city: "Anytown",
|
169
|
-
country: "USA",
|
170
|
-
},
|
171
|
-
pets: [{ animal: "dog", name: "Fido" }],
|
172
|
-
},
|
173
|
-
error: undefined,
|
174
|
-
submittedData: objectFromPathEntries(Object.entries(data)),
|
175
|
-
});
|
176
|
-
});
|
177
|
-
|
178
|
-
it("should accept FormData directly and return errors", async () => {
|
179
|
-
const formData = new TestFormData();
|
180
|
-
formData.set("firstName", "John");
|
181
|
-
formData.set("lastName", "Doe");
|
182
|
-
formData.set("address.streetAddress", "123 Main St");
|
183
|
-
formData.set("address.country", "USA");
|
184
|
-
formData.set("pets[0].animal", "dog");
|
185
|
-
|
186
|
-
expect(await validator.validate(formData)).toEqual({
|
187
|
-
data: undefined,
|
188
|
-
error: {
|
189
|
-
fieldErrors: {
|
190
|
-
"address.city": anyString,
|
191
|
-
"pets[0].name": anyString,
|
192
|
-
},
|
193
|
-
subaction: undefined,
|
194
|
-
},
|
195
|
-
submittedData: objectFromPathEntries([...formData.entries()]),
|
196
|
-
});
|
197
|
-
});
|
198
|
-
|
199
|
-
it("should accept FormData directly and return valid data", async () => {
|
200
|
-
const formData = new TestFormData();
|
201
|
-
formData.set("firstName", "John");
|
202
|
-
formData.set("lastName", "Doe");
|
203
|
-
formData.set("address.streetAddress", "123 Main St");
|
204
|
-
formData.set("address.country", "USA");
|
205
|
-
formData.set("address.city", "Anytown");
|
206
|
-
formData.set("pets[0].animal", "dog");
|
207
|
-
formData.set("pets[0].name", "Fido");
|
208
|
-
|
209
|
-
expect(await validator.validate(formData)).toEqual({
|
210
|
-
data: {
|
211
|
-
firstName: "John",
|
212
|
-
lastName: "Doe",
|
213
|
-
address: {
|
214
|
-
streetAddress: "123 Main St",
|
215
|
-
country: "USA",
|
216
|
-
city: "Anytown",
|
217
|
-
},
|
218
|
-
pets: [{ animal: "dog", name: "Fido" }],
|
219
|
-
},
|
220
|
-
error: undefined,
|
221
|
-
subaction: undefined,
|
222
|
-
submittedData: objectFromPathEntries([...formData.entries()]),
|
223
|
-
});
|
224
|
-
});
|
225
|
-
|
226
|
-
it("should return the subaction in the ValidatorError if there is one", async () => {
|
227
|
-
const person = {
|
228
|
-
lastName: "Doe",
|
229
|
-
age: 20,
|
230
|
-
address: {
|
231
|
-
streetAddress: "123 Main St",
|
232
|
-
city: "Anytown",
|
233
|
-
country: "USA",
|
234
|
-
},
|
235
|
-
pets: [{ animal: "dog", name: "Fido" }],
|
236
|
-
subaction: "updatePerson",
|
237
|
-
};
|
238
|
-
expect(await validator.validate(person)).toEqual({
|
239
|
-
error: {
|
240
|
-
fieldErrors: {
|
241
|
-
firstName: anyString,
|
242
|
-
},
|
243
|
-
subaction: "updatePerson",
|
244
|
-
},
|
245
|
-
data: undefined,
|
246
|
-
submittedData: person,
|
247
|
-
});
|
248
|
-
});
|
249
|
-
});
|
250
|
-
|
251
|
-
describe("validateField", () => {
|
252
|
-
it("should not return an error if field is valid", async () => {
|
253
|
-
const person = {
|
254
|
-
firstName: "John",
|
255
|
-
lastName: {}, // invalid, but we should only be validating firstName
|
256
|
-
};
|
257
|
-
expect(await validator.validateField(person, "firstName")).toEqual({
|
258
|
-
error: undefined,
|
259
|
-
});
|
260
|
-
});
|
261
|
-
it("should not return an error if a nested field is valid", async () => {
|
262
|
-
const person = {
|
263
|
-
firstName: "John",
|
264
|
-
lastName: {}, // invalid, but we should only be validating firstName
|
265
|
-
address: {
|
266
|
-
streetAddress: "123 Main St",
|
267
|
-
city: "Anytown",
|
268
|
-
country: "USA",
|
269
|
-
},
|
270
|
-
pets: [{ animal: "dog", name: "Fido" }],
|
271
|
-
};
|
272
|
-
expect(
|
273
|
-
await validator.validateField(person, "address.streetAddress")
|
274
|
-
).toEqual({
|
275
|
-
error: undefined,
|
276
|
-
});
|
277
|
-
expect(await validator.validateField(person, "address.city")).toEqual({
|
278
|
-
error: undefined,
|
279
|
-
});
|
280
|
-
expect(
|
281
|
-
await validator.validateField(person, "address.country")
|
282
|
-
).toEqual({
|
283
|
-
error: undefined,
|
284
|
-
});
|
285
|
-
expect(await validator.validateField(person, "pets[0].animal")).toEqual(
|
286
|
-
{
|
287
|
-
error: undefined,
|
288
|
-
}
|
289
|
-
);
|
290
|
-
expect(await validator.validateField(person, "pets[0].name")).toEqual({
|
291
|
-
error: undefined,
|
292
|
-
});
|
293
|
-
});
|
294
|
-
|
295
|
-
it("should return an error if field is invalid", async () => {
|
296
|
-
const person = {
|
297
|
-
firstName: "John",
|
298
|
-
lastName: {},
|
299
|
-
address: {
|
300
|
-
streetAddress: "123 Main St",
|
301
|
-
city: 1234,
|
302
|
-
},
|
303
|
-
};
|
304
|
-
expect(await validator.validateField(person, "lastName")).toEqual({
|
305
|
-
error: anyString,
|
306
|
-
});
|
307
|
-
});
|
308
|
-
|
309
|
-
it("should return an error if a nested field is invalid", async () => {
|
310
|
-
const person = {
|
311
|
-
firstName: "John",
|
312
|
-
lastName: {},
|
313
|
-
address: {
|
314
|
-
streetAddress: "123 Main St",
|
315
|
-
city: 1234,
|
316
|
-
},
|
317
|
-
pets: [{ animal: "dog" }],
|
318
|
-
};
|
319
|
-
expect(
|
320
|
-
await validator.validateField(person, "address.country")
|
321
|
-
).toEqual({
|
322
|
-
error: anyString,
|
323
|
-
});
|
324
|
-
expect(await validator.validateField(person, "pets[0].name")).toEqual({
|
325
|
-
error: anyString,
|
326
|
-
});
|
327
|
-
});
|
328
|
-
});
|
329
|
-
});
|
330
|
-
});
|