@remoteoss/json-schema-form 0.5.0-dev.20230901130231 → 0.6.1-beta.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ #### 0.6.1-beta.0 (2023-09-13)
2
+
3
+ ##### Changes
4
+
5
+
6
+
7
+ - Computed string based values for json-logic ([#37](https://github.com/remoteoss/json-schema-form/pull/37)) ([6e042ea5](https://github.com/remoteoss/json-schema-form/commit/6e042ea579497ea573710c307a6ff7ee2f19b931))
8
+
9
+ #### 0.5.0-beta.0 (2023-09-12)
10
+
11
+ ##### Changes
12
+
13
+ - Computed Attributes ([#36](https://github.com/remoteoss/json-schema-form/pull/36)) ([80c29589](https://github.com/remoteoss/json-schema-form/commit/80c29589ac0972e0f33add70a59df15a46db1b43))
14
+ - JSON Logic Skeleton ([#35](https://github.com/remoteoss/json-schema-form/pull/35)) ([63149ae8](https://github.com/remoteoss/json-schema-form/commit/63149ae863cf1b5ad76a3b2a49c7f343e55ce07b))
15
+
1
16
  #### 0.4.5-beta.0 (2023-08-31)
2
17
 
3
18
  ##### Changes
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
3
  Copyright (c) 2023 Remote Technology, Inc.
4
- NPM Package: @remoteoss/json-schema-form@0.5.0-dev.20230901130231
5
- Generated: Fri, 01 Sep 2023 13:03:04 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.6.1-beta.0
5
+ Generated: Wed, 13 Sep 2023 09:54:37 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -417,6 +417,7 @@ var import_json_logic_js = __toESM(require("json-logic-js"));
417
417
  function createValidationChecker(schema) {
418
418
  const scopes = /* @__PURE__ */ new Map();
419
419
  function createScopes(jsonSchema, key = "root") {
420
+ const sampleEmptyObject = buildSampleEmptyObject(schema);
420
421
  scopes.set(key, createValidationsScope(jsonSchema));
421
422
  Object.entries(jsonSchema?.properties ?? {}).filter(([, property]) => property.type === "object" || property.type === "array").forEach(([key2, property]) => {
422
423
  if (property.type === "array") {
@@ -425,6 +426,7 @@ function createValidationChecker(schema) {
425
426
  createScopes(property, key2);
426
427
  }
427
428
  });
429
+ validateInlineRules(jsonSchema, sampleEmptyObject);
428
430
  }
429
431
  createScopes(schema);
430
432
  return {
@@ -443,10 +445,19 @@ function createValidationsScope(schema) {
443
445
  };
444
446
  const validations = Object.entries(logic.validations ?? {});
445
447
  const computedValues = Object.entries(logic.computedValues ?? {});
448
+ const sampleEmptyObject = buildSampleEmptyObject(schema);
446
449
  validations.forEach(([id, validation]) => {
450
+ if (!validation.rule) {
451
+ throw Error(`[json-schema-form] json-logic error: Validation "${id}" has missing rule.`);
452
+ }
453
+ checkRuleIntegrity(validation.rule, id, sampleEmptyObject);
447
454
  validationMap.set(id, validation);
448
455
  });
449
456
  computedValues.forEach(([id, computedValue]) => {
457
+ if (!computedValue.rule) {
458
+ throw Error(`[json-schema-form] json-logic error: Computed value "${id}" has missing rule.`);
459
+ }
460
+ checkRuleIntegrity(computedValue.rule, id, sampleEmptyObject);
450
461
  computedValuesMap.set(id, computedValue);
451
462
  });
452
463
  function validate(rule, values) {
@@ -460,8 +471,13 @@ function createValidationsScope(schema) {
460
471
  const validation = validationMap.get(id);
461
472
  return validate(validation.rule, values);
462
473
  },
463
- applyComputedValueInField(id, values) {
474
+ applyComputedValueInField(id, values, fieldName) {
464
475
  const validation = computedValuesMap.get(id);
476
+ if (validation === void 0) {
477
+ throw Error(
478
+ `[json-schema-form] json-logic error: Computed value "${id}" doesn't exist in field "${fieldName}".`
479
+ );
480
+ }
465
481
  return validate(validation.rule, values);
466
482
  },
467
483
  applyComputedValueRuleInCondition(id, values) {
@@ -478,6 +494,11 @@ function replaceUndefinedValuesWithNulls(values = {}) {
478
494
  function yupSchemaWithCustomJSONLogic({ field, logic, config, id }) {
479
495
  const { parentID = "root" } = config;
480
496
  const validation = logic.getScope(parentID).validationMap.get(id);
497
+ if (validation === void 0) {
498
+ throw Error(
499
+ `[json-schema-form] json-logic error: "${field.name}" required validation "${id}" doesn't exist.`
500
+ );
501
+ }
481
502
  return (yupSchema) => yupSchema.test(
482
503
  `${field.name}-validation-${id}`,
483
504
  validation?.errorMessage ?? "This field is invalid.",
@@ -488,24 +509,152 @@ function yupSchemaWithCustomJSONLogic({ field, logic, config, id }) {
488
509
  }
489
510
  );
490
511
  }
512
+ var HANDLEBARS_REGEX = /\{\{([^{}]+)\}\}/g;
513
+ function replaceHandlebarsTemplates({
514
+ value: toReplace,
515
+ logic,
516
+ formValues,
517
+ parentID,
518
+ name: fieldName
519
+ }) {
520
+ if (typeof toReplace === "string") {
521
+ return toReplace.replace(HANDLEBARS_REGEX, (match, key) => {
522
+ return logic.getScope(parentID).applyComputedValueInField(key.trim(), formValues, fieldName);
523
+ });
524
+ } else if (typeof toReplace === "object") {
525
+ const { value, ...rules } = toReplace;
526
+ if (Object.keys(rules).length > 1 && !value) {
527
+ throw Error("Cannot define multiple rules without a template string with key `value`.");
528
+ }
529
+ const computedTemplateValue = Object.entries(rules).reduce((prev, [key, rule]) => {
530
+ const computedValue = logic.getScope(parentID).evaluateValidation(rule, formValues);
531
+ return prev.replaceAll(`{{${key}}}`, computedValue);
532
+ }, value);
533
+ return computedTemplateValue.replace(/\{\{([^{}]+)\}\}/g, (match, key) => {
534
+ return logic.getScope(parentID).applyComputedValueInField(key.trim(), formValues, fieldName);
535
+ });
536
+ }
537
+ return toReplace;
538
+ }
491
539
  function calculateComputedAttributes(fieldParams, { parentID = "root" } = {}) {
492
- return ({ logic, formValues }) => {
493
- const { computedAttributes } = fieldParams;
540
+ return ({ logic, isRequired, config, formValues }) => {
541
+ const { name, computedAttributes } = fieldParams;
494
542
  const attributes = Object.fromEntries(
495
- Object.entries(computedAttributes).map(handleComputedAttribute(logic, formValues, parentID)).filter(([, value]) => value !== null)
543
+ Object.entries(computedAttributes).map(handleComputedAttribute(logic, formValues, parentID, name)).filter(([, value]) => value !== null)
496
544
  );
497
- return attributes;
545
+ return {
546
+ ...attributes,
547
+ schema: buildYupSchema(
548
+ { ...fieldParams, ...attributes, required: isRequired },
549
+ config,
550
+ logic
551
+ )
552
+ };
498
553
  };
499
554
  }
500
- function handleComputedAttribute(logic, formValues, parentID) {
555
+ function handleComputedAttribute(logic, formValues, parentID, name) {
501
556
  return ([key, value]) => {
502
- if (key === "const")
503
- return [key, logic.getScope(parentID).applyComputedValueInField(value, formValues)];
504
- if (typeof value === "string") {
505
- return [key, logic.getScope(parentID).applyComputedValueInField(value, formValues)];
557
+ switch (key) {
558
+ case "description":
559
+ return [key, replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
560
+ case "title":
561
+ return ["label", replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
562
+ case "x-jsf-errorMessage":
563
+ return [
564
+ "errorMessage",
565
+ handleNestedObjectForComputedValues(value, formValues, parentID, logic, name)
566
+ ];
567
+ case "x-jsf-presentation": {
568
+ if (value.statement) {
569
+ return [
570
+ "statement",
571
+ handleNestedObjectForComputedValues(value.statement, formValues, parentID, logic, name)
572
+ ];
573
+ }
574
+ return [
575
+ key,
576
+ handleNestedObjectForComputedValues(value.statement, formValues, parentID, logic, name)
577
+ ];
578
+ }
579
+ case "const":
580
+ default:
581
+ return [key, logic.getScope(parentID).applyComputedValueInField(value, formValues, name)];
506
582
  }
507
583
  };
508
584
  }
585
+ function handleNestedObjectForComputedValues(values, formValues, parentID, logic, name) {
586
+ return Object.fromEntries(
587
+ Object.entries(values).map(([key, value]) => {
588
+ return [key, replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
589
+ })
590
+ );
591
+ }
592
+ function buildSampleEmptyObject(schema = {}) {
593
+ const sample = {};
594
+ if (typeof schema !== "object" || !schema.properties) {
595
+ return schema;
596
+ }
597
+ for (const key in schema.properties) {
598
+ if (schema.properties[key].type === "object") {
599
+ sample[key] = buildSampleEmptyObject(schema.properties[key]);
600
+ } else if (schema.properties[key].type === "array") {
601
+ const itemSchema = schema.properties[key].items;
602
+ sample[key] = buildSampleEmptyObject(itemSchema);
603
+ } else {
604
+ sample[key] = true;
605
+ }
606
+ }
607
+ return sample;
608
+ }
609
+ function validateInlineRules(jsonSchema, sampleEmptyObject) {
610
+ const properties = (jsonSchema?.properties || jsonSchema?.items?.properties) ?? {};
611
+ Object.entries(properties).filter(([, property]) => property["x-jsf-logic-computedAttrs"] !== void 0).forEach(([fieldName, property]) => {
612
+ Object.entries(property["x-jsf-logic-computedAttrs"]).filter(([, value]) => typeof value === "object").forEach(([key, item]) => {
613
+ Object.values(item).forEach((rule) => {
614
+ checkRuleIntegrity(
615
+ rule,
616
+ fieldName,
617
+ sampleEmptyObject,
618
+ (item2) => `[json-schema-form] json-logic error: fieldName "${item2.var}" doesn't exist in field "${fieldName}.x-jsf-logic-computedAttrs.${key}".`
619
+ );
620
+ });
621
+ });
622
+ });
623
+ }
624
+ function checkRuleIntegrity(rule, id, data, errorMessage = (item) => `[json-schema-form] json-logic error: rule "${id}" has no variable "${item.var}".`) {
625
+ Object.entries(rule ?? {}).map(([operator, subRule]) => {
626
+ if (!Array.isArray(subRule) && subRule !== null && subRule !== void 0)
627
+ return;
628
+ throwIfUnknownOperator(operator, subRule, id);
629
+ subRule.map((item) => {
630
+ const isVar = item !== null && typeof item === "object" && Object.hasOwn(item, "var");
631
+ if (isVar) {
632
+ const exists = import_json_logic_js.default.apply({ var: removeIndicesFromPath(item.var) }, data);
633
+ if (exists === null) {
634
+ throw Error(errorMessage(item));
635
+ }
636
+ } else {
637
+ checkRuleIntegrity(item, id, data);
638
+ }
639
+ });
640
+ });
641
+ }
642
+ function throwIfUnknownOperator(operator, subRule, id) {
643
+ try {
644
+ import_json_logic_js.default.apply({ [operator]: subRule });
645
+ } catch (e) {
646
+ if (e.message === `Unrecognized operation ${operator}`) {
647
+ throw Error(
648
+ `[json-schema-form] json-logic error: in "${id}" rule there is an unknown operator "${operator}".`
649
+ );
650
+ }
651
+ }
652
+ }
653
+ var regexToGetIndices = /\.\d+\./g;
654
+ function removeIndicesFromPath(path) {
655
+ const intermediatePath = path.replace(regexToGetIndices, ".");
656
+ return intermediatePath.replace(/\.\d+$/, "");
657
+ }
509
658
 
510
659
  // src/yupSchema.js
511
660
  var DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
@@ -1098,6 +1247,7 @@ function extractParametersFromNode(schemaNode) {
1098
1247
  return (0, import_omitBy.default)(
1099
1248
  {
1100
1249
  const: node.const,
1250
+ ...node.const && node.default ? { value: node.const } : {},
1101
1251
  label: node.title,
1102
1252
  readOnly: node.readOnly,
1103
1253
  ...node.deprecated && {
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
3
  Copyright (c) 2023 Remote Technology, Inc.
4
- NPM Package: @remoteoss/json-schema-form@0.5.0-dev.20230901130231
5
- Generated: Fri, 01 Sep 2023 13:03:04 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.6.1-beta.0
5
+ Generated: Wed, 13 Sep 2023 09:54:37 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -381,6 +381,7 @@ import jsonLogic from "json-logic-js";
381
381
  function createValidationChecker(schema) {
382
382
  const scopes = /* @__PURE__ */ new Map();
383
383
  function createScopes(jsonSchema, key = "root") {
384
+ const sampleEmptyObject = buildSampleEmptyObject(schema);
384
385
  scopes.set(key, createValidationsScope(jsonSchema));
385
386
  Object.entries(jsonSchema?.properties ?? {}).filter(([, property]) => property.type === "object" || property.type === "array").forEach(([key2, property]) => {
386
387
  if (property.type === "array") {
@@ -389,6 +390,7 @@ function createValidationChecker(schema) {
389
390
  createScopes(property, key2);
390
391
  }
391
392
  });
393
+ validateInlineRules(jsonSchema, sampleEmptyObject);
392
394
  }
393
395
  createScopes(schema);
394
396
  return {
@@ -407,10 +409,19 @@ function createValidationsScope(schema) {
407
409
  };
408
410
  const validations = Object.entries(logic.validations ?? {});
409
411
  const computedValues = Object.entries(logic.computedValues ?? {});
412
+ const sampleEmptyObject = buildSampleEmptyObject(schema);
410
413
  validations.forEach(([id, validation]) => {
414
+ if (!validation.rule) {
415
+ throw Error(`[json-schema-form] json-logic error: Validation "${id}" has missing rule.`);
416
+ }
417
+ checkRuleIntegrity(validation.rule, id, sampleEmptyObject);
411
418
  validationMap.set(id, validation);
412
419
  });
413
420
  computedValues.forEach(([id, computedValue]) => {
421
+ if (!computedValue.rule) {
422
+ throw Error(`[json-schema-form] json-logic error: Computed value "${id}" has missing rule.`);
423
+ }
424
+ checkRuleIntegrity(computedValue.rule, id, sampleEmptyObject);
414
425
  computedValuesMap.set(id, computedValue);
415
426
  });
416
427
  function validate(rule, values) {
@@ -424,8 +435,13 @@ function createValidationsScope(schema) {
424
435
  const validation = validationMap.get(id);
425
436
  return validate(validation.rule, values);
426
437
  },
427
- applyComputedValueInField(id, values) {
438
+ applyComputedValueInField(id, values, fieldName) {
428
439
  const validation = computedValuesMap.get(id);
440
+ if (validation === void 0) {
441
+ throw Error(
442
+ `[json-schema-form] json-logic error: Computed value "${id}" doesn't exist in field "${fieldName}".`
443
+ );
444
+ }
429
445
  return validate(validation.rule, values);
430
446
  },
431
447
  applyComputedValueRuleInCondition(id, values) {
@@ -442,6 +458,11 @@ function replaceUndefinedValuesWithNulls(values = {}) {
442
458
  function yupSchemaWithCustomJSONLogic({ field, logic, config, id }) {
443
459
  const { parentID = "root" } = config;
444
460
  const validation = logic.getScope(parentID).validationMap.get(id);
461
+ if (validation === void 0) {
462
+ throw Error(
463
+ `[json-schema-form] json-logic error: "${field.name}" required validation "${id}" doesn't exist.`
464
+ );
465
+ }
445
466
  return (yupSchema) => yupSchema.test(
446
467
  `${field.name}-validation-${id}`,
447
468
  validation?.errorMessage ?? "This field is invalid.",
@@ -452,24 +473,152 @@ function yupSchemaWithCustomJSONLogic({ field, logic, config, id }) {
452
473
  }
453
474
  );
454
475
  }
476
+ var HANDLEBARS_REGEX = /\{\{([^{}]+)\}\}/g;
477
+ function replaceHandlebarsTemplates({
478
+ value: toReplace,
479
+ logic,
480
+ formValues,
481
+ parentID,
482
+ name: fieldName
483
+ }) {
484
+ if (typeof toReplace === "string") {
485
+ return toReplace.replace(HANDLEBARS_REGEX, (match, key) => {
486
+ return logic.getScope(parentID).applyComputedValueInField(key.trim(), formValues, fieldName);
487
+ });
488
+ } else if (typeof toReplace === "object") {
489
+ const { value, ...rules } = toReplace;
490
+ if (Object.keys(rules).length > 1 && !value) {
491
+ throw Error("Cannot define multiple rules without a template string with key `value`.");
492
+ }
493
+ const computedTemplateValue = Object.entries(rules).reduce((prev, [key, rule]) => {
494
+ const computedValue = logic.getScope(parentID).evaluateValidation(rule, formValues);
495
+ return prev.replaceAll(`{{${key}}}`, computedValue);
496
+ }, value);
497
+ return computedTemplateValue.replace(/\{\{([^{}]+)\}\}/g, (match, key) => {
498
+ return logic.getScope(parentID).applyComputedValueInField(key.trim(), formValues, fieldName);
499
+ });
500
+ }
501
+ return toReplace;
502
+ }
455
503
  function calculateComputedAttributes(fieldParams, { parentID = "root" } = {}) {
456
- return ({ logic, formValues }) => {
457
- const { computedAttributes } = fieldParams;
504
+ return ({ logic, isRequired, config, formValues }) => {
505
+ const { name, computedAttributes } = fieldParams;
458
506
  const attributes = Object.fromEntries(
459
- Object.entries(computedAttributes).map(handleComputedAttribute(logic, formValues, parentID)).filter(([, value]) => value !== null)
507
+ Object.entries(computedAttributes).map(handleComputedAttribute(logic, formValues, parentID, name)).filter(([, value]) => value !== null)
460
508
  );
461
- return attributes;
509
+ return {
510
+ ...attributes,
511
+ schema: buildYupSchema(
512
+ { ...fieldParams, ...attributes, required: isRequired },
513
+ config,
514
+ logic
515
+ )
516
+ };
462
517
  };
463
518
  }
464
- function handleComputedAttribute(logic, formValues, parentID) {
519
+ function handleComputedAttribute(logic, formValues, parentID, name) {
465
520
  return ([key, value]) => {
466
- if (key === "const")
467
- return [key, logic.getScope(parentID).applyComputedValueInField(value, formValues)];
468
- if (typeof value === "string") {
469
- return [key, logic.getScope(parentID).applyComputedValueInField(value, formValues)];
521
+ switch (key) {
522
+ case "description":
523
+ return [key, replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
524
+ case "title":
525
+ return ["label", replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
526
+ case "x-jsf-errorMessage":
527
+ return [
528
+ "errorMessage",
529
+ handleNestedObjectForComputedValues(value, formValues, parentID, logic, name)
530
+ ];
531
+ case "x-jsf-presentation": {
532
+ if (value.statement) {
533
+ return [
534
+ "statement",
535
+ handleNestedObjectForComputedValues(value.statement, formValues, parentID, logic, name)
536
+ ];
537
+ }
538
+ return [
539
+ key,
540
+ handleNestedObjectForComputedValues(value.statement, formValues, parentID, logic, name)
541
+ ];
542
+ }
543
+ case "const":
544
+ default:
545
+ return [key, logic.getScope(parentID).applyComputedValueInField(value, formValues, name)];
470
546
  }
471
547
  };
472
548
  }
549
+ function handleNestedObjectForComputedValues(values, formValues, parentID, logic, name) {
550
+ return Object.fromEntries(
551
+ Object.entries(values).map(([key, value]) => {
552
+ return [key, replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
553
+ })
554
+ );
555
+ }
556
+ function buildSampleEmptyObject(schema = {}) {
557
+ const sample = {};
558
+ if (typeof schema !== "object" || !schema.properties) {
559
+ return schema;
560
+ }
561
+ for (const key in schema.properties) {
562
+ if (schema.properties[key].type === "object") {
563
+ sample[key] = buildSampleEmptyObject(schema.properties[key]);
564
+ } else if (schema.properties[key].type === "array") {
565
+ const itemSchema = schema.properties[key].items;
566
+ sample[key] = buildSampleEmptyObject(itemSchema);
567
+ } else {
568
+ sample[key] = true;
569
+ }
570
+ }
571
+ return sample;
572
+ }
573
+ function validateInlineRules(jsonSchema, sampleEmptyObject) {
574
+ const properties = (jsonSchema?.properties || jsonSchema?.items?.properties) ?? {};
575
+ Object.entries(properties).filter(([, property]) => property["x-jsf-logic-computedAttrs"] !== void 0).forEach(([fieldName, property]) => {
576
+ Object.entries(property["x-jsf-logic-computedAttrs"]).filter(([, value]) => typeof value === "object").forEach(([key, item]) => {
577
+ Object.values(item).forEach((rule) => {
578
+ checkRuleIntegrity(
579
+ rule,
580
+ fieldName,
581
+ sampleEmptyObject,
582
+ (item2) => `[json-schema-form] json-logic error: fieldName "${item2.var}" doesn't exist in field "${fieldName}.x-jsf-logic-computedAttrs.${key}".`
583
+ );
584
+ });
585
+ });
586
+ });
587
+ }
588
+ function checkRuleIntegrity(rule, id, data, errorMessage = (item) => `[json-schema-form] json-logic error: rule "${id}" has no variable "${item.var}".`) {
589
+ Object.entries(rule ?? {}).map(([operator, subRule]) => {
590
+ if (!Array.isArray(subRule) && subRule !== null && subRule !== void 0)
591
+ return;
592
+ throwIfUnknownOperator(operator, subRule, id);
593
+ subRule.map((item) => {
594
+ const isVar = item !== null && typeof item === "object" && Object.hasOwn(item, "var");
595
+ if (isVar) {
596
+ const exists = jsonLogic.apply({ var: removeIndicesFromPath(item.var) }, data);
597
+ if (exists === null) {
598
+ throw Error(errorMessage(item));
599
+ }
600
+ } else {
601
+ checkRuleIntegrity(item, id, data);
602
+ }
603
+ });
604
+ });
605
+ }
606
+ function throwIfUnknownOperator(operator, subRule, id) {
607
+ try {
608
+ jsonLogic.apply({ [operator]: subRule });
609
+ } catch (e) {
610
+ if (e.message === `Unrecognized operation ${operator}`) {
611
+ throw Error(
612
+ `[json-schema-form] json-logic error: in "${id}" rule there is an unknown operator "${operator}".`
613
+ );
614
+ }
615
+ }
616
+ }
617
+ var regexToGetIndices = /\.\d+\./g;
618
+ function removeIndicesFromPath(path) {
619
+ const intermediatePath = path.replace(regexToGetIndices, ".");
620
+ return intermediatePath.replace(/\.\d+$/, "");
621
+ }
473
622
 
474
623
  // src/yupSchema.js
475
624
  var DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
@@ -1062,6 +1211,7 @@ function extractParametersFromNode(schemaNode) {
1062
1211
  return omitBy(
1063
1212
  {
1064
1213
  const: node.const,
1214
+ ...node.const && node.default ? { value: node.const } : {},
1065
1215
  label: node.title,
1066
1216
  readOnly: node.readOnly,
1067
1217
  ...node.deprecated && {
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
3
  Copyright (c) 2023 Remote Technology, Inc.
4
- NPM Package: @remoteoss/json-schema-form@0.5.0-dev.20230901130231
5
- Generated: Fri, 01 Sep 2023 13:03:04 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.6.1-beta.0
5
+ Generated: Wed, 13 Sep 2023 09:54:37 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -11735,6 +11735,7 @@ var import_json_logic_js = __toESM(require_logic());
11735
11735
  function createValidationChecker(schema) {
11736
11736
  const scopes = /* @__PURE__ */ new Map();
11737
11737
  function createScopes(jsonSchema, key = "root") {
11738
+ const sampleEmptyObject = buildSampleEmptyObject(schema);
11738
11739
  scopes.set(key, createValidationsScope(jsonSchema));
11739
11740
  Object.entries(jsonSchema?.properties ?? {}).filter(([, property2]) => property2.type === "object" || property2.type === "array").forEach(([key2, property2]) => {
11740
11741
  if (property2.type === "array") {
@@ -11743,6 +11744,7 @@ function createValidationChecker(schema) {
11743
11744
  createScopes(property2, key2);
11744
11745
  }
11745
11746
  });
11747
+ validateInlineRules(jsonSchema, sampleEmptyObject);
11746
11748
  }
11747
11749
  createScopes(schema);
11748
11750
  return {
@@ -11761,10 +11763,19 @@ function createValidationsScope(schema) {
11761
11763
  };
11762
11764
  const validations = Object.entries(logic.validations ?? {});
11763
11765
  const computedValues = Object.entries(logic.computedValues ?? {});
11766
+ const sampleEmptyObject = buildSampleEmptyObject(schema);
11764
11767
  validations.forEach(([id, validation]) => {
11768
+ if (!validation.rule) {
11769
+ throw Error(`[json-schema-form] json-logic error: Validation "${id}" has missing rule.`);
11770
+ }
11771
+ checkRuleIntegrity(validation.rule, id, sampleEmptyObject);
11765
11772
  validationMap.set(id, validation);
11766
11773
  });
11767
11774
  computedValues.forEach(([id, computedValue]) => {
11775
+ if (!computedValue.rule) {
11776
+ throw Error(`[json-schema-form] json-logic error: Computed value "${id}" has missing rule.`);
11777
+ }
11778
+ checkRuleIntegrity(computedValue.rule, id, sampleEmptyObject);
11768
11779
  computedValuesMap.set(id, computedValue);
11769
11780
  });
11770
11781
  function validate2(rule, values2) {
@@ -11778,8 +11789,13 @@ function createValidationsScope(schema) {
11778
11789
  const validation = validationMap.get(id);
11779
11790
  return validate2(validation.rule, values2);
11780
11791
  },
11781
- applyComputedValueInField(id, values2) {
11792
+ applyComputedValueInField(id, values2, fieldName) {
11782
11793
  const validation = computedValuesMap.get(id);
11794
+ if (validation === void 0) {
11795
+ throw Error(
11796
+ `[json-schema-form] json-logic error: Computed value "${id}" doesn't exist in field "${fieldName}".`
11797
+ );
11798
+ }
11783
11799
  return validate2(validation.rule, values2);
11784
11800
  },
11785
11801
  applyComputedValueRuleInCondition(id, values2) {
@@ -11796,6 +11812,11 @@ function replaceUndefinedValuesWithNulls(values2 = {}) {
11796
11812
  function yupSchemaWithCustomJSONLogic({ field, logic, config, id }) {
11797
11813
  const { parentID = "root" } = config;
11798
11814
  const validation = logic.getScope(parentID).validationMap.get(id);
11815
+ if (validation === void 0) {
11816
+ throw Error(
11817
+ `[json-schema-form] json-logic error: "${field.name}" required validation "${id}" doesn't exist.`
11818
+ );
11819
+ }
11799
11820
  return (yupSchema) => yupSchema.test(
11800
11821
  `${field.name}-validation-${id}`,
11801
11822
  validation?.errorMessage ?? "This field is invalid.",
@@ -11806,24 +11827,152 @@ function yupSchemaWithCustomJSONLogic({ field, logic, config, id }) {
11806
11827
  }
11807
11828
  );
11808
11829
  }
11830
+ var HANDLEBARS_REGEX = /\{\{([^{}]+)\}\}/g;
11831
+ function replaceHandlebarsTemplates({
11832
+ value: toReplace,
11833
+ logic,
11834
+ formValues,
11835
+ parentID,
11836
+ name: fieldName
11837
+ }) {
11838
+ if (typeof toReplace === "string") {
11839
+ return toReplace.replace(HANDLEBARS_REGEX, (match, key) => {
11840
+ return logic.getScope(parentID).applyComputedValueInField(key.trim(), formValues, fieldName);
11841
+ });
11842
+ } else if (typeof toReplace === "object") {
11843
+ const { value, ...rules } = toReplace;
11844
+ if (Object.keys(rules).length > 1 && !value) {
11845
+ throw Error("Cannot define multiple rules without a template string with key `value`.");
11846
+ }
11847
+ const computedTemplateValue = Object.entries(rules).reduce((prev, [key, rule]) => {
11848
+ const computedValue = logic.getScope(parentID).evaluateValidation(rule, formValues);
11849
+ return prev.replaceAll(`{{${key}}}`, computedValue);
11850
+ }, value);
11851
+ return computedTemplateValue.replace(/\{\{([^{}]+)\}\}/g, (match, key) => {
11852
+ return logic.getScope(parentID).applyComputedValueInField(key.trim(), formValues, fieldName);
11853
+ });
11854
+ }
11855
+ return toReplace;
11856
+ }
11809
11857
  function calculateComputedAttributes(fieldParams, { parentID = "root" } = {}) {
11810
- return ({ logic, formValues }) => {
11811
- const { computedAttributes } = fieldParams;
11858
+ return ({ logic, isRequired, config, formValues }) => {
11859
+ const { name, computedAttributes } = fieldParams;
11812
11860
  const attributes = Object.fromEntries(
11813
- Object.entries(computedAttributes).map(handleComputedAttribute(logic, formValues, parentID)).filter(([, value]) => value !== null)
11861
+ Object.entries(computedAttributes).map(handleComputedAttribute(logic, formValues, parentID, name)).filter(([, value]) => value !== null)
11814
11862
  );
11815
- return attributes;
11863
+ return {
11864
+ ...attributes,
11865
+ schema: buildYupSchema(
11866
+ { ...fieldParams, ...attributes, required: isRequired },
11867
+ config,
11868
+ logic
11869
+ )
11870
+ };
11816
11871
  };
11817
11872
  }
11818
- function handleComputedAttribute(logic, formValues, parentID) {
11873
+ function handleComputedAttribute(logic, formValues, parentID, name) {
11819
11874
  return ([key, value]) => {
11820
- if (key === "const")
11821
- return [key, logic.getScope(parentID).applyComputedValueInField(value, formValues)];
11822
- if (typeof value === "string") {
11823
- return [key, logic.getScope(parentID).applyComputedValueInField(value, formValues)];
11875
+ switch (key) {
11876
+ case "description":
11877
+ return [key, replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
11878
+ case "title":
11879
+ return ["label", replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
11880
+ case "x-jsf-errorMessage":
11881
+ return [
11882
+ "errorMessage",
11883
+ handleNestedObjectForComputedValues(value, formValues, parentID, logic, name)
11884
+ ];
11885
+ case "x-jsf-presentation": {
11886
+ if (value.statement) {
11887
+ return [
11888
+ "statement",
11889
+ handleNestedObjectForComputedValues(value.statement, formValues, parentID, logic, name)
11890
+ ];
11891
+ }
11892
+ return [
11893
+ key,
11894
+ handleNestedObjectForComputedValues(value.statement, formValues, parentID, logic, name)
11895
+ ];
11896
+ }
11897
+ case "const":
11898
+ default:
11899
+ return [key, logic.getScope(parentID).applyComputedValueInField(value, formValues, name)];
11824
11900
  }
11825
11901
  };
11826
11902
  }
11903
+ function handleNestedObjectForComputedValues(values2, formValues, parentID, logic, name) {
11904
+ return Object.fromEntries(
11905
+ Object.entries(values2).map(([key, value]) => {
11906
+ return [key, replaceHandlebarsTemplates({ value, logic, formValues, parentID, name })];
11907
+ })
11908
+ );
11909
+ }
11910
+ function buildSampleEmptyObject(schema = {}) {
11911
+ const sample = {};
11912
+ if (typeof schema !== "object" || !schema.properties) {
11913
+ return schema;
11914
+ }
11915
+ for (const key in schema.properties) {
11916
+ if (schema.properties[key].type === "object") {
11917
+ sample[key] = buildSampleEmptyObject(schema.properties[key]);
11918
+ } else if (schema.properties[key].type === "array") {
11919
+ const itemSchema = schema.properties[key].items;
11920
+ sample[key] = buildSampleEmptyObject(itemSchema);
11921
+ } else {
11922
+ sample[key] = true;
11923
+ }
11924
+ }
11925
+ return sample;
11926
+ }
11927
+ function validateInlineRules(jsonSchema, sampleEmptyObject) {
11928
+ const properties = (jsonSchema?.properties || jsonSchema?.items?.properties) ?? {};
11929
+ Object.entries(properties).filter(([, property2]) => property2["x-jsf-logic-computedAttrs"] !== void 0).forEach(([fieldName, property2]) => {
11930
+ Object.entries(property2["x-jsf-logic-computedAttrs"]).filter(([, value]) => typeof value === "object").forEach(([key, item]) => {
11931
+ Object.values(item).forEach((rule) => {
11932
+ checkRuleIntegrity(
11933
+ rule,
11934
+ fieldName,
11935
+ sampleEmptyObject,
11936
+ (item2) => `[json-schema-form] json-logic error: fieldName "${item2.var}" doesn't exist in field "${fieldName}.x-jsf-logic-computedAttrs.${key}".`
11937
+ );
11938
+ });
11939
+ });
11940
+ });
11941
+ }
11942
+ function checkRuleIntegrity(rule, id, data, errorMessage = (item) => `[json-schema-form] json-logic error: rule "${id}" has no variable "${item.var}".`) {
11943
+ Object.entries(rule ?? {}).map(([operator, subRule]) => {
11944
+ if (!Array.isArray(subRule) && subRule !== null && subRule !== void 0)
11945
+ return;
11946
+ throwIfUnknownOperator(operator, subRule, id);
11947
+ subRule.map((item) => {
11948
+ const isVar = item !== null && typeof item === "object" && Object.hasOwn(item, "var");
11949
+ if (isVar) {
11950
+ const exists = import_json_logic_js.default.apply({ var: removeIndicesFromPath(item.var) }, data);
11951
+ if (exists === null) {
11952
+ throw Error(errorMessage(item));
11953
+ }
11954
+ } else {
11955
+ checkRuleIntegrity(item, id, data);
11956
+ }
11957
+ });
11958
+ });
11959
+ }
11960
+ function throwIfUnknownOperator(operator, subRule, id) {
11961
+ try {
11962
+ import_json_logic_js.default.apply({ [operator]: subRule });
11963
+ } catch (e) {
11964
+ if (e.message === `Unrecognized operation ${operator}`) {
11965
+ throw Error(
11966
+ `[json-schema-form] json-logic error: in "${id}" rule there is an unknown operator "${operator}".`
11967
+ );
11968
+ }
11969
+ }
11970
+ }
11971
+ var regexToGetIndices = /\.\d+\./g;
11972
+ function removeIndicesFromPath(path) {
11973
+ const intermediatePath = path.replace(regexToGetIndices, ".");
11974
+ return intermediatePath.replace(/\.\d+$/, "");
11975
+ }
11827
11976
 
11828
11977
  // src/yupSchema.js
11829
11978
  var DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
@@ -12416,6 +12565,7 @@ function extractParametersFromNode(schemaNode) {
12416
12565
  return (0, import_omitBy.default)(
12417
12566
  {
12418
12567
  const: node.const,
12568
+ ...node.const && node.default ? { value: node.const } : {},
12419
12569
  label: node.title,
12420
12570
  readOnly: node.readOnly,
12421
12571
  ...node.deprecated && {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remoteoss/json-schema-form",
3
- "version": "0.5.0-dev.20230901130231",
3
+ "version": "0.6.1-beta.0",
4
4
  "description": "Headless UI form powered by JSON Schemas",
5
5
  "author": "Remote.com <engineering@remote.com> (https://remote.com/)",
6
6
  "license": "MIT",
@@ -83,4 +83,16 @@ describe('validations: const', () => {
83
83
  });
84
84
  expect(handleValidation({ string: 'hello' }).formErrors).toEqual(undefined);
85
85
  });
86
+
87
+ it('Should have value attribute for when const & default is present', () => {
88
+ const { fields } = createHeadlessForm(
89
+ {
90
+ properties: {
91
+ ten_only: { type: 'number', const: 10, default: 10 },
92
+ },
93
+ },
94
+ { strictInputType: false }
95
+ );
96
+ expect(fields[0]).toMatchObject({ value: 10, const: 10, default: 10 });
97
+ });
86
98
  });
@@ -57,6 +57,7 @@ import {
57
57
  schemaForErrorMessageSpecificity,
58
58
  jsfConfigForErrorMessageSpecificity,
59
59
  } from './helpers';
60
+ import { mockConsole, restoreConsoleAndEnsureItWasNotCalled } from './testUtils';
60
61
 
61
62
  function buildJSONSchemaInput({ presentationFields, inputFields = {}, required }) {
62
63
  return {
@@ -92,17 +93,8 @@ const getField = (fields, name, ...subNames) => {
92
93
  return field;
93
94
  };
94
95
 
95
- beforeEach(() => {
96
- jest.spyOn(console, 'warn').mockImplementation(() => {});
97
- jest.spyOn(console, 'error').mockImplementation(() => {});
98
- });
99
-
100
- afterEach(() => {
101
- expect(console.error).not.toHaveBeenCalled();
102
- console.error.mockRestore();
103
- expect(console.warn).not.toHaveBeenCalled();
104
- console.warn.mockRestore();
105
- });
96
+ beforeEach(mockConsole);
97
+ afterEach(restoreConsoleAndEnsureItWasNotCalled);
106
98
 
107
99
  describe('createHeadlessForm', () => {
108
100
  it('returns empty result given no schema', () => {
@@ -77,6 +77,70 @@ export const schemaWithNativeAndJSONLogicChecks = {
77
77
  required: ['field_a'],
78
78
  };
79
79
 
80
+ export const schemaWithMissingRule = {
81
+ properties: {
82
+ field_a: {
83
+ type: 'number',
84
+ 'x-jsf-logic-validations': ['a_greater_than_ten'],
85
+ },
86
+ },
87
+ 'x-jsf-logic': {
88
+ validations: {
89
+ a_greater_than_ten: {
90
+ errorMessage: 'Must be greater than 10',
91
+ // rule: { '>': [{ var: 'field_a' }, 10] }, this missing causes test to fail.
92
+ },
93
+ },
94
+ },
95
+ required: [],
96
+ };
97
+
98
+ export const schemaWithUnknownVariableInValidations = {
99
+ properties: {
100
+ // field_a: { type: 'number' }, this missing causes test to fail.
101
+ },
102
+ 'x-jsf-logic': {
103
+ validations: {
104
+ a_equals_ten: {
105
+ errorMessage: 'Must equal 10',
106
+ rule: { '===': [{ var: 'field_a' }, 10] },
107
+ },
108
+ },
109
+ },
110
+ };
111
+
112
+ export const schemaWithUnknownVariableInComputedValues = {
113
+ properties: {
114
+ // field_a: { type: 'number' }, this missing causes test to fail.
115
+ },
116
+ 'x-jsf-logic': {
117
+ computedValues: {
118
+ a_times_ten: {
119
+ rule: { '*': [{ var: 'field_a' }, 10] },
120
+ },
121
+ },
122
+ },
123
+ };
124
+
125
+ export const schemaWithMissingComputedValue = {
126
+ properties: {
127
+ field_a: {
128
+ type: 'number',
129
+ 'x-jsf-logic-computedAttrs': {
130
+ title: '{{a_plus_ten}}',
131
+ },
132
+ },
133
+ },
134
+ 'x-jsf-logic': {
135
+ computedValues: {
136
+ a_plus_ten: {
137
+ // rule: { '+': [{ var: 'field_a' }, 10 ]} this missing causes test to fail.
138
+ },
139
+ },
140
+ },
141
+ required: [],
142
+ };
143
+
80
144
  export const multiRuleSchema = {
81
145
  properties: {
82
146
  field_a: {
@@ -144,8 +208,113 @@ export const schemaWithComputedAttributes = {
144
208
  field_b: {
145
209
  type: 'number',
146
210
  'x-jsf-logic-computedAttrs': {
211
+ title: 'This is {{a_times_two}}!',
147
212
  const: 'a_times_two',
148
213
  default: 'a_times_two',
214
+ description: 'This field is 2 times bigger than field_a with value of {{a_times_two}}.',
215
+ },
216
+ },
217
+ },
218
+ required: ['field_a', 'field_b'],
219
+ 'x-jsf-logic': {
220
+ computedValues: {
221
+ a_times_two: {
222
+ rule: {
223
+ '*': [{ var: 'field_a' }, 2],
224
+ },
225
+ },
226
+ },
227
+ },
228
+ };
229
+
230
+ export const schemaWithInlineRuleForComputedAttributeWithoutCopy = {
231
+ properties: {
232
+ field_a: {
233
+ type: 'number',
234
+ },
235
+ field_b: {
236
+ type: 'number',
237
+ 'x-jsf-logic-computedAttrs': {
238
+ title: {
239
+ rule: {
240
+ '+': [{ var: 'field_a' }, 10],
241
+ },
242
+ },
243
+ },
244
+ },
245
+ },
246
+ };
247
+
248
+ export const schemaWithComputedAttributeThatDoesntExist = {
249
+ properties: {
250
+ field_a: {
251
+ type: 'number',
252
+ 'x-jsf-logic-computedAttrs': {
253
+ default: 'iDontExist',
254
+ },
255
+ },
256
+ },
257
+ // x-jsf-logic: { computedValues: { iDontExist: { rule: 10 }} this missing causes test to fail.
258
+ };
259
+
260
+ export const schemaWithInlinedRuleOnComputedAttributeThatReferencesUnknownVar = {
261
+ properties: {
262
+ // iDontExist: { type: 'number' } this missing causes test to fail.
263
+ field_a: {
264
+ type: 'number',
265
+ 'x-jsf-logic-computedAttrs': {
266
+ title: {
267
+ rule: {
268
+ '+': [{ var: 'IdontExist' }, 10],
269
+ },
270
+ },
271
+ },
272
+ },
273
+ },
274
+ };
275
+
276
+ export const schemaWithComputedAttributeThatDoesntExistTitle = {
277
+ properties: {
278
+ field_a: {
279
+ type: 'number',
280
+ 'x-jsf-logic-computedAttrs': {
281
+ title: `this doesn't exist {{iDontExist}}`,
282
+ },
283
+ },
284
+ },
285
+ };
286
+
287
+ export const schemaWithComputedAttributeThatDoesntExistDescription = {
288
+ properties: {
289
+ // iDontExist: { type: 'number'}, this missing causes test to fail
290
+ field_a: {
291
+ type: 'number',
292
+ 'x-jsf-logic-computedAttrs': {
293
+ description: `this doesn't exist {{iDontExist}}`,
294
+ },
295
+ },
296
+ },
297
+ };
298
+
299
+ export const schemaWithComputedAttributesAndErrorMessages = {
300
+ properties: {
301
+ field_a: {
302
+ type: 'number',
303
+ },
304
+ field_b: {
305
+ type: 'number',
306
+ 'x-jsf-logic-computedAttrs': {
307
+ minimum: 'a_times_two',
308
+ maximum: 'a_times_four',
309
+ 'x-jsf-errorMessage': {
310
+ minimum: 'Must be bigger than {{a_times_two}}',
311
+ maximum: 'Must be smaller than {{a_times_four}}',
312
+ },
313
+ 'x-jsf-presentation': {
314
+ statement: {
315
+ description: 'Must be bigger than {{a_times_two}} and smaller than {{a_times_four}}',
316
+ },
317
+ },
149
318
  },
150
319
  },
151
320
  },
@@ -157,6 +326,116 @@ export const schemaWithComputedAttributes = {
157
326
  '*': [{ var: 'field_a' }, 2],
158
327
  },
159
328
  },
329
+ a_times_four: {
330
+ rule: {
331
+ '*': [{ var: 'field_a' }, 4],
332
+ },
333
+ },
334
+ },
335
+ },
336
+ };
337
+
338
+ export const schemaWithDeepVarThatDoesNotExist = {
339
+ properties: {
340
+ field_a: {
341
+ type: 'number',
342
+ },
343
+ },
344
+ 'x-jsf-logic': {
345
+ validations: {
346
+ dummy_rule: {
347
+ errorMessage: 'Random stuff to illustrate a deeply nested rule.',
348
+ rule: {
349
+ '>': [{ var: 'field_a' }, { '*': [2, { '/': [2, { '*': [1, { var: 'field_b' }] }] }] }],
350
+ },
351
+ },
352
+ },
353
+ },
354
+ required: [],
355
+ };
356
+
357
+ export const schemaWithDeepVarThatDoesNotExistOnFieldset = {
358
+ properties: {
359
+ field_a: {
360
+ type: 'object',
361
+ properties: {
362
+ child: {
363
+ type: 'number',
364
+ },
365
+ },
366
+ 'x-jsf-logic': {
367
+ validations: {
368
+ dummy_rule: {
369
+ errorMessage: 'Must be greater than 10',
370
+ rule: {
371
+ '>': [{ var: 'child' }, { '*': [2, { '/': [2, { '*': [1, { var: 'field_a' }] }] }] }],
372
+ },
373
+ },
374
+ },
375
+ },
376
+ },
377
+ },
378
+ required: [],
379
+ };
380
+
381
+ export const schemaWithValidationThatDoesNotExistOnProperty = {
382
+ properties: {
383
+ field_a: {
384
+ type: 'number',
385
+ 'x-jsf-logic-validations': ['iDontExist'],
386
+ },
387
+ },
388
+ };
389
+
390
+ export const schemaWithPropertyThatDoesNotExistInThatLevelButDoesInFieldset = {
391
+ properties: {
392
+ field_a: {
393
+ type: 'object',
394
+ 'x-jsf-presentation': {
395
+ inputType: 'fieldset',
396
+ },
397
+ properties: {
398
+ child: {
399
+ type: 'number',
400
+ 'x-jsf-logic-validations': ['child_greater_than_10'],
401
+ },
402
+ other_child: {
403
+ type: 'number',
404
+ 'x-jsf-logic-validations': ['greater_than_child'],
405
+ },
406
+ },
407
+ required: ['child', 'other_child'],
408
+ },
409
+ },
410
+ // the issue here is that this should be nested inside `field_a` in order to not fail.
411
+ 'x-jsf-logic': {
412
+ validations: {
413
+ validation_parent: {
414
+ errorMessage: 'Must be greater than 10!',
415
+ rule: {
416
+ '>': [{ var: 'child' }, 10],
417
+ },
418
+ },
419
+ greater_than_child: {
420
+ errorMessage: 'Must be greater than child',
421
+ rule: {
422
+ '>': [{ var: 'other_child' }, { var: 'child' }],
423
+ },
424
+ },
425
+ },
426
+ },
427
+ required: ['field_a'],
428
+ };
429
+
430
+ export const schemaWithBadOperation = {
431
+ properties: {},
432
+ 'x-jsf-logic': {
433
+ validations: {
434
+ badOperator: {
435
+ rule: {
436
+ '++': [10, 2],
437
+ },
438
+ },
160
439
  },
161
440
  },
162
441
  };
@@ -4,11 +4,29 @@ import {
4
4
  createSchemaWithRulesOnFieldA,
5
5
  createSchemaWithThreePropertiesWithRuleOnFieldA,
6
6
  multiRuleSchema,
7
+ schemaWithBadOperation,
8
+ schemaWithComputedAttributeThatDoesntExist,
9
+ schemaWithComputedAttributeThatDoesntExistDescription,
10
+ schemaWithComputedAttributeThatDoesntExistTitle,
7
11
  schemaWithComputedAttributes,
12
+ schemaWithComputedAttributesAndErrorMessages,
13
+ schemaWithDeepVarThatDoesNotExist,
14
+ schemaWithDeepVarThatDoesNotExistOnFieldset,
15
+ schemaWithInlinedRuleOnComputedAttributeThatReferencesUnknownVar,
16
+ schemaWithMissingComputedValue,
17
+ schemaWithMissingRule,
8
18
  schemaWithNativeAndJSONLogicChecks,
9
19
  schemaWithNonRequiredField,
20
+ schemaWithPropertyThatDoesNotExistInThatLevelButDoesInFieldset,
10
21
  schemaWithTwoRules,
22
+ schemaWithUnknownVariableInComputedValues,
23
+ schemaWithUnknownVariableInValidations,
24
+ schemaWithValidationThatDoesNotExistOnProperty,
11
25
  } from './jsonLogic.fixtures';
26
+ import { mockConsole, restoreConsoleAndEnsureItWasNotCalled } from './testUtils';
27
+
28
+ beforeEach(mockConsole);
29
+ afterEach(restoreConsoleAndEnsureItWasNotCalled);
12
30
 
13
31
  describe('jsonLogic: cross-values validations', () => {
14
32
  describe('Does not conflict with native JSON schema', () => {
@@ -82,6 +100,85 @@ describe('jsonLogic: cross-values validations', () => {
82
100
  });
83
101
  });
84
102
 
103
+ describe('Incorrectly written schemas', () => {
104
+ afterEach(() => console.error.mockClear());
105
+
106
+ const cases = [
107
+ [
108
+ 'x-jsf-logic.validations: throw when theres a missing rule',
109
+ schemaWithMissingRule,
110
+ '[json-schema-form] json-logic error: Validation "a_greater_than_ten" has missing rule.',
111
+ ],
112
+ [
113
+ 'x-jsf-logic.validations: throw when theres a value that does not exist in a rule',
114
+ schemaWithUnknownVariableInValidations,
115
+ '[json-schema-form] json-logic error: rule "a_equals_ten" has no variable "field_a".',
116
+ ],
117
+ [
118
+ 'x-jsf-logic.computedValues: throw when theres a value that does not exist in a rule',
119
+ schemaWithUnknownVariableInComputedValues,
120
+ '[json-schema-form] json-logic error: rule "a_times_ten" has no variable "field_a".',
121
+ ],
122
+ [
123
+ 'x-jsf-logic.computedValues: throw when theres a missing computed value',
124
+ schemaWithMissingComputedValue,
125
+ '[json-schema-form] json-logic error: Computed value "a_plus_ten" has missing rule.',
126
+ ],
127
+ [
128
+ 'x-jsf-logic-computedAttrs: error if theres a value that does not exist on an attribute.',
129
+ schemaWithComputedAttributeThatDoesntExist,
130
+ `[json-schema-form] json-logic error: Computed value "iDontExist" doesn't exist in field "field_a".`,
131
+ ],
132
+ [
133
+ 'x-jsf-logic-computedAttrs: error if theres a value that does not exist on a template string (title).',
134
+ schemaWithComputedAttributeThatDoesntExistTitle,
135
+ `[json-schema-form] json-logic error: Computed value "iDontExist" doesn't exist in field "field_a".`,
136
+ ],
137
+ [
138
+ 'x-jsf-logic-computedAttrs: error if theres a value that does not exist on a template string (description).',
139
+ schemaWithComputedAttributeThatDoesntExistDescription,
140
+ `[json-schema-form] json-logic error: Computed value "iDontExist" doesn't exist in field "field_a".`,
141
+ ],
142
+ [
143
+ 'x-jsf-logic-computedAttrs:, error if theres a value referenced that does not exist on an inline rule.',
144
+ schemaWithInlinedRuleOnComputedAttributeThatReferencesUnknownVar,
145
+ `[json-schema-form] json-logic error: fieldName "IdontExist" doesn't exist in field "field_a.x-jsf-logic-computedAttrs.title".`,
146
+ ],
147
+ [
148
+ 'x-jsf-logic.validations: error if a field does not exist in a deeply nested rule',
149
+ schemaWithDeepVarThatDoesNotExist,
150
+ '[json-schema-form] json-logic error: rule "dummy_rule" has no variable "field_b".',
151
+ ],
152
+ [
153
+ 'x-jsf-logic.validations: error if rule does not exist on a fieldset property',
154
+ schemaWithDeepVarThatDoesNotExistOnFieldset,
155
+ '[json-schema-form] json-logic error: rule "dummy_rule" has no variable "field_a".',
156
+ ],
157
+ [
158
+ 'x-jsf-validations: error if a validation name does not exist',
159
+ schemaWithValidationThatDoesNotExistOnProperty,
160
+ `[json-schema-form] json-logic error: "field_a" required validation "iDontExist" doesn't exist.`,
161
+ ],
162
+ [
163
+ 'x-jsf-logic.validations: A top level logic keyword will not be able to reference fieldset properties',
164
+ schemaWithPropertyThatDoesNotExistInThatLevelButDoesInFieldset,
165
+ '[json-schema-form] json-logic error: rule "validation_parent" has no variable "child".',
166
+ ],
167
+ [
168
+ 'x-jsf-logic.validations: error if unknown operation',
169
+ schemaWithBadOperation,
170
+ '[json-schema-form] json-logic error: in "badOperator" rule there is an unknown operator "++".',
171
+ ],
172
+ ];
173
+
174
+ test.each(cases)('%p', (_, schema, expectedErrorString) => {
175
+ const { error } = createHeadlessForm(schema, { strictInputType: false });
176
+ const expectedError = new Error(expectedErrorString);
177
+ expect(console.error).toHaveBeenCalledWith('JSON Schema invalid!', expectedError);
178
+ expect(error).toEqual(expectedError);
179
+ });
180
+ });
181
+
85
182
  describe('Arithmetic: +, -, *, /', () => {
86
183
  it('multiple: field_a > field_b * 2', () => {
87
184
  const schema = createSchemaWithRulesOnFieldA({
@@ -221,10 +318,31 @@ describe('jsonLogic: cross-values validations', () => {
221
318
  initialValues: { field_a: 2 },
222
319
  });
223
320
  const fieldB = fields.find((i) => i.name === 'field_b');
321
+ expect(fieldB.description).toEqual(
322
+ 'This field is 2 times bigger than field_a with value of 4.'
323
+ );
224
324
  expect(fieldB.default).toEqual(4);
225
325
  expect(fieldB.value).toEqual(4);
226
326
  handleValidation({ field_a: 4 });
227
327
  expect(fieldB.default).toEqual(8);
328
+ expect(fieldB.label).toEqual('This is 8!');
329
+ });
330
+
331
+ it('Derived errorMessages and statements work', () => {
332
+ const { fields, handleValidation } = createHeadlessForm(
333
+ schemaWithComputedAttributesAndErrorMessages,
334
+ { strictInputType: false }
335
+ );
336
+ const fieldB = fields.find((i) => i.name === 'field_b');
337
+ expect(handleValidation({ field_a: 2, field_b: 0 }).formErrors).toEqual({
338
+ field_b: 'Must be bigger than 4',
339
+ });
340
+ expect(handleValidation({ field_a: 2, field_b: 100 }).formErrors).toEqual({
341
+ field_b: 'Must be smaller than 8',
342
+ });
343
+ expect(fieldB.minimum).toEqual(4);
344
+ expect(fieldB.maximum).toEqual(8);
345
+ expect(fieldB.statement).toEqual({ description: 'Must be bigger than 4 and smaller than 8' });
228
346
  });
229
347
  });
230
348
  });
@@ -0,0 +1,11 @@
1
+ export function mockConsole() {
2
+ jest.spyOn(console, 'warn').mockImplementation(() => {});
3
+ jest.spyOn(console, 'error').mockImplementation(() => {});
4
+ }
5
+
6
+ export function restoreConsoleAndEnsureItWasNotCalled() {
7
+ expect(console.error).not.toHaveBeenCalled();
8
+ console.error.mockRestore();
9
+ expect(console.warn).not.toHaveBeenCalled();
10
+ console.warn.mockRestore();
11
+ }