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: refactor tests for more complex components #40163

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type * as NativeNavigation from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
import {fireEvent, screen, waitFor} from '@testing-library/react-native';
import type {TextMatch} from '@testing-library/react-native/build/matches';
import {fireEvent, screen} from '@testing-library/react-native';
import React, {useMemo} from 'react';
import type {ComponentType} from 'react';
import Onyx from 'react-native-onyx';
Expand All @@ -23,7 +22,6 @@ import type {Beta, PersonalDetails, Report} from '@src/types/onyx';
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';
Expand Down Expand Up @@ -123,7 +121,6 @@ beforeEach(() => {
// Clear out Onyx after each test so that each test starts with a clean state
afterEach(() => {
Onyx.clear();
PusherHelper.teardown();
});

type ChatFinderPageProps = StackScreenProps<RootStackParamList, typeof SCREENS.CHAT_FINDER_ROOT> & {
Expand All @@ -134,12 +131,14 @@ type ChatFinderPageProps = StackScreenProps<RootStackParamList, typeof SCREENS.C

function ChatFinderPageWrapper(args: ChatFinderPageProps) {
return (
<ComposeProviders components={[OnyxProvider, LocaleContextProvider, OptionListContextProvider, KeyboardStateProvider]}>
<ChatFinderPage
// eslint-disable-next-line react/jsx-props-no-spreading
{...args}
navigation={args.navigation}
/>
<ComposeProviders components={[OnyxProvider, LocaleContextProvider, KeyboardStateProvider]}>
<OptionListContextProvider>
<ChatFinderPage
// eslint-disable-next-line react/jsx-props-no-spreading
{...args}
navigation={args.navigation}
/>
</OptionListContextProvider>
</ComposeProviders>
);
}
Expand All @@ -158,7 +157,7 @@ function ChatFinderPageWithCachedOptions(args: ChatFinderPageProps) {
);
}

test('[Search Page] should render list with cached options', async () => {
test('[ChatFinderPage] should render list with cached options', async () => {
const {addListener} = TestHelper.createAddListenerMock();

const scenario = async () => {
Expand All @@ -182,7 +181,7 @@ test('[Search Page] should render list with cached options', async () => {
);
});

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

const scenario = async () => {
Expand Down Expand Up @@ -210,94 +209,3 @@ test('[Search Page] should interact when text input changes', async () => {
.then(() => measurePerformance(<ChatFinderPageWrapper navigation={navigation} />, {scenario}))
);
});

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

const scenario = async () => {
await screen.findByTestId('ChatFinderPage');
await waitFor(triggerTransitionEnd as Awaited<() => Promise<void>>);
await screen.findByTestId('selection-list');
await screen.findByText(smallMockedPersonalDetails['1'].login as TextMatch);
await screen.findByText(smallMockedPersonalDetails['2'].login as TextMatch);
};

const navigation = {addListener};

return (
waitForBatchedUpdates()
.then(() =>
Onyx.multiSet({
...mockedReports,
[ONYXKEYS.PERSONAL_DETAILS_LIST]: smallMockedPersonalDetails,
[ONYXKEYS.BETAS]: mockedBetas,
[ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true,
}),
)
// @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
.then(() => measurePerformance(<ChatFinderPageWrapper navigation={navigation} />, {scenario}))
);
});

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

const scenario = async () => {
await screen.findByTestId('ChatFinderPage');
await waitFor(triggerTransitionEnd as Awaited<() => Promise<void>>);

const input = screen.getByTestId('selection-list-text-input');
const searchValue = mockedPersonalDetails['88'].login;

fireEvent.changeText(input, searchValue);
await screen.findByText(searchValue as TextMatch);
};

const navigation = {addListener};

return (
waitForBatchedUpdates()
.then(() =>
Onyx.multiSet({
...mockedReports,
[ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails,
[ONYXKEYS.BETAS]: mockedBetas,
[ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true,
}),
)
// @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
.then(() => measurePerformance(<ChatFinderPageWrapper navigation={navigation} />, {scenario}))
);
});

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

const scenario = async () => {
await screen.findByTestId('ChatFinderPage');
const input = screen.getByTestId('selection-list-text-input');
await waitFor(triggerTransitionEnd as Awaited<() => Promise<void>>);

const searchValue = mockedPersonalDetails['4'].login as TextMatch;
fireEvent.changeText(input, searchValue);

const optionButton = await screen.findByText(searchValue);
fireEvent.press(optionButton);
};

const navigation = {addListener};
return (
waitForBatchedUpdates()
.then(() =>
Onyx.multiSet({
...mockedReports,
[ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails,
[ONYXKEYS.BETAS]: mockedBetas,
[ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true,
}),
)
// @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
.then(() => measurePerformance(<ChatFinderPageWrapper navigation={navigation} />, {scenario}))
);
});
2 changes: 2 additions & 0 deletions tests/perf-test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ We use Reassure for monitoring performance regression. It helps us check if our
- The primary focus is on testing business cases rather than small, reusable parts that typically don't introduce regressions, although some tests in that area are still necessary.
- To achieve this goal, it's recommended to stay relatively high up in the React tree, targeting whole screens to recreate real-life scenarios that users may encounter.
- For example, consider scenarios where an additional `useMemo` call could impact performance negatively.
- Please note that high-complexity components, such as `ReportScreen` for example, may have many external dependencies (as well as their child components), which may cause tests to be flaky. Therefore, it is not recommended to add detailed tests to these types of components. Instead, add a test for those lower in the React tree (e.g. `Composer`).
- Make sure all additional dependencies are mocked correctly (navigation, contexts, external libraries, API etc.).

## `measureFunction` API approach

Expand Down
89 changes: 11 additions & 78 deletions tests/perf-test/ReportScreen.perf-test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {StackScreenProps} from '@react-navigation/stack';
import {fireEvent, screen, waitFor} from '@testing-library/react-native';
import {screen, waitFor} from '@testing-library/react-native';
import type {ComponentType} from 'react';
import React from 'react';
import Onyx from 'react-native-onyx';
Expand All @@ -16,7 +16,6 @@ import {CurrentReportIDContextProvider} from '@src/components/withCurrentReportI
import {KeyboardStateProvider} from '@src/components/withKeyboardState';
import {WindowDimensionsProvider} from '@src/components/withWindowDimensions';
import CONST from '@src/CONST';
import * as Localize from '@src/libs/Localize';
import ONYXKEYS from '@src/ONYXKEYS';
import {ReportAttachmentsProvider} from '@src/pages/home/report/ReportAttachmentsContext';
import ReportScreen from '@src/pages/home/ReportScreen';
Expand All @@ -36,6 +35,12 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch

type ReportScreenWrapperProps = StackScreenProps<CentralPaneNavigatorParamList, typeof SCREENS.REPORT>;

jest.mock('@src/libs/API', () => ({
write: jest.fn(),
makeRequestWithSideEffects: jest.fn(),
read: jest.fn(),
}));

jest.mock('react-native-reanimated', () => {
const actualNav = jest.requireActual('react-native-reanimated/mock');
return {
Expand Down Expand Up @@ -156,10 +161,10 @@ function ReportScreenWrapper(props: ReportScreenWrapperProps) {
}

const report = {...createRandomReport(1), policyID: '1'};
const reportActions = ReportTestUtils.getMockedReportActionsMap(500);
const reportActions = ReportTestUtils.getMockedReportActionsMap(1000);
const mockRoute = {params: {reportID: '1'}};

test('[ReportScreen] should render ReportScreen with composer interactions', () => {
test('[ReportScreen] should render ReportScreen', () => {
const {triggerTransitionEnd, addListener} = TestHelper.createAddListenerMock();
const scenario = async () => {
/**
Expand All @@ -173,83 +178,10 @@ test('[ReportScreen] should render ReportScreen with composer interactions', ()
await waitFor(triggerTransitionEnd);

// Query for the composer
const composer = await screen.findByTestId('composer');

// Type in the composer
fireEvent.changeText(composer, 'Test message');

const hintSendButtonText = Localize.translateLocal('common.send');

// Query for the send button
const sendButton = await screen.findByLabelText(hintSendButtonText);

// Click on the send button
fireEvent.press(sendButton);

const hintHeaderText = Localize.translateLocal('common.back');

// Query for the header
await screen.findByLabelText(hintHeaderText);
};

const navigation = {addListener};

return waitForBatchedUpdates()
.then(() => {
const reportCollectionDataSet: ReportCollectionDataSet = {
[`${ONYXKEYS.COLLECTION.REPORT}${mockRoute.params.reportID}`]: report,
};

const reportActionsCollectionDataSet: ReportActionsCollectionDataSet = {
[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${mockRoute.params.reportID}`]: reportActions,
};

return Onyx.multiSet({
[ONYXKEYS.IS_SIDEBAR_LOADED]: true,
[ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetails,
[ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS],
[`${ONYXKEYS.COLLECTION.POLICY}`]: policies,
[ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT]: true,
...reportCollectionDataSet,
...reportActionsCollectionDataSet,
});
})
.then(() =>
measurePerformance(
<ReportScreenWrapper
// @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
navigation={navigation}
// @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
route={mockRoute}
/>,
{scenario},
),
);
});

test.skip('[ReportScreen] should press of the report item', () => {
const {triggerTransitionEnd, addListener} = TestHelper.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 waitFor(triggerTransitionEnd);
await screen.findByTestId('composer');
OlimpiaZurek marked this conversation as resolved.
Show resolved Hide resolved

// Query for the report list
await screen.findByTestId('report-actions-list');

const hintText = Localize.translateLocal('accessibilityHints.chatMessage');

// Query for the list of items
const reportItems = await screen.findAllByLabelText(hintText);

fireEvent.press(reportItems[0], 'onLongPress');
};

const navigation = {addListener};
Expand All @@ -269,6 +201,7 @@ test.skip('[ReportScreen] should press of the report item', () => {
[ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetails,
[ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS],
[`${ONYXKEYS.COLLECTION.POLICY}`]: policies,
[ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT]: true,
...reportCollectionDataSet,
...reportActionsCollectionDataSet,
});
Expand Down
Loading
Loading