diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 7ce9114553f5..cc576244a193 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -702,6 +702,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro setIsLinkingToMessage(!!reportActionIDFromRoute); return null; } + console.log('test') return ( diff --git a/tests/perf-test/BaseOptionsList.perf-test.tsx b/tests/perf-test/BaseOptionsList.perf-test.tsx index dc5768610861..0971b9290939 100644 --- a/tests/perf-test/BaseOptionsList.perf-test.tsx +++ b/tests/perf-test/BaseOptionsList.perf-test.tsx @@ -1,10 +1,11 @@ import {fireEvent} from '@testing-library/react-native'; import type {RenderResult} from '@testing-library/react-native'; import React, {useState} from 'react'; -import {measurePerformance} from 'reassure'; +import {measureRenders} from 'reassure'; import BaseOptionsList from '@components/OptionsList/BaseOptionsList'; import type {OptionData} from '@libs/ReportUtils'; import variables from '@styles/variables'; +import wrapInAct from '../utils/wrapInActHelper'; type BaseOptionsListWrapperProps = { /** Whether this is a multi-select list */ @@ -66,32 +67,41 @@ describe('[BaseOptionsList]', () => { ); } - test('Should render 1 section and a thousand items', () => { - measurePerformance(); + test('Should render 1 section and a thousand items', async () => { + await measureRenders(); }); - test('Should press a list item', () => { + test('Should press a list item', async () => { // eslint-disable-next-line @typescript-eslint/require-await const scenario = async (screen: RenderResult) => { - fireEvent.press(screen.getByText('Item 5')); + // use act to ensure that any state updates caused by interactions are processed correctly before we proceed with any further logic, + // otherwise the error "An update to BaseOptionsList inside a test was not wrapped in act(...)" will be thrown + // eslint-disable-next-line testing-library/no-unnecessary-act + await wrapInAct(async () => { + fireEvent.press(await screen.findByText('Item 5')); + }); }; - measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); - test('Should render multiple selection and select 4 items', () => { + test('Should render multiple selection and select 4 items', async () => { // eslint-disable-next-line @typescript-eslint/require-await const scenario = async (screen: RenderResult) => { - fireEvent.press(screen.getByText('Item 1')); - fireEvent.press(screen.getByText('Item 2')); - fireEvent.press(screen.getByText('Item 3')); - fireEvent.press(screen.getByText('Item 4')); + // use act to ensure that any state updates caused by interactions are processed correctly before we proceed with any further logic, + // otherwise the error "An update to BaseOptionsList inside a test was not wrapped in act(...)" will be thrown + await wrapInAct(async () => { + fireEvent.press(await screen.findByText('Item 1')); + fireEvent.press(screen.getByText('Item 2')); + fireEvent.press(screen.getByText('Item 3')); + fireEvent.press(screen.getByText('Item 4')); + }); }; - measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); - test('Should scroll and select a few items', () => { + test('Should scroll and select a few items', async () => { const eventData = { nativeEvent: { contentOffset: { @@ -112,14 +122,18 @@ describe('[BaseOptionsList]', () => { // eslint-disable-next-line @typescript-eslint/require-await const scenario = async (screen: RenderResult) => { - fireEvent.press(screen.getByText('Item 1')); - // see https://github.com/callstack/react-native-testing-library/issues/1540 - fireEvent(screen.getByTestId('options-list'), 'onContentSizeChange', eventData.nativeEvent.contentSize.width, eventData.nativeEvent.contentSize.height); - fireEvent.scroll(screen.getByTestId('options-list'), eventData); - fireEvent.press(screen.getByText('Item 7')); - fireEvent.press(screen.getByText('Item 15')); + // use act to ensure that any state updates caused by interactions are processed correctly before we proceed with any further logic, + // otherwise the error "An update to BaseOptionsList inside a test was not wrapped in act(...)" will be thrown + await wrapInAct(async () => { + fireEvent.press(await screen.findByText('Item 1')); + // see https://github.com/callstack/react-native-testing-library/issues/1540 + fireEvent(await screen.findByTestId('options-list'), 'onContentSizeChange', eventData.nativeEvent.contentSize.width, eventData.nativeEvent.contentSize.height); + fireEvent.scroll(await screen.findByTestId('options-list'), eventData); + fireEvent.press(await screen.findByText('Item 7')); + fireEvent.press(await screen.findByText('Item 15')); + }); }; - measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); }); diff --git a/tests/perf-test/ChatFinderPage.perf-test.tsx b/tests/perf-test/ChatFinderPage.perf-test.tsx index 4346977a1cd0..a26b2caaa76c 100644 --- a/tests/perf-test/ChatFinderPage.perf-test.tsx +++ b/tests/perf-test/ChatFinderPage.perf-test.tsx @@ -5,7 +5,7 @@ import React, {useMemo} from 'react'; import type {ComponentType} from 'react'; import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {measurePerformance} from 'reassure'; +import {measureRenders} from 'reassure'; import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OptionListContextProvider, {OptionsListContext} from '@components/OptionListContextProvider'; import {KeyboardStateProvider} from '@components/withKeyboardState'; @@ -187,7 +187,7 @@ test('[ChatFinderPage] should render list with cached options', async () => { }), ) .then(() => - measurePerformance( + measureRenders( { }), ) .then(() => - measurePerformance( + measureRenders( ({ useAnimatedRef: jest.fn(), })); +jest.mock('../../src/libs/Navigation/Navigation', () => ({ + navigate: jest.fn(), + getReportRHPActiveRoute: jest.fn(), +})); + jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); return { @@ -105,7 +110,7 @@ test('[ReportActionCompose] should render Composer with text input interactions' }; await waitForBatchedUpdates(); - await measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); test('[ReportActionCompose] should scroll to hide suggestions', async () => { @@ -118,7 +123,7 @@ test('[ReportActionCompose] should scroll to hide suggestions', async () => { }; await waitForBatchedUpdates(); - await measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); test('[ReportActionCompose] should press to block suggestions', async () => { @@ -131,7 +136,7 @@ test('[ReportActionCompose] should press to block suggestions', async () => { }; await waitForBatchedUpdates(); - await measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); test('[ReportActionCompose] should press add attachemnt button', async () => { @@ -144,7 +149,7 @@ test('[ReportActionCompose] should press add attachemnt button', async () => { }; await waitForBatchedUpdates(); - await measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); test('[ReportActionCompose] should press add emoji button', async () => { @@ -157,7 +162,7 @@ test('[ReportActionCompose] should press add emoji button', async () => { }; await waitForBatchedUpdates(); - await measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); test('[ReportActionCompose] should press send message button', async () => { @@ -170,7 +175,7 @@ test('[ReportActionCompose] should press send message button', async () => { }; await waitForBatchedUpdates(); - await measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); test('[ReportActionCompose] press add attachment button', async () => { @@ -182,7 +187,7 @@ test('[ReportActionCompose] press add attachment button', async () => { }; await waitForBatchedUpdates(); - await measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); test('[ReportActionCompose] should press split bill button', async () => { @@ -193,7 +198,7 @@ test('[ReportActionCompose] should press split bill button', async () => { }; await waitForBatchedUpdates(); - await measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); test('[ReportActionCompose] should press assign task button', async () => { @@ -204,5 +209,5 @@ test('[ReportActionCompose] should press assign task button', async () => { }; await waitForBatchedUpdates(); - await measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); diff --git a/tests/perf-test/ReportScreen.perf-test.tsx b/tests/perf-test/ReportScreen.perf-test.tsx index 95ac9729e606..550b6adabc36 100644 --- a/tests/perf-test/ReportScreen.perf-test.tsx +++ b/tests/perf-test/ReportScreen.perf-test.tsx @@ -1,12 +1,12 @@ import type {StackNavigationProp, StackScreenProps} from '@react-navigation/stack'; -import {screen} from '@testing-library/react-native'; +import {screen, waitFor} from '@testing-library/react-native'; import type {ComponentType} from 'react'; import React from 'react'; import type ReactNative from 'react-native'; import {Dimensions, InteractionManager} from 'react-native'; import Onyx from 'react-native-onyx'; import type Animated from 'react-native-reanimated'; -import {measurePerformance} from 'reassure'; +import {measureRenders} from 'reassure'; import type {WithNavigationFocusProps} from '@components/withNavigationFocus'; import type Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; @@ -206,7 +206,10 @@ const mockRoute = {params: {reportID: '1', reportActionID: ''}, key: 'Report', n test('[ReportScreen] should render ReportScreen', async () => { const {addListener} = createAddListenerMock(); const scenario = async () => { - await screen.findByTestId(`report-screen-${report.reportID}`); + // wrapp the screen with waitFor to wait for the screen to be rendered + await waitFor(async () => { + await screen.findByTestId(`report-screen-${report.reportID}`); + }); }; const navigation = {addListener} as unknown as StackNavigationProp; @@ -228,7 +231,8 @@ test('[ReportScreen] should render ReportScreen', async () => { ...reportCollectionDataSet, ...reportActionsCollectionDataSet, }); - await measurePerformance( + + await measureRenders( { test('[ReportScreen] should render composer', async () => { const {addListener} = createAddListenerMock(); const scenario = async () => { - await screen.findByTestId('composer'); + await waitFor(async () => { + await screen.findByTestId('composer'); + }); }; const navigation = {addListener} as unknown as StackNavigationProp; @@ -263,7 +269,7 @@ test('[ReportScreen] should render composer', async () => { ...reportCollectionDataSet, ...reportActionsCollectionDataSet, }); - await measurePerformance( + await measureRenders( { test('[ReportScreen] should render report list', async () => { const {addListener} = createAddListenerMock(); const scenario = async () => { - await screen.findByTestId('report-actions-list'); + await waitFor(async () => { + await screen.findByTestId('report-actions-list'); + }); }; const navigation = {addListener} as unknown as StackNavigationProp; @@ -303,7 +311,8 @@ test('[ReportScreen] should render report list', async () => { ...reportCollectionDataSet, ...reportActionsCollectionDataSet, }); - await measurePerformance( + + await measureRenders( { - measureRenders(); +test('[SelectionList] should render 1 section and a thousand items', async () => { + await measureRenders(); }); -test('[SelectionList] should press a list item', () => { +test('[SelectionList] should press a list item', async () => { // eslint-disable-next-line @typescript-eslint/require-await const scenario = async (screen: RenderResult) => { fireEvent.press(screen.getByText('Item 5')); }; - measureRenders(, {scenario}); + await measureRenders(, {scenario}); }); -test('[SelectionList] should render multiple selection and select 3 items', () => { +test('[SelectionList] should render multiple selection and select 3 items', async () => { // eslint-disable-next-line @typescript-eslint/require-await const scenario = async (screen: RenderResult) => { fireEvent.press(screen.getByText('Item 1')); @@ -147,10 +147,10 @@ test('[SelectionList] should render multiple selection and select 3 items', () = fireEvent.press(screen.getByText('Item 3')); }; - measureRenders(, {scenario}); + await measureRenders(, {scenario}); }); -test('[SelectionList] should scroll and select a few items', () => { +test('[SelectionList] should scroll and select a few items', async () => { const eventData = { nativeEvent: { contentOffset: { @@ -179,5 +179,5 @@ test('[SelectionList] should scroll and select a few items', () => { fireEvent.press(screen.getByText('Item 15')); }; - measureRenders(, {scenario}); + await measureRenders(, {scenario}); }); diff --git a/tests/perf-test/SidebarLinks.perf-test.tsx b/tests/perf-test/SidebarLinks.perf-test.tsx index 9a385c1917fb..d1942522af38 100644 --- a/tests/perf-test/SidebarLinks.perf-test.tsx +++ b/tests/perf-test/SidebarLinks.perf-test.tsx @@ -1,12 +1,13 @@ -import {fireEvent, screen} from '@testing-library/react-native'; +import {fireEvent, screen, waitFor} from '@testing-library/react-native'; import Onyx from 'react-native-onyx'; -import {measurePerformance} from 'reassure'; +import {measureRenders} from 'reassure'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import * as LHNTestUtils from '../utils/LHNTestUtils'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +import wrapInAct from '../utils/wrapInActHelper'; import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; jest.mock('@libs/Permissions'); @@ -43,7 +44,7 @@ const getMockedReportsMap = (length = 100) => { return mockReports; }; -const mockedResponseMap = getMockedReportsMap(5); +const mockedResponseMap = getMockedReportsMap(500); describe('SidebarLinks', () => { beforeAll(() => { @@ -68,8 +69,10 @@ describe('SidebarLinks', () => { test('[SidebarLinks] should render Sidebar with 500 reports stored', async () => { const scenario = async () => { - // Query for the sidebar - await screen.findByTestId('lhn-options-list'); + await waitFor(async () => { + // Query for the sidebar + await screen.findByTestId('lhn-options-list'); + }); }; await waitForBatchedUpdates(); @@ -82,16 +85,18 @@ describe('SidebarLinks', () => { ...mockedResponseMap, }); - await measurePerformance(, {scenario, runs: 1}); + await measureRenders(, {scenario}); }); test('[SidebarLinks] should render list itmes', async () => { const scenario = async () => { - /** - * Query for display names of participants [1, 2]. - * This will ensure that the sidebar renders a list of items. - */ - await screen.findAllByText('Email Two'); + await waitFor(async () => { + /** + * Query for display names of participants [1, 2]. + * This will ensure that the sidebar renders a list of items. + */ + await screen.findAllByText('Email Two'); + }); }; await waitForBatchedUpdates(); @@ -104,7 +109,7 @@ describe('SidebarLinks', () => { ...mockedResponseMap, }); - await measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); test('[SidebarLinks] should scroll through the list of items ', async () => { @@ -127,9 +132,11 @@ describe('SidebarLinks', () => { }, }; - const lhnOptionsList = await screen.findByTestId('lhn-options-list'); + await wrapInAct(async () => { + const lhnOptionsList = await screen.findByTestId('lhn-options-list'); - fireEvent.scroll(lhnOptionsList, eventData); + fireEvent.scroll(lhnOptionsList, eventData); + }); }; await waitForBatchedUpdates(); @@ -142,13 +149,15 @@ describe('SidebarLinks', () => { ...mockedResponseMap, }); - await measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); test('[SidebarLinks] should click on list item', async () => { const scenario = async () => { - const button = await screen.findByTestId('1'); - fireEvent.press(button); + await wrapInAct(async () => { + const button = await screen.findByTestId('1'); + fireEvent.press(button); + }); }; await waitForBatchedUpdates(); @@ -161,6 +170,6 @@ describe('SidebarLinks', () => { ...mockedResponseMap, }); - await measurePerformance(, {scenario}); + await measureRenders(, {scenario}); }); }); diff --git a/tests/utils/wrapInActHelper.ts b/tests/utils/wrapInActHelper.ts new file mode 100644 index 000000000000..7272474b35e0 --- /dev/null +++ b/tests/utils/wrapInActHelper.ts @@ -0,0 +1,19 @@ +import {act} from 'react-test-renderer'; + +/** + * A utility function to wrap custom state updates in act(). + * + * While React Native Testing Library's render and event methods already + * wrap their calls in act() under the hood, this function is useful for + * explicitly wrapping custom callbacks that might trigger state updates + * outside of the RNTL's built-in mechanisms. This helps avoid warnings about + * updates not being wrapped in act() and ensures that all updates are handled + * consistently during the test lifecycle. + */ +const wrapInAct = async (callback: () => Promise | void) => { + await act(async () => { + await callback(); + }); +}; + +export default wrapInAct;