From 24c67196da20566da15b153ce862b6a74786143e Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 8 May 2025 14:01:07 +0700 Subject: [PATCH 01/13] fix: inconsistent category order display in list & expense page --- src/libs/TagsOptionsListUtils.ts | 4 +--- src/pages/workspace/categories/WorkspaceCategoriesPage.tsx | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/libs/TagsOptionsListUtils.ts b/src/libs/TagsOptionsListUtils.ts index e67c4378b245a..d7471dc1fcb5a 100644 --- a/src/libs/TagsOptionsListUtils.ts +++ b/src/libs/TagsOptionsListUtils.ts @@ -1,4 +1,3 @@ -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'; @@ -162,8 +161,7 @@ function hasEnabledTags(policyTagList: Array | Array) { - // 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}; diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 23ccdbccde48b..d03d5fad10a5d 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -1,4 +1,3 @@ -import lodashSortBy from 'lodash/sortBy'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; @@ -154,7 +153,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { return !!categoryOption.text?.toLowerCase().includes(searchInput) || !!categoryOption.alternateText?.toLowerCase().includes(searchInput); }, []); 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); From 1e48f751b66573b7d9dae9722df55fa2ed3928c3 Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 8 May 2025 22:24:31 +0700 Subject: [PATCH 02/13] fix: lint --- src/libs/TagsOptionsListUtils.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libs/TagsOptionsListUtils.ts b/src/libs/TagsOptionsListUtils.ts index d7471dc1fcb5a..3b5f6adfcaafe 100644 --- a/src/libs/TagsOptionsListUtils.ts +++ b/src/libs/TagsOptionsListUtils.ts @@ -2,10 +2,10 @@ 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; @@ -23,7 +23,7 @@ type SelectedTagOption = { function getTagsOptions(tags: Array>, 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, @@ -83,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({ @@ -129,7 +129,7 @@ function getTagListSections({ tagSections.push({ // "Recent" section - title: Localize.translateLocal('common.recent'), + title: translateLocal('common.recent'), shouldShow: true, data: getTagsOptions(cutRecentlyUsedTags, selectedOptions), }); @@ -137,7 +137,7 @@ function getTagListSections({ 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), }); @@ -154,7 +154,7 @@ function hasEnabledTags(policyTagList: Array Object.values(tags)) .flat(); - return OptionsListUtils.hasEnabledOptions(policyTagValueList); + return hasEnabledOptions(policyTagValueList); } /** From d846b2be190e9067d67bf09a11baa2c257fd8431 Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 8 May 2025 22:43:31 +0700 Subject: [PATCH 03/13] fix: test --- tests/unit/TagsOptionsListUtilsTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/TagsOptionsListUtilsTest.ts b/tests/unit/TagsOptionsListUtilsTest.ts index 5cd18668c064d..f70a0b6ecbc11 100644 --- a/tests/unit/TagsOptionsListUtilsTest.ts +++ b/tests/unit/TagsOptionsListUtilsTest.ts @@ -323,7 +323,7 @@ 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', '10', '10bc', '2', '20', '20a', '3', 'a', 'a1', 'a20', 'b', 'b1', 'b10', 'c', '中国', '日本']; const unorderedTags = createTagObjects(unorderedTagNames); const expectedOrder = createTagObjects(expectedOrderNames); expect(TagsOptionsListUtils.sortTags(unorderedTags)).toStrictEqual(expectedOrder); From bf266e1547aacc26b52f74f2928e2a8d59ab53d3 Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 8 May 2025 22:48:52 +0700 Subject: [PATCH 04/13] fix: lint --- tests/unit/TagsOptionsListUtilsTest.ts | 36 +++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/unit/TagsOptionsListUtilsTest.ts b/tests/unit/TagsOptionsListUtilsTest.ts index f70a0b6ecbc11..f194ccc642d5c 100644 --- a/tests/unit/TagsOptionsListUtilsTest.ts +++ b/tests/unit/TagsOptionsListUtilsTest.ts @@ -1,6 +1,6 @@ -import type * as OptionsListUtils from '@libs/OptionsListUtils'; +import type {Section} from '@libs/OptionsListUtils'; +import {getTagListSections, sortTags} from '@libs/TagsOptionsListUtils'; import type {SelectedTagOption} from '@libs/TagsOptionsListUtils'; -import * as TagsOptionsListUtils from '@libs/TagsOptionsListUtils'; jest.mock('@components/ConfirmedRoute.tsx'); @@ -41,7 +41,7 @@ describe('TagsOptionsListUtils', () => { pendingAction: 'delete', }, }; - const smallResultList: OptionsListUtils.Section[] = [ + const smallResultList: Section[] = [ { title: '', shouldShow: false, @@ -77,7 +77,7 @@ describe('TagsOptionsListUtils', () => { ], }, ]; - const smallSearchResultList: OptionsListUtils.Section[] = [ + const smallSearchResultList: Section[] = [ { title: '', shouldShow: true, @@ -94,7 +94,7 @@ describe('TagsOptionsListUtils', () => { ], }, ]; - const smallWrongSearchResultList: OptionsListUtils.Section[] = [ + const smallWrongSearchResultList: Section[] = [ { title: '', shouldShow: true, @@ -159,7 +159,7 @@ describe('TagsOptionsListUtils', () => { accountID: undefined, }, }; - const largeResultList: OptionsListUtils.Section[] = [ + const largeResultList: Section[] = [ { title: '', shouldShow: true, @@ -261,7 +261,7 @@ describe('TagsOptionsListUtils', () => { ], }, ]; - const largeSearchResultList: OptionsListUtils.Section[] = [ + const largeSearchResultList: Section[] = [ { title: '', shouldShow: true, @@ -287,7 +287,7 @@ describe('TagsOptionsListUtils', () => { ], }, ]; - const largeWrongSearchResultList: OptionsListUtils.Section[] = [ + const largeWrongSearchResultList: Section[] = [ { title: '', shouldShow: true, @@ -295,22 +295,22 @@ describe('TagsOptionsListUtils', () => { }, ]; - const smallResult = TagsOptionsListUtils.getTagListSections({searchValue: emptySearch, tags: smallTagsList}); + const smallResult = getTagListSections({searchValue: emptySearch, tags: smallTagsList}); expect(smallResult).toStrictEqual(smallResultList); - const smallSearchResult = TagsOptionsListUtils.getTagListSections({searchValue: search, tags: smallTagsList}); + const smallSearchResult = getTagListSections({searchValue: search, tags: smallTagsList}); expect(smallSearchResult).toStrictEqual(smallSearchResultList); - const smallWrongSearchResult = TagsOptionsListUtils.getTagListSections({searchValue: wrongSearch, tags: smallTagsList}); + const smallWrongSearchResult = getTagListSections({searchValue: wrongSearch, tags: smallTagsList}); expect(smallWrongSearchResult).toStrictEqual(smallWrongSearchResultList); - const largeResult = TagsOptionsListUtils.getTagListSections({searchValue: emptySearch, selectedOptions, tags: largeTagsList, recentlyUsedTags}); + const largeResult = getTagListSections({searchValue: emptySearch, selectedOptions, tags: largeTagsList, recentlyUsedTags}); expect(largeResult).toStrictEqual(largeResultList); - const largeSearchResult = TagsOptionsListUtils.getTagListSections({searchValue: search, selectedOptions, tags: largeTagsList, recentlyUsedTags}); + const largeSearchResult = getTagListSections({searchValue: search, selectedOptions, tags: largeTagsList, recentlyUsedTags}); expect(largeSearchResult).toStrictEqual(largeSearchResultList); - const largeWrongSearchResult = TagsOptionsListUtils.getTagListSections({ + const largeWrongSearchResult = getTagListSections({ searchValue: wrongSearch, selectedOptions, tags: largeTagsList, @@ -326,13 +326,13 @@ describe('TagsOptionsListUtils', () => { const expectedOrderNames = ['!', '@', '#', '$', '0', '0a', '1', '10', '10bc', '2', '20', '20a', '3', 'a', 'a1', 'a20', 'b', 'b1', 'b10', 'c', '中国', '日本']; const unorderedTags = createTagObjects(unorderedTagNames); const expectedOrder = createTagObjects(expectedOrderNames); - expect(TagsOptionsListUtils.sortTags(unorderedTags)).toStrictEqual(expectedOrder); + 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 unorderedTags2 = createTagObjects(unorderedTagNames2); const expectedOrder2 = createTagObjects(expectedOrderNames2); - expect(TagsOptionsListUtils.sortTags(unorderedTags2)).toStrictEqual(expectedOrder2); + expect(sortTags(unorderedTags2)).toStrictEqual(expectedOrder2); const unorderedTagNames3 = [ '61', @@ -540,7 +540,7 @@ describe('TagsOptionsListUtils', () => { ]; const unorderedTags3 = createTagObjects(unorderedTagNames3); const expectedOrder3 = createTagObjects(expectedOrderNames3); - expect(TagsOptionsListUtils.sortTags(unorderedTags3)).toStrictEqual(expectedOrder3); + expect(sortTags(unorderedTags3)).toStrictEqual(expectedOrder3); }); it('sortTags by object works the same', () => { @@ -564,7 +564,7 @@ describe('TagsOptionsListUtils', () => { }, }; - const sorted = TagsOptionsListUtils.sortTags(tagsObject.tags); + const sorted = sortTags(tagsObject.tags); expect(Array.isArray(sorted)).toBe(true); // Expect to be sorted alphabetically expect(sorted.at(0)?.name).toBe('Car'); From 6365655a39efc0b73faedd1d05fb1a53b0feafc3 Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 12 May 2025 21:24:12 +0700 Subject: [PATCH 05/13] fix: apply Array.sort for taxes and per diem --- src/libs/TaxOptionsListUtils.ts | 4 ++-- src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx | 3 +-- src/pages/workspace/taxes/WorkspaceTaxesPage.tsx | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/libs/TaxOptionsListUtils.ts b/src/libs/TaxOptionsListUtils.ts index 1c5896f2d932a..6e96039c617c2 100644 --- a/src/libs/TaxOptionsListUtils.ts +++ b/src/libs/TaxOptionsListUtils.ts @@ -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 = { @@ -32,7 +32,7 @@ type TaxSection = { * Sorts tax rates alphabetically by name. */ function sortTaxRates(taxRates: TaxRates): TaxRate[] { - const sortedtaxRates = lodashSortBy(taxRates, (taxRate) => taxRate.name); + const sortedtaxRates = Object.values(taxRates).sort((a, b) => localeCompare(a.name, b.name)); return sortedtaxRates; } diff --git a/src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx b/src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx index d5dcf6d7628ec..614f6de1c64dc 100644 --- a/src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx +++ b/src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx @@ -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'; @@ -201,7 +200,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 sections = useMemo(() => [{data: filteredSubRatesList, isDisabled: false}], [filteredSubRatesList]); diff --git a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx index 52b2490d5839b..8c67955ad0e8a 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx @@ -33,6 +33,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 goBackFromWorkspaceCentralScreen from '@libs/Navigation/helpers/goBackFromWorkspaceCentralScreen'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; @@ -195,11 +196,10 @@ 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); - const sections = useMemo(() => [{data: filteredTaxesList, isDisabled: false}], [filteredTaxesList]); const isLoading = !isOffline && taxesList === undefined; @@ -410,7 +410,7 @@ function WorkspaceTaxesPage({ canSelectMultiple={canSelectMultiple} turnOnSelectionModeOnLongPress onTurnOnSelectionMode={(item) => item && toggleTax(item)} - sections={sections} + sections={[{data: filteredTaxesList, isDisabled: false}]} onCheckboxPress={toggleTax} onSelectRow={navigateToEditTaxRate} onSelectAll={toggleAllTaxes} From 336b1f6461a591738336de7c71a3d1f1728f799a Mon Sep 17 00:00:00 2001 From: daledah Date: Fri, 16 May 2025 18:50:09 +0700 Subject: [PATCH 06/13] fix: items not sorting, add compare to tags --- src/hooks/useSearchResults.ts | 10 ++-------- src/libs/LocaleCompare.ts | 2 +- src/pages/workspace/tags/WorkspaceTagsPage.tsx | 3 +-- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/hooks/useSearchResults.ts b/src/hooks/useSearchResults.ts index caade03bcd630..d478ce475276a 100644 --- a/src/hooks/useSearchResults.ts +++ b/src/hooks/useSearchResults.ts @@ -12,17 +12,11 @@ function useSearchResults(data: TValue[], filterData: ( const [inputValue, setInputValue] = useState(''); const [result, setResult] = useState(data); const prevData = usePrevious(data); - const prevInputValueRef = useRef(undefined); const [, startTransition] = useTransition(); useEffect(() => { - const normalizedSearchQuery = inputValue.trim().toLowerCase(); - const filtered = normalizedSearchQuery.length ? data.filter((item) => filterData(item, normalizedSearchQuery)) : data; - if (prevInputValueRef.current === inputValue) { - setResult(filtered); - return; - } - prevInputValueRef.current = inputValue; startTransition(() => { + const normalizedSearchQuery = inputValue.trim().toLowerCase(); + const filtered = normalizedSearchQuery.length ? data.filter((item) => filterData(item, normalizedSearchQuery)) : data; const sorted = sortData(filtered); setResult(sorted); }); diff --git a/src/libs/LocaleCompare.ts b/src/libs/LocaleCompare.ts index b2c48b410d32a..178e91f36c758 100644 --- a/src/libs/LocaleCompare.ts +++ b/src/libs/LocaleCompare.ts @@ -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: 'base', numeric: true}; let collator = new Intl.Collator(CONST.LOCALES.DEFAULT, COLLATOR_OPTIONS); diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index 51d29fa18f266..9be90330aae02 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -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'; @@ -207,7 +206,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( From 54b88f1d2000aadc05908980da220a2d84075c02 Mon Sep 17 00:00:00 2001 From: daledah Date: Fri, 16 May 2025 18:50:37 +0700 Subject: [PATCH 07/13] fix: lint --- src/hooks/useSearchResults.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useSearchResults.ts b/src/hooks/useSearchResults.ts index d478ce475276a..b6851f4f34f51 100644 --- a/src/hooks/useSearchResults.ts +++ b/src/hooks/useSearchResults.ts @@ -1,4 +1,4 @@ -import {useEffect, useRef, useState, useTransition} from 'react'; +import {useEffect, useState, useTransition} from 'react'; import type {ListItem} from '@components/SelectionList/types'; import CONST from '@src/CONST'; import usePrevious from './usePrevious'; From 0e38debedde05377c24cca21f0ebcfd0503af94e Mon Sep 17 00:00:00 2001 From: daledah Date: Fri, 16 May 2025 18:58:35 +0700 Subject: [PATCH 08/13] fix: test --- tests/unit/TagsOptionsListUtilsTest.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/unit/TagsOptionsListUtilsTest.ts b/tests/unit/TagsOptionsListUtilsTest.ts index f194ccc642d5c..a1ce1b1c76559 100644 --- a/tests/unit/TagsOptionsListUtilsTest.ts +++ b/tests/unit/TagsOptionsListUtilsTest.ts @@ -323,13 +323,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); @@ -438,8 +438,15 @@ describe('TagsOptionsListUtils', () => { ]; const expectedOrderNames3 = [ '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', '10', - '100', '11', '12', '13', @@ -449,7 +456,6 @@ describe('TagsOptionsListUtils', () => { '17', '18', '19', - '2', '20', '21', '22', @@ -460,7 +466,6 @@ describe('TagsOptionsListUtils', () => { '27', '28', '29', - '3', '30', '31', '32', @@ -471,7 +476,6 @@ describe('TagsOptionsListUtils', () => { '37', '38', '39', - '4', '40', '41', '42', @@ -482,7 +486,6 @@ describe('TagsOptionsListUtils', () => { '47', '48', '49', - '5', '50', '51', '52', @@ -493,7 +496,6 @@ describe('TagsOptionsListUtils', () => { '57', '58', '59', - '6', '60', '61', '62', @@ -504,7 +506,6 @@ describe('TagsOptionsListUtils', () => { '67', '68', '69', - '7', '70', '71', '72', @@ -515,7 +516,6 @@ describe('TagsOptionsListUtils', () => { '77', '78', '79', - '8', '80', '81', '82', @@ -526,7 +526,6 @@ describe('TagsOptionsListUtils', () => { '87', '88', '89', - '9', '90', '91', '92', @@ -537,6 +536,7 @@ describe('TagsOptionsListUtils', () => { '97', '98', '99', + '100', ]; const unorderedTags3 = createTagObjects(unorderedTagNames3); const expectedOrder3 = createTagObjects(expectedOrderNames3); From 01c59a042fb7d283bc4d9ef98054d1832fbd115c Mon Sep 17 00:00:00 2001 From: daledah Date: Tue, 20 May 2025 23:21:29 +0700 Subject: [PATCH 09/13] fix: sorting with uppercase first --- src/libs/LocaleCompare.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/LocaleCompare.ts b/src/libs/LocaleCompare.ts index 178e91f36c758..e31f531aefed4 100644 --- a/src/libs/LocaleCompare.ts +++ b/src/libs/LocaleCompare.ts @@ -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', numeric: true}; +const COLLATOR_OPTIONS: Intl.CollatorOptions = {usage: 'sort', sensitivity: 'base', numeric: true, caseFirst: 'upper'}; let collator = new Intl.Collator(CONST.LOCALES.DEFAULT, COLLATOR_OPTIONS); From 9eff9a4f41b661423b230cc69486b347640b07b2 Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 22 May 2025 21:32:09 +0700 Subject: [PATCH 10/13] fix: use variant sensitivity --- src/libs/LocaleCompare.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/LocaleCompare.ts b/src/libs/LocaleCompare.ts index e31f531aefed4..817f617523f3a 100644 --- a/src/libs/LocaleCompare.ts +++ b/src/libs/LocaleCompare.ts @@ -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', numeric: true, caseFirst: 'upper'}; +const COLLATOR_OPTIONS: Intl.CollatorOptions = {usage: 'sort', sensitivity: 'variant', numeric: true, caseFirst: 'upper'}; let collator = new Intl.Collator(CONST.LOCALES.DEFAULT, COLLATOR_OPTIONS); From 4048e947a36d22a7e1d6f76b12d1e6bec286fe28 Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 22 May 2025 21:43:57 +0700 Subject: [PATCH 11/13] fix: test --- tests/unit/LocaleCompareTest.ts | 2 +- tests/unit/compareUserInListTest.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/LocaleCompareTest.ts b/tests/unit/LocaleCompareTest.ts index 3c709675f31d1..18ce9c7c73923 100644 --- a/tests/unit/LocaleCompareTest.ts +++ b/tests/unit/LocaleCompareTest.ts @@ -36,7 +36,7 @@ describe('localeCompare', () => { it('should discard sensitivity differences', () => { const result = localeCompare('apple', 'Apple'); - expect(result).toBe(0); + expect(result).toBe(1); }); it('distinguishes spanish diacritic characters', async () => { diff --git a/tests/unit/compareUserInListTest.ts b/tests/unit/compareUserInListTest.ts index ce8b27ddf2268..9c601bb88e6bf 100644 --- a/tests/unit/compareUserInListTest.ts +++ b/tests/unit/compareUserInListTest.ts @@ -24,7 +24,7 @@ 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 second = {login: 'aguero', weight: 2, accountID: 7}; - expect(compareUserInList(first, second)).toBe(-1); - expect(compareUserInList(second, first)).toBe(1); + expect(compareUserInList(first, second)).toBe(1); + expect(compareUserInList(second, first)).toBe(-1); }); }); From 30055ec5fc7c98a41b498ace2d45036a578e7460 Mon Sep 17 00:00:00 2001 From: daledah Date: Tue, 27 May 2025 10:58:21 +0700 Subject: [PATCH 12/13] fix: correct order in IOURequestStepCategory --- src/libs/CategoryOptionListUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/CategoryOptionListUtils.ts b/src/libs/CategoryOptionListUtils.ts index a420bee2ade03..87bc4f5a17165 100644 --- a/src/libs/CategoryOptionListUtils.ts +++ b/src/libs/CategoryOptionListUtils.ts @@ -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'; @@ -218,7 +219,7 @@ function getCategoryListSections({ */ function sortCategories(categories: Record): 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 = {}; From bc15ab4965e692103f29434466fa14142e7fef05 Mon Sep 17 00:00:00 2001 From: daledah Date: Wed, 28 May 2025 21:57:27 +0700 Subject: [PATCH 13/13] fix: test --- tests/unit/LocaleCompareTest.ts | 2 +- tests/unit/compareUserInListTest.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/unit/LocaleCompareTest.ts b/tests/unit/LocaleCompareTest.ts index 18ce9c7c73923..f362d8949cb16 100644 --- a/tests/unit/LocaleCompareTest.ts +++ b/tests/unit/LocaleCompareTest.ts @@ -33,7 +33,7 @@ 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(1); diff --git a/tests/unit/compareUserInListTest.ts b/tests/unit/compareUserInListTest.ts index 9c601bb88e6bf..bbba68f56dbd7 100644 --- a/tests/unit/compareUserInListTest.ts +++ b/tests/unit/compareUserInListTest.ts @@ -22,8 +22,15 @@ 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); });