Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/core/src/models/uischema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/util/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 53 additions & 0 deletions packages/core/test/util/runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down