From 8b0d9810ce3b1ad6e222c5ce8633b0e3dad3d3d4 Mon Sep 17 00:00:00 2001 From: "ICX\\Tatsiana.Hashtold" Date: Mon, 28 Oct 2024 14:55:43 +0300 Subject: [PATCH 1/2] FIO-9255: fixed an issue where nested forms lose data after submission if some parent has condtional components --- src/process/__tests__/process.test.ts | 368 ++++++++++++++++++++++- src/utils/formUtil/eachComponent.ts | 42 ++- src/utils/formUtil/eachComponentAsync.ts | 24 +- src/utils/formUtil/index.ts | 5 +- 4 files changed, 417 insertions(+), 22 deletions(-) diff --git a/src/process/__tests__/process.test.ts b/src/process/__tests__/process.test.ts index 29a80fa5..85f435dd 100644 --- a/src/process/__tests__/process.test.ts +++ b/src/process/__tests__/process.test.ts @@ -972,8 +972,6 @@ describe('Process Tests', function () { submission.data = context.data; context.processors = ProcessTargets.evaluator; processSync(context); - console.log(context.scope.errors); - assert.equal(context.scope.errors.length, 0); }); @@ -4033,13 +4031,377 @@ describe('Process Tests', function () { submission.data = context.data; context.processors = ProcessTargets.evaluator; processSync(context); - console.log(111, context.data, context.scope.conditionals); assert.deepEqual(context.data.textFieldShowOnTest1, 'test'); context.scope.conditionals.forEach((cond: any) => { assert.equal(cond.conditionallyHidden, cond.path !== 'textFieldShowOnTest1'); }); }); + it('Should not unset values of deeply nested form when some components in other forms have conditional components', function () { + const form = { + _id: '6718a657d064efa63512550e', + title: 'eSubmissionsExt', + name: 'eSubmissionsExt', + path: 'esubmissionsext', + type: 'form', + display: 'wizard', + tags: [], + deleted: null, + owner: '6718e9edf62d8a6fd8104211', + components: [ + { + title: 'eSubmissions', + breadcrumbClickable: true, + buttonSettings: { + previous: true, + cancel: true, + next: true, + }, + navigateOnEnter: false, + saveOnEnter: false, + scrollToTop: false, + collapsible: false, + key: 'eSubmissions', + type: 'panel', + label: 'Page 1', + components: [ + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + validateWhenHidden: false, + key: 'textField', + type: 'textfield', + input: true, + }, + { + label: 'PMTA', + tableView: true, + form: '6718a657d064efa6351254eb', + useOriginalRevision: false, + reference: false, + key: 'pmta', + conditional: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'textField', + operator: 'isEqual', + value: '5', + }, + ], + }, + type: 'form', + input: true, + components: [ + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + { + title: 'Section I - Applicant Identification', + breadcrumbClickable: true, + buttonSettings: { + previous: true, + cancel: true, + next: true, + }, + navigateOnEnter: false, + saveOnEnter: false, + scrollToTop: false, + collapsible: false, + key: 'section1', + type: 'panel', + label: 'Page 6', + input: false, + tableView: false, + components: [ + { + label: 'Identification Information', + tableView: true, + form: '6718a657d064efa6351254e4', + useOriginalRevision: false, + reference: false, + clearOnHide: false, + key: 'contacts', + type: 'form', + input: true, + components: [ + { + title: 'Part A: Applicant Identification', + breadcrumbClickable: true, + buttonSettings: { + previous: true, + cancel: true, + next: true, + }, + navigateOnEnter: false, + saveOnEnter: false, + scrollToTop: false, + collapsible: false, + key: 'section1A', + type: 'panel', + label: 'Page 1', + components: [ + { + label: 'Applicant Organization', + tableView: true, + form: '6718a657d064efa6351254c8', + useOriginalRevision: false, + reference: false, + clearOnHide: false, + key: 'applicantOrganization', + type: 'form', + lazyLoad: true, + input: true, + components: [ + { + label: 'Address', + tableView: true, + form: '6718f5a4f62d8a6fd8110a41', + useOriginalRevision: false, + reference: false, + clearOnHide: false, + key: 'address', + type: 'form', + input: true, + components: [ + { + label: 'Country', + widget: 'choicesjs', + tableView: true, + data: { + values: [ + { label: 'a', value: 'a' }, + { label: 'b', value: 'b' }, + ], + resource: '6718a657d064efa635125495', + }, + clearOnHide: false, + validateWhenHidden: false, + key: 'country', + type: 'select', + input: true, + }, + { + label: 'Zip Code', + displayMask: '99999-9999', + applyMaskOn: 'change', + tableView: true, + clearOnHide: false, + validateOn: 'blur', + validateWhenHidden: false, + key: 'zipCode', + conditional: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'country', + operator: 'isEqual', + value: 'a', + }, + ], + }, + type: 'textfield', + input: true, + }, + { + label: 'Postal Code', + applyMaskOn: 'change', + tableView: true, + clearOnHide: false, + validateWhenHidden: false, + key: 'postalCode', + conditional: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'country', + operator: 'isNotEqual', + value: 'a', + }, + ], + }, + type: 'textfield', + input: true, + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + }, + ], + input: false, + tableView: false, + }, + { + title: 'Part B: Authorized Representative or U.S. Agent Information', + breadcrumbClickable: true, + buttonSettings: { + previous: true, + cancel: true, + next: true, + }, + navigateOnEnter: false, + saveOnEnter: false, + scrollToTop: false, + collapsible: false, + key: 'section1B', + properties: { subsection: '1' }, + type: 'panel', + label: 'Page 1', + components: [ + { + label: 'Authorized Representative', + tableView: true, + form: '6718a657d064efa6351254d6', + useOriginalRevision: false, + reference: false, + clearOnHide: false, + key: 'authorizedRepresentative', + type: 'form', + lazyLoad: true, + input: true, + components: [ + { + label: 'Organization', + tableView: true, + form: '6718a657d064efa6351254b0', + useOriginalRevision: false, + reference: false, + key: 'organization', + type: 'form', + input: true, + clearOnHide: false, + components: [ + { + label: 'Organization Name', + applyMaskOn: 'change', + tableView: true, + clearOnHide: false, + validateWhenHidden: false, + key: 'organizationName', + type: 'textfield', + input: true, + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + }, + ], + input: false, + tableView: false, + }, + ], + }, + ], + }, + ], + }, + ], + input: false, + tableView: false, + }, + ], + created: '2024-10-23T07:31:35.235Z', + modified: '2024-10-23T13:14:44.389Z', + }; + + const data = { + textField: '5', + pmta: { + data: { + contacts: { + data: { + applicantOrganization: { + data: { + address: { + data: { country: 'a', zipCode: '555555555', postalCode: '' }, + metadata: { selectData: { country: { label: 'a' } } }, + }, + }, + metadata: {}, + }, + authorizedRepresentative: { + data: { + organization: { + data: { organizationName: '66666' }, + metadata: {}, + }, + }, + metadata: {}, + }, + }, + }, + }, + }, + }; + const submission = { + data: fastCloneDeep(data), + }; + + const errors: any = []; + const conditionals: any = []; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.submission, + scope: { errors, conditionals }, + config: { + server: true, + }, + }; + + processSync(context); + submission.data = context.data; + context.processors = ProcessTargets.evaluator; + processSync(context); + assert.deepEqual(context.data, data); + context.scope.conditionals.forEach((cond: any) => { + assert.equal(cond.conditionallyHidden, cond.path === 'postalCode'); + }); + }); + describe('Required component validation in nested form in DataGrid/EditGrid', function () { const nestedForm = { key: 'form', diff --git a/src/utils/formUtil/eachComponent.ts b/src/utils/formUtil/eachComponent.ts index ee9a6b5b..5d4a6810 100644 --- a/src/utils/formUtil/eachComponent.ts +++ b/src/utils/formUtil/eachComponent.ts @@ -14,6 +14,8 @@ import { componentInfo, componentPath, componentFormPath } from './index'; * The current data path of the element. Example: data.user.firstName * @param {Object} parent * The parent object. + * @param {Boolean} runClean + * Whether or not to add properties (e.g. path/parent) to the component object */ export function eachComponent( components: Component[], @@ -21,6 +23,7 @@ export function eachComponent( includeAll?: boolean, path: string = '', parent?: Component, + runClean?: boolean, ) { if (!components) return; components.forEach((component: any) => { @@ -30,7 +33,7 @@ export function eachComponent( const info = componentInfo(component); let noRecurse = false; // Keep track of parent references. - if (parent) { + if (parent && !runClean) { // Ensure we don't create infinite JSON structures. Object.defineProperty(component, 'parent', { enumerable: false, @@ -53,26 +56,44 @@ export function eachComponent( delete component.parent.rows; } - Object.defineProperty(component, 'path', { - enumerable: false, - writable: true, - value: componentPath(component, path), - }); + const compPath = componentPath(component, path); + + if (!runClean) { + Object.defineProperty(component, 'path', { + enumerable: false, + writable: true, + value: compPath, + }); + } if (includeAll || component.tree || !info.layout) { - noRecurse = !!fn(component, component.path, components, parent); + noRecurse = !!fn(component, compPath, components, parent); } if (!noRecurse) { if (info.hasColumns) { component.columns.forEach((column: any) => - eachComponent(column.components, fn, includeAll, path, parent ? component : null), + eachComponent( + column.components, + fn, + includeAll, + path, + parent ? component : null, + runClean, + ), ); } else if (info.hasRows) { component.rows.forEach((row: any) => { if (Array.isArray(row)) { row.forEach((column) => - eachComponent(column.components, fn, includeAll, path, parent ? component : null), + eachComponent( + column.components, + fn, + includeAll, + path, + parent ? component : null, + runClean, + ), ); } }); @@ -81,8 +102,9 @@ export function eachComponent( component.components, fn, includeAll, - componentFormPath(component, path, component.path), + componentFormPath(component, path, compPath), parent ? component : null, + runClean, ); } } diff --git a/src/utils/formUtil/eachComponentAsync.ts b/src/utils/formUtil/eachComponentAsync.ts index a2686397..923a89ef 100644 --- a/src/utils/formUtil/eachComponentAsync.ts +++ b/src/utils/formUtil/eachComponentAsync.ts @@ -7,6 +7,7 @@ export async function eachComponentAsync( includeAll = false, path = '', parent?: any, + runClean?: boolean, ) { if (!components) return; for (let i = 0; i < components.length; i++) { @@ -16,7 +17,7 @@ export async function eachComponentAsync( const component = components[i]; const info = componentInfo(component); // Keep track of parent references. - if (parent) { + if (parent && !runClean) { // Ensure we don't create infinite JSON structures. Object.defineProperty(component, 'parent', { enumerable: false, @@ -38,13 +39,17 @@ export async function eachComponentAsync( delete component.parent.columns; delete component.parent.rows; } - Object.defineProperty(component, 'path', { - enumerable: false, - writable: true, - value: componentPath(component, path), - }); + const compPath = componentPath(component, path); + + if (!runClean) { + Object.defineProperty(component, 'path', { + enumerable: false, + writable: true, + value: compPath, + }); + } if (includeAll || component.tree || !info.layout) { - if (await fn(component, component.path, components, parent)) { + if (await fn(component, compPath, components, parent)) { continue; } } @@ -56,6 +61,7 @@ export async function eachComponentAsync( includeAll, path, parent ? component : null, + runClean, ); } } else if (info.hasRows) { @@ -69,6 +75,7 @@ export async function eachComponentAsync( includeAll, path, parent ? component : null, + runClean, ); } } @@ -78,8 +85,9 @@ export async function eachComponentAsync( component.components, fn, includeAll, - componentFormPath(component, path, component.path), + componentFormPath(component, path, compPath), parent ? component : null, + runClean, ); } } diff --git a/src/utils/formUtil/index.ts b/src/utils/formUtil/index.ts index a15ae3bc..318759d7 100644 --- a/src/utils/formUtil/index.ts +++ b/src/utils/formUtil/index.ts @@ -425,12 +425,15 @@ export function getComponent( eachComponent( components, (component: Component, path: any) => { - if (path === key || component.path === key || (component.input && component.key === key)) { + if (path === key || (component.input && component.key === key)) { result = component; return true; } }, includeAll, + undefined, + undefined, + true, ); return result; } From b490f6eba44649da2a68ff873b7430a6d12a14d8 Mon Sep 17 00:00:00 2001 From: "ICX\\Tatsiana.Hashtold" Date: Wed, 30 Oct 2024 14:24:23 +0300 Subject: [PATCH 2/2] changed parameter name to noComponentChange --- src/utils/formUtil/eachComponent.ts | 14 +++++++------- src/utils/formUtil/eachComponentAsync.ts | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/utils/formUtil/eachComponent.ts b/src/utils/formUtil/eachComponent.ts index 5d4a6810..9f9dc050 100644 --- a/src/utils/formUtil/eachComponent.ts +++ b/src/utils/formUtil/eachComponent.ts @@ -14,7 +14,7 @@ import { componentInfo, componentPath, componentFormPath } from './index'; * The current data path of the element. Example: data.user.firstName * @param {Object} parent * The parent object. - * @param {Boolean} runClean + * @param {Boolean} noComponentChange * Whether or not to add properties (e.g. path/parent) to the component object */ export function eachComponent( @@ -23,7 +23,7 @@ export function eachComponent( includeAll?: boolean, path: string = '', parent?: Component, - runClean?: boolean, + noComponentChange?: boolean, ) { if (!components) return; components.forEach((component: any) => { @@ -33,7 +33,7 @@ export function eachComponent( const info = componentInfo(component); let noRecurse = false; // Keep track of parent references. - if (parent && !runClean) { + if (parent && !noComponentChange) { // Ensure we don't create infinite JSON structures. Object.defineProperty(component, 'parent', { enumerable: false, @@ -58,7 +58,7 @@ export function eachComponent( const compPath = componentPath(component, path); - if (!runClean) { + if (!noComponentChange) { Object.defineProperty(component, 'path', { enumerable: false, writable: true, @@ -79,7 +79,7 @@ export function eachComponent( includeAll, path, parent ? component : null, - runClean, + noComponentChange, ), ); } else if (info.hasRows) { @@ -92,7 +92,7 @@ export function eachComponent( includeAll, path, parent ? component : null, - runClean, + noComponentChange, ), ); } @@ -104,7 +104,7 @@ export function eachComponent( includeAll, componentFormPath(component, path, compPath), parent ? component : null, - runClean, + noComponentChange, ); } } diff --git a/src/utils/formUtil/eachComponentAsync.ts b/src/utils/formUtil/eachComponentAsync.ts index 923a89ef..deee2143 100644 --- a/src/utils/formUtil/eachComponentAsync.ts +++ b/src/utils/formUtil/eachComponentAsync.ts @@ -7,7 +7,7 @@ export async function eachComponentAsync( includeAll = false, path = '', parent?: any, - runClean?: boolean, + noComponentChange?: boolean, ) { if (!components) return; for (let i = 0; i < components.length; i++) { @@ -17,7 +17,7 @@ export async function eachComponentAsync( const component = components[i]; const info = componentInfo(component); // Keep track of parent references. - if (parent && !runClean) { + if (parent && !noComponentChange) { // Ensure we don't create infinite JSON structures. Object.defineProperty(component, 'parent', { enumerable: false, @@ -41,7 +41,7 @@ export async function eachComponentAsync( } const compPath = componentPath(component, path); - if (!runClean) { + if (!noComponentChange) { Object.defineProperty(component, 'path', { enumerable: false, writable: true, @@ -61,7 +61,7 @@ export async function eachComponentAsync( includeAll, path, parent ? component : null, - runClean, + noComponentChange, ); } } else if (info.hasRows) { @@ -75,7 +75,7 @@ export async function eachComponentAsync( includeAll, path, parent ? component : null, - runClean, + noComponentChange, ); } } @@ -87,7 +87,7 @@ export async function eachComponentAsync( includeAll, componentFormPath(component, path, compPath), parent ? component : null, - runClean, + noComponentChange, ); } }