From f436aec55a2025f9b2344d9354a4b87004f81354 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Thu, 5 Mar 2026 23:53:15 +0500 Subject: [PATCH 1/3] remove useExportedToAutocompleteList hook --- src/hooks/useExportedToAutocompleteList.ts | 30 ---- .../useExportedToAutocompleteList.test.ts | 130 ------------------ 2 files changed, 160 deletions(-) delete mode 100644 src/hooks/useExportedToAutocompleteList.ts delete mode 100644 tests/unit/hooks/useExportedToAutocompleteList.test.ts diff --git a/src/hooks/useExportedToAutocompleteList.ts b/src/hooks/useExportedToAutocompleteList.ts deleted file mode 100644 index ee7f41672594..000000000000 --- a/src/hooks/useExportedToAutocompleteList.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {useMemo} from 'react'; -import {getStandardExportTemplateDisplayName} from '@libs/AccountingUtils'; -import {getExportTemplates} from '@libs/actions/Search'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import useLocalize from './useLocalize'; -import useOnyx from './useOnyx'; - -/** - * Hook that returns the list of export template names for the "exported-to:" search autocomplete. - * Combines predefined integration names EXPORTED_TO_INTEGRATION_DISPLAY_NAMES with custom export template names. - */ -export default function useExportedToAutocompleteList(): string[] { - const {translate} = useLocalize(); - const [integrationsExportTemplates] = useOnyx(ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES); - const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS); - - return useMemo(() => { - const exportTemplates = getExportTemplates(integrationsExportTemplates ?? [], csvExportLayouts ?? {}, translate, undefined, true); - const customNames = exportTemplates.flatMap((template) => { - const templateDisplayName = getStandardExportTemplateDisplayName(template.templateName); - if (templateDisplayName !== template.templateName) { - return [templateDisplayName]; - } - return [template.templateName].filter(Boolean); - }); - - return Array.from(new Set([...CONST.POLICY.CONNECTIONS.EXPORTED_TO_INTEGRATION_DISPLAY_NAMES, ...customNames])); - }, [integrationsExportTemplates, csvExportLayouts, translate]); -} diff --git a/tests/unit/hooks/useExportedToAutocompleteList.test.ts b/tests/unit/hooks/useExportedToAutocompleteList.test.ts deleted file mode 100644 index 4363c8f648a1..000000000000 --- a/tests/unit/hooks/useExportedToAutocompleteList.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -import {renderHook} from '@testing-library/react-native'; -import Onyx from 'react-native-onyx'; -import useExportedToAutocompleteList from '@hooks/useExportedToAutocompleteList'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type {ExportTemplate} from '@src/types/onyx'; -import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates'; - -const mockTranslate = jest.fn((key: string) => key); -const mockGetExportTemplates = jest.fn(); - -jest.mock('@hooks/useLocalize', () => ({ - // eslint-disable-next-line @typescript-eslint/naming-convention - __esModule: true, - default: () => ({translate: mockTranslate}), -})); - -jest.mock('@libs/actions/Search', () => ({ - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - getExportTemplates: (...args: unknown[]) => mockGetExportTemplates(...args), -})); - -describe('useExportedToAutocompleteList', () => { - const predefinedNames = CONST.POLICY.CONNECTIONS.EXPORTED_TO_INTEGRATION_DISPLAY_NAMES; - const expenseLevelLabel = CONST.REPORT.EXPORT_OPTION_LABELS.EXPENSE_LEVEL_EXPORT; - const reportLevelLabel = CONST.REPORT.EXPORT_OPTION_LABELS.REPORT_LEVEL_EXPORT; - - beforeAll(() => { - Onyx.init({keys: ONYXKEYS}); - }); - - beforeEach(async () => { - await Onyx.clear(); - await waitForBatchedUpdates(); - jest.clearAllMocks(); - mockGetExportTemplates.mockReturnValue([]); - }); - - it('returns predefined integration names and standard labels for default templates', () => { - mockGetExportTemplates.mockReturnValue([ - {templateName: CONST.REPORT.EXPORT_OPTIONS.EXPENSE_LEVEL_EXPORT, name: expenseLevelLabel} as ExportTemplate, - {templateName: CONST.REPORT.EXPORT_OPTIONS.REPORT_LEVEL_EXPORT, name: reportLevelLabel} as ExportTemplate, - ]); - - const {result} = renderHook(() => useExportedToAutocompleteList()); - - expect(result.current).toEqual(expect.arrayContaining(predefinedNames)); - expect(result.current).toContain(expenseLevelLabel); - expect(result.current).toContain(reportLevelLabel); - }); - - it('passes empty array and object to getExportTemplates when Onyx is undefined', () => { - renderHook(() => useExportedToAutocompleteList()); - - expect(mockGetExportTemplates).toHaveBeenCalledWith([], {}, mockTranslate, undefined, true); - }); - - it('includes standard template display name for expense level template', () => { - mockGetExportTemplates.mockReturnValue([{templateName: CONST.REPORT.EXPORT_OPTIONS.EXPENSE_LEVEL_EXPORT, name: 'Expense Level'} as ExportTemplate]); - - const {result} = renderHook(() => useExportedToAutocompleteList()); - - expect(result.current).toContain(expenseLevelLabel); - expect(result.current).toEqual(expect.arrayContaining([...predefinedNames, expenseLevelLabel])); - }); - - it('includes custom template name when template has no standard mapping', () => { - const customTemplateName = 'Custom Export Layout'; - mockGetExportTemplates.mockReturnValue([{templateName: customTemplateName, name: customTemplateName} as ExportTemplate]); - - const {result} = renderHook(() => useExportedToAutocompleteList()); - - expect(result.current).toContain(customTemplateName); - expect(result.current).toEqual(expect.arrayContaining([...predefinedNames, customTemplateName])); - }); - - it('filters out empty template names', () => { - mockGetExportTemplates.mockReturnValue([{templateName: '', name: 'Empty'} as ExportTemplate, {templateName: 'ValidTemplate', name: 'Valid'} as ExportTemplate]); - - const {result} = renderHook(() => useExportedToAutocompleteList()); - - expect(result.current).not.toContain(''); - expect(result.current).toContain('ValidTemplate'); - }); - - it('returns predefined integration name once when matches it', () => { - const quickBooksDisplayName = 'QuickBooks Online'; - mockGetExportTemplates.mockReturnValue([{templateName: 'quickbooksOnline', name: quickBooksDisplayName} as ExportTemplate]); - - const {result} = renderHook(() => useExportedToAutocompleteList()); - - const count = result.current.filter((item) => item === quickBooksDisplayName).length; - expect(count).toBe(1); - expect(result.current).toContain(quickBooksDisplayName); - }); - - it('returns each custom name once when templates repeat', () => { - mockGetExportTemplates.mockReturnValue([{templateName: 'Duplicate', name: 'Duplicate'} as ExportTemplate, {templateName: 'Duplicate', name: 'Duplicate'} as ExportTemplate]); - - const {result} = renderHook(() => useExportedToAutocompleteList()); - - const count = result.current.filter((item) => item === 'Duplicate').length; - expect(count).toBe(1); - }); - - it('includes standard and custom template names together', () => { - const customName = 'Custom CSV Layout'; - mockGetExportTemplates.mockReturnValue([ - {templateName: CONST.REPORT.EXPORT_OPTIONS.REPORT_LEVEL_EXPORT, name: reportLevelLabel} as ExportTemplate, - {templateName: customName, name: customName} as ExportTemplate, - ]); - - const {result} = renderHook(() => useExportedToAutocompleteList()); - - expect(result.current).toContain(reportLevelLabel); - expect(result.current).toContain(customName); - expect(result.current).toEqual(expect.arrayContaining([...predefinedNames, reportLevelLabel, customName])); - }); - - it('returns same array reference on rerender when deps unchanged', () => { - mockGetExportTemplates.mockReturnValue([]); - - const {result, rerender} = renderHook(() => useExportedToAutocompleteList()); - const firstResult = result.current; - rerender({}); - const secondResult = result.current; - - expect(firstResult).toBe(secondResult); - }); -}); From c70b484a9e46ac3275192b11fa1119825b5a4797 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Thu, 5 Mar 2026 23:54:54 +0500 Subject: [PATCH 2/3] add Exported to filter back in search filters --- src/hooks/useAdvancedSearchFilters.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/useAdvancedSearchFilters.ts b/src/hooks/useAdvancedSearchFilters.ts index a2837683944d..9cbcc545f72a 100644 --- a/src/hooks/useAdvancedSearchFilters.ts +++ b/src/hooks/useAdvancedSearchFilters.ts @@ -55,7 +55,7 @@ const typeFiltersKeys = { CONST.SEARCH.SYNTAX_FILTER_KEYS.APPROVED, CONST.SEARCH.SYNTAX_FILTER_KEYS.PAID, CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED, - // CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED_TO, // Temporarily hidden + CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED_TO, CONST.SEARCH.SYNTAX_FILTER_KEYS.WITHDRAWAL_TYPE, CONST.SEARCH.SYNTAX_FILTER_KEYS.WITHDRAWAL_ID, CONST.SEARCH.SYNTAX_FILTER_KEYS.WITHDRAWN, @@ -80,7 +80,7 @@ const typeFiltersKeys = { CONST.SEARCH.SYNTAX_FILTER_KEYS.APPROVED, CONST.SEARCH.SYNTAX_FILTER_KEYS.PAID, CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED, - // CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED_TO, // Temporarily hidden + CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED_TO, CONST.SEARCH.SYNTAX_FILTER_KEYS.WITHDRAWAL_TYPE, CONST.SEARCH.SYNTAX_FILTER_KEYS.WITHDRAWAL_ID, CONST.SEARCH.SYNTAX_FILTER_KEYS.WITHDRAWN, @@ -156,7 +156,7 @@ const typeFiltersKeys = { CONST.SEARCH.SYNTAX_FILTER_KEYS.APPROVED, CONST.SEARCH.SYNTAX_FILTER_KEYS.PAID, CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED, - // CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED_TO, // Temporarily hidden + CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED_TO, CONST.SEARCH.SYNTAX_FILTER_KEYS.TITLE, ], ], From 2f2d6f3075fdfd2dc01d3915c62ac68c176aa663 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Thu, 5 Mar 2026 23:58:13 +0500 Subject: [PATCH 3/3] remove custom templates from exported to options --- src/hooks/useExportedToFilterOptions.ts | 11 +++----- .../SearchFiltersExportedToPage.tsx | 26 +++++++------------ .../hooks/useExportedToFilterOptions.test.ts | 4 +-- 3 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/hooks/useExportedToFilterOptions.ts b/src/hooks/useExportedToFilterOptions.ts index c21a2082c08a..f5c692915ded 100644 --- a/src/hooks/useExportedToFilterOptions.ts +++ b/src/hooks/useExportedToFilterOptions.ts @@ -17,14 +17,14 @@ type UseExportedToFilterDataResult = { /** * Hook that prepares all data needed for the exported to search filter. - * It collects export templates and all connected integrations to build the filter options. + * It collects standard export templates and all connected integrations to build the filter options. * When currentSearchQueryJSON has policyID, options are scoped to those workspaces so form hydration and autocomplete stay consistent. */ export default function useExportedToFilterOptions(): UseExportedToFilterDataResult { const {currentSearchQueryJSON} = useSearchStateContext(); const policyIDs = currentSearchQueryJSON?.policyID; - const {translate, localeCompare} = useLocalize(); + const {translate} = useLocalize(); const [integrationsExportTemplates] = useOnyx(ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES); const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS); const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); @@ -45,20 +45,15 @@ export default function useExportedToFilterOptions(): UseExportedToFilterDataRes const combinedUniqueExportTemplates = Array.from(uniqueExportTemplatesByName.values()); const standardExportTemplates: string[] = []; - const customExportTemplates: string[] = []; for (const template of combinedUniqueExportTemplates) { const displayName = getStandardExportTemplateDisplayName(template.templateName); const isStandardTemplate = displayName !== template.templateName; if (isStandardTemplate) { standardExportTemplates.push(displayName); - } else { - customExportTemplates.push(template.name ?? template.templateName); } } - customExportTemplates.sort((a, b) => localeCompare(a, b)); - const connectedIntegrationNames = policyIDs && policyIDs.length === 0 ? new Set() : getConnectedIntegrationNamesForPolicies(policies, policyIDs); const displayNameToConnectionName = new Map( @@ -70,7 +65,7 @@ export default function useExportedToFilterOptions(): UseExportedToFilterDataRes return connectionName && connectedIntegrationNames.has(connectionName); }); - const exportedToFilterOptions = [...connectedIntegrationDisplayNames, ...customExportTemplates, ...standardExportTemplates]; + const exportedToFilterOptions = [...connectedIntegrationDisplayNames, ...standardExportTemplates]; return { exportedToFilterOptions, diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExportedToPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExportedToPage.tsx index 0ab976b11921..67bed48efd14 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExportedToPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExportedToPage.tsx @@ -30,7 +30,7 @@ const STANDARD_EXPORT_TEMPLATE_ID_TO_DISPLAY_LABEL: Record = { function SearchFiltersExportedToPage() { const styles = useThemeStyles(); - const {translate, localeCompare} = useLocalize(); + const {translate} = useLocalize(); const StyleUtils = useStyleUtils(); const theme = useTheme(); const expensifyIcons = useMemoizedLazyExpensifyIcons(['XeroSquare', 'QBOSquare', 'NetSuiteSquare', 'IntacctSquare', 'QBDSquare', 'CertiniaSquare', 'Table']); @@ -95,7 +95,6 @@ function SearchFiltersExportedToPage() { } const deduplicatedExportTemplates = Array.from(exportTemplatesByTemplateId.values()); - const customExportTemplatePickerItems: SearchMultipleSelectionPickerItem[] = []; const standardExportTemplatePickerItems: SearchMultipleSelectionPickerItem[] = []; for (const template of deduplicatedExportTemplates) { @@ -103,27 +102,20 @@ function SearchFiltersExportedToPage() { continue; } + if (!STANDARD_EXPORT_TEMPLATE_ID_TO_DISPLAY_LABEL[template.templateName]) { + continue; + } + const displayName = template.name ?? template.templateName ?? ''; - const isStandardExportTemplate = !!STANDARD_EXPORT_TEMPLATE_ID_TO_DISPLAY_LABEL[template.templateName]; - const filterValue = isStandardExportTemplate - ? (STANDARD_EXPORT_TEMPLATE_ID_TO_DISPLAY_LABEL[template.templateName] ?? template.templateName) - : (template.name ?? template.templateName); - const pickerItem: SearchMultipleSelectionPickerItem = { + const filterValue = STANDARD_EXPORT_TEMPLATE_ID_TO_DISPLAY_LABEL[template.templateName] ?? template.templateName; + standardExportTemplatePickerItems.push({ name: displayName, value: filterValue, leftElement: tableIconForExportOption, - }; - - if (STANDARD_EXPORT_TEMPLATE_ID_TO_DISPLAY_LABEL[template.templateName]) { - standardExportTemplatePickerItems.push(pickerItem); - } else { - customExportTemplatePickerItems.push(pickerItem); - } + }); } - customExportTemplatePickerItems.sort((a, b) => localeCompare(a.name, b.name)); - - return [...connectedIntegrationPickerItems, ...customExportTemplatePickerItems, ...standardExportTemplatePickerItems]; + return [...connectedIntegrationPickerItems, ...standardExportTemplatePickerItems]; })(); const initiallySelectedPickerItems: SearchMultipleSelectionPickerItem[] | undefined = (() => { diff --git a/tests/unit/hooks/useExportedToFilterOptions.test.ts b/tests/unit/hooks/useExportedToFilterOptions.test.ts index 424a24b8fcb3..2dab1601c775 100644 --- a/tests/unit/hooks/useExportedToFilterOptions.test.ts +++ b/tests/unit/hooks/useExportedToFilterOptions.test.ts @@ -59,13 +59,13 @@ describe('useExportedToFilterOptions', () => { expect(result.current.connectedIntegrationNames).toContain(CONST.POLICY.CONNECTIONS.NAME.QBO); }); - it('includes custom template name in options when getExportTemplates returns custom template', () => { + it('excludes custom template name from options when getExportTemplates returns custom template', () => { const customName = 'Export Layout'; mockGetExportTemplates.mockReturnValue([{templateName: customName, name: customName} as ExportTemplate]); const {result} = renderHook(() => useExportedToFilterOptions()); - expect(result.current.exportedToFilterOptions).toContain(customName); + expect(result.current.exportedToFilterOptions).not.toContain(customName); }); it('includes standard export label in options when getExportTemplates returns standard template', () => {