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] add perf tests for ReportActionsUtils #32093

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
91 changes: 0 additions & 91 deletions tests/perf-test/ReportActionsUtils.perf-test.js

This file was deleted.

167 changes: 167 additions & 0 deletions tests/perf-test/ReportActionsUtils.perf-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import Onyx from 'react-native-onyx';
import {measureFunction} from 'reassure';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ReportAction, {ReportActions} from '@src/types/onyx/ReportAction';
import createCollection from '../utils/collections/createCollection';
import createRandomReportAction from '../utils/collections/reportActions';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';

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

// Clear out Onyx after each test so that each test starts with a clean slate
afterEach(() => {
Onyx.clear();
});

const getMockedReportActionsMap = (reportsLength = 10, actionsPerReportLength = 100) => {
const mockReportActions = Array.from({length: actionsPerReportLength}, (v, i) => {
const reportActionKey = i + 1;
const reportAction = createRandomReportAction(reportActionKey);

return {[reportActionKey]: reportAction};
});

const reportKeysMap = Array.from({length: reportsLength}, (v, i) => {
const key = i + 1;

return {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${key}`]: Object.assign({}, ...mockReportActions)};
});

return Object.assign({}, ...reportKeysMap) as Partial<ReportAction>;
};

const mockedReportActionsMap = getMockedReportActionsMap(2, 10000);

const reportActions = createCollection<ReportAction>(
(item) => `${item.reportActionID}`,
(index) => createRandomReportAction(index),
);

const reportId = '1';

const runs = CONST.PERFORMANCE_TESTS.RUNS;

/**
* This function will be executed 20 times and the average time will be used on the comparison.
* It will fail based on the CI configuration around Reassure:
* @see /.github/workflows/reassurePerformanceTests.yml
*
* Max deviation on the duration is set to 20% at the time of writing.
*
* More on the measureFunction API:
* @see https://callstack.github.io/reassure/docs/api#measurefunction-function
*/
test('getLastVisibleAction on 10k reportActions', async () => {
await Onyx.multiSet({
...mockedReportActionsMap,
});

await waitForBatchedUpdates();
await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId), {runs});
});

test('getLastVisibleAction on 10k reportActions with actionsToMerge', async () => {
const parentReportActionId = '1';
const fakeParentAction = reportActions[parentReportActionId];
const actionsToMerge = {
[parentReportActionId]: {
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
previousMessage: fakeParentAction.message,
message: [
{
translationKey: '',
type: 'COMMENT',
html: '',
text: '',
isEdited: true,
isDeletedParentAction: true,
},
],
errors: null,
linkMetaData: [],
},
} as unknown as ReportActions;

await Onyx.multiSet({
...mockedReportActionsMap,
});
await waitForBatchedUpdates();
await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId, actionsToMerge), {runs});
});

test('getMostRecentIOURequestActionID on 10k ReportActions', async () => {
const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions);
await Onyx.multiSet({
...mockedReportActionsMap,
});
await waitForBatchedUpdates();
await measureFunction(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActionsArray), {runs});
});

test('getLastVisibleMessage on 10k ReportActions', async () => {
await Onyx.multiSet({
...mockedReportActionsMap,
});
await waitForBatchedUpdates();
await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId), {runs});
});

test('getLastVisibleMessage on 10k ReportActions with actionsToMerge', async () => {
const parentReportActionId = '1';
const fakeParentAction = reportActions[parentReportActionId];
const actionsToMerge = {
[parentReportActionId]: {
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
previousMessage: fakeParentAction.message,
message: [
{
translationKey: '',
type: 'COMMENT',
html: '',
text: '',
isEdited: true,
isDeletedParentAction: true,
},
],
errors: null,
linkMetaData: [],
},
} as unknown as ReportActions;

await Onyx.multiSet({
...mockedReportActionsMap,
});
await waitForBatchedUpdates();
await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId, actionsToMerge), {runs});
});

test('getSortedReportActionsForDisplay on 10k ReportActions', async () => {
await Onyx.multiSet({
...mockedReportActionsMap,
});
await waitForBatchedUpdates();
await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions), {runs});
});

test('getLastClosedReportAction on 10k ReportActions', async () => {
await Onyx.multiSet({
...mockedReportActionsMap,
});
await waitForBatchedUpdates();
await measureFunction(() => ReportActionsUtils.getLastClosedReportAction(reportActions), {runs});
});

test('getMostRecentReportActionLastModified', async () => {
await Onyx.multiSet({
...mockedReportActionsMap,
});
await waitForBatchedUpdates();
await measureFunction(() => ReportActionsUtils.getMostRecentReportActionLastModified(), {runs});
});
4 changes: 2 additions & 2 deletions tests/perf-test/ReportScreen.perf-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ test('should render ReportScreen with composer interactions', () => {
};

const report = LHNTestUtils.getFakeReport();
const reportActions = ReportTestUtils.getMockedReportsMap(1000);
const reportActions = ReportTestUtils.getMockedReportActionsMap(1000);
const mockRoute = {params: {reportID: '1'}};

return waitForBatchedUpdates()
Expand Down Expand Up @@ -198,7 +198,7 @@ test('should press of the report item', () => {
};

const report = LHNTestUtils.getFakeReport();
const reportActions = ReportTestUtils.getMockedReportsMap(1000);
const reportActions = ReportTestUtils.getMockedReportActionsMap(1000);
const mockRoute = {params: {reportID: '2'}};

return waitForBatchedUpdates()
Expand Down
6 changes: 3 additions & 3 deletions tests/utils/ReportTestUtils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import _ from 'underscore';

const actionNames = ['ADDCOMMENT', 'IOU', 'REPORTPREVIEW'];
const actionNames = ['ADDCOMMENT', 'IOU', 'REPORTPREVIEW', 'CLOSED'];

const getFakeReportAction = (index, actionName) => ({
actionName,
Expand Down Expand Up @@ -47,7 +47,7 @@ const getFakeReportAction = (index, actionName) => ({

const getMockedSortedReportActions = (length = 100) => Array.from({length}, (__, i) => getFakeReportAction(i));

const getMockedReportsMap = (length = 100) => {
const getMockedReportActionsMap = (length = 100) => {
const mockReports = Array.from({length}, (__, i) => {
const reportID = i + 1;
const actionName = i === 0 ? 'CREATED' : actionNames[i % actionNames.length];
Expand All @@ -58,4 +58,4 @@ const getMockedReportsMap = (length = 100) => {
return _.assign({}, ...mockReports);
};

export {getFakeReportAction, getMockedSortedReportActions, getMockedReportsMap};
export {getFakeReportAction, getMockedSortedReportActions, getMockedReportActionsMap};
72 changes: 72 additions & 0 deletions tests/utils/collections/reportActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {rand, randAggregation, randBoolean, randPastDate, randWord} from '@ngneat/falso';
import CONST from '@src/CONST';
import {ReportAction} from '@src/types/onyx';

type ActionType = keyof typeof CONST.REPORT.ACTIONS.TYPE;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const flattenActionNamesValues = (actionNames: any) => {
let result = [] as ActionType[];
Object.keys(actionNames).forEach((key) => {
if (typeof actionNames[key] === 'object') {
result = result.concat(flattenActionNamesValues(actionNames[key]));
} else {
result.push(actionNames[key]);
}
});
return result;
};

export default function createRandomReportAction(index: number): ReportAction {
return {
// we need to add any here because of the way we are generating random values
// eslint-disable-next-line @typescript-eslint/no-explicit-any
actionName: rand(flattenActionNamesValues(CONST.REPORT.ACTIONS.TYPE)) as any,
reportActionID: index.toString(),
previousReportActionID: index.toString(),
actorAccountID: index,
person: [
{
type: randWord(),
style: randWord(),
text: randWord(),
},
],
created: randPastDate().toISOString(),
message: [
{
type: randWord(),
html: randWord(),
style: randWord(),
text: randWord(),
isEdited: randBoolean(),
isDeletedParentAction: randBoolean(),
whisperedTo: randAggregation(),
reactions: [
{
emoji: randWord(),
users: [
{
accountID: index,
skinTone: index,
},
],
},
],
},
],
originalMessage: {
html: randWord(),
type: rand(Object.values(CONST.IOU.REPORT_ACTION_TYPE)),
},
whisperedToAccountIDs: randAggregation(),
avatar: randWord(),
automatic: randBoolean(),
shouldShow: randBoolean(),
lastModified: randPastDate().toISOString(),
pendingAction: rand(Object.values(CONST.RED_BRICK_ROAD_PENDING_ACTION)),
delegateAccountID: index.toString(),
errors: {},
isAttachment: randBoolean(),
};
}