From 830ac47fc05e52a975b7a4a1316f12cec622adf7 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Mon, 30 Sep 2024 16:00:08 +0200 Subject: [PATCH 01/15] Remove old chat finder functionality --- src/SCREENS.ts | 1 - src/libs/E2E/reactNativeLaunchingTest.ts | 1 - .../Navigators/LeftModalNavigator.tsx | 5 - .../ChatFinderPage/ChatFinderPageFooter.tsx | 11 - src/pages/ChatFinderPage/index.tsx | 209 ---------------- tests/e2e/config.ts | 4 - tests/perf-test/ChatFinderPage.perf-test.tsx | 232 ------------------ 7 files changed, 463 deletions(-) delete mode 100644 src/pages/ChatFinderPage/ChatFinderPageFooter.tsx delete mode 100644 src/pages/ChatFinderPage/index.tsx delete mode 100644 tests/perf-test/ChatFinderPage.perf-test.tsx diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 9a94d612dc80..8d363e6317c1 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -141,7 +141,6 @@ const SCREENS = { ROOT: 'SaveTheWorld_Root', }, LEFT_MODAL: { - CHAT_FINDER: 'ChatFinder', WORKSPACE_SWITCHER: 'WorkspaceSwitcher', }, RIGHT_MODAL: { diff --git a/src/libs/E2E/reactNativeLaunchingTest.ts b/src/libs/E2E/reactNativeLaunchingTest.ts index f952998f0aad..21cb0336c5fb 100644 --- a/src/libs/E2E/reactNativeLaunchingTest.ts +++ b/src/libs/E2E/reactNativeLaunchingTest.ts @@ -32,7 +32,6 @@ if (!appInstanceId) { // import your test here, define its name and config first in e2e/config.js const tests: Tests = { [E2EConfig.TEST_NAMES.AppStartTime]: require('./tests/appStartTimeTest.e2e').default, - [E2EConfig.TEST_NAMES.OpenChatFinderPage]: require('./tests/openChatFinderPageTest.e2e').default, [E2EConfig.TEST_NAMES.ChatOpening]: require('./tests/chatOpeningTest.e2e').default, [E2EConfig.TEST_NAMES.ReportTyping]: require('./tests/reportTypingTest.e2e').default, [E2EConfig.TEST_NAMES.Linking]: require('./tests/linkingTest.e2e').default, diff --git a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx index 077bdce94545..50439c19845e 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx @@ -14,7 +14,6 @@ import Overlay from './Overlay'; type LeftModalNavigatorProps = StackScreenProps; -const loadChatFinder = () => require('../../../../pages/ChatFinderPage').default; const loadWorkspaceSwitcherPage = () => require('../../../../pages/WorkspaceSwitcherPage').default; const Stack = createStackNavigator(); @@ -37,10 +36,6 @@ function LeftModalNavigator({navigation}: LeftModalNavigatorProps) { screenOptions={screenOptions} id={NAVIGATORS.LEFT_MODAL_NAVIGATOR} > - ; -} - -ChatFinderPageFooter.displayName = 'ChatFinderPageFooter'; - -export default ChatFinderPageFooter; diff --git a/src/pages/ChatFinderPage/index.tsx b/src/pages/ChatFinderPage/index.tsx deleted file mode 100644 index aabf881a8bed..000000000000 --- a/src/pages/ChatFinderPage/index.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import type {StackScreenProps} from '@react-navigation/stack'; -import isEmpty from 'lodash/isEmpty'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import {useOptionsList} from '@components/OptionListContextProvider'; -import ScreenWrapper from '@components/ScreenWrapper'; -import SelectionList from '@components/SelectionList'; -import UserListItem from '@components/SelectionList/UserListItem'; -import useCancelSearchOnModalClose from '@hooks/useCancelSearchOnModalClose'; -import useDebouncedState from '@hooks/useDebouncedState'; -import useDismissedReferralBanners from '@hooks/useDismissedReferralBanners'; -import useLocalize from '@hooks/useLocalize'; -import useNetwork from '@hooks/useNetwork'; -import Navigation from '@libs/Navigation/Navigation'; -import type {RootStackParamList} from '@libs/Navigation/types'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import Performance from '@libs/Performance'; -import type {OptionData} from '@libs/ReportUtils'; -import * as Report from '@userActions/Report'; -import Timing from '@userActions/Timing'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import type SCREENS from '@src/SCREENS'; -import type * as OnyxTypes from '@src/types/onyx'; -import ChatFinderPageFooter from './ChatFinderPageFooter'; - -type ChatFinderPageOnyxProps = { - /** Beta features list */ - betas: OnyxEntry; - - /** Whether or not we are searching for reports on the server */ - isSearchingForReports: OnyxEntry; -}; - -type ChatFinderPageProps = ChatFinderPageOnyxProps & StackScreenProps; - -type ChatFinderPageSectionItem = { - data: OptionData[]; - shouldShow: boolean; -}; - -type ChatFinderPageSectionList = ChatFinderPageSectionItem[]; - -const setPerformanceTimersEnd = () => { - Timing.end(CONST.TIMING.CHAT_FINDER_RENDER); - Performance.markEnd(CONST.TIMING.CHAT_FINDER_RENDER); -}; - -const ChatFinderPageFooterInstance = ; - -function ChatFinderPage({betas, isSearchingForReports, navigation}: ChatFinderPageProps) { - const [isScreenTransitionEnd, setIsScreenTransitionEnd] = useState(false); - const {translate} = useLocalize(); - const {isOffline} = useNetwork(); - const {options, areOptionsInitialized} = useOptionsList({ - shouldInitialize: isScreenTransitionEnd, - }); - - const offlineMessage: string = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; - - const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); - const [, debouncedSearchValueInServer, setSearchValueInServer] = useDebouncedState('', 500); - const updateSearchValue = useCallback( - (value: string) => { - setSearchValue(value); - setSearchValueInServer(value); - }, - [setSearchValue, setSearchValueInServer], - ); - useCancelSearchOnModalClose(); - - useEffect(() => { - Report.searchInServer(debouncedSearchValueInServer.trim()); - }, [debouncedSearchValueInServer]); - - const searchOptions = useMemo(() => { - if (!areOptionsInitialized || !isScreenTransitionEnd) { - return { - recentReports: [], - personalDetails: [], - userToInvite: null, - currentUserOption: null, - categoryOptions: [], - tagOptions: [], - taxRatesOptions: [], - headerMessage: '', - }; - } - const optionList = OptionsListUtils.getSearchOptions(options, '', betas ?? []); - const header = OptionsListUtils.getHeaderMessage(optionList.recentReports.length + optionList.personalDetails.length !== 0, !!optionList.userToInvite, ''); - return {...optionList, headerMessage: header}; - }, [areOptionsInitialized, betas, isScreenTransitionEnd, options]); - - const filteredOptions = useMemo(() => { - if (debouncedSearchValue.trim() === '') { - return { - recentReports: [], - personalDetails: [], - userToInvite: null, - headerMessage: '', - }; - } - - Timing.start(CONST.TIMING.SEARCH_FILTER_OPTIONS); - const newOptions = OptionsListUtils.filterOptions(searchOptions, debouncedSearchValue, {sortByReportTypeInSearch: true, preferChatroomsOverThreads: true}); - Timing.end(CONST.TIMING.SEARCH_FILTER_OPTIONS); - - const header = OptionsListUtils.getHeaderMessage(newOptions.recentReports.length + Number(!!newOptions.userToInvite) > 0, false, debouncedSearchValue); - return { - recentReports: newOptions.recentReports, - personalDetails: newOptions.personalDetails, - userToInvite: newOptions.userToInvite, - headerMessage: header, - }; - }, [debouncedSearchValue, searchOptions]); - - const {recentReports, personalDetails: localPersonalDetails, userToInvite, headerMessage} = debouncedSearchValue.trim() !== '' ? filteredOptions : searchOptions; - - const sections = useMemo((): ChatFinderPageSectionList => { - const newSections: ChatFinderPageSectionList = []; - - if (recentReports?.length > 0) { - newSections.push({ - data: recentReports, - shouldShow: true, - }); - } - - if (localPersonalDetails.length > 0) { - newSections.push({ - data: localPersonalDetails, - shouldShow: true, - }); - } - - if (!isEmpty(userToInvite)) { - newSections.push({ - data: [userToInvite], - shouldShow: true, - }); - } - - return newSections; - }, [localPersonalDetails, recentReports, userToInvite]); - - const selectReport = (option: OptionData) => { - if (!option) { - return; - } - - if (option.reportID) { - Navigation.closeAndNavigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID)); - } else { - Report.navigateToAndOpenReport(option.login ? [option.login] : []); - } - }; - - const handleScreenTransitionEnd = () => { - setIsScreenTransitionEnd(true); - }; - - const {isDismissed} = useDismissedReferralBanners({referralContentType: CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND}); - - return ( - - - - sections={areOptionsInitialized ? sections : CONST.EMPTY_ARRAY} - ListItem={UserListItem} - textInputValue={searchValue} - textInputLabel={translate('selectionList.nameEmailOrPhoneNumber')} - textInputHint={offlineMessage} - onChangeText={updateSearchValue} - headerMessage={headerMessage} - onLayout={setPerformanceTimersEnd} - onSelectRow={selectReport} - shouldSingleExecuteRowSelect - showLoadingPlaceholder={!areOptionsInitialized || !isScreenTransitionEnd} - footerContent={!isDismissed && ChatFinderPageFooterInstance} - isLoadingNewOptions={!!isSearchingForReports} - shouldDelayFocus={false} - /> - - ); -} - -ChatFinderPage.displayName = 'ChatFinderPage'; - -export default withOnyx({ - betas: { - key: ONYXKEYS.BETAS, - }, - isSearchingForReports: { - key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, - initWithStoredValues: false, - }, -})(ChatFinderPage); diff --git a/tests/e2e/config.ts b/tests/e2e/config.ts index bdb24bee0bc5..8b14fb8de7a0 100644 --- a/tests/e2e/config.ts +++ b/tests/e2e/config.ts @@ -4,7 +4,6 @@ const OUTPUT_DIR = process.env.WORKING_DIRECTORY || './tests/e2e/results'; // add your test name here … const TEST_NAMES = { AppStartTime: 'App start time', - OpenChatFinderPage: 'Open chat finder page TTI', ReportTyping: 'Report typing', ChatOpening: 'Chat opening', Linking: 'Linking', @@ -73,9 +72,6 @@ export default { name: TEST_NAMES.AppStartTime, // ... any additional config you might need }, - [TEST_NAMES.OpenChatFinderPage]: { - name: TEST_NAMES.OpenChatFinderPage, - }, [TEST_NAMES.ReportTyping]: { name: TEST_NAMES.ReportTyping, reportScreen: { diff --git a/tests/perf-test/ChatFinderPage.perf-test.tsx b/tests/perf-test/ChatFinderPage.perf-test.tsx deleted file mode 100644 index 4346977a1cd0..000000000000 --- a/tests/perf-test/ChatFinderPage.perf-test.tsx +++ /dev/null @@ -1,232 +0,0 @@ -import type * as NativeNavigation from '@react-navigation/native'; -import type {StackNavigationProp, StackScreenProps} from '@react-navigation/stack'; -import {fireEvent, screen} from '@testing-library/react-native'; -import React, {useMemo} from 'react'; -import type {ComponentType} from 'react'; -import Onyx from 'react-native-onyx'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {measurePerformance} from 'reassure'; -import {LocaleContextProvider} from '@components/LocaleContextProvider'; -import OptionListContextProvider, {OptionsListContext} from '@components/OptionListContextProvider'; -import {KeyboardStateProvider} from '@components/withKeyboardState'; -import type {WithNavigationFocusProps} from '@components/withNavigationFocus'; -import type {RootStackParamList} from '@libs/Navigation/types'; -import {createOptionList} from '@libs/OptionsListUtils'; -import ChatFinderPage from '@pages/ChatFinderPage'; -import ComposeProviders from '@src/components/ComposeProviders'; -import OnyxProvider from '@src/components/OnyxProvider'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type SCREENS from '@src/SCREENS'; -import type {Beta, PersonalDetails, Report} from '@src/types/onyx'; -import createCollection from '../utils/collections/createCollection'; -import createPersonalDetails from '../utils/collections/personalDetails'; -import createRandomReport from '../utils/collections/reports'; -import createAddListenerMock from '../utils/createAddListenerMock'; -import * as TestHelper from '../utils/TestHelper'; -import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; -import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; - -jest.mock('lodash/debounce', () => - jest.fn((fn: Record) => { - // eslint-disable-next-line no-param-reassign - fn.cancel = jest.fn(); - return fn; - }), -); - -jest.mock('@src/libs/Log'); - -jest.mock('@src/libs/API', () => ({ - write: jest.fn(), - makeRequestWithSideEffects: jest.fn(), - read: jest.fn(), -})); - -jest.mock('@src/libs/Navigation/Navigation', () => ({ - dismissModalWithReport: jest.fn(), - getTopmostReportId: jest.fn(), - isNavigationReady: jest.fn(() => Promise.resolve()), - isDisplayedInModal: jest.fn(() => false), -})); - -jest.mock('@react-navigation/native', () => { - const actualNav = jest.requireActual('@react-navigation/native'); - return { - ...actualNav, - useFocusEffect: jest.fn(), - useIsFocused: () => true, - useRoute: () => jest.fn(), - useNavigation: () => ({ - navigate: jest.fn(), - addListener: () => jest.fn(), - }), - createNavigationContainerRef: () => ({ - addListener: () => jest.fn(), - removeListener: () => jest.fn(), - isReady: () => jest.fn(), - getCurrentRoute: () => jest.fn(), - getState: () => jest.fn(), - }), - }; -}); - -jest.mock('@src/components/withNavigationFocus', () => (Component: ComponentType) => { - function WithNavigationFocus(props: WithNavigationFocusProps) { - return ( - - ); - } - - WithNavigationFocus.displayName = 'WithNavigationFocus'; - - return WithNavigationFocus; -}); -// mock of useDismissedReferralBanners -jest.mock('../../src/hooks/useDismissedReferralBanners', () => ({ - // eslint-disable-next-line @typescript-eslint/naming-convention - __esModule: true, - default: jest.fn(() => ({ - isDismissed: false, - setAsDismissed: () => {}, - })), -})); - -const getMockedReports = (length = 100) => - createCollection( - (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, - (index) => createRandomReport(index), - length, - ); - -const getMockedPersonalDetails = (length = 100) => - createCollection( - (item) => item.accountID, - (index) => createPersonalDetails(index), - length, - ); - -const mockedReports = getMockedReports(600); -const mockedBetas = Object.values(CONST.BETAS); -const mockedPersonalDetails = getMockedPersonalDetails(100); -const mockedOptions = createOptionList(mockedPersonalDetails, mockedReports); - -beforeAll(() => - Onyx.init({ - keys: ONYXKEYS, - safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT], - }), -); - -// Initialize the network key for OfflineWithFeedback -beforeEach(() => { - global.fetch = TestHelper.getGlobalFetchMock(); - wrapOnyxWithWaitForBatchedUpdates(Onyx); - Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); -}); - -// Clear out Onyx after each test so that each test starts with a clean state -afterEach(() => { - Onyx.clear(); -}); - -type ChatFinderPageProps = StackScreenProps & { - betas?: OnyxEntry; - reports?: OnyxCollection; - isSearchingForReports?: OnyxEntry; -}; - -function ChatFinderPageWrapper(args: ChatFinderPageProps) { - return ( - - - - - - ); -} - -function ChatFinderPageWithCachedOptions(args: ChatFinderPageProps) { - return ( - - ({options: mockedOptions, initializeOptions: () => {}, areOptionsInitialized: true}), [])}> - - - - ); -} - -test('[ChatFinderPage] should render list with cached options', async () => { - const {addListener} = createAddListenerMock(); - - const scenario = async () => { - await screen.findByTestId('ChatFinderPage'); - }; - - const navigation = {addListener} as unknown as StackNavigationProp; - - return waitForBatchedUpdates() - .then(() => - Onyx.multiSet({ - ...mockedReports, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, - [ONYXKEYS.BETAS]: mockedBetas, - [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, - }), - ) - .then(() => - measurePerformance( - , - {scenario}, - ), - ); -}); - -test('[ChatFinderPage] should interact when text input changes', async () => { - const {addListener} = createAddListenerMock(); - - const scenario = async () => { - await screen.findByTestId('ChatFinderPage'); - - const input = screen.getByTestId('selection-list-text-input'); - fireEvent.changeText(input, 'Email Four'); - fireEvent.changeText(input, 'Report'); - fireEvent.changeText(input, 'Email Five'); - }; - - const navigation = {addListener} as unknown as StackNavigationProp; - - return waitForBatchedUpdates() - .then(() => - Onyx.multiSet({ - ...mockedReports, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, - [ONYXKEYS.BETAS]: mockedBetas, - [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, - }), - ) - .then(() => - measurePerformance( - , - {scenario}, - ), - ); -}); From 4052f9a4db738cce4afbe8573a33f838db2afe59 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Wed, 2 Oct 2024 13:06:17 +0200 Subject: [PATCH 02/15] Use SearchRouter instead of Chat finder and migrate e2e tests --- src/CONST.ts | 2 +- src/ROUTES.ts | 1 - .../Search/SearchRouter/SearchButton.tsx | 12 +- src/libs/E2E/reactNativeLaunchingTest.ts | 2 + ...est.e2e.ts => openSearchRouterTest.e2e.ts} | 17 +- .../Navigation/AppNavigator/AuthScreens.tsx | 10 +- .../createCustomBottomTabNavigator/TopBar.tsx | 33 +-- src/libs/Navigation/linkingConfig/config.ts | 1 - src/libs/Navigation/types.ts | 1 - src/pages/Search/SearchPageBottomTab.tsx | 7 +- .../SidebarScreen/BaseSidebarScreen.tsx | 5 + tests/e2e/config.ts | 4 + tests/perf-test/ChatFinderPage.perf-test.tsx | 217 ++++++++++++++++++ 13 files changed, 259 insertions(+), 53 deletions(-) rename src/libs/E2E/tests/{openChatFinderPageTest.e2e.ts => openSearchRouterTest.e2e.ts} (79%) create mode 100644 tests/perf-test/ChatFinderPage.perf-test.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 9a6bf21db303..0b130ae7564b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1084,7 +1084,7 @@ const CONST = { }, TIMING: { CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action', - CHAT_FINDER_RENDER: 'search_render', + SEARCH_ROUTER_OPEN: 'search_router_render', CHAT_RENDER: 'chat_render', OPEN_REPORT: 'open_report', HOMEPAGE_INITIAL_RENDER: 'homepage_initial_render', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 9c429dd3e909..d7e6b37a60fd 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -74,7 +74,6 @@ const ROUTES = { route: 'flag/:reportID/:reportActionID', getRoute: (reportID: string, reportActionID: string, backTo?: string) => getUrlWithBackToParam(`flag/${reportID}/${reportActionID}` as const, backTo), }, - CHAT_FINDER: 'chat-finder', PROFILE: { route: 'a/:accountID', getRoute: (accountID?: string | number, backTo?: string, login?: string) => { diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx index 05693ad5ea22..53660ee1f6c2 100644 --- a/src/components/Search/SearchRouter/SearchButton.tsx +++ b/src/components/Search/SearchRouter/SearchButton.tsx @@ -5,7 +5,11 @@ import {PressableWithoutFeedback} from '@components/Pressable'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import Performance from '@libs/Performance'; import Permissions from '@libs/Permissions'; +import * as Session from '@userActions/Session'; +import Timing from '@userActions/Timing'; +import CONST from '@src/CONST'; import {useSearchRouterContext} from './SearchRouterContext'; function SearchButton() { @@ -22,9 +26,13 @@ function SearchButton() { { + onPress={Session.checkIfActionIsAllowed(() => { + // Todo [Search] add finishing for this timing in + Timing.start(CONST.TIMING.SEARCH_ROUTER_OPEN); + Performance.markStart(CONST.TIMING.SEARCH_ROUTER_OPEN); + openSearchRouter(); - }} + })} > ('./tests/appStartTimeTest.e2e').default, + // Todo [Search] rename + [E2EConfig.TEST_NAMES.OpenSearchRouter]: require('./tests/openSearchRouterTest.e2e').default, [E2EConfig.TEST_NAMES.ChatOpening]: require('./tests/chatOpeningTest.e2e').default, [E2EConfig.TEST_NAMES.ReportTyping]: require('./tests/reportTypingTest.e2e').default, [E2EConfig.TEST_NAMES.Linking]: require('./tests/linkingTest.e2e').default, diff --git a/src/libs/E2E/tests/openChatFinderPageTest.e2e.ts b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts similarity index 79% rename from src/libs/E2E/tests/openChatFinderPageTest.e2e.ts rename to src/libs/E2E/tests/openSearchRouterTest.e2e.ts index 2c2f2eda4efe..6ce1891a63d4 100644 --- a/src/libs/E2E/tests/openChatFinderPageTest.e2e.ts +++ b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts @@ -3,14 +3,12 @@ import E2ELogin from '@libs/E2E/actions/e2eLogin'; import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; import getPromiseWithResolve from '@libs/E2E/utils/getPromiseWithResolve'; -import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; const test = () => { // check for login (if already logged in the action will simply resolve) - console.debug('[E2E] Logging in for chat finder'); + console.debug('[E2E] Logging in for new search router'); E2ELogin().then((neededLogin: boolean): Promise | undefined => { if (neededLogin) { @@ -20,7 +18,7 @@ const test = () => { ); } - console.debug('[E2E] Logged in, getting chat finder metrics and submitting them…'); + console.debug('[E2E] Logged in, getting search router metrics and submitting them…'); const [openSearchPagePromise, openSearchPageResolve] = getPromiseWithResolve(); const [loadSearchOptionsPromise, loadSearchOptionsResolve] = getPromiseWithResolve(); @@ -32,19 +30,12 @@ const test = () => { }); Performance.subscribeToMeasurements((entry) => { - if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { - console.debug(`[E2E] Sidebar loaded, navigating to chat finder route…`); - Performance.markStart(CONST.TIMING.CHAT_FINDER_RENDER); - Navigation.navigate(ROUTES.CHAT_FINDER); - return; - } - console.debug(`[E2E] Entry: ${JSON.stringify(entry)}`); - if (entry.name === CONST.TIMING.CHAT_FINDER_RENDER) { + if (entry.name === CONST.TIMING.SEARCH_ROUTER_OPEN) { E2EClient.submitTestResults({ branch: Config.E2E_BRANCH, - name: 'Open Chat Finder Page TTI', + name: 'Open Search Router TTI', metric: entry.duration, unit: 'ms', }) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index f5f35fd21025..62be199ee183 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -8,6 +8,7 @@ import ComposeProviders from '@components/ComposeProviders'; import OptionsListContextProvider from '@components/OptionListContextProvider'; import {SearchContextProvider} from '@components/Search/SearchContext'; import SearchRouterModal from '@components/Search/SearchRouter/SearchRouterModal'; +import {useSearchRouterContext} from '@components/Search/SearchRouter/SearchRouterContext'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useOnboardingFlowRouter from '@hooks/useOnboardingFlow'; import usePermissions from '@hooks/usePermissions'; @@ -233,6 +234,8 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie const screenOptions = getRootNavigatorScreenOptions(shouldUseNarrowLayout, styles, StyleUtils); const {canUseDefaultRooms} = usePermissions(); const {activeWorkspaceID} = useActiveWorkspace(); + const {openSearchRouter} = useSearchRouterContext(); + const onboardingModalScreenOptions = useMemo(() => screenOptions.onboardingModalNavigator(onboardingIsMediumOrLargerScreenWidth), [screenOptions, onboardingIsMediumOrLargerScreenWidth]); const onboardingScreenOptions = useMemo( () => getOnboardingModalScreenOptions(shouldUseNarrowLayout, styles, StyleUtils, onboardingIsMediumOrLargerScreenWidth), @@ -241,6 +244,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie const modal = useRef({}); const [didPusherInit, setDidPusherInit] = useState(false); const {isOnboardingCompleted} = useOnboardingFlowRouter(); + let initialReportID: string | undefined; const isInitialRender = useRef(true); if (isInitialRender.current) { @@ -363,13 +367,15 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie ); // Listen for the key K being pressed so that focus can be given to - // the chat switcher, or new group chat + // Search Router, or new group chat // based on the key modifiers pressed and the operating system const unsubscribeSearchShortcut = KeyboardShortcut.subscribe( searchShortcutConfig.shortcutKey, () => { Modal.close( - Session.checkIfActionIsAllowed(() => Navigation.navigate(ROUTES.CHAT_FINDER)), + Session.checkIfActionIsAllowed(() => { + openSearchRouter(); + }), true, true, ); diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx index 4684eb9637be..8967486165f8 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx @@ -2,32 +2,25 @@ import React from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Breadcrumbs from '@components/Breadcrumbs'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; import {PressableWithoutFeedback} from '@components/Pressable'; import SearchButton from '@components/Search/SearchRouter/SearchButton'; import Text from '@components/Text'; -import Tooltip from '@components/Tooltip'; import WorkspaceSwitcherButton from '@components/WorkspaceSwitcherButton'; import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import Performance from '@libs/Performance'; import * as SearchUtils from '@libs/SearchUtils'; import SignInButton from '@pages/home/sidebar/SignInButton'; import * as Session from '@userActions/Session'; -import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -type TopBarProps = {breadcrumbLabel: string; activeWorkspaceID?: string; shouldDisplaySearch?: boolean; isCustomSearchQuery?: boolean; shouldDisplaySearchRouter?: boolean}; +type TopBarProps = {breadcrumbLabel: string; activeWorkspaceID?: string; shouldDisplaySearch?: boolean; shouldDisplayCancelSearch?: boolean}; -function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, isCustomSearchQuery = false, shouldDisplaySearchRouter = false}: TopBarProps) { +function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, shouldDisplayCancelSearch = false}: TopBarProps) { const styles = useThemeStyles(); - const theme = useTheme(); const {translate} = useLocalize(); const policy = usePolicy(activeWorkspaceID); const [session] = useOnyx(ONYXKEYS.SESSION, {selector: (sessionValue) => sessionValue && {authTokenType: sessionValue.authTokenType}}); @@ -63,7 +56,7 @@ function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, {displaySignIn && } - {isCustomSearchQuery && ( + {shouldDisplayCancelSearch && ( {translate('common.cancel')} )} - {shouldDisplaySearchRouter && } - {displaySearch && ( - - { - Timing.start(CONST.TIMING.CHAT_FINDER_RENDER); - Performance.markStart(CONST.TIMING.CHAT_FINDER_RENDER); - Navigation.navigate(ROUTES.CHAT_FINDER); - })} - > - - - - )} + {displaySearch && } ); diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 114e89ff2bf5..980b28c3d635 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -76,7 +76,6 @@ const config: LinkingOptions['config'] = { [SCREENS.NOT_FOUND]: '*', [NAVIGATORS.LEFT_MODAL_NAVIGATOR]: { screens: { - [SCREENS.LEFT_MODAL.CHAT_FINDER]: ROUTES.CHAT_FINDER, [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: { path: ROUTES.WORKSPACE_SWITCHER, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index b698681966e2..ecd2895053f1 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1236,7 +1236,6 @@ type TransactionDuplicateNavigatorParamList = { }; type LeftModalNavigatorParamList = { - [SCREENS.LEFT_MODAL.CHAT_FINDER]: undefined; [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: undefined; }; diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx index 38e4c5166884..b4fb2aac61ff 100644 --- a/src/pages/Search/SearchPageBottomTab.tsx +++ b/src/pages/Search/SearchPageBottomTab.tsx @@ -85,6 +85,8 @@ function SearchPageBottomTab() { ); } + const shouldDisplayCancelSearch = shouldUseNarrowLayout && !SearchUtils.isCannedSearchQuery(queryJSON); + return ( diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index edc8dfb3cb3a..e77f2000b85f 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -4,6 +4,7 @@ import {useOnyx} from 'react-native-onyx'; import ScreenWrapper from '@components/ScreenWrapper'; import useActiveWorkspaceFromNavigationState from '@hooks/useActiveWorkspaceFromNavigationState'; import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {updateLastAccessedWorkspace} from '@libs/actions/Policy/Policy'; import * as Browser from '@libs/Browser'; @@ -27,6 +28,7 @@ function BaseSidebarScreen() { const styles = useThemeStyles(); const activeWorkspaceID = useActiveWorkspaceFromNavigationState(); const {translate} = useLocalize(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); const [activeWorkspace] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID ?? -1}`); useEffect(() => { @@ -43,6 +45,8 @@ function BaseSidebarScreen() { updateLastAccessedWorkspace(undefined); }, [activeWorkspace, activeWorkspaceID]); + const shouldDisplaySearch = shouldUseNarrowLayout; + return ( + jest.fn((fn: Record) => { + // eslint-disable-next-line no-param-reassign + fn.cancel = jest.fn(); + return fn; + }), +); + +jest.mock('@src/libs/Log'); + +jest.mock('@src/libs/API', () => ({ + write: jest.fn(), + makeRequestWithSideEffects: jest.fn(), + read: jest.fn(), +})); + +jest.mock('@src/libs/Navigation/Navigation', () => ({ + dismissModalWithReport: jest.fn(), + getTopmostReportId: jest.fn(), + isNavigationReady: jest.fn(() => Promise.resolve()), + isDisplayedInModal: jest.fn(() => false), +})); + +jest.mock('@react-navigation/native', () => { + const actualNav = jest.requireActual('@react-navigation/native'); + return { + ...actualNav, + useFocusEffect: jest.fn(), + useIsFocused: () => true, + useRoute: () => jest.fn(), + useNavigation: () => ({ + navigate: jest.fn(), + addListener: () => jest.fn(), + }), + createNavigationContainerRef: () => ({ + addListener: () => jest.fn(), + removeListener: () => jest.fn(), + isReady: () => jest.fn(), + getCurrentRoute: () => jest.fn(), + getState: () => jest.fn(), + }), + }; +}); + +jest.mock('@src/components/withNavigationFocus', () => (Component: ComponentType) => { + function WithNavigationFocus(props: WithNavigationFocusProps) { + return ( + + ); + } + + WithNavigationFocus.displayName = 'WithNavigationFocus'; + + return WithNavigationFocus; +}); +// mock of useDismissedReferralBanners +jest.mock('../../src/hooks/useDismissedReferralBanners', () => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + __esModule: true, + default: jest.fn(() => ({ + isDismissed: false, + setAsDismissed: () => {}, + })), +})); + +const getMockedReports = (length = 100) => + createCollection( + (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, + (index) => createRandomReport(index), + length, + ); + +const getMockedPersonalDetails = (length = 100) => + createCollection( + (item) => item.accountID, + (index) => createPersonalDetails(index), + length, + ); + +const mockedReports = getMockedReports(600); +const mockedBetas = Object.values(CONST.BETAS); +const mockedPersonalDetails = getMockedPersonalDetails(100); +const mockedOptions = createOptionList(mockedPersonalDetails, mockedReports); + +beforeAll(() => + Onyx.init({ + keys: ONYXKEYS, + safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT], + }), +); + +// Initialize the network key for OfflineWithFeedback +beforeEach(() => { + global.fetch = TestHelper.getGlobalFetchMock(); + wrapOnyxWithWaitForBatchedUpdates(Onyx); + Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); +}); + +// Clear out Onyx after each test so that each test starts with a clean state +afterEach(() => { + Onyx.clear(); +}); + +function ChatFinderPageWrapper(args: any) { + return ( + + + + {/* */} + + + ); +} + +function ChatFinderPageWithCachedOptions(args: any) { + return ( + + ({options: mockedOptions, initializeOptions: () => {}, areOptionsInitialized: true}), [])}> + {/* */} + + + ); +} + +test('[ChatFinderPage] should render list with cached options', async () => { + const {addListener} = createAddListenerMock(); + + const scenario = async () => { + await screen.findByTestId('ChatFinderPage'); // Todo [Search] fix testID no longer existing + }; + + // const navigation = {addListener} as unknown as StackNavigationProp; + + return waitForBatchedUpdates() + .then(() => + Onyx.multiSet({ + ...mockedReports, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, + [ONYXKEYS.BETAS]: mockedBetas, + [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, + }), + ) + .then(() => measureRenders(, {scenario})); +}); + +test('[ChatFinderPage] should interact when text input changes', async () => { + const {addListener} = createAddListenerMock(); + + const scenario = async () => { + await screen.findByTestId('ChatFinderPage'); + + const input = screen.getByTestId('selection-list-text-input'); + fireEvent.changeText(input, 'Email Four'); + fireEvent.changeText(input, 'Report'); + fireEvent.changeText(input, 'Email Five'); + }; + + // const navigation = {addListener} as unknown as StackNavigationProp; + + return waitForBatchedUpdates() + .then(() => + Onyx.multiSet({ + ...mockedReports, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, + [ONYXKEYS.BETAS]: mockedBetas, + [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, + }), + ) + .then(() => + measureRenders( + , + {scenario}, + ), + ); +}); From cb51d04588f2c442d9b18b1223104d10f73474b7 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Thu, 3 Oct 2024 16:55:14 +0200 Subject: [PATCH 03/15] Add performance timings handling for SearchRouterList --- src/components/Search/SearchRouter/SearchButton.tsx | 1 - src/components/Search/SearchRouter/SearchRouter.tsx | 3 +++ src/components/Search/SearchRouter/SearchRouterList.tsx | 9 +++++++++ src/libs/E2E/tests/openSearchRouterTest.e2e.ts | 6 +++--- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx index 53660ee1f6c2..b1e59a323926 100644 --- a/src/components/Search/SearchRouter/SearchButton.tsx +++ b/src/components/Search/SearchRouter/SearchButton.tsx @@ -27,7 +27,6 @@ function SearchButton() { accessibilityLabel={translate('common.search')} style={[styles.flexRow, styles.touchableButtonImage]} onPress={Session.checkIfActionIsAllowed(() => { - // Todo [Search] add finishing for this timing in Timing.start(CONST.TIMING.SEARCH_ROUTER_OPEN); Performance.markStart(CONST.TIMING.SEARCH_ROUTER_OPEN); diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 8f5ad55bc0c9..868144492ce7 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -19,6 +19,7 @@ import * as SearchUtils from '@libs/SearchUtils'; import Navigation from '@navigation/Navigation'; import variables from '@styles/variables'; import * as Report from '@userActions/Report'; +import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -65,7 +66,9 @@ function SearchRouter() { }; } + Timing.start(CONST.TIMING.SEARCH_FILTER_OPTIONS); const newOptions = OptionsListUtils.filterOptions(searchOptions, debouncedInputValue, {sortByReportTypeInSearch: true, preferChatroomsOverThreads: true}); + Timing.end(CONST.TIMING.SEARCH_FILTER_OPTIONS); return { recentReports: newOptions.recentReports, diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index 7d86ce1150d5..a58ea969d28f 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -13,10 +13,13 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; +import Performance from '@libs/Performance'; import {getAllTaxRates} from '@libs/PolicyUtils'; import type {OptionData} from '@libs/ReportUtils'; import * as SearchUtils from '@libs/SearchUtils'; import * as Report from '@userActions/Report'; +import Timing from '@userActions/Timing'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -47,6 +50,11 @@ type SearchRouterListProps = { closeAndClearRouter: () => void; }; +const setPerformanceTimersEnd = () => { + Timing.end(CONST.TIMING.SEARCH_ROUTER_OPEN); + Performance.markEnd(CONST.TIMING.SEARCH_ROUTER_OPEN); +}; + function isSearchQueryItem(item: OptionData | SearchQueryItem): item is SearchQueryItem { if ('singleIcon' in item && item.singleIcon && 'query' in item && item.query) { return true; @@ -174,6 +182,7 @@ function SearchRouterList( ListItem={SearchRouterItem} containerStyle={[styles.mh100]} sectionListStyle={[isSmallScreenWidth ? styles.ph5 : styles.ph2, styles.pb2]} + onLayout={setPerformanceTimersEnd} ref={ref} showScrollIndicator={!isSmallScreenWidth} /> diff --git a/src/libs/E2E/tests/openSearchRouterTest.e2e.ts b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts index 6ce1891a63d4..6c4646b09fbf 100644 --- a/src/libs/E2E/tests/openSearchRouterTest.e2e.ts +++ b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts @@ -20,10 +20,10 @@ const test = () => { console.debug('[E2E] Logged in, getting search router metrics and submitting them…'); - const [openSearchPagePromise, openSearchPageResolve] = getPromiseWithResolve(); + const [openSearchRouterPromise, openSearchRouterResolve] = getPromiseWithResolve(); const [loadSearchOptionsPromise, loadSearchOptionsResolve] = getPromiseWithResolve(); - Promise.all([openSearchPagePromise, loadSearchOptionsPromise]).then(() => { + Promise.all([openSearchRouterPromise, loadSearchOptionsPromise]).then(() => { console.debug(`[E2E] Submitting!`); E2EClient.submitTestDone(); @@ -40,7 +40,7 @@ const test = () => { unit: 'ms', }) .then(() => { - openSearchPageResolve(); + openSearchRouterResolve(); console.debug('[E2E] Done with search, exiting…'); }) .catch((err) => { From 635e0da1032b297e9834791d4c7200b5852bc625 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Fri, 4 Oct 2024 11:52:05 +0200 Subject: [PATCH 04/15] Migrate reassure tests for ChatFinder to SearchRouter --- .../Search/SearchRouter/SearchRouter.tsx | 34 +++++----- .../Search/SearchRouter/SearchRouterInput.tsx | 1 + .../Search/SearchRouter/SearchRouterModal.tsx | 2 +- src/libs/E2E/reactNativeLaunchingTest.ts | 1 - .../Navigation/AppNavigator/AuthScreens.tsx | 2 +- ...rf-test.tsx => SearchRouter.perf-test.tsx} | 65 +++++-------------- 6 files changed, 35 insertions(+), 70 deletions(-) rename tests/perf-test/{ChatFinderPage.perf-test.tsx => SearchRouter.perf-test.tsx} (72%) diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 868144492ce7..f49112e86c6c 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -23,13 +23,16 @@ import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {useSearchRouterContext} from './SearchRouterContext'; import SearchRouterInput from './SearchRouterInput'; import SearchRouterList from './SearchRouterList'; const SEARCH_DEBOUNCE_DELAY = 150; -function SearchRouter() { +type SearchRouterProps = { + onRouterClose: () => void; +}; + +function SearchRouter({onRouterClose}: SearchRouterProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [betas] = useOnyx(ONYXKEYS.BETAS); @@ -37,7 +40,6 @@ function SearchRouter() { const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false}); const {isSmallScreenWidth} = useResponsiveLayout(); - const {isSearchRouterDisplayed, closeSearchRouter} = useSearchRouterContext(); const listRef = useRef(null); const [textInputValue, debouncedInputValue, setTextInputValue] = useDebouncedState('', 500); @@ -90,15 +92,6 @@ function SearchRouter() { Report.searchInServer(debouncedInputValue.trim()); }, [debouncedInputValue]); - useEffect(() => { - if (!textInputValue && isSearchRouterDisplayed) { - return; - } - listRef.current?.updateAndScrollToFocusedIndex(0); - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isSearchRouterDisplayed]); - const contextualReportData = contextualReportID ? searchOptions.recentReports?.find((option) => option.reportID === contextualReportID) : undefined; const clearUserQuery = () => { @@ -135,40 +128,43 @@ function SearchRouter() { }; const closeAndClearRouter = useCallback(() => { - closeSearchRouter(); + onRouterClose(); clearUserQuery(); // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps - }, [closeSearchRouter]); + }, [onRouterClose]); const onSearchSubmit = useCallback( (query: SearchQueryJSON | undefined) => { if (!query) { return; } - closeSearchRouter(); + onRouterClose(); const queryString = SearchUtils.buildSearchQueryString(query); Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: queryString})); clearUserQuery(); }, // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps - [closeSearchRouter], + [onRouterClose], ); useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ESCAPE, () => { - closeSearchRouter(); + onRouterClose(); clearUserQuery(); }); const modalWidth = isSmallScreenWidth ? styles.w100 : {width: variables.popoverWidth}; return ( - + {isSmallScreenWidth && ( closeSearchRouter()} + onBackButtonPress={() => onRouterClose()} /> )} - {isSearchRouterDisplayed && } + {isSearchRouterDisplayed && } ); } diff --git a/src/libs/E2E/reactNativeLaunchingTest.ts b/src/libs/E2E/reactNativeLaunchingTest.ts index 672d66637ef0..fdd305baf88c 100644 --- a/src/libs/E2E/reactNativeLaunchingTest.ts +++ b/src/libs/E2E/reactNativeLaunchingTest.ts @@ -32,7 +32,6 @@ if (!appInstanceId) { // import your test here, define its name and config first in e2e/config.js const tests: Tests = { [E2EConfig.TEST_NAMES.AppStartTime]: require('./tests/appStartTimeTest.e2e').default, - // Todo [Search] rename [E2EConfig.TEST_NAMES.OpenSearchRouter]: require('./tests/openSearchRouterTest.e2e').default, [E2EConfig.TEST_NAMES.ChatOpening]: require('./tests/chatOpeningTest.e2e').default, [E2EConfig.TEST_NAMES.ReportTyping]: require('./tests/reportTypingTest.e2e').default, diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 62be199ee183..2ec31d8a8688 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -7,8 +7,8 @@ import ActiveGuidesEventListener from '@components/ActiveGuidesEventListener'; import ComposeProviders from '@components/ComposeProviders'; import OptionsListContextProvider from '@components/OptionListContextProvider'; import {SearchContextProvider} from '@components/Search/SearchContext'; -import SearchRouterModal from '@components/Search/SearchRouter/SearchRouterModal'; import {useSearchRouterContext} from '@components/Search/SearchRouter/SearchRouterContext'; +import SearchRouterModal from '@components/Search/SearchRouter/SearchRouterModal'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useOnboardingFlowRouter from '@hooks/useOnboardingFlow'; import usePermissions from '@hooks/usePermissions'; diff --git a/tests/perf-test/ChatFinderPage.perf-test.tsx b/tests/perf-test/SearchRouter.perf-test.tsx similarity index 72% rename from tests/perf-test/ChatFinderPage.perf-test.tsx rename to tests/perf-test/SearchRouter.perf-test.tsx index ed43b699adc4..5be0c93105c3 100644 --- a/tests/perf-test/ChatFinderPage.perf-test.tsx +++ b/tests/perf-test/SearchRouter.perf-test.tsx @@ -2,11 +2,11 @@ import type * as NativeNavigation from '@react-navigation/native'; import {fireEvent, screen} from '@testing-library/react-native'; import React, {useMemo} from 'react'; import type {ComponentType} from 'react'; -import {View} from 'react-native'; import Onyx from 'react-native-onyx'; import {measureRenders} from 'reassure'; import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OptionListContextProvider, {OptionsListContext} from '@components/OptionListContextProvider'; +import SearchRouter from '@components/Search/SearchRouter/SearchRouter'; import {KeyboardStateProvider} from '@components/withKeyboardState'; import type {WithNavigationFocusProps} from '@components/withNavigationFocus'; import {createOptionList} from '@libs/OptionsListUtils'; @@ -23,8 +23,6 @@ import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; -// Todo [Search] Either migrate this test to tests new SearchRouter or remove it completely. - jest.mock('lodash/debounce', () => jest.fn((fn: Record) => { // eslint-disable-next-line no-param-reassign @@ -66,6 +64,9 @@ jest.mock('@react-navigation/native', () => { getCurrentRoute: () => jest.fn(), getState: () => jest.fn(), }), + useNavigationState: () => ({ + routes: [], + }), }; }); @@ -84,15 +85,6 @@ jest.mock('@src/components/withNavigationFocus', () => (Component: ComponentType return WithNavigationFocus; }); -// mock of useDismissedReferralBanners -jest.mock('../../src/hooks/useDismissedReferralBanners', () => ({ - // eslint-disable-next-line @typescript-eslint/naming-convention - __esModule: true, - default: jest.fn(() => ({ - isDismissed: false, - setAsDismissed: () => {}, - })), -})); const getMockedReports = (length = 100) => createCollection( @@ -132,44 +124,33 @@ afterEach(() => { Onyx.clear(); }); -function ChatFinderPageWrapper(args: any) { +const mockOnClose = jest.fn(); + +function SearchRouterWrapper() { return ( - - {/* */} + ); } -function ChatFinderPageWithCachedOptions(args: any) { +function SearchRouterWrapperWithCachedOptions() { return ( ({options: mockedOptions, initializeOptions: () => {}, areOptionsInitialized: true}), [])}> - {/* */} + ); } -test('[ChatFinderPage] should render list with cached options', async () => { - const {addListener} = createAddListenerMock(); - +test('[SearchRouter] should render chat list with cached options', async () => { const scenario = async () => { - await screen.findByTestId('ChatFinderPage'); // Todo [Search] fix testID no longer existing + await screen.findByTestId('SearchRouter'); }; - // const navigation = {addListener} as unknown as StackNavigationProp; - return waitForBatchedUpdates() .then(() => Onyx.multiSet({ @@ -179,23 +160,19 @@ test('[ChatFinderPage] should render list with cached options', async () => { [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, }), ) - .then(() => measureRenders(, {scenario})); + .then(() => measureRenders(, {scenario})); }); -test('[ChatFinderPage] should interact when text input changes', async () => { - const {addListener} = createAddListenerMock(); - +test('[SearchRouter] should react to text input changes', async () => { const scenario = async () => { - await screen.findByTestId('ChatFinderPage'); + await screen.findByTestId('SearchRouter'); - const input = screen.getByTestId('selection-list-text-input'); + const input = screen.getByTestId('search-router-text-input'); fireEvent.changeText(input, 'Email Four'); fireEvent.changeText(input, 'Report'); fireEvent.changeText(input, 'Email Five'); }; - // const navigation = {addListener} as unknown as StackNavigationProp; - return waitForBatchedUpdates() .then(() => Onyx.multiSet({ @@ -205,13 +182,5 @@ test('[ChatFinderPage] should interact when text input changes', async () => { [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, }), ) - .then(() => - measureRenders( - , - {scenario}, - ), - ); + .then(() => measureRenders(, {scenario})); }); From 078420e53f8500f1e75fef3cfde10d64f2e7e8d5 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Fri, 4 Oct 2024 12:15:39 +0200 Subject: [PATCH 05/15] Remove SearchRouter dev check --- src/components/Search/SearchRouter/SearchButton.tsx | 5 ----- src/libs/Permissions.ts | 13 ------------- tests/perf-test/SearchRouter.perf-test.tsx | 1 - 3 files changed, 19 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx index b1e59a323926..2ddc9a8262c2 100644 --- a/src/components/Search/SearchRouter/SearchButton.tsx +++ b/src/components/Search/SearchRouter/SearchButton.tsx @@ -6,7 +6,6 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Performance from '@libs/Performance'; -import Permissions from '@libs/Permissions'; import * as Session from '@userActions/Session'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; @@ -18,10 +17,6 @@ function SearchButton() { const {translate} = useLocalize(); const {openSearchRouter} = useSearchRouterContext(); - if (!Permissions.canUseNewSearchRouter()) { - return; - } - return ( ): boolean { return !!betas?.includes(CONST.BETAS.ALL); @@ -50,17 +49,6 @@ function canUseCombinedTrackSubmit(betas: OnyxEntry): boolean { return !!betas?.includes(CONST.BETAS.COMBINED_TRACK_SUBMIT); } -/** - * New Search Router is under construction and for now should be displayed only in dev to allow developers to work on it. - * We are not using BETA for this feature, as betas are heavier to cleanup, - * and the development of new router is expected to take 2-3 weeks at most - * - * After everything is implemented this function can be removed, as we will always use SearchRouter in the App. - */ -function canUseNewSearchRouter() { - return Environment.isDevelopment(); -} - /** * Link previews are temporarily disabled. */ @@ -80,5 +68,4 @@ export default { canUseNewDotCopilot, canUseWorkspaceRules, canUseCombinedTrackSubmit, - canUseNewSearchRouter, }; diff --git a/tests/perf-test/SearchRouter.perf-test.tsx b/tests/perf-test/SearchRouter.perf-test.tsx index 5be0c93105c3..e9154a36a9a1 100644 --- a/tests/perf-test/SearchRouter.perf-test.tsx +++ b/tests/perf-test/SearchRouter.perf-test.tsx @@ -18,7 +18,6 @@ import type {PersonalDetails, Report} from '@src/types/onyx'; import createCollection from '../utils/collections/createCollection'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomReport from '../utils/collections/reports'; -import createAddListenerMock from '../utils/createAddListenerMock'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; From e21ed95a38c101515a3dbc8a10f7307375b6e22b Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Fri, 4 Oct 2024 17:37:12 +0200 Subject: [PATCH 06/15] Fix SearchRouter opening from keyboard shortcut --- .../SearchRouter/SearchRouterContext.tsx | 25 ++++++++++++++++--- .../Navigation/AppNavigator/AuthScreens.tsx | 12 +++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouterContext.tsx b/src/components/Search/SearchRouter/SearchRouterContext.tsx index d935fff110a4..4e01071a0916 100644 --- a/src/components/Search/SearchRouter/SearchRouterContext.tsx +++ b/src/components/Search/SearchRouter/SearchRouterContext.tsx @@ -1,10 +1,11 @@ -import React, {useContext, useMemo, useState} from 'react'; +import React, {useContext, useMemo, useRef, useState} from 'react'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; const defaultSearchContext = { isSearchRouterDisplayed: false, openSearchRouter: () => {}, closeSearchRouter: () => {}, + toggleSearchRouter: () => {}, }; type SearchRouterContext = typeof defaultSearchContext; @@ -13,15 +14,33 @@ const Context = React.createContext(defaultSearchContext); function SearchRouterContextProvider({children}: ChildrenProps) { const [isSearchRouterDisplayed, setIsSearchRouterDisplayed] = useState(false); + const searchRouterDisplayedRef = useRef(false); const routerContext = useMemo(() => { - const openSearchRouter = () => setIsSearchRouterDisplayed(true); - const closeSearchRouter = () => setIsSearchRouterDisplayed(false); + const openSearchRouter = () => { + setIsSearchRouterDisplayed(true); + searchRouterDisplayedRef.current = true; + }; + const closeSearchRouter = () => { + setIsSearchRouterDisplayed(false); + searchRouterDisplayedRef.current = false; + }; + + // There are callbacks that live outside of React render-loop and interact with SearchRouter + // So we need a function that is based on ref to correctly open/close it + const toggleSearchRouter = () => { + if (searchRouterDisplayedRef.current) { + closeSearchRouter(); + } else { + openSearchRouter(); + } + }; return { isSearchRouterDisplayed, openSearchRouter, closeSearchRouter, + toggleSearchRouter, }; }, [isSearchRouterDisplayed, setIsSearchRouterDisplayed]); diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 2ec31d8a8688..b9ea16a344f2 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -234,7 +234,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie const screenOptions = getRootNavigatorScreenOptions(shouldUseNarrowLayout, styles, StyleUtils); const {canUseDefaultRooms} = usePermissions(); const {activeWorkspaceID} = useActiveWorkspace(); - const {openSearchRouter} = useSearchRouterContext(); + const {toggleSearchRouter} = useSearchRouterContext(); const onboardingModalScreenOptions = useMemo(() => screenOptions.onboardingModalNavigator(onboardingIsMediumOrLargerScreenWidth), [screenOptions, onboardingIsMediumOrLargerScreenWidth]); const onboardingScreenOptions = useMemo( @@ -372,13 +372,9 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie const unsubscribeSearchShortcut = KeyboardShortcut.subscribe( searchShortcutConfig.shortcutKey, () => { - Modal.close( - Session.checkIfActionIsAllowed(() => { - openSearchRouter(); - }), - true, - true, - ); + Session.checkIfActionIsAllowed(() => { + toggleSearchRouter(); + })(); }, shortcutsOverviewShortcutConfig.descriptionKey, shortcutsOverviewShortcutConfig.modifiers, From 4b81669e070a276265442998aaba3890603e66b1 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 7 Oct 2024 11:47:54 +0200 Subject: [PATCH 07/15] close modals on openSearchRouter --- .../Search/SearchRouter/SearchRouterContext.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouterContext.tsx b/src/components/Search/SearchRouter/SearchRouterContext.tsx index 4e01071a0916..2e4cbec0d6bb 100644 --- a/src/components/Search/SearchRouter/SearchRouterContext.tsx +++ b/src/components/Search/SearchRouter/SearchRouterContext.tsx @@ -1,4 +1,5 @@ import React, {useContext, useMemo, useRef, useState} from 'react'; +import * as Modal from '@userActions/Modal'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; const defaultSearchContext = { @@ -18,8 +19,14 @@ function SearchRouterContextProvider({children}: ChildrenProps) { const routerContext = useMemo(() => { const openSearchRouter = () => { - setIsSearchRouterDisplayed(true); - searchRouterDisplayedRef.current = true; + Modal.close( + () => { + setIsSearchRouterDisplayed(true); + searchRouterDisplayedRef.current = true; + }, + false, + true, + ); }; const closeSearchRouter = () => { setIsSearchRouterDisplayed(false); From 47ba23f6e5dd5b744135ed46dbe76fac100df4a7 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Tue, 8 Oct 2024 11:05:11 +0200 Subject: [PATCH 08/15] fix design comments --- src/components/Search/SearchRouter/SearchRouterList.tsx | 1 + src/components/Search/SearchRouter/SearchRouterModal.tsx | 2 +- src/styles/index.ts | 4 ++-- src/styles/utils/spacing.ts | 4 ++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index a58ea969d28f..70abc5c89550 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -185,6 +185,7 @@ function SearchRouterList( onLayout={setPerformanceTimersEnd} ref={ref} showScrollIndicator={!isSmallScreenWidth} + sectionTitleStyles={styles.mhn2} /> ); } diff --git a/src/components/Search/SearchRouter/SearchRouterModal.tsx b/src/components/Search/SearchRouter/SearchRouterModal.tsx index c44b66044ab3..7e403461dd34 100644 --- a/src/components/Search/SearchRouter/SearchRouterModal.tsx +++ b/src/components/Search/SearchRouter/SearchRouterModal.tsx @@ -17,7 +17,7 @@ function SearchRouterModal() { type={modalType} fullscreen isVisible={isSearchRouterDisplayed} - popoverAnchorPosition={{right: 20, top: 20}} + popoverAnchorPosition={{right: 6, top: 6}} onClose={closeSearchRouter} > {isSearchRouterDisplayed && } diff --git a/src/styles/index.ts b/src/styles/index.ts index 8220389867d9..b81d76c8d7d9 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3615,8 +3615,8 @@ const styles = (theme: ThemeColors) => searchInputStyle: { color: theme.textSupporting, - fontSize: 13, - lineHeight: 16, + fontSize: variables.fontSizeNormal, + lineHeight: variables.fontSizeNormalHeight, }, searchRouterTextInputContainer: { diff --git a/src/styles/utils/spacing.ts b/src/styles/utils/spacing.ts index c2008d8a68f0..9f741c9d925b 100644 --- a/src/styles/utils/spacing.ts +++ b/src/styles/utils/spacing.ts @@ -55,6 +55,10 @@ export default { marginHorizontal: 32, }, + mhn2: { + marginHorizontal: -8, + }, + mhn5: { marginHorizontal: -20, }, From c4f70882f836269bf2fedb6f448e20e87db1315b Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Thu, 10 Oct 2024 12:16:03 +0200 Subject: [PATCH 09/15] fix PR comments --- src/components/HeaderWithBackButton/index.tsx | 2 +- src/components/Search/SearchRouter/SearchRouter.tsx | 3 +-- src/components/Search/SearchRouter/SearchRouterList.tsx | 2 +- src/styles/variables.ts | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/HeaderWithBackButton/index.tsx b/src/components/HeaderWithBackButton/index.tsx index eb04ad5540eb..e1843ee506d5 100755 --- a/src/components/HeaderWithBackButton/index.tsx +++ b/src/components/HeaderWithBackButton/index.tsx @@ -191,7 +191,7 @@ function HeaderWithBackButton({ /> )} {middleContent} - + {children} {shouldShowDownloadButton && ( diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index f49112e86c6c..564e5f0feda0 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -150,8 +150,7 @@ function SearchRouter({onRouterClose}: SearchRouterProps) { ); useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ESCAPE, () => { - onRouterClose(); - clearUserQuery(); + closeAndClearRouter(); }); const modalWidth = isSmallScreenWidth ? styles.w100 : {width: variables.popoverWidth}; diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index 70abc5c89550..d05588afbe9a 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -167,7 +167,7 @@ function SearchRouterList( // Handle selection of "Recent chat" closeAndClearRouter(); if ('reportID' in item && item?.reportID) { - Navigation.closeAndNavigate(ROUTES.REPORT_WITH_ID.getRoute(item?.reportID)); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(item?.reportID)); } else if ('login' in item) { Report.navigateToAndOpenReport(item?.login ? [item.login] : []); } diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 82d443928c7a..cd81e5fcb9c8 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -166,7 +166,7 @@ export default { signInLogoWidthLargeScreenPill: 162, modalContentMaxWidth: 360, listItemHeightNormal: 64, - popoverWidth: 375, + popoverWidth: 512, bankAccountActionPopoverRightSpacing: 32, bankAccountActionPopoverTopSpacing: 14, addPaymentPopoverRightSpacing: 23, From 7e44690b9ffe42ad43d43e3b4d074efe3518ed81 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Thu, 10 Oct 2024 12:32:26 +0200 Subject: [PATCH 10/15] fix paddings --- src/components/Search/SearchRouter/SearchButton.tsx | 9 +++++++-- src/pages/home/HeaderView.tsx | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx index 2ddc9a8262c2..ae755fbf2e27 100644 --- a/src/components/Search/SearchRouter/SearchButton.tsx +++ b/src/components/Search/SearchRouter/SearchButton.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import {PressableWithoutFeedback} from '@components/Pressable'; @@ -11,7 +12,11 @@ import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import {useSearchRouterContext} from './SearchRouterContext'; -function SearchButton() { +type SearchButtonProps = { + style?: StyleProp; +}; + +function SearchButton({style}: SearchButtonProps) { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); @@ -20,7 +25,7 @@ function SearchButton() { return ( { Timing.start(CONST.TIMING.SEARCH_ROUTER_OPEN); Performance.markStart(CONST.TIMING.SEARCH_ROUTER_OPEN); diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 42483cc3d223..31aaf7cb1f1c 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -280,7 +280,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto {isTaskReport && !shouldUseNarrowLayout && ReportUtils.isOpenTaskReport(report, parentReportAction) && } {canJoin && !shouldUseNarrowLayout && joinButton} - + Date: Thu, 10 Oct 2024 15:21:25 +0200 Subject: [PATCH 11/15] fix linter --- .../TopBar.tsx | 33 +++---------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/TopBar.tsx b/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/TopBar.tsx index 4684eb9637be..8967486165f8 100644 --- a/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/TopBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/TopBar.tsx @@ -2,32 +2,25 @@ import React from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Breadcrumbs from '@components/Breadcrumbs'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; import {PressableWithoutFeedback} from '@components/Pressable'; import SearchButton from '@components/Search/SearchRouter/SearchButton'; import Text from '@components/Text'; -import Tooltip from '@components/Tooltip'; import WorkspaceSwitcherButton from '@components/WorkspaceSwitcherButton'; import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import Performance from '@libs/Performance'; import * as SearchUtils from '@libs/SearchUtils'; import SignInButton from '@pages/home/sidebar/SignInButton'; import * as Session from '@userActions/Session'; -import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -type TopBarProps = {breadcrumbLabel: string; activeWorkspaceID?: string; shouldDisplaySearch?: boolean; isCustomSearchQuery?: boolean; shouldDisplaySearchRouter?: boolean}; +type TopBarProps = {breadcrumbLabel: string; activeWorkspaceID?: string; shouldDisplaySearch?: boolean; shouldDisplayCancelSearch?: boolean}; -function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, isCustomSearchQuery = false, shouldDisplaySearchRouter = false}: TopBarProps) { +function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, shouldDisplayCancelSearch = false}: TopBarProps) { const styles = useThemeStyles(); - const theme = useTheme(); const {translate} = useLocalize(); const policy = usePolicy(activeWorkspaceID); const [session] = useOnyx(ONYXKEYS.SESSION, {selector: (sessionValue) => sessionValue && {authTokenType: sessionValue.authTokenType}}); @@ -63,7 +56,7 @@ function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, {displaySignIn && } - {isCustomSearchQuery && ( + {shouldDisplayCancelSearch && ( {translate('common.cancel')} )} - {shouldDisplaySearchRouter && } - {displaySearch && ( - - { - Timing.start(CONST.TIMING.CHAT_FINDER_RENDER); - Performance.markStart(CONST.TIMING.CHAT_FINDER_RENDER); - Navigation.navigate(ROUTES.CHAT_FINDER); - })} - > - - - - )} + {displaySearch && } ); From 90e18c518ab36f348eae3c735bb0188d936452cd Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Fri, 11 Oct 2024 13:36:56 +0200 Subject: [PATCH 12/15] fix PR comments --- src/components/Search/SearchRouter/SearchRouter.tsx | 2 +- src/styles/variables.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 564e5f0feda0..5c1f4e27c93d 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -153,7 +153,7 @@ function SearchRouter({onRouterClose}: SearchRouterProps) { closeAndClearRouter(); }); - const modalWidth = isSmallScreenWidth ? styles.w100 : {width: variables.popoverWidth}; + const modalWidth = isSmallScreenWidth ? styles.w100 : {width: variables.searchRouterPopoverWidth}; return ( Date: Mon, 14 Oct 2024 15:29:08 +0200 Subject: [PATCH 13/15] fix style bugs --- src/components/Search/SearchRouter/SearchRouterInput.tsx | 2 +- src/components/Search/SearchRouter/SearchRouterList.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouterInput.tsx b/src/components/Search/SearchRouter/SearchRouterInput.tsx index e28775c3686e..ee236494fcc6 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput.tsx +++ b/src/components/Search/SearchRouter/SearchRouterInput.tsx @@ -88,7 +88,7 @@ function SearchRouterInput({ disabled={disabled} onSubmitEditing={onSubmit} shouldUseDisabledStyles={false} - textInputContainerStyles={styles.borderNone} + textInputContainerStyles={[styles.borderNone, styles.pb0]} inputStyle={[styles.searchInputStyle, inputWidth, styles.pl3, styles.pr3]} onFocus={() => { setIsFocused(true); diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index d05588afbe9a..8485d0c2eb50 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -80,7 +80,6 @@ function SearchRouterItem(props: UserListItemProps | SearchQueryList return ( @@ -145,7 +144,7 @@ function SearchRouterList( sections.push({title: translate('search.recentSearches'), data: recentSearchesData}); } - const styledRecentReports = recentReports.map((item) => ({...item, pressableStyle: styles.br2})); + const styledRecentReports = recentReports.map((item) => ({...item, pressableStyle: styles.br2, wrapperStyle: [styles.pr3, styles.pl3]})); sections.push({title: translate('search.recentChats'), data: styledRecentReports}); const onSelectRow = useCallback( @@ -182,6 +181,7 @@ function SearchRouterList( ListItem={SearchRouterItem} containerStyle={[styles.mh100]} sectionListStyle={[isSmallScreenWidth ? styles.ph5 : styles.ph2, styles.pb2]} + listItemWrapperStyle={[styles.pr3, styles.pl3]} onLayout={setPerformanceTimersEnd} ref={ref} showScrollIndicator={!isSmallScreenWidth} From 935067ed56dd45bd24edd3ce834edc2dd112bc7d Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 14 Oct 2024 16:03:53 +0200 Subject: [PATCH 14/15] add offline message --- src/CONST.ts | 2 +- .../Search/SearchRouter/SearchButton.tsx | 4 +- .../Search/SearchRouter/SearchRouter.tsx | 9 ++- .../Search/SearchRouter/SearchRouterInput.tsx | 77 ++++++++++++------- .../Search/SearchRouter/SearchRouterList.tsx | 5 +- .../E2E/tests/openSearchRouterTest.e2e.ts | 2 +- 6 files changed, 64 insertions(+), 35 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index ab6d057393e2..51da86fbcb7f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1102,7 +1102,7 @@ const CONST = { }, TIMING: { CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action', - SEARCH_ROUTER_OPEN: 'search_router_render', + SEARCH_ROUTER_RENDER: 'search_router_render', CHAT_RENDER: 'chat_render', OPEN_REPORT: 'open_report', HOMEPAGE_INITIAL_RENDER: 'homepage_initial_render', diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx index ae755fbf2e27..7ed22ec8162f 100644 --- a/src/components/Search/SearchRouter/SearchButton.tsx +++ b/src/components/Search/SearchRouter/SearchButton.tsx @@ -27,8 +27,8 @@ function SearchButton({style}: SearchButtonProps) { accessibilityLabel={translate('common.search')} style={[styles.flexRow, styles.touchableButtonImage, style]} onPress={Session.checkIfActionIsAllowed(() => { - Timing.start(CONST.TIMING.SEARCH_ROUTER_OPEN); - Performance.markStart(CONST.TIMING.SEARCH_ROUTER_OPEN); + Timing.start(CONST.TIMING.SEARCH_ROUTER_RENDER); + Performance.markStart(CONST.TIMING.SEARCH_ROUTER_RENDER); openSearchRouter(); })} diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 1e6d8ff16ba8..e8d613e0b6f1 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -3,6 +3,7 @@ import debounce from 'lodash/debounce'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import FormHelpMessage from '@components/FormHelpMessage'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {useOptionsList} from '@components/OptionListContextProvider'; import type {SearchQueryJSON} from '@components/Search/types'; @@ -10,6 +11,7 @@ import type {SelectionListHandle} from '@components/SelectionList/types'; import useDebouncedState from '@hooks/useDebouncedState'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Log from '@libs/Log'; @@ -176,8 +178,13 @@ function SearchRouter({onRouterClose}: SearchRouterProps) { setValue={setTextInputValue} isFullWidth={isSmallScreenWidth} updateSearch={onSearchChange} + onSubmit={() => { + onSearchSubmit(SearchUtils.buildSearchQueryJSON(textInputValue)); + }} routerListRef={listRef} - wrapperStyle={[isSmallScreenWidth ? styles.mv3 : styles.mv2, isSmallScreenWidth ? styles.mh5 : styles.mh2, styles.border]} + shouldShowOfflineMessage + wrapperStyle={[styles.border, styles.alignItemsCenter]} + outerWrapperStyle={[isSmallScreenWidth ? styles.mv3 : styles.mv2, isSmallScreenWidth ? styles.mh5 : styles.mh2]} wrapperFocusedStyle={[styles.borderColorFocus]} isSearchingForReports={isSearchingForReports} /> diff --git a/src/components/Search/SearchRouter/SearchRouterInput.tsx b/src/components/Search/SearchRouter/SearchRouterInput.tsx index ee236494fcc6..84ebf2edc0b7 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput.tsx +++ b/src/components/Search/SearchRouter/SearchRouterInput.tsx @@ -2,9 +2,11 @@ import React, {useState} from 'react'; import type {ReactNode, RefObject} from 'react'; import {View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; +import FormHelpMessage from '@components/FormHelpMessage'; import type {SelectionListHandle} from '@components/SelectionList/types'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -31,6 +33,9 @@ type SearchRouterInputProps = { /** Whether the input is disabled */ disabled?: boolean; + /** Whether the offline message should be shown */ + shouldShowOfflineMessage?: boolean; + /** Whether the input should be focused */ autoFocus?: boolean; @@ -40,6 +45,9 @@ type SearchRouterInputProps = { /** Any additional styles to apply when input is focused */ wrapperFocusedStyle?: StyleProp; + /** Any additional styles to apply to text input along with FormHelperMessage */ + outerWrapperStyle?: StyleProp; + /** Component to be displayed on the right */ rightComponent?: ReactNode; @@ -55,15 +63,19 @@ function SearchRouterInput({ routerListRef, isFullWidth, disabled = false, + shouldShowOfflineMessage = false, autoFocus = true, wrapperStyle, wrapperFocusedStyle, + outerWrapperStyle, rightComponent, isSearchingForReports, }: SearchRouterInputProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [isFocused, setIsFocused] = useState(false); + const {isOffline} = useNetwork(); + const offlineMessage: string = isOffline && shouldShowOfflineMessage ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const onChangeText = (text: string) => { setValue(text); @@ -73,35 +85,44 @@ function SearchRouterInput({ const inputWidth = isFullWidth ? styles.w100 : {width: variables.popoverWidth}; return ( - - - { - setIsFocused(true); - routerListRef?.current?.updateExternalTextInputFocus(true); - }} - onBlur={() => { - setIsFocused(false); - routerListRef?.current?.updateExternalTextInputFocus(false); - }} - isLoading={!!isSearchingForReports} - /> + + + + { + setIsFocused(true); + routerListRef?.current?.updateExternalTextInputFocus(true); + }} + onBlur={() => { + setIsFocused(false); + routerListRef?.current?.updateExternalTextInputFocus(false); + }} + isLoading={!!isSearchingForReports} + /> + + {rightComponent && {rightComponent}} - {rightComponent && {rightComponent}} + ); } diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index 8485d0c2eb50..84c300bfc895 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -51,8 +51,8 @@ type SearchRouterListProps = { }; const setPerformanceTimersEnd = () => { - Timing.end(CONST.TIMING.SEARCH_ROUTER_OPEN); - Performance.markEnd(CONST.TIMING.SEARCH_ROUTER_OPEN); + Timing.end(CONST.TIMING.SEARCH_ROUTER_RENDER); + Performance.markEnd(CONST.TIMING.SEARCH_ROUTER_RENDER); }; function isSearchQueryItem(item: OptionData | SearchQueryItem): item is SearchQueryItem { @@ -186,6 +186,7 @@ function SearchRouterList( ref={ref} showScrollIndicator={!isSmallScreenWidth} sectionTitleStyles={styles.mhn2} + shouldSingleExecuteRowSelect /> ); } diff --git a/src/libs/E2E/tests/openSearchRouterTest.e2e.ts b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts index 6c4646b09fbf..840af5acc2c9 100644 --- a/src/libs/E2E/tests/openSearchRouterTest.e2e.ts +++ b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts @@ -32,7 +32,7 @@ const test = () => { Performance.subscribeToMeasurements((entry) => { console.debug(`[E2E] Entry: ${JSON.stringify(entry)}`); - if (entry.name === CONST.TIMING.SEARCH_ROUTER_OPEN) { + if (entry.name === CONST.TIMING.SEARCH_ROUTER_RENDER) { E2EClient.submitTestResults({ branch: Config.E2E_BRANCH, name: 'Open Search Router TTI', From a6a74c5562f34291ebd5361297dd3048950966c4 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 14 Oct 2024 16:33:04 +0200 Subject: [PATCH 15/15] fix linter --- src/components/Search/SearchRouter/SearchRouter.tsx | 2 -- src/components/Search/SearchRouter/SearchRouterInput.tsx | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index e8d613e0b6f1..a957806ee4f7 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -3,7 +3,6 @@ import debounce from 'lodash/debounce'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; -import FormHelpMessage from '@components/FormHelpMessage'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {useOptionsList} from '@components/OptionListContextProvider'; import type {SearchQueryJSON} from '@components/Search/types'; @@ -11,7 +10,6 @@ import type {SelectionListHandle} from '@components/SelectionList/types'; import useDebouncedState from '@hooks/useDebouncedState'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useLocalize from '@hooks/useLocalize'; -import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Log from '@libs/Log'; diff --git a/src/components/Search/SearchRouter/SearchRouterInput.tsx b/src/components/Search/SearchRouter/SearchRouterInput.tsx index 84ebf2edc0b7..ef6963152c42 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput.tsx +++ b/src/components/Search/SearchRouter/SearchRouterInput.tsx @@ -120,6 +120,7 @@ function SearchRouterInput({ {rightComponent && {rightComponent}}