diff --git a/src/libs/LocaleCompare.ts b/src/libs/LocaleCompare.ts index 5142c5b43d9a..b2c48b410d32 100644 --- a/src/libs/LocaleCompare.ts +++ b/src/libs/LocaleCompare.ts @@ -1,19 +1,26 @@ import Onyx from 'react-native-onyx'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -const DEFAULT_LOCALE = 'en'; - const COLLATOR_OPTIONS: Intl.CollatorOptions = {usage: 'sort', sensitivity: 'base'}; -let collator = new Intl.Collator(DEFAULT_LOCALE, COLLATOR_OPTIONS); +let collator = new Intl.Collator(CONST.LOCALES.DEFAULT, COLLATOR_OPTIONS); Onyx.connect({ key: ONYXKEYS.NVP_PREFERRED_LOCALE, callback: (locale) => { - collator = new Intl.Collator(locale ?? DEFAULT_LOCALE, COLLATOR_OPTIONS); + collator = new Intl.Collator(locale ?? CONST.LOCALES.DEFAULT, COLLATOR_OPTIONS); }, }); +/** + * This is a wrapper around the localeCompare function that uses the preferred locale from the user's settings. + * + * It re-uses Intl.Collator with static options for performance reasons. See https://github.com/facebook/hermes/issues/867 for more details. + * @param a + * @param b + * @returns -1 if a < b, 1 if a > b, 0 if a === b + */ function localeCompare(a: string, b: string) { return collator.compare(a, b); } diff --git a/tests/unit/LocaleCompareTest.js b/tests/unit/LocaleCompareTest.js new file mode 100644 index 000000000000..3c709675f31d --- /dev/null +++ b/tests/unit/LocaleCompareTest.js @@ -0,0 +1,51 @@ +import Onyx from 'react-native-onyx'; +import localeCompare from '@libs/LocaleCompare'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; + +describe('localeCompare', () => { + beforeAll(() => { + Onyx.init({ + keys: {NVP_PREFERRED_LOCALE: ONYXKEYS.NVP_PREFERRED_LOCALE}, + initialKeyStates: {[ONYXKEYS.NVP_PREFERRED_LOCALE]: CONST.LOCALES.DEFAULT}, + }); + return waitForBatchedUpdates(); + }); + + afterEach(() => Onyx.clear()); + + it('should return -1 for descending comparison', () => { + const result = localeCompare('Da Vinci', 'Tesla'); + + expect(result).toBe(-1); + }); + + it('should return -1 for ascending comparison', () => { + const result = localeCompare('Zidane', 'Messi'); + + expect(result).toBe(1); + }); + + it('should return 0 for equal strings', () => { + const result = localeCompare('Cat', 'Cat'); + + expect(result).toBe(0); + }); + + it('should discard sensitivity differences', () => { + const result = localeCompare('apple', 'Apple'); + + expect(result).toBe(0); + }); + + it('distinguishes spanish diacritic characters', async () => { + await Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES); + + const input = ['zorro', 'árbol', 'jalapeño', 'jalapeno', 'nino', 'niño']; + + input.sort(localeCompare); + + expect(input).toEqual(['árbol', 'jalapeno', 'jalapeño', 'nino', 'niño', 'zorro']); + }); +});