@nolwenture/formik-jsonschema-form 0.23.0 → 0.23.2
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/fields/BooleanField.js +5 -1
- package/dist/fields/FileUploadField.js +1 -1
- package/dist/sampleData/fileUpload.js +1 -0
- package/dist/styledComponents.d.ts +1 -1
- package/dist/styledComponents.js +3 -3
- package/dist/templates/ArrayFieldTemplate.js +9 -1
- package/dist/types.d.ts +11 -0
- package/dist/utils/__tests/createInitialValues.test.js +4 -4
- package/dist/utils/__tests/createValidationSchema.test.js +248 -20
- package/dist/utils/createInitialValues.d.ts +1 -1
- package/dist/utils/createInitialValues.js +3 -4
- package/dist/utils/createValidationSchema.js +37 -18
- package/package.json +1 -1
|
@@ -2,10 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useContext } from "react";
|
|
3
3
|
import { FormRegistry } from "../Form";
|
|
4
4
|
import { useExtractUiSchema, useSetDefault } from "../hooks";
|
|
5
|
+
import { useFormikContext } from "formik";
|
|
5
6
|
export const BooleanField = (props) => {
|
|
6
7
|
const { templates: { FieldTemplate }, components: { FieldInput, BooleanFieldWrapper }, } = useContext(FormRegistry);
|
|
7
8
|
useSetDefault(props);
|
|
8
9
|
const { name, uiSchema } = props;
|
|
9
10
|
const { label, props: inputProps } = useExtractUiSchema(uiSchema);
|
|
10
|
-
|
|
11
|
+
const { values, setValues } = useFormikContext();
|
|
12
|
+
return (_jsx(FieldTemplate, Object.assign({}, props, { "data-fjsf-boolean": name, children: _jsxs(BooleanFieldWrapper, { children: [_jsx(FieldInput, Object.assign({}, inputProps, { name: name, type: "checkbox" })), _jsx("span", { onClick: () => {
|
|
13
|
+
setValues(Object.assign(Object.assign({}, values), { [name]: !(values === null || values === void 0 ? void 0 : values[name]) }));
|
|
14
|
+
}, children: label })] }) })));
|
|
11
15
|
};
|
|
@@ -66,5 +66,5 @@ export const FileUploadField = (props) => {
|
|
|
66
66
|
setFieldValue(name, null);
|
|
67
67
|
}
|
|
68
68
|
});
|
|
69
|
-
return (_jsx(FieldTemplate, Object.assign({}, props, { "data-fjsf-object": name, children: _jsx(FileUploadField, Object.assign({}, inputProps, { name: name, value: value, handleRemoveFile: handleRemoveFile, uploadedFile: uploadedFile, setFieldValue: setFieldValue, handleFileUpload: handleFileUpload, uploadProgress: uploadProgress, removeButton: uiSchema["ui:removeButton"], uploadButtonText: uiSchema["ui:uploadButtonText"], infoText: uiSchema["ui:infoText"] })) })));
|
|
69
|
+
return (_jsx(FieldTemplate, Object.assign({}, props, { "data-fjsf-object": name, children: _jsx(FileUploadField, Object.assign({}, inputProps, { name: name, value: value, handleRemoveFile: handleRemoveFile, uploadedFile: uploadedFile, setFieldValue: setFieldValue, handleFileUpload: handleFileUpload, uploadProgress: uploadProgress, removeButton: uiSchema["ui:removeButton"], uploadButtonText: uiSchema["ui:uploadButtonText"], infoText: uiSchema["ui:infoText"], hideRemoveButton: inputProps === null || inputProps === void 0 ? void 0 : inputProps.removeButton.hidden })) })));
|
|
70
70
|
};
|
|
@@ -246,7 +246,7 @@ export declare const RemoveButton: import("styled-components/dist/types").IStyle
|
|
|
246
246
|
export declare const FileInfoText: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, never>> & string;
|
|
247
247
|
export declare const FileUploadFieldContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
248
248
|
export declare const FileUploadField: {
|
|
249
|
-
({ name, uploadedFile, handleFileUpload, value, setFieldValue, className, uploadProgress, handleRemoveFile, removeButton, uploadButtonText, infoText, }: FileUploadFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
249
|
+
({ name, uploadedFile, handleFileUpload, value, setFieldValue, className, uploadProgress, handleRemoveFile, removeButton, uploadButtonText, infoText, hideRemoveButton, }: FileUploadFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
250
250
|
styles: {
|
|
251
251
|
FileUploadFieldFileUploadFieldContainerStyle: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
252
252
|
FileUploadFieldFileUploadButtonStyle: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, never>> & string;
|
package/dist/styledComponents.js
CHANGED
|
@@ -419,6 +419,7 @@ export const TextAreaInputField = ({ inputProps, name, className, }) => {
|
|
|
419
419
|
};
|
|
420
420
|
export const BooleanFieldWrapper = styled.div `
|
|
421
421
|
display: flex;
|
|
422
|
+
cursor: pointer;
|
|
422
423
|
`;
|
|
423
424
|
const ClickableEl = styled.button `
|
|
424
425
|
background: none;
|
|
@@ -540,16 +541,15 @@ export const FileInfoText = styled.span `
|
|
|
540
541
|
color: #555;
|
|
541
542
|
`;
|
|
542
543
|
export const FileUploadFieldContainer = styled.div ``;
|
|
543
|
-
export const FileUploadField = ({ name, uploadedFile, handleFileUpload, value, setFieldValue, className, uploadProgress, handleRemoveFile, removeButton, uploadButtonText, infoText, }) => {
|
|
544
|
+
export const FileUploadField = ({ name, uploadedFile, handleFileUpload, value, setFieldValue, className, uploadProgress, handleRemoveFile, removeButton, uploadButtonText, infoText, hideRemoveButton, }) => {
|
|
544
545
|
var _a, _b, _c;
|
|
545
|
-
const uiSchema = {};
|
|
546
546
|
return (_jsxs(FileUploadFieldContainer, { className: className, children: [_jsxs("div", { children: [_jsx(FileUploadButton, { type: "button", onClick: () => { var _a; return (_a = document.getElementById(name)) === null || _a === void 0 ? void 0 : _a.click(); }, children: uploadButtonText !== null && uploadButtonText !== void 0 ? uploadButtonText : "Choose File" }), _jsx(HiddenFileInput, { id: name, name: name, type: "file", onChange: (event) => {
|
|
547
547
|
const file = event.currentTarget.files[0];
|
|
548
548
|
if (file) {
|
|
549
549
|
handleFileUpload(file, setFieldValue);
|
|
550
550
|
event.target.value = null;
|
|
551
551
|
}
|
|
552
|
-
} }), !value ? (_jsx(FileInfoText, { children: infoText !== null && infoText !== void 0 ? infoText : "No file chosen" })) : null] }), uploadedFile && value && (_jsx(FileItemContainer, { children: uploadProgress === 100 ? (_jsxs(_Fragment, { children: [_jsx("span", { children: uploadedFile.name }), _jsx(RemoveButton, Object.assign({ type: "button", onClick: () => handleRemoveFile(setFieldValue) }, removeButton === null || removeButton === void 0 ? void 0 : removeButton.props, { style: Object.assign({ color: "red", fontWeight: "bold" }, ((_b = (_a = removeButton === null || removeButton === void 0 ? void 0 : removeButton.props) === null || _a === void 0 ? void 0 : _a.style) !== null && _b !== void 0 ? _b : {})), children: (_c = removeButton === null || removeButton === void 0 ? void 0 : removeButton.content) !== null && _c !== void 0 ? _c : "X" }))] })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: uploadedFile.name }), _jsxs(ProgressBarContainer, { children: [_jsx(ProgressBar, { "$progress": uploadProgress }), _jsxs(ProgressText, { children: [uploadProgress, "%"] })] })] })) }))] }));
|
|
552
|
+
} }), !value ? (_jsx(FileInfoText, { children: infoText !== null && infoText !== void 0 ? infoText : "No file chosen" })) : null] }), uploadedFile && value && (_jsx(FileItemContainer, { children: uploadProgress === 100 ? (_jsxs(_Fragment, { children: [_jsx("span", { children: uploadedFile.name }), !hideRemoveButton && (_jsx(RemoveButton, Object.assign({ type: "button", onClick: () => handleRemoveFile(setFieldValue) }, removeButton === null || removeButton === void 0 ? void 0 : removeButton.props, { style: Object.assign({ color: "red", fontWeight: "bold" }, ((_b = (_a = removeButton === null || removeButton === void 0 ? void 0 : removeButton.props) === null || _a === void 0 ? void 0 : _a.style) !== null && _b !== void 0 ? _b : {})), children: (_c = removeButton === null || removeButton === void 0 ? void 0 : removeButton.content) !== null && _c !== void 0 ? _c : "X" })))] })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: uploadedFile.name }), _jsxs(ProgressBarContainer, { children: [_jsx(ProgressBar, { "$progress": uploadProgress }), _jsxs(ProgressText, { children: [uploadProgress, "%"] })] })] })) }))] }));
|
|
553
553
|
};
|
|
554
554
|
FileUploadField.styles = {
|
|
555
555
|
FileUploadFieldFileUploadFieldContainerStyle: FileUploadFieldContainer,
|
|
@@ -15,6 +15,7 @@ import { FormRegistry } from "../Form";
|
|
|
15
15
|
import { getDescription, getTitle } from "../utils";
|
|
16
16
|
import { useExtractUiSchema } from "../hooks";
|
|
17
17
|
import { RequiredIndicator } from "../styledComponents";
|
|
18
|
+
import { getIn, useFormikContext } from "formik";
|
|
18
19
|
export const ArrayFieldTemplate = (props) => {
|
|
19
20
|
const { components: { ArrayFieldWrapper, ArrayFieldTitle, FieldDescription, ErrorMessage, FieldHelp, InfoFieldButton, }, } = useContext(FormRegistry);
|
|
20
21
|
const { name, uiSchema, children } = props, otherProps = __rest(props, ["name", "uiSchema", "children"]);
|
|
@@ -30,5 +31,12 @@ export const ArrayFieldTemplate = (props) => {
|
|
|
30
31
|
return acc;
|
|
31
32
|
}, {});
|
|
32
33
|
const mergedWrapperProps = Object.assign(Object.assign({}, wrapperProps), dataAttributes);
|
|
33
|
-
|
|
34
|
+
// These lines make sure the error message is a string, which means the error
|
|
35
|
+
// is actually for the whole Array component, not for one of the children.
|
|
36
|
+
const { errors } = useFormikContext();
|
|
37
|
+
const error = getIn(errors, name);
|
|
38
|
+
// In case of array of objects, it will give error message as an object value,
|
|
39
|
+
// which is incorrect value for React, causing UI to crash.
|
|
40
|
+
const errorIsString = typeof error === "string";
|
|
41
|
+
return (_jsxs(ArrayFieldWrapper, Object.assign({}, mergedWrapperProps, { children: [title && (_jsxs(ArrayFieldTitle, Object.assign({}, titleProps, { htmlFor: name, children: [title, (props === null || props === void 0 ? void 0 : props.required) && _jsx(RequiredIndicator, { children: "*" })] }))), info && _jsx(InfoFieldButton, Object.assign({}, info)), description && (_jsx(FieldDescription, Object.assign({}, descriptionProps, { children: description }))), children, help && _jsx(FieldHelp, Object.assign({}, helpProps, { children: help })), errorIsString && _jsx(ErrorMessage, Object.assign({}, errorProps, { name: name }))] })));
|
|
34
42
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FormikContextType, FormikErrors } from "formik";
|
|
2
2
|
import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
|
|
3
3
|
import React, { MouseEventHandler } from "react";
|
|
4
|
+
import Yup from "yup";
|
|
4
5
|
export type FormProps<T> = React.PropsWithChildren<{
|
|
5
6
|
/** If true, disabled prop is passed down to each field on the form */
|
|
6
7
|
disabled?: boolean | undefined;
|
|
@@ -292,8 +293,18 @@ export type FileUploadFieldProps = {
|
|
|
292
293
|
};
|
|
293
294
|
uploadButtonText?: string;
|
|
294
295
|
infoText?: string;
|
|
296
|
+
hideRemoveButton: boolean;
|
|
295
297
|
};
|
|
296
298
|
export type FormValues = {
|
|
297
299
|
[key: string]: string | number | boolean | number[] | string[] | FormValues | FormValues[];
|
|
298
300
|
};
|
|
301
|
+
export type MixedOfProps = {
|
|
302
|
+
itemSchemas: Yup.ISchema<unknown, any, any, any>[];
|
|
303
|
+
additionalItemSchema?: Yup.ISchema<unknown, any, any, any>;
|
|
304
|
+
};
|
|
305
|
+
declare module "yup" {
|
|
306
|
+
interface ArraySchema<TIn extends any[] | null | undefined, TContext, TDefault = undefined, TFlags extends Yup.Flags = ""> extends Yup.Schema<TIn, TContext, TDefault, TFlags> {
|
|
307
|
+
mixedOf(schemas: MixedOfProps): this;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
299
310
|
export {};
|
|
@@ -66,7 +66,7 @@ describe("Create Initial Values", () => {
|
|
|
66
66
|
name: "",
|
|
67
67
|
age: 0,
|
|
68
68
|
number: 0,
|
|
69
|
-
feeling:
|
|
69
|
+
feeling: "",
|
|
70
70
|
arrayOfObject: [],
|
|
71
71
|
nestedObject: {
|
|
72
72
|
hobbies: "",
|
|
@@ -95,17 +95,17 @@ describe("Create Initial Values", () => {
|
|
|
95
95
|
// @ts-expect-error
|
|
96
96
|
// Should only accept boolean false
|
|
97
97
|
booleanIncorrect: true,
|
|
98
|
-
booleanCorrect:
|
|
98
|
+
booleanCorrect: "",
|
|
99
99
|
object: {
|
|
100
100
|
string: "",
|
|
101
101
|
number: 0,
|
|
102
102
|
array: [],
|
|
103
|
-
boolean:
|
|
103
|
+
boolean: "",
|
|
104
104
|
nested: {
|
|
105
105
|
string: "",
|
|
106
106
|
number: 0,
|
|
107
107
|
array: [],
|
|
108
|
-
boolean:
|
|
108
|
+
boolean: "",
|
|
109
109
|
},
|
|
110
110
|
},
|
|
111
111
|
};
|
|
@@ -66,7 +66,11 @@ const simpleExampleSchema = {
|
|
|
66
66
|
lastName: { type: "string", title: "Last name" },
|
|
67
67
|
age: { type: "number", title: "Age" },
|
|
68
68
|
drinksCoffee: { type: "boolean", title: "Do you drink coffee?" },
|
|
69
|
-
simpleArray: {
|
|
69
|
+
simpleArray: {
|
|
70
|
+
type: "array",
|
|
71
|
+
title: "Simple array",
|
|
72
|
+
items: [{ type: "string" }],
|
|
73
|
+
},
|
|
70
74
|
},
|
|
71
75
|
};
|
|
72
76
|
const dependenciesExampleSchema = {
|
|
@@ -260,15 +264,15 @@ const nestedRequiredConditionalExampleSchema = {
|
|
|
260
264
|
],
|
|
261
265
|
};
|
|
262
266
|
describe("Create Validation Schema", () => {
|
|
263
|
-
function listErrors(schema, data) {
|
|
267
|
+
function listErrors(schema, data, definitions) {
|
|
264
268
|
var _a;
|
|
265
|
-
const yupSchema = createValidationSchema(schema);
|
|
269
|
+
const yupSchema = createValidationSchema(schema, definitions);
|
|
266
270
|
try {
|
|
267
271
|
yupSchema.validateSync(data, { abortEarly: false });
|
|
268
272
|
}
|
|
269
273
|
catch (err) {
|
|
270
274
|
const error = err;
|
|
271
|
-
return (_a = error === null || error === void 0 ? void 0 : error.inner) === null || _a === void 0 ? void 0 : _a.map((
|
|
275
|
+
return (_a = error === null || error === void 0 ? void 0 : error.inner) === null || _a === void 0 ? void 0 : _a.map((e, i) => { var _a; return `${e.path}: ${error.errors[i].replace((_a = e === null || e === void 0 ? void 0 : e.path) !== null && _a !== void 0 ? _a : "", "").trim()}`; });
|
|
272
276
|
}
|
|
273
277
|
}
|
|
274
278
|
it("creates yup validationSchema based on given JSON schema", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -332,7 +336,7 @@ describe("Create Validation Schema", () => {
|
|
|
332
336
|
age: 17,
|
|
333
337
|
drinksCoffee: false,
|
|
334
338
|
simpleArray: "Hello :)",
|
|
335
|
-
}, { strict: true })).rejects.toThrow('simpleArray must be a `
|
|
339
|
+
}, { strict: true })).rejects.toThrow('simpleArray must be a `tuple` type, but the final value was: `"Hello :)"`.');
|
|
336
340
|
}));
|
|
337
341
|
describe("Conditionals", () => {
|
|
338
342
|
it("generates validation for dependencies without oneOf", () => {
|
|
@@ -443,11 +447,11 @@ describe("Create Validation Schema", () => {
|
|
|
443
447
|
customerType: "BUSINESS",
|
|
444
448
|
});
|
|
445
449
|
expect(faultyBusiness).toEqual(expect.arrayContaining([
|
|
446
|
-
"address.country",
|
|
447
|
-
"address.city",
|
|
448
|
-
"address.streetName",
|
|
449
|
-
"businessName",
|
|
450
|
-
"code",
|
|
450
|
+
"address.country: Required",
|
|
451
|
+
"address.city: Required",
|
|
452
|
+
"address.streetName: Required",
|
|
453
|
+
"businessName: Required",
|
|
454
|
+
"code: Required",
|
|
451
455
|
]));
|
|
452
456
|
const business = listErrors(allOfExample, {
|
|
453
457
|
customerType: "BUSINESS",
|
|
@@ -464,11 +468,11 @@ describe("Create Validation Schema", () => {
|
|
|
464
468
|
customerType: "INDIVIDUAL",
|
|
465
469
|
});
|
|
466
470
|
expect(faultyIndividual).toEqual(expect.arrayContaining([
|
|
467
|
-
"address.country",
|
|
468
|
-
"address.city",
|
|
469
|
-
"address.streetName",
|
|
470
|
-
"firstName",
|
|
471
|
-
"code",
|
|
471
|
+
"address.country: Required",
|
|
472
|
+
"address.city: Required",
|
|
473
|
+
"address.streetName: Required",
|
|
474
|
+
"firstName: Required",
|
|
475
|
+
"code: Required",
|
|
472
476
|
]));
|
|
473
477
|
const individual = listErrors(allOfExample, {
|
|
474
478
|
customerType: "INDIVIDUAL",
|
|
@@ -485,11 +489,11 @@ describe("Create Validation Schema", () => {
|
|
|
485
489
|
customerType: "UNKNOWN",
|
|
486
490
|
});
|
|
487
491
|
expect(faultyUnknown).toEqual(expect.arrayContaining([
|
|
488
|
-
"address.country",
|
|
489
|
-
"address.city",
|
|
490
|
-
"address.streetName",
|
|
491
|
-
"newCustomer",
|
|
492
|
-
"code",
|
|
492
|
+
"address.country: Required",
|
|
493
|
+
"address.city: Required",
|
|
494
|
+
"address.streetName: Required",
|
|
495
|
+
"newCustomer: Required",
|
|
496
|
+
"code: Required",
|
|
493
497
|
]));
|
|
494
498
|
const unknown = listErrors(allOfExample, {
|
|
495
499
|
customerType: "UNKNOWN",
|
|
@@ -530,4 +534,228 @@ describe("Create Validation Schema", () => {
|
|
|
530
534
|
expect(isValidWhenConditionMetAndRequiredFieldsAreFilled).toEqual(true);
|
|
531
535
|
});
|
|
532
536
|
});
|
|
537
|
+
describe("Array", () => {
|
|
538
|
+
const createArraySchema = ({ items, additionalItems, }) => ({
|
|
539
|
+
title: "Array of objects Schema",
|
|
540
|
+
required: ["arrayOfObjects"],
|
|
541
|
+
properties: {
|
|
542
|
+
arrayOfObjects: Object.assign({ type: "array", title: "A list of fixed items", items }, (additionalItems ? { additionalItems } : {})),
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
it("requires fields with array items and additionalItems", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
546
|
+
const arraySchema = createArraySchema({
|
|
547
|
+
items: [
|
|
548
|
+
{ title: "A string value", type: "string" },
|
|
549
|
+
{ title: "An integer value", type: "integer" },
|
|
550
|
+
{ title: "A number value", type: "number" },
|
|
551
|
+
{
|
|
552
|
+
title: "An arrayContaining value",
|
|
553
|
+
type: "array",
|
|
554
|
+
items: [{ title: "A string value", type: "string" }],
|
|
555
|
+
},
|
|
556
|
+
{ title: "a boolean value", type: "boolean" },
|
|
557
|
+
{
|
|
558
|
+
type: "object",
|
|
559
|
+
title: "User",
|
|
560
|
+
required: ["name"],
|
|
561
|
+
properties: {
|
|
562
|
+
name: { type: "string", title: "Name" },
|
|
563
|
+
age: { type: "number", title: "Age" },
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
],
|
|
567
|
+
additionalItems: {
|
|
568
|
+
title: "Additional item",
|
|
569
|
+
type: "number",
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
const faultyItem1 = listErrors(arraySchema, {
|
|
573
|
+
arrayOfObjects: [
|
|
574
|
+
true,
|
|
575
|
+
123.123,
|
|
576
|
+
456,
|
|
577
|
+
["array"],
|
|
578
|
+
false,
|
|
579
|
+
{ name: "John", age: 27 },
|
|
580
|
+
],
|
|
581
|
+
});
|
|
582
|
+
expect(faultyItem1).toEqual([
|
|
583
|
+
"arrayOfObjects[0]: must be a `string` type, but the final value was: `true`.",
|
|
584
|
+
]);
|
|
585
|
+
const faultyItem2 = listErrors(arraySchema, {
|
|
586
|
+
arrayOfObjects: [
|
|
587
|
+
"string",
|
|
588
|
+
false,
|
|
589
|
+
456,
|
|
590
|
+
["array"],
|
|
591
|
+
false,
|
|
592
|
+
{ name: "John", age: 27 },
|
|
593
|
+
],
|
|
594
|
+
});
|
|
595
|
+
expect(faultyItem2).toEqual([
|
|
596
|
+
"arrayOfObjects[1]: must be a `number` type, but the final value was: `false`.",
|
|
597
|
+
]);
|
|
598
|
+
const faultyItem3 = listErrors(arraySchema, {
|
|
599
|
+
arrayOfObjects: [
|
|
600
|
+
"string",
|
|
601
|
+
123.123,
|
|
602
|
+
"incorrect",
|
|
603
|
+
["array"],
|
|
604
|
+
false,
|
|
605
|
+
{ name: "John", age: 27 },
|
|
606
|
+
],
|
|
607
|
+
});
|
|
608
|
+
expect(faultyItem3).toEqual([
|
|
609
|
+
'arrayOfObjects[2]: must be a `number` type, but the final value was: `"incorrect"`.',
|
|
610
|
+
]);
|
|
611
|
+
const faultyItem4 = listErrors(arraySchema, {
|
|
612
|
+
arrayOfObjects: [
|
|
613
|
+
"string",
|
|
614
|
+
123.123,
|
|
615
|
+
456,
|
|
616
|
+
{ id: 123 },
|
|
617
|
+
false,
|
|
618
|
+
{ name: "John", age: 27 },
|
|
619
|
+
],
|
|
620
|
+
});
|
|
621
|
+
expect(faultyItem4).toEqual([
|
|
622
|
+
'arrayOfObjects[3]: must be a `tuple` type, but the final value was: `{\n "id": "123"\n}`.',
|
|
623
|
+
]);
|
|
624
|
+
const faultyItem5 = listErrors(arraySchema, {
|
|
625
|
+
arrayOfObjects: [
|
|
626
|
+
"string",
|
|
627
|
+
123.123,
|
|
628
|
+
456,
|
|
629
|
+
["array"],
|
|
630
|
+
3.154,
|
|
631
|
+
{ name: "John", age: 27 },
|
|
632
|
+
],
|
|
633
|
+
});
|
|
634
|
+
expect(faultyItem5).toEqual([
|
|
635
|
+
"arrayOfObjects[4]: must be a `boolean` type, but the final value was: `3.154`.",
|
|
636
|
+
]);
|
|
637
|
+
const faultyItem6 = listErrors(arraySchema, {
|
|
638
|
+
arrayOfObjects: [
|
|
639
|
+
"string",
|
|
640
|
+
123.123,
|
|
641
|
+
456,
|
|
642
|
+
["array"],
|
|
643
|
+
false,
|
|
644
|
+
"faulty value",
|
|
645
|
+
],
|
|
646
|
+
});
|
|
647
|
+
expect(faultyItem6).toEqual([
|
|
648
|
+
'arrayOfObjects[5]: must be a `object` type, but the final value was: `"faulty value"`.',
|
|
649
|
+
]);
|
|
650
|
+
const objectPropertyRequired = listErrors(arraySchema, {
|
|
651
|
+
arrayOfObjects: ["string", 123.123, 456, ["array"], false, { age: 27 }],
|
|
652
|
+
});
|
|
653
|
+
expect(objectPropertyRequired).toEqual([
|
|
654
|
+
"arrayOfObjects[5].name: Required",
|
|
655
|
+
]);
|
|
656
|
+
const allRequired = listErrors(arraySchema, {
|
|
657
|
+
arrayOfObjects: [
|
|
658
|
+
undefined,
|
|
659
|
+
undefined,
|
|
660
|
+
undefined,
|
|
661
|
+
[],
|
|
662
|
+
undefined,
|
|
663
|
+
undefined,
|
|
664
|
+
],
|
|
665
|
+
});
|
|
666
|
+
expect(allRequired).toEqual([
|
|
667
|
+
"arrayOfObjects[0]: Required",
|
|
668
|
+
"arrayOfObjects[1]: Required",
|
|
669
|
+
"arrayOfObjects[2]: Required",
|
|
670
|
+
"arrayOfObjects[3]: tuple value has too few items, expected a length of 1 but got 0 for value: `[]`",
|
|
671
|
+
"arrayOfObjects[4]: Required",
|
|
672
|
+
"arrayOfObjects[5]: Required",
|
|
673
|
+
]);
|
|
674
|
+
const correct = listErrors(arraySchema, {
|
|
675
|
+
arrayOfObjects: [
|
|
676
|
+
"string",
|
|
677
|
+
123.123,
|
|
678
|
+
456,
|
|
679
|
+
["array"],
|
|
680
|
+
false,
|
|
681
|
+
{ name: "John", age: 27 },
|
|
682
|
+
],
|
|
683
|
+
});
|
|
684
|
+
expect(correct).toEqual(undefined);
|
|
685
|
+
}));
|
|
686
|
+
it("required fields with array items only", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
687
|
+
const arraySchema = createArraySchema({
|
|
688
|
+
items: [
|
|
689
|
+
{ title: "A string value", type: "string" },
|
|
690
|
+
{ title: "An integer value", type: "integer" },
|
|
691
|
+
{ title: "A boolean value", type: "boolean" },
|
|
692
|
+
{ title: "A number value", type: "number" },
|
|
693
|
+
],
|
|
694
|
+
});
|
|
695
|
+
const faulty = listErrors(arraySchema, {
|
|
696
|
+
arrayOfObjects: ["name", undefined, undefined, false],
|
|
697
|
+
});
|
|
698
|
+
expect(faulty).toEqual([
|
|
699
|
+
"arrayOfObjects[1]: Required",
|
|
700
|
+
"arrayOfObjects[2]: Required",
|
|
701
|
+
"arrayOfObjects[3]: must be a `number` type, but the final value was: `false`.",
|
|
702
|
+
]);
|
|
703
|
+
const coprrecorrect = listErrors(arraySchema, {
|
|
704
|
+
arrayOfObjects: ["name", 123, true, 123.141241],
|
|
705
|
+
});
|
|
706
|
+
expect(coprrecorrect).toEqual(undefined);
|
|
707
|
+
}));
|
|
708
|
+
it.only("requires fields with array items and additionalItems with definitions", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
709
|
+
const arraySchema = {
|
|
710
|
+
title: "TITLE",
|
|
711
|
+
properties: {
|
|
712
|
+
arrayOfDefinitions: {
|
|
713
|
+
type: "array",
|
|
714
|
+
title: "Array of objects",
|
|
715
|
+
items: [{ $ref: "#/definitions/commodity" }],
|
|
716
|
+
additionalItems: { $ref: "#/definitions/commodity" },
|
|
717
|
+
},
|
|
718
|
+
},
|
|
719
|
+
};
|
|
720
|
+
const definitions = {
|
|
721
|
+
commodity: {
|
|
722
|
+
type: "object",
|
|
723
|
+
title: "Item",
|
|
724
|
+
required: ["commodityId"],
|
|
725
|
+
properties: {
|
|
726
|
+
commodityId: {
|
|
727
|
+
$ref: "#/definitions/selection",
|
|
728
|
+
},
|
|
729
|
+
insuredValue: {
|
|
730
|
+
type: "number",
|
|
731
|
+
title: "Insured value in USD",
|
|
732
|
+
},
|
|
733
|
+
},
|
|
734
|
+
},
|
|
735
|
+
selection: {
|
|
736
|
+
type: "string",
|
|
737
|
+
title: "HS Code",
|
|
738
|
+
oneOf: [
|
|
739
|
+
{ const: "1", title: "One" },
|
|
740
|
+
{ const: "2", title: "Two" },
|
|
741
|
+
{ const: "3", title: "Three" },
|
|
742
|
+
],
|
|
743
|
+
},
|
|
744
|
+
};
|
|
745
|
+
const faulty = listErrors(arraySchema, { arrayOfDefinitions: [true, 123, []] }, definitions);
|
|
746
|
+
expect(faulty).toEqual([
|
|
747
|
+
"arrayOfDefinitions[0]: must be a `object` type, but the final value was: `true`.",
|
|
748
|
+
"arrayOfDefinitions[1]: must be a `object` type, but the final value was: `123`.",
|
|
749
|
+
"arrayOfDefinitions[2]: must be a `object` type, but the final value was: `[]`.",
|
|
750
|
+
]);
|
|
751
|
+
const correct = listErrors(arraySchema, {
|
|
752
|
+
arrayOfDefinitions: [
|
|
753
|
+
{ commodityId: "1", insuredValue: "123" },
|
|
754
|
+
{ commodityId: "2", insuredValue: "456" },
|
|
755
|
+
{ commodityId: "3", insuredValue: "789" },
|
|
756
|
+
],
|
|
757
|
+
}, definitions);
|
|
758
|
+
expect(correct).toEqual(undefined);
|
|
759
|
+
}));
|
|
760
|
+
});
|
|
533
761
|
});
|
|
@@ -13,12 +13,11 @@ export function createInitialValues(schema, definitions = {}) {
|
|
|
13
13
|
switch (type) {
|
|
14
14
|
case "array":
|
|
15
15
|
return [];
|
|
16
|
-
case "boolean":
|
|
17
|
-
return false;
|
|
18
|
-
case "null":
|
|
19
|
-
return "";
|
|
20
16
|
case "number":
|
|
21
17
|
return 0;
|
|
18
|
+
// Boolean cannot be true/false by default, since that is a valid value
|
|
19
|
+
case "boolean":
|
|
20
|
+
case "null":
|
|
22
21
|
case "string":
|
|
23
22
|
return "";
|
|
24
23
|
default:
|
|
@@ -12,6 +12,19 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
import * as Yup from "yup";
|
|
13
13
|
import { resolveOneOfValue } from "./resolveSchema";
|
|
14
14
|
import { solveReferences } from "./solveReferences";
|
|
15
|
+
// Difficult to solve
|
|
16
|
+
// @ts-expect-error
|
|
17
|
+
Yup.addMethod(Yup.array, "mixedOf", function ({ itemSchemas, additionalItemSchema }) {
|
|
18
|
+
return Yup.lazy((value) => {
|
|
19
|
+
var _a;
|
|
20
|
+
const valueLength = (_a = value === null || value === void 0 ? void 0 : value.length) !== null && _a !== void 0 ? _a : 0;
|
|
21
|
+
const numOfAdditionalItems = itemSchemas.length > valueLength ? 0 : valueLength - itemSchemas.length;
|
|
22
|
+
const additionalSchemas = additionalItemSchema
|
|
23
|
+
? Array(numOfAdditionalItems).fill(additionalItemSchema)
|
|
24
|
+
: [];
|
|
25
|
+
return Yup.tuple([...itemSchemas, ...additionalSchemas]).typeError("Required");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
15
28
|
function getConditionalValidations(schema) {
|
|
16
29
|
const { dependencies, allOf } = schema;
|
|
17
30
|
let conditionalValidations = [];
|
|
@@ -96,7 +109,8 @@ function getConditionalValidations(schema) {
|
|
|
96
109
|
}
|
|
97
110
|
return conditionalValidations;
|
|
98
111
|
}
|
|
99
|
-
function getYupType(
|
|
112
|
+
function getYupType(schema) {
|
|
113
|
+
const type = schema === null || schema === void 0 ? void 0 : schema.type;
|
|
100
114
|
switch (type) {
|
|
101
115
|
case "object":
|
|
102
116
|
return Yup.object();
|
|
@@ -112,7 +126,8 @@ function getYupType(type) {
|
|
|
112
126
|
case "string":
|
|
113
127
|
return Yup.string();
|
|
114
128
|
default:
|
|
115
|
-
|
|
129
|
+
console.error({ schema });
|
|
130
|
+
throw new Error(`Unsupported schema type: ${type}`);
|
|
116
131
|
}
|
|
117
132
|
}
|
|
118
133
|
export function createValidationSchema(schema, definitions = {}, conditionsContext = {}, condition) {
|
|
@@ -122,38 +137,43 @@ export function createValidationSchema(schema, definitions = {}, conditionsConte
|
|
|
122
137
|
}
|
|
123
138
|
const schemaDefinitions = Object.assign(Object.assign({}, definitions), ((_a = schema.definitions) !== null && _a !== void 0 ? _a : {}));
|
|
124
139
|
const requiredFields = schema.required;
|
|
125
|
-
function getValue(
|
|
126
|
-
var _a;
|
|
140
|
+
function getValue(givenSchema, key, required) {
|
|
141
|
+
var _a, _b;
|
|
142
|
+
const { schema } = solveReferences(givenSchema, schemaDefinitions, {}, {});
|
|
127
143
|
let type = schema.type;
|
|
128
144
|
let yupSchema;
|
|
129
145
|
if (schema.properties) {
|
|
130
146
|
yupSchema = createValidationSchema(schema, schemaDefinitions, conditionsContext);
|
|
131
147
|
type = type !== null && type !== void 0 ? type : "object";
|
|
132
148
|
}
|
|
149
|
+
else if (type === "array") {
|
|
150
|
+
const schemaItems = (_a = schema === null || schema === void 0 ? void 0 : schema.items) !== null && _a !== void 0 ? _a : [];
|
|
151
|
+
const items = Array.isArray(schemaItems) ? schemaItems : [schemaItems];
|
|
152
|
+
yupSchema = Yup.array().mixedOf({
|
|
153
|
+
itemSchemas: items.map((item) => getValue(item, "#ignore-this", true)),
|
|
154
|
+
additionalItemSchema: (schema === null || schema === void 0 ? void 0 : schema.additionalItems)
|
|
155
|
+
? getValue(schema === null || schema === void 0 ? void 0 : schema.additionalItems, "#ignore-this", true)
|
|
156
|
+
: undefined,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
133
159
|
else {
|
|
134
|
-
yupSchema = getYupType(
|
|
160
|
+
yupSchema = getYupType(schema);
|
|
135
161
|
}
|
|
136
162
|
if (type === "null")
|
|
137
163
|
yupSchema.nullable();
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
.min(1, "Required")
|
|
142
|
-
.required("Required");
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
yupSchema = yupSchema["required"]("Required");
|
|
146
|
-
}
|
|
164
|
+
const isRequired = (requiredFields === null || requiredFields === void 0 ? void 0 : requiredFields.includes(key)) || required === true;
|
|
165
|
+
if (isRequired && type !== "array") {
|
|
166
|
+
yupSchema = yupSchema.required("Required");
|
|
147
167
|
}
|
|
148
168
|
if (!condition) {
|
|
149
169
|
return yupSchema;
|
|
150
170
|
}
|
|
151
|
-
const existing = (
|
|
171
|
+
const existing = (_b = conditionsContext[key]) !== null && _b !== void 0 ? _b : [];
|
|
152
172
|
conditionsContext[key] = [...existing, { yupSchema, condition }];
|
|
153
173
|
// TODO: The latest given type for duplicate field is used.
|
|
154
174
|
// This might become a problem at some point, but testing has not
|
|
155
175
|
// shown any problems so far.
|
|
156
|
-
const yupType = getYupType(
|
|
176
|
+
const yupType = getYupType(schema).when(condition.dependentKeys, {
|
|
157
177
|
is: condition.conditionFn,
|
|
158
178
|
then: () => yupSchema,
|
|
159
179
|
});
|
|
@@ -172,8 +192,7 @@ export function createValidationSchema(schema, definitions = {}, conditionsConte
|
|
|
172
192
|
const baseValidationSchema = Object.keys(schema.properties).reduce((validationSchema, key) => {
|
|
173
193
|
var _a;
|
|
174
194
|
const value = (_a = schema.properties) === null || _a === void 0 ? void 0 : _a[key];
|
|
175
|
-
|
|
176
|
-
return validationSchema.concat(Yup.object().shape({ [key]: getValue(solvedValue.schema, key) }));
|
|
195
|
+
return validationSchema.concat(Yup.object().shape({ [key]: getValue(value, key) }));
|
|
177
196
|
}, Yup.object().shape({}));
|
|
178
197
|
return getConditionalValidations(schema).reduce((validationSchema, conditionalValidation) => {
|
|
179
198
|
const solvedSchemas = solveReferences(conditionalValidation.schema, schemaDefinitions, {}, {});
|