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
20 changes: 17 additions & 3 deletions v0/src/jsonLogic.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,26 +404,40 @@ function validateInlineRules(jsonSchema, sampleEmptyObject) {
* @param {Function} errorMessage - Function to generate custom error message.
* Receives the invalid rule part and should throw an error message string.
*/

function checkRuleIntegrity(
rule,
id,
data,
errorMessage = (item) =>
`[json-schema-form] json-logic error: rule "${id}" has no variable "${item.var}".`
`[json-schema-form] json-logic error: rule "${id}" has no variable "${item.var}".`,
inReduceOrMap = false
) {
Object.entries(rule ?? {}).map(([operator, subRule]) => {
if (!Array.isArray(subRule) && subRule !== null && subRule !== undefined) return;
throwIfUnknownOperator(operator, subRule, id);

// If we are within a reduce or map, we allow references to `accumulator` and/or `current`.
// We augment the data so that we still validate any other field.
const isReduceOrMap = inReduceOrMap || operator === 'reduce' || operator === 'map';

const validationData = isReduceOrMap
? {
...data,
accumulator: 0,
current: null,
}
: data;

subRule.map((item) => {
const isVar = item !== null && typeof item === 'object' && Object.hasOwn(item, 'var');
if (isVar) {
const exists = jsonLogic.apply({ var: removeIndicesFromPath(item.var) }, data);
const exists = jsonLogic.apply({ var: removeIndicesFromPath(item.var) }, validationData);
if (exists === null) {
throw Error(errorMessage(item));
}
} else {
checkRuleIntegrity(item, id, data);
checkRuleIntegrity(item, id, data, errorMessage, isReduceOrMap);
}
});
});
Expand Down
52 changes: 50 additions & 2 deletions v0/src/tests/jsonLogic.fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -1061,7 +1061,7 @@ export const schemaWithReduceAccumulator = {
type: 'number',
'x-jsf-logic-computedAttrs': {
const: 'computed_work_hours_per_week',
defaultValue: 'computed_work_hours_per_week',
default: 'computed_work_hours_per_week',
title: '{{computed_work_hours_per_week}} hours per week',
},
},
Expand All @@ -1073,11 +1073,59 @@ export const schemaWithReduceAccumulator = {
'*': [
{ var: 'working_hours_per_day' },
{
reduce: [{ var: 'work_days' }, { '+': [{ var: ['accumulator', 0] }, 1] }, 0],
reduce: [{ var: 'work_days' }, { '+': [{ var: 'accumulator' }, 1] }, 0],
},
],
},
},
},
},
};

export const schemaWithReduceAccumulatorAndMerge = {
properties: {
work_days: {
items: {
anyOf: [
{ const: 'monday', title: 'Monday' },
{ const: 'tuesday', title: 'Tuesday' },
{ const: 'wednesday', title: 'Wednesday' },
{ const: 'thursday', title: 'Thursday' },
{ const: 'friday', title: 'Friday' },
{ const: 'saturday', title: 'Saturday' },
{ const: 'sunday', title: 'Sunday' },
],
},
type: 'array',
uniqueItems: true,
'x-jsf-presentation': {
inputType: 'select',
},
},
working_hours_per_day: {
type: 'number',
},
working_hours_per_week: {
type: 'number',
'x-jsf-logic-computedAttrs': {
const: 'computed_work_hours_per_week',
default: 'computed_work_hours_per_week',
title: '{{computed_work_hours_per_week}} hours per week',
},
},
},
'x-jsf-logic': {
computedValues: {
computed_work_hours_per_week: {
rule: {
reduce: [
{ var: 'work_days' },
{ merge: [{ var: 'accumulator' }, [{ var: 'current' }]] },
// note we use the wrong accumulator type
0,
],
},
},
},
},
};
16 changes: 14 additions & 2 deletions v0/src/tests/jsonLogic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
schemaWithValidationThatDoesNotExistOnProperty,
badSchemaThatWillNotSetAForcedValue,
schemaWithReduceAccumulator,
schemaWithReduceAccumulatorAndMerge,
schemaWithComputedPresentationAttributes,
} from './jsonLogic.fixtures';
import { mockConsole, restoreConsoleAndEnsureItWasNotCalled } from './testUtils';
Expand Down Expand Up @@ -245,8 +246,7 @@ describe('jsonLogic: cross-values validations', () => {
});
});

// TODO: Implement this test.
describe.skip('Reduce', () => {
describe('Reduce', () => {
it('reduce: working_hours_per_day * work_days', () => {
const { fields, handleValidation } = createHeadlessForm(schemaWithReduceAccumulator, {
strictInputType: false,
Expand All @@ -260,6 +260,18 @@ describe('jsonLogic: cross-values validations', () => {
expect(field.default).toEqual(16);
expect(field.label).toEqual('16 hours per week');
});

it('reduce: handles when operator is non numerical', () => {
const { fields, handleValidation } = createHeadlessForm(schemaWithReduceAccumulatorAndMerge, {
strictInputType: false,
});
handleValidation({
work_days: ['monday', 'tuesday'],
working_hours_per_day: 8,
});
const field = fields.find((i) => i.name === 'working_hours_per_week');
expect(field.const).toEqual([0, 'monday', 'tuesday']);
});
});

describe('Logical: ||, &&', () => {
Expand Down
Loading