remix-validated-form 5.0.0 → 5.0.2

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remix-validated-form",
3
- "version": "5.0.0",
3
+ "version": "5.0.2",
4
4
  "description": "Form component and utils for easy form validation in remix",
5
5
  "main": "./dist/index.cjs.js",
6
6
  "module": "./dist/index.esm.js",
@@ -51,7 +51,7 @@ type SubactionData<
51
51
  // Not all validation libraries support encoding a literal value in the schema type (e.g. yup).
52
52
  // This condition here allows us to provide strictness for users who are using a validation library that does support it,
53
53
  // but also allows us to support users who are using a validation library that doesn't support it.
54
- type DefaultValuesForSubaction<
54
+ type DataForSubaction<
55
55
  DataType,
56
56
  Subaction extends string | undefined
57
57
  > = Subaction extends string // Not all validation libraries support encoding a literal value in the schema type.
@@ -70,7 +70,7 @@ export type FormProps<DataType, Subaction extends string | undefined> = {
70
70
  * after all validations have been run.
71
71
  */
72
72
  onSubmit?: (
73
- data: DataType,
73
+ data: DataForSubaction<DataType, Subaction>,
74
74
  event: React.FormEvent<HTMLFormElement>
75
75
  ) => void | Promise<void>;
76
76
  /**
@@ -83,7 +83,7 @@ export type FormProps<DataType, Subaction extends string | undefined> = {
83
83
  * Accepts an object of default values for the form
84
84
  * that will automatically be propagated to the form fields via `useField`.
85
85
  */
86
- defaultValues?: Partial<DefaultValuesForSubaction<DataType, Subaction>>;
86
+ defaultValues?: Partial<DataForSubaction<DataType, Subaction>>;
87
87
  /**
88
88
  * A ref to the form element.
89
89
  */
@@ -362,7 +362,7 @@ export function ValidatedForm<DataType, Subaction extends string | undefined>({
362
362
  } else {
363
363
  setFieldErrors({});
364
364
  const eventProxy = formEventProxy(e);
365
- await onSubmit?.(result.data, eventProxy);
365
+ await onSubmit?.(result.data as any, eventProxy);
366
366
  if (eventProxy.defaultPrevented) {
367
367
  endSubmit();
368
368
  return;
@@ -110,7 +110,7 @@ export const useHasActiveFormSubmit = ({
110
110
  let navigation = useNavigation();
111
111
  const hasActiveSubmission = fetcher
112
112
  ? fetcher.state === "submitting"
113
- : navigation.state === "submitting";
113
+ : navigation.state === "submitting" || navigation.state === "loading";
114
114
  return hasActiveSubmission;
115
115
  };
116
116
 
package/tsconfig.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "module": "esnext"
5
5
  },
6
6
  "include": ["src/**/*.ts", "src/**/*.tsx"],
7
- "exclude": ["node_modules", "**/*.test.ts", "**/*.test.tsx"]
7
+ "exclude": ["node_modules"]
8
8
  }
@@ -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
- });