diff --git a/src/process/__tests__/process.test.ts b/src/process/__tests__/process.test.ts index 8fa78c30..0849b876 100644 --- a/src/process/__tests__/process.test.ts +++ b/src/process/__tests__/process.test.ts @@ -3,7 +3,9 @@ import assert from 'node:assert' import type { ContainerComponent, ProcessContext, ProcessorScope, ValidationScope } from 'types'; import { getComponent } from 'utils/formUtil'; import { process, processSync, ProcessTargets } from '../index'; +import { fastCloneDeep } from 'utils'; import { addressComponentWithOtherCondComponents, addressComponentWithOtherCondComponents2, clearOnHideWithCustomCondition, clearOnHideWithHiddenParent, forDataGridRequired, skipValidForConditionallyHiddenComp, skipValidForLogicallyHiddenComp, skipValidWithHiddenParentComp } from './fixtures' + /* describe('Process Tests', () => { it('Should perform the processes using the processReduced method.', async () => { @@ -3509,7 +3511,153 @@ describe('Process Tests', () => { assert.equal(context.scope.errors.length, 0); }); - it('Should not return error for the form with conditionals based on the Day component', () => { +it('Should not unset values for conditionally visible fields with different formats of condtion based on selectboxes value', async () => { + const form = { + _id: '66ffa92ac25689df8702f283', + title: 'cond NEW', + name: 'condnew', + path: 'condnew', + type: 'form', + display: 'form', + owner: '637b2e6b48c1227e60b1f910', + components: [ + { + label: 'Container', + tableView: false, + validateWhenHidden: false, + key: 'container', + type: 'container', + input: true, + components: [ + { + label: 'Select Boxes', + optionsLabelPosition: 'right', + tableView: false, + values: [ + { + label: 'a', + value: 'a', + shortcut: '', + }, + { + label: 'b', + value: 'b', + shortcut: '', + }, + { + label: 'c', + value: 'c', + 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: [ + { + component: 'container.selectBoxes', + operator: 'isEqual', + value: 'a', + }, + ], + }, + type: 'textfield', + input: true, + }, + { + label: 'Text Field new wrong format', + applyMaskOn: 'change', + tableView: true, + validateWhenHidden: false, + key: 'textFieldNewWrong', + conditional: { + show: true, + conjunction: 'all', + conditions: [ + { + component: 'container.selectBoxes.a', + operator: 'isEqual', + value: 'true', + }, + ], + }, + type: 'textfield', + input: true, + }, + { + label: 'Text Field Old Format', + applyMaskOn: 'change', + tableView: true, + validateWhenHidden: false, + key: 'textFieldOldFormat', + conditional: { + show: true, + eq: 'true', + when: 'container.selectBoxes.a', + }, + type: 'textfield', + input: true, + }, + { + label: 'Submit', + showValidations: false, + tableView: false, + key: 'submit', + type: 'button', + input: true, + saveOnEnter: false, + }, + ] + }; + const data= { + textField: 'correct condition', + textFieldNewWrong: 'new condition with wrong format', + textFieldOldFormat: 'legacy condtion', + container: { selectBoxes: { a: true, b: false, c: false } }, + submit: true, + } + + const submission = { + data: fastCloneDeep(data), + _id: '66f68c17481ea2ffbf5bb310', + state: 'submitted', + }; + + const errors: any = []; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.submission, + scope: { errors }, + config: { + server: true, + }, + }; + processSync(context); + submission.data = context.data; + context.processors = ProcessTargets.evaluator; + processSync(context); + (context.scope as any).conditionals.forEach((v: any) => assert.equal(v.conditionallyHidden, false)) + assert.deepEqual(context.data, data); + }); + + it('Should not return error for the form with conditionals based on the Day component', () => { const form = { _id: '66ffe59b598a729e707869bf', title: '9143 condition day', @@ -3609,8 +3757,7 @@ describe('Process Tests', () => { expect(context.scope.conditionals).to.have.length(1); expect(context.scope.conditionals?.[0].conditionallyHidden).to.be.false; assert.equal(context.scope.errors.length, 0); - }) - + }); describe('Required component validation in nested form in DataGrid/EditGrid', () => { const nestedForm = { diff --git a/src/utils/conditions.ts b/src/utils/conditions.ts index 385abecf..6c11d04b 100644 --- a/src/utils/conditions.ts +++ b/src/utils/conditions.ts @@ -1,7 +1,7 @@ import { ConditionsContext, JSONConditional, LegacyConditional, SimpleConditional } from "types"; import { EvaluatorFn, evaluate, JSONLogicEvaluator } from 'modules/jsonlogic'; -import { getComponent, getComponentActualValue } from "./formUtil"; -import { has, isObject, map, every, some, find, filter } from 'lodash'; +import { flattenComponents, getComponent, getComponentActualValue } from "./formUtil"; +import { has, isObject, map, every, some, find, filter, isBoolean, split } from 'lodash'; import ConditionOperators from './operators'; export const isJSONConditional = (conditional: any): conditional is JSONConditional => { @@ -88,6 +88,22 @@ export function checkJsonConditional(conditional: JSONConditional, context: Cond return JSONLogicEvaluator.evaluate(conditional.json, evalContextValue); } +/** + * Checks if condition can potentially have a value path instead of component path. + * @param condition + * @returns {boolean} + */ +function isConditionPotentiallyBasedOnValuePath(condition: any = {}) { + let comparedValue; + try { + comparedValue = JSON.parse(condition.value); + } + catch(e) { + comparedValue = condition.value; + } + return isBoolean(comparedValue) && (condition.component || '').split('.').length > 1 && condition.operator === 'isEqual'; +} + /** * Checks the simple conditionals. * @param conditional @@ -95,7 +111,7 @@ export function checkJsonConditional(conditional: JSONConditional, context: Cond * @returns */ export function checkSimpleConditional(conditional: SimpleConditional, context: ConditionsContext): boolean | null { - const { component, data, row, instance, form, components = [] } = context; + const { component, data, row, instance, form } = context; if (!conditional || !isSimpleConditional(conditional)) { return null; } @@ -105,13 +121,38 @@ export function checkSimpleConditional(conditional: SimpleConditional, context: } const conditionsResult = filter(map(conditions, (cond) => { - const { value: comparedValue, operator, component: conditionComponentPath } = cond; + let { value: comparedValue, operator, component: conditionComponentPath } = cond; if (!conditionComponentPath) { // Ignore conditions if there is no component path. return null; } + const formComponents = form?.components || []; + let conditionComponent = getComponent(formComponents, conditionComponentPath, true); + // If condition componenet is not found, check if conditionComponentPath is value path. + // Need to handle condtions like: + // { + // "component": "selectBoxes.a", + // "operator": "isEqual", + // "value": "true" + // } + if (!conditionComponent && isConditionPotentiallyBasedOnValuePath(cond) && formComponents.length) { + const flattenedComponents = flattenComponents(formComponents, true); + const pathParts = split(conditionComponentPath, '.'); + const valuePathParts = []; + + while (!conditionComponent && pathParts.length) { + conditionComponent = flattenedComponents[`${pathParts.join('.')}`]; + if (!conditionComponent) { + valuePathParts.unshift(pathParts.pop()); + } + } + if (conditionComponent && conditionComponent.type === 'selectboxes' && valuePathParts.length) { + console.warn('Condition based on selectboxes has wrong format. Resave the form in the form builder to fix it.'); + conditionComponentPath = pathParts.join('.'); + comparedValue = valuePathParts.join('.'); + } + } - const conditionComponent = getComponent(form?.components || components, conditionComponentPath, true); const value = conditionComponent ? getComponentActualValue(conditionComponent, conditionComponentPath, data, row) : null; const ConditionOperator = ConditionOperators[operator];