From e7f7ac47e499206cf31573387496c872282b4e5d Mon Sep 17 00:00:00 2001 From: brendanbond Date: Wed, 27 Aug 2025 16:46:25 +0000 Subject: [PATCH] $'syncing commit from monorepo. PR: 418, Title: FIO-10588: fixed an issue where logic with the result variable does not work properly on server side' --- src/process/__tests__/process.test.ts | 202 +++++++++++++++++++++++- src/types/process/logic/LogicContext.ts | 1 + src/utils/logic.ts | 18 ++- 3 files changed, 217 insertions(+), 4 deletions(-) diff --git a/src/process/__tests__/process.test.ts b/src/process/__tests__/process.test.ts index 44f7137f..6716df4b 100644 --- a/src/process/__tests__/process.test.ts +++ b/src/process/__tests__/process.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import assert from 'node:assert'; -import type { ContainerComponent, ValidationScope } from 'types'; +import type { ContainerComponent, ProcessContext, ValidationScope } from 'types'; import { getComponent } from 'utils/formUtil'; import { process, processSync, Processors } from '../index'; import { fastCloneDeep } from 'utils'; @@ -16,6 +16,7 @@ import { skipValidWithHiddenParentComp, requiredFieldInsideEditGrid, formWithDefaultValues, + requiredValidationDisabledByLogic, } from './fixtures'; import _ from 'lodash'; @@ -6027,6 +6028,26 @@ describe('Process Tests', function () { expect((scope as ValidationScope).errors).to.have.length(1); }); + it('Should not trigger validation errors for components that remove required property by logic', async function () { + const form = requiredValidationDisabledByLogic; + const submission = { + data: { + b: '', + skipRequired: true, + }, + }; + const context = { + form, + submission: submission, + data: submission.data, + components: form.components, + processors: Processors, + scope: {} as ValidationScope, + } as ProcessContext; + processSync(context); + expect(context.scope.errors.length).to.equal(0); + }); + describe('Required component validation in nested form in DataGrid/EditGrid', function () { const nestedForm = { key: 'form', @@ -7126,5 +7147,184 @@ describe('Process Tests', function () { assert(!context.data.hasOwnProperty('fname')); assert(!context.data.hasOwnProperty('lname')); }); + + it('Should not return the error for required component with logic where result var is used', async function () { + const form = { + components: [ + { + label: 'Registration number required', + applyMaskOn: 'change', + tableView: true, + case: 'uppercase', + validate: { + required: true, + maxLength: 50, + }, + validateWhenHidden: false, + key: 'plateNumber2', + logic: [ + { + name: 'keep letter and number', + trigger: { + type: 'javascript', + javascript: 'result=row[component.key];', + }, + actions: [ + { + name: 'set', + type: 'value', + value: "value=result.replace(/[^a-zA-Z0-9]/g, '');\n", + }, + ], + }, + ], + type: 'textfield', + input: true, + keyModified: true, + }, + { + label: 'Submit', + showValidations: false, + tableView: false, + key: 'submit', + type: 'button', + input: true, + }, + ], + }; + const submission = { + data: { plateNumber2: 'TEST', submit: true }, + }; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: Processors, + scope: {}, + }; + processSync(context as any); + assert.equal(context.data.plateNumber2, 'TEST'); + assert.equal((context.scope as any).errors.length, 0); + }); + + it('Should return the value formatted by logic where result var is used', async function () { + const form = { + components: [ + { + label: 'Registration number required', + applyMaskOn: 'change', + tableView: true, + case: 'uppercase', + validate: { + required: true, + maxLength: 50, + }, + validateWhenHidden: false, + key: 'plateNumber2', + logic: [ + { + name: 'keep letter and number', + trigger: { + type: 'javascript', + javascript: 'result=row[component.key];', + }, + actions: [ + { + name: 'set', + type: 'value', + value: "value=result.replace(/[^a-zA-Z0-9]/g, '');\n", + }, + ], + }, + ], + type: 'textfield', + input: true, + keyModified: true, + }, + { + label: 'Submit', + showValidations: false, + tableView: false, + key: 'submit', + type: 'button', + input: true, + }, + ], + }; + const submission = { + data: { plateNumber2: 'TEST 123 TEST', submit: true }, + }; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: Processors, + scope: {}, + }; + processSync(context as any); + assert.equal(context.data.plateNumber2, 'TEST123TEST'); + assert.equal((context.scope as any).errors.length, 0); + }); + + it('Should return the error for required component with logic where result var is used', async function () { + const form = { + components: [ + { + label: 'Registration number required', + applyMaskOn: 'change', + tableView: true, + case: 'uppercase', + validate: { + required: true, + maxLength: 50, + }, + validateWhenHidden: false, + key: 'plateNumber2', + logic: [ + { + name: 'keep letter and number', + trigger: { + type: 'javascript', + javascript: 'result=row[component.key];', + }, + actions: [ + { + name: 'set', + type: 'value', + value: "value=result.replace(/[^a-zA-Z0-9]/g, '');\n", + }, + ], + }, + ], + type: 'textfield', + input: true, + keyModified: true, + }, + { + label: 'Submit', + showValidations: false, + tableView: false, + key: 'submit', + type: 'button', + input: true, + }, + ], + }; + const submission = { + data: { plateNumber2: '', submit: true }, + }; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: Processors, + scope: {}, + }; + processSync(context as any); + assert.equal((context.scope as any).errors.length, 1); + }); }); }); diff --git a/src/types/process/logic/LogicContext.ts b/src/types/process/logic/LogicContext.ts index c242a866..7bfc50fc 100644 --- a/src/types/process/logic/LogicContext.ts +++ b/src/types/process/logic/LogicContext.ts @@ -2,4 +2,5 @@ import { ProcessorContext } from '../ProcessorContext'; import { LogicScope } from './LogicScope'; export type LogicContext = ProcessorContext & { populated?: any; + result?: any; }; diff --git a/src/utils/logic.ts b/src/utils/logic.ts index 4f6d75e9..45b72109 100644 --- a/src/utils/logic.ts +++ b/src/utils/logic.ts @@ -15,7 +15,7 @@ import { LogicActionPropertyString, LogicActionValue, } from 'types/AdvancedLogic'; -import { get, set, clone, isEqual, assign } from 'lodash'; +import { get, set, clone, isEqual, assign, unset } from 'lodash'; import { evaluate, interpolate } from 'utils/utils'; import { setComponentScope } from 'utils/formUtil'; @@ -30,6 +30,10 @@ export const hasLogic = (context: LogicContext): boolean => { export const checkTrigger = (context: LogicContext, trigger: any): boolean => { let shouldTrigger: boolean | null = false; + if (!trigger) { + return false; + } + switch (trigger.type) { case 'simple': if (isLegacyConditional(trigger.simple)) { @@ -139,6 +143,7 @@ export function setValueProperty(context: LogicContext, action: LogicActionValue const oldValue = get(data, path); const newValue = evaluate(action.value, context, 'value', false, (evalContext: any) => { evalContext.value = clone(oldValue); + evalContext.result = context.result; }); if ( !isEqual(oldValue, newValue) && @@ -167,6 +172,7 @@ export function setMergeComponentSchema( false, (evalContext: any) => { evalContext.value = clone(oldValue); + evalContext.result = context.result; }, ); const merged = assign({}, component, schema); @@ -189,10 +195,13 @@ export const applyActions = (context: LogicContext): boolean => { } return logic.reduce((changed, logicItem) => { const { actions, trigger } = logicItem; - if (!trigger || !actions || !actions.length || !checkTrigger(context, trigger)) { + const result = checkTrigger(context, trigger); + if (!trigger || !actions || !actions.length || !result) { return changed; } - return actions.reduce((changed, action) => { + // remove trigger result of current logic block to the evaluation context + context.result = result; + const actionsResult = actions.reduce((changed, action) => { switch (action.type) { case 'property': if (setActionProperty(context, action)) { @@ -212,5 +221,8 @@ export const applyActions = (context: LogicContext): boolean => { return changed; } }, changed); + // remove result of current logic block from context + unset(context, 'result'); + return actionsResult; }, false); };