diff --git a/src/process/clearHidden.ts b/src/process/clearHidden.ts index 370a844b..03ed8e80 100644 --- a/src/process/clearHidden.ts +++ b/src/process/clearHidden.ts @@ -31,7 +31,7 @@ export const clearHiddenProcess: ProcessorFnSync = (context) = // Check if there's a conditional set for the component and if it's marked as conditionally hidden const isConditionallyHidden = (scope as ConditionsScope).conditionals?.find((cond) => { - return path.includes(cond.path) && cond.conditionallyHidden; + return path === cond.path && cond.conditionallyHidden; }); const shouldClearValueWhenHidden = !component.hasOwnProperty('clearOnHide') || component.clearOnHide; diff --git a/src/process/conditions/__tests__/conditions.test.ts b/src/process/conditions/__tests__/conditions.test.ts index eb84c890..4ede936e 100644 --- a/src/process/conditions/__tests__/conditions.test.ts +++ b/src/process/conditions/__tests__/conditions.test.ts @@ -1,142 +1,648 @@ import { expect } from 'chai'; -import { processSync } from '../../process' +import { processSync } from '../../process'; import { conditionProcessInfo } from '../index'; import { ConditionsScope, ProcessContext } from 'types'; import { get } from 'lodash'; const processForm = (form: any, submission: any) => { - const context: ProcessContext = { - processors: [conditionProcessInfo], - components: form.components, - data: submission.data, - scope: {} - }; - processSync(context); - return context; + const context: ProcessContext = { + processors: [conditionProcessInfo], + components: form.components, + data: submission.data, + scope: {}, + }; + processSync(context); + return context; }; describe('Condition processor', () => { - it('Should modify component\'s "hidden" property when conditionally visible is false', async () => { - const form = { - components: [ - { - type: 'textfield', - key: 'a', - input: true - }, - { - type: 'textfield', - key: 'b', - input: true, - conditional: { - show: false, - conjunction: 'all', - conditions: [ - { - component: 'a', - operator: 'isEmpty' - } - ] - }, - } - ] - }; - - const submission = { - data: { - a: '', - } - }; - - const context: ProcessContext = processForm(form, submission); - expect(context.components[1]).to.haveOwnProperty('hidden'); - expect(context.components[1].hidden).to.be.true; - }); - - it('Should not define a conditional component (that condition is based on selectBoxes value) as hidden', async () => { - const form1 = { - components: [ - { - label: 'Select Boxes', - optionsLabelPosition: 'right', - tableView: false, - defaultValue: { - '1': false, - '2': false, - '3': false, - test3: false, - }, - values: [ + it('Should modify component\'s "hidden" property when conditionally visible is false', async () => { + const form = { + components: [ + { + type: 'textfield', + key: 'a', + input: true, + }, + { + type: 'textfield', + key: 'b', + input: true, + conditional: { + show: false, + conjunction: 'all', + conditions: [ { - label: '1', - value: '1', - shortcut: '', + component: 'a', + operator: 'isEmpty', }, + ], + }, + }, + ], + }; + + const submission = { + data: { + a: '', + }, + }; + + const context: ProcessContext = processForm( + form, + submission + ); + expect(context.components[1]).to.haveOwnProperty('hidden'); + expect(context.components[1].hidden).to.be.true; + }); + + it('Should not define a conditional component (that condition is based on selectBoxes value) as hidden', async () => { + const form1 = { + components: [ + { + label: 'Select Boxes', + optionsLabelPosition: 'right', + tableView: false, + defaultValue: { + '1': false, + '2': false, + '3': false, + test3: false, + }, + values: [ + { + label: '1', + value: '1', + shortcut: '', + }, + { + label: '2', + value: '2', + shortcut: '', + }, + { + label: '3', + value: '3', + shortcut: '', + }, + ], + validateWhenHidden: false, + key: 'selectBoxes', + type: 'selectboxes', + input: true, + inputType: 'checkbox', + }, + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + validateWhenHidden: false, + key: 'textField', + conditional: { + show: true, + conjunction: 'all', + conditions: [ { - label: '2', - value: '2', - shortcut: '', + component: 'selectBoxes', + operator: 'isEqual', + value: '3', }, + ], + }, + type: 'textfield', + input: true, + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + }; + + const submission1 = { + data: { + selectBoxes: { + '1': false, + '2': false, + '3': true, + }, + textField: 'test', + submit: true, + }, + }; + + const context: ProcessContext = processForm( + form1, + submission1 + ); + + expect(get(context, 'scope.conditionals[0].conditionallyHidden')).to.be + .false; + }); + + it('Should always add components keyed by absolute path to conditional scope (simple components)', async () => { + const form = { + components: [ + { + type: 'textfield', + key: 'a', + input: true, + }, + { + type: 'textfield', + key: 'b', + input: true, + conditional: { + show: false, + conjunction: 'all', + conditions: [ { - label: '3', - value: '3', - shortcut: '', + component: 'a', + operator: 'isEmpty', }, ], - validateWhenHidden: false, - key: 'selectBoxes', - type: 'selectboxes', - input: true, - inputType: 'checkbox', }, - { - label: 'Text Field', - applyMaskOn: 'change', - tableView: true, - validateWhenHidden: false, - key: 'textField', - conditional: { - show: true, - conjunction: 'all', - conditions: [ + }, + ], + }; + + const submission = { + data: { + a: '', + }, + }; + + const context: ProcessContext = processForm( + form, + submission + ); + expect(context.scope.conditionals).to.have.length(1); + expect(context.scope.conditionals?.[0].path).to.equal('b'); + }); + + it('Should always add components keyed by absolute data path to conditional scope (data grid components)', async () => { + const form = { + components: [ + { + label: 'Check Me', + tableView: false, + validateWhenHidden: false, + key: 'checkMe', + type: 'checkbox', + input: true, + defaultValue: false, + }, + { + label: 'Data Grid', + reorder: false, + addAnotherPosition: 'bottom', + layoutFixed: false, + enableRowGroups: false, + initEmpty: false, + tableView: false, + defaultValue: [{}], + validateWhenHidden: false, + key: 'dataGrid', + type: 'datagrid', + input: true, + components: [ + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + validateWhenHidden: false, + key: 'textField', + conditional: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'checkMe', + operator: 'isEqual', + value: true, + }, + ], + }, + type: 'textfield', + input: true, + }, + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + validateWhenHidden: false, + key: 'textField1', + type: 'textfield', + input: true, + }, + ], + }, + ], + }; + + const submission = { + data: { + checkMe: false, + dataGrid: [{ textField: 'test' }, { textField: 'test1' }], + }, + }; + + const context: ProcessContext = processForm( + form, + submission + ); + expect(context.scope.conditionals).to.have.length(2); + expect(context.scope.conditionals?.[0].path).to.equal( + 'dataGrid[0].textField' + ); + expect(context.scope.conditionals?.[1].path).to.equal( + 'dataGrid[1].textField' + ); + }); + + it('Should always add components keyed by absolute data path to conditional scope (edit grid components)', async () => { + const form = { + components: [ + { + label: 'Check Me', + tableView: false, + validateWhenHidden: false, + key: 'checkMe', + type: 'checkbox', + input: true, + defaultValue: false, + }, + { + label: 'Edit Grid', + reorder: false, + addAnotherPosition: 'bottom', + layoutFixed: false, + enableRowGroups: false, + initEmpty: false, + tableView: false, + defaultValue: [{}], + validateWhenHidden: false, + key: 'editGrid', + type: 'editgrid', + input: true, + components: [ + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + validateWhenHidden: false, + key: 'textField', + conditional: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'checkMe', + operator: 'isEqual', + value: true, + }, + ], + }, + type: 'textfield', + input: true, + }, + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + validateWhenHidden: false, + key: 'textField1', + type: 'textfield', + input: true, + }, + ], + }, + ], + }; + + const submission = { + data: { + checkMe: false, + editGrid: [{ textField: 'test' }, { textField: 'test1' }], + }, + }; + + const context: ProcessContext = processForm( + form, + submission + ); + expect(context.scope.conditionals).to.have.length(2); + expect(context.scope.conditionals?.[0].path).to.equal( + 'editGrid[0].textField' + ); + expect(context.scope.conditionals?.[1].path).to.equal( + 'editGrid[1].textField' + ); + }); + + it('Should always add components keyed by absolute data path to conditional scope (container components)', async () => { + const form = { + components: [ + { + label: 'Check Me', + tableView: false, + validateWhenHidden: false, + key: 'checkMe', + type: 'checkbox', + input: true, + defaultValue: false, + }, + { + label: 'Container', + tableView: false, + key: 'container', + type: 'container', + input: true, + components: [ + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + validateWhenHidden: false, + key: 'textField', + conditional: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'checkMe', + operator: 'isEqual', + value: true, + }, + ], + }, + type: 'textfield', + input: true, + }, + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + validateWhenHidden: false, + key: 'textField1', + type: 'textfield', + input: true, + }, + ], + }, + ], + }; + + const submission = { + data: { + checkMe: false, + container: { textField: 'test' }, + }, + }; + + const context: ProcessContext = processForm( + form, + submission + ); + expect(context.scope.conditionals).to.have.length(1); + expect(context.scope.conditionals?.[0].path).to.equal( + 'container.textField' + ); + }); + + it('Should always add components keyed by absolute data path to conditional scope (layout components)', async () => { + const form = { + components: [ + { + label: 'Check Me', + tableView: false, + validateWhenHidden: false, + key: 'checkMe', + type: 'checkbox', + input: true, + defaultValue: false, + }, + { + label: 'Panel', + tableView: false, + key: 'panel', + type: 'panel', + input: true, + components: [ + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + validateWhenHidden: false, + key: 'textField', + conditional: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'checkMe', + operator: 'isEqual', + value: true, + }, + ], + }, + type: 'textfield', + input: true, + }, + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + validateWhenHidden: false, + key: 'textField1', + type: 'textfield', + input: true, + }, + ], + }, + ], + }; + + const submission = { + data: { + checkMe: false, + panel: { textField: 'test' }, + }, + }; + + const context: ProcessContext = processForm( + form, + submission + ); + expect(context.scope.conditionals).to.have.length(1); + // Panel components are layout components, so are not pathed + expect(context.scope.conditionals?.[0].path).to.equal('textField'); + }); + + it('Should always add components keyed by absolute data path to conditional scope (deeply nested components)', async () => { + const form = { + components: [ + { + label: 'Tabs', + components: [ + { + label: 'Tab 1', + key: 'tab1', + components: [ { - component: 'selectBoxes', - operator: 'isEqual', - value: '3', + label: 'Check Me', + tableView: false, + validateWhenHidden: false, + key: 'checkMe', + type: 'checkbox', + input: true, + defaultValue: false, }, ], }, - type: 'textfield', - input: true, - }, + { + label: 'Tab 2', + key: 'tab2', + components: [ + { + label: 'Outer Data Grid', + reorder: false, + addAnotherPosition: 'bottom', + layoutFixed: false, + enableRowGroups: false, + initEmpty: false, + tableView: false, + defaultValue: [ + { + outerContainer: { + innerContainer: { + dataGrid: [ + { + textField1: '', + }, + ], + }, + }, + }, + ], + validateWhenHidden: false, + key: 'outerDataGrid', + type: 'datagrid', + input: true, + components: [ + { + label: 'Outer Container', + tableView: false, + validateWhenHidden: false, + key: 'outerContainer', + type: 'container', + input: true, + components: [ + { + label: 'Inner Container', + tableView: false, + validateWhenHidden: false, + key: 'innerContainer', + type: 'container', + input: true, + components: [ + { + label: 'Inner Data Grid', + reorder: false, + addAnotherPosition: 'bottom', + layoutFixed: false, + enableRowGroups: false, + initEmpty: false, + tableView: false, + defaultValue: [ + { + textField1: '', + }, + ], + validateWhenHidden: false, + key: 'innerDataGrid', + type: 'datagrid', + input: true, + components: [ + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + validateWhenHidden: false, + key: 'textField', + conditional: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'checkMe', + operator: 'isEqual', + value: true, + }, + ], + }, + type: 'textfield', + input: true, + }, + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + validateWhenHidden: false, + key: 'textField1', + type: 'textfield', + input: true, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + key: 'tabs', + type: 'tabs', + input: false, + tableView: false, + }, + ], + }; + + const submission = { + data: { + checkMe: false, + outerDataGrid: [ { - type: 'button', - label: 'Submit', - key: 'submit', - disableOnInvalid: true, - input: true, - tableView: false, + outerContainer: { + innerContainer: { + innerDataGrid: [ + { + textField1: 'test', + }, + ], + }, + }, }, ], - }; - - const submission1 = { - data: { - selectBoxes: { - '1': false, - '2': false, - '3': true, - }, - textField: 'test', - submit: true, - }, - }; - - const context: ProcessContext = processForm( - form1, - submission1 - ); - - expect(get(context, 'scope.conditionals[0].conditionallyHidden')).to.be.false; - }); + submit: true, + }, + }; + + const context: ProcessContext = processForm( + form, + submission + ); + expect(context.scope.conditionals).to.have.length(1); + expect(context.scope.conditionals?.[0].path).to.equal( + 'outerDataGrid[0].outerContainer.innerContainer.innerDataGrid[0].textField' + ); + }); }); diff --git a/src/process/conditions/index.ts b/src/process/conditions/index.ts index 211a702e..874a6b79 100644 --- a/src/process/conditions/index.ts +++ b/src/process/conditions/index.ts @@ -79,7 +79,7 @@ export const isConditionallyHidden = (context: ConditionsContext): boolean => { export type ConditionallyHidden = (context: ConditionsContext) => boolean; export const conditionalProcess = (context: ConditionsContext, isHidden: ConditionallyHidden) => { - const { component, row, scope, path } = context; + const { component, scope, path } = context; if (!hasConditions(context)) { return; } diff --git a/src/process/hideChildren.ts b/src/process/hideChildren.ts index 2d2c81de..ec90a3e7 100644 --- a/src/process/hideChildren.ts +++ b/src/process/hideChildren.ts @@ -17,7 +17,7 @@ export const hideChildrenProcessor: ProcessorFnSync = (context) const { component, path, row, scope } = context; // Check if there's a conditional set for the component and if it's marked as conditionally hidden const isConditionallyHidden = scope.conditionals?.find((cond) => { - return path.includes(cond.path) && cond.conditionallyHidden; + return path === cond.path && cond.conditionallyHidden; }); if (component.hidden && isConditionallyHidden) { const info = componentInfo(component);