diff --git a/src/process/__tests__/process.test.ts b/src/process/__tests__/process.test.ts index f5212586..439ca4fe 100644 --- a/src/process/__tests__/process.test.ts +++ b/src/process/__tests__/process.test.ts @@ -5106,6 +5106,191 @@ describe('Process Tests', function () { processSync(context); expect((context.scope as ValidationScope).errors).to.have.length(1); }); + + 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: ProcessTargets.submission, + scope: {}, + }; + processSync(context as any); + context.processors = ProcessTargets.evaluator; + 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: ProcessTargets.submission, + scope: {}, + }; + processSync(context as any); + context.processors = ProcessTargets.evaluator; + 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: ProcessTargets.submission, + scope: {}, + }; + processSync(context as any); + context.processors = ProcessTargets.evaluator; + 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 e169b22e..8b4be4f0 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 'modules/jsonlogic'; import { registerEphermalState } from './utils'; import { getComponentAbsolutePath } from './formUtil'; @@ -31,6 +31,9 @@ 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)) { @@ -136,6 +139,7 @@ export function setValueProperty(context: LogicContext, action: LogicActionValue const oldValue = get(data, path); const newValue = evaluate(context, action.value, 'value', (evalContext: any) => { evalContext.value = clone(oldValue); + evalContext.result = context.result; }); if ( !isEqual(oldValue, newValue) && @@ -159,6 +163,7 @@ export function setMergeComponentSchema( 'schema', (evalContext: any) => { evalContext.value = clone(oldValue); + evalContext.result = context.result; }, ); const merged = assign({}, component, schema); @@ -181,10 +186,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)) { @@ -204,5 +212,8 @@ export const applyActions = (context: LogicContext): boolean => { return changed; } }, changed); + // remove result of current logic block from context + unset(context, 'result'); + return actionsResult; }, false); };