From 69449637fc5bbc73205684de483c3289cb683298 Mon Sep 17 00:00:00 2001 From: Brendan Bond Date: Mon, 23 Sep 2024 09:47:17 -0400 Subject: [PATCH] FIO-9055: separate rowPath from componentPath in getComponentActualValue fn (#153) * separate rowPath from componentPath in getComponentActualValue fn * update tests for edge case --- Changelog.md | 4 + src/process/__tests__/process.test.ts | 537 ++++++++++++++++++++++++++ src/utils/formUtil.ts | 13 +- 3 files changed, 548 insertions(+), 6 deletions(-) diff --git a/Changelog.md b/Changelog.md index 44219f9f..184d2d34 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,7 @@ +## [Unreleased: 2.2.3-rc.4] +### Changed + - FIO-9055: separate rowPath from componentPath in getComponentActualValue fn + ## 2.2.3-rc.3 ### Changed - moved 2.2.3-rc.2 to new branch 2.3.x diff --git a/src/process/__tests__/process.test.ts b/src/process/__tests__/process.test.ts index caa6908e..cc5acf3a 100644 --- a/src/process/__tests__/process.test.ts +++ b/src/process/__tests__/process.test.ts @@ -2701,6 +2701,543 @@ describe('Process Tests', () => { }); }); + it('Should allow conditionally hidden components that depend on state outside of the contextual row data', async () => { + const form = { + display: 'form', + components: [ + { + label: 'Select', + widget: 'choicesjs', + tableView: true, + data: { + values: [ + { + label: 'Action1', + value: 'action1', + }, + { + label: 'Custom', + value: 'custom', + }, + ], + }, + key: 'select', + type: 'select', + input: true, + }, + { + label: 'Edit Grid', + tableView: false, + rowDrafts: false, + key: 'editGrid', + type: 'editgrid', + displayAsTable: false, + input: true, + components: [ + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + key: 'textField', + conditional: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'select', + operator: 'isEqual', + value: 'custom', + }, + ], + }, + type: 'textfield', + input: true, + }, + ], + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + }; + + const submission = { + data: { + select: 'custom', + editGrid: [ + { + textField: 'TEST', + }, + ], + }, + }; + + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: {}, + config: { + server: true, + }, + }; + processSync(context); + expect(context.data).to.deep.equal({ + select: 'custom', + editGrid: [ + { + textField: 'TEST', + }, + ], + }); + }); + + it('Should allow conditionally hidden components that depend on state outside of the contextual row data with nested structures', async () => { + const form = { + display: 'form', + components: [ + { + key: 'container', + type: 'container', + components: [ + { + label: 'Select', + widget: 'choicesjs', + tableView: true, + data: { + values: [ + { + label: 'Action1', + value: 'action1', + }, + { + label: 'Custom', + value: 'custom', + }, + ], + }, + key: 'select', + type: 'select', + input: true, + }, + ], + input: true + }, + { + label: 'Edit Grid', + tableView: false, + rowDrafts: false, + key: 'editGrid', + type: 'editgrid', + displayAsTable: false, + input: true, + components: [ + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + key: 'textField', + conditional: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'container.select', + operator: 'isEqual', + value: 'custom', + }, + ], + }, + type: 'textfield', + input: true, + }, + ], + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + }; + + const submission = { + data: { + container: { + select: 'custom', + }, + editGrid: [ + { + textField: 'TEST', + }, + ], + }, + }; + + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: {}, + config: { + server: true, + }, + }; + processSync(context); + expect(context.data).to.deep.equal({ + container: { + select: 'custom', + }, + editGrid: [ + { + textField: 'TEST', + }, + ], + }); + }); + + it('Should allow conditionally hidden components that depend on state outside of the contextual row data, with co-located component keys in the row that are not targets of conditions', async () => { + const form = { + display: 'form', + components: [ + { + label: 'Select', + widget: 'choicesjs', + tableView: true, + data: { + values: [ + { + label: 'Action1', + value: 'action1', + }, + { + label: 'Custom', + value: 'custom', + }, + ], + }, + key: 'select', + type: 'select', + input: true, + }, + { + label: 'Edit Grid', + tableView: false, + rowDrafts: false, + key: 'editGrid', + type: 'editgrid', + displayAsTable: false, + input: true, + components: [ + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + key: 'textField', + conditional: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'select', + operator: 'isEqual', + value: 'custom', + }, + ], + }, + type: 'textfield', + input: true, + }, + { + label: 'Select', + widget: 'choicesjs', + tableView: true, + data: { + values: [ + { + label: 'Action1', + value: 'action1', + }, + { + label: 'Custom', + value: 'custom', + }, + ], + }, + key: 'select', + type: 'select', + input: true, + }, + ] + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + }; + + const submission = { + data: { + select: 'custom', + editGrid: [ + { + textField: 'TEST', + select: 'action1' + }, + ], + }, + }; + + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: {}, + config: { + server: true, + }, + }; + processSync(context); + expect(context.data).to.deep.equal({ + select: 'custom', + editGrid: [ + { + textField: 'TEST', + select: 'action1' + }, + ], + }); + }); + + it('Should allow conditionally hidden components that depend on state outside of the contextual row data, with nested and co-located component keys in the row that are not targets of conditions', async () => { + const form = { + display: 'form', + components: [ + { + type: 'container', + key: 'container', + input: true, + components: [ + { + label: 'Select', + widget: 'choicesjs', + tableView: true, + data: { + values: [ + { + label: 'Action1', + value: 'action1', + }, + { + label: 'Custom', + value: 'custom', + }, + ], + }, + key: 'select', + type: 'select', + input: true, + }, + { + label: 'Edit Grid', + tableView: false, + rowDrafts: false, + key: 'editGrid', + type: 'editgrid', + displayAsTable: false, + input: true, + components: [ + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + key: 'textField', + conditional: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'container.select', + operator: 'isEqual', + value: 'custom', + }, + ], + }, + type: 'textfield', + input: true, + }, + { + label: 'Select', + widget: 'choicesjs', + tableView: true, + data: { + values: [ + { + label: 'Action1', + value: 'action1', + }, + { + label: 'Custom', + value: 'custom', + }, + ], + }, + key: 'select', + type: 'select', + input: true, + }, + ] + }, + ], + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + }; + + const submission = { + data: { + container: { + select: 'custom', + editGrid: [ + { + textField: 'TEST', + select: 'action1' + }, + ], + } + }, + }; + + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: {}, + config: { + server: true, + }, + }; + processSync(context); + expect(context.data).to.deep.equal({ + container: { + select: 'custom', + editGrid: [ + { + textField: 'TEST', + select: 'action1' + }, + ], + } + }); + }); + + it('Should include submission data for logically visible fields', async () => { + const form = { + display: 'form', + components: [ + { + type: 'textfield', + key: 'textField', + label: 'Text Field', + input: true, + }, + { + type: 'textarea', + key: 'textArea', + label: 'Text Area', + input: true, + hidden: true, + logic: [ + { + name: 'Show When Not Empty', + trigger: { + type: 'simple' as const, + simple: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'textField', + operator: 'isNotEmpty', + }, + ], + }, + }, + actions: [ + { + name: 'Show', + type: 'property' as const, + property: { + label: 'Hidden', + value: 'hidden', + type: 'boolean' as const, + }, + state: false, + }, + ], + }, + ], + }, + ], + }; + + const submission = { + data: { + textField: 'not empty', + textArea: 'should be conditionally visible', + }, + }; + + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.evaluator, + scope: {}, + config: { + server: true, + }, + }; + processSync(context); + expect((context.scope as any).conditionals).to.deep.equal([{ + path: 'textArea', + conditionallyHidden: false, + }]); + expect(context.data).to.deep.equal({ + textArea: 'should be conditionally visible', + textField: 'not empty', + }); + }); + it('Should not filter a simple datamap compoennt', async () => { const form = { display: 'form', diff --git a/src/utils/formUtil.ts b/src/utils/formUtil.ts index 7947f7ac..3d9fd920 100644 --- a/src/utils/formUtil.ts +++ b/src/utils/formUtil.ts @@ -601,6 +601,7 @@ export function getComponentActualValue(component: Component, compPath: string, // let parentInputComponent: any = null; let parent = component; + let rowPath = ''; while (parent?.parent?.path && !parentInputComponent) { parent = parent.parent; @@ -611,17 +612,17 @@ export function getComponentActualValue(component: Component, compPath: string, if (parentInputComponent) { const parentCompPath = parentInputComponent.path.replace(/\[[0-9]+\]/g, ''); - compPath = compPath.replace(parentCompPath, ''); - compPath = trim(compPath, '. '); + rowPath = compPath.replace(parentCompPath, ''); + rowPath = trim(rowPath, '. '); } let value = null; - if (row) { - value = get(row, compPath); - } - if (data && isNil(value)) { + if (data) { value = get(data, compPath); } + if (rowPath && row && isNil(value)) { + value = get(row, rowPath); + } if (isNil(value) || (isObject(value) && isEmpty(value))) { value = ''; }