diff --git a/tests/perf-test/GooglePlacesUtils.perf-test.js b/tests/perf-test/GooglePlacesUtils.perf-test.js index 6c453ae9ddb3..674fb4329205 100644 --- a/tests/perf-test/GooglePlacesUtils.perf-test.js +++ b/tests/perf-test/GooglePlacesUtils.perf-test.js @@ -151,7 +151,7 @@ const bigObjectToFind = { * More on the measureFunction API: * @see https://callstack.github.io/reassure/docs/api#measurefunction-function */ -test('getAddressComponents on a big dataset', async () => { +test('[GooglePlacesUtils] getAddressComponents on a big dataset', async () => { await measureFunction( () => { GooglePlacesUtils.getAddressComponents(addressComponents, bigObjectToFind); diff --git a/tests/perf-test/ReportActionCompose.perf-test.js b/tests/perf-test/ReportActionCompose.perf-test.js index 05d2b5a4906a..de34c139a361 100644 --- a/tests/perf-test/ReportActionCompose.perf-test.js +++ b/tests/perf-test/ReportActionCompose.perf-test.js @@ -80,7 +80,7 @@ function ReportActionComposeWrapper() { } const mockEvent = {preventDefault: jest.fn()}; -test('should render Composer with text input interactions', async () => { +test('[ReportActionCompose] should render Composer with text input interactions', async () => { const scenario = async () => { // Query for the composer const composer = await screen.findByTestId('composer'); @@ -99,7 +99,7 @@ test('should render Composer with text input interactions', async () => { return waitForBatchedUpdates().then(() => measurePerformance(, {scenario, runs})); }); -test('should press add attachemnt button', async () => { +test('[ReportActionCompose] should press add attachemnt button', async () => { const scenario = async () => { // Query for the attachment button const hintAttachmentButtonText = Localize.translateLocal('reportActionCompose.addAction'); @@ -111,7 +111,7 @@ test('should press add attachemnt button', async () => { return waitForBatchedUpdates().then(() => measurePerformance(, {scenario, runs})); }); -test('should press add emoji button', async () => { +test('[ReportActionCompose] should press add emoji button', async () => { const scenario = async () => { // Query for the emoji button const hintEmojiButtonText = Localize.translateLocal('reportActionCompose.emoji'); @@ -123,7 +123,7 @@ test('should press add emoji button', async () => { return waitForBatchedUpdates().then(() => measurePerformance(, {scenario, runs})); }); -test('should press send message button', async () => { +test('[ReportActionCompose] should press send message button', async () => { const scenario = async () => { // Query for the send button const hintSendButtonText = Localize.translateLocal('common.send'); @@ -135,7 +135,7 @@ test('should press send message button', async () => { return waitForBatchedUpdates().then(() => measurePerformance(, {scenario, runs})); }); -test('render composer with attachement modal interactions', async () => { +test('[ReportActionCompose] render composer with attachement modal interactions', async () => { const scenario = async () => { const hintAddAttachmentButtonText = Localize.translateLocal('reportActionCompose.addAttachment'); const hintAssignTaskButtonText = Localize.translateLocal('newTaskPage.assignTask'); diff --git a/tests/perf-test/ReportActionsList.perf-test.js b/tests/perf-test/ReportActionsList.perf-test.js index ee0fdcb135fc..8e3312cfa4c7 100644 --- a/tests/perf-test/ReportActionsList.perf-test.js +++ b/tests/perf-test/ReportActionsList.perf-test.js @@ -99,7 +99,7 @@ function ReportActionsListWrapper() { const runs = CONST.PERFORMANCE_TESTS.RUNS; -test('should render ReportActionsList with 500 reportActions stored', () => { +test('[ReportActionsList] should render ReportActionsList with 500 reportActions stored', () => { const scenario = async () => { await screen.findByTestId('report-actions-list'); const hintText = Localize.translateLocal('accessibilityHints.chatMessage'); @@ -116,7 +116,7 @@ test('should render ReportActionsList with 500 reportActions stored', () => { .then(() => measurePerformance(, {scenario, runs})); }); -test('should scroll and click some of the reports', () => { +test('[ReportActionsList] should scroll and click some of the reports', () => { const eventData = { nativeEvent: { contentOffset: { diff --git a/tests/perf-test/ReportActionsUtils.perf-test.ts b/tests/perf-test/ReportActionsUtils.perf-test.ts index a52e75c8b75f..d4a1b9b58e44 100644 --- a/tests/perf-test/ReportActionsUtils.perf-test.ts +++ b/tests/perf-test/ReportActionsUtils.perf-test.ts @@ -58,7 +58,7 @@ const runs = CONST.PERFORMANCE_TESTS.RUNS; * More on the measureFunction API: * @see https://callstack.github.io/reassure/docs/api#measurefunction-function */ -test('getLastVisibleAction on 10k reportActions', async () => { +test('[ReportActionsUtils] getLastVisibleAction on 10k reportActions', async () => { await Onyx.multiSet({ ...mockedReportActionsMap, }); @@ -67,7 +67,7 @@ test('getLastVisibleAction on 10k reportActions', async () => { await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId), {runs}); }); -test('getLastVisibleAction on 10k reportActions with actionsToMerge', async () => { +test('[ReportActionsUtils] getLastVisibleAction on 10k reportActions with actionsToMerge', async () => { const parentReportActionId = '1'; const fakeParentAction = reportActions[parentReportActionId]; const actionsToMerge = { @@ -96,7 +96,7 @@ test('getLastVisibleAction on 10k reportActions with actionsToMerge', async () = await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId, actionsToMerge), {runs}); }); -test('getMostRecentIOURequestActionID on 10k ReportActions', async () => { +test('[ReportActionsUtils] getMostRecentIOURequestActionID on 10k ReportActions', async () => { const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); await Onyx.multiSet({ ...mockedReportActionsMap, @@ -105,7 +105,7 @@ test('getMostRecentIOURequestActionID on 10k ReportActions', async () => { await measureFunction(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActionsArray), {runs}); }); -test('getLastVisibleMessage on 10k ReportActions', async () => { +test('[ReportActionsUtils] getLastVisibleMessage on 10k ReportActions', async () => { await Onyx.multiSet({ ...mockedReportActionsMap, }); @@ -113,7 +113,7 @@ test('getLastVisibleMessage on 10k ReportActions', async () => { await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId), {runs}); }); -test('getLastVisibleMessage on 10k ReportActions with actionsToMerge', async () => { +test('[ReportActionsUtils] getLastVisibleMessage on 10k ReportActions with actionsToMerge', async () => { const parentReportActionId = '1'; const fakeParentAction = reportActions[parentReportActionId]; const actionsToMerge = { @@ -142,7 +142,7 @@ test('getLastVisibleMessage on 10k ReportActions with actionsToMerge', async () await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId, actionsToMerge), {runs}); }); -test('getSortedReportActionsForDisplay on 10k ReportActions', async () => { +test('[ReportActionsUtils] getSortedReportActionsForDisplay on 10k ReportActions', async () => { await Onyx.multiSet({ ...mockedReportActionsMap, }); @@ -150,7 +150,7 @@ test('getSortedReportActionsForDisplay on 10k ReportActions', async () => { await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions), {runs}); }); -test('getLastClosedReportAction on 10k ReportActions', async () => { +test('[ReportActionsUtils] getLastClosedReportAction on 10k ReportActions', async () => { await Onyx.multiSet({ ...mockedReportActionsMap, }); @@ -158,7 +158,7 @@ test('getLastClosedReportAction on 10k ReportActions', async () => { await measureFunction(() => ReportActionsUtils.getLastClosedReportAction(reportActions), {runs}); }); -test('getMostRecentReportActionLastModified', async () => { +test('[ReportActionsUtils] getMostRecentReportActionLastModified', async () => { await Onyx.multiSet({ ...mockedReportActionsMap, }); diff --git a/tests/perf-test/ReportScreen.perf-test.js b/tests/perf-test/ReportScreen.perf-test.js index b901888bba0f..96514112cd05 100644 --- a/tests/perf-test/ReportScreen.perf-test.js +++ b/tests/perf-test/ReportScreen.perf-test.js @@ -124,7 +124,7 @@ function ReportScreenWrapper(args) { const runs = CONST.PERFORMANCE_TESTS.RUNS; -test('should render ReportScreen with composer interactions', () => { +test('[ReportScreen] should render ReportScreen with composer interactions', () => { const scenario = async () => { // Query for the report list await screen.findByTestId('report-actions-list'); @@ -175,7 +175,7 @@ test('should render ReportScreen with composer interactions', () => { .then(() => measurePerformance(, {scenario, runs})); }); -test('should press of the report item', () => { +test('[ReportScreen] should press of the report item', () => { const scenario = async () => { // Query for the report list await screen.findByTestId('report-actions-list'); diff --git a/tests/perf-test/ReportUtils.perf-test.ts b/tests/perf-test/ReportUtils.perf-test.ts new file mode 100644 index 000000000000..ab6ee72a0082 --- /dev/null +++ b/tests/perf-test/ReportUtils.perf-test.ts @@ -0,0 +1,267 @@ +import {randAmount} from '@ngneat/falso'; +import Onyx from 'react-native-onyx'; +import {measureFunction} from 'reassure'; +import * as ReportUtils from '@libs/ReportUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import {PersonalDetails, Policy, Report, ReportAction} from '@src/types/onyx'; +import createCollection from '../utils/collections/createCollection'; +import createPersonalDetails from '../utils/collections/personalDetails'; +import createRandomPolicy from '../utils/collections/policies'; +import createRandomReportAction from '../utils/collections/reportActions'; +import createRandomReport from '../utils/collections/reports'; +import createRandomTransaction from '../utils/collections/transaction'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; + +const runs = CONST.PERFORMANCE_TESTS.RUNS; + +beforeAll(() => + Onyx.init({ + keys: ONYXKEYS, + safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], + }), +); + +// Clear out Onyx after each test so that each test starts with a clean state +afterEach(() => { + Onyx.clear(); +}); + +const getMockedReports = (length = 500) => + createCollection( + (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, + (index) => createRandomReport(index), + length, + ); + +const getMockedPolicies = (length = 500) => + createCollection( + (item) => `${ONYXKEYS.COLLECTION.POLICY}${item.id}`, + (index) => createRandomPolicy(index), + length, + ); + +const personalDetails = createCollection( + (item) => item.accountID, + (index) => createPersonalDetails(index), + 1000, +); + +const mockedReportsMap = getMockedReports(5000) as Record<`${typeof ONYXKEYS.COLLECTION.REPORT}`, Report>; +const mockedPoliciesMap = getMockedPolicies(5000) as Record<`${typeof ONYXKEYS.COLLECTION.POLICY}`, Policy>; +const participantAccountIDs = Array.from({length: 1000}, (v, i) => i + 1); + +test('[ReportUtils] findLastAccessedReport on 2k reports and policies', async () => { + const ignoreDomainRooms = true; + const isFirstTimeNewExpensifyUser = true; + const reports = getMockedReports(2000); + const policies = getMockedPolicies(2000); + const openOnAdminRoom = true; + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.findLastAccessedReport(reports, ignoreDomainRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom), {runs}); +}); + +test('[ReportUtils] canDeleteReportAction on 5k reports and policies', async () => { + const reportID = '1'; + + const reportAction = {...createRandomReportAction(1), actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT} as unknown as ReportAction; + + await Onyx.multiSet({ + ...mockedPoliciesMap, + ...mockedReportsMap, + }); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.canDeleteReportAction(reportAction, reportID), {runs}); +}); + +test('[ReportUtils] getReportRecipientAccountID on 1k participants', async () => { + const report = {...createRandomReport(1), participantAccountIDs}; + const currentLoginAccountID = 1; + + await Onyx.multiSet({ + ...mockedReportsMap, + }); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getReportRecipientAccountIDs(report, currentLoginAccountID), {runs}); +}); + +test('[ReportUtils] getIconsForParticipants on 1k participants', async () => { + const participants = Array.from({length: 1000}, (v, i) => i + 1); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getIconsForParticipants(participants, personalDetails), {runs}); +}); + +test('[ReportUtils] getIcons on 1k participants', async () => { + const report = {...createRandomReport(1), parentReportID: '1', parentReportActionID: '1', type: CONST.REPORT.TYPE.CHAT}; + const policy = createRandomPolicy(1); + const defaultIcon = null; + const defaultName = ''; + const defaultIconId = -1; + + await Onyx.multiSet({ + [ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetails, + }); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getIcons(report, personalDetails, defaultIcon, defaultName, defaultIconId, policy), {runs}); +}); + +test('[ReportUtils] getDisplayNamesWithTooltips 1k participants', async () => { + const isMultipleParticipantReport = true; + const shouldFallbackToHidden = true; + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getDisplayNamesWithTooltips(personalDetails, isMultipleParticipantReport, shouldFallbackToHidden), {runs}); +}); + +test('[ReportUtils] getReportPreviewMessage on 5k policies', async () => { + const reportAction = createRandomReportAction(1); + const report = createRandomReport(1); + const policy = createRandomPolicy(1); + const shouldConsiderReceiptBeingScanned = true; + const isPreviewMessageForParentChatReport = true; + + await Onyx.multiSet({ + ...mockedPoliciesMap, + }); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getReportPreviewMessage(report, reportAction, shouldConsiderReceiptBeingScanned, isPreviewMessageForParentChatReport, policy), {runs}); +}); + +test('[ReportUtils] getModifiedExpenseMessage on 5k reports and policies', async () => { + const reportAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE, + originalMessage: { + originalMessage: { + amount: randAmount(), + currency: CONST.CURRENCY.USD, + oldAmount: randAmount(), + oldCurrency: CONST.CURRENCY.USD, + }, + }, + }; + + await Onyx.multiSet({ + ...mockedPoliciesMap, + ...mockedReportsMap, + }); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getModifiedExpenseMessage(reportAction), {runs}); +}); + +test('[ReportUtils] getReportName on 1k participants', async () => { + const report = {...createRandomReport(1), chatType: undefined, participantAccountIDs}; + const policy = createRandomPolicy(1); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getReportName(report, policy), {runs}); +}); + +test('[ReportUtils] canShowReportRecipientLocalTime on 1k participants', async () => { + const report = {...createRandomReport(1), participantAccountIDs}; + const accountID = 1; + await Onyx.multiSet({ + ...mockedReportsMap, + }); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.canShowReportRecipientLocalTime(personalDetails, report, accountID), {runs}); +}); + +test('[ReportUtils] shouldReportBeInOptionList on 1k participant', async () => { + const report = {...createRandomReport(1), participantAccountIDs, type: CONST.REPORT.TYPE.CHAT}; + const currentReportId = '2'; + const isInGSDMode = true; + const betas = [CONST.BETAS.DEFAULT_ROOMS]; + const policies = getMockedPolicies(); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas, policies), {runs}); +}); + +test('[ReportUtils] getWorkspaceIcon on 5k policies', async () => { + const report = createRandomReport(1); + const policy = createRandomPolicy(1); + + await Onyx.multiSet({ + ...mockedPoliciesMap, + }); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getWorkspaceIcon(report, policy), {runs}); +}); + +test('[ReportUtils] getMoneyRequestOptions on 1k participants', async () => { + const report = {...createRandomReport(1), type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: true}; + const reportParticipants = Array.from({length: 1000}, (v, i) => i + 1); + + await Onyx.multiSet({ + ...mockedPoliciesMap, + }); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getMoneyRequestOptions(report, reportParticipants), {runs}); +}); + +test('[ReportUtils] getWorkspaceAvatar on 5k policies', async () => { + const report = createRandomReport(1); + + await Onyx.multiSet({ + ...mockedPoliciesMap, + }); + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getWorkspaceAvatar(report), {runs}); +}); + +test('[ReportUtils] getWorkspaceChat on 5k policies', async () => { + const policyID = '1'; + const accountsID = Array.from({length: 20}, (v, i) => i + 1); + + await Onyx.multiSet({ + ...mockedReportsMap, + }); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getWorkspaceChats(policyID, accountsID), {runs}); +}); + +test('[ReportUtils] getTransactionDetails on 5k reports', async () => { + const transaction = createRandomTransaction(1); + + await Onyx.multiSet({ + ...mockedReportsMap, + }); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getTransactionDetails(transaction, 'yyyy-MM-dd'), {runs}); +}); + +test('[ReportUtils] getIOUReportActionDisplayMessage on 5k policies', async () => { + const reportAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + originalMessage: { + IOUReportID: '1', + IOUTransactionID: '1', + amount: 100, + participantAccountID: 1, + currency: CONST.CURRENCY.USD, + type: CONST.IOU.REPORT_ACTION_TYPE.PAY, + paymentType: CONST.IOU.PAYMENT_TYPE.EXPENSIFY, + }, + }; + + await Onyx.multiSet({ + ...mockedPoliciesMap, + }); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getIOUReportActionDisplayMessage(reportAction), {runs}); +}); diff --git a/tests/perf-test/SelectionList.perf-test.js b/tests/perf-test/SelectionList.perf-test.js index fd607269ec6e..355449b962f3 100644 --- a/tests/perf-test/SelectionList.perf-test.js +++ b/tests/perf-test/SelectionList.perf-test.js @@ -95,11 +95,11 @@ function SelectionListWrapper(args) { const runs = CONST.PERFORMANCE_TESTS.RUNS; -test('should render 1 section and a thousand items', () => { +test('[SelectionList] should render 1 section and a thousand items', () => { measurePerformance(); }); -test('should press a list item', () => { +test('[SelectionList] should press a list item', () => { const scenario = (screen) => { fireEvent.press(screen.getByText('Item 5')); }; @@ -107,7 +107,7 @@ test('should press a list item', () => { measurePerformance(, {scenario, runs}); }); -test('should render multiple selection and select 3 items', () => { +test('[SelectionList] should render multiple selection and select 3 items', () => { const scenario = (screen) => { fireEvent.press(screen.getByText('Item 1')); fireEvent.press(screen.getByText('Item 2')); @@ -117,7 +117,7 @@ test('should render multiple selection and select 3 items', () => { measurePerformance(, {scenario, runs}); }); -test('should scroll and select a few items', () => { +test('[SelectionList] should scroll and select a few items', () => { const eventData = { nativeEvent: { contentOffset: { diff --git a/tests/perf-test/SidebarLinks.perf-test.js b/tests/perf-test/SidebarLinks.perf-test.js index 785b93514157..c6e6c024c597 100644 --- a/tests/perf-test/SidebarLinks.perf-test.js +++ b/tests/perf-test/SidebarLinks.perf-test.js @@ -51,7 +51,7 @@ const mockedResponseMap = getMockedReportsMap(500); const runs = CONST.PERFORMANCE_TESTS.RUNS; -test('should render Sidebar with 500 reports stored', () => { +test('[SidebarLinks] should render Sidebar with 500 reports stored', () => { const scenario = async () => { // Query for the sidebar await screen.findByTestId('lhn-options-list'); @@ -76,7 +76,7 @@ test('should render Sidebar with 500 reports stored', () => { .then(() => measurePerformance(, {scenario, runs})); }); -test('should scroll and click some of the items', () => { +test('[SidebarLinks] should scroll and click some of the items', () => { const scenario = async () => { const eventData = { nativeEvent: { diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 78f385cde90b..05143848f8b9 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -46,7 +46,7 @@ const personalDetails = createCollection( const mockedResponseMap = getMockedReports(5000) as Record<`${typeof ONYXKEYS.COLLECTION.REPORT}`, Report>; const runs = CONST.PERFORMANCE_TESTS.RUNS; -test('getOptionData on 5k reports', async () => { +test('[SidebarUtils] getOptionData on 5k reports', async () => { const report = createRandomReport(1); const preferredLocale = 'en'; const policy = createRandomPolicy(1); @@ -60,7 +60,7 @@ test('getOptionData on 5k reports', async () => { await measureFunction(() => SidebarUtils.getOptionData(report, reportActions, personalDetails, preferredLocale, policy, parentReportAction), {runs}); }); -test('getOrderedReportIDs on 5k reports', async () => { +test('[SidebarUtils] getOrderedReportIDs on 5k reports', async () => { const currentReportId = '1'; const allReports = getMockedReports(); const betas = [CONST.BETAS.DEFAULT_ROOMS, CONST.BETAS.POLICY_ROOMS]; diff --git a/tests/utils/collections/transaction.ts b/tests/utils/collections/transaction.ts new file mode 100644 index 000000000000..5f7cd47de990 --- /dev/null +++ b/tests/utils/collections/transaction.ts @@ -0,0 +1,32 @@ +import {rand, randAmount, randBoolean, randPastDate, randWord} from '@ngneat/falso'; +import CONST from '@src/CONST'; +import {Transaction} from '@src/types/onyx'; + +export default function createRandomTransaction(index: number): Transaction { + return { + amount: randAmount(), + billable: randBoolean(), + category: randWord(), + comment: { + comment: randWord(), + waypoints: { + [randWord()]: { + address: randWord(), + lat: index, + lng: index, + name: randWord(), + }, + }, + }, + created: randPastDate().toISOString(), + currency: CONST.CURRENCY.USD, + merchant: randWord(), + modifiedMerchant: randWord(), + pendingAction: rand(Object.values(CONST.RED_BRICK_ROAD_PENDING_ACTION)), + reportID: index.toString(), + transactionID: index.toString(), + tag: randWord(), + parentTransactionID: index.toString(), + status: rand(Object.values(CONST.TRANSACTION.STATUS)), + }; +}