diff --git a/tests/ui/PaginationTest.tsx b/tests/ui/PaginationTest.tsx
index 97b9ba885870..794c54d4a121 100644
--- a/tests/ui/PaginationTest.tsx
+++ b/tests/ui/PaginationTest.tsx
@@ -4,7 +4,6 @@ import {act, fireEvent, render, screen} from '@testing-library/react-native';
import {addSeconds, format, subMinutes} from 'date-fns';
import React from 'react';
import Onyx from 'react-native-onyx';
-import type {ApiCommand} from '@libs/API/types';
import * as Localize from '@libs/Localize';
import * as AppActions from '@userActions/App';
import * as User from '@userActions/User';
@@ -88,14 +87,17 @@ const USER_A_EMAIL = 'user_a@test.com';
const USER_B_ACCOUNT_ID = 2;
const USER_B_EMAIL = 'user_b@test.com';
-function mockOpenReport(messageCount: number, includeCreatedAction: boolean) {
+function buildReportComments(count: number, initialID: string, reverse = false) {
+ let currentID = parseInt(initialID, 10);
const TEN_MINUTES_AGO = subMinutes(new Date(), 10);
- const actions = Object.fromEntries(
- Array.from({length: messageCount}).map((_, index) => {
- const created = format(addSeconds(TEN_MINUTES_AGO, 10 * index), CONST.DATE.FNS_DB_FORMAT_STRING);
+ return Object.fromEntries(
+ Array.from({length: Math.min(count, currentID)}).map(() => {
+ const created = format(addSeconds(TEN_MINUTES_AGO, 10 * currentID), CONST.DATE.FNS_DB_FORMAT_STRING);
+ const id = currentID;
+ currentID += reverse ? 1 : -1;
return [
- `${index + 1}`,
- index === 0 && includeCreatedAction
+ `${id}`,
+ id === 1
? {
reportActionID: '1',
actionName: 'CREATED' as const,
@@ -107,23 +109,44 @@ function mockOpenReport(messageCount: number, includeCreatedAction: boolean) {
},
],
}
- : TestHelper.buildTestReportComment(created, USER_B_ACCOUNT_ID, `${index + 1}`),
+ : TestHelper.buildTestReportComment(created, USER_B_ACCOUNT_ID, `${id}`),
];
}),
);
- fetchMock.mockAPICommand('OpenReport', [
+}
+
+function mockOpenReport(messageCount: number, initialID: string) {
+ fetchMock.mockAPICommand('OpenReport', () => [
{
onyxMethod: 'merge',
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`,
- value: actions,
+ value: buildReportComments(messageCount, initialID),
},
]);
}
-function expectAPICommandToHaveBeenCalled(commandName: ApiCommand, expectedCalls: number) {
- expect(fetchMock.mock.calls.filter((c) => c[0] === `https://www.expensify.com.dev/api/${commandName}?`)).toHaveLength(expectedCalls);
+function mockGetOlderActions(messageCount: number) {
+ fetchMock.mockAPICommand('GetOlderActions', ({reportActionID}) => [
+ {
+ onyxMethod: 'merge',
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`,
+ // The API also returns the action that was requested with the reportActionID.
+ value: buildReportComments(messageCount + 1, reportActionID),
+ },
+ ]);
}
+// function mockGetNewerActions(messageCount: number) {
+// fetchMock.mockAPICommand('GetNewerActions', ({reportActionID}) => [
+// {
+// onyxMethod: 'merge',
+// key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`,
+// // The API also returns the action that was requested with the reportActionID.
+// value: buildReportComments(messageCount + 1, reportActionID, true),
+// },
+// ]);
+// }
+
/**
* Sets up a test with a logged in user. Returns the test instance.
*/
@@ -182,15 +205,16 @@ describe('Pagination', () => {
});
it('opens a chat and load initial messages', async () => {
- mockOpenReport(5, true);
+ mockOpenReport(5, '5');
await signInAndGetApp();
await navigateToSidebarOption(0);
expect(getReportActions()).toHaveLength(5);
- expectAPICommandToHaveBeenCalled('OpenReport', 1);
- expectAPICommandToHaveBeenCalled('GetOlderActions', 0);
- expectAPICommandToHaveBeenCalled('GetNewerActions', 0);
+ TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 1);
+ TestHelper.expectAPICommandToHaveBeenCalledWith('OpenReport', 0, {reportID: REPORT_ID, reportActionID: ''});
+ TestHelper.expectAPICommandToHaveBeenCalled('GetOlderActions', 0);
+ TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 0);
// Scrolling here should not trigger a new network request.
scrollToOffset(LIST_CONTENT_SIZE.height);
@@ -198,28 +222,31 @@ describe('Pagination', () => {
scrollToOffset(0);
await waitForBatchedUpdatesWithAct();
- expectAPICommandToHaveBeenCalled('OpenReport', 1);
- expectAPICommandToHaveBeenCalled('GetOlderActions', 0);
- expectAPICommandToHaveBeenCalled('GetNewerActions', 0);
+ TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 1);
+ TestHelper.expectAPICommandToHaveBeenCalled('GetOlderActions', 0);
+ TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 0);
});
it('opens a chat and load older messages', async () => {
- mockOpenReport(5, false);
+ mockOpenReport(5, '8');
+ mockGetOlderActions(5);
await signInAndGetApp();
await navigateToSidebarOption(0);
expect(getReportActions()).toHaveLength(5);
- expectAPICommandToHaveBeenCalled('OpenReport', 1);
- expectAPICommandToHaveBeenCalled('GetOlderActions', 0);
- expectAPICommandToHaveBeenCalled('GetNewerActions', 0);
+ TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 1);
+ TestHelper.expectAPICommandToHaveBeenCalledWith('OpenReport', 0, {reportID: REPORT_ID, reportActionID: ''});
+ TestHelper.expectAPICommandToHaveBeenCalled('GetOlderActions', 0);
+ TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 0);
// Scrolling here should trigger a new network request.
scrollToOffset(LIST_CONTENT_SIZE.height);
await waitForBatchedUpdatesWithAct();
- expectAPICommandToHaveBeenCalled('OpenReport', 1);
- expectAPICommandToHaveBeenCalled('GetOlderActions', 1);
- expectAPICommandToHaveBeenCalled('GetNewerActions', 0);
+ TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 1);
+ TestHelper.expectAPICommandToHaveBeenCalled('GetOlderActions', 1);
+ TestHelper.expectAPICommandToHaveBeenCalledWith('GetOlderActions', 0, {reportID: REPORT_ID, reportActionID: '4'});
+ TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 0);
});
});
diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts
index f3aed289acff..81fe1e9173f4 100644
--- a/tests/utils/TestHelper.ts
+++ b/tests/utils/TestHelper.ts
@@ -2,6 +2,7 @@ import type * as NativeNavigation from '@react-navigation/native';
import {Str} from 'expensify-common';
import {Linking} from 'react-native';
import Onyx from 'react-native-onyx';
+import type {ApiCommand, ApiRequestCommandParameters} from '@libs/API/types';
import * as Pusher from '@libs/Pusher/pusher';
import PusherConnectionManager from '@libs/PusherConnectionManager';
import CONFIG from '@src/CONFIG';
@@ -14,18 +15,20 @@ import appSetup from '@src/setup';
import type {Response as OnyxResponse, PersonalDetails, Report} from '@src/types/onyx';
import waitForBatchedUpdates from './waitForBatchedUpdates';
+console.debug = () => {};
+
type MockFetch = ReturnType & {
pause: () => void;
fail: () => void;
succeed: () => void;
resume: () => Promise;
- mockAPICommand: (command: string, response: OnyxResponse['onyxData']) => void;
+ mockAPICommand: (command: TCommand, responseHandler: (params: ApiRequestCommandParameters[TCommand]) => OnyxResponse['onyxData']) => void;
};
type QueueItem = {
resolve: (value: Partial | PromiseLike>) => void;
input: RequestInfo;
- init?: RequestInit;
+ options?: RequestInit;
};
type FormData = {
@@ -186,11 +189,12 @@ function signOutTestUser() {
*/
function getGlobalFetchMock(): typeof fetch {
let queue: QueueItem[] = [];
- let responses = new Map();
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let responses = new Map OnyxResponse['onyxData']>();
let isPaused = false;
let shouldFail = false;
- const getResponse = (input: RequestInfo): Partial =>
+ const getResponse = (input: RequestInfo, options?: RequestInit): Partial =>
shouldFail
? {
ok: true,
@@ -202,20 +206,22 @@ function getGlobalFetchMock(): typeof fetch {
const commandMatch = typeof input === 'string' ? input.match(/https:\/\/www.expensify.com.dev\/api\/(\w+)\?/) : null;
const command = commandMatch ? commandMatch[1] : null;
- if (command && responses.has(command)) {
- return Promise.resolve({jsonCode: 200, onyxData: responses.get(command)});
+ const responseHandler = command ? responses.get(command) : null;
+ if (responseHandler) {
+ const requestData = options?.body instanceof FormData ? Object.fromEntries(options.body) : {};
+ return Promise.resolve({jsonCode: 200, onyxData: responseHandler(requestData)});
}
return Promise.resolve({jsonCode: 200});
},
};
- const mockFetch = jest.fn().mockImplementation((input: RequestInfo) => {
+ const mockFetch = jest.fn().mockImplementation((input: RequestInfo, options?: RequestInit) => {
if (!isPaused) {
- return Promise.resolve(getResponse(input));
+ return Promise.resolve(getResponse(input, options));
}
return new Promise((resolve) => {
- queue.push({resolve, input});
+ queue.push({resolve, input, options});
});
}) as MockFetch;
@@ -237,8 +243,8 @@ function getGlobalFetchMock(): typeof fetch {
};
mockFetch.fail = () => (shouldFail = true);
mockFetch.succeed = () => (shouldFail = false);
- mockFetch.mockAPICommand = (command: string, response: OnyxResponse['onyxData']) => {
- responses.set(command, response);
+ mockFetch.mockAPICommand = (command: TCommand, responseHandler: (params: ApiRequestCommandParameters[TCommand]) => OnyxResponse['onyxData']): void => {
+ responses.set(command, responseHandler);
};
return mockFetch as typeof fetch;
}
@@ -256,6 +262,28 @@ function setupGlobalFetchMock(): MockFetch {
return mockFetch as MockFetch;
}
+function getFetchMockCalls(commandName: ApiCommand) {
+ return (global.fetch as MockFetch).mock.calls.filter((c) => c[0] === `https://www.expensify.com.dev/api/${commandName}?`);
+}
+
+/**
+ * Assertion helper to validate that a command has been called a specific number of times.
+ */
+function expectAPICommandToHaveBeenCalled(commandName: ApiCommand, expectedCalls: number) {
+ expect(getFetchMockCalls(commandName)).toHaveLength(expectedCalls);
+}
+
+/**
+ * Assertion helper to validate that a command has been called with specific parameters.
+ */
+function expectAPICommandToHaveBeenCalledWith(commandName: TCommand, callIndex: number, expectedParams: ApiRequestCommandParameters[TCommand]) {
+ const call = getFetchMockCalls(commandName).at(callIndex);
+ expect(call).toBeTruthy();
+ const body = call?.at(1)?.body;
+ const params = body instanceof FormData ? Object.fromEntries(body) : {};
+ expect(params).toEqual(expect.objectContaining(expectedParams));
+}
+
function setPersonalDetails(login: string, accountID: number) {
Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {
[accountID]: buildPersonalDetails(login, accountID),
@@ -292,6 +320,8 @@ export {
getGlobalFetchMock,
setupApp,
setupGlobalFetchMock,
+ expectAPICommandToHaveBeenCalled,
+ expectAPICommandToHaveBeenCalledWith,
setPersonalDetails,
signInWithTestUser,
signOutTestUser,
diff --git a/tsconfig.json b/tsconfig.json
index ea072fc4a354..16497c29b8cb 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,6 +4,7 @@
"compilerOptions": {
"module": "commonjs",
"types": ["react-native", "jest"],
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
"isolatedModules": true,
"strict": true,
"allowSyntheticDefaultImports": true,