diff --git a/package-lock.json b/package-lock.json index ac20b08..5878c7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "@gisce/ooui", "version": "1.0.0-alpha.18", "dependencies": { - "@gisce/conscheck": "1.0.5", + "@gisce/conscheck": "1.0.7", "html-entities": "^2.3.3", "moment": "^2.29.3", "txml": "^5.1.1" @@ -543,9 +543,9 @@ } }, "node_modules/@gisce/conscheck": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@gisce/conscheck/-/conscheck-1.0.5.tgz", - "integrity": "sha512-NzA5btxIbB3JD55WHBn7NspHt5fNdjLBAAURJOCdQI/gvoBdSlJq21drUsfU7G5n3epWBVu7tWoMJXEIjTjiZg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@gisce/conscheck/-/conscheck-1.0.7.tgz", + "integrity": "sha512-0tHWCLDgdapZSboe3l5g1QAUzgR32A2hmlVSFpGi6aIQZDz7zImUTxHZTJjHgG90TEakQt0z/6D7QuR5+G45EA==", "engines": { "node": "20.5.0" } @@ -13121,9 +13121,9 @@ "dev": true }, "@gisce/conscheck": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@gisce/conscheck/-/conscheck-1.0.5.tgz", - "integrity": "sha512-NzA5btxIbB3JD55WHBn7NspHt5fNdjLBAAURJOCdQI/gvoBdSlJq21drUsfU7G5n3epWBVu7tWoMJXEIjTjiZg==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@gisce/conscheck/-/conscheck-1.0.7.tgz", + "integrity": "sha512-0tHWCLDgdapZSboe3l5g1QAUzgR32A2hmlVSFpGi6aIQZDz7zImUTxHZTJjHgG90TEakQt0z/6D7QuR5+G45EA==" }, "@humanwhocodes/config-array": { "version": "0.11.11", diff --git a/package.json b/package.json index 0d2a3b0..414e803 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "analyze": "npx vite-bundle-visualizer" }, "dependencies": { - "@gisce/conscheck": "1.0.5", + "@gisce/conscheck": "1.0.7", "html-entities": "^2.3.3", "moment": "^2.29.3", "txml": "^5.1.1" diff --git a/src/helpers/attributeParser.ts b/src/helpers/attributeParser.ts index 18f6659..2d3f423 100644 --- a/src/helpers/attributeParser.ts +++ b/src/helpers/attributeParser.ts @@ -1,6 +1,8 @@ import { decode } from "html-entities"; import { Condition, + FieldComparisonParams, + FieldComparisonResult, evaluateCondition as evaluateConscheckCondition, } from "@gisce/conscheck"; @@ -20,79 +22,122 @@ const evaluateCondition = ({ values: any; fields: any; }) => { - const [fieldName, operator, expectedValue] = entry; + let [fieldName, operator, expectedValue] = entry; + let valueInObject = values[fieldName]; + + const comparisonResult = evaluateFieldComparison({ + fieldName, + valueInObject, + expectedValue, + fields, + }); + + if (comparisonResult.directOutcome !== undefined) { + return comparisonResult.directOutcome; + } + if (comparisonResult.modifiedValueInObject !== null) { + valueInObject = comparisonResult.modifiedValueInObject; + } + if (comparisonResult.modifiedExpectedValue !== null) { + expectedValue = comparisonResult.modifiedExpectedValue; + } + + switch (operator.toLowerCase()) { + case "=": + case "==": + return valueInObject == expectedValue; + case "<>": + case "!=": + return valueInObject != expectedValue; + case ">": + return valueInObject > expectedValue; + case ">=": + return valueInObject >= expectedValue; + case "<": + return valueInObject < expectedValue; + case "<=": + return valueInObject <= expectedValue; + case "in": + return expectedValue.includes(valueInObject); + case "not in": + return !expectedValue.includes(valueInObject); + default: + return false; + } +}; + +const replaceEntities = (string: string): string => { + return decode(string, { level: "xml" }); +}; + +const evaluateFieldComparison = ({ + fieldName, + valueInObject, + expectedValue, + fields = {}, +}: FieldComparisonParams & { fields: any }): FieldComparisonResult => { + const result: FieldComparisonResult = { + modifiedValueInObject: valueInObject, + modifiedExpectedValue: null, + directOutcome: undefined, + }; if (fields[fieldName] === undefined) { - return false; + return { + modifiedValueInObject: null, + modifiedExpectedValue: null, + directOutcome: false, + }; } if ( - values[fieldName] === undefined && + valueInObject === undefined && fields[fieldName].type !== "boolean" && fields[fieldName].type !== "many2one" ) { - return false; + return { + modifiedValueInObject: null, + modifiedExpectedValue: null, + directOutcome: false, + }; } - let filteredExpectedValue = expectedValue; - - let value = - fields[fieldName].type === "boolean" - ? !!values[fieldName] - : values[fieldName]; + result.modifiedValueInObject = + fields[fieldName].type === "boolean" ? !!valueInObject : valueInObject; if ( fields[fieldName].type === "many2one" && expectedValue === false && - values[fieldName] === undefined + valueInObject === undefined ) { - filteredExpectedValue = undefined; + result.modifiedExpectedValue = undefined; } else { - value = value === undefined ? false : value; - value = value === null ? false : value; + result.modifiedValueInObject = + result.modifiedValueInObject === undefined + ? false + : result.modifiedValueInObject; + result.modifiedValueInObject = + result.modifiedValueInObject === null + ? false + : result.modifiedValueInObject; } if ( fields[fieldName].type === "many2one" && - Array.isArray(value) && - value[0] === undefined + Array.isArray(result.modifiedValueInObject) && + result.modifiedValueInObject[0] === undefined ) { - value = false; + result.modifiedValueInObject = false; } if ( fields[fieldName].type === "boolean" && (expectedValue === 0 || expectedValue === 1) ) { - filteredExpectedValue = expectedValue !== 0; + result.modifiedExpectedValue = expectedValue !== 0; } - switch (operator.toLowerCase()) { - case "=": - case "==": - return value == filteredExpectedValue; - case "<>": - case "!=": - return value != filteredExpectedValue; - case ">": - return value > filteredExpectedValue; - case ">=": - return value >= filteredExpectedValue; - case "<": - return value < filteredExpectedValue; - case "<=": - return value <= filteredExpectedValue; - case "in": - return filteredExpectedValue.includes(value); - case "not in": - return !filteredExpectedValue.includes(value); - default: - return false; - } -}; - -const replaceEntities = (string: string): string => { - return decode(string, { level: "xml" }); + return result; }; const parseAttributes = ({ @@ -140,9 +185,11 @@ const parseAttributes = ({ export const parseJsonAttributes = ({ attrs, values, + fields, }: { attrs: string; values: any; + fields: any; }) => { try { const attrsWithReplacedEntities = replaceEntities(attrs); @@ -151,10 +198,22 @@ export const parseJsonAttributes = ({ ) as JsonAttributes; const finalAttributes: Record = {}; for (const attrField of Object.keys(jsonAttributes)) { - finalAttributes[attrField] = evaluateConscheckCondition( - values, - jsonAttributes[attrField], - ); + finalAttributes[attrField] = evaluateConscheckCondition({ + object: values, + condition: jsonAttributes[attrField], + evaluateFieldComparison: ({ + fieldName, + valueInObject, + expectedValue, + }: FieldComparisonParams) => { + return evaluateFieldComparison({ + fieldName, + valueInObject, + expectedValue, + fields, + }); + }, + }); } return finalAttributes; @@ -199,6 +258,7 @@ const evaluateAttributes = ({ finalTagAttributes = parseJsonAttributes({ attrs: tagAttributes.json_attrs, values, + fields, }); } catch (error) { if (fallbackMode && tagAttributes.attrs) { diff --git a/src/spec/attributeParser.spec.ts b/src/spec/attributeParser.spec.ts index e0814e8..ec6de5e 100644 --- a/src/spec/attributeParser.spec.ts +++ b/src/spec/attributeParser.spec.ts @@ -257,22 +257,47 @@ describe("An Attribute Parser", () => { '{"invisible":{"condition":"OR","rules":[{"field":"age","operator":"<","value":18},{"field":"citizenship","operator":"=","value":false}]}}'; const sampleObject = { age: 17, citizenship: false }; expect( - parseJsonAttributes({ attrs: stringJson, values: sampleObject }), + parseJsonAttributes({ + attrs: stringJson, + values: sampleObject, + fields: { + age: { type: "integer" }, + citizenship: { type: "boolean" }, + }, + }), ).toStrictEqual({ invisible: true }); }); it("should properly parse attributes with special entities and singlequotes", () => { + const fields = { + state: { + selection: [ + ["esborrany", "Esborrany"], + ["actiu", "Actiu"], + ["pending", "Pendent d'activació"], + ["baixa", "Baixa"], + ["installed", "instalat"], + ["baixa3", "Baixa per renovació"], + ["baixa4", "Baixa per nova pòlissa"], + ], + string: "Estat", + type: "selection", + views: {}, + }, + }; const stringJson = "{"invisible": {"rules": [{"operator": "!=", "field": "state", "value": "installed"}], "condition": "AND"}}"; expect( parseJsonAttributes({ attrs: stringJson, values: { state: "installed" }, + fields, }), ).toStrictEqual({ invisible: false }); expect( parseJsonAttributes({ attrs: stringJson, values: { state: "pending" }, + fields, }), ).toStrictEqual({ invisible: true }); }); @@ -427,8 +452,7 @@ describe("An Attribute Parser", () => { }); expect(evaluatedAttrs.invisible).toBeTruthy(); }); - // TODO: This test is failing - it.skip("should properly parse a boolean attribute without value (default true)", () => { + it("should properly parse a boolean attribute without value (default true)", () => { const tagAttributes = { json_attrs: `{"invisible": {"condition": "AND", "rules": [{"field": "change_adm", "operator": "=", "value": false}]}}`, }; @@ -455,8 +479,7 @@ describe("An Attribute Parser", () => { }); expect(evaluatedAttrs.invisible).toBeTruthy(); }); - // TODO: This test is failing - it.skip("should properly parse a many2one attribute with undefined value", () => { + it("should properly parse a many2one attribute with undefined value", () => { const tagAttributes = { json_attrs: '{"invisible":{"condition":"AND","rules":[{"field":"autoconsum_id","operator":"=","value":false}]}}',