remix-validated-form 3.0.0-beta.1 → 3.0.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.
@@ -1,36 +1,11 @@
1
1
  $ jest src
2
-  PASS  src/validation/validation.test.ts
3
- Validation
4
- Adapter for yup
5
- validate
6
- ✓ should return the data when valid (2 ms)
7
- ✓ should return field errors when invalid (1 ms)
8
- ✓ should unflatten data when validating
9
- ✓ should accept FormData directly and return errors (1 ms)
10
- ✓ should accept FormData directly and return valid data (1 ms)
11
- validateField
12
- ✓ should not return an error if field is valid
13
- ✓ should not return an error if a nested field is valid (1 ms)
14
- ✓ should return an error if field is invalid (2 ms)
15
- ✓ should return an error if a nested field is invalid
16
- Adapter for zod
17
- validate
18
- ✓ should return the data when valid (1 ms)
19
- ✓ should return field errors when invalid
20
- ✓ should unflatten data when validating (1 ms)
21
- ✓ should accept FormData directly and return errors
22
- ✓ should accept FormData directly and return valid data
23
- validateField
24
- ✓ should not return an error if field is valid (1 ms)
25
- ✓ should not return an error if a nested field is valid
26
- ✓ should return an error if field is invalid
27
- ✓ should return an error if a nested field is invalid
28
- withZod
29
- ✓ returns coherent errors for complex schemas (1 ms)
30
- ✓ returns errors for fields that are unions
31
-
32
- Test Suites: 1 passed, 1 total
33
- Tests: 20 passed, 20 total
34
- Snapshots: 0 total
35
- Time: 1.1 s, estimated 2 s
36
- Ran all test suites matching /src/i.
2
+ No tests found, exiting with code 1
3
+ Run with `--passWithNoTests` to exit with code 0
4
+ In /Users/aaronpettengill/dev/remix-validated-form/packages/remix-validated-form
5
+ 68 files checked.
6
+ testMatch: **/__tests__/**/*.[jt]s?(x), **/?(*.)+(spec|test).[tj]s?(x) - 2 matches
7
+ testPathIgnorePatterns: /node_modules/ - 68 matches
8
+ testRegex: - 0 matches
9
+ Pattern: src - 0 matches
10
+ info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
11
+ error Command failed with exit code 1.
package/README.md CHANGED
@@ -27,10 +27,25 @@ yarn sample-app
27
27
 
28
28
  ## Install
29
29
 
30
+ ### Base package
31
+
30
32
  ```bash
31
33
  npm install remix-validated-form
32
34
  ```
33
35
 
36
+ ### Validation library adapter
37
+
38
+ There are official adapters available for `zod` and `yup`.
39
+ If you're using a different library,
40
+ see the [Validation library support](#validation-library-support) section below.
41
+
42
+ - @remix-validated-form/with-zod
43
+ - @remix-validated-form/with-yup
44
+
45
+ ```bash
46
+ npm install @remix-validated-form/with-zod
47
+ ```
48
+
34
49
  ## Create an input component
35
50
 
36
51
  In order to display field errors or do field-by-field validation,
@@ -164,10 +179,10 @@ export default function MyForm() {
164
179
 
165
180
  # Validation Library Support
166
181
 
167
- This library currently includes an out-of-the-box adapter for `yup` and `zod`,
182
+ There are official adapters available for `zod` and `yup` ,
168
183
  but you can easily support whatever library you want by creating your own adapter.
169
184
 
170
- And if you create an adapter for a library, feel free to make a PR on this library to add official support 😊
185
+ And if you create an adapter for a library, feel free to make a PR on this repository 😊
171
186
 
172
187
  ## Creating an adapter
173
188
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remix-validated-form",
3
- "version": "3.0.0-beta.1",
3
+ "version": "3.0.0",
4
4
  "description": "Form component and utils for easy form validation in remix",
5
5
  "browser": "./browser/index.js",
6
6
  "main": "./build/index.js",
@@ -14,8 +14,6 @@
14
14
  "build": "npm run build:browser && npm run build:main",
15
15
  "build:browser": "tsc --module ESNext --outDir ./browser",
16
16
  "build:main": "tsc --module CommonJS --outDir ./build",
17
- "test": "jest src",
18
- "test:watch": "jest src --watch",
19
17
  "prepublishOnly": "cp ../../README.md ./README.md && npm run build",
20
18
  "postpublish": "rm ./README.md"
21
19
  },
@@ -1,15 +0,0 @@
1
- export declare class TestFormData implements FormData {
2
- private _params;
3
- constructor(body?: string);
4
- append(name: string, value: string | Blob, fileName?: string): void;
5
- delete(name: string): void;
6
- get(name: string): FormDataEntryValue | null;
7
- getAll(name: string): FormDataEntryValue[];
8
- has(name: string): boolean;
9
- set(name: string, value: string | Blob, fileName?: string): void;
10
- forEach(callbackfn: (value: FormDataEntryValue, key: string, parent: FormData) => void, thisArg?: any): void;
11
- entries(): IterableIterator<[string, FormDataEntryValue]>;
12
- keys(): IterableIterator<string>;
13
- values(): IterableIterator<FormDataEntryValue>;
14
- [Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>;
15
- }
@@ -1,50 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TestFormData = void 0;
4
- // Copied from remix to use in tests
5
- // https://github.com/remix-run/remix/blob/a69a631cb5add72d5fb24211ab2a0be367b6f2fd/packages/remix-node/form-data.ts
6
- class TestFormData {
7
- constructor(body) {
8
- this._params = new URLSearchParams(body);
9
- }
10
- append(name, value, fileName) {
11
- if (typeof value !== "string") {
12
- throw new Error("formData.append can only accept a string");
13
- }
14
- this._params.append(name, value);
15
- }
16
- delete(name) {
17
- this._params.delete(name);
18
- }
19
- get(name) {
20
- return this._params.get(name);
21
- }
22
- getAll(name) {
23
- return this._params.getAll(name);
24
- }
25
- has(name) {
26
- return this._params.has(name);
27
- }
28
- set(name, value, fileName) {
29
- if (typeof value !== "string") {
30
- throw new Error("formData.set can only accept a string");
31
- }
32
- this._params.set(name, value);
33
- }
34
- forEach(callbackfn, thisArg) {
35
- this._params.forEach(callbackfn, thisArg);
36
- }
37
- entries() {
38
- return this._params.entries();
39
- }
40
- keys() {
41
- return this._params.keys();
42
- }
43
- values() {
44
- return this._params.values();
45
- }
46
- *[Symbol.iterator]() {
47
- yield* this._params;
48
- }
49
- }
50
- exports.TestFormData = TestFormData;
@@ -1 +0,0 @@
1
- export {};
@@ -1,295 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
- }) : (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- o[k2] = m[k];
8
- }));
9
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
- Object.defineProperty(o, "default", { enumerable: true, value: v });
11
- }) : function(o, v) {
12
- o["default"] = v;
13
- });
14
- var __importStar = (this && this.__importStar) || function (mod) {
15
- if (mod && mod.__esModule) return mod;
16
- var result = {};
17
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
- __setModuleDefault(result, mod);
19
- return result;
20
- };
21
- Object.defineProperty(exports, "__esModule", { value: true });
22
- const yup = __importStar(require("yup"));
23
- const zod_1 = require("zod");
24
- const __1 = require("..");
25
- const flatten_1 = require("../internal/flatten");
26
- const testFormData_1 = require("../test-data/testFormData");
27
- const withZod_1 = require("./withZod");
28
- const validationTestCases = [
29
- {
30
- name: "yup",
31
- validator: (0, __1.withYup)(yup.object({
32
- firstName: yup.string().required(),
33
- lastName: yup.string().required(),
34
- age: yup.number(),
35
- address: yup
36
- .object({
37
- streetAddress: yup.string().required(),
38
- city: yup.string().required(),
39
- country: yup.string().required(),
40
- })
41
- .required(),
42
- pets: yup.array().of(yup.object({
43
- animal: yup.string().required(),
44
- name: yup.string().required(),
45
- })),
46
- })),
47
- },
48
- {
49
- name: "zod",
50
- validator: (0, withZod_1.withZod)(zod_1.z.object({
51
- firstName: zod_1.z.string().nonempty(),
52
- lastName: zod_1.z.string().nonempty(),
53
- age: zod_1.z.optional(zod_1.z.number()),
54
- address: zod_1.z.preprocess((value) => (value == null ? {} : value), zod_1.z.object({
55
- streetAddress: zod_1.z.string().nonempty(),
56
- city: zod_1.z.string().nonempty(),
57
- country: zod_1.z.string().nonempty(),
58
- })),
59
- pets: zod_1.z
60
- .object({
61
- animal: zod_1.z.string().nonempty(),
62
- name: zod_1.z.string().nonempty(),
63
- })
64
- .array()
65
- .optional(),
66
- })),
67
- },
68
- ];
69
- // Not going to enforce exact error strings here
70
- const anyString = expect.any(String);
71
- describe("Validation", () => {
72
- describe.each(validationTestCases)("Adapter for $name", ({ validator }) => {
73
- describe("validate", () => {
74
- it("should return the data when valid", () => {
75
- const person = {
76
- firstName: "John",
77
- lastName: "Doe",
78
- age: 30,
79
- address: {
80
- streetAddress: "123 Main St",
81
- city: "Anytown",
82
- country: "USA",
83
- },
84
- pets: [{ animal: "dog", name: "Fido" }],
85
- };
86
- expect(validator.validate(person)).toEqual({
87
- data: person,
88
- error: undefined,
89
- });
90
- });
91
- it("should return field errors when invalid", () => {
92
- const obj = { age: "hi!", pets: [{ animal: "dog" }] };
93
- expect(validator.validate(obj)).toEqual({
94
- data: undefined,
95
- error: {
96
- firstName: anyString,
97
- lastName: anyString,
98
- age: anyString,
99
- "address.city": anyString,
100
- "address.country": anyString,
101
- "address.streetAddress": anyString,
102
- "pets[0].name": anyString,
103
- _submittedData: obj,
104
- },
105
- });
106
- });
107
- it("should unflatten data when validating", () => {
108
- const data = {
109
- firstName: "John",
110
- lastName: "Doe",
111
- age: 30,
112
- "address.streetAddress": "123 Main St",
113
- "address.city": "Anytown",
114
- "address.country": "USA",
115
- "pets[0].animal": "dog",
116
- "pets[0].name": "Fido",
117
- };
118
- expect(validator.validate(data)).toEqual({
119
- data: {
120
- firstName: "John",
121
- lastName: "Doe",
122
- age: 30,
123
- address: {
124
- streetAddress: "123 Main St",
125
- city: "Anytown",
126
- country: "USA",
127
- },
128
- pets: [{ animal: "dog", name: "Fido" }],
129
- },
130
- error: undefined,
131
- });
132
- });
133
- it("should accept FormData directly and return errors", () => {
134
- const formData = new testFormData_1.TestFormData();
135
- formData.set("firstName", "John");
136
- formData.set("lastName", "Doe");
137
- formData.set("address.streetAddress", "123 Main St");
138
- formData.set("address.country", "USA");
139
- formData.set("pets[0].animal", "dog");
140
- expect(validator.validate(formData)).toEqual({
141
- data: undefined,
142
- error: {
143
- "address.city": anyString,
144
- "pets[0].name": anyString,
145
- _submittedData: (0, flatten_1.objectFromPathEntries)([...formData.entries()]),
146
- },
147
- });
148
- });
149
- it("should accept FormData directly and return valid data", () => {
150
- const formData = new testFormData_1.TestFormData();
151
- formData.set("firstName", "John");
152
- formData.set("lastName", "Doe");
153
- formData.set("address.streetAddress", "123 Main St");
154
- formData.set("address.country", "USA");
155
- formData.set("address.city", "Anytown");
156
- formData.set("pets[0].animal", "dog");
157
- formData.set("pets[0].name", "Fido");
158
- expect(validator.validate(formData)).toEqual({
159
- data: {
160
- firstName: "John",
161
- lastName: "Doe",
162
- address: {
163
- streetAddress: "123 Main St",
164
- country: "USA",
165
- city: "Anytown",
166
- },
167
- pets: [{ animal: "dog", name: "Fido" }],
168
- },
169
- error: undefined,
170
- });
171
- });
172
- });
173
- describe("validateField", () => {
174
- it("should not return an error if field is valid", () => {
175
- const person = {
176
- firstName: "John",
177
- lastName: {}, // invalid, but we should only be validating firstName
178
- };
179
- expect(validator.validateField(person, "firstName")).toEqual({
180
- error: undefined,
181
- });
182
- });
183
- it("should not return an error if a nested field is valid", () => {
184
- const person = {
185
- firstName: "John",
186
- lastName: {},
187
- address: {
188
- streetAddress: "123 Main St",
189
- city: "Anytown",
190
- country: "USA",
191
- },
192
- pets: [{ animal: "dog", name: "Fido" }],
193
- };
194
- expect(validator.validateField(person, "address.streetAddress")).toEqual({
195
- error: undefined,
196
- });
197
- expect(validator.validateField(person, "address.city")).toEqual({
198
- error: undefined,
199
- });
200
- expect(validator.validateField(person, "address.country")).toEqual({
201
- error: undefined,
202
- });
203
- expect(validator.validateField(person, "pets[0].animal")).toEqual({
204
- error: undefined,
205
- });
206
- expect(validator.validateField(person, "pets[0].name")).toEqual({
207
- error: undefined,
208
- });
209
- });
210
- it("should return an error if field is invalid", () => {
211
- const person = {
212
- firstName: "John",
213
- lastName: {},
214
- address: {
215
- streetAddress: "123 Main St",
216
- city: 1234,
217
- },
218
- };
219
- expect(validator.validateField(person, "lastName")).toEqual({
220
- error: anyString,
221
- });
222
- });
223
- it("should return an error if a nested field is invalid", () => {
224
- const person = {
225
- firstName: "John",
226
- lastName: {},
227
- address: {
228
- streetAddress: "123 Main St",
229
- city: 1234,
230
- },
231
- pets: [{ animal: "dog" }],
232
- };
233
- expect(validator.validateField(person, "address.country")).toEqual({
234
- error: anyString,
235
- });
236
- expect(validator.validateField(person, "pets[0].name")).toEqual({
237
- error: anyString,
238
- });
239
- });
240
- });
241
- });
242
- });
243
- describe("withZod", () => {
244
- it("returns coherent errors for complex schemas", () => {
245
- const schema = zod_1.z.union([
246
- zod_1.z.object({
247
- type: zod_1.z.literal("foo"),
248
- foo: zod_1.z.string(),
249
- }),
250
- zod_1.z.object({
251
- type: zod_1.z.literal("bar"),
252
- bar: zod_1.z.string(),
253
- }),
254
- ]);
255
- const obj = {
256
- type: "foo",
257
- bar: 123,
258
- foo: 123,
259
- };
260
- expect((0, withZod_1.withZod)(schema).validate(obj)).toEqual({
261
- data: undefined,
262
- error: {
263
- type: anyString,
264
- bar: anyString,
265
- foo: anyString,
266
- _submittedData: obj,
267
- },
268
- });
269
- });
270
- it("returns errors for fields that are unions", () => {
271
- const schema = zod_1.z.object({
272
- field1: zod_1.z.union([zod_1.z.literal("foo"), zod_1.z.literal("bar")]),
273
- field2: zod_1.z.union([zod_1.z.literal("foo"), zod_1.z.literal("bar")]),
274
- });
275
- const obj = {
276
- field1: "a value",
277
- // field2 missing
278
- };
279
- const validator = (0, withZod_1.withZod)(schema);
280
- expect(validator.validate(obj)).toEqual({
281
- data: undefined,
282
- error: {
283
- field1: anyString,
284
- field2: anyString,
285
- _submittedData: obj,
286
- },
287
- });
288
- expect(validator.validateField(obj, "field1")).toEqual({
289
- error: anyString,
290
- });
291
- expect(validator.validateField(obj, "field2")).toEqual({
292
- error: anyString,
293
- });
294
- });
295
- });
@@ -1,6 +0,0 @@
1
- import type { AnyObjectSchema, InferType } from "yup";
2
- import { Validator } from "./types";
3
- /**
4
- * Create a `Validator` using a `yup` schema.
5
- */
6
- export declare const withYup: <Schema extends AnyObjectSchema>(validationSchema: Schema) => Validator<InferType<Schema>>;
@@ -1,44 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.withYup = void 0;
4
- const createValidator_1 = require("./createValidator");
5
- const validationErrorToFieldErrors = (error) => {
6
- const fieldErrors = {};
7
- error.inner.forEach((innerError) => {
8
- if (!innerError.path)
9
- return;
10
- fieldErrors[innerError.path] = innerError.message;
11
- });
12
- return fieldErrors;
13
- };
14
- /**
15
- * Create a `Validator` using a `yup` schema.
16
- */
17
- const withYup = (validationSchema) => {
18
- return (0, createValidator_1.createValidator)({
19
- validate: (data) => {
20
- try {
21
- const validated = validationSchema.validateSync(data, {
22
- abortEarly: false,
23
- });
24
- return { data: validated, error: undefined };
25
- }
26
- catch (err) {
27
- return {
28
- error: validationErrorToFieldErrors(err),
29
- data: undefined,
30
- };
31
- }
32
- },
33
- validateField: (data, field) => {
34
- try {
35
- validationSchema.validateSyncAt(field, data);
36
- return {};
37
- }
38
- catch (err) {
39
- return { error: err.message };
40
- }
41
- },
42
- });
43
- };
44
- exports.withYup = withYup;
@@ -1,6 +0,0 @@
1
- import type { z } from "zod";
2
- import { Validator } from "..";
3
- /**
4
- * Create a validator using a `zod` schema.
5
- */
6
- export declare function withZod<T>(zodSchema: z.Schema<T>): Validator<T>;
@@ -1,57 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.withZod = void 0;
7
- const isEqual_1 = __importDefault(require("lodash/isEqual"));
8
- const toPath_1 = __importDefault(require("lodash/toPath"));
9
- const createValidator_1 = require("./createValidator");
10
- const getIssuesForError = (err) => {
11
- return err.issues.flatMap((issue) => {
12
- if ("unionErrors" in issue) {
13
- return issue.unionErrors.flatMap((err) => getIssuesForError(err));
14
- }
15
- else {
16
- return [issue];
17
- }
18
- });
19
- };
20
- function pathToString(array) {
21
- return array.reduce(function (string, item) {
22
- var prefix = string === "" ? "" : ".";
23
- return string + (isNaN(Number(item)) ? prefix + item : "[" + item + "]");
24
- }, "");
25
- }
26
- /**
27
- * Create a validator using a `zod` schema.
28
- */
29
- function withZod(zodSchema) {
30
- return (0, createValidator_1.createValidator)({
31
- validate: (value) => {
32
- const result = zodSchema.safeParse(value);
33
- if (result.success)
34
- return { data: result.data, error: undefined };
35
- const fieldErrors = {};
36
- getIssuesForError(result.error).forEach((issue) => {
37
- const path = pathToString(issue.path);
38
- if (!fieldErrors[path])
39
- fieldErrors[path] = issue.message;
40
- });
41
- return { error: fieldErrors, data: undefined };
42
- },
43
- validateField: (data, field) => {
44
- var _a;
45
- const result = zodSchema.safeParse(data);
46
- if (result.success)
47
- return { error: undefined };
48
- return {
49
- error: (_a = getIssuesForError(result.error).find((issue) => {
50
- const allPathsAsString = issue.path.map((p) => `${p}`);
51
- return (0, isEqual_1.default)(allPathsAsString, (0, toPath_1.default)(field));
52
- })) === null || _a === void 0 ? void 0 : _a.message,
53
- };
54
- },
55
- });
56
- }
57
- exports.withZod = withZod;