From 8fe5fca94c2904357af08503b445714ccb2eebb7 Mon Sep 17 00:00:00 2001 From: Clemens Zeidler Date: Thu, 16 Feb 2023 15:09:59 +1300 Subject: [PATCH] Add a scopeRequired option to the SchemaBasedCondition Co-authored-by: Stefan Dirix --- packages/core/src/models/uischema.ts | 13 ++++++ packages/core/src/util/runtime.ts | 3 ++ packages/core/test/util/runtime.test.ts | 53 +++++++++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/packages/core/src/models/uischema.ts b/packages/core/src/models/uischema.ts index bde29ba2e1..efdf8d5faa 100644 --- a/packages/core/src/models/uischema.ts +++ b/packages/core/src/models/uischema.ts @@ -135,6 +135,19 @@ export interface LeafCondition extends Condition, Scoped { export interface SchemaBasedCondition extends Condition, Scoped { schema: JsonSchema; + + /** + * When the scope resolves to undefined and `failWhenUndefined` is set to `true`, the condition + * will fail. Therefore the reverse effect will be applied. + * + * Background: + * Most JSON Schemas will successfully validate against `undefined` data. Specifying that a + * condition shall fail when data is `undefined` requires to lift the scope to being able to use + * JSON Schema's `required`. + * + * Using `failWhenUndefined` allows to more conveniently express this condition. + */ + failWhenUndefined?: boolean; } /** diff --git a/packages/core/src/util/runtime.ts b/packages/core/src/util/runtime.ts index 2db828eb47..4a59c79150 100644 --- a/packages/core/src/util/runtime.ts +++ b/packages/core/src/util/runtime.ts @@ -79,6 +79,9 @@ const evaluateCondition = ( return value === condition.expectedValue; } else if (isSchemaCondition(condition)) { const value = resolveData(data, getConditionScope(condition, path)); + if (condition.failWhenUndefined && value === undefined) { + return false; + } return ajv.validate(condition.schema, value) as boolean; } else { // unknown condition diff --git a/packages/core/test/util/runtime.test.ts b/packages/core/test/util/runtime.test.ts index 6b554e6463..434ba9827b 100644 --- a/packages/core/test/util/runtime.test.ts +++ b/packages/core/test/util/runtime.test.ts @@ -552,6 +552,59 @@ test('evalEnablement disable invalid case based on schema condition', (t) => { ); }); +test('evalEnablement fail on failWhenUndefined', (t) => { + const condition: SchemaBasedCondition = { + scope: '#/properties/ruleValue', + schema: { + enum: ['bar', 'baz'], + }, + }; + const failConditionTrue: SchemaBasedCondition = { + ...condition, + failWhenUndefined: true, + }; + const failConditionFalse: SchemaBasedCondition = { + ...condition, + failWhenUndefined: false, + }; + + const uischema: ControlElement = { + type: 'Control', + scope: '#/properties/value', + rule: { + effect: RuleEffect.DISABLE, + condition: failConditionTrue, + }, + }; + const failConditionTrueUischema: ControlElement = { + type: 'Control', + scope: '#/properties/value', + rule: { + effect: RuleEffect.DISABLE, + condition: failConditionTrue, + }, + }; + const failConditionFalseUischema: ControlElement = { + type: 'Control', + scope: '#/properties/value', + rule: { + effect: RuleEffect.DISABLE, + condition: failConditionFalse, + }, + }; + const data = { + value: 'foo', + }; + t.is( + evalEnablement(failConditionTrueUischema, data, undefined, createAjv()), + true + ); + t.is( + evalEnablement(failConditionFalseUischema, data, undefined, createAjv()), + evalEnablement(uischema, data, undefined, createAjv()) + ); +}); + test('isInherentlyEnabled disabled globally', (t) => { t.false( isInherentlyEnabled(