Skip to content

Commit

Permalink
Merge pull request Expensify#33286 from callstack-internal/hur/fix-re…
Browse files Browse the repository at this point in the history
…assure-test-for-transition-end

[NoQA] test: add support for transitionEnd event in reassure tests
  • Loading branch information
pecanoro authored Dec 27, 2023
2 parents d6f359f + b8429c3 commit 9e81447
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 6 deletions.
18 changes: 17 additions & 1 deletion src/components/ScreenWrapper/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 */}
<ReportScreen route={route} />
<ReportScreen
// @ts-expect-error Error will be resolved after ReportScreen migration to TypeScript
route={route}
navigation={navigation}
/>
<ReportScreenIDSetter
route={route}
navigation={navigation}
Expand Down
2 changes: 2 additions & 0 deletions src/pages/home/ReportScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ function ReportScreen({
errors,
userLeavingStatus,
currentReportID,
navigation,
}) {
const styles = useThemeStyles();
const {translate} = useLocalize();
Expand Down Expand Up @@ -434,6 +435,7 @@ function ReportScreen({
<ActionListContext.Provider value={actionListValue}>
<ReactionListContext.Provider value={reactionListRef}>
<ScreenWrapper
navigation={navigation}
style={screenWrapperStyle}
shouldEnableKeyboardAvoidingView={isTopMostReportId}
testID={ReportScreen.displayName}
Expand Down
79 changes: 76 additions & 3 deletions tests/perf-test/ReportScreen.perf-test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {fireEvent, screen} from '@testing-library/react-native';
import {act, fireEvent, screen} from '@testing-library/react-native';
import React from 'react';
import Onyx from 'react-native-onyx';
import {measurePerformance} from 'reassure';
import _ from 'underscore';
import ComposeProviders from '../../src/components/ComposeProviders';
import DragAndDropProvider from '../../src/components/DragAndDrop/Provider';
import {LocaleContextProvider} from '../../src/components/LocaleContextProvider';
Expand Down Expand Up @@ -101,6 +102,33 @@ afterEach(() => {
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 (
<ComposeProviders
Expand All @@ -117,6 +145,7 @@ function ReportScreenWrapper(args) {
<ReportScreen
// eslint-disable-next-line react/jsx-props-no-spreading
{...args}
navigation={args.navigation}
/>
</ComposeProviders>
);
Expand All @@ -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');

Expand Down Expand Up @@ -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({
Expand All @@ -172,11 +215,31 @@ test('[ReportScreen] should render ReportScreen with composer interactions', ()
},
}),
)
.then(() => measurePerformance(<ReportScreenWrapper route={mockRoute} />, {scenario, runs}));
.then(() =>
measurePerformance(
<ReportScreenWrapper
navigation={navigation}
route={mockRoute}
/>,
{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');

Expand All @@ -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({
Expand All @@ -215,5 +280,13 @@ test('[ReportScreen] should press of the report item', () => {
},
}),
)
.then(() => measurePerformance(<ReportScreenWrapper route={mockRoute} />, {scenario, runs}));
.then(() =>
measurePerformance(
<ReportScreenWrapper
navigation={navigation}
route={mockRoute}
/>,
{scenario, runs},
),
);
});

0 comments on commit 9e81447

Please sign in to comment.