From 9aaad77a81d125c9b94ca2fc263e9e05d75be459 Mon Sep 17 00:00:00 2001 From: "EP\\qang2" Date: Thu, 16 Nov 2023 17:37:50 +0700 Subject: [PATCH 1/2] Refactor conditions function in js sdk Fixes: AFORM-3701 --- .../ConditionFunctions.ts | 72 +++++++++ .../formDependConditions.ts | 96 +++--------- .../forms-sdk/src/helpers/dependencyHelper.ts | 3 +- .../src/models/enums/ConditionFunctionType.ts | 1 - .../formDependencies.test.ts | 142 +++++++----------- 5 files changed, 151 insertions(+), 163 deletions(-) create mode 100644 src/@optimizely/forms-sdk/src/form-depend-conditions/ConditionFunctions.ts diff --git a/src/@optimizely/forms-sdk/src/form-depend-conditions/ConditionFunctions.ts b/src/@optimizely/forms-sdk/src/form-depend-conditions/ConditionFunctions.ts new file mode 100644 index 0000000..2cf0e37 --- /dev/null +++ b/src/@optimizely/forms-sdk/src/form-depend-conditions/ConditionFunctions.ts @@ -0,0 +1,72 @@ +import { isNull, isNullOrEmpty } from "../helpers"; +import { getConcatString } from "../helpers/dependencyHelper"; +import { ConditionFunctionType } from "../models"; + +export type ConditionFunctionRecord = Record boolean>; + +export const ConditionFunctions: ConditionFunctionRecord = { + [ConditionFunctionType.Contains]: Contains, + [ConditionFunctionType.NotContains]: NotContains, + [ConditionFunctionType.Equals]: Equals, + [ConditionFunctionType.NotEquals]: NotEquals, + [ConditionFunctionType.MatchRegularExpression]: MatchRegularExpression, +}; +/** + * Compare whether user input data equals depend value or not. + * @param actualValue + * @param dependencyFieldValue + * @returns + */ +function Equals(actualValue: Object, dependencyFieldValue: string): boolean { + const _actualValue = !actualValue ? "" : getConcatString(actualValue, ","); + dependencyFieldValue = !dependencyFieldValue ? "" : dependencyFieldValue.toUpperCase(); + return _actualValue === dependencyFieldValue; +} +/** + * Compare whether user input data does NOT equal depend value or not. + * @param actualValue + * @param dependencyFieldValue + * @returns +*/ +function NotEquals(actualValue: Object, dependencyFieldValue: string): boolean { + const _actualValue = !actualValue ? "" : getConcatString(actualValue, ","); + dependencyFieldValue = !dependencyFieldValue ? "" : dependencyFieldValue.toUpperCase(); + return _actualValue !== dependencyFieldValue; +} +/** + * Compare whether user input data contains depend value or not. + * @param actualValue + * @param dependencyFieldValue + * @returns + */ +function Contains(actualValue: Object, dependencyFieldValue: string): boolean { + const _actualValue = isNull(actualValue) ? "" : getConcatString(actualValue, ",").toUpperCase(); + dependencyFieldValue = !dependencyFieldValue ? "" : dependencyFieldValue.toUpperCase(); + return _actualValue.indexOf(dependencyFieldValue) >= 0; +} +/** + * Compare whether user input data does NOT contain depend value or not. + * @param actualValue + * @param dependencyFieldValue + * @returns + */ +function NotContains(actualValue: Object, dependencyFieldValue: string): boolean { + const _actualValue = !actualValue ? "" : getConcatString(actualValue, ","); + const actualValueNull = isNullOrEmpty(_actualValue) + const dependencyFieldValueNull = isNullOrEmpty(dependencyFieldValue) + return (!actualValueNull && dependencyFieldValueNull) || + (actualValueNull && !dependencyFieldValueNull) || + (!actualValueNull && !dependencyFieldValueNull && _actualValue.toUpperCase().indexOf(dependencyFieldValue.toUpperCase()) < 0); +} +/** + * Compare user input with a pattern. Return true if actualValue matchs patternOfExpected + * @param actualValue + * @param patternOfExpected + * @returns + */ +function MatchRegularExpression(actualValue: Object, patternOfExpected: string): boolean { + var regex = new RegExp(patternOfExpected, "igm"); + const _actualValue = !actualValue ? "" : getConcatString(actualValue, ","); + return isNullOrEmpty(patternOfExpected) || (!isNullOrEmpty(patternOfExpected) && regex.test(_actualValue)); +} + diff --git a/src/@optimizely/forms-sdk/src/form-depend-conditions/formDependConditions.ts b/src/@optimizely/forms-sdk/src/form-depend-conditions/formDependConditions.ts index a4ec652..401041c 100644 --- a/src/@optimizely/forms-sdk/src/form-depend-conditions/formDependConditions.ts +++ b/src/@optimizely/forms-sdk/src/form-depend-conditions/formDependConditions.ts @@ -1,93 +1,47 @@ -import { equals, isNull, isNullOrEmpty } from "../helpers"; -import { getConcatString } from "../helpers/dependencyHelper"; -import { ConditionCombinationType, ConditionFunctionType, ConditionProperties, FormElementBase, FormSubmission } from "../models"; - +import { equals, isNull } from "../helpers"; +import { ConditionCombinationType, ConditionProperties, FormElementBase, FormSubmission } from "../models"; +import { ConditionFunctions } from "./ConditionFunctions"; +/** + * Class to check if a element conditions is met + */ export class FormDependConditions { readonly _element: FormElementBase; constructor(element: FormElementBase) { this._element = element; } + /** + * Main function to check if a element conditions is met + * @param formSubmissions + * @returns + */ checkConditions = (formSubmissions: FormSubmission[]): boolean => { if (!isNull(formSubmissions)) { const conditionProps = (this._element.properties as unknown) as ConditionProperties; if (isNull(conditionProps?.conditions)) { - return false; + // no condition to check, return true + return true; } - let conditionArr = conditionProps.conditions.map(condition => { + for (let i = 0; i < conditionProps.conditions.length; i++) { + const condition = conditionProps.conditions[i] const fieldValue = formSubmissions.filter(s => equals(s.elementKey, condition.field))[0]?.value as string if (!isNull(fieldValue)) { - switch (condition.operator) { - case ConditionFunctionType.Contains: - return this.Contains(fieldValue, condition.fieldValue) - case ConditionFunctionType.NotContains: - return this.NotContains(fieldValue, condition.fieldValue) - case ConditionFunctionType.Equals: - return this.Equals(fieldValue, condition.fieldValue) - case ConditionFunctionType.NotEquals: - return this.NotEquals(fieldValue, condition.fieldValue) - case ConditionFunctionType.MatchRegularExpression: - return this.MatchRegularExpression(fieldValue, condition.fieldValue) + const conditionFunction = ConditionFunctions[condition.operator]; + if (!isNull(conditionFunction)){ + var checkResult = conditionFunction(fieldValue, condition.fieldValue) + if (conditionProps.conditionCombination === ConditionCombinationType.Any && checkResult) { + return true + } + if (conditionProps.conditionCombination !== ConditionCombinationType.Any && !checkResult) { + return false + } } } - return false - }); - for (let i = 0; i < conditionArr.length; i++) { - const result = conditionArr[i] - if (conditionProps.conditionCombination === ConditionCombinationType.Any && result) { - return true - } - if (conditionProps.conditionCombination === ConditionCombinationType.All && !result) { - return false - } } // When reach here, there are two cases // 1 : All conditions are statisfied and ConditionCombination === ConditionCombinations.All // 2 : No condition is statisfied and ConditionCombination === ConditionCombinations.Any - return conditionProps.conditionCombination === ConditionCombinationType.All; + return !(conditionProps.conditionCombination === ConditionCombinationType.Any); } return false } - /** - * Compare whether user input data equals depend value or not. - */ - Equals(actualValue: Object, dependencyFieldValue: string): boolean { - const _actualValue = !actualValue ? "" : getConcatString(actualValue, ","); - dependencyFieldValue = !dependencyFieldValue ? "" : dependencyFieldValue.toUpperCase(); - return _actualValue === dependencyFieldValue; - } - /** - * Compare whether user input data does NOT equal depend value or not. - */ - NotEquals(actualValue: Object, dependencyFieldValue: string): boolean { - const _actualValue = !actualValue ? "" : getConcatString(actualValue, ","); - dependencyFieldValue = !dependencyFieldValue ? "" : dependencyFieldValue.toUpperCase(); - return _actualValue !== dependencyFieldValue; - } - /** - * Compare whether user input data contains depend value or not. - */ - Contains(actualValue: Object, dependencyFieldValue: string): boolean { - const _actualValue = isNull(actualValue) ? "" : getConcatString(actualValue, ",").toUpperCase(); - dependencyFieldValue = !dependencyFieldValue ? "" : dependencyFieldValue.toUpperCase(); - return _actualValue.indexOf(dependencyFieldValue) >= 0; - } - /** - * Compare whether user input data does NOT contain depend value or not. - */ - NotContains(actualValue: Object, dependencyFieldValue: string): boolean { - const _actualValue = !actualValue ? "" : getConcatString(actualValue, ","); - const actualValueNull = isNullOrEmpty(_actualValue) - const dependencyFieldValueNull = isNullOrEmpty(dependencyFieldValue) - return (!actualValueNull && dependencyFieldValueNull) || - (actualValueNull && !dependencyFieldValueNull) || - (!actualValueNull && !dependencyFieldValueNull && _actualValue.toUpperCase().indexOf(dependencyFieldValue.toUpperCase()) < 0); - } - /** - * Compare user input with a pattern. Return true if actualValue matchs patternOfExpected - */ - MatchRegularExpression(actualValue: Object, patternOfExpected: string): boolean { - var regex = new RegExp(patternOfExpected, "igm"); - const _actualValue = !actualValue ? "" : getConcatString(actualValue, ","); - return isNullOrEmpty(patternOfExpected) || (!isNullOrEmpty(patternOfExpected) && regex.test(_actualValue)); - } } \ No newline at end of file diff --git a/src/@optimizely/forms-sdk/src/helpers/dependencyHelper.ts b/src/@optimizely/forms-sdk/src/helpers/dependencyHelper.ts index 5d29bff..85372b7 100644 --- a/src/@optimizely/forms-sdk/src/helpers/dependencyHelper.ts +++ b/src/@optimizely/forms-sdk/src/helpers/dependencyHelper.ts @@ -1,3 +1,4 @@ export function getConcatString(srcObject: any, seperator: string): string { - return (srcObject instanceof Array) ? srcObject.join(seperator) : srcObject as string; + let str = (srcObject instanceof Array) ? srcObject.join(seperator) : srcObject as string; + return str.toLocaleUpperCase(); } \ No newline at end of file diff --git a/src/@optimizely/forms-sdk/src/models/enums/ConditionFunctionType.ts b/src/@optimizely/forms-sdk/src/models/enums/ConditionFunctionType.ts index de95f16..db8f5eb 100644 --- a/src/@optimizely/forms-sdk/src/models/enums/ConditionFunctionType.ts +++ b/src/@optimizely/forms-sdk/src/models/enums/ConditionFunctionType.ts @@ -1,6 +1,5 @@ export enum ConditionFunctionType { MatchRegularExpression = "MatchRegularExpression", - NotApplicable = "NotApplicable", Contains = "Contains", NotContains = "NotContains", Equals = "Equals", diff --git a/src/@optimizely/forms-sdk/test/form-depend-conditions/formDependencies.test.ts b/src/@optimizely/forms-sdk/test/form-depend-conditions/formDependencies.test.ts index ea88083..88ff64c 100644 --- a/src/@optimizely/forms-sdk/test/form-depend-conditions/formDependencies.test.ts +++ b/src/@optimizely/forms-sdk/test/form-depend-conditions/formDependencies.test.ts @@ -9,19 +9,27 @@ describe("Test FormDependConditions class", () => { formSubmissions = [ { elementKey: "1490ca1deeb744f1aa7c33db0aefe5a8", - value: "11" + value: "aaaa" }, { elementKey: "2490ca1deeb744f1aa7c33db0aefe5a8", - value: "22" + value: "bbbb" }, { elementKey: "3490ca1deeb744f1aa7c33db0aefe5a8", - value: "33" + value: "cccc" }, { elementKey: "4490ca1deeb744f1aa7c33db0aefe5a8", - value: "44" + value: "dddd" + }, + { + elementKey: "5490ca1deeb744f1aa7c33db0aefe5a8", + value: "eeee" + }, + { + elementKey: "6490ca1deeb744f1aa7c33db0aefe5a8", + value: "ffff" } ] }); @@ -36,7 +44,7 @@ describe("Test FormDependConditions class", () => { { "field": "1490ca1deeb744f1aa7c33db0aefe5a8", "operator": "Contains", - "fieldValue": "1" + "fieldValue": "a" } ] } @@ -72,7 +80,7 @@ describe("Test FormDependConditions class", () => { { "field": "1490ca1deeb744f1aa7c33db0aefe5a8", "operator": "NotContains", - "fieldValue": "1" + "fieldValue": "a" } ] } @@ -108,7 +116,7 @@ describe("Test FormDependConditions class", () => { { "field": "1490ca1deeb744f1aa7c33db0aefe5a8", "operator": "Equals", - "fieldValue": "11" + "fieldValue": "aaaa" } ] } @@ -144,7 +152,7 @@ describe("Test FormDependConditions class", () => { { "field": "1490ca1deeb744f1aa7c33db0aefe5a8", "operator": "NotEquals", - "fieldValue": "11" + "fieldValue": "aaaa" } ] } @@ -180,7 +188,7 @@ describe("Test FormDependConditions class", () => { { "field": "1490ca1deeb744f1aa7c33db0aefe5a8", "operator": "MatchRegularExpression", - "fieldValue": "1+" + "fieldValue": "a+" } ] } @@ -206,42 +214,6 @@ describe("Test FormDependConditions class", () => { expect(formDependConditions.checkConditions(formSubmissions)).toBeFalsy(); }); }); - describe("NotApplicable", () => { - test("When conditionCombination is Any, should return False", () => { - element = { - properties: { - satisfiedAction: "show", - conditionCombination: "Any", - conditions: [ - { - "field": "1490ca1deeb744f1aa7c33db0aefe5a8", - "operator": "NotApplicable", - "fieldValue": "" - } - ] - } - } as InputElementBase; - formDependConditions = new FormDependConditions(element) - expect(formDependConditions.checkConditions(formSubmissions)).toBeFalsy(); - }); - test("When conditionCombination is All, should return False", () => { - element = { - properties: { - satisfiedAction: "show", - conditionCombination: "All", - conditions: [ - { - "field": "1490ca1deeb744f1aa7c33db0aefe5a8", - "operator": "NotApplicable", - "fieldValue": "" - } - ] - } - } as InputElementBase; - formDependConditions = new FormDependConditions(element) - expect(formDependConditions.checkConditions(formSubmissions)).toBeFalsy(); - }); - }); }); describe("Multiple conditions", () => { describe("All conditionCombination", () => { @@ -254,28 +226,28 @@ describe("Test FormDependConditions class", () => { { "field": "1490ca1deeb744f1aa7c33db0aefe5a8", "operator": "Contains", - "fieldValue": "1" + "fieldValue": "a" }, { "field": "2490ca1deeb744f1aa7c33db0aefe5a8", "operator": "NotEqual", - "fieldValue": "3" + "fieldValue": "vvvvv" }, { "field": "3490ca1deeb744f1aa7c33db0aefe5a8", "operator": "Equals", - "fieldValue": "33" + "fieldValue": "cccc" }, { "field": "4490ca1deeb744f1aa7c33db0aefe5a8", "operator": "Contains", - "fieldValue": "4" + "fieldValue": "ddd" } ] } } as InputElementBase; formDependConditions = new FormDependConditions(element) - expect(formDependConditions.checkConditions(formSubmissions)).toBeFalsy(); + expect(formDependConditions.checkConditions(formSubmissions)).toBeTruthy(); }); test("When a condition is not met, should return False", () => { element = { @@ -376,86 +348,76 @@ describe("Test FormDependConditions class", () => { expect(formDependConditions.checkConditions(formSubmissions)).toBeFalsy(); }); }); - + }); describe("Nullable variables", () => { - test("When field of condition is null or not found in formSubmissions, should return False", () => { + test("When properties is null, should return True", () => { element = { properties: { - satisfiedAction: "show", - conditionCombination: "Any", - conditions: [ - { - "field": "1", - "operator": "Contains", - "fieldValue": "1" - } - ] } } as InputElementBase; - - let element2 = { + formDependConditions = new FormDependConditions(element) + expect(formDependConditions.checkConditions(formSubmissions)).toBeTruthy(); + }); + test("When field of condition only consit of NotApplicable or unknown operator and conditionCombination is Any, should return False", () => { + element = { properties: { satisfiedAction: "show", conditionCombination: "Any", conditions: [ { - "operator": "Contains", + "field": "1490ca1deeb744f1aa7c33db0aefe5a8", + "operator": "NotApplicable", + "fieldValue": "1" + }, + { + "field": "1490ca1deeb744f1aa7c33db0aefe5a8", + "operator": "NotApplicable", "fieldValue": "1" } ] } } as InputElementBase; - formDependConditions = new FormDependConditions(element) - expect(formDependConditions.checkConditions(formSubmissions)).toBeFalsy(); - - const formDependConditions2 = new FormDependConditions(element2) + const formDependConditions2 = new FormDependConditions(element) expect(formDependConditions2.checkConditions(formSubmissions)).toBeFalsy(); }); - test("When operator of condition is null or not found in formSubmissions, should return False", () => { + test("When field of condition only consit of NotApplicable or unknown operator and conditionCombination is All, should return True", () => { element = { properties: { satisfiedAction: "show", - conditionCombination: "Any", + conditionCombination: "All", conditions: [ { "field": "1490ca1deeb744f1aa7c33db0aefe5a8", - "operator": "DontExistOperator", + "operator": "NotApplicable", "fieldValue": "1" - } - ] - } - } as InputElementBase; - - let element2 = { - properties: { - satisfiedAction: "show", - conditionCombination: "Any", - conditions: [ + }, { "field": "1490ca1deeb744f1aa7c33db0aefe5a8", + "operator": "NotFoundOperator", "fieldValue": "1" } ] } } as InputElementBase; - formDependConditions = new FormDependConditions(element) - expect(formDependConditions.checkConditions(formSubmissions)).toBeFalsy(); - - const formDependConditions2 = new FormDependConditions(element2) - expect(formDependConditions2.checkConditions(formSubmissions)).toBeFalsy(); + const formDependConditions2 = new FormDependConditions(element) + expect(formDependConditions2.checkConditions(formSubmissions)).toBeTruthy(); }); - test("When conditionCombination is not All or Any, should return False", () => { + test("When conditionCombination is not All or Any, should return True", () => { element = { properties: { satisfiedAction: "show", - conditionCombination: "NotFoundConditionCombination", conditions: [ { "field": "1490ca1deeb744f1aa7c33db0aefe5a8", - "operator": "Contains", + "operator": "NotApplicable", + "fieldValue": "1" + }, + { + "field": "1490ca1deeb744f1aa7c33db0aefe5a8", + "operator": "NotFoundOperator", "fieldValue": "1" } ] @@ -463,7 +425,7 @@ describe("Test FormDependConditions class", () => { } as InputElementBase; formDependConditions = new FormDependConditions(element) - expect(formDependConditions.checkConditions(formSubmissions)).toBeFalsy(); + expect(formDependConditions.checkConditions(formSubmissions)).toBeTruthy(); }); }); }); From 6bbb2d1b97720f29d5f7ab5fce776b1dfa93e288 Mon Sep 17 00:00:00 2001 From: "EP\\qang2" Date: Fri, 17 Nov 2023 14:11:58 +0700 Subject: [PATCH 2/2] Move toLocaleUpperCase() to condition functions instead --- .../ConditionFunctions.ts | 19 +++++++++---------- .../forms-sdk/src/helpers/dependencyHelper.ts | 3 +-- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/@optimizely/forms-sdk/src/form-depend-conditions/ConditionFunctions.ts b/src/@optimizely/forms-sdk/src/form-depend-conditions/ConditionFunctions.ts index 2cf0e37..e76bf0b 100644 --- a/src/@optimizely/forms-sdk/src/form-depend-conditions/ConditionFunctions.ts +++ b/src/@optimizely/forms-sdk/src/form-depend-conditions/ConditionFunctions.ts @@ -18,8 +18,8 @@ export const ConditionFunctions: ConditionFunctionRecord = { * @returns */ function Equals(actualValue: Object, dependencyFieldValue: string): boolean { - const _actualValue = !actualValue ? "" : getConcatString(actualValue, ","); - dependencyFieldValue = !dependencyFieldValue ? "" : dependencyFieldValue.toUpperCase(); + const _actualValue = !actualValue ? "" : getConcatString(actualValue, ",").toLocaleUpperCase(); + dependencyFieldValue = !dependencyFieldValue ? "" : dependencyFieldValue.toLocaleUpperCase(); return _actualValue === dependencyFieldValue; } /** @@ -29,8 +29,8 @@ function Equals(actualValue: Object, dependencyFieldValue: string): boolean { * @returns */ function NotEquals(actualValue: Object, dependencyFieldValue: string): boolean { - const _actualValue = !actualValue ? "" : getConcatString(actualValue, ","); - dependencyFieldValue = !dependencyFieldValue ? "" : dependencyFieldValue.toUpperCase(); + const _actualValue = !actualValue ? "" : getConcatString(actualValue, ",").toLocaleUpperCase(); + dependencyFieldValue = !dependencyFieldValue ? "" : dependencyFieldValue.toLocaleUpperCase(); return _actualValue !== dependencyFieldValue; } /** @@ -40,8 +40,8 @@ function NotEquals(actualValue: Object, dependencyFieldValue: string): boolean { * @returns */ function Contains(actualValue: Object, dependencyFieldValue: string): boolean { - const _actualValue = isNull(actualValue) ? "" : getConcatString(actualValue, ",").toUpperCase(); - dependencyFieldValue = !dependencyFieldValue ? "" : dependencyFieldValue.toUpperCase(); + const _actualValue = isNull(actualValue) ? "" : getConcatString(actualValue, ",").toLocaleUpperCase(); + dependencyFieldValue = !dependencyFieldValue ? "" : dependencyFieldValue.toLocaleUpperCase(); return _actualValue.indexOf(dependencyFieldValue) >= 0; } /** @@ -51,12 +51,12 @@ function Contains(actualValue: Object, dependencyFieldValue: string): boolean { * @returns */ function NotContains(actualValue: Object, dependencyFieldValue: string): boolean { - const _actualValue = !actualValue ? "" : getConcatString(actualValue, ","); + const _actualValue = !actualValue ? "" : getConcatString(actualValue, ",").toLocaleUpperCase(); const actualValueNull = isNullOrEmpty(_actualValue) const dependencyFieldValueNull = isNullOrEmpty(dependencyFieldValue) return (!actualValueNull && dependencyFieldValueNull) || (actualValueNull && !dependencyFieldValueNull) || - (!actualValueNull && !dependencyFieldValueNull && _actualValue.toUpperCase().indexOf(dependencyFieldValue.toUpperCase()) < 0); + (!actualValueNull && !dependencyFieldValueNull && _actualValue.indexOf(dependencyFieldValue.toLocaleUpperCase()) < 0); } /** * Compare user input with a pattern. Return true if actualValue matchs patternOfExpected @@ -68,5 +68,4 @@ function MatchRegularExpression(actualValue: Object, patternOfExpected: string): var regex = new RegExp(patternOfExpected, "igm"); const _actualValue = !actualValue ? "" : getConcatString(actualValue, ","); return isNullOrEmpty(patternOfExpected) || (!isNullOrEmpty(patternOfExpected) && regex.test(_actualValue)); -} - +} \ No newline at end of file diff --git a/src/@optimizely/forms-sdk/src/helpers/dependencyHelper.ts b/src/@optimizely/forms-sdk/src/helpers/dependencyHelper.ts index 85372b7..5d29bff 100644 --- a/src/@optimizely/forms-sdk/src/helpers/dependencyHelper.ts +++ b/src/@optimizely/forms-sdk/src/helpers/dependencyHelper.ts @@ -1,4 +1,3 @@ export function getConcatString(srcObject: any, seperator: string): string { - let str = (srcObject instanceof Array) ? srcObject.join(seperator) : srcObject as string; - return str.toLocaleUpperCase(); + return (srcObject instanceof Array) ? srcObject.join(seperator) : srcObject as string; } \ No newline at end of file