@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 +15 -0
- package/dist/index.cjs +162 -12
- package/dist/index.js +162 -12
- package/dist/standalone.js +162 -12
- package/package.json +1 -1
- package/src/tests/const.test.js +12 -0
- package/src/tests/createHeadlessForm.test.js +3 -11
- package/src/tests/jsonLogic.fixtures.js +279 -0
- package/src/tests/jsonLogic.test.js +118 -0
- package/src/tests/testUtils.js +11 -0
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
|
-
Generated:
|
|
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
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
Generated:
|
|
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
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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 && {
|
package/dist/standalone.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
|
-
Generated:
|
|
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
|
|
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
|
-
|
|
11821
|
-
|
|
11822
|
-
|
|
11823
|
-
|
|
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
package/src/tests/const.test.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|