Skip to content

When survey.data contains a number in a string format and expression … #9693

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions functionalTests/customWidgets/icheckmatrix.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
});
});
});
2 changes: 1 addition & 1 deletion packages/survey-core/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
62 changes: 38 additions & 24 deletions packages/survey-core/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { settings } from "./settings";
export interface HashTable<T = any> {
[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)) {
Expand Down Expand Up @@ -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 = [];
Expand All @@ -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();
Expand All @@ -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")
Expand All @@ -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;
Expand All @@ -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;
}
Expand All @@ -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<T>(array: Array<T>): Array<T> {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
Expand Down
1 change: 0 additions & 1 deletion packages/survey-core/src/question_expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion packages/survey-core/tests/editingObjectTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <QuestionMatrixDynamicModel>survey.getQuestionByName("columns");
var row = matrix.visibleRows[0];
Expand Down
6 changes: 5 additions & 1 deletion packages/survey-core/tests/helperstests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
12 changes: 12 additions & 0 deletions packages/survey-core/tests/question_expressiontests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});