diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index 62571406e2de..ef4aa28aa022 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -37,7 +37,8 @@ import {ReportActionPositionContextProvider, ReportActionScrollToNewestContext} import ReportActionsListItemRenderer from '@pages/inbox/report/ReportActionsListItemRenderer'; import useReportUnreadMessageScrollTracking from '@pages/inbox/report/useReportUnreadMessageScrollTracking'; -import {openReport, subscribeToNewActionEvent} from '@userActions/Report'; +import {openReport} from '@userActions/Report'; +import {subscribeToNewActionEvent} from '@userActions/Report/reportActionSubscribers'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/components/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx index 14163368cb65..4782cab9d7b1 100644 --- a/src/components/ReportActionItem/TaskView.tsx +++ b/src/components/ReportActionItem/TaskView.tsx @@ -33,7 +33,8 @@ import StringUtils from '@libs/StringUtils'; import {isActiveTaskEditRoute} from '@libs/TaskUtils'; import {callFunctionIfActionIsAllowed} from '@userActions/Session'; -import {canActionTask, canModifyTask, clearTaskErrors, completeTask, reopenTask, setTaskReport} from '@userActions/Task'; +import {canActionTask, canModifyTask, completeTask, reopenTask, setTaskReport} from '@userActions/Task'; +import {clearTaskErrors} from '@userActions/TaskDeletion'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/libs/actions/Chronos.ts b/src/libs/actions/Chronos.ts index 6d307711d50d..1a9436049e00 100644 --- a/src/libs/actions/Chronos.ts +++ b/src/libs/actions/Chronos.ts @@ -16,7 +16,7 @@ import type {OnyxUpdate} from 'react-native-onyx'; import {Str} from 'expensify-common'; import Onyx from 'react-native-onyx'; -import {notifyNewAction} from './Report'; +import {notifyNewAction} from './Report/reportActionSubscribers'; type ChronosTimerOnyxUpdate = OnyxUpdate; diff --git a/src/libs/actions/IOU/Hold.ts b/src/libs/actions/IOU/Hold.ts index 0ed7f7decb47..ffb2e7827fd0 100644 --- a/src/libs/actions/IOU/Hold.ts +++ b/src/libs/actions/IOU/Hold.ts @@ -32,7 +32,7 @@ import { } from '@libs/ReportUtils'; import {getAmount, isScanFailedTransactionMovedOnPayment} from '@libs/TransactionUtils'; -import {notifyNewAction} from '@userActions/Report'; +import {notifyNewAction} from '@userActions/Report/reportActionSubscribers'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/libs/actions/IOU/PayMoneyRequest.ts b/src/libs/actions/IOU/PayMoneyRequest.ts index 1ee8496b1913..439ad7710662 100644 --- a/src/libs/actions/IOU/PayMoneyRequest.ts +++ b/src/libs/actions/IOU/PayMoneyRequest.ts @@ -33,7 +33,8 @@ import {shouldSplitScanFailedTransactions} from '@libs/TransactionUtils'; import {buildPolicyData, generatePolicyID} from '@userActions/Policy/Policy'; import type {BuildPolicyDataKeys} from '@userActions/Policy/Policy'; -import {completeOnboarding, notifyNewAction} from '@userActions/Report'; +import {completeOnboarding} from '@userActions/Report'; +import {notifyNewAction} from '@userActions/Report/reportActionSubscribers'; import {getOnboardingMessages} from '@userActions/Welcome/OnboardingFlow'; import type {OnboardingCompanySize} from '@userActions/Welcome/OnboardingFlow'; diff --git a/src/libs/actions/IOU/PerDiem.ts b/src/libs/actions/IOU/PerDiem.ts index 33a33f3623c6..159f80e19c3d 100644 --- a/src/libs/actions/IOU/PerDiem.ts +++ b/src/libs/actions/IOU/PerDiem.ts @@ -41,7 +41,7 @@ import {addOptimization} from '@libs/telemetry/submitFollowUpAction'; import {buildOptimisticTransaction} from '@libs/TransactionUtils'; import {buildOptimisticPolicyRecentlyUsedTags} from '@userActions/Policy/Tag'; -import {notifyNewAction} from '@userActions/Report'; +import {notifyNewAction} from '@userActions/Report/reportActionSubscribers'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/libs/actions/IOU/RejectMoneyRequest.ts b/src/libs/actions/IOU/RejectMoneyRequest.ts index cef21bc9ad54..3843a870a194 100644 --- a/src/libs/actions/IOU/RejectMoneyRequest.ts +++ b/src/libs/actions/IOU/RejectMoneyRequest.ts @@ -31,7 +31,7 @@ import { import {getAmount, getCurrency} from '@libs/TransactionUtils'; import type {AvatarSource} from '@libs/UserAvatarUtils'; -import {notifyNewAction} from '@userActions/Report'; +import {notifyNewAction} from '@userActions/Report/reportActionSubscribers'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; diff --git a/src/libs/actions/IOU/SendInvoice.ts b/src/libs/actions/IOU/SendInvoice.ts index c5c3d5fc864a..b2ed65b65328 100644 --- a/src/libs/actions/IOU/SendInvoice.ts +++ b/src/libs/actions/IOU/SendInvoice.ts @@ -27,7 +27,7 @@ import {addOptimization} from '@libs/telemetry/submitFollowUpAction'; import {buildOptimisticTransaction} from '@libs/TransactionUtils'; import {buildOptimisticPolicyRecentlyUsedTags} from '@userActions/Policy/Tag'; -import {notifyNewAction} from '@userActions/Report'; +import {notifyNewAction} from '@userActions/Report/reportActionSubscribers'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/libs/actions/IOU/SendMoney.ts b/src/libs/actions/IOU/SendMoney.ts index 7a990a340042..b581eaf6a9e9 100644 --- a/src/libs/actions/IOU/SendMoney.ts +++ b/src/libs/actions/IOU/SendMoney.ts @@ -25,7 +25,7 @@ import playSound, {SOUNDS} from '@libs/Sound'; import {addOptimization, startTracking} from '@libs/telemetry/submitFollowUpAction'; import {buildOptimisticTransaction} from '@libs/TransactionUtils'; -import {notifyNewAction} from '@userActions/Report'; +import {notifyNewAction} from '@userActions/Report/reportActionSubscribers'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/libs/actions/IOU/Split.ts b/src/libs/actions/IOU/Split.ts index d95bae324bcd..e836a9382382 100644 --- a/src/libs/actions/IOU/Split.ts +++ b/src/libs/actions/IOU/Split.ts @@ -57,7 +57,7 @@ import { } from '@libs/TransactionUtils'; import {buildOptimisticPolicyRecentlyUsedTags} from '@userActions/Policy/Tag'; -import {notifyNewAction} from '@userActions/Report'; +import {notifyNewAction} from '@userActions/Report/reportActionSubscribers'; import {sanitizeWaypointsForAPI} from '@userActions/Transaction'; import CONST from '@src/CONST'; diff --git a/src/libs/actions/IOU/TrackExpense.ts b/src/libs/actions/IOU/TrackExpense.ts index add79b3835e9..f1ebe8fc34aa 100644 --- a/src/libs/actions/IOU/TrackExpense.ts +++ b/src/libs/actions/IOU/TrackExpense.ts @@ -85,7 +85,8 @@ import {buildAddMembersToWorkspaceOnyxData, buildUpdateWorkspaceMembersRoleOnyxD import {buildPolicyData} from '@userActions/Policy/Policy'; import type {BuildPolicyDataKeys} from '@userActions/Policy/Policy'; import type {GuidedSetupData} from '@userActions/Report'; -import {buildInviteToRoomOnyxData, notifyNewAction} from '@userActions/Report'; +import {buildInviteToRoomOnyxData} from '@userActions/Report'; +import {notifyNewAction} from '@userActions/Report/reportActionSubscribers'; import {stringifyWaypointsForAPI} from '@userActions/Transaction'; import {getOnboardingMessages} from '@userActions/Welcome/OnboardingFlow'; diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index 0da09fd6c48d..7fb165874204 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -2,7 +2,7 @@ import * as API from '@libs/API'; import type {GenerateSpotnanaTokenParams} from '@libs/API/parameters'; import {SIDE_EFFECT_REQUEST_COMMANDS} from '@libs/API/types'; import asyncOpenURL from '@libs/asyncOpenURL'; -import * as Environment from '@libs/Environment/Environment'; +import buildOldDotURL from '@libs/buildOldDotURL'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import isPublicScreenRoute from '@libs/isPublicScreenRoute'; import Log from '@libs/Log'; @@ -17,6 +17,7 @@ import Navigation from '@libs/Navigation/Navigation'; import navigationRef from '@libs/Navigation/navigationRef'; import REPORT_LINK_ROUTE_PARAMS from '@libs/Navigation/reportLinkRouteParams'; import {getIsOffline} from '@libs/NetworkState'; +import openExternalLink from '@libs/openExternalLink'; import {findLastAccessedReport, getReportIDFromLink, getReportOrDraftReport, getRouteFromLink, isMoneyRequestReport} from '@libs/ReportUtils'; import shouldSkipDeepLinkNavigation from '@libs/shouldSkipDeepLinkNavigation'; import {endSpan, getSpan, startSpan} from '@libs/telemetry/activeSpans'; @@ -42,48 +43,15 @@ import {doneCheckingPublicRoom, navigateToConciergeChat, openReport} from './Rep import {canAnonymousUserAccessRoute, isAnonymousUser, signOutAndRedirectToSignIn, waitForUserSignIn} from './Session'; import {setOnboardingErrorMessage} from './Welcome'; -let currentUserEmail = ''; let currentUserAccountID: number = CONST.DEFAULT_NUMBER_ID; // Use connectWithoutView since this is to open an external link and doesn't affect any UI Onyx.connectWithoutView({ key: ONYXKEYS.SESSION, callback: (value) => { - currentUserEmail = value?.email ?? ''; currentUserAccountID = value?.accountID ?? CONST.DEFAULT_NUMBER_ID; }, }); -function buildOldDotURL(url: string, shortLivedAuthToken?: string): Promise { - const hashIndex = url.lastIndexOf('#'); - const hasHashParams = hashIndex !== -1; - const hasURLParams = url.indexOf('?') !== -1; - let originURL = url; - let hashParams = ''; - if (hasHashParams) { - originURL = url.substring(0, hashIndex); - hashParams = url.substring(hashIndex); - } - - const authTokenParam = shortLivedAuthToken ? `authToken=${shortLivedAuthToken}` : ''; - const emailParam = `email=${encodeURIComponent(currentUserEmail)}`; - const paramsArray = [authTokenParam, emailParam]; - const params = paramsArray.filter(Boolean).join('&'); - - return Environment.getOldDotEnvironmentURL().then((environmentURL) => { - const oldDotDomain = addTrailingForwardSlash(environmentURL); - - // If the URL contains # or ?, we can assume they don't need to have the `?` token to start listing url parameters. - return `${oldDotDomain}${originURL}${hasURLParams ? '&' : '?'}${params}${hashParams}`; - }); -} - -/** - * @param shouldSkipCustomSafariLogic When true, we will use `Linking.openURL` even if the browser is Safari. - */ -function openExternalLink(url: string, shouldSkipCustomSafariLogic = false, shouldOpenInSameTab = false) { - asyncOpenURL(Promise.resolve(), url, shouldSkipCustomSafariLogic, shouldOpenInSameTab); -} - function openOldDotLink(url: string, shouldOpenInSameTab = false) { if (getIsOffline()) { buildOldDotURL(url).then((oldDotURL) => openExternalLink(oldDotURL, undefined, shouldOpenInSameTab)); @@ -720,7 +688,6 @@ export { openTravelDotLink, buildTravelDotURL, getTravelDotLink, - buildOldDotURL, openReportFromDeepLink, getShortLivedAuthTokenURL, }; diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 3d6d357d95cb..3013131946d4 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -276,13 +276,7 @@ import {DeviceEventEmitter, Linking} from 'react-native'; import Onyx from 'react-native-onyx'; import deleteReport from './DeleteReport'; - -type SubscriberCallback = (isFromCurrentUser: boolean, reportAction: ReportAction | undefined) => void; - -type ActionSubscriber = { - reportID: string; - callback: SubscriberCallback; -}; +import {notifyNewAction} from './reportActionSubscribers'; type Video = Dimensions & { url: string; @@ -817,35 +811,6 @@ function clearAgentZeroProcessingIndicator(reportID: string, agentAccountID: num AgentZeroReasoningStore.clearReasoning(reportID, agentAccountID); } -// New action subscriber array for report pages -let newActionSubscribers: ActionSubscriber[] = []; - -/** - * Enables the Report actions file to let the ReportActionsList know that a new comment has arrived in realtime for the current report - * Add subscriber for report id - * @returns Remove subscriber for report id - */ -function subscribeToNewActionEvent(reportID: string, callback: SubscriberCallback): () => void { - newActionSubscribers.push({callback, reportID}); - return () => { - newActionSubscribers = newActionSubscribers.filter((subscriber) => subscriber.reportID !== reportID); - }; -} - -/** Notify the ReportActionsList that a new comment has arrived */ -function notifyNewAction(reportID: string | string[] | undefined, reportAction: ReportAction | undefined, isFromCurrentUser: boolean) { - if (!reportID) { - return; - } - const ids = Array.isArray(reportID) ? reportID : [reportID]; - for (const id of ids) { - const actionSubscriber = newActionSubscribers.find((subscriber) => subscriber.reportID === id); - if (actionSubscriber) { - actionSubscriber.callback(isFromCurrentUser, reportAction); - } - } -} - /** * Builds an optimistic report action with resolved followups (followup-list marked as selected). * @param reportAction - The report action to check and potentially resolve @@ -9043,7 +9008,6 @@ export { navigateToConciergeChat, navigateToConciergeChatAndDeleteReport, clearCreateChatError, - notifyNewAction, openReport, openRoomMembersPage, readNewestAction, @@ -9065,7 +9029,6 @@ export { shouldShowReportActionNotification, showReportActionNotification, startNewChat, - subscribeToNewActionEvent, subscribeToReportLeavingEvents, clearAgentZeroProcessingIndicator, clearConciergeThinkingKickoff, diff --git a/src/libs/actions/Report/reportActionSubscribers.ts b/src/libs/actions/Report/reportActionSubscribers.ts new file mode 100644 index 000000000000..8fc4644ac0eb --- /dev/null +++ b/src/libs/actions/Report/reportActionSubscribers.ts @@ -0,0 +1,42 @@ +/** + * Subscriber registry for new report actions, kept out of the Report hub so callers can notify without importing it. + */ +import type ReportAction from '@src/types/onyx/ReportAction'; + +type SubscriberCallback = (isFromCurrentUser: boolean, reportAction: ReportAction | undefined) => void; + +type ActionSubscriber = { + reportID: string; + callback: SubscriberCallback; +}; + +// New action subscriber array for report pages +let newActionSubscribers: ActionSubscriber[] = []; + +/** + * Enables the Report actions file to let the ReportActionsList know that a new comment has arrived in realtime for the current report + * Add subscriber for report id + * @returns Remove subscriber for report id + */ +function subscribeToNewActionEvent(reportID: string, callback: SubscriberCallback): () => void { + newActionSubscribers.push({callback, reportID}); + return () => { + newActionSubscribers = newActionSubscribers.filter((subscriber) => subscriber.reportID !== reportID); + }; +} + +/** Notify the ReportActionsList that a new comment has arrived */ +function notifyNewAction(reportID: string | string[] | undefined, reportAction: ReportAction | undefined, isFromCurrentUser: boolean) { + if (!reportID) { + return; + } + const ids = Array.isArray(reportID) ? reportID : [reportID]; + for (const id of ids) { + const actionSubscriber = newActionSubscribers.find((subscriber) => subscriber.reportID === id); + if (actionSubscriber) { + actionSubscriber.callback(isFromCurrentUser, reportAction); + } + } +} + +export {subscribeToNewActionEvent, notifyNewAction}; diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 20a8ea435683..02f1c891566a 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -1,4 +1,3 @@ -import {buildOldDotURL, openExternalLink} from '@libs/actions/Link'; import * as PersistedRequests from '@libs/actions/PersistedRequests'; import * as API from '@libs/API'; import type { @@ -21,6 +20,7 @@ import type { import type SignInUserParams from '@libs/API/parameters/SignInUserParams'; import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import asyncOpenURL from '@libs/asyncOpenURL'; +import buildOldDotURL from '@libs/buildOldDotURL'; import * as ErrorUtils from '@libs/ErrorUtils'; import FraudProtection from '@libs/FraudProtection'; import getPlatform from '@libs/getPlatform'; @@ -34,6 +34,7 @@ import * as MainQueue from '@libs/Network/MainQueue'; import * as NetworkStore from '@libs/Network/NetworkStore'; import {getCurrentUserEmail} from '@libs/Network/NetworkStore'; import * as SequentialQueue from '@libs/Network/SequentialQueue'; +import openExternalLink from '@libs/openExternalLink'; import {buildPersonalDetailsUpdate} from '@libs/PersonalDetailsUtils'; import type {PersonalDetailsOnyxUpdate} from '@libs/PersonalDetailsUtils'; import clearPrefetchOnAppStart from '@libs/Prefetch/clearPrefetchOnAppStart'; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 88ac2f5caf17..e0c8bba0fe08 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -3,7 +3,7 @@ import FallbackAvatar from '@assets/images/avatars/fallback-avatar.svg'; import type {LocaleContextProps, LocalizedTranslate} from '@components/LocaleContextProvider'; import * as API from '@libs/API'; -import type {CancelTaskParams, CompleteTaskParams, CreateTaskParams, EditTaskAssigneeParams, EditTaskParams, ReopenTaskParams} from '@libs/API/parameters'; +import type {CompleteTaskParams, CreateTaskParams, EditTaskAssigneeParams, EditTaskParams, ReopenTaskParams} from '@libs/API/parameters'; import {WRITE_COMMANDS} from '@libs/API/types'; import DateUtils from '@libs/DateUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; @@ -26,13 +26,11 @@ import {generateAccountID} from '@libs/UserUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES, {DYNAMIC_ROUTES} from '@src/ROUTES'; +import {DYNAMIC_ROUTES} from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {Icon} from '@src/types/onyx/OnyxCommon'; -import type PersonalDetails from '@src/types/onyx/PersonalDetails'; import type {CurrentUserPersonalDetails} from '@src/types/onyx/PersonalDetails'; -import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import type {OnyxData} from '@src/types/onyx/Request'; import type {SearchResultDataType} from '@src/types/onyx/SearchResults'; @@ -43,7 +41,7 @@ import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-nat import {Str} from 'expensify-common'; import Onyx from 'react-native-onyx'; -import {getMostRecentReportID, navigateToConciergeChatAndDeleteReport, notifyNewAction, optimisticReportLastData} from './Report'; +import {notifyNewAction} from './Report/reportActionSubscribers'; import {setSelfTourViewed} from './Welcome'; type EditTaskAssigneeOptions = { @@ -110,11 +108,6 @@ type CreateTaskFromMarkdownParams = { ancestors?: ReportUtils.Ancestor[]; }; -type DeleteTaskOptions = { - ancestors?: ReportUtils.Ancestor[]; - shouldNavigateBack?: boolean; -}; - /** * Clears out the task info from the store */ @@ -1331,179 +1324,6 @@ function getShareDestination( }; } -/** - * Calculate the URL to navigate to after a task deletion - * @param report - The task report being deleted - * @returns The URL to navigate to - */ -function getNavigationUrlOnTaskDelete(report: OnyxEntry, conciergeReportID: string | undefined, reportActions: OnyxEntry): string | undefined { - if (!report) { - return undefined; - } - - const shouldDeleteTaskReport = !ReportActionsUtils.doesReportHaveVisibleActions(report.reportID, reportActions); - if (!shouldDeleteTaskReport) { - return undefined; - } - - if (report?.parentReportID) { - return ROUTES.REPORT_WITH_ID.getRoute(report.parentReportID); - } - - // If no parent report, try to navigate to most recent report - const mostRecentReportID = getMostRecentReportID(report, conciergeReportID); - if (mostRecentReportID) { - return ROUTES.REPORT_WITH_ID.getRoute(mostRecentReportID); - } - - return undefined; -} - -/** - * Cancels a task by setting the report state to SUBMITTED and status to CLOSED - */ -function deleteTask( - report: OnyxEntry, - parentReport: OnyxEntry, - isReportArchived: boolean, - currentUserAccountID: number, - hasOutstandingChildTask: boolean, - parentReportAction: OnyxEntry, - conciergeReportID: string | undefined, - delegateEmail: string | undefined, - reportActions: OnyxEntry, - {ancestors = [], shouldNavigateBack = true}: DeleteTaskOptions = {}, -) { - if (!report) { - return; - } - const message = `deleted task: ${report.reportName}`; - const optimisticCancelReportAction = ReportUtils.buildOptimisticTaskReportAction(report.reportID, CONST.REPORT.ACTIONS.TYPE.TASK_CANCELLED, delegateEmail, message); - const optimisticReportActionID = optimisticCancelReportAction.reportActionID; - const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report, isReportArchived); - - // If the task report is the last visible action in the parent report, we should navigate back to the parent report - const shouldDeleteTaskReport = !ReportActionsUtils.doesReportHaveVisibleActions(report.reportID, reportActions, canUserPerformWriteAction); - const optimisticReportAction: Partial = { - pendingAction: shouldDeleteTaskReport ? CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE : CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, - previousMessage: parentReportAction?.message, - message: [ - { - translationKey: '', - type: 'COMMENT', - html: '', - text: '', - isEdited: true, - isDeletedParentAction: true, - }, - ], - errors: undefined, - linkMetadata: [], - }; - const optimisticReportActions = parentReportAction?.reportActionID ? {[parentReportAction?.reportActionID]: optimisticReportAction} : {}; - - const optimisticLastReportData = optimisticReportLastData(parentReport?.reportID ?? String(CONST.DEFAULT_NUMBER_ID), optimisticReportActions as ReportActions, canUserPerformWriteAction); - const optimisticData: Array> = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, - value: { - lastVisibleActionCreated: optimisticCancelReportAction.created, - lastMessageText: message, - lastActorAccountID: optimisticCancelReportAction.actorAccountID, - isDeletedParentAction: true, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport?.reportID}`, - value: { - ...optimisticLastReportData, - hasOutstandingChildTask, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, - value: { - [optimisticReportActionID]: optimisticCancelReportAction as OnyxTypes.ReportAction, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`, - value: optimisticReportActions as OnyxTypes.ReportActions, - }, - ]; - - // Update optimistic data for parent report action if the report is a child report and the task report has no visible child - const childVisibleActionCount = parentReportAction?.childVisibleActionCount ?? 0; - if (childVisibleActionCount === 0) { - optimisticData.push(...ReportUtils.getOptimisticDataForAncestors(ancestors, parentReport?.lastVisibleActionCreated ?? '', CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)); - } - - const successData: Array> = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, - value: { - [optimisticReportActionID]: { - pendingAction: null, - }, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`, - value: parentReportAction?.reportActionID ? {[parentReportAction.reportActionID]: {pendingAction: null}} : {}, - }, - ]; - - const failureData: Array> = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, - value: { - stateNum: report.stateNum ?? '', - statusNum: report.statusNum ?? '', - } as OnyxTypes.Report, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport?.reportID}`, - value: { - hasOutstandingChildTask: parentReport?.hasOutstandingChildTask, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, - value: { - [optimisticReportActionID]: null, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`, - value: parentReportAction?.reportActionID ? {[parentReportAction?.reportActionID]: {pendingAction: null}} : {}, - }, - ]; - - const parameters: CancelTaskParams = { - cancelledTaskReportActionID: optimisticReportActionID, - taskReportID: report.reportID, - }; - - API.write(WRITE_COMMANDS.CANCEL_TASK, parameters, {optimisticData, successData, failureData}); - notifyNewAction(report.reportID, undefined, true); - - const urlToNavigateBack = shouldNavigateBack ? getNavigationUrlOnTaskDelete(report, conciergeReportID, reportActions) : undefined; - if (urlToNavigateBack) { - Navigation.goBack(); - return urlToNavigateBack; - } -} - /** * Closes the current open task modal and clears out the task info from the store. */ @@ -1598,48 +1418,6 @@ function canActionTask( return false; } -function clearTaskErrors( - report: OnyxEntry, - conciergeReportID: string | undefined, - currentUserAccountID: number, - introSelected: OnyxEntry, - betas: OnyxEntry, - isSelfTourViewed: boolean | undefined, - reportOwnerPersonalDetail: OnyxEntry, - currentUserPersonalDetail: OnyxEntry, - conciergePersonalDetail: OnyxEntry, -) { - const reportID = report?.reportID; - if (!reportID) { - return; - } - - // Delete the task preview in the parent report - if (report?.pendingFields?.createChat === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, report.parentReportActionID ? {[report.parentReportActionID]: null} : {}); - - navigateToConciergeChatAndDeleteReport( - reportID, - conciergeReportID, - currentUserAccountID, - introSelected, - isSelfTourViewed, - betas, - reportOwnerPersonalDetail, - currentUserPersonalDetail, - conciergePersonalDetail, - undefined, - undefined, - ); - return; - } - - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { - pendingFields: null, - errorFields: null, - }); -} - /** Onboarding task info resolved by the `useOnboardingTaskInformation` hook. */ type OnboardingTaskInformation = { taskReport: OnyxEntry; @@ -1749,12 +1527,9 @@ export { startOutCreateTaskQuickAction, getAssignee, getShareDestination, - deleteTask, dismissModalAndClearOutTaskInfo, getTaskAssigneeAccountID, - clearTaskErrors, canModifyTask, - getNavigationUrlOnTaskDelete, canActionTask, getFinishOnboardingTaskOnyxData, completeTestDriveTask, diff --git a/src/libs/actions/TaskDeletion.ts b/src/libs/actions/TaskDeletion.ts new file mode 100644 index 000000000000..f6557fe06015 --- /dev/null +++ b/src/libs/actions/TaskDeletion.ts @@ -0,0 +1,247 @@ +/** + * Task deletion and error clearing, split out of Task so that file does not import the Report hub. + */ + +import {write} from '@libs/API'; +import type {CancelTaskParams} from '@libs/API/parameters'; +import {WRITE_COMMANDS} from '@libs/API/types'; +import Navigation from '@libs/Navigation/Navigation'; +import {doesReportHaveVisibleActions} from '@libs/ReportActionsUtils'; +import type {Ancestor, OptimisticTaskReportAction} from '@libs/ReportUtils'; +import {buildOptimisticTaskReportAction, canUserPerformWriteAction as canUserPerformWriteActionReportUtils, getOptimisticDataForAncestors} from '@libs/ReportUtils'; + +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; +import type PersonalDetails from '@src/types/onyx/PersonalDetails'; +import type ReportAction from '@src/types/onyx/ReportAction'; + +import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx'; + +import Onyx from 'react-native-onyx'; + +import {getMostRecentReportID, navigateToConciergeChatAndDeleteReport, optimisticReportLastData} from './Report'; +import {notifyNewAction} from './Report/reportActionSubscribers'; + +type DeleteTaskOptions = { + ancestors?: Ancestor[]; + shouldNavigateBack?: boolean; +}; + +/** + * Calculate the URL to navigate to after a task deletion + * @param report - The task report being deleted + * @returns The URL to navigate to + */ +function getNavigationUrlOnTaskDelete(report: OnyxEntry, conciergeReportID: string | undefined, reportActions: OnyxEntry): string | undefined { + if (!report) { + return undefined; + } + + const shouldDeleteTaskReport = !doesReportHaveVisibleActions(report.reportID, reportActions); + if (!shouldDeleteTaskReport) { + return undefined; + } + + if (report?.parentReportID) { + return ROUTES.REPORT_WITH_ID.getRoute(report.parentReportID); + } + + // If no parent report, try to navigate to most recent report + const mostRecentReportID = getMostRecentReportID(report, conciergeReportID); + if (mostRecentReportID) { + return ROUTES.REPORT_WITH_ID.getRoute(mostRecentReportID); + } + + return undefined; +} + +/** + * Cancels a task by setting the report state to SUBMITTED and status to CLOSED + */ +function deleteTask( + report: OnyxEntry, + parentReport: OnyxEntry, + isReportArchived: boolean, + currentUserAccountID: number, + hasOutstandingChildTask: boolean, + parentReportAction: OnyxEntry, + conciergeReportID: string | undefined, + delegateEmail: string | undefined, + reportActions: OnyxEntry, + {ancestors = [], shouldNavigateBack = true}: DeleteTaskOptions = {}, +) { + if (!report) { + return; + } + const message = `deleted task: ${report.reportName}`; + const optimisticCancelReportAction = buildOptimisticTaskReportAction(report.reportID, CONST.REPORT.ACTIONS.TYPE.TASK_CANCELLED, delegateEmail, message); + const optimisticReportActionID = optimisticCancelReportAction.reportActionID; + const canUserPerformWriteAction = canUserPerformWriteActionReportUtils(report, isReportArchived); + + // If the task report is the last visible action in the parent report, we should navigate back to the parent report + const shouldDeleteTaskReport = !doesReportHaveVisibleActions(report.reportID, reportActions, canUserPerformWriteAction); + const optimisticReportAction: Partial = { + pendingAction: shouldDeleteTaskReport ? CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE : CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + previousMessage: parentReportAction?.message, + message: [ + { + translationKey: '', + type: 'COMMENT', + html: '', + text: '', + isEdited: true, + isDeletedParentAction: true, + }, + ], + errors: undefined, + linkMetadata: [], + }; + const optimisticReportActions = parentReportAction?.reportActionID ? {[parentReportAction?.reportActionID]: optimisticReportAction} : {}; + + const optimisticLastReportData = optimisticReportLastData(parentReport?.reportID ?? String(CONST.DEFAULT_NUMBER_ID), optimisticReportActions, canUserPerformWriteAction); + const optimisticData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, + value: { + lastVisibleActionCreated: optimisticCancelReportAction.created, + lastMessageText: message, + lastActorAccountID: optimisticCancelReportAction.actorAccountID, + isDeletedParentAction: true, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport?.reportID}`, + value: { + ...optimisticLastReportData, + hasOutstandingChildTask, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, + value: { + [optimisticReportActionID]: optimisticCancelReportAction as OnyxTypes.ReportAction, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`, + value: optimisticReportActions, + }, + ]; + + // Update optimistic data for parent report action if the report is a child report and the task report has no visible child + const childVisibleActionCount = parentReportAction?.childVisibleActionCount ?? 0; + if (childVisibleActionCount === 0) { + optimisticData.push(...getOptimisticDataForAncestors(ancestors, parentReport?.lastVisibleActionCreated ?? '', CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)); + } + + const successData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, + value: { + [optimisticReportActionID]: { + pendingAction: null, + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`, + value: parentReportAction?.reportActionID ? {[parentReportAction.reportActionID]: {pendingAction: null}} : {}, + }, + ]; + + const failureData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, + value: { + stateNum: report.stateNum, + statusNum: report.statusNum, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport?.reportID}`, + value: { + hasOutstandingChildTask: parentReport?.hasOutstandingChildTask, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, + value: { + [optimisticReportActionID]: null, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`, + value: parentReportAction?.reportActionID ? {[parentReportAction?.reportActionID]: {pendingAction: null}} : {}, + }, + ]; + + const parameters: CancelTaskParams = { + cancelledTaskReportActionID: optimisticReportActionID, + taskReportID: report.reportID, + }; + + write(WRITE_COMMANDS.CANCEL_TASK, parameters, {optimisticData, successData, failureData}); + notifyNewAction(report.reportID, undefined, true); + + const urlToNavigateBack = shouldNavigateBack ? getNavigationUrlOnTaskDelete(report, conciergeReportID, reportActions) : undefined; + if (urlToNavigateBack) { + Navigation.goBack(); + return urlToNavigateBack; + } +} + +function clearTaskErrors( + report: OnyxEntry, + conciergeReportID: string | undefined, + currentUserAccountID: number, + introSelected: OnyxEntry, + betas: OnyxEntry, + isSelfTourViewed: boolean | undefined, + reportOwnerPersonalDetail: OnyxEntry, + currentUserPersonalDetail: OnyxEntry, + conciergePersonalDetail: OnyxEntry, +) { + const reportID = report?.reportID; + if (!reportID) { + return; + } + + // Delete the task preview in the parent report + if (report?.pendingFields?.createChat === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, report.parentReportActionID ? {[report.parentReportActionID]: null} : {}); + + navigateToConciergeChatAndDeleteReport( + reportID, + conciergeReportID, + currentUserAccountID, + introSelected, + isSelfTourViewed, + betas, + reportOwnerPersonalDetail, + currentUserPersonalDetail, + conciergePersonalDetail, + undefined, + undefined, + ); + return; + } + + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { + pendingFields: null, + errorFields: null, + }); +} + +export {getNavigationUrlOnTaskDelete, deleteTask, clearTaskErrors}; diff --git a/src/libs/buildOldDotURL.ts b/src/libs/buildOldDotURL.ts new file mode 100644 index 000000000000..8ff2b4575f7f --- /dev/null +++ b/src/libs/buildOldDotURL.ts @@ -0,0 +1,34 @@ +/** + * Builds an OldDot URL with the current user's email and optional short-lived token, kept out of + * actions/Link so callers such as actions/Session skip the deep-link machinery. + */ + +import {getCurrentUserEmail} from './CurrentUserStore'; +import {getOldDotEnvironmentURL} from './Environment/Environment'; +import addTrailingForwardSlash from './UrlUtils'; + +function buildOldDotURL(url: string, shortLivedAuthToken?: string): Promise { + const hashIndex = url.lastIndexOf('#'); + const hasHashParams = hashIndex !== -1; + const hasURLParams = url.indexOf('?') !== -1; + let originURL = url; + let hashParams = ''; + if (hasHashParams) { + originURL = url.substring(0, hashIndex); + hashParams = url.substring(hashIndex); + } + + const authTokenParam = shortLivedAuthToken ? `authToken=${shortLivedAuthToken}` : ''; + const emailParam = `email=${encodeURIComponent(getCurrentUserEmail() ?? '')}`; + const paramsArray = [authTokenParam, emailParam]; + const params = paramsArray.filter(Boolean).join('&'); + + return getOldDotEnvironmentURL().then((environmentURL) => { + const oldDotDomain = addTrailingForwardSlash(environmentURL); + + // If the URL contains # or ?, we can assume they don't need to have the `?` token to start listing url parameters. + return `${oldDotDomain}${originURL}${hasURLParams ? '&' : '?'}${params}${hashParams}`; + }); +} + +export default buildOldDotURL; diff --git a/src/libs/openExternalLink.ts b/src/libs/openExternalLink.ts new file mode 100644 index 000000000000..b7c36c321c55 --- /dev/null +++ b/src/libs/openExternalLink.ts @@ -0,0 +1,10 @@ +import asyncOpenURL from './asyncOpenURL'; + +/** + * @param shouldSkipCustomSafariLogic When true, we will use `Linking.openURL` even if the browser is Safari. + */ +function openExternalLink(url: string, shouldSkipCustomSafariLogic = false, shouldOpenInSameTab = false) { + asyncOpenURL(Promise.resolve(), url, shouldSkipCustomSafariLogic, shouldOpenInSameTab); +} + +export default openExternalLink; diff --git a/src/pages/DynamicReportDetailsPage.tsx b/src/pages/DynamicReportDetailsPage.tsx index 411a7d5e4abb..e4bd10f0e41c 100644 --- a/src/pages/DynamicReportDetailsPage.tsx +++ b/src/pages/DynamicReportDetailsPage.tsx @@ -126,7 +126,8 @@ import { updateGroupChatAvatar, } from '@userActions/Report'; import {callFunctionIfActionIsAllowed} from '@userActions/Session'; -import {canActionTask, canModifyTask, deleteTask, reopenTask} from '@userActions/Task'; +import {canActionTask, canModifyTask, reopenTask} from '@userActions/Task'; +import {deleteTask} from '@userActions/TaskDeletion'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; diff --git a/src/pages/inbox/report/useReportActionsNewActionLiveTail.ts b/src/pages/inbox/report/useReportActionsNewActionLiveTail.ts index da424fdf5e13..557423d1a41d 100644 --- a/src/pages/inbox/report/useReportActionsNewActionLiveTail.ts +++ b/src/pages/inbox/report/useReportActionsNewActionLiveTail.ts @@ -3,7 +3,8 @@ import useIsInSidePanel from '@hooks/useIsInSidePanel'; import type useReportScrollManager from '@hooks/useReportScrollManager'; import type {OpenReportActionParams} from '@libs/actions/Report'; -import {openReport, pruneReportActionPagesToNewestWindow, subscribeToNewActionEvent} from '@libs/actions/Report'; +import {openReport, pruneReportActionPagesToNewestWindow} from '@libs/actions/Report'; +import {subscribeToNewActionEvent} from '@libs/actions/Report/reportActionSubscribers'; import isReportTopmostSplitNavigator from '@libs/Navigation/helpers/isReportTopmostSplitNavigator'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackNavigationProp} from '@libs/Navigation/PlatformStackNavigation/types'; diff --git a/tests/actions/IOU/CreateDraftTransactionTest.ts b/tests/actions/IOU/CreateDraftTransactionTest.ts index 0d8495378a03..eabfb64531ad 100644 --- a/tests/actions/IOU/CreateDraftTransactionTest.ts +++ b/tests/actions/IOU/CreateDraftTransactionTest.ts @@ -54,14 +54,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); const mockedIsReportTopmostSplitNavigator = jest.mocked(isReportTopmostSplitNavigator); diff --git a/tests/actions/IOU/MoneyRequestBuilderTest.ts b/tests/actions/IOU/MoneyRequestBuilderTest.ts index e01849573ca6..978e47804a65 100644 --- a/tests/actions/IOU/MoneyRequestBuilderTest.ts +++ b/tests/actions/IOU/MoneyRequestBuilderTest.ts @@ -49,14 +49,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); // In production, requestMoney defers its API.write() call until the target screen's diff --git a/tests/actions/IOU/MoneyRequestSettersTest.ts b/tests/actions/IOU/MoneyRequestSettersTest.ts index 4ccf51d37653..2cfa047f53f0 100644 --- a/tests/actions/IOU/MoneyRequestSettersTest.ts +++ b/tests/actions/IOU/MoneyRequestSettersTest.ts @@ -63,14 +63,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); // In production, requestMoney defers its API.write() call until the target screen's diff --git a/tests/actions/IOU/RequestMoneyTest.ts b/tests/actions/IOU/RequestMoneyTest.ts index dd512647df22..96a0aa2cb8ff 100644 --- a/tests/actions/IOU/RequestMoneyTest.ts +++ b/tests/actions/IOU/RequestMoneyTest.ts @@ -4,8 +4,8 @@ import {clearAllRelatedReportActionErrors} from '@libs/actions/ClearReportAction import {createTransaction} from '@libs/actions/IOU/MoneyRequest'; import {requestMoney, trackExpense} from '@libs/actions/IOU/TrackExpense'; import initOnyxDerivedValues from '@libs/actions/OnyxDerived'; -import {notifyNewAction} from '@libs/actions/Report'; import deleteReport from '@libs/actions/Report/DeleteReport'; +import {notifyNewAction} from '@libs/actions/Report/reportActionSubscribers'; import {subscribeToUserEvents} from '@libs/actions/User'; import type {ApiCommand} from '@libs/API/types'; import {WRITE_COMMANDS} from '@libs/API/types'; @@ -88,14 +88,10 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); +jest.mock('@src/libs/actions/Report/reportActionSubscribers', () => ({ + ...jest.requireActual>('@src/libs/actions/Report/reportActionSubscribers'), + notifyNewAction: jest.fn(), +})); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); // In production, requestMoney defers its API.write() call until the target screen's diff --git a/tests/actions/IOU/SearchUpdateTest.ts b/tests/actions/IOU/SearchUpdateTest.ts index dc321e3a1652..bd873d4909a3 100644 --- a/tests/actions/IOU/SearchUpdateTest.ts +++ b/tests/actions/IOU/SearchUpdateTest.ts @@ -52,14 +52,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); // In production, requestMoney defers its API.write() call until the target screen's diff --git a/tests/actions/IOU/SplitReportTotalsTest.ts b/tests/actions/IOU/SplitReportTotalsTest.ts index f2ac4aaa9892..f0b5fb70437f 100644 --- a/tests/actions/IOU/SplitReportTotalsTest.ts +++ b/tests/actions/IOU/SplitReportTotalsTest.ts @@ -54,14 +54,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); jest.mock('@libs/actions/IOU/PendingNewTransactions', () => ({ diff --git a/tests/actions/IOUTest/DeleteMoneyRequestTest.ts b/tests/actions/IOUTest/DeleteMoneyRequestTest.ts index a78721a95422..e1704071a93d 100644 --- a/tests/actions/IOUTest/DeleteMoneyRequestTest.ts +++ b/tests/actions/IOUTest/DeleteMoneyRequestTest.ts @@ -23,7 +23,6 @@ import type Transaction from '@src/types/onyx/Transaction'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import Onyx from 'react-native-onyx'; import type {MockFetch} from '../../utils/TestHelper'; @@ -61,14 +60,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); jest.mock('@libs/deferredLayoutWrite', () => ({ diff --git a/tests/actions/IOUTest/DuplicateTest.ts b/tests/actions/IOUTest/DuplicateTest.ts index 49ec87397046..a3ca0c3e6070 100644 --- a/tests/actions/IOUTest/DuplicateTest.ts +++ b/tests/actions/IOUTest/DuplicateTest.ts @@ -61,14 +61,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); const RORY_EMAIL = 'rory@expensifail.com'; diff --git a/tests/actions/IOUTest/GetUpdateMoneyRequestParamsTest.ts b/tests/actions/IOUTest/GetUpdateMoneyRequestParamsTest.ts index 59d6131e14e1..f8dffab84cae 100644 --- a/tests/actions/IOUTest/GetUpdateMoneyRequestParamsTest.ts +++ b/tests/actions/IOUTest/GetUpdateMoneyRequestParamsTest.ts @@ -34,16 +34,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); - jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); const RORY_EMAIL = 'rory@expensifail.com'; diff --git a/tests/actions/IOUTest/HoldTest.ts b/tests/actions/IOUTest/HoldTest.ts index 269d4e07d518..07da16d74bd2 100644 --- a/tests/actions/IOUTest/HoldTest.ts +++ b/tests/actions/IOUTest/HoldTest.ts @@ -50,15 +50,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); - jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/PolicyUtils', () => ({ diff --git a/tests/actions/IOUTest/PayMoneyRequestTest.ts b/tests/actions/IOUTest/PayMoneyRequestTest.ts index 66a2cf63d91a..7a4ddb84cfe7 100644 --- a/tests/actions/IOUTest/PayMoneyRequestTest.ts +++ b/tests/actions/IOUTest/PayMoneyRequestTest.ts @@ -3,7 +3,7 @@ import {cancelPayment, completePaymentOnboarding, markReportPaymentReceived, pay import {requestMoney} from '@libs/actions/IOU/TrackExpense'; import initOnyxDerivedValues from '@libs/actions/OnyxDerived'; import {createWorkspace, generatePolicyID} from '@libs/actions/Policy/Policy'; -import {notifyNewAction} from '@libs/actions/Report'; +import {notifyNewAction} from '@libs/actions/Report/reportActionSubscribers'; import type * as PolicyUtils from '@libs/PolicyUtils'; import {getOriginalMessage, getReportActionHtml, getReportActionText, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import {buildOptimisticIOUReport, buildOptimisticIOUReportAction} from '@libs/ReportUtils'; @@ -25,7 +25,6 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {OnyxEntry, OnyxInputValue} from 'react-native-onyx'; -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import Onyx from 'react-native-onyx'; import type {MockFetch} from '../../utils/TestHelper'; @@ -68,14 +67,10 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); +jest.mock('@src/libs/actions/Report/reportActionSubscribers', () => ({ + ...jest.requireActual>('@src/libs/actions/Report/reportActionSubscribers'), + notifyNewAction: jest.fn(), +})); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); jest.mock('@libs/deferredLayoutWrite', () => ({ diff --git a/tests/actions/IOUTest/ReceiptTest.ts b/tests/actions/IOUTest/ReceiptTest.ts index b4c4256db3d1..93786419bcf6 100644 --- a/tests/actions/IOUTest/ReceiptTest.ts +++ b/tests/actions/IOUTest/ReceiptTest.ts @@ -48,15 +48,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); - jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/PolicyUtils', () => ({ diff --git a/tests/actions/IOUTest/RejectMoneyRequestTest.ts b/tests/actions/IOUTest/RejectMoneyRequestTest.ts index dd779ed1d925..79052bcd2429 100644 --- a/tests/actions/IOUTest/RejectMoneyRequestTest.ts +++ b/tests/actions/IOUTest/RejectMoneyRequestTest.ts @@ -45,15 +45,10 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); - +jest.mock('@src/libs/actions/Report/reportActionSubscribers', () => ({ + ...jest.requireActual>('@src/libs/actions/Report/reportActionSubscribers'), + notifyNewAction: jest.fn(), +})); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); const RORY_EMAIL = 'rory@expensifail.com'; @@ -754,7 +749,7 @@ describe('actions/IOU/RejectMoneyRequest', () => { }); it('should call notifyNewAction after resolving the violation', async () => { - const {notifyNewAction} = require('@src/libs/actions/Report'); + const {notifyNewAction} = require('@src/libs/actions/Report/reportActionSubscribers'); if (!transaction?.transactionID || !iouReport?.reportID) { throw new Error('Required transaction or report data is missing'); @@ -772,7 +767,7 @@ describe('actions/IOU/RejectMoneyRequest', () => { // eslint-disable-next-line rulesdir/no-multiple-api-calls const writeSpy = jest.spyOn(API, 'write').mockImplementation(jest.fn()); - const {notifyNewAction} = require('@src/libs/actions/Report'); + const {notifyNewAction} = require('@src/libs/actions/Report/reportActionSubscribers'); if (!transaction?.transactionID) { throw new Error('Required transaction data is missing'); diff --git a/tests/actions/IOUTest/ReportWorkflowTest.ts b/tests/actions/IOUTest/ReportWorkflowTest.ts index b404932f1cf0..4b6bca1370af 100644 --- a/tests/actions/IOUTest/ReportWorkflowTest.ts +++ b/tests/actions/IOUTest/ReportWorkflowTest.ts @@ -96,14 +96,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); jest.mock('@libs/deferredLayoutWrite', () => ({ diff --git a/tests/actions/IOUTest/SendInvoiceTest.ts b/tests/actions/IOUTest/SendInvoiceTest.ts index b491b3061143..8df7fa7f4b86 100644 --- a/tests/actions/IOUTest/SendInvoiceTest.ts +++ b/tests/actions/IOUTest/SendInvoiceTest.ts @@ -51,14 +51,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); const RORY_EMAIL = 'rory@expensifail.com'; diff --git a/tests/actions/IOUTest/SendMoneyTest.ts b/tests/actions/IOUTest/SendMoneyTest.ts index c1fc34aea282..678e2671e000 100644 --- a/tests/actions/IOUTest/SendMoneyTest.ts +++ b/tests/actions/IOUTest/SendMoneyTest.ts @@ -1,6 +1,5 @@ import {sendMoneyElsewhere} from '@libs/actions/IOU/SendMoney'; import initOnyxDerivedValues from '@libs/actions/OnyxDerived'; -import type * as ReportActions from '@libs/actions/Report'; import {isMoneyRequestAction} from '@libs/ReportActionsUtils'; import CONST from '@src/CONST'; @@ -36,14 +35,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); - jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); jest.mock('@libs/Sound', () => ({ diff --git a/tests/actions/IOUTest/SplitDistanceMessageTest.ts b/tests/actions/IOUTest/SplitDistanceMessageTest.ts index 44b5bf91fa4a..0eccb564453d 100644 --- a/tests/actions/IOUTest/SplitDistanceMessageTest.ts +++ b/tests/actions/IOUTest/SplitDistanceMessageTest.ts @@ -37,16 +37,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); - jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); const RORY_EMAIL = 'rory@expensifail.com'; diff --git a/tests/actions/IOUTest/SplitSelfDMTest.ts b/tests/actions/IOUTest/SplitSelfDMTest.ts index 318d7a249774..04ae3491b91e 100644 --- a/tests/actions/IOUTest/SplitSelfDMTest.ts +++ b/tests/actions/IOUTest/SplitSelfDMTest.ts @@ -44,16 +44,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); - jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@src/libs/SearchQueryUtils', () => { diff --git a/tests/actions/IOUTest/SplitTest.ts b/tests/actions/IOUTest/SplitTest.ts index 53122123811b..67b48e30c361 100644 --- a/tests/actions/IOUTest/SplitTest.ts +++ b/tests/actions/IOUTest/SplitTest.ts @@ -7,7 +7,8 @@ import {getReportPreviewReportAction} from '@libs/actions/IOU/MoneyRequestBuilde import {requestMoney} from '@libs/actions/IOU/TrackExpense'; import initOnyxDerivedValues from '@libs/actions/OnyxDerived'; import {createWorkspace, generatePolicyID, setWorkspaceApprovalMode} from '@libs/actions/Policy/Policy'; -import {addComment, notifyNewAction} from '@libs/actions/Report'; +import {addComment} from '@libs/actions/Report'; +import {notifyNewAction} from '@libs/actions/Report/reportActionSubscribers'; import initSplitExpense from '@libs/actions/SplitExpenses'; import type * as API from '@libs/API'; import {WRITE_COMMANDS} from '@libs/API/types'; @@ -113,11 +114,15 @@ jest.mock('@src/libs/actions/Report', () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return { ...originalModule, - notifyNewAction: jest.fn(), setDeleteTransactionNavigateBackUrl: jest.fn(), }; }); +jest.mock('@src/libs/actions/Report/reportActionSubscribers', () => ({ + ...jest.requireActual>('@src/libs/actions/Report/reportActionSubscribers'), + notifyNewAction: jest.fn(), +})); + jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); jest.mock('@libs/deferredLayoutWrite', () => ({ diff --git a/tests/actions/IOUTest/TrackExpenseTest.ts b/tests/actions/IOUTest/TrackExpenseTest.ts index b654b5027bd6..cfe5cb40ec5f 100644 --- a/tests/actions/IOUTest/TrackExpenseTest.ts +++ b/tests/actions/IOUTest/TrackExpenseTest.ts @@ -76,15 +76,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); - jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); jest.mock('@hooks/useCardFeedsForDisplay', () => jest.fn(() => ({defaultCardFeed: null, cardFeedsByPolicy: {}}))); diff --git a/tests/actions/IOUTest/UpdateMoneyRequestTest.ts b/tests/actions/IOUTest/UpdateMoneyRequestTest.ts index 3207cbfc97f6..af2f90ea133e 100644 --- a/tests/actions/IOUTest/UpdateMoneyRequestTest.ts +++ b/tests/actions/IOUTest/UpdateMoneyRequestTest.ts @@ -32,7 +32,6 @@ import type Transaction from '@src/types/onyx/Transaction'; import type {NullishDeep, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import {format} from 'date-fns'; import Onyx from 'react-native-onyx'; @@ -72,14 +71,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); jest.mock('@libs/deferredLayoutWrite', () => ({ diff --git a/tests/actions/OdometerTransactionUtilsTest.ts b/tests/actions/OdometerTransactionUtilsTest.ts index 2876d0bb0468..d16c583e3802 100644 --- a/tests/actions/OdometerTransactionUtilsTest.ts +++ b/tests/actions/OdometerTransactionUtilsTest.ts @@ -57,14 +57,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); // In production, requestMoney defers its API.write() call until the target screen's diff --git a/tests/actions/SessionTest.ts b/tests/actions/SessionTest.ts index 06215afb23c6..bd0359117cdb 100644 --- a/tests/actions/SessionTest.ts +++ b/tests/actions/SessionTest.ts @@ -3,13 +3,13 @@ import {beforeEach, jest, test} from '@jest/globals'; import {openApp, reconnectApp} from '@libs/actions/App'; -import {buildOldDotURL, openExternalLink} from '@libs/actions/Link'; import OnyxUpdateManager from '@libs/actions/OnyxUpdateManager'; import {getAll as getAllPersistedRequests} from '@libs/actions/PersistedRequests'; import {initReconnect} from '@libs/actions/Reconnect'; import * as SignInRedirect from '@libs/actions/SignInRedirect'; import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import asyncOpenURL from '@libs/asyncOpenURL'; +import buildOldDotURL from '@libs/buildOldDotURL'; import getPlatform from '@libs/getPlatform'; import HttpUtils from '@libs/HttpUtils'; import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; @@ -17,6 +17,7 @@ import * as NetworkStore from '@libs/Network/NetworkStore'; import {setHasRadio} from '@libs/NetworkState'; import PushNotification from '@libs/Notification/PushNotification'; import {isRecord} from '@libs/ObjectUtils'; +import openExternalLink from '@libs/openExternalLink'; import reauthenticate from '@libs/Reauthentication'; import CONFIG from '@src/CONFIG'; @@ -55,12 +56,15 @@ jest.mock('expo-web-browser', () => ({ openAuthSessionAsync: jest.fn(() => Promise.resolve({type: 'success'})), })); -jest.mock('@libs/actions/Link', () => { - return { - buildOldDotURL: jest.fn(() => Promise.resolve('mockOldDotURL')), - openExternalLink: jest.fn(), - }; -}); +jest.mock('@libs/buildOldDotURL', () => ({ + __esModule: true, + default: jest.fn(() => Promise.resolve('mockOldDotURL')), +})); + +jest.mock('@libs/openExternalLink', () => ({ + __esModule: true, + default: jest.fn(), +})); jest.mock('@libs/getPlatform', () => jest.fn()); diff --git a/tests/actions/TaskTest.ts b/tests/actions/TaskTest.ts index fe4873756c26..7b380991dff5 100644 --- a/tests/actions/TaskTest.ts +++ b/tests/actions/TaskTest.ts @@ -14,14 +14,13 @@ import { completeTestDriveTask, createTaskAndNavigate, createTaskFromMarkdown, - deleteTask, editTask, editTaskAssignee, getAssignee, getFinishOnboardingTaskOnyxData, - getNavigationUrlOnTaskDelete, getShareDestination, } from '@libs/actions/Task'; +import {deleteTask, getNavigationUrlOnTaskDelete} from '@libs/actions/TaskDeletion'; import * as API from '@libs/API'; import {WRITE_COMMANDS} from '@libs/API/types'; import DateUtils from '@libs/DateUtils'; diff --git a/tests/actions/TransactionTest.ts b/tests/actions/TransactionTest.ts index 3ad67c561b57..58c8c6d1118b 100644 --- a/tests/actions/TransactionTest.ts +++ b/tests/actions/TransactionTest.ts @@ -93,14 +93,6 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ jest.mock('@react-navigation/native'); -jest.mock('@src/libs/actions/Report', () => { - const originalModule = jest.requireActual('@src/libs/actions/Report'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...originalModule, - notifyNewAction: jest.fn(), - }; -}); jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); // In production, requestMoney defers its API.write() call until the target screen's diff --git a/tests/unit/hooks/useDeleteTransactions.test.ts b/tests/unit/hooks/useDeleteTransactions.test.ts index 06bfc62a4718..4544629335ec 100644 --- a/tests/unit/hooks/useDeleteTransactions.test.ts +++ b/tests/unit/hooks/useDeleteTransactions.test.ts @@ -36,7 +36,6 @@ jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest. jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn()); jest.mock('@src/libs/actions/Report', () => ({ ...jest.requireActual>('@src/libs/actions/Report'), - notifyNewAction: jest.fn(), setDeleteTransactionNavigateBackUrl: jest.fn(), })); diff --git a/tests/unit/libs/buildOldDotURLTest.ts b/tests/unit/libs/buildOldDotURLTest.ts new file mode 100644 index 000000000000..63f10cf6b180 --- /dev/null +++ b/tests/unit/libs/buildOldDotURLTest.ts @@ -0,0 +1,41 @@ +import buildOldDotURL from '@libs/buildOldDotURL'; +import {getCurrentUserEmail} from '@libs/CurrentUserStore'; + +jest.mock('@libs/CurrentUserStore', () => ({ + getCurrentUserEmail: jest.fn(), +})); + +jest.mock('@libs/Environment/Environment', () => ({ + __esModule: true, + getOldDotEnvironmentURL: jest.fn(() => Promise.resolve('https://www.example.com')), +})); + +const mockedGetCurrentUserEmail = jest.mocked(getCurrentUserEmail); + +describe('buildOldDotURL', () => { + beforeEach(() => { + mockedGetCurrentUserEmail.mockReturnValue('test@example.com'); + }); + + test('appends the email param to a plain path', async () => { + await expect(buildOldDotURL('r/12345')).resolves.toBe('https://www.example.com/r/12345?email=test%40example.com'); + }); + + test('joins params with & when the url already has a query', async () => { + await expect(buildOldDotURL('r/12345?tab=expenses')).resolves.toBe('https://www.example.com/r/12345?tab=expenses&email=test%40example.com'); + }); + + test('keeps hash params at the end of the built url', async () => { + await expect(buildOldDotURL('r/12345#reports')).resolves.toBe('https://www.example.com/r/12345?email=test%40example.com#reports'); + }); + + test('puts the short lived auth token before the email param', async () => { + await expect(buildOldDotURL('r/12345', 'secret-token')).resolves.toBe('https://www.example.com/r/12345?authToken=secret-token&email=test%40example.com'); + }); + + test('uses an empty email when there is no signed-in user', async () => { + mockedGetCurrentUserEmail.mockReturnValue(null); + + await expect(buildOldDotURL('r/12345')).resolves.toBe('https://www.example.com/r/12345?email='); + }); +}); diff --git a/tests/unit/libs/openExternalLinkTest.ts b/tests/unit/libs/openExternalLinkTest.ts new file mode 100644 index 000000000000..14f9c3622019 --- /dev/null +++ b/tests/unit/libs/openExternalLinkTest.ts @@ -0,0 +1,21 @@ +import asyncOpenURL from '@libs/asyncOpenURL'; +import openExternalLink from '@libs/openExternalLink'; + +jest.mock('@libs/asyncOpenURL'); + +const mockedAsyncOpenURL = jest.mocked(asyncOpenURL); + +describe('openExternalLink', () => { + test('delegates to asyncOpenURL with the link and its options', () => { + openExternalLink('https://example.com', true, true); + + expect(mockedAsyncOpenURL).toHaveBeenCalledTimes(1); + expect(mockedAsyncOpenURL).toHaveBeenLastCalledWith(expect.any(Promise), 'https://example.com', true, true); + }); + + test('defaults both options to false', () => { + openExternalLink('https://example.com'); + + expect(mockedAsyncOpenURL).toHaveBeenLastCalledWith(expect.any(Promise), 'https://example.com', false, false); + }); +}); diff --git a/tests/unit/libs/reportActionSubscribersTest.ts b/tests/unit/libs/reportActionSubscribersTest.ts new file mode 100644 index 000000000000..262557d19a03 --- /dev/null +++ b/tests/unit/libs/reportActionSubscribersTest.ts @@ -0,0 +1,59 @@ +import {notifyNewAction, subscribeToNewActionEvent} from '@libs/actions/Report/reportActionSubscribers'; + +describe('subscribeToNewActionEvent', () => { + test('notifies the subscriber of the matching report', () => { + const callback = jest.fn(); + const unsubscribe = subscribeToNewActionEvent('1', callback); + + notifyNewAction('1', undefined, true); + + expect(callback).toHaveBeenCalledWith(true, undefined); + unsubscribe(); + }); + + test('does not notify subscribers of other reports', () => { + const callback = jest.fn(); + const unsubscribe = subscribeToNewActionEvent('1', callback); + + notifyNewAction('2', undefined, true); + + expect(callback).not.toHaveBeenCalled(); + unsubscribe(); + }); + + test('notifies every subscriber whose report id is in the list', () => { + const firstCallback = jest.fn(); + const secondCallback = jest.fn(); + const otherCallback = jest.fn(); + const unsubscribes = [subscribeToNewActionEvent('1', firstCallback), subscribeToNewActionEvent('2', secondCallback), subscribeToNewActionEvent('3', otherCallback)]; + + notifyNewAction(['1', '2'], undefined, false); + + expect(firstCallback).toHaveBeenCalled(); + expect(secondCallback).toHaveBeenCalled(); + expect(otherCallback).not.toHaveBeenCalled(); + for (const unsubscribe of unsubscribes) { + unsubscribe(); + } + }); + + test('ignores an undefined report id', () => { + const callback = jest.fn(); + const unsubscribe = subscribeToNewActionEvent('1', callback); + + notifyNewAction(undefined, undefined, true); + + expect(callback).not.toHaveBeenCalled(); + unsubscribe(); + }); + + test('stops notifying after the unsubscribe function runs', () => { + const callback = jest.fn(); + const unsubscribe = subscribeToNewActionEvent('1', callback); + + unsubscribe(); + notifyNewAction('1', undefined, true); + + expect(callback).not.toHaveBeenCalled(); + }); +}); diff --git a/tests/unit/useReportActionsNewActionLiveTailTest.ts b/tests/unit/useReportActionsNewActionLiveTailTest.ts index 87e435b002a5..d5917205de93 100644 --- a/tests/unit/useReportActionsNewActionLiveTailTest.ts +++ b/tests/unit/useReportActionsNewActionLiveTailTest.ts @@ -70,6 +70,9 @@ jest.mock('@libs/actions/Report', () => ({ mockOpenReport(...args); }, pruneReportActionPagesToNewestWindow: jest.fn(), +})); + +jest.mock('@libs/actions/Report/reportActionSubscribers', () => ({ subscribeToNewActionEvent: (_reportID: string, callback: (isFromCurrentUser: boolean, action?: ReportAction) => void) => { newActionHandler = callback; return jest.fn();