@nolwenture/formik-jsonschema-form 0.22.4 → 0.22.5

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.
@@ -159,6 +159,77 @@ const ifThenElseExampleSchema = {
159
159
  },
160
160
  ],
161
161
  };
162
+ const allOfExample = {
163
+ type: "object",
164
+ properties: {
165
+ customerType: { type: "string" },
166
+ },
167
+ allOf: [
168
+ {
169
+ if: { properties: { customerType: { const: "BUSINESS" } } },
170
+ then: {
171
+ properties: {
172
+ businessName: { type: "string", title: "Company name" },
173
+ code: { type: "string", title: "Code" },
174
+ notRequired: { type: "string", title: "Not required" },
175
+ address: { $ref: "#/definitions/address" },
176
+ },
177
+ required: ["businessName", "code", "address"],
178
+ },
179
+ },
180
+ {
181
+ if: { properties: { customerType: { const: "INDIVIDUAL" } } },
182
+ then: {
183
+ properties: {
184
+ firstName: { type: "string", title: "First name" },
185
+ code: { type: "number", title: "Code" },
186
+ address: { $ref: "#/definitions/address" },
187
+ },
188
+ required: ["firstName", "code", "address"],
189
+ },
190
+ },
191
+ {
192
+ if: { properties: { customerType: { const: "UNKNOWN" } } },
193
+ then: {
194
+ properties: {
195
+ newCustomer: { type: "boolean", title: "New customer" },
196
+ code: { type: "boolean", title: "Code" },
197
+ extraField: { type: "number", title: "Extra field" },
198
+ address: { $ref: "#/definitions/address" },
199
+ },
200
+ required: ["newCustomer", "code", "address"],
201
+ },
202
+ },
203
+ ],
204
+ definitions: {
205
+ address: {
206
+ type: "object",
207
+ required: ["streetName", "city", "country"],
208
+ properties: {
209
+ streetName: {
210
+ type: "string",
211
+ title: "Street name",
212
+ },
213
+ city: {
214
+ type: "string",
215
+ title: "Town/City",
216
+ },
217
+ postcode: {
218
+ type: "string",
219
+ title: "Postcode",
220
+ },
221
+ state: {
222
+ type: "string",
223
+ title: "State/Province/Region",
224
+ },
225
+ country: {
226
+ type: "string",
227
+ title: "Country",
228
+ },
229
+ },
230
+ },
231
+ },
232
+ };
162
233
  const nestedRequiredConditionalExampleSchema = {
163
234
  type: "object",
164
235
  properties: {
@@ -189,6 +260,17 @@ const nestedRequiredConditionalExampleSchema = {
189
260
  ],
190
261
  };
191
262
  describe("Create Validation Schema", () => {
263
+ function listErrors(schema, data) {
264
+ var _a;
265
+ const yupSchema = createValidationSchema(schema);
266
+ try {
267
+ yupSchema.validateSync(data, { abortEarly: false });
268
+ }
269
+ catch (err) {
270
+ const error = err;
271
+ return (_a = error === null || error === void 0 ? void 0 : error.inner) === null || _a === void 0 ? void 0 : _a.map((aa) => aa.path);
272
+ }
273
+ }
192
274
  it("creates yup validationSchema based on given JSON schema", () => __awaiter(void 0, void 0, void 0, function* () {
193
275
  const result = createValidationSchema(exampleSchema);
194
276
  const expected = result.isValidSync({
@@ -356,6 +438,76 @@ describe("Create Validation Schema", () => {
356
438
  expect(firstTrueInvalid3).toEqual(false);
357
439
  expect(firstTrueValid).toEqual(true);
358
440
  });
441
+ it("generates validation for if-then", () => __awaiter(void 0, void 0, void 0, function* () {
442
+ const faultyBusiness = listErrors(allOfExample, {
443
+ customerType: "BUSINESS",
444
+ });
445
+ expect(faultyBusiness).toEqual(expect.arrayContaining([
446
+ "address.country",
447
+ "address.city",
448
+ "address.streetName",
449
+ "businessName",
450
+ "code",
451
+ ]));
452
+ const business = listErrors(allOfExample, {
453
+ customerType: "BUSINESS",
454
+ businessName: ".",
455
+ code: ".",
456
+ address: {
457
+ streetName: ".",
458
+ city: ".",
459
+ country: ".",
460
+ },
461
+ });
462
+ expect(business).toEqual(undefined);
463
+ const faultyIndividual = listErrors(allOfExample, {
464
+ customerType: "INDIVIDUAL",
465
+ });
466
+ expect(faultyIndividual).toEqual(expect.arrayContaining([
467
+ "address.country",
468
+ "address.city",
469
+ "address.streetName",
470
+ "firstName",
471
+ "code",
472
+ ]));
473
+ const individual = listErrors(allOfExample, {
474
+ customerType: "INDIVIDUAL",
475
+ firstName: ".",
476
+ code: 123,
477
+ address: {
478
+ streetName: ".",
479
+ city: ".",
480
+ country: ".",
481
+ },
482
+ });
483
+ expect(individual).toEqual(undefined);
484
+ const faultyUnknown = listErrors(allOfExample, {
485
+ customerType: "UNKNOWN",
486
+ });
487
+ expect(faultyUnknown).toEqual(expect.arrayContaining([
488
+ "address.country",
489
+ "address.city",
490
+ "address.streetName",
491
+ "newCustomer",
492
+ "code",
493
+ ]));
494
+ const unknown = listErrors(allOfExample, {
495
+ customerType: "UNKNOWN",
496
+ newCustomer: true,
497
+ code: false,
498
+ address: {
499
+ streetName: ".",
500
+ city: ".",
501
+ country: ".",
502
+ },
503
+ });
504
+ expect(unknown).toEqual(undefined);
505
+ const yupSchema = createValidationSchema(allOfExample);
506
+ // Makes sure the context resets between function calls.
507
+ // eslint-disable-next-line
508
+ // @ts-expect-error
509
+ expect(yupSchema.fields.code.deps.length).toEqual(3);
510
+ }));
359
511
  it("generates validation for conditional objects with nested validations", () => {
360
512
  const result = createValidationSchema(nestedRequiredConditionalExampleSchema);
361
513
  const isValidWhenConditionNotNet = result.isValidSync({
@@ -4,5 +4,9 @@ type Condition = {
4
4
  dependentKeys: string[];
5
5
  conditionFn: (...value: any[]) => boolean;
6
6
  };
7
- export declare function createValidationSchema(schema: Schema, definitions?: Schema, condition?: Condition): Yup.ObjectSchema<{}, Yup.AnyObject, {}, "">;
7
+ type ConditionsContext = {
8
+ condition: Condition;
9
+ yupSchema: Yup.Schema<any, any, any, "">;
10
+ };
11
+ export declare function createValidationSchema(schema: Schema, definitions?: Schema, conditionsContext?: Record<string, ConditionsContext[]>, condition?: Condition): Yup.ObjectSchema<{}, Yup.AnyObject, {}, "">;
8
12
  export {};
@@ -9,9 +9,9 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { solveReferences } from "./solveReferences";
13
12
  import * as Yup from "yup";
14
13
  import { resolveOneOfValue } from "./resolveSchema";
14
+ import { solveReferences } from "./solveReferences";
15
15
  function getConditionalValidations(schema) {
16
16
  const { dependencies, allOf } = schema;
17
17
  let conditionalValidations = [];
@@ -115,7 +115,7 @@ function getYupType(type) {
115
115
  throw new Error("Unsupported schema type");
116
116
  }
117
117
  }
118
- export function createValidationSchema(schema, definitions = {}, condition) {
118
+ export function createValidationSchema(schema, definitions = {}, conditionsContext = {}, condition) {
119
119
  var _a;
120
120
  if (!(schema === null || schema === void 0 ? void 0 : schema.properties)) {
121
121
  return Yup.object().shape({});
@@ -123,10 +123,11 @@ export function createValidationSchema(schema, definitions = {}, condition) {
123
123
  const schemaDefinitions = Object.assign(Object.assign({}, definitions), ((_a = schema.definitions) !== null && _a !== void 0 ? _a : {}));
124
124
  const requiredFields = schema.required;
125
125
  function getValue(schema, key) {
126
+ var _a;
126
127
  let type = schema.type;
127
128
  let yupSchema;
128
129
  if (schema.properties) {
129
- yupSchema = createValidationSchema(schema, schemaDefinitions);
130
+ yupSchema = createValidationSchema(schema, schemaDefinitions, conditionsContext);
130
131
  type = type !== null && type !== void 0 ? type : "object";
131
132
  }
132
133
  else {
@@ -144,12 +145,29 @@ export function createValidationSchema(schema, definitions = {}, condition) {
144
145
  yupSchema = yupSchema["required"]("Required");
145
146
  }
146
147
  }
147
- return condition
148
- ? getYupType(type).when(condition.dependentKeys, {
149
- is: condition.conditionFn,
150
- then: () => yupSchema,
151
- })
152
- : yupSchema;
148
+ if (!condition) {
149
+ return yupSchema;
150
+ }
151
+ const existing = (_a = conditionsContext[key]) !== null && _a !== void 0 ? _a : [];
152
+ conditionsContext[key] = [...existing, { yupSchema, condition }];
153
+ // TODO: The latest given type for duplicate field is used.
154
+ // This might become a problem at some point, but testing has not
155
+ // shown any problems so far.
156
+ const yupType = getYupType(type).when(condition.dependentKeys, {
157
+ is: condition.conditionFn,
158
+ then: () => yupSchema,
159
+ });
160
+ // If there are existing conditions for the field with same name
161
+ // for example definition or duplicate field, add those clauses in.
162
+ // Otherwise the validation would be overwritten by the latest one
163
+ // breaking the earlier validations.
164
+ return existing.reduce((result, context) => {
165
+ const { condition: ctxCondition, yupSchema: ctxYupSchema } = context;
166
+ return result.when(ctxCondition.dependentKeys, {
167
+ is: ctxCondition.conditionFn,
168
+ then: () => ctxYupSchema,
169
+ });
170
+ }, yupType);
153
171
  }
154
172
  const baseValidationSchema = Object.keys(schema.properties).reduce((validationSchema, key) => {
155
173
  var _a;
@@ -159,6 +177,12 @@ export function createValidationSchema(schema, definitions = {}, condition) {
159
177
  }, Yup.object().shape({}));
160
178
  return getConditionalValidations(schema).reduce((validationSchema, conditionalValidation) => {
161
179
  const solvedSchemas = solveReferences(conditionalValidation.schema, schemaDefinitions, {}, {});
162
- return validationSchema.concat(createValidationSchema(solvedSchemas.schema, schemaDefinitions, conditionalValidation.condition));
180
+ /*
181
+ "Only schemas of the same type can be concatenated. concat is not
182
+ a "merge" function in the sense that all settings from the provided schema,
183
+ override ones in the base, including type, presence and nullability."
184
+ Source: https://github.com/jquense/yup?tab=readme-ov-file#schemaconcatschema-schema-schema
185
+ */
186
+ return validationSchema.concat(createValidationSchema(solvedSchemas.schema, schemaDefinitions, conditionsContext, conditionalValidation.condition));
163
187
  }, baseValidationSchema);
164
188
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nolwenture/formik-jsonschema-form",
3
- "version": "0.22.4",
3
+ "version": "0.22.5",
4
4
  "description": "Formik form generation from json schema",
5
5
  "author": "Antti Vikman <antti.vikman@nolwenture.com>",
6
6
  "contributors": [