diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 4e227884e8921..1e421079f4113 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -1,14 +1,34 @@ import React, {useCallback, useContext, useMemo, useRef, useState} from 'react'; -import useOnyx from '@hooks/useOnyx'; +// We need direct access to useOnyx from react-native-onyx to avoid circular dependencies in SearchContext +// eslint-disable-next-line no-restricted-imports +import {useOnyx} from 'react-native-onyx'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useTodos from '@hooks/useTodos'; import {isMoneyRequestReport} from '@libs/ReportUtils'; -import {isTransactionListItemType, isTransactionReportGroupListItemType} from '@libs/SearchUIUtils'; +import {getSuggestedSearches, isTodoSearch, isTransactionListItemType, isTransactionReportGroupListItemType} from '@libs/SearchUIUtils'; import type {SearchKey} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {SearchResults} from '@src/types/onyx'; +import type {SearchResultsInfo} from '@src/types/onyx/SearchResults'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {SearchContextData, SearchContextProps, SearchQueryJSON, SelectedTransactions} from './types'; +// Default search info when building from live data +// Used for to-do searches where we build SearchResults from live Onyx data instead of API snapshots +const defaultSearchInfo: SearchResultsInfo = { + offset: 0, + type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT, + status: CONST.SEARCH.STATUS.EXPENSE.ALL, + hasMoreResults: false, + hasResults: true, + isLoading: false, + count: 0, + total: 0, + currency: '', +}; + const defaultSearchContextData: SearchContextData = { currentSearchHash: -1, currentSearchKey: undefined, @@ -29,6 +49,7 @@ const defaultSearchContext: SearchContextProps = { showSelectAllMatchingItems: false, shouldShowFiltersBarLoading: false, currentSearchResults: undefined, + shouldUseLiveData: false, setLastSearchType: () => {}, setCurrentSearchHashAndKey: () => {}, setCurrentSearchQueryJSON: () => {}, @@ -51,7 +72,37 @@ function SearchContextProvider({children}: ChildrenProps) { const [searchContextData, setSearchContextData] = useState(defaultSearchContextData); const areTransactionsEmpty = useRef(true); - const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${searchContextData.currentSearchHash}`, {canBeMissing: true}); + const [snapshotSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${searchContextData.currentSearchHash}`, {canBeMissing: true}); + const {todoSearchResultsData} = useTodos(); + + const currentSearchKey = searchContextData.currentSearchKey; + const currentSearchHash = searchContextData.currentSearchHash; + const {accountID} = useCurrentUserPersonalDetails(); + const suggestedSearches = useMemo(() => getSuggestedSearches(accountID), [accountID]); + const shouldUseLiveData = !!currentSearchKey && isTodoSearch(currentSearchHash, suggestedSearches); + + // If viewing a to-do search, use live data from useTodos, otherwise return the snapshot data + // We do this so that we can show the counters for the to-do search results without visiting the specific to-do page, e.g. show `Approve [3]` while viewing the `Submit` to-do search. + const currentSearchResults = useMemo((): SearchResults | undefined => { + if (shouldUseLiveData) { + const liveData = todoSearchResultsData[currentSearchKey as keyof typeof todoSearchResultsData]; + const searchInfo: SearchResultsInfo = { + ...(snapshotSearchResults?.search ?? defaultSearchInfo), + count: liveData.metadata.count, + total: liveData.metadata.total, + currency: liveData.metadata.currency, + }; + const hasResults = Object.keys(liveData.data).length > 0; + // For to-do searches, always return a valid SearchResults object (even with empty data) + // This ensures we show the empty state instead of loading/blocking views + return { + search: {...searchInfo, isLoading: false, hasResults}, + data: liveData.data, + }; + } + + return snapshotSearchResults ?? undefined; + }, [shouldUseLiveData, currentSearchKey, todoSearchResultsData, snapshotSearchResults]); const setCurrentSearchHashAndKey = useCallback((searchHash: number, searchKey: SearchKey | undefined) => { setSearchContextData((prevState) => { @@ -207,6 +258,7 @@ function SearchContextProvider({children}: ChildrenProps) { () => ({ ...searchContextData, currentSearchResults, + shouldUseLiveData, removeTransaction, setCurrentSearchHashAndKey, setCurrentSearchQueryJSON, @@ -225,6 +277,7 @@ function SearchContextProvider({children}: ChildrenProps) { [ searchContextData, currentSearchResults, + shouldUseLiveData, removeTransaction, setCurrentSearchHashAndKey, setCurrentSearchQueryJSON, diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 0423f0fba432e..3934881d95e5d 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -237,6 +237,7 @@ function Search({ selectAllMatchingItems, shouldResetSearchQuery, setShouldResetSearchQuery, + shouldUseLiveData, } = useSearchContext(); const [offset, setOffset] = useState(0); @@ -267,9 +268,8 @@ function Search({ const {defaultCardFeed} = useCardFeedsForDisplay(); const suggestedSearches = useMemo(() => getSuggestedSearches(accountID, defaultCardFeed?.id), [defaultCardFeed?.id, accountID]); - const searchKey = useMemo(() => Object.values(suggestedSearches).find((search) => search.similarSearchHash === similarSearchHash)?.key, [suggestedSearches, similarSearchHash]); - + const searchDataType = useMemo(() => (shouldUseLiveData ? CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT : searchResults?.search?.type), [shouldUseLiveData, searchResults?.search?.type]); const shouldCalculateTotals = useSearchShouldCalculateTotals(searchKey, similarSearchHash, offset === 0); const previousReportActions = usePrevious(reportActions); @@ -357,15 +357,18 @@ function Search({ shouldCalculateTotals, reportActions, previousReportActions, + shouldUseLiveData, }); // 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 // we also need to check that the searchResults matches the type and status of the current search - const isDataLoaded = isSearchDataLoaded(searchResults, queryJSON); + const isDataLoaded = shouldUseLiveData || isSearchDataLoaded(searchResults, queryJSON); const hasErrors = Object.keys(searchResults?.errors ?? {}).length > 0 && !isOffline; + // For to-do searches, we never show loading state since the data is always available locally from Onyx const shouldShowLoadingState = + !shouldUseLiveData && !isOffline && (!isDataLoaded || (!!searchResults?.search.isLoading && Array.isArray(searchResults?.data) && searchResults?.data.length === 0) || (hasErrors && !searchRequestResponseStatusCode)); const shouldShowLoadingMoreItems = !shouldShowLoadingState && searchResults?.search?.isLoading && searchResults?.search?.offset > 0; @@ -862,8 +865,8 @@ function Search({ if (!searchResults?.data) { return []; } - return getColumnsToShow(accountID, searchResults?.data, visibleColumns, false, searchResults?.search?.type, validGroupBy); - }, [accountID, searchResults?.data, searchResults?.search?.type, visibleColumns, validGroupBy]); + return getColumnsToShow(accountID, searchResults?.data, visibleColumns, false, searchDataType, validGroupBy); + }, [accountID, searchResults?.data, searchDataType, visibleColumns, validGroupBy]); const opacity = useSharedValue(1); const animatedStyle = useAnimatedStyle(() => ({ @@ -1067,7 +1070,7 @@ function Search({ } const visibleDataLength = filteredData.filter((item) => item.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline).length; - if (shouldShowEmptyState(isDataLoaded, visibleDataLength, searchResults?.search?.type)) { + if (shouldShowEmptyState(isDataLoaded, visibleDataLength, searchDataType)) { cancelSpan(CONST.TELEMETRY.SPAN_NAVIGATE_TO_REPORTS_TAB); return ( diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index b4272937bfeda..29a47ad886b73 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -136,6 +136,8 @@ type SearchContextData = { type SearchContextProps = SearchContextData & { currentSearchResults: SearchResults | undefined; + /** Whether we're on a main to-do search and should use live Onyx data instead of snapshots */ + shouldUseLiveData: boolean; setCurrentSearchHashAndKey: (hash: number, key: SearchKey | undefined) => void; setCurrentSearchQueryJSON: (searchQueryJSON: SearchQueryJSON | undefined) => void; /** If you want to set `selectedTransactionIDs`, pass an array as the first argument, object/record otherwise */ diff --git a/src/hooks/useOnyx.ts b/src/hooks/useOnyx.ts index db42d64b636d3..3834c1af47ea3 100644 --- a/src/hooks/useOnyx.ts +++ b/src/hooks/useOnyx.ts @@ -52,16 +52,18 @@ const useOnyx: OriginalUseOnyx = > | undefined; const {selector: selectorProp, ...optionsWithoutSelector} = useOnyxOptions ?? {}; // Determine if we should use snapshot data based on search state and key - const shouldUseSnapshot = isOnSearch && !!currentSearchHash && isSnapshotCompatibleKey; + const shouldUseSnapshot = isOnSearch && !!currentSearchHash && isSnapshotCompatibleKey && !shouldUseLiveData; // Create selector function that handles both regular and snapshot data const selector = useMemo(() => { diff --git a/src/hooks/useSearchHighlightAndScroll.ts b/src/hooks/useSearchHighlightAndScroll.ts index 87d08877018a1..a66f5e43ef60e 100644 --- a/src/hooks/useSearchHighlightAndScroll.ts +++ b/src/hooks/useSearchHighlightAndScroll.ts @@ -23,6 +23,7 @@ type UseSearchHighlightAndScroll = { searchKey: SearchKey | undefined; offset: number; shouldCalculateTotals: boolean; + shouldUseLiveData: boolean; }; /** @@ -38,6 +39,7 @@ function useSearchHighlightAndScroll({ searchKey, offset, shouldCalculateTotals, + shouldUseLiveData, }: UseSearchHighlightAndScroll) { const isFocused = useIsFocused(); const {isOffline} = useNetwork(); @@ -147,12 +149,14 @@ function useSearchHighlightAndScroll({ ]); useEffect(() => { + // For live data, isLoading is always false, so we also need to reset when searchResultsData changes + // For snapshot data, we wait for isLoading to become false after the API call completes if (searchResults?.search?.isLoading) { return; } searchTriggeredRef.current = false; - }, [searchResults?.search?.isLoading]); + }, [searchResults?.search?.isLoading, shouldUseLiveData, searchResultsData]); // Initialize the set with existing IDs only once useEffect(() => { diff --git a/src/hooks/useTodos.ts b/src/hooks/useTodos.ts index 609621bfc4eb4..40d1912b3a4c5 100644 --- a/src/hooks/useTodos.ts +++ b/src/hooks/useTodos.ts @@ -1,10 +1,120 @@ import {useMemo} from 'react'; +// We need direct access to useOnyx from react-native-onyx to avoid using snapshots for live to-do data +// eslint-disable-next-line no-restricted-imports +import {useOnyx} from 'react-native-onyx'; import {isApproveAction, isExportAction, isPrimaryPayAction, isSubmitAction} from '@libs/ReportPrimaryActionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Report, Transaction} from '@src/types/onyx'; +import type {PersonalDetailsList, Policy, Report, ReportActions, ReportNameValuePairs, SearchResults, Transaction, TransactionViolations} from '@src/types/onyx'; import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails'; -import useOnyx from './useOnyx'; + +type TodoSearchResultsData = SearchResults['data']; + +type TodoMetadata = { + /** Total number of transactions across all reports */ + count: number; + /** Sum of all report totals (in cents) */ + total: number; + /** Currency of the first report, used as reference currency */ + currency: string | undefined; +}; + +function computeMetadata(reports: Report[], transactionsByReportID: Record): TodoMetadata { + let count = 0; + let total = 0; + let currency: string | undefined; + + for (const report of reports) { + if (!report?.reportID) { + continue; + } + + const reportTransactions = transactionsByReportID[report.reportID]; + if (reportTransactions) { + count += reportTransactions.length; + + for (const transaction of reportTransactions) { + if (transaction.groupAmount) { + total -= transaction.groupAmount; + } + + if (currency === undefined && transaction.groupCurrency) { + currency = transaction.groupCurrency; + } + } + } + } + + return {count, total, currency}; +} + +/** + * Builds a SearchResults-compatible data object from the given reports and related data. + * This allows the search UI to use live Onyx data instead of snapshot data when viewing to-do results. + */ +function buildSearchResultsData( + reports: Report[], + transactionsByReportID: Record, + allPolicies: Record | undefined, + allReportActions: Record | undefined, + allReportNameValuePairs: Record | undefined, + personalDetails: PersonalDetailsList | undefined, + transactionViolations: Record | undefined, +): TodoSearchResultsData { + const data: Record = {}; + + for (const report of reports) { + if (!report?.reportID) { + continue; + } + data[`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`] = report; + + if (report.policyID && allPolicies) { + const policyKey = `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`; + if (allPolicies[policyKey] && !data[policyKey]) { + data[policyKey] = allPolicies[policyKey]; + } + } + + // Add the report name value pairs for the chat report (needed for pay eligibility checks) + // Note: We don't add the chat report itself to match API behavior and avoid affecting shouldShowYear calculations + if (report.chatReportID && allReportNameValuePairs) { + const nvpKey = `${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.chatReportID}`; + if (allReportNameValuePairs[nvpKey] && !data[nvpKey]) { + data[nvpKey] = allReportNameValuePairs[nvpKey]; + } + } + + if (allReportActions) { + const actionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`; + if (allReportActions[actionsKey] && !data[actionsKey]) { + data[actionsKey] = allReportActions[actionsKey]; + } + } + + // Add transactions for this report using the pre-computed mapping + const reportTransactions = transactionsByReportID[report.reportID]; + if (reportTransactions) { + for (const transaction of reportTransactions) { + const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`; + data[transactionKey] = transaction; + + if (transactionViolations) { + const violationsKey = `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`; + if (transactionViolations[violationsKey]) { + data[violationsKey] = transactionViolations[violationsKey]; + } + } + } + } + } + + if (personalDetails) { + data[ONYXKEYS.PERSONAL_DETAILS_LIST] = personalDetails; + } + + return data as TodoSearchResultsData; +} export default function useTodos() { const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false}); @@ -12,21 +122,23 @@ export default function useTodos() { const [allReportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {canBeMissing: false}); const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false}); const [allReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {canBeMissing: false}); + const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true}); const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true}); + const [personalDetailsList] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: true}); const {login = '', accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); - return useMemo(() => { + const todos = useMemo(() => { const reportsToSubmit: Report[] = []; const reportsToApprove: Report[] = []; const reportsToPay: Report[] = []; const reportsToExport: Report[] = []; + const transactionsByReportID: Record = {}; const reports = allReports ? Object.values(allReports) : []; if (reports.length === 0) { - return {reportsToSubmit, reportsToApprove, reportsToPay, reportsToExport}; + return {reportsToSubmit, reportsToApprove, reportsToPay, reportsToExport, transactionsByReportID}; } - const transactionsByReportID: Record = {}; if (allTransactions) { for (const transaction of Object.values(allTransactions)) { if (!transaction?.reportID) { @@ -59,6 +171,60 @@ export default function useTodos() { } } - return {reportsToSubmit, reportsToApprove, reportsToPay, reportsToExport}; + return {reportsToSubmit, reportsToApprove, reportsToPay, reportsToExport, transactionsByReportID}; }, [allReports, allTransactions, allPolicies, allReportNameValuePairs, allReportActions, currentUserAccountID, login, bankAccountList]); + + const {reportsToSubmit, reportsToApprove, reportsToPay, reportsToExport, transactionsByReportID} = todos; + + // Build SearchResults-formatted data for each to-do category + const todoSearchResultsData = useMemo(() => { + const buildData = (reports: Report[]): {data: TodoSearchResultsData; metadata: TodoMetadata} => { + if (reports.length === 0) { + // Return empty object like the Search API would when there's no data + return { + data: {} as TodoSearchResultsData, + metadata: {count: 0, total: 0, currency: undefined}, + }; + } + + const metadata = computeMetadata(reports, transactionsByReportID); + const data = buildSearchResultsData( + reports, + transactionsByReportID, + allPolicies as Record | undefined, + allReportActions as Record | undefined, + allReportNameValuePairs as Record | undefined, + personalDetailsList, + allTransactionViolations as Record | undefined, + ); + + return {data, metadata}; + }; + + return { + [CONST.SEARCH.SEARCH_KEYS.SUBMIT]: buildData(reportsToSubmit), + [CONST.SEARCH.SEARCH_KEYS.APPROVE]: buildData(reportsToApprove), + [CONST.SEARCH.SEARCH_KEYS.PAY]: buildData(reportsToPay), + [CONST.SEARCH.SEARCH_KEYS.EXPORT]: buildData(reportsToExport), + }; + }, [ + reportsToSubmit, + reportsToApprove, + reportsToPay, + reportsToExport, + transactionsByReportID, + allPolicies, + allReportActions, + allReportNameValuePairs, + personalDetailsList, + allTransactionViolations, + ]); + + return { + reportsToSubmit, + reportsToApprove, + reportsToPay, + reportsToExport, + todoSearchResultsData, + }; } diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 1cc5c295ac061..1c82744af9bf3 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -2760,6 +2760,12 @@ function isCorrectSearchUserName(displayName?: string) { return displayName && displayName.toUpperCase() !== CONST.REPORT.OWNER_EMAIL_FAKE; } +function isTodoSearch(hash: number, suggestedSearches: Record) { + const TODO_KEYS: SearchKey[] = [CONST.SEARCH.SEARCH_KEYS.SUBMIT, CONST.SEARCH.SEARCH_KEYS.APPROVE, CONST.SEARCH.SEARCH_KEYS.PAY, CONST.SEARCH.SEARCH_KEYS.EXPORT]; + const matchedSearchKey = Object.values(suggestedSearches).find((search) => search.hash === hash)?.key; + return !!matchedSearchKey && TODO_KEYS.includes(matchedSearchKey); +} + // eslint-disable-next-line @typescript-eslint/max-params function createTypeMenuSections( icons: Record<'Document' | 'Pencil' | 'ThumbsUp', IconAsset>, @@ -2975,8 +2981,8 @@ function createBaseSavedSearchMenuItem(item: SaveSearchItem, key: string, index: /** * Whether to show the empty state or not */ -function shouldShowEmptyState(isDataLoaded: boolean, dataLength: number, type: SearchDataTypes) { - return !isDataLoaded || dataLength === 0 || !Object.values(CONST.SEARCH.DATA_TYPES).includes(type); +function shouldShowEmptyState(isDataLoaded: boolean, dataLength: number, type: SearchDataTypes | undefined) { + return !isDataLoaded || dataLength === 0 || !type || !Object.values(CONST.SEARCH.DATA_TYPES).includes(type); } function isSearchDataLoaded(searchResults: SearchResults | undefined, queryJSON: SearchQueryJSON | undefined) { @@ -3540,5 +3546,6 @@ export { getTableMinWidth, getCustomColumns, getCustomColumnDefault, + isTodoSearch, }; export type {SavedSearchMenuItem, SearchTypeMenuSection, SearchTypeMenuItem, SearchDateModifier, SearchDateModifierLower, SearchKey, ArchivedReportsIDSet}; diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 02c2a31e37054..d28ff9d228afa 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -108,7 +108,8 @@ function SearchPage({route}: SearchPageProps) { const theme = useTheme(); const {isOffline} = useNetwork(); const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); - const {selectedTransactions, clearSelectedTransactions, selectedReports, lastSearchType, setLastSearchType, areAllMatchingItemsSelected, selectAllMatchingItems} = useSearchContext(); + const {selectedTransactions, clearSelectedTransactions, selectedReports, lastSearchType, setLastSearchType, areAllMatchingItemsSelected, selectAllMatchingItems, currentSearchResults} = + useSearchContext(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const isMobileSelectionModeEnabled = useMobileSelectionMode(); const allTransactions = useAllTransactions(); @@ -163,8 +164,6 @@ function SearchPage({route}: SearchPageProps) { 'ArrowSplit', ] as const); - // eslint-disable-next-line rulesdir/no-default-id-values - const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${queryJSON?.hash ?? CONST.DEFAULT_NUMBER_ID}`, {canBeMissing: true}); const lastNonEmptySearchResults = useRef(undefined); const selectedTransactionReportIDs = useMemo( () => [ @@ -197,10 +196,12 @@ function SearchPage({route}: SearchPageProps) { const totalFormattedAmount = getTotalFormattedAmount(selectedReports, selectedTransactions, selectedBulkCurrency); const onlyShowPayElsewhere = useMemo(() => { - const selectedPolicy = currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${selectedPolicyIDs.at(0)}`]; + const firstPolicyID = selectedPolicyIDs.at(0); + const selectedPolicy = firstPolicyID ? currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${firstPolicyID}`] : undefined; return (selectedTransactionReportIDs ?? selectedReportIDs).some((reportID) => { const report = currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; - const chatReport = currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.chatReportID}`]; + const chatReportID = report?.chatReportID; + const chatReport = chatReportID ? currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`] : undefined; return ( report && !canIOUBePaid(report, chatReport, selectedPolicy, bankAccountList, undefined, false) && diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 223a90cb8a55a..26a51fae61a79 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -215,6 +215,7 @@ jest.mock('@libs/PolicyUtils', () => ({ ...jest.requireActual('@libs/PolicyUtils'), isPolicyAdmin: jest.fn().mockImplementation((policy?: Policy) => policy?.role === 'admin'), isPaidGroupPolicy: jest.fn().mockImplementation((policy?: Policy) => policy?.type === 'corporate' || policy?.type === 'team'), + isPolicyOwner: jest.fn().mockImplementation((policy?: Policy, currentUserAccountID?: number) => !!currentUserAccountID && policy?.ownerAccountID === currentUserAccountID), })); const mockedPolicyUtils = PolicyUtils as jest.Mocked; diff --git a/tests/unit/TransactionGroupListItemTest.tsx b/tests/unit/TransactionGroupListItemTest.tsx index 0a3dc1ebb35eb..71b9fae209316 100644 --- a/tests/unit/TransactionGroupListItemTest.tsx +++ b/tests/unit/TransactionGroupListItemTest.tsx @@ -22,6 +22,7 @@ jest.mock('@libs/SearchUIUtils', () => ({ getSections: jest.fn(() => []), isCorrectSearchUserName: jest.fn(() => true), getTableMinWidth: jest.fn(() => 0), + getSuggestedSearches: jest.fn(() => ({})), })); const mockTransaction: TransactionListItemType = { diff --git a/tests/unit/useSearchHighlightAndScrollTest.ts b/tests/unit/useSearchHighlightAndScrollTest.ts index 8bf7650b979fe..30ac57245929a 100644 --- a/tests/unit/useSearchHighlightAndScrollTest.ts +++ b/tests/unit/useSearchHighlightAndScrollTest.ts @@ -25,6 +25,7 @@ afterEach(() => { describe('useSearchHighlightAndScroll', () => { const baseProps: UseSearchHighlightAndScroll = { + shouldUseLiveData: false, searchResults: { data: { personalDetailsList: {},