Skip to content
Open
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
44 changes: 27 additions & 17 deletions tests/actions/IOU/RequestMoneyTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import * as IsFileUploadable from '@libs/isFileUploadable';
import Navigation from '@libs/Navigation/Navigation';
import {rand64} from '@libs/NumberUtils';
import type * as PolicyUtils from '@libs/PolicyUtils';
import {getAllReportActions, getIOUActionForReportID, getOriginalMessage, isActionableTrackExpense, isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {getAllReportActions, getIOUActionForReportID, getOriginalMessage, isActionableTrackExpense, isMoneyRequestAction, isMovedTransactionAction} from '@libs/ReportActionsUtils';
import {mintAndStampReceiptTraceId} from '@libs/telemetry/ReceiptObservability';

import type {IOUAction} from '@src/CONST';
Expand All @@ -25,7 +25,6 @@ import DateUtils from '@src/libs/DateUtils';
import * as SearchQueryUtils from '@src/libs/SearchQueryUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PersonalDetailsList, Policy, RecentlyUsedTags, Report} from '@src/types/onyx';
import type {OriginalMessageMovedTransaction} from '@src/types/onyx/OriginalMessage';
import type {CurrentUserPersonalDetails} from '@src/types/onyx/PersonalDetails';
import type {Participant} from '@src/types/onyx/Report';
import type ReportAction from '@src/types/onyx/ReportAction';
Expand All @@ -47,7 +46,17 @@ import createPersonalDetails from '../../utils/collections/personalDetails';
import {createRandomReport} from '../../utils/collections/reports';
import createRandomTransaction from '../../utils/collections/transaction';
import getOnyxValue from '../../utils/getOnyxValue';
import {expectAPICommandToHaveBeenCalled, formatPhoneNumber, getGlobalFetchMock, getOnyxData, setPersonalDetails, signInWithTestUser, translateLocal} from '../../utils/TestHelper';
import {
expectAPICommandToHaveBeenCalled,
formatPhoneNumber,
getGlobalFetchMock,
getOnyxData,
getRequiredOnyxUpdates,
getRequiredWriteCall,
setPersonalDetails,
signInWithTestUser,
translateLocal,
} from '../../utils/TestHelper';
import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates';
import waitForNetworkPromises from '../../utils/waitForNetworkPromises';

Expand Down Expand Up @@ -173,8 +182,8 @@ describe('actions/IOU', () => {
let mockFetch: MockFetch;
beforeEach(() => {
jest.clearAllTimers();
global.fetch = getGlobalFetchMock();
mockFetch = fetch as MockFetch;
mockFetch = getGlobalFetchMock();
global.fetch = mockFetch;
return Onyx.clear().then(waitForBatchedUpdates);
});

Expand Down Expand Up @@ -2012,11 +2021,9 @@ describe('actions/IOU', () => {

// Also, the fromReportID of movedTransactionAction should be CONST.REPORT.UNREPORTED_REPORT_ID
const updatedTransactionThreadReportActions = getAllReportActions(transactionThreadReport?.reportID);
const movedTransactionAction = Object.values(updatedTransactionThreadReportActions ?? {}).find(
(reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.MOVED_TRANSACTION,
);
const movedTransactionAction = Object.values(updatedTransactionThreadReportActions ?? {}).find(isMovedTransactionAction);
expect(movedTransactionAction).toBeTruthy();
const originalMessage = getOriginalMessage(movedTransactionAction) as OriginalMessageMovedTransaction | undefined;
const originalMessage = getOriginalMessage(movedTransactionAction);
expect(originalMessage?.fromReportID).toBe(CONST.REPORT.UNREPORTED_REPORT_ID);
});

Expand Down Expand Up @@ -2542,11 +2549,12 @@ describe('actions/IOU', () => {
// Then the correct API request should be made
expect(writeSpy).toHaveBeenCalledTimes(1);

const [command, params] = writeSpy.mock.calls.at(0);
const writeCalls: unknown = writeSpy.mock.calls;
const [command, params] = getRequiredWriteCall(writeCalls);
expect(command).toBe(expectedCommand);

// And the parameters should be supported by XMLHttpRequest
for (const value of Object.values(params as Record<string, unknown>)) {
for (const value of Object.values(params)) {
expect(Array.isArray(value) ? value.every(isValid) : isValid(value)).toBe(true);
}
});
Expand Down Expand Up @@ -2670,10 +2678,11 @@ describe('actions/IOU', () => {
await waitForBatchedUpdates();

expect(writeSpy).toHaveBeenCalledTimes(1);
const [, , requestData] = writeSpy.mock.calls.at(0) as [ApiCommand, Record<string, unknown>, {optimisticData?: Array<{key: string}>}];
const optimisticData = requestData.optimisticData ?? [];
const writeCalls: unknown = writeSpy.mock.calls;
const [, , requestData] = getRequiredWriteCall(writeCalls);
const optimisticData = getRequiredOnyxUpdates(requestData, 'optimisticData');
const mainSnapshotKey = `${ONYXKEYS.COLLECTION.SNAPSHOT}${currentSearchQueryJSON.hash}`;
expect(optimisticData.some((update) => update.key === mainSnapshotKey)).toBeTruthy();
expect(optimisticData).toEqual(expect.arrayContaining([expect.objectContaining({key: mainSnapshotKey})]));

const newFlatFilters = currentSearchQueryJSON.flatFilters.filter((filter) => filter.key !== CONST.SEARCH.SYNTAX_FILTER_KEYS.FROM);
newFlatFilters.push({
Expand All @@ -2693,7 +2702,7 @@ describe('actions/IOU', () => {
throw new Error('Expected grouped transactions query JSON to be defined');
}
const groupedSnapshotKey = `${ONYXKEYS.COLLECTION.SNAPSHOT}${groupedTransactionsQueryJSON.hash}`;
expect(optimisticData.some((update) => update.key === groupedSnapshotKey)).toBeTruthy();
expect(optimisticData).toEqual(expect.arrayContaining([expect.objectContaining({key: groupedSnapshotKey})]));

getCurrentSearchQueryJSONSpy.mockRestore();
});
Expand Down Expand Up @@ -2750,15 +2759,16 @@ describe('actions/IOU', () => {
// Then the correct API request should be made
expect(writeSpy).toHaveBeenCalledTimes(1);

const [command, params] = writeSpy.mock.calls.at(0);
const writeCalls: unknown = writeSpy.mock.calls;
const [command, params] = getRequiredWriteCall(writeCalls);
expect(command).toBe(expectedCommand);

if (expectedCommand === WRITE_COMMANDS.SHARE_TRACKED_EXPENSE) {
expect(params).toHaveProperty('policyName');
}

// And the parameters should be supported by XMLHttpRequest
for (const value of Object.values(params as Record<string, unknown>)) {
for (const value of Object.values(params)) {
expect(Array.isArray(value) ? value.every(isValid) : isValid(value)).toBe(true);
}
});
Expand Down
14 changes: 7 additions & 7 deletions tests/navigation/createDynamicRouteTests.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
import Navigation from '@libs/Navigation/Navigation';

import type {DynamicRouteSuffix} from '@src/ROUTES';

jest.mock('@libs/Navigation/Navigation', () => ({
getActiveRoute: jest.fn(),
}));
Expand All @@ -26,7 +24,7 @@ jest.mock('@src/ROUTES', () => ({
}));

describe('createDynamicRoute', () => {
const mockGetActiveRoute = Navigation.getActiveRoute as jest.Mock;
const mockGetActiveRoute = jest.mocked(Navigation.getActiveRoute);

beforeEach(() => {
jest.clearAllMocks();
Expand Down Expand Up @@ -81,15 +79,17 @@ describe('createDynamicRoute', () => {

mockGetActiveRoute.mockReturnValue(activeRoute);

const result = createDynamicRoute(suffix as unknown as DynamicRouteSuffix);
const result = createDynamicRoute(suffix);

expect(result).toBe(expectedPath);
});

it('should throw an error if the suffix is invalid', () => {
const suffix = 'invalid-suffix';

expect(() => createDynamicRoute(suffix as unknown as DynamicRouteSuffix)).toThrow(`The route name ${suffix} is not supported in createDynamicRoute`);
expect(() => {
createDynamicRoute(suffix);
}).toThrow(`The route name ${suffix} is not supported in createDynamicRoute`);
expect(mockGetActiveRoute).not.toHaveBeenCalled();
});

Expand All @@ -100,7 +100,7 @@ describe('createDynamicRoute', () => {

mockGetActiveRoute.mockReturnValue(activeRoute);

const result = createDynamicRoute(suffix as unknown as DynamicRouteSuffix);
const result = createDynamicRoute(suffix);

expect(result).toBe(expectedPath);
});
Expand All @@ -112,7 +112,7 @@ describe('createDynamicRoute', () => {

mockGetActiveRoute.mockReturnValue(activeRoute);

const result = createDynamicRoute(suffix as unknown as DynamicRouteSuffix);
const result = createDynamicRoute(suffix);

expect(result).toBe(expectedPath);
});
Expand Down
42 changes: 25 additions & 17 deletions tests/ui/BottomTabBarTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,30 @@ import NAVIGATORS from '@src/NAVIGATORS';
import ONYXKEYS from '@src/ONYXKEYS';
import SCREENS from '@src/SCREENS';

import type {NavigationState} from '@react-navigation/native';

import {NavigationContainer} from '@react-navigation/native';
import React from 'react';
import Onyx from 'react-native-onyx';

import createMock from '../utils/createMock';

// Configurable per-test: simulates which tab is currently focused inside TAB_NAVIGATOR.
jest.mock('@hooks/useRootNavigationState', () => ({
__esModule: true,
default: jest.fn(),
}));

const setMockFocusedTab = (tabName: string) => {
(useRootNavigationState as jest.Mock).mockImplementation((selector: (state: unknown) => unknown) =>
selector({
routes: [
{
name: NAVIGATORS.TAB_NAVIGATOR,
state: {
routes: [{name: tabName, params: {}}],
index: 0,
},
},
],
index: 0,
}),
// Keep this partial runtime fixture tied to the production NavigationState
// contract while preserving the selected route values.
jest.mocked(useRootNavigationState).mockImplementation((selector) =>
selector(
createMock<NavigationState>({
routes: [{name: NAVIGATORS.TAB_NAVIGATOR, state: {routes: [{name: tabName, params: {}}], index: 0}}],
index: 0,
}),
),
);
};

Expand Down Expand Up @@ -94,7 +94,7 @@ describe('DebugTabView', () => {
});
beforeEach(() => {
Onyx.clear([ONYXKEYS.NVP_PREFERRED_LOCALE]);
(useResponsiveLayout as jest.Mock).mockReturnValue({shouldUseNarrowLayout: true});
jest.mocked(useResponsiveLayout).mockReturnValue(createMock<ReturnType<typeof useResponsiveLayout>>({shouldUseNarrowLayout: true}));
});

afterEach(async () => {
Expand Down Expand Up @@ -198,7 +198,7 @@ describe('DebugTabView', () => {

describe('Wide layout', () => {
beforeEach(() => {
(useResponsiveLayout as jest.Mock).mockReturnValue({shouldUseNarrowLayout: false});
jest.mocked(useResponsiveLayout).mockReturnValue(createMock<ReturnType<typeof useResponsiveLayout>>({shouldUseNarrowLayout: false}));
Onyx.set(ONYXKEYS.IS_DEBUG_MODE_ENABLED, true);
Onyx.set(ONYXKEYS.LOGINS, {
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand All @@ -221,7 +221,11 @@ describe('DebugTabView', () => {

const container = await screen.findByTestId('DebugTabViewContainer');
expect(container.props.pointerEvents).toBe('box-none');
expect((container.props.style as Array<Record<string, unknown>>).at(0)).toEqual(
const style: unknown = container.props.style;
if (!Array.isArray(style)) {
throw new Error('Expected DebugTabViewContainer style to be an array.');
}
expect(style.at(0)).toEqual(
expect.objectContaining({
top: 0,
left: variables.navigationTabBarSize,
Expand All @@ -237,7 +241,11 @@ describe('DebugTabView', () => {

const container = await screen.findByTestId('DebugTabViewContainer');
expect(container.props.pointerEvents).toBe('box-none');
expect((container.props.style as Array<Record<string, unknown>>).at(0)).toEqual(
const style: unknown = container.props.style;
if (!Array.isArray(style)) {
throw new Error('Expected DebugTabViewContainer style to be an array.');
}
expect(style.at(0)).toEqual(
expect.objectContaining({
bottom: 0,
left: variables.navigationTabBarSize,
Expand Down
61 changes: 28 additions & 33 deletions tests/ui/IOURequestStepScanTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavig
import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types';

import IOURequestStepScan from '@pages/iou/request/step/IOURequestStepScan';
import type {ScanRoute} from '@pages/iou/request/step/IOURequestStepScan/types';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -22,6 +23,7 @@ import React from 'react';
import Onyx from 'react-native-onyx';

import createRandomTransaction from '../utils/collections/transaction';
import createMock from '../utils/createMock';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct';

Expand All @@ -43,10 +45,7 @@ jest.mock('react-native-permissions', () => ({
}));

jest.mock('@hooks/useFilesValidation', () => {
const ReactLib = require('react') as {
createElement: (type: unknown, props?: unknown, ...children: unknown[]) => unknown;
Fragment: unknown;
};
const ReactLib = jest.requireActual<typeof React>('react');
return (callback: (files: FileObject[]) => void) => {
triggerFileSelection = callback;
return {
Expand Down Expand Up @@ -139,21 +138,19 @@ describe('IOURequestStepScan', () => {
<LocaleContextProvider>
<NavigationContainer>
<IOURequestStepScan
route={
{
key: 'StepScan',
name: SCREENS.MONEY_REQUEST.STEP_SCAN,
params: {
action: CONST.IOU.ACTION.CREATE,
iouType: CONST.IOU.TYPE.SUBMIT,
reportID: REPORT_ID,
transactionID: TRANSACTION_ID_1,
backTo: ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.route,
pageIndex: 0,
},
} as unknown as PlatformStackScreenProps<MoneyRequestNavigatorParamList, typeof SCREENS.MONEY_REQUEST.STEP_SCAN>['route']
}
navigation={{} as never}
route={createMock<PlatformStackScreenProps<MoneyRequestNavigatorParamList, typeof SCREENS.MONEY_REQUEST.STEP_SCAN>['route']>({
key: 'StepScan',
name: SCREENS.MONEY_REQUEST.STEP_SCAN,
params: {
action: CONST.IOU.ACTION.CREATE,
iouType: CONST.IOU.TYPE.SUBMIT,
reportID: REPORT_ID,
transactionID: TRANSACTION_ID_1,
backTo: ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.route,
pageIndex: 0,
},
})}
Comment on lines +141 to +152

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 CONSISTENCY: IOURequestStepScanTest.tsx: Use ScanRoute for both renders

The second render uses the exported ScanRoute, but the first still spells out createMock<PlatformStackScreenProps<MoneyRequestNavigatorParamList, typeof SCREENS.MONEY_REQUEST.STEP_SCAN>['route']>.

ScanRoute is PlatformStackRouteProp<..., STEP_SCAN | CREATE> (IOURequestStepScan/types.ts:27), so it already covers the STEP_SCAN case, both route props can be createMock<ScanRoute>(...).

Right now the same prop is typed two different ways in one file, which reads like an oversight.
(The navigation props can stay as-is)

navigation={createMock<PlatformStackScreenProps<MoneyRequestNavigatorParamList, typeof SCREENS.MONEY_REQUEST.STEP_SCAN>['navigation']>({})}
/>
</NavigationContainer>
</LocaleContextProvider>
Expand Down Expand Up @@ -189,20 +186,18 @@ describe('IOURequestStepScan', () => {
<LocaleContextProvider>
<NavigationContainer>
<IOURequestStepScan
route={
{
key: 'StepScan2',
name: SCREENS.MONEY_REQUEST.CREATE,
params: {
action: CONST.IOU.ACTION.CREATE,
iouType: CONST.IOU.TYPE.SUBMIT,
reportID: REPORT_ID,
transactionID: TRANSACTION_ID_1,
pageIndex: 0,
},
} as unknown as PlatformStackScreenProps<MoneyRequestNavigatorParamList, typeof SCREENS.MONEY_REQUEST.CREATE>['route']
}
navigation={{} as never}
route={createMock<ScanRoute>({
key: 'StepScan2',
name: SCREENS.MONEY_REQUEST.CREATE,
params: {
action: CONST.IOU.ACTION.CREATE,
iouType: CONST.IOU.TYPE.SUBMIT,
reportID: REPORT_ID,
transactionID: TRANSACTION_ID_1,
pageIndex: 0,
},
})}
navigation={createMock<PlatformStackScreenProps<MoneyRequestNavigatorParamList, typeof SCREENS.MONEY_REQUEST.CREATE>['navigation']>({})}
/>
</NavigationContainer>
</LocaleContextProvider>
Expand Down
Loading
Loading