Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NoQA] [Reassure] SearchPage perf tests #33872

Merged
11 changes: 10 additions & 1 deletion src/pages/SearchPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,24 @@ const propTypes = {

/** Whether we are searching for reports in the server */
isSearchingForReports: PropTypes.bool,

/**
* The navigation prop passed by the navigator.
*
* This is required because transitionEnd event doesn't trigger in the automated testing environment.
*/
navigation: PropTypes.shape({}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add a comment here to adhere with the style rules. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally noting this is for automated tests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, will do

};

const defaultProps = {
betas: [],
personalDetails: {},
reports: {},
isSearchingForReports: false,
navigation: {},
};

function SearchPage({betas, personalDetails, reports, isSearchingForReports}) {
function SearchPage({betas, personalDetails, reports, isSearchingForReports, navigation}) {
const [searchValue, setSearchValue] = useState('');
const [searchOptions, setSearchOptions] = useState({
recentReports: {},
Expand Down Expand Up @@ -165,6 +173,7 @@ function SearchPage({betas, personalDetails, reports, isSearchingForReports}) {
includeSafeAreaPaddingBottom={false}
testID={SearchPage.displayName}
onEntryTransitionEnd={updateOptions}
navigation={navigation}
>
{({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => (
<>
Expand Down
206 changes: 206 additions & 0 deletions tests/perf-test/SearchPage.perf-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
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 SearchPage from '@pages/SearchPage';
import ComposeProviders from '../../src/components/ComposeProviders';
import {LocaleContextProvider} from '../../src/components/LocaleContextProvider';
import OnyxProvider from '../../src/components/OnyxProvider';
import {CurrentReportIDContextProvider} from '../../src/components/withCurrentReportID';
import {KeyboardStateProvider} from '../../src/components/withKeyboardState';
import {WindowDimensionsProvider} from '../../src/components/withWindowDimensions';
import CONST from '../../src/CONST';
import ONYXKEYS from '../../src/ONYXKEYS';
import createCollection from '../utils/collections/createCollection';
import createPersonalDetails from '../utils/collections/personalDetails';
import createRandomReport from '../utils/collections/reports';
import PusherHelper from '../utils/PusherHelper';
import * as TestHelper from '../utils/TestHelper';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates';

jest.mock('../../src/libs/Navigation/Navigation');

const mockedNavigate = jest.fn();
jest.mock('@react-navigation/native', () => {
const actualNav = jest.requireActual('@react-navigation/native');
return {
...actualNav,
useFocusEffect: jest.fn(),
useIsFocused: () => ({
navigate: mockedNavigate,
}),
useRoute: () => jest.fn(),
useNavigation: () => ({
navigate: jest.fn(),
addListener: () => jest.fn(),
}),
createNavigationContainerRef: jest.fn(),
};
});

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 = _.values(CONST.BETAS);
const mockedPersonalDetails = getMockedPersonalDetails(100);

beforeAll(() =>
Onyx.init({
keys: ONYXKEYS,
safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT],
registerStorageEventListener: () => {},
}),
);

// 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();
PusherHelper.teardown();
});

function SearchPageWrapper(args) {
return (
<ComposeProviders components={[OnyxProvider, CurrentReportIDContextProvider, KeyboardStateProvider, WindowDimensionsProvider, LocaleContextProvider]}>
<SearchPage
// eslint-disable-next-line react/jsx-props-no-spreading
{...args}
navigation={args.navigation}
/>
</ComposeProviders>
);
}

const runs = CONST.PERFORMANCE_TESTS.RUNS;

test('[Search Page] should interact when text input changes', async () => {
const {addListener} = TestHelper.createAddListenerMock();

const scenario = async () => {
await screen.findByTestId('SearchPage');

const input = screen.getByTestId('options-selector-input');
fireEvent.changeText(input, 'Email Four');
fireEvent.changeText(input, 'Report');
fireEvent.changeText(input, 'Email Five');
};

const navigation = {addListener};

return waitForBatchedUpdates()
.then(() =>
Onyx.multiSet({
...mockedReports,
[ONYXKEYS.IS_SIDEBAR_LOADED]: true,
[ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails,
[ONYXKEYS.BETAS]: mockedBetas,
[ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true,
}),
)
.then(() => measurePerformance(<SearchPageWrapper navigation={navigation} />, {scenario, runs}));
});

test('[Search Page] should render options list', async () => {
const {triggerTransitionEnd, addListener} = TestHelper.createAddListenerMock();
const smallMockedPersonalDetails = getMockedPersonalDetails(5);

const scenario = async () => {
await screen.findByTestId('SearchPage');
await act(triggerTransitionEnd);
await screen.findByText(smallMockedPersonalDetails['1'].login);
await screen.findByText(smallMockedPersonalDetails['2'].login);
};

const navigation = {addListener};

return waitForBatchedUpdates()
.then(() =>
Onyx.multiSet({
...mockedReports,
[ONYXKEYS.IS_SIDEBAR_LOADED]: true,
[ONYXKEYS.PERSONAL_DETAILS_LIST]: smallMockedPersonalDetails,
[ONYXKEYS.BETAS]: mockedBetas,
[ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true,
}),
)
.then(() => measurePerformance(<SearchPageWrapper navigation={navigation} />, {scenario, runs}));
});

test('[Search Page] should search in options list', async () => {
const {triggerTransitionEnd, addListener} = TestHelper.createAddListenerMock();

const scenario = async () => {
await screen.findByTestId('SearchPage');
const input = screen.getByTestId('options-selector-input');

fireEvent.changeText(input, mockedPersonalDetails['88'].login);
await act(triggerTransitionEnd);
await screen.findByText(mockedPersonalDetails['88'].login);

fireEvent.changeText(input, mockedPersonalDetails['45'].login);
await act(triggerTransitionEnd);
await screen.findByText(mockedPersonalDetails['45'].login);
};

const navigation = {addListener};

return waitForBatchedUpdates()
.then(() =>
Onyx.multiSet({
...mockedReports,
[ONYXKEYS.IS_SIDEBAR_LOADED]: true,
[ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails,
[ONYXKEYS.BETAS]: mockedBetas,
[ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true,
}),
)
.then(() => measurePerformance(<SearchPageWrapper navigation={navigation} />, {scenario, runs}));
});

test('[Search Page] should click on list item', async () => {
const {triggerTransitionEnd, addListener} = TestHelper.createAddListenerMock();

const scenario = async () => {
await screen.findByTestId('SearchPage');
const input = screen.getByTestId('options-selector-input');

fireEvent.changeText(input, mockedPersonalDetails['6'].login);
await act(triggerTransitionEnd);
const optionButton = await screen.findByText(mockedPersonalDetails['6'].login);

fireEvent.press(optionButton);
};

const navigation = {addListener};
return waitForBatchedUpdates()
.then(() =>
Onyx.multiSet({
...mockedReports,
[ONYXKEYS.IS_SIDEBAR_LOADED]: true,
[ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails,
[ONYXKEYS.BETAS]: mockedBetas,
[ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true,
}),
)
.then(() => measurePerformance(<SearchPageWrapper navigation={navigation} />, {scenario, runs}));
});
25 changes: 24 additions & 1 deletion tests/utils/TestHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,27 @@ function assertFormDataMatchesObject(formData, obj) {
expect(_.reduce(Array.from(formData.entries()), (memo, x) => ({...memo, [x[0]]: x[1]}), {})).toEqual(expect.objectContaining(obj));
}

export {getGlobalFetchMock, signInWithTestUser, signOutTestUser, setPersonalDetails, buildPersonalDetails, buildTestReportComment, assertFormDataMatchesObject};
/**
* This is a helper function to create a mock for the addListener function of the react-navigation library.
*
* @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};
};

export {getGlobalFetchMock, signInWithTestUser, signOutTestUser, setPersonalDetails, buildPersonalDetails, buildTestReportComment, assertFormDataMatchesObject, createAddListenerMock};
Loading