From 1c83a603d3f2b73c31c0d9dd074890fd535a225e Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 3 Dec 2024 14:50:40 +0100 Subject: [PATCH 1/3] Add useNavigationResetRootOnLayoutChange --- .../createResponsiveStackNavigator/index.tsx | 4 ++-- .../useNavigationResetOnLayoutChange.ts | 7 +++++-- .../useNavigationResetRootOnLayoutChange.ts | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 src/libs/Navigation/AppNavigator/useNavigationResetRootOnLayoutChange.ts diff --git a/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/index.tsx b/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/index.tsx index 00b009efc089..9ac2ffd6c8f9 100644 --- a/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/index.tsx +++ b/src/libs/Navigation/AppNavigator/createResponsiveStackNavigator/index.tsx @@ -1,6 +1,6 @@ import type {ParamListBase} from '@react-navigation/native'; import {createNavigatorFactory} from '@react-navigation/native'; -import useNavigationResetOnLayoutChange from '@libs/Navigation/AppNavigator/useNavigationResetOnLayoutChange'; +import useNavigationResetRootOnLayoutChange from '@libs/Navigation/AppNavigator/useNavigationResetRootOnLayoutChange'; import createPlatformStackNavigatorComponent from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent'; import defaultPlatformStackScreenOptions from '@libs/Navigation/PlatformStackNavigation/defaultPlatformStackScreenOptions'; import type {PlatformStackNavigationEventMap, PlatformStackNavigationOptions, PlatformStackNavigationState} from '@libs/Navigation/PlatformStackNavigation/types'; @@ -12,7 +12,7 @@ const ResponsiveStackNavigatorComponent = createPlatformStackNavigatorComponent( createRouter: CustomRouter, defaultScreenOptions: defaultPlatformStackScreenOptions, useCustomState: useStateWithSearch, - useCustomEffects: useNavigationResetOnLayoutChange, + useCustomEffects: useNavigationResetRootOnLayoutChange, ExtraContent: RenderSearchRoute, }); diff --git a/src/libs/Navigation/AppNavigator/useNavigationResetOnLayoutChange.ts b/src/libs/Navigation/AppNavigator/useNavigationResetOnLayoutChange.ts index d86d38711f8c..2dbc37b1424c 100644 --- a/src/libs/Navigation/AppNavigator/useNavigationResetOnLayoutChange.ts +++ b/src/libs/Navigation/AppNavigator/useNavigationResetOnLayoutChange.ts @@ -1,15 +1,18 @@ +import type {ParamListBase} from '@react-navigation/native'; import {useEffect} from 'react'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import navigationRef from '@libs/Navigation/navigationRef'; +import type {CustomEffectsHookProps} from '@libs/Navigation/PlatformStackNavigation/types'; -function useNavigationResetOnLayoutChange() { +function useNavigationResetOnLayoutChange({navigation}: CustomEffectsHookProps) { const {shouldUseNarrowLayout} = useResponsiveLayout(); useEffect(() => { if (!navigationRef.isReady()) { return; } - navigationRef.resetRoot(navigationRef.getRootState()); + navigation.reset(navigation.getState()); + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [shouldUseNarrowLayout]); } diff --git a/src/libs/Navigation/AppNavigator/useNavigationResetRootOnLayoutChange.ts b/src/libs/Navigation/AppNavigator/useNavigationResetRootOnLayoutChange.ts new file mode 100644 index 000000000000..ffc1b2c893e1 --- /dev/null +++ b/src/libs/Navigation/AppNavigator/useNavigationResetRootOnLayoutChange.ts @@ -0,0 +1,16 @@ +import {useEffect} from 'react'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import navigationRef from '@libs/Navigation/navigationRef'; + +function useNavigationResetRootOnLayoutChange() { + const {shouldUseNarrowLayout} = useResponsiveLayout(); + + useEffect(() => { + if (!navigationRef.isReady()) { + return; + } + navigationRef.resetRoot(navigationRef.getRootState()); + }, [shouldUseNarrowLayout]); +} + +export default useNavigationResetRootOnLayoutChange; From 5b46385713b541e7cae0f4c956d586b76f370202 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 4 Dec 2024 11:01:17 +0100 Subject: [PATCH 2/3] Add comments to useNavigationResetOnLayoutChange hooks --- .../AppNavigator/useNavigationResetOnLayoutChange.ts | 5 +++++ .../AppNavigator/useNavigationResetRootOnLayoutChange.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/libs/Navigation/AppNavigator/useNavigationResetOnLayoutChange.ts b/src/libs/Navigation/AppNavigator/useNavigationResetOnLayoutChange.ts index 2dbc37b1424c..0a671529ee7d 100644 --- a/src/libs/Navigation/AppNavigator/useNavigationResetOnLayoutChange.ts +++ b/src/libs/Navigation/AppNavigator/useNavigationResetOnLayoutChange.ts @@ -4,6 +4,11 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import navigationRef from '@libs/Navigation/navigationRef'; import type {CustomEffectsHookProps} from '@libs/Navigation/PlatformStackNavigation/types'; +/** + * This hook resets the navigation root state when changing the layout size, resetting the state calls the getRehydredState method in CustomFullScreenRouter.tsx. + * When the screen size is changed, it is necessary to check whether the application displays the content correctly. + * When the app is opened on a small layout and the user resizes it to wide, a second screen has to be present in the navigation state to fill the space. + */ function useNavigationResetOnLayoutChange({navigation}: CustomEffectsHookProps) { const {shouldUseNarrowLayout} = useResponsiveLayout(); diff --git a/src/libs/Navigation/AppNavigator/useNavigationResetRootOnLayoutChange.ts b/src/libs/Navigation/AppNavigator/useNavigationResetRootOnLayoutChange.ts index ffc1b2c893e1..03caac57410f 100644 --- a/src/libs/Navigation/AppNavigator/useNavigationResetRootOnLayoutChange.ts +++ b/src/libs/Navigation/AppNavigator/useNavigationResetRootOnLayoutChange.ts @@ -2,6 +2,11 @@ import {useEffect} from 'react'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import navigationRef from '@libs/Navigation/navigationRef'; +/** + * This hook resets the navigation root state when changing the layout size, resetting the state calls the getRehydredState method in CustomRouter.ts. + * When the screen size is changed, it is necessary to check whether the application displays the content correctly. + * When the app is opened on a small layout and the user resizes it to wide, a second screen has to be present in the navigation state to fill the space. + */ function useNavigationResetRootOnLayoutChange() { const {shouldUseNarrowLayout} = useResponsiveLayout(); From de1a3d3bd6f7e69d853b406fd9b1b50326e9da0d Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 10 Dec 2024 15:21:39 +0100 Subject: [PATCH 3/3] Add ResizeScreenTests --- tests/ui/ResizeScreenTests.tsx | 95 ++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 tests/ui/ResizeScreenTests.tsx diff --git a/tests/ui/ResizeScreenTests.tsx b/tests/ui/ResizeScreenTests.tsx new file mode 100644 index 000000000000..5bd86ad152b2 --- /dev/null +++ b/tests/ui/ResizeScreenTests.tsx @@ -0,0 +1,95 @@ +import {NavigationContainer} from '@react-navigation/native'; +import {render, renderHook} from '@testing-library/react-native'; +import React from 'react'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import type ResponsiveLayoutResult from '@hooks/useResponsiveLayout/types'; +import getIsNarrowLayout from '@libs/getIsNarrowLayout'; +import createResponsiveStackNavigator from '@libs/Navigation/AppNavigator/createResponsiveStackNavigator'; +import BottomTabNavigator from '@libs/Navigation/AppNavigator/Navigators/BottomTabNavigator'; +import useNavigationResetRootOnLayoutChange from '@libs/Navigation/AppNavigator/useNavigationResetRootOnLayoutChange'; +import navigationRef from '@libs/Navigation/navigationRef'; +import type {AuthScreensParamList} from '@libs/Navigation/types'; +import ProfilePage from '@pages/settings/Profile/ProfilePage'; +import NAVIGATORS from '@src/NAVIGATORS'; +import SCREENS from '@src/SCREENS'; + +const RootStack = createResponsiveStackNavigator(); + +jest.mock('@hooks/useResponsiveLayout', () => jest.fn()); +jest.mock('@libs/getIsNarrowLayout', () => jest.fn()); + +jest.mock('@pages/settings/InitialSettingsPage'); +jest.mock('@pages/settings/Profile/ProfilePage'); +jest.mock('@libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar'); + +const DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE: ResponsiveLayoutResult = { + shouldUseNarrowLayout: true, + isSmallScreenWidth: true, + isInNarrowPaneModal: false, + isExtraSmallScreenHeight: false, + isMediumScreenWidth: false, + isLargeScreenWidth: false, + isExtraSmallScreenWidth: false, + isSmallScreen: false, + onboardingIsMediumOrLargerScreenWidth: false, +}; + +const INITIAL_STATE = { + routes: [ + { + name: NAVIGATORS.BOTTOM_TAB_NAVIGATOR, + state: { + index: 1, + routes: [{name: SCREENS.HOME}, {name: SCREENS.SETTINGS.ROOT}], + }, + }, + ], +}; + +const mockedGetIsNarrowLayout = getIsNarrowLayout as jest.MockedFunction; +const mockedUseResponsiveLayout = useResponsiveLayout as jest.MockedFunction; + +describe('Resize screen', () => { + it('Should display the settings profile after resizing the screen with the settings page opened to the wide layout', () => { + // Given the initialized navigation on the narrow layout with the settings screen + mockedGetIsNarrowLayout.mockReturnValue(true); + mockedUseResponsiveLayout.mockReturnValue({...DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE, shouldUseNarrowLayout: true}); + + const {rerender} = renderHook(() => useNavigationResetRootOnLayoutChange()); + + render( + + + + + + + , + ); + + const rootStateBeforeResize = navigationRef.current?.getRootState(); + + expect(rootStateBeforeResize?.routes.at(0)?.name).toBe(NAVIGATORS.BOTTOM_TAB_NAVIGATOR); + expect(rootStateBeforeResize?.routes.at(1)).toBeUndefined(); + + // When resizing the screen to the wide layout + mockedGetIsNarrowLayout.mockReturnValue(false); + mockedUseResponsiveLayout.mockReturnValue({...DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE, shouldUseNarrowLayout: false}); + rerender({}); + + const rootStateAfterResize = navigationRef.current?.getRootState(); + + // Then the settings profile page should be displayed on the screen + expect(rootStateAfterResize?.routes.at(0)?.name).toBe(NAVIGATORS.BOTTOM_TAB_NAVIGATOR); + expect(rootStateAfterResize?.routes.at(1)?.name).toBe(SCREENS.SETTINGS.PROFILE.ROOT); + }); +});