Skip to content
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
3 changes: 2 additions & 1 deletion src/libs/CategoryOptionListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {PolicyCategories} from '@src/types/onyx';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import times from '@src/utils/times';
import localeCompare from './LocaleCompare';
import {translateLocal} from './Localize';
import type {OptionTree, SectionBase} from './OptionsListUtils';

Expand Down Expand Up @@ -218,7 +219,7 @@ function getCategoryListSections({
*/
function sortCategories(categories: Record<string, Category>): Category[] {
// Sorts categories alphabetically by name.
const sortedCategories = Object.values(categories).sort((a, b) => a.name.localeCompare(b.name));
const sortedCategories = Object.values(categories).sort((a, b) => localeCompare(a.name, b.name));

// An object that respects nesting of categories. Also, can contain only uniq categories.
const hierarchy: Hierarchy = {};
Expand Down
2 changes: 1 addition & 1 deletion src/libs/LocaleCompare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Onyx from 'react-native-onyx';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

const COLLATOR_OPTIONS: Intl.CollatorOptions = {usage: 'sort', sensitivity: 'base'};
const COLLATOR_OPTIONS: Intl.CollatorOptions = {usage: 'sort', sensitivity: 'variant', numeric: true, caseFirst: 'upper'};

let collator = new Intl.Collator(CONST.LOCALES.DEFAULT, COLLATOR_OPTIONS);

Expand Down
22 changes: 10 additions & 12 deletions src/libs/TagsOptionsListUtils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import lodashSortBy from 'lodash/sortBy';
import CONST from '@src/CONST';
import type {PolicyTag, PolicyTagLists, PolicyTags} from '@src/types/onyx';
import type {PendingAction} from '@src/types/onyx/OnyxCommon';
import localeCompare from './LocaleCompare';
import * as Localize from './Localize';
import {translateLocal} from './Localize';
import {hasEnabledOptions} from './OptionsListUtils';
import type {Option} from './OptionsListUtils';
import * as OptionsListUtils from './OptionsListUtils';
import * as PolicyUtils from './PolicyUtils';
import {getCleanedTagName} from './PolicyUtils';

type SelectedTagOption = {
name: string;
Expand All @@ -24,7 +23,7 @@ type SelectedTagOption = {
function getTagsOptions(tags: Array<Pick<PolicyTag, 'name' | 'enabled' | 'pendingAction'>>, selectedOptions?: SelectedTagOption[]): Option[] {
return tags.map((tag) => {
// This is to remove unnecessary escaping backslash in tag name sent from backend.
const cleanedName = PolicyUtils.getCleanedTagName(tag.name);
const cleanedName = getCleanedTagName(tag.name);
return {
text: cleanedName,
keyForList: tag.name,
Expand Down Expand Up @@ -84,8 +83,8 @@ function getTagListSections({
}

if (searchValue) {
const enabledSearchTags = enabledTagsWithoutSelectedOptions.filter((tag) => PolicyUtils.getCleanedTagName(tag.name.toLowerCase()).includes(searchValue.toLowerCase()));
const selectedSearchTags = selectedTagsWithDisabledState.filter((tag) => PolicyUtils.getCleanedTagName(tag.name.toLowerCase()).includes(searchValue.toLowerCase()));
const enabledSearchTags = enabledTagsWithoutSelectedOptions.filter((tag) => getCleanedTagName(tag.name.toLowerCase()).includes(searchValue.toLowerCase()));
const selectedSearchTags = selectedTagsWithDisabledState.filter((tag) => getCleanedTagName(tag.name.toLowerCase()).includes(searchValue.toLowerCase()));
const tagsForSearch = [...selectedSearchTags, ...enabledSearchTags];

tagSections.push({
Expand Down Expand Up @@ -130,15 +129,15 @@ function getTagListSections({

tagSections.push({
// "Recent" section
title: Localize.translateLocal('common.recent'),
title: translateLocal('common.recent'),
shouldShow: true,
data: getTagsOptions(cutRecentlyUsedTags, selectedOptions),
});
}

tagSections.push({
// "All" section when items amount more than the threshold
title: Localize.translateLocal('common.all'),
title: translateLocal('common.all'),
shouldShow: true,
data: getTagsOptions(enabledTagsWithoutSelectedOptions, selectedOptions),
});
Expand All @@ -155,15 +154,14 @@ function hasEnabledTags(policyTagList: Array<PolicyTagLists[keyof PolicyTagLists
.map(({tags}) => Object.values(tags))
.flat();

return OptionsListUtils.hasEnabledOptions(policyTagValueList);
return hasEnabledOptions(policyTagValueList);
}

/**
* Sorts tags alphabetically by name.
*/
function sortTags(tags: Record<string, PolicyTag | SelectedTagOption> | Array<PolicyTag | SelectedTagOption>) {
// Use lodash's sortBy to ensure consistency with oldDot.
return lodashSortBy(tags, 'name', localeCompare) as PolicyTag[];
return Object.values(tags ?? {}).sort((a, b) => localeCompare(a.name, b.name)) as PolicyTag[];
}

export {getTagsOptions, getTagListSections, hasEnabledTags, sortTags};
Expand Down
6 changes: 3 additions & 3 deletions src/libs/TaxOptionsListUtils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import lodashSortBy from 'lodash/sortBy';
import type {OnyxEntry} from 'react-native-onyx';
import CONST from '@src/CONST';
import type {Policy, TaxRate, TaxRates, Transaction} from '@src/types/onyx';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import localeCompare from './LocaleCompare';
import {transformedTaxRates} from './TransactionUtils';

type TaxRatesOption = {
Expand Down Expand Up @@ -32,8 +32,8 @@ type TaxSection = {
* Sorts tax rates alphabetically by name.
*/
function sortTaxRates(taxRates: TaxRates): TaxRate[] {
const sortedTaxRates = lodashSortBy(taxRates, (taxRate) => taxRate.name);
return sortedTaxRates;
const sortedtaxRates = Object.values(taxRates).sort((a, b) => localeCompare(a.name, b.name));
return sortedtaxRates;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import lodashSortBy from 'lodash/sortBy';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {ActivityIndicator, InteractionManager, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
Expand Down Expand Up @@ -164,7 +163,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) {
return categoryText.includes(normalizedSearchInput) || alternateText.includes(normalizedSearchInput);
}, []);
const sortCategories = useCallback((data: PolicyOption[]) => {
return lodashSortBy(data, 'text', localeCompare) as PolicyOption[];
return data.sort((a, b) => localeCompare(a.text ?? '', b?.text ?? ''));
}, []);
const [inputValue, setInputValue, filteredCategoryList] = useSearchResults(categoryList, filterCategory, sortCategories);

Expand Down
3 changes: 1 addition & 2 deletions src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {useFocusEffect} from '@react-navigation/native';
import lodashSortBy from 'lodash/sortBy';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {ActivityIndicator, InteractionManager, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
Expand Down Expand Up @@ -200,7 +199,7 @@ function WorkspacePerDiemPage({route}: WorkspacePerDiemPageProps) {
const normalizedSearchInput = StringUtils.normalize(searchInput.toLowerCase());
return rateText.includes(normalizedSearchInput);
}, []);
const sortRates = useCallback((rates: PolicyOption[]) => lodashSortBy(rates, 'text', localeCompare) as PolicyOption[], []);
const sortRates = useCallback((rates: PolicyOption[]) => rates.sort((a, b) => localeCompare(a.text ?? '', b.text ?? '')), []);
const [inputValue, setInputValue, filteredSubRatesList] = useSearchResults(subRatesList, filterRate, sortRates);

const toggleSubRate = (subRate: PolicyOption) => {
Expand Down
3 changes: 1 addition & 2 deletions src/pages/workspace/tags/WorkspaceTagsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import lodashSortBy from 'lodash/sortBy';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {ActivityIndicator, InteractionManager, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
Expand Down Expand Up @@ -241,7 +240,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) {
const normalizeSearchInput = StringUtils.normalize(searchInput.toLowerCase());
return tagText.includes(normalizeSearchInput) || tagValue.includes(normalizeSearchInput);
}, []);
const sortTags = useCallback((tags: TagListItem[]) => lodashSortBy(tags, 'value', localeCompare) as TagListItem[], []);
const sortTags = useCallback((tags: TagListItem[]) => tags.sort((a, b) => localeCompare(a.value, b.value)), []);
const [inputValue, setInputValue, filteredTagList] = useSearchResults(tagList, filterTag, sortTags);

const filteredTagListKeyedByName = useMemo(
Expand Down
3 changes: 2 additions & 1 deletion src/pages/workspace/taxes/WorkspaceTaxesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
import {clearTaxRateError, deletePolicyTaxes, setPolicyTaxesEnabled} from '@libs/actions/TaxRate';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import {getLatestErrorFieldForAnyField} from '@libs/ErrorUtils';
import localeCompare from '@libs/LocaleCompare';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import {
Expand Down Expand Up @@ -192,7 +193,7 @@ function WorkspaceTaxesPage({
return taxes.sort((a, b) => {
const aText = a.text ?? a.keyForList ?? '';
const bText = b.text ?? b.keyForList ?? '';
return aText.localeCompare(bText);
return localeCompare(aText, bText);
});
}, []);
const [inputValue, setInputValue, filteredTaxesList] = useSearchResults(taxesList, filterTax, sortTaxes);
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/LocaleCompareTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ describe('localeCompare', () => {
expect(result).toBe(0);
});

it('should discard sensitivity differences', () => {
it('should put uppercase letters first', () => {
const result = localeCompare('apple', 'Apple');

expect(result).toBe(0);
expect(result).toBe(1);
});

it('distinguishes spanish diacritic characters', async () => {
Expand Down
22 changes: 11 additions & 11 deletions tests/unit/TagsOptionsListUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,13 +321,13 @@ describe('TagsOptionsListUtils', () => {
const createTagObjects = (names: string[]) => names.map((name) => ({name, enabled: true}));

const unorderedTagNames = ['10bc', 'b', '0a', '1', '中国', 'b10', '!', '2', '0', '@', 'a1', 'a', '3', 'b1', '日本', '$', '20', '20a', '#', 'a20', 'c', '10'];
const expectedOrderNames = ['!', '#', '$', '0', '0a', '1', '10', '10bc', '2', '20', '20a', '3', '@', 'a', 'a1', 'a20', 'b', 'b1', 'b10', 'c', '中国', '日本'];
const expectedOrderNames = ['!', '@', '#', '$', '0', '0a', '1', '2', '3', '10', '10bc', '20', '20a', 'a', 'a1', 'a20', 'b', 'b1', 'b10', 'c', '中国', '日本'];
const unorderedTags = createTagObjects(unorderedTagNames);
const expectedOrder = createTagObjects(expectedOrderNames);
expect(sortTags(unorderedTags)).toStrictEqual(expectedOrder);

const unorderedTagNames2 = ['0', 'a1', '1', 'b1', '3', '10', 'b10', 'a', '2', 'c', '20', 'a20', 'b'];
const expectedOrderNames2 = ['0', '1', '10', '2', '20', '3', 'a', 'a1', 'a20', 'b', 'b1', 'b10', 'c'];
const expectedOrderNames2 = ['0', '1', '2', '3', '10', '20', 'a', 'a1', 'a20', 'b', 'b1', 'b10', 'c'];
const unorderedTags2 = createTagObjects(unorderedTagNames2);
const expectedOrder2 = createTagObjects(expectedOrderNames2);
expect(sortTags(unorderedTags2)).toStrictEqual(expectedOrder2);
Expand Down Expand Up @@ -436,8 +436,15 @@ describe('TagsOptionsListUtils', () => {
];
const expectedOrderNames3 = [
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'100',
'11',
'12',
'13',
Expand All @@ -447,7 +454,6 @@ describe('TagsOptionsListUtils', () => {
'17',
'18',
'19',
'2',
'20',
'21',
'22',
Expand All @@ -458,7 +464,6 @@ describe('TagsOptionsListUtils', () => {
'27',
'28',
'29',
'3',
'30',
'31',
'32',
Expand All @@ -469,7 +474,6 @@ describe('TagsOptionsListUtils', () => {
'37',
'38',
'39',
'4',
'40',
'41',
'42',
Expand All @@ -480,7 +484,6 @@ describe('TagsOptionsListUtils', () => {
'47',
'48',
'49',
'5',
'50',
'51',
'52',
Expand All @@ -491,7 +494,6 @@ describe('TagsOptionsListUtils', () => {
'57',
'58',
'59',
'6',
'60',
'61',
'62',
Expand All @@ -502,7 +504,6 @@ describe('TagsOptionsListUtils', () => {
'67',
'68',
'69',
'7',
'70',
'71',
'72',
Expand All @@ -513,7 +514,6 @@ describe('TagsOptionsListUtils', () => {
'77',
'78',
'79',
'8',
'80',
'81',
'82',
Expand All @@ -524,7 +524,6 @@ describe('TagsOptionsListUtils', () => {
'87',
'88',
'89',
'9',
'90',
'91',
'92',
Expand All @@ -535,6 +534,7 @@ describe('TagsOptionsListUtils', () => {
'97',
'98',
'99',
'100',
];
const unorderedTags3 = createTagObjects(unorderedTagNames3);
const expectedOrder3 = createTagObjects(expectedOrderNames3);
Expand Down
9 changes: 8 additions & 1 deletion tests/unit/compareUserInListTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,16 @@ describe('compareUserInList', () => {
});

it('Should compare the accountID if both the weight and displayName are the same', () => {
const first = {login: 'águero', weight: 2, accountID: 6};
const first = {login: 'aguero', weight: 2, accountID: 6};
const second = {login: 'aguero', weight: 2, accountID: 7};
expect(compareUserInList(first, second)).toBe(-1);
expect(compareUserInList(second, first)).toBe(1);
});

it('Should compare the displayName with different diacritics if the weight is the same', () => {
const first = {login: 'águero', weight: 2, accountID: 8};
const second = {login: 'aguero', weight: 2, accountID: 8};
expect(compareUserInList(first, second)).toBe(1);
expect(compareUserInList(second, first)).toBe(-1);
});
});