Skip to content
Draft
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
19 changes: 11 additions & 8 deletions tests/ui/CountrySelectionListTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jest.mock('@hooks/useLocalize', () =>
translate: (key: string) => {
if (key.startsWith('allCountries.')) {
const countryISO = key.split('.').at(-1) ?? '';
return mockAllCountries[countryISO as keyof typeof mockAllCountries] ?? key;
return Object.entries(mockAllCountries).find(([iso]) => iso === countryISO)?.[1] ?? key;
}

return key;
Expand Down Expand Up @@ -211,13 +211,16 @@ describe('CountrySelectionList', () => {
const searchedProps = mockedSelectionList.mock.lastCall?.[0];
const expectedSearchResults = searchOptions(
'Uni',
countries.map((countryISO) => ({
value: countryISO,
keyForList: countryISO,
text: CONST.ALL_COUNTRIES[countryISO as keyof typeof CONST.ALL_COUNTRIES],
isSelected: countryISO === initialCountry,
searchValue: StringUtils.sanitizeString(`${countryISO}${CONST.ALL_COUNTRIES[countryISO as keyof typeof CONST.ALL_COUNTRIES]}`),
})),
countries.map((countryISO) => {
const countryName = Object.entries(CONST.ALL_COUNTRIES).find(([iso]) => iso === countryISO)?.[1] ?? '';
return {
value: countryISO,
keyForList: countryISO,
text: countryName,
isSelected: countryISO === initialCountry,
searchValue: StringUtils.sanitizeString(`${countryISO}${countryName}`),
};
}),
);

expect(searchedProps?.data.map((item) => item.keyForList)).toEqual(expectedSearchResults.map((item) => item.keyForList));
Expand Down
8 changes: 4 additions & 4 deletions tests/ui/CountrySelectorModalTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jest.mock('@hooks/useLocalize', () =>
translate: (key: string) => {
if (key.startsWith('allCountries.')) {
const countryISO = key.split('.').at(-1) ?? '';
return mockAllCountries[countryISO as keyof typeof mockAllCountries] ?? key;
return Object.entries(mockAllCountries).find(([iso]) => iso === countryISO)?.[1] ?? key;
}

return key;
Expand Down Expand Up @@ -102,12 +102,12 @@ describe('CountrySelectorModal', () => {
const searchedProps = mockedSelectionList.mock.lastCall?.[0];
const expectedSearchResults = searchOptions(
'Uni',
Object.keys(CONST.ALL_COUNTRIES).map((countryISO) => ({
Object.entries(CONST.ALL_COUNTRIES).map(([countryISO, countryName]) => ({
value: countryISO,
keyForList: countryISO,
text: CONST.ALL_COUNTRIES[countryISO as keyof typeof CONST.ALL_COUNTRIES],
text: countryName,
isSelected: countryISO === 'US',
searchValue: StringUtils.sanitizeString(`${countryISO}${CONST.ALL_COUNTRIES[countryISO as keyof typeof CONST.ALL_COUNTRIES]}`),
searchValue: StringUtils.sanitizeString(`${countryISO}${countryName}`),
})),
);

Expand Down
13 changes: 5 additions & 8 deletions tests/ui/DynamicContactMethodsPageTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import LockedAccountModalProvider from '@src/components/LockedAccountModalProvid
import type CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

import type ReactNative from 'react-native';
import type {ValueOf} from 'type-fest';

import React from 'react';
Expand All @@ -29,10 +30,8 @@ jest.mock('@hooks/useDynamicBackPath', () => jest.fn(() => ''));

// Mock RenderHTML component
jest.mock('@components/RenderHTML', () => {
const ReactMock = require('react') as typeof React;
const {Text} = require('react-native') as {
Text: React.ComponentType<{children?: React.ReactNode}>;
};
const ReactMock = jest.requireActual<typeof React>('react');
const {Text} = jest.requireActual<typeof ReactNative>('react-native');

return ({html}: {html: string}) => {
const plainText = html.replaceAll(/<[^>]*>/g, '');
Expand All @@ -42,10 +41,8 @@ jest.mock('@components/RenderHTML', () => {

// Replace MenuItem with a simple test double that exposes props in the tree
jest.mock('@components/MenuItem', () => {
const ReactMock = require('react') as typeof React;
const {Text} = require('react-native') as {
Text: React.ComponentType<{testID: string; children?: React.ReactNode}>;
};
const ReactMock = jest.requireActual<typeof React>('react');
const {Text} = jest.requireActual<typeof ReactNative>('react-native');
return ({title, brickRoadIndicator}: {title: string; brickRoadIndicator?: ValueOf<typeof CONST.BRICK_ROAD_INDICATOR_STATUS>}) =>
ReactMock.createElement(Text, {testID: `menu-${String(title)}`}, `${brickRoadIndicator ?? 'none'}-brickRoadIndicator`);
});
Expand Down
8 changes: 4 additions & 4 deletions tests/ui/DynamicCountrySelectionPageTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jest.mock('@hooks/useLocalize', () =>
translate: (key: string) => {
if (key.startsWith('allCountries.')) {
const countryISO = key.split('.').at(-1) ?? '';
return mockAllCountries[countryISO as keyof typeof mockAllCountries] ?? key;
return Object.entries(mockAllCountries).find(([iso]) => iso === countryISO)?.[1] ?? key;
}

return key;
Expand Down Expand Up @@ -101,12 +101,12 @@ describe('DynamicCountrySelectionPage', () => {
const searchedProps = mockedSelectionList.mock.lastCall?.[0];
const expectedSearchResults = searchOptions(
'Uni',
Object.keys(CONST.ALL_COUNTRIES).map((countryISO) => ({
Object.entries(CONST.ALL_COUNTRIES).map(([countryISO, countryName]) => ({
value: countryISO,
keyForList: countryISO,
text: CONST.ALL_COUNTRIES[countryISO as keyof typeof CONST.ALL_COUNTRIES],
text: countryName,
isSelected: countryISO === 'US',
searchValue: StringUtils.sanitizeString(`${countryISO}${CONST.ALL_COUNTRIES[countryISO as keyof typeof CONST.ALL_COUNTRIES]}`),
searchValue: StringUtils.sanitizeString(`${countryISO}${countryName}`),
})),
);

Expand Down
14 changes: 11 additions & 3 deletions tests/ui/ForYouSectionTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ jest.mock('react-native-reanimated', () => {
});

const mockNavigate = jest.mocked(Navigation.navigate);
const mockUseResponsiveLayout = useResponsiveLayout as jest.MockedFunction<typeof useResponsiveLayout>;
const mockUseResponsiveLayout = jest.mocked(useResponsiveLayout);
const mockUseTodoCounts = jest.mocked(useTodoCounts);

const ACCOUNT_ID = 12345;
Expand Down Expand Up @@ -813,7 +813,11 @@ describe('ForYouSection', () => {
pressFirstBeginButton();

expect(mockNavigate).toHaveBeenCalledTimes(1);
const calledRoute = mockNavigate.mock.calls.at(0)?.at(0) as string;
const calledRoute = mockNavigate.mock.calls.at(0)?.at(0);
expect(typeof calledRoute).toBe('string');
if (typeof calledRoute !== 'string') {
return;
}
expect(calledRoute).toContain(ROUTES.SEARCH_ROOT.route);
});

Expand All @@ -832,7 +836,11 @@ describe('ForYouSection', () => {
pressFirstBeginButton();

expect(mockNavigate).toHaveBeenCalledTimes(1);
const calledRoute = mockNavigate.mock.calls.at(0)?.at(0) as string;
const calledRoute = mockNavigate.mock.calls.at(0)?.at(0);
expect(typeof calledRoute).toBe('string');
if (typeof calledRoute !== 'string') {
return;
}
expect(calledRoute).toContain(ROUTES.SEARCH_ROOT.route);
});
});
Expand Down
12 changes: 7 additions & 5 deletions tests/ui/LocationPermissionModalTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import AndroidLocationPermissionModal from '@components/LocationPermissionModal/

import getPlatform from '@libs/getPlatform';

import type * as LocationPermissionModule from '@pages/iou/request/step/IOURequestStepScan/LocationPermission';

import CONST from '@src/CONST';

import React from 'react';
Expand All @@ -15,8 +17,8 @@ import type * as MockUseConfirmModalUtil from '../utils/mockUseConfirmModal';

import {getShowConfirmModalOption, mockShowConfirmModal, resetMockConfirmModal, resolveShowConfirmModal} from '../utils/mockUseConfirmModal';

const mockGetLocationPermission = jest.fn();
const mockRequestLocationPermission = jest.fn();
const mockGetLocationPermission = jest.fn<ReturnType<typeof LocationPermissionModule.getLocationPermission>, Parameters<typeof LocationPermissionModule.getLocationPermission>>();
const mockRequestLocationPermission = jest.fn<ReturnType<typeof LocationPermissionModule.requestLocationPermission>, Parameters<typeof LocationPermissionModule.requestLocationPermission>>();
const mockUpdateLastLocationPermissionPrompt = jest.fn();

jest.mock('@hooks/useConfirmModal', () => {
Expand Down Expand Up @@ -59,8 +61,8 @@ jest.mock('@libs/Visibility', () => ({
}));

jest.mock('@pages/iou/request/step/IOURequestStepScan/LocationPermission', () => ({
getLocationPermission: (...args: unknown[]) => mockGetLocationPermission(...args) as Promise<string>,
requestLocationPermission: (...args: unknown[]) => mockRequestLocationPermission(...args) as Promise<string>,
getLocationPermission: (...args: Parameters<typeof LocationPermissionModule.getLocationPermission>) => mockGetLocationPermission(...args),
requestLocationPermission: (...args: Parameters<typeof LocationPermissionModule.requestLocationPermission>) => mockRequestLocationPermission(...args),
}));

jest.mock('@userActions/IOU/MoneyRequest', () => ({
Expand All @@ -74,7 +76,7 @@ jest.mock('react-native-permissions', () => ({
}));

const originalOpenSettings = Linking.openSettings?.bind(Linking);
const mockGetPlatform = getPlatform as jest.MockedFunction<typeof getPlatform>;
const mockGetPlatform = jest.mocked(getPlatform);

function setOpenSettings(openSettings: typeof Linking.openSettings | undefined) {
Object.defineProperty(Linking, 'openSettings', {
Expand Down
29 changes: 14 additions & 15 deletions tests/ui/ScanSkipConfirmationTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,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 Down Expand Up @@ -63,7 +64,7 @@ jest.mock('@pages/iou/request/step/IOURequestStepScan/hooks/useScanRouteParams',
}));

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 @@ -156,20 +157,18 @@ describe('ScanSkipConfirmation submit orchestration', () => {
<LocaleContextProvider>
<NavigationContainer>
<IOURequestStepScan
route={
{
key: 'StepScanSkip',
name: SCREENS.MONEY_REQUEST.STEP_SCAN,
params: {
action: CONST.IOU.ACTION.CREATE,
iouType: CONST.IOU.TYPE.SUBMIT,
reportID: REPORT_ID,
transactionID: TRANSACTION_ID,
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: 'StepScanSkip',
name: SCREENS.MONEY_REQUEST.STEP_SCAN,
params: {
action: CONST.IOU.ACTION.CREATE,
iouType: CONST.IOU.TYPE.SUBMIT,
reportID: REPORT_ID,
transactionID: TRANSACTION_ID,
pageIndex: 0,
},
})}
navigation={createMock<PlatformStackScreenProps<MoneyRequestNavigatorParamList, typeof SCREENS.MONEY_REQUEST.STEP_SCAN>['navigation']>({})}
/>
</NavigationContainer>
</LocaleContextProvider>
Expand Down
33 changes: 28 additions & 5 deletions tests/ui/SearchReportAvatarTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'

type AvatarData = {
uri: string;
avatarID?: number;
avatarID?: number | string;
name?: string;
parent: string;
};
Expand All @@ -40,8 +40,8 @@ const parseSource = (source: AvatarSource | IconAsset): string => {
if (typeof source === 'string') {
return source;
}
if (typeof source === 'object' && 'name' in source) {
return source.name as string;
if (typeof source === 'object' && 'name' in source && typeof source.name === 'string') {
return source.name;
}
if (typeof source === 'object' && 'uri' in source) {
return source.uri ?? 'No Source';
Expand Down Expand Up @@ -219,16 +219,39 @@ function renderSearchReportAvatar(props: {primaryAvatar?: Icon; secondaryAvatar?
);
}

function parseRenderedAvatarData(dataSet: unknown): AvatarData {
if (typeof dataSet !== 'object' || dataSet === null || !('uri' in dataSet) || typeof dataSet.uri !== 'string' || !('parent' in dataSet) || typeof dataSet.parent !== 'string') {
throw new Error('Rendered avatar data is missing its URI or parent');
}

const avatarID = 'avatarID' in dataSet ? dataSet.avatarID : undefined;
const name = 'name' in dataSet ? dataSet.name : undefined;
if (avatarID !== undefined && typeof avatarID !== 'number' && typeof avatarID !== 'string') {
throw new Error('Rendered avatar data has an invalid avatar ID');
}
if (name !== undefined && typeof name !== 'string') {
throw new Error('Rendered avatar data has an invalid name');
}

return {uri: dataSet.uri, parent: dataSet.parent, avatarID, name};
}

async function retrieveAvatarData(props: {primaryAvatar?: Icon; secondaryAvatar?: Icon; avatarType?: ValueOf<typeof CONST.REPORT_ACTION_AVATARS.TYPE>; reportID: string}) {
renderSearchReportAvatar(props);

await waitForBatchedUpdatesWithAct();

const images = screen.queryAllByTestId('MockedAvatarData');
const fragments = screen.queryAllByTestId('ReportActionAvatars-', {exact: false}).map((fragment) => fragment.props.testID as string);
const fragments = screen.queryAllByTestId('ReportActionAvatars-', {exact: false}).map((fragment) => {
const testID: unknown = fragment.props.testID;
if (typeof testID !== 'string') {
throw new Error('Rendered report action avatar fragment is missing its test ID');
}
return testID;
});

return {
images: images.map((img) => img.props.dataSet as AvatarData),
images: images.map((img) => parseRenderedAvatarData(img.props.dataSet)),
fragments,
};
}
Expand Down
31 changes: 19 additions & 12 deletions tests/ui/WorkEmailOnboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {NavigationContainer} from '@react-navigation/native';
import React from 'react';
import Onyx from 'react-native-onyx';

import createMock from '../utils/createMock';
import * as TestHelper from '../utils/TestHelper';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct';
Expand Down Expand Up @@ -282,10 +283,12 @@ describe('OnboardingWorkEmail Page', () => {
});

beforeEach(() => {
jest.spyOn(useResponsiveLayoutModule, 'default').mockReturnValue({
isSmallScreenWidth: false,
shouldUseNarrowLayout: false,
} as ResponsiveLayoutResult);
jest.spyOn(useResponsiveLayoutModule, 'default').mockReturnValue(
createMock<ResponsiveLayoutResult>({
isSmallScreenWidth: false,
shouldUseNarrowLayout: false,
}),
);
});

afterEach(async () => {
Expand Down Expand Up @@ -639,10 +642,12 @@ describe('OnboardingWorkEmailValidation Page', () => {
});

beforeEach(() => {
jest.spyOn(useResponsiveLayoutModule, 'default').mockReturnValue({
isSmallScreenWidth: false,
shouldUseNarrowLayout: false,
} as ResponsiveLayoutResult);
jest.spyOn(useResponsiveLayoutModule, 'default').mockReturnValue(
createMock<ResponsiveLayoutResult>({
isSmallScreenWidth: false,
shouldUseNarrowLayout: false,
}),
);
});

afterEach(async () => {
Expand Down Expand Up @@ -984,10 +989,12 @@ describe('OnboardingPrivateDomain Page', () => {
});

beforeEach(() => {
jest.spyOn(useResponsiveLayoutModule, 'default').mockReturnValue({
isSmallScreenWidth: false,
shouldUseNarrowLayout: false,
} as ResponsiveLayoutResult);
jest.spyOn(useResponsiveLayoutModule, 'default').mockReturnValue(
createMock<ResponsiveLayoutResult>({
isSmallScreenWidth: false,
shouldUseNarrowLayout: false,
}),
);
});

afterEach(async () => {
Expand Down
Loading
Loading