diff --git a/functionalTests/customWidgets/icheckmatrix.js b/functionalTests/customWidgets/icheckmatrix.js index 40f2e334bf..4b72f6a24f 100644 --- a/functionalTests/customWidgets/icheckmatrix.js +++ b/functionalTests/customWidgets/icheckmatrix.js @@ -59,8 +59,8 @@ frameworks.forEach(framework => { surveyResult = await getSurveyResult(); await t.expect(surveyResult.Quality).eql({ - affordable: 2, - "does what it claims": 3 + affordable: "2", + "does what it claims": "3" }); }); }); diff --git a/packages/survey-core/src/base.ts b/packages/survey-core/src/base.ts index 52e832527e..f4a92d48b7 100644 --- a/packages/survey-core/src/base.ts +++ b/packages/survey-core/src/base.ts @@ -1153,7 +1153,7 @@ export class Base { caseInSensitive: boolean = false, trimString: boolean = false ): boolean { - return Helpers.isTwoValueEquals(x, y, false, !caseInSensitive, trimString); + return Helpers.checkIfValuesEqual(x, y, { ignoreOrder: false, caseSensitive: !caseInSensitive, trimStrings: trimString, doNotConvertNumbers: true }); } private static copyObject(dst: any, src: any) { for (var key in src) { diff --git a/packages/survey-core/src/helpers.ts b/packages/survey-core/src/helpers.ts index 57fdc8246d..bd7d34c842 100644 --- a/packages/survey-core/src/helpers.ts +++ b/packages/survey-core/src/helpers.ts @@ -3,7 +3,12 @@ import { settings } from "./settings"; export interface HashTable { [key: string]: T; } - +export interface IEqualValuesParameters { + ignoreOrder?: boolean; + caseSensitive?: boolean; + trimStrings?: boolean; + doNotConvertNumbers?: boolean; +} export function createDate(reason: string, val?: number | string | Date): Date { if(!val) return new Date(); if(!settings.storeUtcDates && typeof val === "string" && isISODateOnly(val)) { @@ -49,15 +54,10 @@ export class Helpers { } return true; } - public static isArraysEqual( - x: any, - y: any, - ignoreOrder: boolean = false, - caseSensitive?: boolean, - trimStrings? : boolean - ): boolean { + public static checkIfArraysEqual(x: any, y: any, params: IEqualValuesParameters): boolean { if (!Array.isArray(x) || !Array.isArray(y)) return false; if (x.length !== y.length) return false; + const ignoreOrder: boolean = params.ignoreOrder !== undefined ? params.ignoreOrder : false; if (ignoreOrder) { var xSorted = []; var ySorted = []; @@ -71,10 +71,19 @@ export class Helpers { y = ySorted; } for (var i = 0; i < x.length; i++) { - if (!Helpers.isTwoValueEquals(x[i], y[i], ignoreOrder, caseSensitive, trimStrings)) return false; + if (!Helpers.checkIfValuesEqual(x[i], y[i], params)) return false; } return true; } + public static isArraysEqual( + x: any, + y: any, + ignoreOrder: boolean = false, + caseSensitive?: boolean, + trimStrings? : boolean + ): boolean { + return Helpers.checkIfArraysEqual(x, y, { ignoreOrder: ignoreOrder, caseSensitive: caseSensitive, trimStrings: trimStrings }); + } public static compareStrings(x: string, y: string): number { const normalize = settings.comparator.normalizeTextCallback; if(!!x) x = normalize(x, "compare").trim(); @@ -100,13 +109,7 @@ export class Helpers { } return x > y ? 1 : -1; } - public static isTwoValueEquals( - x: any, - y: any, - ignoreOrder: boolean = false, - caseSensitive?: boolean, - trimStrings? : boolean - ): boolean { + public static checkIfValuesEqual(x: any, y: any, params: IEqualValuesParameters): boolean { if (x === y) return true; if (Array.isArray(x) && x.length === 0 && typeof y === "undefined") @@ -115,8 +118,8 @@ export class Helpers { return true; if ((x === undefined || x === null) && y === "") return true; if ((y === undefined || y === null) && x === "") return true; - if(trimStrings === undefined) trimStrings = settings.comparator.trimStrings; - if(caseSensitive === undefined) caseSensitive = settings.comparator.caseSensitive; + const caseSensitive = params.caseSensitive !== undefined ? params.caseSensitive : settings.comparator.caseSensitive; + const trimStrings = params.trimStrings !== undefined ? params.trimStrings : settings.comparator.trimStrings; if(typeof x === "string" && typeof y === "string") { const normalize = settings.comparator.normalizeTextCallback; @@ -133,8 +136,8 @@ export class Helpers { return x === y; } if(x instanceof Date && y instanceof Date) return x.getTime() == y.getTime(); - - if (Helpers.isConvertibleToNumber(x) && Helpers.isConvertibleToNumber(y)) { + const convertNumbers = !params.doNotConvertNumbers; + if (convertNumbers && Helpers.isConvertibleToNumber(x) && Helpers.isConvertibleToNumber(y)) { if (parseInt(x) === parseInt(y) && parseFloat(x) === parseFloat(y)) { return true; } @@ -151,23 +154,34 @@ export class Helpers { if ((y === true || y === false) && typeof x == "string") { return y.toString() === x.toLocaleLowerCase(); } - if (!Helpers.isValueObject(x) && !Helpers.isValueObject(y)) return x == y; - if (!Helpers.isValueObject(x) || !Helpers.isValueObject(y)) return false; + const isXObj = Helpers.isValueObject(x); + const isYObj = Helpers.isValueObject(y); + if (!isXObj && !isYObj && (convertNumbers || (typeof x !== "number" && typeof y !== "number"))) return x == y; + if (!isXObj || !isYObj) return false; if (x["equals"] && y["equals"]) return x.equals(y); if (Array.isArray(x) && Array.isArray(y)) { - return Helpers.isArraysEqual(x, y, ignoreOrder, caseSensitive, trimStrings); + return Helpers.checkIfArraysEqual(x, y, params); } for (var p in x) { if (!x.hasOwnProperty(p)) continue; if (!y.hasOwnProperty(p)) return false; - if (!this.isTwoValueEquals(x[p], y[p], ignoreOrder, caseSensitive, trimStrings)) return false; + if (!this.checkIfValuesEqual(x[p], y[p], params)) return false; } for (p in y) { if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) return false; } return true; } + public static isTwoValueEquals( + x: any, + y: any, + ignoreOrder: boolean = false, + caseSensitive?: boolean, + trimStrings? : boolean + ): boolean { + return this.checkIfValuesEqual(x, y, { ignoreOrder: ignoreOrder, caseSensitive: caseSensitive, trimStrings: trimStrings }); + } public static randomizeArray(array: Array): Array { for (var i = array.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); diff --git a/packages/survey-core/src/question_expression.ts b/packages/survey-core/src/question_expression.ts index 0056802d63..7d2602705b 100644 --- a/packages/survey-core/src/question_expression.ts +++ b/packages/survey-core/src/question_expression.ts @@ -4,7 +4,6 @@ import { Serializer } from "./jsonobject"; import { QuestionFactory } from "./questionfactory"; import { LocalizableString } from "./localizablestring"; import { ExpressionRunner } from "./conditions"; -import { settings } from "./settings"; /** * A class that describes the Expression question type. It is a read-only question type that calculates a value based on a specified expression. diff --git a/packages/survey-core/tests/editingObjectTests.ts b/packages/survey-core/tests/editingObjectTests.ts index 22be288b2e..1f6c5f121a 100644 --- a/packages/survey-core/tests/editingObjectTests.ts +++ b/packages/survey-core/tests/editingObjectTests.ts @@ -1075,7 +1075,7 @@ QUnit.test("Validate in matrix, checkErrorsMode: onValueChanging", function ( }); survey.onMatrixCellValidate.add(function (sender, options) { if (options.columnName != "name") return; - options.error = options.value.length != 4 ? "Error in name" : null; + options.error = options.value.length != 4 ? "Error in name" : undefined; }); var matrix = survey.getQuestionByName("columns"); var row = matrix.visibleRows[0]; diff --git a/packages/survey-core/tests/helperstests.ts b/packages/survey-core/tests/helperstests.ts index 7f1507e9c6..db0ace3fc0 100644 --- a/packages/survey-core/tests/helperstests.ts +++ b/packages/survey-core/tests/helperstests.ts @@ -283,7 +283,11 @@ QUnit.test("isTwoValueEquals, 0 and '0'", function(assert) { "undefined and null" ); }); - +QUnit.test("isTwoValueEquals/checkIfValuesEqual, numbers and string, Bug# 9690", function(assert) { + assert.equal(Helpers.isTwoValueEquals(10, "10"), true, "10 equals '10', #1"); + assert.equal(Helpers.checkIfValuesEqual(10, "10", {}), true, "10 equals '10, #2"); + assert.equal(Helpers.checkIfValuesEqual(10, "10", { doNotConvertNumbers: true }), false, "10 doesn't equal '10', #3"); +}); QUnit.test( "isTwoValueEquals, numbers and string + string and string, Bug# 2000", function(assert) { diff --git a/packages/survey-core/tests/question_expressiontests.ts b/packages/survey-core/tests/question_expressiontests.ts index cb02655706..1f313e48ca 100644 --- a/packages/survey-core/tests/question_expressiontests.ts +++ b/packages/survey-core/tests/question_expressiontests.ts @@ -322,3 +322,15 @@ QUnit.test("Do not serialized required, resetValueIf, setValueIf, defaultValueEx q1.isRequired = true; assert.deepEqual(q1.toJSON(), { name: "q1", expression: "{q2} + {q3}" }, "Serialize only expression"); }); +QUnit.test("Values as number and as string, Bug#9690", function (assert) { + const survey = new SurveyModel({ + elements: [ + { type: "expression", name: "q1", expression: "{q2} + 1" }, + { type: "dropdown", name: "q2", choices: [1, 2, 3] } + ] + }); + survey.data = { q2: 1, q1: "2" }; + const q1 = survey.getQuestionByName("q1"); + assert.strictEqual(q1.value, 2, "q1.value is number"); + assert.strictEqual(survey.getValue("q1"), 2, "survey.getValue('q1') is string"); +});