(core) Use unicode-aware comparisons for user-visible strings.

Summary:
- Switch code that compares user strings to use localeCompare() based on Intl.Collator.
- Use en-US locale for now. (Ideally should be a document property.)
- Note that with this change, sorting is also becoming case-insensitive (which
  seems an improvement)

- Updated a sorted test fixture
- Updated a browser test with lots of unicode to expect different order.
- Added a bit of unicode to test ordering in Reference autocomplete dropdown.

Test Plan: Fixed / updated tests

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D2758
This commit is contained in:
Dmitry S
2021-03-12 21:25:44 -05:00
parent 85a2492123
commit 6e844a2e76
5 changed files with 35 additions and 19 deletions

View File

@@ -7,7 +7,7 @@
* currently implemented.
*/
import {ColumnGetters} from 'app/common/ColumnGetters';
import {nativeCompare} from 'app/common/gutil';
import {localeCompare, nativeCompare} from 'app/common/gutil';
/**
* Compare two cell values, paying attention to types and values. Note that native JS comparison
@@ -19,16 +19,27 @@ import {nativeCompare} from 'app/common/gutil';
* because e.g. a numerical column may contain text (alttext) or null values.
*/
export function typedCompare(val1: any, val2: any): number {
// TODO: We should use Intl.Collator for string comparisons to handle accented strings.
let type1, array1;
return nativeCompare(type1 = typeof val1, typeof val2) ||
// We need to worry about Array comparisons because formulas returing Any may return null or
// object values represented as arrays (e.g. ['D', ...] for dates). Comparing those without
// distinguishing types would break the sort. Also, arrays need a special comparator.
(type1 === 'object' &&
(nativeCompare(array1 = val1 instanceof Array, val2 instanceof Array) ||
(array1 && _arrayCompare(val1, val2)))) ||
nativeCompare(val1, val2);
let result: number, type1: string, array1: boolean;
// tslint:disable-next-line:no-conditional-assignment
if ((result = nativeCompare(type1 = typeof val1, typeof val2)) !== 0) {
return result;
}
// We need to worry about Array comparisons because formulas returing Any may return null or
// object values represented as arrays (e.g. ['D', ...] for dates). Comparing those without
// distinguishing types would break the sort. Also, arrays need a special comparator.
if (type1 === 'object') {
// tslint:disable-next-line:no-conditional-assignment
if ((result = nativeCompare(array1 = val1 instanceof Array, val2 instanceof Array)) !== 0) {
return result;
}
if (array1) {
return _arrayCompare(val1, val2);
}
}
if (type1 === 'string') {
return localeCompare(val1, val2);
}
return nativeCompare(val1, val2);
}
function _arrayCompare(val1: any[], val2: any[]): number {
@@ -36,7 +47,7 @@ function _arrayCompare(val1: any[], val2: any[]): number {
if (i >= val2.length) {
return 1;
}
const value = nativeCompare(val1[i], val2[i]);
const value = typedCompare(val1[i], val2[i]);
if (value) {
return value;
}

View File

@@ -444,6 +444,11 @@ export function nativeCompare<T>(a: T, b: T): number {
return (a < b ? -1 : (a > b ? 1 : 0));
}
// TODO: In the future, locale should be a value associated with the document or the user.
export const defaultLocale = 'en-US';
export const defaultCollator = new Intl.Collator(defaultLocale);
export const localeCompare = defaultCollator.compare;
/**
* A copy of python`s `setdefault` function.
* Sets key in mapInst to value, if key is not already set.