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
8 changes: 7 additions & 1 deletion packages/core/src/models/uischema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import {JsonSchema} from './jsonSchema';

/**
* Interface for describing an UI schema element that is referencing
* a subschema. The value of the scope must be a JSON Pointer.
Expand Down Expand Up @@ -78,7 +80,7 @@ export interface Condition {
/**
* The type of condition.
*/
type: string;
type?: string;
}

/**
Expand All @@ -94,6 +96,10 @@ export interface LeafCondition extends Condition, Scopable {
expectedValue: any;
}

export interface SchemaBasedCondition extends Condition, Scopable {
schema: JsonSchema;
}

/**
* Common base interface for any UI schema element.
*/
Expand Down
69 changes: 43 additions & 26 deletions packages/core/src/util/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,51 +25,68 @@
import * as _ from 'lodash';
// TODO: pass in uischema and data instead of props and state
import { getData } from '../reducers';
import { LeafCondition, RuleEffect, UISchemaElement } from '../models/uischema';
import {
Condition,
LeafCondition,
RuleEffect,
SchemaBasedCondition,
UISchemaElement
} from '../models/uischema';
import { resolveData } from './resolvers';
import { toDataPath } from './path';
import { createAjv } from './validator';

const isRuleDefined = (uischema: UISchemaElement): boolean =>
!_.has(uischema, 'rule.condition') || !_.has(uischema, 'rule.condition.type') ||
const ajv = createAjv();

const ruleIsMissingProperties = (uischema: UISchemaElement): boolean =>
!_.has(uischema, 'rule.condition') ||
!_.has(uischema, 'rule.condition.scope') ||
!_.has(uischema, 'rule.condition.expectedValue');
(!_.has(uischema, 'rule.condition.expectedValue') && !_.has(uischema, 'rule.condition.schema'));

export const evalVisibility = (uischema: UISchemaElement, data: any) => {
// TODO condition evaluation should be done somewhere else
if (isRuleDefined(uischema)) {
const isLeafCondition = (condition: Condition): condition is LeafCondition =>
condition.type === 'LEAF';

const isSchemaCondition = (condition: Condition): condition is SchemaBasedCondition =>
_.has(condition, 'schema');

const isConditionFulfilled = (uischema: UISchemaElement, data: any) => {
if (ruleIsMissingProperties(uischema)) {
return true;
}
const condition = uischema.rule.condition as LeafCondition;
const value = resolveData(data, toDataPath(condition.scope));
const equals = value === condition.expectedValue;

switch (uischema.rule.effect) {
case RuleEffect.HIDE: return !equals;
case RuleEffect.SHOW: return equals;
default:
// visible by default
const condition = uischema.rule.condition;

if (isLeafCondition(condition)) {
const value = resolveData(data, toDataPath(condition.scope));
return value === condition.expectedValue;
} else if (isSchemaCondition(condition)) {
const value = resolveData(data, toDataPath(condition.scope));
return ajv.validate(condition.schema, value);
} else {
// unknown condition
return true;
}
};

export const evalEnablement = (uischema: UISchemaElement, data: any) => {

if (isRuleDefined(uischema)) {
export const evalVisibility = (uischema: UISchemaElement, data: any) => {
const fulfilled = isConditionFulfilled(uischema, data);

return true;
switch (uischema.rule.effect) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we unify the formatting for evalVisibility and evalEnablement

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

case RuleEffect.HIDE: return !fulfilled;
case RuleEffect.SHOW: return fulfilled;
// visible by default
default: return true;
}
};

const condition = uischema.rule.condition as LeafCondition;
const value = resolveData(data, toDataPath(condition.scope));
const equals = value === condition.expectedValue;
export const evalEnablement = (uischema: UISchemaElement, data: any) => {
const fulfilled = isConditionFulfilled(uischema, data);

switch (uischema.rule.effect) {
case RuleEffect.DISABLE: return !equals;
case RuleEffect.ENABLE: return equals;
default:
case RuleEffect.DISABLE: return !fulfilled;
case RuleEffect.ENABLE: return fulfilled;
// enabled by default
return true;
default: return true;
}
};

Expand Down
72 changes: 71 additions & 1 deletion packages/core/test/util/runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
THE SOFTWARE.
*/
import test from 'ava';
import { ControlElement, LeafCondition, RuleEffect } from '../../src';
import { ControlElement, LeafCondition, RuleEffect, SchemaBasedCondition } from '../../src';
import { evalEnablement, evalVisibility } from '../../src/util/runtime';

test('evalVisibility show valid case', t => {
Expand All @@ -47,6 +47,52 @@ test('evalVisibility show valid case', t => {
t.is(evalVisibility(uischema, data), true);
});

test('evalVisibility show valid case based on schema condition', t => {
const condition: SchemaBasedCondition = {
scope: '#/properties/ruleValue',
schema: {
const: 'bar'
}
};
const uischema: ControlElement = {
type: 'Control',
scope: '#/properties/value',
rule: {
effect: RuleEffect.SHOW,
condition
}
};
const data = {
value: 'foo',
ruleValue: 'bar'
};
t.is(evalVisibility(uischema, data), true);
});

test.only('evalVisibility show valid case based on schema condition and enum', t => {
const condition: SchemaBasedCondition = {
scope: '#/properties/ruleValue',
schema: {
enum: ['bar', 'baz']
}
};
const uischema: ControlElement = {
type: 'Control',
scope: '#/properties/value',
rule: {
effect: RuleEffect.SHOW,
condition
}
};
const data = {
value: 'foo',
ruleValue: 'bar'
};
t.is(evalVisibility(uischema, data), true);
t.is(evalVisibility(uischema, { ...data, ruleValue: 'baz', }), true);
t.is(evalVisibility(uischema, { ...data, ruleValue: 'foo'}), false);
});

test('evalVisibility show invalid case', t => {
const leafCondition: LeafCondition = {
type: 'LEAF' ,
Expand Down Expand Up @@ -191,3 +237,27 @@ test('evalEnablement disable invalid case', t => {
};
t.is(evalEnablement(uischema, data), true);
});

test('evalEnablement disable invalid case based on schema condition', t => {
const condition: SchemaBasedCondition = {
scope: '#/properties/ruleValue',
schema: {
enum: ['bar', 'baz']
}
};
const uischema: ControlElement = {
type: 'Control',
scope: '#/properties/value',
rule: {
effect: RuleEffect.DISABLE,
condition
}
};
const data = {
value: 'foo',
ruleValue: 'bar'
};
t.is(evalEnablement(uischema, data), true);
t.is(evalEnablement(uischema, {...data, ruleValue: 'baz'}), true);
t.is(evalEnablement(uischema, {...data, ruleValue: 'foo'}), false);
});