Skip to content

Commit

Permalink
Merge pull request #32093 from OlimpiaZurek/feat/30569/ReportActionsU…
Browse files Browse the repository at this point in the history
…tils-reassure-tests

[NoQA] add perf tests for ReportActionsUtils
  • Loading branch information
mountiny authored Nov 30, 2023
2 parents 9bbb8ec + 243396a commit d20ef03
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 96 deletions.
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(),
};
}

0 comments on commit d20ef03

Please sign in to comment.