diff --git a/src/hooks/useSearchHighlightAndScroll.ts b/src/hooks/useSearchHighlightAndScroll.ts index 8c62c0eb3fdd..77e057924e11 100644 --- a/src/hooks/useSearchHighlightAndScroll.ts +++ b/src/hooks/useSearchHighlightAndScroll.ts @@ -383,11 +383,13 @@ function extractReportActionIDsFromSearchResults(searchResultsData: Partial, @@ -399,11 +401,17 @@ function hasChangedTransactionInSearchResults( return false; } + const isReportInSearchResults = (reportID: string | undefined) => !!reportID && !!searchResultsData[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const changedTransactionIDs: string[] = []; for (const [key, transaction] of Object.entries(transactions ?? {})) { - if (!transaction?.transactionID || !previousTransactionKeys.has(key) || previousTransactions?.[key] === transaction) { + const previousTransaction = previousTransactions?.[key]; + if (!transaction?.transactionID || !previousTransactionKeys.has(key) || previousTransaction === transaction) { continue; } + if (transaction.reportID !== previousTransaction?.reportID && (isReportInSearchResults(transaction.reportID) || isReportInSearchResults(previousTransaction?.reportID))) { + return true; + } changedTransactionIDs.push(transaction.transactionID); } diff --git a/src/pages/DynamicReportChangeWorkspacePage.tsx b/src/pages/DynamicReportChangeWorkspacePage.tsx index a9666643729a..bbf7c62d8ff6 100644 --- a/src/pages/DynamicReportChangeWorkspacePage.tsx +++ b/src/pages/DynamicReportChangeWorkspacePage.tsx @@ -2,6 +2,7 @@ import ActivityIndicator from '@components/ActivityIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {useSession} from '@components/OnyxListItemProvider'; import ScreenWrapper from '@components/ScreenWrapper'; +import {useSearchQueryContext, useSearchResultsContext} from '@components/Search/SearchContext'; import SelectionList from '@components/SelectionList'; import type {WorkspaceListItemType} from '@components/SelectionList/ListItem/types'; import UserListItem from '@components/SelectionList/ListItem/UserListItem'; @@ -19,6 +20,7 @@ import useParentReportAction from '@hooks/useParentReportAction'; import usePermissions from '@hooks/usePermissions'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useReportTransactions from '@hooks/useReportTransactions'; +import useSearchShouldCalculateTotals from '@hooks/useSearchShouldCalculateTotals'; import useShouldSuppressPromotionalUI from '@hooks/useShouldSuppressPromotionalUI'; import useThemeStyles from '@hooks/useThemeStyles'; import useWorkspaceList from '@hooks/useWorkspaceList'; @@ -38,6 +40,7 @@ import { isSettled, isWorkspaceEligibleForReportChange, } from '@libs/ReportUtils'; +import refreshSearchAfterReportAction from '@libs/SearchRefreshUtils'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; import {hasAppliedCommuterExclusion, isManualDistanceRequest, isOdometerDistanceRequest} from '@libs/TransactionUtils'; @@ -110,6 +113,20 @@ function DynamicReportChangeWorkspacePage({report}: DynamicReportChangeWorkspace isOdometerDistanceRequest: hasOdometerDistanceRequest, }); const [isTrackIntentUser] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: isTrackIntentUserSelector}); + const {currentSearchQueryJSON, currentSearchKey} = useSearchQueryContext(); + const {currentSearchResults} = useSearchResultsContext(); + const shouldCalculateTotals = useSearchShouldCalculateTotals(currentSearchKey, currentSearchQueryJSON?.hash, true); + + // The snapshot keeps the report row after a workspace change, and only the server can tell whether it still matches the query. + const refreshSearch = () => { + refreshSearchAfterReportAction({ + currentSearchQueryJSON, + currentSearchKey, + shouldCalculateTotals, + isOffline, + isLoading: !!currentSearchResults?.search?.isLoading, + }); + }; const selectPolicy = (policyID?: string) => { const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; @@ -140,6 +157,7 @@ function DynamicReportChangeWorkspacePage({report}: DynamicReportChangeWorkspace if (!invite?.policyExpenseChatReportID) { moveIOUReportToPolicy(report, policy, reportPreviewAction, getCurrencyDecimals, false, reportTransactions); } + refreshSearch(); return; // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 } @@ -169,6 +187,7 @@ function DynamicReportChangeWorkspacePage({report}: DynamicReportChangeWorkspace isTrackIntentUser, reportTransactions, }); + refreshSearch(); return; } @@ -189,6 +208,7 @@ function DynamicReportChangeWorkspacePage({report}: DynamicReportChangeWorkspace isTrackIntentUser, reportTransactions, }); + refreshSearch(); }; const {data, shouldShowNoResultsFoundMessage, shouldShowSearchInput} = useWorkspaceList({ diff --git a/tests/unit/useSearchHighlightAndScrollTest.ts b/tests/unit/useSearchHighlightAndScrollTest.ts index 9ece9df78e55..d34623d5f160 100644 --- a/tests/unit/useSearchHighlightAndScrollTest.ts +++ b/tests/unit/useSearchHighlightAndScrollTest.ts @@ -316,6 +316,60 @@ describe('useSearchHighlightAndScroll', () => { expect(search).not.toHaveBeenCalled(); }); + it('should trigger search when a transaction moves into a report the results display', () => { + const movedTransaction = createMock({transactionID: '99', reportID: '5'}); + const initialProps = createMock({ + ...baseProps, + searchResults: { + ...baseProps.searchResults, + data: { + report_2: {reportID: '2'}, + }, + }, + transactions: {transactions_99: movedTransaction}, + previousTransactions: {transactions_99: movedTransaction}, + }); + + const {rerender} = renderHook((props: UseSearchHighlightAndScroll) => useSearchHighlightAndScroll(props), { + initialProps, + }); + + const updatedProps = createMock({ + ...initialProps, + transactions: {transactions_99: {transactionID: '99', reportID: '2'}}, + }); + + rerender(updatedProps); + expect(search).toHaveBeenCalledWith({queryJSON: baseProps.queryJSON, searchKey: undefined, offset: 0, shouldCalculateTotals: false, isLoading: false}); + }); + + it('should not trigger search when a transaction moves between reports the results do not display', () => { + const movedTransaction = createMock({transactionID: '99', reportID: '5'}); + const initialProps = createMock({ + ...baseProps, + searchResults: { + ...baseProps.searchResults, + data: { + report_2: {reportID: '2'}, + }, + }, + transactions: {transactions_99: movedTransaction}, + previousTransactions: {transactions_99: movedTransaction}, + }); + + const {rerender} = renderHook((props: UseSearchHighlightAndScroll) => useSearchHighlightAndScroll(props), { + initialProps, + }); + + const updatedProps = createMock({ + ...initialProps, + transactions: {transactions_99: {transactionID: '99', reportID: '7'}}, + }); + + rerender(updatedProps); + expect(search).not.toHaveBeenCalled(); + }); + it('should trigger the deferred search once Search is active again, after previousTransactions caught up', () => { const transaction = createMock({transactionID: '1', amount: 100}); const editedTransaction = createMock({transactionID: '1', amount: 250});