diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index bd277ffa1ab8..432139353c56 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -39,16 +39,32 @@ const ScreenWrapper = React.forwardRef( shouldDismissKeyboardBeforeClose, onEntryTransitionEnd, testID, + + /** + * The navigation prop is passed by the navigator. It is used to trigger the onEntryTransitionEnd callback + * when the screen transition ends. + * + * This is required because transitionEnd event doesn't trigger in the testing environment. + */ + navigation: navigationProp, }, ref, ) => { + /** + * We are only passing navigation as prop from + * ReportScreenWrapper -> ReportScreen -> ScreenWrapper + * + * so in other places where ScreenWrapper is used, we need to + * fallback to useNavigation. + */ + const navigationFallback = useNavigation(); + const navigation = navigationProp || navigationFallback; const {windowHeight, isSmallScreenWidth} = useWindowDimensions(); const {initialHeight} = useInitialDimensions(); const styles = useThemeStyles(); const keyboardState = useKeyboardState(); const {isDevelopment} = useEnvironment(); const {isOffline} = useNetwork(); - const navigation = useNavigation(); const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); const maxHeight = shouldEnableMaxHeight ? windowHeight : undefined; const minHeight = shouldEnableMinHeight && !Browser.isSafari() ? initialHeight : undefined; diff --git a/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx b/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx index 20922fd785ce..f3295aadb888 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx +++ b/src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx @@ -12,8 +12,11 @@ function ReportScreenWrapper({route, navigation}: ReportScreenWrapperProps) { // until the reportID is loaded and set in the route param return ( <> - {/* @ts-expect-error Error will be resolved after ReportScreen migration to TypeScript */} - + { PusherHelper.teardown(); }); +/** + * This is a helper function to create a mock for the addListener function of the react-navigation library. + * The reason we need this is because we need to trigger the transitionEnd event in our tests to simulate + * the transitionEnd event that is triggered when the screen transition animation is completed. + * + * P.S: This can't be moved to a utils file because Jest wants any external function to stay in the scope. + * + * @returns {Object} An object with two functions: triggerTransitionEnd and addListener + */ +const createAddListenerMock = () => { + const transitionEndListeners = []; + const triggerTransitionEnd = () => { + transitionEndListeners.forEach((transitionEndListener) => transitionEndListener()); + }; + + const addListener = jest.fn().mockImplementation((listener, callback) => { + if (listener === 'transitionEnd') { + transitionEndListeners.push(callback); + } + return () => { + _.filter(transitionEndListeners, (cb) => cb !== callback); + }; + }); + + return {triggerTransitionEnd, addListener}; +}; + function ReportScreenWrapper(args) { return ( ); @@ -125,7 +154,19 @@ function ReportScreenWrapper(args) { const runs = CONST.PERFORMANCE_TESTS.RUNS; test('[ReportScreen] should render ReportScreen with composer interactions', () => { + const {triggerTransitionEnd, addListener} = createAddListenerMock(); const scenario = async () => { + /** + * First make sure ReportScreen is mounted, so that we can trigger + * the transitionEnd event manually. + * + * If we don't do that, then the transitionEnd event will be triggered + * before the ReportScreen is mounted, and the test will fail. + */ + await screen.findByTestId('ReportScreen'); + + await act(triggerTransitionEnd); + // Query for the report list await screen.findByTestId('report-actions-list'); @@ -158,6 +199,8 @@ test('[ReportScreen] should render ReportScreen with composer interactions', () const reportActions = ReportTestUtils.getMockedReportActionsMap(1000); const mockRoute = {params: {reportID: '1'}}; + const navigation = {addListener}; + return waitForBatchedUpdates() .then(() => Onyx.multiSet({ @@ -172,11 +215,31 @@ test('[ReportScreen] should render ReportScreen with composer interactions', () }, }), ) - .then(() => measurePerformance(, {scenario, runs})); + .then(() => + measurePerformance( + , + {scenario, runs}, + ), + ); }); test('[ReportScreen] should press of the report item', () => { + const {triggerTransitionEnd, addListener} = createAddListenerMock(); const scenario = async () => { + /** + * First make sure ReportScreen is mounted, so that we can trigger + * the transitionEnd event manually. + * + * If we don't do that, then the transitionEnd event will be triggered + * before the ReportScreen is mounted, and the test will fail. + */ + await screen.findByTestId('ReportScreen'); + + await act(triggerTransitionEnd); + // Query for the report list await screen.findByTestId('report-actions-list'); @@ -201,6 +264,8 @@ test('[ReportScreen] should press of the report item', () => { const reportActions = ReportTestUtils.getMockedReportActionsMap(1000); const mockRoute = {params: {reportID: '2'}}; + const navigation = {addListener}; + return waitForBatchedUpdates() .then(() => Onyx.multiSet({ @@ -215,5 +280,13 @@ test('[ReportScreen] should press of the report item', () => { }, }), ) - .then(() => measurePerformance(, {scenario, runs})); + .then(() => + measurePerformance( + , + {scenario, runs}, + ), + ); });