From 7d03a9564b38e59620d86eb4ac492597b60cb5ec Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Fri, 2 May 2025 14:41:07 -0400 Subject: [PATCH] Revert "Optimize /Search call in useSearchHighlightAndScroll hook" --- src/components/Search/SearchList.tsx | 3 +- src/components/Search/index.tsx | 16 +- src/components/SelectionList/types.ts | 3 - src/hooks/useSearchHighlightAndScroll.ts | 102 +++- src/hooks/useSearchPusherUpdates.ts | 71 --- tests/unit/useSearchHighlightAndScrollTest.ts | 439 ++++++------------ 6 files changed, 240 insertions(+), 394 deletions(-) delete mode 100644 src/hooks/useSearchPusherUpdates.ts diff --git a/src/components/Search/SearchList.tsx b/src/components/Search/SearchList.tsx index 3c0c787be7b9..8628f925d518 100644 --- a/src/components/Search/SearchList.tsx +++ b/src/components/Search/SearchList.tsx @@ -14,7 +14,7 @@ import {PressableWithFeedback} from '@components/Pressable'; import type ChatListItem from '@components/SelectionList/ChatListItem'; import type ReportListItem from '@components/SelectionList/Search/ReportListItem'; import type TransactionListItem from '@components/SelectionList/Search/TransactionListItem'; -import type {ExtendedTargetedEvent, ReportListItemType, SearchListItem} from '@components/SelectionList/types'; +import type {ExtendedTargetedEvent, ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; import Text from '@components/Text'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; @@ -31,6 +31,7 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +type SearchListItem = TransactionListItemType | ReportListItemType | ReportActionListItemType; type SearchListItemComponentType = typeof TransactionListItem | typeof ChatListItem | typeof ReportListItem; type SearchListHandle = { diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 120814728aa8..74dbfd2fcc97 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -15,7 +15,6 @@ import usePermissions from '@hooks/usePermissions'; import usePrevious from '@hooks/usePrevious'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSearchHighlightAndScroll from '@hooks/useSearchHighlightAndScroll'; -import useSearchPusherUpdates from '@hooks/useSearchPusherUpdates'; import useThemeStyles from '@hooks/useThemeStyles'; import {turnOffMobileSelectionMode, turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import {search, updateSearchResultsWithTransactionThreadReportID} from '@libs/actions/Search'; @@ -152,7 +151,9 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS const {type, status, sortBy, sortOrder, hash, groupBy} = queryJSON; const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: true}); + const previousTransactions = usePrevious(transactions); const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {canBeMissing: true}); + const previousReportActions = usePrevious(reportActions); const {translate} = useLocalize(); const shouldGroupByReports = groupBy === CONST.SEARCH.GROUP_BY.REPORTS; @@ -199,17 +200,14 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS search({queryJSON, offset}); }, [isOffline, offset, queryJSON]); - // Use custom hook to detect and handle Pusher updates - useSearchPusherUpdates({ - isOffline, - queryJSON, - transactions, - reportActions, - }); - const {newSearchResultKey, handleSelectionListScroll} = useSearchHighlightAndScroll({ searchResults, + transactions, + previousTransactions, queryJSON, + offset, + reportActions, + previousReportActions, }); // There's a race condition in Onyx which makes it return data from the previous Search, so in addition to checking that the data is loaded diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 771f1d2ffb67..9fac2f657f9b 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -726,8 +726,6 @@ type SectionListDataType = ExtendedSectionListData; + transactions: OnyxCollection; + previousTransactions: OnyxCollection; + reportActions: OnyxCollection; + previousReportActions: OnyxCollection; queryJSON: SearchQueryJSON; + offset: number; }; /** - * Hook used to handle highlighting and scrolling for new search results. + * Hook used to trigger a search when a new transaction or report action is added and handle highlighting and scrolling. */ -function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighlightAndScroll) { +function useSearchHighlightAndScroll({searchResults, transactions, previousTransactions, reportActions, previousReportActions, queryJSON, offset}: UseSearchHighlightAndScroll) { + // Ref to track if the search was triggered by this hook + const triggeredByHookRef = useRef(false); + const searchTriggeredRef = useRef(false); + const hasNewItemsRef = useRef(false); + const previousSearchResults = usePrevious(searchResults?.data); const [newSearchResultKey, setNewSearchResultKey] = useState(null); const highlightedIDs = useRef>(new Set()); const initializedRef = useRef(false); - const previousSearchResults = usePrevious(searchResults?.data); const isChat = queryJSON.type === CONST.SEARCH.DATA_TYPES.CHAT; + // Trigger search when a new report action is added while on chat or when a new transaction is added for the other search types. + useEffect(() => { + const previousTransactionsIDs = Object.keys(previousTransactions ?? {}); + const transactionsIDs = Object.keys(transactions ?? {}); + + const reportActionsIDs = Object.values(reportActions ?? {}) + .map((actions) => Object.keys(actions ?? {})) + .flat(); + const previousReportActionsIDs = Object.values(previousReportActions ?? {}) + .map((actions) => Object.keys(actions ?? {})) + .flat(); + + if (searchTriggeredRef.current) { + return; + } + const hasTransactionsIDsChange = !isEqual(transactionsIDs, previousTransactionsIDs); + const hasReportActionsIDsChange = !isEqual(reportActionsIDs, previousReportActionsIDs); + + // Check if there is a change in the transactions or report actions list + if ((!isChat && hasTransactionsIDsChange) || hasReportActionsIDsChange) { + // We only want to highlight new items if the addition of transactions or report actions triggered the search. + // This is because, on deletion of items, the backend sometimes returns old items in place of the deleted ones. + // We don't want to highlight these old items, even if they appear new in the current search results. + hasNewItemsRef.current = isChat ? reportActionsIDs.length > previousReportActionsIDs.length : transactionsIDs.length > previousTransactionsIDs.length; + + // Set the flag indicating the search is triggered by the hook + triggeredByHookRef.current = true; + + // Trigger the search + search({queryJSON, offset}); + + // Set the ref to prevent further triggers until reset + searchTriggeredRef.current = true; + } + + // Reset the ref when transactions or report actions in chat search type are updated + return () => { + searchTriggeredRef.current = false; + }; + }, [transactions, previousTransactions, queryJSON, offset, reportActions, previousReportActions, isChat]); + // Initialize the set with existing IDs only once useEffect(() => { if (initializedRef.current || !searchResults?.data) { @@ -34,7 +86,7 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli initializedRef.current = true; }, [searchResults?.data, isChat]); - // Detect new items in search results after a change in transactions or report actions + // Detect new items (transactions or report actions) useEffect(() => { if (!previousSearchResults || !searchResults?.data) { return; @@ -46,7 +98,7 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli // Find new report action IDs that are not in the previousReportActionIDs and not already highlighted const newReportActionIDs = currentReportActionIDs.filter((id) => !previousReportActionIDs.includes(id) && !highlightedIDs.current.has(id)); - if (newReportActionIDs.length === 0) { + if (!triggeredByHookRef.current || newReportActionIDs.length === 0 || !hasNewItemsRef.current) { return; } @@ -55,25 +107,23 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli setNewSearchResultKey(newReportActionKey); highlightedIDs.current.add(newReportActionID); - return; - } - - // For expenses/transactions - const previousTransactionIDs = extractTransactionIDsFromSearchResults(previousSearchResults); - const currentTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data); + } else { + const previousTransactionIDs = extractTransactionIDsFromSearchResults(previousSearchResults); + const currentTransactionIDs = extractTransactionIDsFromSearchResults(searchResults.data); - // Find new transaction IDs not in previous search results and not already highlighted - const newTransactionIDs = currentTransactionIDs.filter((id) => !previousTransactionIDs.includes(id) && !highlightedIDs.current.has(id)); + // Find new transaction IDs that are not in the previousTransactionIDs and not already highlighted + const newTransactionIDs = currentTransactionIDs.filter((id) => !previousTransactionIDs.includes(id) && !highlightedIDs.current.has(id)); - if (newTransactionIDs.length === 0) { - return; - } + if (!triggeredByHookRef.current || newTransactionIDs.length === 0 || !hasNewItemsRef.current) { + return; + } - const newTransactionID = newTransactionIDs.at(0) ?? ''; - const newTransactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${newTransactionID}`; + const newTransactionID = newTransactionIDs.at(0) ?? ''; + const newTransactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${newTransactionID}`; - setNewSearchResultKey(newTransactionKey); - highlightedIDs.current.add(newTransactionID); + setNewSearchResultKey(newTransactionKey); + highlightedIDs.current.add(newTransactionID); + } }, [searchResults?.data, previousSearchResults, isChat]); // Reset newSearchResultKey after it's been used @@ -93,10 +143,10 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli * Callback to handle scrolling to the new search result. */ const handleSelectionListScroll = useCallback( - (data: SearchListItem[]) => (ref: SelectionListHandle | null) => { + (data: Array) => (ref: SelectionListHandle | null) => { // Early return if there's no ref, new transaction wasn't brought in by this hook // or there's no new search result key - if (!ref || newSearchResultKey === null) { + if (!ref || !triggeredByHookRef.current || newSearchResultKey === null) { return; } @@ -131,6 +181,8 @@ function useSearchHighlightAndScroll({searchResults, queryJSON}: UseSearchHighli // Perform the scrolling action ref.scrollToIndex(indexOfNewItem); + // Reset the trigger flag to prevent unintended future scrolls and highlights + triggeredByHookRef.current = false; }, [newSearchResultKey, isChat], ); diff --git a/src/hooks/useSearchPusherUpdates.ts b/src/hooks/useSearchPusherUpdates.ts deleted file mode 100644 index d814a7fb2aa6..000000000000 --- a/src/hooks/useSearchPusherUpdates.ts +++ /dev/null @@ -1,71 +0,0 @@ -import {useEffect} from 'react'; -import type {OnyxCollection} from 'react-native-onyx'; -import type {SearchQueryJSON} from '@components/Search/types'; -import {search} from '@libs/actions/Search'; -import CONST from '@src/CONST'; -import type {ReportActions, Transaction} from '@src/types/onyx'; -import usePrevious from './usePrevious'; - -/** - * Hook to detect new transactions or report actions from Pusher and trigger a search refresh - */ -function useSearchPusherUpdates({ - isOffline, - queryJSON, - transactions, - reportActions, -}: { - isOffline: boolean; - queryJSON: SearchQueryJSON; - transactions?: OnyxCollection; - reportActions?: OnyxCollection; -}) { - const previousTransactions = usePrevious(transactions); - const previousReportActions = usePrevious(reportActions); - const isChat = queryJSON.type === CONST.SEARCH.DATA_TYPES.CHAT; - - // Detect Pusher updates and trigger search - useEffect(() => { - if (isOffline) { - return; - } - - // Only proceed if we have stable references to compare - if (isChat && (!reportActions || !previousReportActions)) { - return; - } - - if (!isChat && (!transactions || !previousTransactions)) { - return; - } - - // For chat, check if there are new report actions - if (isChat) { - const currentReportActionsIDs = Object.values(reportActions ?? {}).flatMap((actions) => Object.keys(actions ?? {})); - - const previousReportActionsIDs = new Set(Object.values(previousReportActions ?? {}).flatMap((actions) => Object.keys(actions ?? {}))); - - // Only trigger search if Pusher added new report actions - const hasNewReportActions = currentReportActionsIDs.length > previousReportActionsIDs.size && currentReportActionsIDs.some((id) => !previousReportActionsIDs.has(id)); - - if (hasNewReportActions) { - search({queryJSON, offset: 0}); - } - - return; - } - - // For expenses/transactions, check if there are new transactions - const currentTransactionIDs = Object.keys(transactions ?? {}); - const previousTransactionIDs = Object.keys(previousTransactions ?? {}); - - // Only trigger search if Pusher added new transactions - const hasNewTransactions = currentTransactionIDs.length > previousTransactionIDs.length && currentTransactionIDs.some((id) => !previousTransactionIDs.includes(id)); - - if (hasNewTransactions) { - search({queryJSON, offset: 0}); - } - }, [isOffline, queryJSON, transactions, previousTransactions, reportActions, previousReportActions, isChat]); -} - -export default useSearchPusherUpdates; diff --git a/tests/unit/useSearchHighlightAndScrollTest.ts b/tests/unit/useSearchHighlightAndScrollTest.ts index 22c5bdf50e4b..8fc6aed25780 100644 --- a/tests/unit/useSearchHighlightAndScrollTest.ts +++ b/tests/unit/useSearchHighlightAndScrollTest.ts @@ -1,80 +1,17 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import {act, renderHook} from '@testing-library/react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import type {SearchQueryJSON} from '@components/Search/types'; -import * as usePreviousModule from '@hooks/usePrevious'; +import {renderHook} from '@testing-library/react-native'; import useSearchHighlightAndScroll from '@hooks/useSearchHighlightAndScroll'; import type {UseSearchHighlightAndScroll} from '@hooks/useSearchHighlightAndScroll'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type {SearchResults} from '@src/types/onyx'; - -// Mock the usePrevious hook -jest.mock('@hooks/usePrevious', () => ({ - __esModule: true, - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - default: jest.fn((value) => value), -})); +import * as Search from '@libs/actions/Search'; +jest.mock('@libs/actions/Search'); jest.mock('@src/components/ConfirmedRoute.tsx'); -const mockUsePrevious = jest.mocked(usePreviousModule.default); - describe('useSearchHighlightAndScroll', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockUsePrevious.mockImplementation(() => undefined); - jest.useFakeTimers(); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - describe('Transaction search', () => { - const transactionQueryJSON = { - type: 'expense', - status: 'all', - sortBy: 'date', - sortOrder: 'desc', - filters: {operator: 'and', left: 'tag', right: ''}, - inputQuery: 'type:expense status:all sortBy:date sortOrder:desc', - flatFilters: [], - hash: 243428839, - recentSearchHash: 422547233, - } as SearchQueryJSON; - - it('should initialize with null newSearchResultKey when searchResults is empty', () => { - const initialProps: UseSearchHighlightAndScroll = { - searchResults: { - data: {personalDetailsList: {}}, - search: { - columnsToShow: { - shouldShowCategoryColumn: true, - shouldShowTagColumn: true, - shouldShowTaxColumn: true, - }, - hasMoreResults: false, - hasResults: true, - offset: 0, - status: 'all', - type: 'expense', - isLoading: false, - }, - }, - queryJSON: transactionQueryJSON, - }; - - const {result} = renderHook(() => useSearchHighlightAndScroll(initialProps)); - expect(result.current.newSearchResultKey).toBeNull(); - }); - - it('should detect new transactions and set newSearchResultKey', () => { - // Initial search results with transaction1 - const initialSearchResults = { - data: { - transaction1: {transactionID: 'transaction1'}, - personalDetailsList: {}, - }, + it('should trigger Search when transactionIDs list change', () => { + const initialProps: UseSearchHighlightAndScroll = { + searchResults: { + data: {personalDetailsList: {}}, search: { columnsToShow: { shouldShowCategoryColumn: true, @@ -88,236 +25,168 @@ describe('useSearchHighlightAndScroll', () => { type: 'expense', isLoading: false, }, - }; - - // Updated search results with transaction2 added - const updatedSearchResults = { - data: { - transaction1: {transactionID: 'transaction1'}, - transaction2: {transactionID: 'transaction2'}, - personalDetailsList: {}, - }, - search: { - columnsToShow: { - shouldShowCategoryColumn: true, - shouldShowTagColumn: true, - shouldShowTaxColumn: true, + }, + transactions: { + transactions_1: { + amount: -100, + bank: '', + billable: false, + cardID: 0, + cardName: 'Cash Expense', + cardNumber: '', + category: '', + comment: { + comment: '', }, - hasMoreResults: false, - hasResults: true, - offset: 0, - status: 'all', - type: 'expense', - isLoading: false, - }, - }; - - // Mock usePrevious to return the initial search results data - mockUsePrevious.mockImplementation(() => initialSearchResults.data); - - const initialProps: UseSearchHighlightAndScroll = { - searchResults: initialSearchResults as OnyxEntry, - queryJSON: transactionQueryJSON, - }; - - const updatedProps: UseSearchHighlightAndScroll = { - searchResults: updatedSearchResults as OnyxEntry, - queryJSON: transactionQueryJSON, - }; - - const {result, rerender} = renderHook((props) => useSearchHighlightAndScroll(props), { - initialProps, - }); - - // Rerender with updated search results - rerender(updatedProps); - - // Check if newSearchResultKey is set correctly - expect(result.current.newSearchResultKey).toBe(`${ONYXKEYS.COLLECTION.TRANSACTION}transaction2`); - - // Reset timer to verify it clears newSearchResultKey - act(() => { - jest.runAllTimers(); - }); - - expect(result.current.newSearchResultKey).toBeNull(); - }); - - it('should not highlight already highlighted transactions', () => { - // Initial search results - const initialSearchResults = { - data: { - transaction1: {transactionID: 'transaction1'}, - personalDetailsList: {}, - }, - search: { - columnsToShow: { - shouldShowCategoryColumn: true, - shouldShowTagColumn: true, - shouldShowTaxColumn: true, + created: '2025-01-08', + currency: 'ETB', + filename: 'w_c989c343d834d48a4e004c38d03c90bff9434768.png', + inserted: '2025-01-08 15:35:32', + managedCard: false, + merchant: 'g', + modifiedAmount: 0, + modifiedCreated: '', + modifiedCurrency: '', + modifiedMerchant: '', + originalAmount: 0, + originalCurrency: '', + parentTransactionID: '', + posted: '', + receipt: { + receiptID: 7409094723954473, + state: 'SCANCOMPLETE', + source: 'https://www.expensify.com/receipts/w_c989c343d834d48a4e004c38d03c90bff9434768.png', }, - hasMoreResults: false, - hasResults: true, - offset: 0, - status: 'all', - type: 'expense', - isLoading: false, - }, - }; - - // Updated search results with transaction2 added - const updatedSearchResults = { - data: { - transaction1: {transactionID: 'transaction1'}, - transaction2: {transactionID: 'transaction2'}, - personalDetailsList: {}, + reimbursable: true, + reportID: '2309609540437471', + status: 'Posted', + tag: '', + transactionID: '1', + hasEReceipt: false, }, - search: { - columnsToShow: { - shouldShowCategoryColumn: true, - shouldShowTagColumn: true, - shouldShowTaxColumn: true, + }, + previousTransactions: { + transactions_1: { + amount: -100, + bank: '', + billable: false, + cardID: 0, + cardName: 'Cash Expense', + cardNumber: '', + category: '', + comment: { + comment: '', }, - hasMoreResults: false, - hasResults: true, - offset: 0, - status: 'all', - type: 'expense', - isLoading: false, - }, - }; - - // Another update adding transaction3 - const furtherUpdatedSearchResults = { - data: { - transaction1: {transactionID: 'transaction1'}, - transaction2: {transactionID: 'transaction2'}, - transaction3: {transactionID: 'transaction3'}, - personalDetailsList: {}, - }, - search: { - columnsToShow: { - shouldShowCategoryColumn: true, - shouldShowTagColumn: true, - shouldShowTaxColumn: true, + created: '2025-01-08', + currency: 'ETB', + filename: 'w_c989c343d834d48a4e004c38d03c90bff9434768.png', + inserted: '2025-01-08 15:35:32', + managedCard: false, + merchant: 'g', + modifiedAmount: 0, + modifiedCreated: '', + modifiedCurrency: '', + modifiedMerchant: '', + originalAmount: 0, + originalCurrency: '', + parentTransactionID: '', + posted: '', + receipt: { + receiptID: 7409094723954473, + state: 'SCANCOMPLETE', + source: 'https://www.expensify.com/receipts/w_c989c343d834d48a4e004c38d03c90bff9434768.png', }, - hasMoreResults: false, - hasResults: true, - offset: 0, - status: 'all', - type: 'expense', - isLoading: false, + reimbursable: true, + reportID: '2309609540437471', + status: 'Posted', + tag: '', + transactionID: '1', + hasEReceipt: false, }, - }; - - // Mock usePrevious to return the initial search results data - mockUsePrevious.mockImplementation(() => initialSearchResults.data); - - const initialProps: UseSearchHighlightAndScroll = { - searchResults: initialSearchResults as OnyxEntry, - queryJSON: transactionQueryJSON, - }; - - const {result, rerender} = renderHook((props) => useSearchHighlightAndScroll(props), { - initialProps, - }); - - // Rerender with updated search results - rerender({ - searchResults: updatedSearchResults as OnyxEntry, - queryJSON: transactionQueryJSON, - }); - - // Check if newSearchResultKey is set correctly for transaction2 - expect(result.current.newSearchResultKey).toBe(`${ONYXKEYS.COLLECTION.TRANSACTION}transaction2`); - - // Run timers to clear highlight - act(() => { - jest.runAllTimers(); - }); - - // Update previous search results mock - mockUsePrevious.mockImplementation(() => updatedSearchResults.data); - - // Rerender with further updated search results - rerender({ - searchResults: furtherUpdatedSearchResults as OnyxEntry, - queryJSON: transactionQueryJSON, - }); - - // Check if newSearchResultKey is set correctly for transaction3 - expect(result.current.newSearchResultKey).toBe(`${ONYXKEYS.COLLECTION.TRANSACTION}transaction3`); - }); - - it('should handle nested transactions in report items', () => { - // Initial search results with nested transactions - const initialSearchResults = { - data: { - report1: { - reportID: 'report1', - transactions: [{transactionID: 'transaction1'}], + }, + reportActions: { + reportActions_209647397999267: { + 1: { + actionName: 'POLICYCHANGELOG_CORPORATE_UPGRADE', + reportActionID: '1', + created: '', }, - personalDetailsList: {}, }, - search: { - columnsToShow: { - shouldShowCategoryColumn: true, - shouldShowTagColumn: true, - shouldShowTaxColumn: true, + }, + previousReportActions: { + reportActions_209647397999267: { + 1: { + actionName: 'POLICYCHANGELOG_CORPORATE_UPGRADE', + reportActionID: '1', + created: '', }, - hasMoreResults: false, - hasResults: true, - offset: 0, - status: 'all', - type: 'expense', - isLoading: false, }, - }; - - // Updated search results with a new nested transaction - const updatedSearchResults = { - data: { - report1: { - reportID: 'report1', - transactions: [{transactionID: 'transaction1'}, {transactionID: 'transaction2'}], + }, + queryJSON: { + type: 'expense', + status: 'all', + sortBy: 'date', + sortOrder: 'desc', + filters: {operator: 'and', left: 'tag', right: ''}, + inputQuery: 'type:expense status:all sortBy:date sortOrder:desc', + flatFilters: [], + hash: 243428839, + recentSearchHash: 422547233, + }, + offset: 0, + }; + const changedProp: UseSearchHighlightAndScroll = { + ...initialProps, + transactions: { + transactions_2: { + amount: -100, + bank: '', + billable: false, + cardID: 0, + cardName: 'Cash Expense', + cardNumber: '', + category: '', + comment: { + comment: '', }, - personalDetailsList: {}, - }, - search: { - columnsToShow: { - shouldShowCategoryColumn: true, - shouldShowTagColumn: true, - shouldShowTaxColumn: true, + created: '2025-01-08', + currency: 'ETB', + filename: 'w_c989c343d834d48a4e004c38d03c90bff9434768.png', + inserted: '2025-01-08 15:35:32', + managedCard: false, + merchant: 'g', + modifiedAmount: 0, + modifiedCreated: '', + modifiedCurrency: '', + modifiedMerchant: '', + originalAmount: 0, + originalCurrency: '', + parentTransactionID: '', + posted: '', + receipt: { + receiptID: 7409094723954473, + state: 'SCANCOMPLETE', + source: 'https://www.expensify.com/receipts/w_c989c343d834d48a4e004c38d03c90bff9434768.png', }, - hasMoreResults: false, - hasResults: true, - offset: 0, - status: 'all', - type: 'expense', - isLoading: false, + reimbursable: true, + reportID: '2309609540437471', + status: 'Posted', + tag: '', + transactionID: '2', + hasEReceipt: false, }, - }; - - // Mock usePrevious to return the initial search results data - mockUsePrevious.mockImplementation(() => initialSearchResults.data); + }, + }; - const initialProps: UseSearchHighlightAndScroll = { - searchResults: initialSearchResults as OnyxEntry, - queryJSON: transactionQueryJSON, - }; - - const {result, rerender} = renderHook((props) => useSearchHighlightAndScroll(props), { - initialProps, - }); + const {rerender} = renderHook((prop: UseSearchHighlightAndScroll) => useSearchHighlightAndScroll(prop), { + initialProps, + }); + expect(Search.search).not.toHaveBeenCalled(); - // Rerender with updated search results - rerender({ - searchResults: updatedSearchResults as OnyxEntry, - queryJSON: transactionQueryJSON, - }); + // When the transaction ids list change though it has the same length as previous value + rerender(changedProp); - // Check if newSearchResultKey is set correctly - expect(result.current.newSearchResultKey).toBe(`${ONYXKEYS.COLLECTION.TRANSACTION}transaction2`); - }); + // Then Search will be triggerred. + expect(Search.search).toHaveBeenCalled(); }); });