Skip to content

Commit

Permalink
Fix filtering issues for string in different locales
Browse files Browse the repository at this point in the history
  • Loading branch information
GoodDayForSurf committed Aug 29, 2024
1 parent fee59e3 commit 7772911
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 19 deletions.
70 changes: 51 additions & 19 deletions packages/devextreme/js/data/array_query.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,13 @@ const compileCriteria = (function() {

const _toComparable = (value, caseSensitivity = false) => toComparable(value, caseSensitivity, langParams);

const toLowerCase = (value) => langParams?.locale ? value.toLocaleLowerCase(langParams.locale) : value.toLowerCase();
const toUpperCase = (value) => langParams?.locale ? value.toLocaleUpperCase(langParams.locale) : value.toUpperCase();

const compareCaseInsensitive = (value1, value2) => {
return toLowerCase(value1) === toLowerCase(value2) || toUpperCase(value1) === toUpperCase(value2);
};

const compileUniformEqualsCriteria = (crit) => {
const getter = compileGetter(crit[0][0]);
const filterValues = crit.reduce((acc, item, i) => {
Expand Down Expand Up @@ -310,6 +317,7 @@ const compileCriteria = (function() {
const getter = compileGetter(crit[0]);
const op = crit[1];
const origValue = crit[2];

const value = _toComparable(origValue);

const compare = (obj, operatorFn) => {
Expand All @@ -332,27 +340,49 @@ const compileCriteria = (function() {
return (obj) => compare(obj, (a, b) => a <= b);
case 'startswith':
return function(obj) {
const objValue = _toComparable(toString(getter(obj)));
const result = objValue.indexOf(value) === 0;
/* eslint-disable-next-line no-undef */
const compareResult = new Intl.Collator(langParams.locale, { sensitivity: 'base', usage: 'search' }).compare(_toComparable(value, true), objValue);
let objValue = toString(getter(obj));
let result;

if(objValue.length < origValue.length) {
return false;
}

if(langParams.collatorOptions?.sensitivity !== 'case') {
objValue = _toComparable(objValue, true);
const searchValue = _toComparable(origValue, true);

result = toUpperCase(objValue).startsWith(toUpperCase(searchValue)) ||
toLowerCase(objValue).startsWith(toLowerCase(searchValue));
} else {
result = _toComparable(objValue).startsWith(value);
}

return result || (compareResult === 0 || compareResult === -1);
return result;
};
case 'endswith':
return function(obj) {
const getterValue = _toComparable(toString(getter(obj)));
const searchValue = toString(value);
let objValue = toString(getter(obj));
let searchValue = toString(origValue);

if(getterValue.length < searchValue.length) {
if(objValue.length < searchValue.length) {
return false;
}

const index = getterValue.lastIndexOf(value);
return index !== -1 && index === getterValue.length - value.length;
let result = _toComparable(objValue).endsWith(_toComparable(searchValue));

if(!result && langParams.collatorOptions?.sensitivity !== 'case') {
objValue = _toComparable(objValue, true);
searchValue = _toComparable(searchValue, true);

result = toUpperCase(objValue).endsWith(toUpperCase(searchValue));
}

return result;
};
case 'contains':
return function(obj) { return _toComparable(toString(getter(obj))).indexOf(value) > -1; };
return function(obj) {
return _toComparable(toString(getter(obj))).indexOf(value) > -1;
};
case 'notcontains':
return function(obj) { return _toComparable(toString(getter(obj))).indexOf(value) === -1; };
}
Expand All @@ -362,24 +392,26 @@ const compileCriteria = (function() {

function compileEquals(getter, value, negate) {
return function(obj) {
let result;

obj = getter(obj);
// eslint-disable-next-line eqeqeq
let result;

if(typeof obj === 'string' && typeof value === 'string' && langParams?.locale) {
if(typeof obj === 'string' && typeof value === 'string' && langParams.collatorOptions?.sensitivity !== 'case' && !useStrictComparison(value)) {
/* eslint-disable-next-line no-undef */
const compareResult = new Intl.Collator(langParams.locale, { sensitivity: 'base', usage: 'search' }).compare(_toComparable(value, true), _toComparable(obj, true));

result = compareResult === 0;
result = compareCaseInsensitive(_toComparable(value, true), _toComparable(obj, true));
} else {
obj = _toComparable(getter(obj));
obj = _toComparable(obj);
value = _toComparable(value);

// eslint-disable-next-line eqeqeq
result = useStrictComparison(value) ? obj === value : obj == value;
}

return negate ? !result : result;
if(negate) {
result = !result;
}

return result;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,49 @@ QUnit.test('filter with collatorOptions.sensitivity set to "base"', function(ass
assert.false(containsUnwantedValue);
});

QUnit.test('filtering use real case insensitivity equal', function(assert) {
const input = [{ ID: 1, Name: 'AΙΤΗΣ' }, { ID: 2, Name: 'aιτης' }, { ID: 3, Name: 'abcde' }];

const array = QUERY(input, {
langParams: {
locale: 'el-GR'
}
}).filter(['Name', '=', 'AΙΤΗΣ']).toArray();

assert.equal(array.length, 2);

const containsUnwantedValue = array.some(item => item.ID === 3);
assert.false(containsUnwantedValue);
});

QUnit.test('filtering use real case insensitivity search', function(assert) {
const input = [
{ ID: 1, Name: 'AΙΤΗΣ' },
{ ID: 2, Name: 'aιτης' },
{ ID: 3, Name: 'aιτησa' },
{ ID: 4, Name: 'AΙΤΗΣΗ' },
{ ID: 5, Name: 'ΑBΤΗΣΗ' },
];

const array = QUERY(input, {
langParams: {
locale: 'el-GR'
}
}).filter(['Name', 'startswith', 'AΙΤΗΣ']).toArray();

const array2 = QUERY(input, {
langParams: {
locale: 'el-GR'
}
}).filter(['Name', 'endswith', 'ΙΤΗΣ']).toArray();

assert.equal(array.length, 4);
assert.equal(array2.length, 2);

const containsUnwantedValue = array.some(item => item.ID === 5) || array2.some(item => [5, 4, 3].includes(item.ID));
assert.false(containsUnwantedValue);
});

QUnit.test('missing operation means equal', function(assert) {
assert.expect(1);

Expand Down

0 comments on commit 7772911

Please sign in to comment.