diff --git a/src/Navigation.test.tsx b/src/Navigation.test.tsx index 3372c152..22d84776 100644 --- a/src/Navigation.test.tsx +++ b/src/Navigation.test.tsx @@ -16,17 +16,32 @@ type MockScreenListeners = { transitionStart?: (event: { data: { closing: boolean } }) => void; }; +type MockNavigationContainerProps = { + children: React.ReactNode; + onReady?: () => void; + onStateChange?: () => void; +}; + +let mockActiveRouteName = 'List'; +let mockNavigationContainerProps: MockNavigationContainerProps | undefined; const mockScreenListeners: Record = {}; jest.mock('@react-navigation/native', () => { const actual = jest.requireActual('@react-navigation/native'); + const React = jest.requireActual('react'); return { ...actual, - NavigationContainer: ({ children }: { children: React.ReactNode }) => { + NavigationContainer: React.forwardRef(({ children, onReady, onStateChange }: MockNavigationContainerProps, ref: React.Ref<{ getCurrentRoute: () => { name: string } }>) => { const { View } = jest.requireActual('react-native'); + + mockNavigationContainerProps = { children, onReady, onStateChange }; + React.useImperativeHandle(ref, () => ({ + getCurrentRoute: () => ({ name: mockActiveRouteName }), + })); + return {children}; - }, + }), }; }); @@ -140,14 +155,24 @@ const getGameListeners = (): Required => { return listeners as Required; }; +const setActiveRoute = (routeName: string) => { + mockActiveRouteName = routeName; + + act(() => { + mockNavigationContainerProps?.onStateChange?.(); + }); +}; + describe('Navigation', () => { beforeEach(() => { + mockActiveRouteName = 'List'; + mockNavigationContainerProps = undefined; Object.keys(mockScreenListeners).forEach((key) => { delete mockScreenListeners[key]; }); }); - it('shows and hides the game sheet from native stack lifecycle events', () => { + it('shows and hides the game sheet from native stack lifecycle and route state events', () => { const { queryByTestId } = renderNavigation(); const gameListeners = getGameListeners(); @@ -182,16 +207,51 @@ describe('Navigation', () => { }); expect(queryByTestId('game-sheet')).toBeNull(); + + setActiveRoute('Game'); + + expect(queryByTestId('game-sheet')).toBeTruthy(); + + setActiveRoute('List'); + + expect(queryByTestId('game-sheet')).toBeNull(); + + setActiveRoute('Game'); + + expect(queryByTestId('game-sheet')).toBeTruthy(); + + setActiveRoute('EditGame'); + + expect(queryByTestId('game-sheet')).toBeNull(); + + setActiveRoute('Game'); + + expect(queryByTestId('game-sheet')).toBeTruthy(); + + setActiveRoute('Share'); + + expect(queryByTestId('game-sheet')).toBeNull(); }); it('does not render the game sheet when fullscreen mode is enabled', () => { const { queryByTestId } = renderNavigation(true); - const gameListeners = getGameListeners(); + + setActiveRoute('Game'); + + expect(queryByTestId('game-sheet')).toBeNull(); + }); + + it('syncs the game sheet with the initial route when navigation is ready', () => { + mockActiveRouteName = 'Game'; + + const { queryByTestId } = renderNavigation(); + + expect(queryByTestId('game-sheet')).toBeNull(); act(() => { - gameListeners.focus(); + mockNavigationContainerProps?.onReady?.(); }); - expect(queryByTestId('game-sheet')).toBeNull(); + expect(queryByTestId('game-sheet')).toBeTruthy(); }); }); diff --git a/src/Navigation.tsx b/src/Navigation.tsx index faf063c2..1bf3a4db 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native'; +import { DarkTheme, DefaultTheme, NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { Platform, View } from 'react-native'; @@ -39,6 +39,7 @@ export type RootStackParamList = { const Stack = createNativeStackNavigator(); export const Navigation = () => { + const navigationRef = useNavigationContainerRef(); const theme = useTheme(); const isAndroid = Platform.OS === 'android'; const isIOS = Platform.OS === 'ios'; @@ -51,12 +52,19 @@ export const Navigation = () => { : { ...DefaultTheme, colors: { ...DefaultTheme.colors, background: theme.background, card: theme.backgroundSecondary } }; const fullscreen = useAppSelector(state => state.settings.home_fullscreen); - const [showGameSheet, setShowGameSheet] = useState(false); + const [showGameSheetForActiveRoute, setShowGameSheetForActiveRoute] = useState(false); + + const syncGameSheetWithActiveRoute = () => { + setShowGameSheetForActiveRoute(navigationRef.getCurrentRoute()?.name === 'Game'); + }; return ( @@ -84,13 +92,13 @@ export const Navigation = () => { headerBackButtonDisplayMode: 'minimal', }} listeners={{ - focus: () => setShowGameSheet(true), + focus: () => setShowGameSheetForActiveRoute(true), transitionStart: (event) => { if (event.data.closing) { - setShowGameSheet(false); + setShowGameSheetForActiveRoute(false); } }, - blur: () => setShowGameSheet(false), + blur: () => setShowGameSheetForActiveRoute(false), }} /> { - {!fullscreen && showGameSheet && } + {!fullscreen && showGameSheetForActiveRoute && } );