diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts index c63e1c13d29a..a54095ad9c23 100644 --- a/src/libs/actions/EmojiPickerAction.ts +++ b/src/libs/actions/EmojiPickerAction.ts @@ -112,4 +112,4 @@ function resetEmojiPopoverAnchor() { } export {emojiPickerRef, showEmojiPicker, hideEmojiPicker, isActive, clearActive, isEmojiPickerVisible, resetEmojiPopoverAnchor}; -export type {AnchorOrigin}; +export type {AnchorOrigin, EmojiPickerRef}; diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index ad81ae480cd0..86383a8d0047 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -2,6 +2,8 @@ import type {ValueOf} from 'type-fest'; import type {FileObject} from '@components/AttachmentModal'; import type {AvatarSource} from '@libs/UserUtils'; import type CONST from '@src/CONST'; +import type ONYXKEYS from '@src/ONYXKEYS'; +import type CollectionDataSet from '@src/types/utils/CollectionDataSet'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import type * as OnyxCommon from './OnyxCommon'; import type {Decision, Reaction} from './OriginalMessage'; @@ -230,5 +232,7 @@ type ReportAction = ReportActionBase & OriginalMessage; type ReportActions = Record; +type ReportActionsCollectionDataSet = CollectionDataSet; + export default ReportAction; -export type {ReportActions, ReportActionBase, Message, LinkMetadata, OriginalMessage}; +export type {ReportActions, ReportActionBase, Message, LinkMetadata, OriginalMessage, ReportActionsCollectionDataSet}; diff --git a/tests/perf-test/ReportActionCompose.perf-test.js b/tests/perf-test/ReportActionCompose.perf-test.tsx similarity index 76% rename from tests/perf-test/ReportActionCompose.perf-test.js rename to tests/perf-test/ReportActionCompose.perf-test.tsx index 5b70666632f9..8d6cb1ac7e57 100644 --- a/tests/perf-test/ReportActionCompose.perf-test.js +++ b/tests/perf-test/ReportActionCompose.perf-test.tsx @@ -1,25 +1,34 @@ import {fireEvent, screen} from '@testing-library/react-native'; +import type {ComponentType} from 'react'; import React from 'react'; import Onyx from 'react-native-onyx'; +import type Animated from 'react-native-reanimated'; import {measurePerformance} from 'reassure'; -import ComposeProviders from '../../src/components/ComposeProviders'; -import {LocaleContextProvider} from '../../src/components/LocaleContextProvider'; -import OnyxProvider from '../../src/components/OnyxProvider'; -import {KeyboardStateProvider} from '../../src/components/withKeyboardState'; -import {WindowDimensionsProvider} from '../../src/components/withWindowDimensions'; -import * as Localize from '../../src/libs/Localize'; -import ONYXKEYS from '../../src/ONYXKEYS'; -import ReportActionCompose from '../../src/pages/home/report/ReportActionCompose/ReportActionCompose'; +import type {WithNavigationFocusProps} from '@components/withNavigationFocus'; +import type {EmojiPickerRef} from '@libs/actions/EmojiPickerAction'; +import type Navigation from '@libs/Navigation/Navigation'; +import ComposeProviders from '@src/components/ComposeProviders'; +import {LocaleContextProvider} from '@src/components/LocaleContextProvider'; +import OnyxProvider from '@src/components/OnyxProvider'; +import {KeyboardStateProvider} from '@src/components/withKeyboardState'; +import {WindowDimensionsProvider} from '@src/components/withWindowDimensions'; +import * as Localize from '@src/libs/Localize'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ReportActionCompose from '@src/pages/home/report/ReportActionCompose/ReportActionCompose'; import * as LHNTestUtils from '../utils/LHNTestUtils'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; // mock PortalStateContext jest.mock('@gorhom/portal'); -jest.mock('react-native-reanimated', () => ({ - ...jest.requireActual('react-native-reanimated/mock'), - useAnimatedRef: jest.fn, -})); +jest.mock( + 'react-native-reanimated', + () => + ({ + ...jest.requireActual('react-native-reanimated/mock'), + useAnimatedRef: jest.fn(), + } as typeof Animated), +); jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); @@ -32,11 +41,11 @@ jest.mock('@react-navigation/native', () => { useIsFocused: () => ({ navigate: jest.fn(), }), - }; + } as typeof Navigation; }); -jest.mock('../../src/libs/actions/EmojiPickerAction', () => { - const actualEmojiPickerAction = jest.requireActual('../../src/libs/actions/EmojiPickerAction'); +jest.mock('@src/libs/actions/EmojiPickerAction', () => { + const actualEmojiPickerAction = jest.requireActual('@src/libs/actions/EmojiPickerAction'); return { ...actualEmojiPickerAction, emojiPickerRef: { @@ -47,15 +56,15 @@ jest.mock('../../src/libs/actions/EmojiPickerAction', () => { showEmojiPicker: jest.fn(), hideEmojiPicker: jest.fn(), isActive: () => true, - }; + } as EmojiPickerRef; }); -jest.mock('../../src/components/withNavigationFocus', () => (Component) => { - function WithNavigationFocus(props) { +jest.mock('@src/components/withNavigationFocus', () => (Component: ComponentType) => { + function WithNavigationFocus(props: Omit) { return ( ); @@ -70,7 +79,6 @@ beforeAll(() => Onyx.init({ keys: ONYXKEYS, safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], - registerStorageEventListener: () => {}, }), ); @@ -87,6 +95,8 @@ function ReportActionComposeWrapper() { reportID="1" disabled={false} report={LHNTestUtils.getFakeReport()} + isComposerFullSize + listHeight={200} /> ); diff --git a/tests/perf-test/ReportActionsList.perf-test.js b/tests/perf-test/ReportActionsList.perf-test.tsx similarity index 60% rename from tests/perf-test/ReportActionsList.perf-test.js rename to tests/perf-test/ReportActionsList.perf-test.tsx index 1555f2032275..012d8dc8b90f 100644 --- a/tests/perf-test/ReportActionsList.perf-test.js +++ b/tests/perf-test/ReportActionsList.perf-test.tsx @@ -1,17 +1,21 @@ import {fireEvent, screen} from '@testing-library/react-native'; +import type {ComponentType} from 'react'; import Onyx from 'react-native-onyx'; import {measurePerformance} from 'reassure'; -import ComposeProviders from '../../src/components/ComposeProviders'; -import {LocaleContextProvider} from '../../src/components/LocaleContextProvider'; -import OnyxProvider from '../../src/components/OnyxProvider'; -import {WindowDimensionsProvider} from '../../src/components/withWindowDimensions'; -import * as Localize from '../../src/libs/Localize'; -import ONYXKEYS from '../../src/ONYXKEYS'; -import ReportActionsList from '../../src/pages/home/report/ReportActionsList'; -import {ReportAttachmentsProvider} from '../../src/pages/home/report/ReportAttachmentsContext'; -import {ActionListContext, ReactionListContext} from '../../src/pages/home/ReportScreenContext'; -import variables from '../../src/styles/variables'; -import * as LHNTestUtils from '../utils/LHNTestUtils'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import type Navigation from '@libs/Navigation/Navigation'; +import ComposeProviders from '@src/components/ComposeProviders'; +import {LocaleContextProvider} from '@src/components/LocaleContextProvider'; +import OnyxProvider from '@src/components/OnyxProvider'; +import {WindowDimensionsProvider} from '@src/components/withWindowDimensions'; +import * as Localize from '@src/libs/Localize'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ReportActionsList from '@src/pages/home/report/ReportActionsList'; +import {ReportAttachmentsProvider} from '@src/pages/home/report/ReportAttachmentsContext'; +import {ActionListContext, ReactionListContext} from '@src/pages/home/ReportScreenContext'; +import variables from '@src/styles/variables'; +import createRandomReportAction from '../utils/collections/reportActions'; +import * as LHNTestUtilsModule from '../utils/LHNTestUtils'; import PusherHelper from '../utils/PusherHelper'; import * as ReportTestUtils from '../utils/ReportTestUtils'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -19,20 +23,29 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch const mockedNavigate = jest.fn(); -jest.mock('../../src/components/withNavigationFocus', () => (Component) => { - function WithNavigationFocus(props) { - return ( - - ); - } +jest.mock('@components/withCurrentUserPersonalDetails', () => { + // Lazy loading of LHNTestUtils + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + const lazyLoadLHNTestUtils = () => require('../utils/LHNTestUtils'); - WithNavigationFocus.displayName = 'WithNavigationFocus'; + return (Component: ComponentType) => { + function WrappedComponent(props: Omit) { + const currentUserAccountID = 5; + const LHNTestUtils = lazyLoadLHNTestUtils(); // Load LHNTestUtils here - return WithNavigationFocus; + return ( + + ); + } + + WrappedComponent.displayName = 'WrappedComponent'; + + return WrappedComponent; + }; }); jest.mock('@react-navigation/native', () => { @@ -41,16 +54,15 @@ jest.mock('@react-navigation/native', () => { ...actualNav, useRoute: () => mockedNavigate, useIsFocused: () => true, - }; + } as typeof Navigation; }); -jest.mock('../../src/components/ConfirmedRoute.tsx'); +jest.mock('@src/components/ConfirmedRoute.tsx'); beforeAll(() => Onyx.init({ keys: ONYXKEYS, safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], - registerStorageEventListener: () => {}, }), ); @@ -61,7 +73,7 @@ afterAll(() => { const mockOnLayout = jest.fn(); const mockOnScroll = jest.fn(); const mockLoadChats = jest.fn(); -const mockRef = {current: null}; +const mockRef = {current: null, flatListRef: null, scrollPosition: null, setScrollPosition: () => {}}; // Initialize the network key for OfflineWithFeedback beforeEach(() => { @@ -76,22 +88,21 @@ afterEach(() => { PusherHelper.teardown(); }); -const currentUserAccountID = 5; - function ReportActionsListWrapper() { return ( {}} + listID={1} loadOlderChats={mockLoadChats} loadNewerChats={mockLoadChats} - currentUserPersonalDetails={LHNTestUtils.fakePersonalDetails[currentUserAccountID]} /> @@ -110,7 +121,7 @@ test('[ReportActionsList] should render ReportActionsList with 500 reportActions return waitForBatchedUpdates() .then(() => Onyx.multiSet({ - [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtilsModule.fakePersonalDetails, }), ) .then(() => measurePerformance(, {scenario})); @@ -148,7 +159,7 @@ test('[ReportActionsList] should scroll and click some of the reports', () => { return waitForBatchedUpdates() .then(() => Onyx.multiSet({ - [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtilsModule.fakePersonalDetails, }), ) .then(() => measurePerformance(, {scenario})); diff --git a/tests/perf-test/ReportScreen.perf-test.js b/tests/perf-test/ReportScreen.perf-test.tsx similarity index 61% rename from tests/perf-test/ReportScreen.perf-test.js rename to tests/perf-test/ReportScreen.perf-test.tsx index bc127ff8a1f1..b79a6916b7cf 100644 --- a/tests/perf-test/ReportScreen.perf-test.js +++ b/tests/perf-test/ReportScreen.perf-test.tsx @@ -1,19 +1,29 @@ +import type {StackScreenProps} from '@react-navigation/stack'; import {fireEvent, screen, waitFor} from '@testing-library/react-native'; +import type {ComponentType} from 'react'; import React from 'react'; import Onyx from 'react-native-onyx'; +import type Animated from 'react-native-reanimated'; import {measurePerformance} from 'reassure'; -import ComposeProviders from '../../src/components/ComposeProviders'; -import DragAndDropProvider from '../../src/components/DragAndDrop/Provider'; -import {LocaleContextProvider} from '../../src/components/LocaleContextProvider'; -import OnyxProvider from '../../src/components/OnyxProvider'; -import {CurrentReportIDContextProvider} from '../../src/components/withCurrentReportID'; -import {KeyboardStateProvider} from '../../src/components/withKeyboardState'; -import {WindowDimensionsProvider} from '../../src/components/withWindowDimensions'; -import CONST from '../../src/CONST'; -import * as Localize from '../../src/libs/Localize'; -import ONYXKEYS from '../../src/ONYXKEYS'; -import {ReportAttachmentsProvider} from '../../src/pages/home/report/ReportAttachmentsContext'; -import ReportScreen from '../../src/pages/home/ReportScreen'; +import type {WithNavigationFocusProps} from '@components/withNavigationFocus'; +import type Navigation from '@libs/Navigation/Navigation'; +import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types'; +import ComposeProviders from '@src/components/ComposeProviders'; +import DragAndDropProvider from '@src/components/DragAndDrop/Provider'; +import {LocaleContextProvider} from '@src/components/LocaleContextProvider'; +import OnyxProvider from '@src/components/OnyxProvider'; +import {CurrentReportIDContextProvider} from '@src/components/withCurrentReportID'; +import {KeyboardStateProvider} from '@src/components/withKeyboardState'; +import {WindowDimensionsProvider} from '@src/components/withWindowDimensions'; +import CONST from '@src/CONST'; +import * as Localize from '@src/libs/Localize'; +import ONYXKEYS from '@src/ONYXKEYS'; +import {ReportAttachmentsProvider} from '@src/pages/home/report/ReportAttachmentsContext'; +import ReportScreen from '@src/pages/home/ReportScreen'; +import type SCREENS from '@src/SCREENS'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {ReportCollectionDataSet} from '@src/types/onyx/Report'; +import type {ReportActionsCollectionDataSet} from '@src/types/onyx/ReportAction'; import createCollection from '../utils/collections/createCollection'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicy from '../utils/collections/policies'; @@ -24,21 +34,26 @@ import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; -jest.mock('react-native-reanimated', () => ({ - ...jest.requireActual('react-native-reanimated/mock'), - useSharedValue: jest.fn, - useAnimatedStyle: jest.fn, - useAnimatedRef: jest.fn, -})); +type ReportScreenWrapperProps = StackScreenProps; + +jest.mock('react-native-reanimated', () => { + const actualNav = jest.requireActual('react-native-reanimated/mock'); + return { + ...actualNav, + useSharedValue: jest.fn, + useAnimatedStyle: jest.fn, + useAnimatedRef: jest.fn, + } as typeof Animated; +}); -jest.mock('../../src/components/ConfirmedRoute.tsx'); +jest.mock('@src/components/ConfirmedRoute.tsx'); -jest.mock('../../src/components/withNavigationFocus', () => (Component) => { - function WithNavigationFocus(props) { +jest.mock('@src/components/withNavigationFocus', () => (Component: ComponentType) => { + function WithNavigationFocus(props: Omit) { return ( ); @@ -49,7 +64,7 @@ jest.mock('../../src/components/withNavigationFocus', () => (Component) => { return WithNavigationFocus; }); -jest.mock('../../src/hooks/useEnvironment', () => +jest.mock('@src/hooks/useEnvironment', () => jest.fn(() => ({ environment: 'development', environmentURL: 'https://new.expensify.com', @@ -58,13 +73,13 @@ jest.mock('../../src/hooks/useEnvironment', () => })), ); -jest.mock('../../src/libs/Permissions', () => ({ +jest.mock('@src/libs/Permissions', () => ({ canUseLinkPreviews: jest.fn(() => true), canUseDefaultRooms: jest.fn(() => true), })); -jest.mock('../../src/hooks/usePermissions.ts'); +jest.mock('@src/hooks/usePermissions.ts'); -jest.mock('../../src/libs/Navigation/Navigation'); +jest.mock('@src/libs/Navigation/Navigation'); const mockedNavigate = jest.fn(); jest.mock('@react-navigation/native', () => { @@ -81,7 +96,7 @@ jest.mock('@react-navigation/native', () => { addListener: () => jest.fn(), }), createNavigationContainerRef: jest.fn(), - }; + } as typeof Navigation; }); // mock PortalStateContext @@ -91,12 +106,12 @@ beforeAll(() => Onyx.init({ keys: ONYXKEYS, safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], - registerStorageEventListener: () => {}, }), ); // Initialize the network key for OfflineWithFeedback beforeEach(() => { + // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); wrapOnyxWithWaitForBatchedUpdates(Onyx); Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); @@ -109,18 +124,18 @@ afterEach(() => { }); const policies = createCollection( - (item) => `${ONYXKEYS.COLLECTION.POLICY}${item.id}`, - (index) => createRandomPolicy(index), + (item: OnyxTypes.Policy) => `${ONYXKEYS.COLLECTION.POLICY}${item.id}`, + (index: number) => createRandomPolicy(index), 10, ); const personalDetails = createCollection( - (item) => item.accountID, - (index) => createPersonalDetails(index), + (item: OnyxTypes.PersonalDetails) => item.accountID, + (index: number) => createPersonalDetails(index), 20, ); -function ReportScreenWrapper(args) { +function ReportScreenWrapper(props: ReportScreenWrapperProps) { return ( ); @@ -147,6 +163,7 @@ const reportActions = ReportTestUtils.getMockedReportActionsMap(500); const mockRoute = {params: {reportID: '1'}}; test('[ReportScreen] should render ReportScreen with composer interactions', () => { + // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. const {triggerTransitionEnd, addListener} = TestHelper.createAddListenerMock(); const scenario = async () => { /** @@ -182,21 +199,31 @@ test('[ReportScreen] should render ReportScreen with composer interactions', () const navigation = {addListener}; return waitForBatchedUpdates() - .then(() => - Onyx.multiSet({ - [ONYXKEYS.IS_SIDEBAR_LOADED]: true, + .then(() => { + const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${mockRoute.params.reportID}`]: report, + }; + + const reportActionsCollectionDataSet: ReportActionsCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${mockRoute.params.reportID}`]: reportActions, + }; + + return Onyx.multiSet({ + [ONYXKEYS.IS_SIDEBAR_LOADED]: true, [ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetails, [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS], [`${ONYXKEYS.COLLECTION.POLICY}`]: policies, [ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT]: true, - }), - ) + ...reportCollectionDataSet, + ...reportActionsCollectionDataSet, + }); + }) .then(() => measurePerformance( , {scenario}, @@ -205,6 +232,7 @@ test('[ReportScreen] should render ReportScreen with composer interactions', () }); test('[ReportScreen] should press of the report item', () => { + // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. const {triggerTransitionEnd, addListener} = TestHelper.createAddListenerMock(); const scenario = async () => { /** @@ -232,20 +260,30 @@ test('[ReportScreen] should press of the report item', () => { const navigation = {addListener}; return waitForBatchedUpdates() - .then(() => - Onyx.multiSet({ - [ONYXKEYS.IS_SIDEBAR_LOADED]: true, + .then(() => { + const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${mockRoute.params.reportID}`]: report, + }; + + const reportActionsCollectionDataSet: ReportActionsCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${mockRoute.params.reportID}`]: reportActions, + }; + + return Onyx.multiSet({ + [ONYXKEYS.IS_SIDEBAR_LOADED]: true, [ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetails, [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS], [`${ONYXKEYS.COLLECTION.POLICY}`]: policies, - }), - ) + ...reportCollectionDataSet, + ...reportActionsCollectionDataSet, + }); + }) .then(() => measurePerformance( , {scenario}, diff --git a/tests/unit/MiddlewareTest.js b/tests/unit/MiddlewareTest.ts similarity index 69% rename from tests/unit/MiddlewareTest.js rename to tests/unit/MiddlewareTest.ts index 6a9576511f38..b3ba6d32a7bb 100644 --- a/tests/unit/MiddlewareTest.js +++ b/tests/unit/MiddlewareTest.ts @@ -1,10 +1,11 @@ import Onyx from 'react-native-onyx'; -import HttpUtils from '../../src/libs/HttpUtils'; -import * as MainQueue from '../../src/libs/Network/MainQueue'; -import * as NetworkStore from '../../src/libs/Network/NetworkStore'; -import * as SequentialQueue from '../../src/libs/Network/SequentialQueue'; -import * as Request from '../../src/libs/Request'; -import ONYXKEYS from '../../src/ONYXKEYS'; +import HttpUtils from '@src/libs/HttpUtils'; +import handleUnusedOptimisticID from '@src/libs/Middleware/HandleUnusedOptimisticID'; +import * as MainQueue from '@src/libs/Network/MainQueue'; +import * as NetworkStore from '@src/libs/Network/NetworkStore'; +import * as SequentialQueue from '@src/libs/Network/SequentialQueue'; +import * as Request from '@src/libs/Request'; +import ONYXKEYS from '@src/ONYXKEYS'; import * as TestHelper from '../utils/TestHelper'; import waitForNetworkPromises from '../utils/waitForNetworkPromises'; @@ -13,6 +14,7 @@ Onyx.init({ }); beforeAll(() => { + // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); }); @@ -29,8 +31,6 @@ beforeEach(async () => { describe('Middleware', () => { describe('HandleUnusedOptimisticID', () => { test('Normal request', async () => { - const actual = jest.requireActual('../../src/libs/Middleware/HandleUnusedOptimisticID'); - const handleUnusedOptimisticID = jest.spyOn(actual, 'default'); Request.use(handleUnusedOptimisticID); const requests = [ { @@ -50,14 +50,12 @@ describe('Middleware', () => { expect(global.fetch).toHaveBeenCalledTimes(2); expect(global.fetch).toHaveBeenLastCalledWith('https://www.expensify.com.dev/api/AddComment?', expect.anything()); - TestHelper.assertFormDataMatchesObject(global.fetch.mock.calls[1][1].body, {reportID: '1234', reportActionID: '5678', reportComment: 'foo'}); + TestHelper.assertFormDataMatchesObject((global.fetch as jest.Mock).mock.calls[1][1].body, {reportID: '1234', reportActionID: '5678', reportComment: 'foo'}); expect(global.fetch).toHaveBeenNthCalledWith(1, 'https://www.expensify.com.dev/api/OpenReport?', expect.anything()); - TestHelper.assertFormDataMatchesObject(global.fetch.mock.calls[0][1].body, {reportID: '1234'}); + TestHelper.assertFormDataMatchesObject((global.fetch as jest.Mock).mock.calls[0][1].body, {reportID: '1234'}); }); test('Request with preexistingReportID', async () => { - const actual = jest.requireActual('../../src/libs/Middleware/HandleUnusedOptimisticID'); - const handleUnusedOptimisticID = jest.spyOn(actual, 'default'); Request.use(handleUnusedOptimisticID); const requests = [ { @@ -73,8 +71,10 @@ describe('Middleware', () => { SequentialQueue.push(request); } - global.fetch.mockImplementationOnce(async () => ({ + // eslint-disable-next-line @typescript-eslint/require-await + (global.fetch as jest.Mock).mockImplementationOnce(async () => ({ ok: true, + // eslint-disable-next-line @typescript-eslint/require-await json: async () => ({ jsonCode: 200, onyxData: [ @@ -94,9 +94,9 @@ describe('Middleware', () => { expect(global.fetch).toHaveBeenCalledTimes(2); expect(global.fetch).toHaveBeenLastCalledWith('https://www.expensify.com.dev/api/AddComment?', expect.anything()); - TestHelper.assertFormDataMatchesObject(global.fetch.mock.calls[1][1].body, {reportID: '5555', reportActionID: '5678', reportComment: 'foo'}); + TestHelper.assertFormDataMatchesObject((global.fetch as jest.Mock).mock.calls[1][1].body, {reportID: '5555', reportActionID: '5678', reportComment: 'foo'}); expect(global.fetch).toHaveBeenNthCalledWith(1, 'https://www.expensify.com.dev/api/OpenReport?', expect.anything()); - TestHelper.assertFormDataMatchesObject(global.fetch.mock.calls[0][1].body, {reportID: '1234'}); + TestHelper.assertFormDataMatchesObject((global.fetch as jest.Mock).mock.calls[0][1].body, {reportID: '1234'}); }); }); }); diff --git a/tests/unit/markPullRequestsAsDeployedTest.js b/tests/unit/markPullRequestsAsDeployedTest.ts similarity index 78% rename from tests/unit/markPullRequestsAsDeployedTest.js rename to tests/unit/markPullRequestsAsDeployedTest.ts index a401a9f96a67..3d024982c21d 100644 --- a/tests/unit/markPullRequestsAsDeployedTest.js +++ b/tests/unit/markPullRequestsAsDeployedTest.ts @@ -1,10 +1,39 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + /** * @jest-environment node */ -import _ from 'underscore'; import GithubUtils from '../../.github/libs/GithubUtils'; import GitUtils from '../../.github/libs/GitUtils'; +type ObjectMethodData = { + data: T; +}; + +type PullRequest = { + issue_number: number; + title: string; + merged_by: {login: string}; +}; + +type PullRequestParams = { + pull_number: number; +}; + +type PullRequestData = { + data?: PullRequest; +}; + +type Commit = { + commit_sha: string; +}; + +type CommitData = { + data: { + message: string; + }; +}; + let run; const mockGetInput = jest.fn(); @@ -12,8 +41,10 @@ const mockGetPullRequest = jest.fn(); const mockCreateComment = jest.fn(); const mockListTags = jest.fn(); const mockGetCommit = jest.fn(); -let workflowRunURL; -const PRList = { + +let workflowRunURL: string | null; + +const PRList: Record = { 1: { issue_number: 1, title: 'Test PR 1', @@ -35,15 +66,10 @@ const defaultTags = [ {name: '42.42.42-41', commit: {sha: 'efgh'}}, ]; -/** - * @param {String} key - * @returns {Boolean|String} - * @throws {Error} - */ -function mockGetInputDefaultImplementation(key) { +function mockGetInputDefaultImplementation(key: string): boolean | string { switch (key) { case 'PR_LIST': - return JSON.stringify(_.keys(PRList)); + return JSON.stringify(Object.keys(PRList)); case 'IS_PRODUCTION_DEPLOY': return false; case 'DEPLOY_VERSION': @@ -58,11 +84,7 @@ function mockGetInputDefaultImplementation(key) { } } -/** - * @param {String} sha - * @returns {Promise<{data: {message: String}}>} - */ -async function mockGetCommitDefaultImplementation({commit_sha}) { +function mockGetCommitDefaultImplementation({commit_sha}: Commit): CommitData { if (commit_sha === 'abcd') { return {data: {message: 'Test commit 1'}}; } @@ -80,6 +102,7 @@ beforeAll(() => { const moctokit = { rest: { issues: { + // eslint-disable-next-line @typescript-eslint/require-await listForRepo: jest.fn().mockImplementation(async () => ({ data: [ { @@ -87,6 +110,7 @@ beforeAll(() => { }, ], })), + // eslint-disable-next-line @typescript-eslint/require-await listEvents: jest.fn().mockImplementation(async () => ({ data: [{event: 'closed', actor: {login: 'thor'}}], })), @@ -102,18 +126,20 @@ beforeAll(() => { getCommit: mockGetCommit, }, }, - paginate: jest.fn().mockImplementation((objectMethod) => objectMethod().then(({data}) => data)), + paginate: jest.fn().mockImplementation((objectMethod: () => Promise>) => objectMethod().then(({data}) => data)), }; + + // @ts-expect-error TODO: Remove this once GithubUtils (https://github.com/Expensify/App/issues/25382) is migrated to TypeScript. GithubUtils.internalOctokit = moctokit; // Mock GitUtils GitUtils.getPullRequestsMergedBetween = jest.fn(); jest.mock('../../.github/libs/ActionUtils', () => ({ - getJSONInput: jest.fn().mockImplementation((name, defaultValue) => { + getJSONInput: jest.fn().mockImplementation((name: string, defaultValue: string) => { try { - const input = mockGetInput(name); - return JSON.parse(input); + const input: string = mockGetInput(name); + return JSON.parse(input) as unknown; } catch (err) { return defaultValue; } @@ -123,12 +149,12 @@ beforeAll(() => { // Set GH runner environment variables process.env.GITHUB_SERVER_URL = 'https://github.com'; process.env.GITHUB_REPOSITORY = 'Expensify/App'; - process.env.GITHUB_RUN_ID = 1234; + process.env.GITHUB_RUN_ID = '1234'; workflowRunURL = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; }); beforeEach(() => { - mockGetPullRequest.mockImplementation(async ({pull_number}) => (pull_number in PRList ? {data: PRList[pull_number]} : {})); + mockGetPullRequest.mockImplementation(({pull_number}: PullRequestParams): PullRequestData => (pull_number in PRList ? {data: PRList[pull_number]} : {})); mockListTags.mockResolvedValue({ data: defaultTags, }); @@ -150,8 +176,8 @@ describe('markPullRequestsAsDeployed', () => { // Note: we import this in here so that it executes after all the mocks are set up run = require('../../.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed'); await run(); - expect(mockCreateComment).toHaveBeenCalledTimes(_.keys(PRList).length); - for (let i = 0; i < _.keys(PRList).length; i++) { + expect(mockCreateComment).toHaveBeenCalledTimes(Object.keys(PRList).length); + for (let i = 0; i < Object.keys(PRList).length; i++) { const PR = PRList[i + 1]; expect(mockCreateComment).toHaveBeenNthCalledWith(i + 1, { body: `🚀 [Deployed](${workflowRunURL}) to staging by https://github.com/${PR.merged_by.login} in version: ${version} 🚀 @@ -181,8 +207,8 @@ platform | result run = require('../../.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed'); await run(); - expect(mockCreateComment).toHaveBeenCalledTimes(_.keys(PRList).length); - for (let i = 0; i < _.keys(PRList).length; i++) { + expect(mockCreateComment).toHaveBeenCalledTimes(Object.keys(PRList).length); + for (let i = 0; i < Object.keys(PRList).length; i++) { expect(mockCreateComment).toHaveBeenNthCalledWith(i + 1, { body: `🚀 [Deployed](${workflowRunURL}) to production by https://github.com/thor in version: ${version} 🚀 @@ -209,7 +235,7 @@ platform | result } return mockGetInputDefaultImplementation(key); }); - mockGetPullRequest.mockImplementation(async ({pull_number}) => { + mockGetPullRequest.mockImplementation(({pull_number}: PullRequestParams) => { if (pull_number === 3) { return { data: { @@ -226,11 +252,11 @@ platform | result mockListTags.mockResolvedValue({ data: [{name: '42.42.42-43', commit: {sha: 'xyz'}}, ...defaultTags], }); - mockGetCommit.mockImplementation(async ({commit_sha}) => { + mockGetCommit.mockImplementation(({commit_sha}: Commit) => { if (commit_sha === 'xyz') { return {data: {message: 'Test PR 3 (cherry picked from commit dagdag)', committer: {name: 'freyja'}}}; } - return mockGetCommitDefaultImplementation(commit_sha); + return mockGetCommitDefaultImplementation({commit_sha}); }); // Note: we import this in here so that it executes after all the mocks are set up @@ -271,8 +297,8 @@ platform | result // Note: we import this in here so that it executes after all the mocks are set up run = require('../../.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed'); await run(); - expect(mockCreateComment).toHaveBeenCalledTimes(_.keys(PRList).length); - for (let i = 0; i < _.keys(PRList).length; i++) { + expect(mockCreateComment).toHaveBeenCalledTimes(Object.keys(PRList).length); + for (let i = 0; i < Object.keys(PRList).length; i++) { const PR = PRList[i + 1]; expect(mockCreateComment).toHaveBeenNthCalledWith(i + 1, { body: `🚀 [Deployed](${workflowRunURL}) to staging by https://github.com/${PR.merged_by.login} in version: ${version} 🚀