diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 10ff7122d9984..b1e5163f5efda 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -395,6 +395,9 @@ const ONYXKEYS = { /** Set when we are loading fresh subscription/billing data from the server */ IS_LOADING_SUBSCRIPTION_DATA: 'isLoadingSubscriptionData', + /** Set when OpenSearchPage has been fetched for the first time */ + IS_SEARCH_PAGE_DATA_LOADED: 'isSearchPageDataLoaded', + /** Is the app loaded? */ HAS_LOADED_APP: 'hasLoadedApp', @@ -1359,6 +1362,7 @@ type OnyxValuesMapping = { [ONYXKEYS.IS_LOADING_SHARE_BANK_ACCOUNTS]: boolean; [ONYXKEYS.IS_LOADING_POLICY_CODING_RULES_PREVIEW]: boolean; [ONYXKEYS.IS_LOADING_SUBSCRIPTION_DATA]: boolean; + [ONYXKEYS.IS_SEARCH_PAGE_DATA_LOADED]: boolean; [ONYXKEYS.IS_LOADING_REPORT_DATA]: boolean; [ONYXKEYS.IS_TEST_TOOLS_MODAL_OPEN]: boolean; [ONYXKEYS.IS_LOADING_APP]: boolean; diff --git a/src/hooks/useSearchTypeMenuSections.ts b/src/hooks/useSearchTypeMenuSections.ts index 4bcf26e586cd9..2dd1d2154c237 100644 --- a/src/hooks/useSearchTypeMenuSections.ts +++ b/src/hooks/useSearchTypeMenuSections.ts @@ -95,10 +95,6 @@ const useSearchTypeMenuSections = (queryParams?: UseSearchTypeMenuSectionsParams openCreateReportConfirmation(); }, [pendingReportCreation, openCreateReportConfirmation]); - const isSuggestedSearchDataReady = useMemo(() => { - return Object.values(allPolicies ?? {}).some((policy) => policy?.employeeList !== undefined && policy?.exporter !== undefined); - }, [allPolicies]); - const typeMenuSections = useMemo( () => createTypeMenuSections({ @@ -149,7 +145,6 @@ const useSearchTypeMenuSections = (queryParams?: UseSearchTypeMenuSectionsParams return { typeMenuSections, - shouldShowSuggestedSearchSkeleton: !isSuggestedSearchDataReady && !isOffline, activeItemIndex, }; }; diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 41269099bcc40..9fb960e5b7f11 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -441,7 +441,15 @@ function deleteSavedSearch(hash: number) { } function openSearchPage({includePartiallySetupBankAccounts}: OpenSearchPageParams) { - API.read(READ_COMMANDS.OPEN_SEARCH_PAGE, {includePartiallySetupBankAccounts}); + const successData: Array> = [ + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.IS_SEARCH_PAGE_DATA_LOADED, + value: true, + }, + ]; + + API.read(READ_COMMANDS.OPEN_SEARCH_PAGE, {includePartiallySetupBankAccounts}, {successData}); } // Tracks in-flight search requests by hash+offset to prevent duplicate API calls diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index 1646efb6d64fc..d4b3645339019 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -10,6 +10,7 @@ import type {SearchQueryJSON} from '@components/Search/types'; import Text from '@components/Text'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import useSearchTypeMenuSections from '@hooks/useSearchTypeMenuSections'; import useSingleExecution from '@hooks/useSingleExecution'; @@ -21,6 +22,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import todosReportCountsSelector from '@src/selectors/Todos'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import SavedSearchList from './SavedSearchList'; import SearchTypeMenuItem from './SearchTypeMenuItem'; import SuggestedSearchSkeleton from './SuggestedSearchSkeleton'; @@ -33,9 +35,10 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { const {hash, similarSearchHash} = queryJSON ?? {}; const styles = useThemeStyles(); - const {singleExecution} = useSingleExecution(); + const {isOffline} = useNetwork(); const {translate} = useLocalize(); - const {typeMenuSections, shouldShowSuggestedSearchSkeleton, activeItemIndex} = useSearchTypeMenuSections({hash, similarSearchHash}); + const {singleExecution} = useSingleExecution(); + const {typeMenuSections, activeItemIndex} = useSearchTypeMenuSections({hash, similarSearchHash}); const expensifyIcons = useMemoizedLazyExpensifyIcons([ 'Basket', 'CalendarSolid', @@ -51,6 +54,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { 'Folder', ] as const); const {clearSelectedTransactions} = useSearchActionsContext(); + const [isSearchDataLoaded, isSearchDataLoadedResult] = useOnyx(ONYXKEYS.IS_SEARCH_PAGE_DATA_LOADED); const [reportCounts = CONST.EMPTY_TODOS_REPORT_COUNTS] = useOnyx(ONYXKEYS.DERIVED.TODOS, {selector: todosReportCountsSelector}); const route = useRoute(); @@ -87,13 +91,15 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query: searchQuery})); }); + const areSuggestedSearchesLoading = !isOffline && !isSearchDataLoaded && !isLoadingOnyxValue(isSearchDataLoadedResult); + return ( - {shouldShowSuggestedSearchSkeleton ? ( + {areSuggestedSearchesLoading ? ( diff --git a/tests/unit/useSearchTypeMenuSectionsTest.ts b/tests/unit/useSearchTypeMenuSectionsTest.ts deleted file mode 100644 index 29ad606f70541..0000000000000 --- a/tests/unit/useSearchTypeMenuSectionsTest.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {renderHook} from '@testing-library/react-native'; -import useNetwork from '@hooks/useNetwork'; -import useSearchTypeMenuSections from '@hooks/useSearchTypeMenuSections'; -import ONYXKEYS from '@src/ONYXKEYS'; - -jest.mock('@libs/ReportUtils', () => ({ - getPersonalDetailsForAccountID: jest.fn(), - hasEmptyReportsForPolicy: jest.fn(() => false), - hasViolations: jest.fn(() => false), -})); - -jest.mock('@userActions/Report', () => ({ - createNewReport: jest.fn(() => ({reportID: 'mock-report-id'})), -})); - -jest.mock('@hooks/useCardFeedsForDisplay', () => jest.fn(() => ({defaultCardFeed: null, cardFeedsByPolicy: {}}))); -jest.mock('@hooks/useCreateEmptyReportConfirmation', () => jest.fn(() => ({openCreateReportConfirmation: jest.fn()}))); -jest.mock('@hooks/useNetwork', () => jest.fn(() => ({isOffline: false}))); -jest.mock('@hooks/usePermissions', () => jest.fn(() => ({isBetaEnabled: jest.fn(() => false)}))); - -const onyxData: Record = {}; - -const mockUseOnyx = jest.fn( - ( - key: string, - options?: { - selector?: (value: unknown) => unknown; - }, - ) => { - const value = onyxData[key]; - const selectedValue = options?.selector ? options.selector(value as never) : value; - return [selectedValue]; - }, -); - -jest.mock('@hooks/useOnyx', () => ({ - __esModule: true, - default: (key: string, options?: {selector?: (value: unknown) => unknown}) => mockUseOnyx(key, options), -})); - -const mockUseMappedPolicies = jest.fn(() => [onyxData[ONYXKEYS.COLLECTION.POLICY], {}]); - -jest.mock('@hooks/useMappedPolicies', () => ({ - __esModule: true, - default: () => mockUseMappedPolicies(), -})); - -describe('useSearchTypeMenuSections', () => { - beforeEach(() => { - onyxData[ONYXKEYS.COLLECTION.POLICY] = {}; - onyxData[ONYXKEYS.SESSION] = {email: 'test@example.com', accountID: 1}; - onyxData[ONYXKEYS.SAVED_SEARCHES] = {}; - onyxData[ONYXKEYS.COLLECTION.REPORT] = {}; - - mockUseOnyx.mockClear(); - }); - - it('shows suggested search skeleton when policies are missing employeeList', () => { - onyxData[ONYXKEYS.COLLECTION.POLICY] = { - policy1: { - id: 'policy1', - employeeList: undefined, - exporter: 'test@gmail.com', - }, - }; - - const {result} = renderHook(() => useSearchTypeMenuSections()); - - expect(result.current.shouldShowSuggestedSearchSkeleton).toBe(true); - }); - - it('shows suggested search skeleton when policies are missing exporter', () => { - onyxData[ONYXKEYS.COLLECTION.POLICY] = { - policy1: { - id: 'policy1', - employeeList: {'test@gmail.com': {accountID: 10000}}, - exporter: undefined, - }, - }; - - const {result} = renderHook(() => useSearchTypeMenuSections()); - - expect(result.current.shouldShowSuggestedSearchSkeleton).toBe(true); - }); - - it('hides suggested search skeleton when at least one policy has required data', () => { - onyxData[ONYXKEYS.COLLECTION.POLICY] = { - policy1: { - id: 'policy1', - employeeList: {'test@gmail.com': {accountID: 10000}}, - exporter: '', - }, - policy2: { - id: 'policy2', - employeeList: undefined, - exporter: undefined, - }, - }; - - const {result} = renderHook(() => useSearchTypeMenuSections()); - - expect(result.current.shouldShowSuggestedSearchSkeleton).toBe(false); - }); - - it('does not show suggested search skeleton when offline', () => { - (useNetwork as jest.Mock).mockReturnValue({ - isOffline: true, - }); - const {result} = renderHook(() => useSearchTypeMenuSections()); - - expect(result.current.shouldShowSuggestedSearchSkeleton).toBe(false); - }); -});