Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/libs/actions/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ type CreateTaskAndNavigateParams = {
taskCreatorAndAssigneeDetails: OnyxEntry<OnyxTypes.PersonalDetailsList>;
};

type DeleteTaskOptions = {
ancestors?: ReportUtils.Ancestor[];
shouldNavigateBack?: boolean;
};

/**
* Clears out the task info from the store
*/
Expand Down Expand Up @@ -1218,7 +1223,7 @@ function deleteTask(
conciergeReportID: string | undefined,
delegateEmail: string | undefined,
reportActions: OnyxEntry<OnyxTypes.ReportActions>,
ancestors: ReportUtils.Ancestor[] = [],
{ancestors = [], shouldNavigateBack = true}: DeleteTaskOptions = {},
) {
if (!report) {
return;
Expand Down Expand Up @@ -1343,7 +1348,7 @@ function deleteTask(
API.write(WRITE_COMMANDS.CANCEL_TASK, parameters, {optimisticData, successData, failureData});
notifyNewAction(report.reportID, undefined, true);

const urlToNavigateBack = getNavigationUrlOnTaskDelete(report, conciergeReportID, reportActions);
const urlToNavigateBack = shouldNavigateBack ? getNavigationUrlOnTaskDelete(report, conciergeReportID, reportActions) : undefined;
if (urlToNavigateBack) {
Navigation.goBack();
return urlToNavigateBack;
Expand Down
14 changes: 13 additions & 1 deletion src/pages/DynamicReportDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ function DynamicReportDetailsPage({policy, report, route, reportMetadata, report
'Hashtag',
]);
const navigateBackFromReportDetailsPath = useDynamicBackPath(DYNAMIC_ROUTES.REPORT_DETAILS.path);
const taskDeleteBackTo = Navigation.getTopmostSearchReportRouteParams()?.backTo;

const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END);
const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED);
Expand Down Expand Up @@ -995,7 +996,10 @@ function DynamicReportDetailsPage({policy, report, route, reportMetadata, report
conciergeReportID,
delegateEmail,
reportActionsForOriginalReportID,
ancestors,
{
ancestors,
shouldNavigateBack: !taskDeleteBackTo,
},
);
return;
}
Expand Down Expand Up @@ -1035,6 +1039,7 @@ function DynamicReportDetailsPage({policy, report, route, reportMetadata, report
}
}, [
caseID,
taskDeleteBackTo,
requestParentReportAction,
iouTransaction,
iouOriginalTransaction,
Expand Down Expand Up @@ -1067,6 +1072,11 @@ function DynamicReportDetailsPage({policy, report, route, reportMetadata, report

// Where to navigate back to after deleting the transaction and its report.
const navigateToTargetUrl = useCallback(() => {
if (caseID === CASES.DEFAULT && taskDeleteBackTo) {
Navigation.goBack(taskDeleteBackTo);
return;
}

let urlToNavigateBack: string | undefined;
// Only proceed with navigation logic if transaction was actually deleted
if (!isEmptyObject(requestParentReportAction)) {
Expand Down Expand Up @@ -1133,6 +1143,8 @@ function DynamicReportDetailsPage({policy, report, route, reportMetadata, report
navigateBackOnDeleteTransaction(urlToNavigateBack as Route);
}
}, [
caseID,
taskDeleteBackTo,
requestParentReportAction,
route.params.reportID,
moneyRequestReport,
Expand Down
33 changes: 33 additions & 0 deletions tests/actions/TaskTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1551,6 +1551,39 @@ describe('actions/Task', () => {
expect(Navigation.goBack).toHaveBeenCalled();
});

it('should skip fallback navigation when task delete navigation was already handled', async () => {
const taskReportID = 'task_report_delete_skip_navigation';
const parentReportID = 'parent_report_delete_skip_navigation';

const taskReport = {
reportID: taskReportID,
type: CONST.REPORT.TYPE.TASK,
reportName: 'Test Task To Delete Without Fallback Navigation',
parentReportID,
stateNum: CONST.REPORT.STATE_NUM.OPEN,
statusNum: CONST.REPORT.STATUS_NUM.OPEN,
ownerAccountID: mockCurrentUserAccountID,
};

const parentReport = {
reportID: parentReportID,
type: CONST.REPORT.TYPE.CHAT,
};

await act(async () => {
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, taskReport);
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`, parentReport);
});
await waitForBatchedUpdatesWithAct();

const result = deleteTask(taskReport, parentReport, false, mockCurrentUserAccountID, false, undefined, 'concierge_123', undefined, undefined, {shouldNavigateBack: false});

expect(result).toBeUndefined();
expect(Navigation.goBack).not.toHaveBeenCalled();
// eslint-disable-next-line rulesdir/no-multiple-api-calls
expect(API.write).toHaveBeenCalledWith('CancelTask', expect.objectContaining({taskReportID}), expect.any(Object));
});

it('should return conciergeReportID-based URL when no parentReportID and no recent report', async () => {
const taskReportID = 'task_report_delete_3';
const conciergeReportID = 'concierge_456';
Expand Down
112 changes: 109 additions & 3 deletions tests/unit/components/reportDetails/DynamicReportDetailsPageTest.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,59 @@
import {act, render} from '@testing-library/react-native';
import {act, fireEvent, render, screen} from '@testing-library/react-native';

import {CurrentUserPersonalDetailsContext} from '@components/CurrentUserPersonalDetailsProvider';
import {LocaleContextProvider} from '@components/LocaleContextProvider';
import OnyxListItemProvider from '@components/OnyxListItemProvider';

import type Navigation from '@libs/Navigation/Navigation';
import AppNavigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import TransitionTracker from '@libs/Navigation/TransitionTracker';
import type {ReportDetailsNavigatorParamList} from '@libs/Navigation/types';
import Parser from '@libs/Parser';

import DynamicReportDetailsPage from '@pages/DynamicReportDetailsPage';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {Report, ReportAction} from '@src/types/onyx';

import React from 'react';
import Onyx from 'react-native-onyx';

import type * as MockUseConfirmModalUtil from '../../../utils/mockUseConfirmModal';

import createRandomReportAction from '../../../utils/collections/reportActions';
import {createRandomReport} from '../../../utils/collections/reports';
import {mockShowConfirmModal, resetMockConfirmModal, resolveShowConfirmModal} from '../../../utils/mockUseConfirmModal';
import waitForBatchedUpdatesWithAct from '../../../utils/waitForBatchedUpdatesWithAct';

jest.mock('@src/components/ConfirmedRoute.tsx');
jest.mock('@hooks/useConfirmModal', () => {
const {default: mockUseConfirmModal} = jest.requireActual<typeof MockUseConfirmModalUtil>('../../../utils/mockUseConfirmModal');
return mockUseConfirmModal;
});
jest.mock('@components/Modal/Global/ModalContext', () => {
const {createMockModalContextModule} = jest.requireActual<typeof MockUseConfirmModalUtil>('../../../utils/mockUseConfirmModal');
return createMockModalContextModule();
});
jest.mock('@libs/Navigation/helpers/isReportTopmostSplitNavigator', () => jest.fn(() => false));

jest.mock('@react-navigation/native', () => {
const actualNav = jest.requireActual<typeof Navigation>('@react-navigation/native');
return {
...actualNav,
useFocusEffect: jest.fn(),
useIsFocused: jest.fn(),
useRoute: jest.fn(),
usePreventRemove: jest.fn(),
};
});

const mockHtmlToText = jest.spyOn(Parser, 'htmlToText');
const navigationMock = {} as PlatformStackScreenProps<ReportDetailsNavigatorParamList, typeof SCREENS.REPORT_DETAILS.DYNAMIC_ROOT>['navigation'];
const getRouteMock = (reportID: string) => ({params: {reportID}}) as PlatformStackScreenProps<ReportDetailsNavigatorParamList, typeof SCREENS.REPORT_DETAILS.DYNAMIC_ROOT>['route'];

describe('DynamicReportDetailsPage', () => {
beforeAll(() => {
Expand All @@ -46,9 +65,12 @@ describe('DynamicReportDetailsPage', () => {

beforeEach(() => {
mockHtmlToText.mockClear();
resetMockConfirmModal();
jest.spyOn(TransitionTracker, 'runAfterTransitions').mockReturnValue({cancel: jest.fn()});
});

afterEach(async () => {
jest.restoreAllMocks();
await act(async () => {
await Onyx.clear();
});
Expand Down Expand Up @@ -83,12 +105,12 @@ describe('DynamicReportDetailsPage', () => {
<DynamicReportDetailsPage
betas={[]}
isLoadingReportData={false}
navigation={{} as PlatformStackScreenProps<ReportDetailsNavigatorParamList, typeof SCREENS.REPORT_DETAILS.DYNAMIC_ROOT>['navigation']}
navigation={navigationMock}
policy={undefined}
report={report}
reportMetadata={undefined}
reportLoadingState={undefined}
route={{params: {reportID}} as PlatformStackScreenProps<ReportDetailsNavigatorParamList, typeof SCREENS.REPORT_DETAILS.DYNAMIC_ROOT>['route']}
route={getRouteMock(reportID)}
/>
</LocaleContextProvider>
</OnyxListItemProvider>,
Expand All @@ -98,4 +120,88 @@ describe('DynamicReportDetailsPage', () => {

expect(mockHtmlToText).not.toHaveBeenCalled();
});

it('should navigate to the Search backTo route when deleting a task from Search', async () => {
const currentUserAccountID = 1;
const reportID = '11';
const parentReportID = '22';
const parentActionID = '101';
const searchBackTo = ROUTES.SEARCH_REPORT.getRoute({
reportID: parentReportID,
reportActionID: parentActionID,
backTo: ROUTES.SEARCH_ROOT.getRoute({query: 'type:chat'}),
});

const parentReportAction = {
...createRandomReportAction(Number(parentActionID)),
actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT,
actorAccountID: currentUserAccountID,
childManagerAccountID: currentUserAccountID,
childReportID: reportID,
message: [
{
type: CONST.REPORT.MESSAGE.TYPE.COMMENT,
html: '',
text: '',
isDeletedParentAction: false,
},
],
} as ReportAction;

const taskReport: Report = {
...createRandomReport(Number(reportID), undefined),
type: CONST.REPORT.TYPE.TASK,
stateNum: CONST.REPORT.STATE_NUM.OPEN,
statusNum: CONST.REPORT.STATUS_NUM.OPEN,
ownerAccountID: currentUserAccountID,
parentReportID,
parentReportActionID: parentActionID,
};

await act(async () => {
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, taskReport);
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`, {
...createRandomReport(Number(parentReportID), undefined),
participants: {[currentUserAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}},
});
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, {
[parentActionID]: parentReportAction,
});
});

jest.spyOn(AppNavigation, 'getTopmostSearchReportRouteParams').mockReturnValue({reportID, backTo: searchBackTo});
const goBackSpy = jest.spyOn(AppNavigation, 'goBack');

render(
<OnyxListItemProvider>
<CurrentUserPersonalDetailsContext.Provider value={{accountID: currentUserAccountID}}>
<LocaleContextProvider>
<DynamicReportDetailsPage
betas={[]}
isLoadingReportData={false}
navigation={navigationMock}
policy={undefined}
report={taskReport}
reportMetadata={undefined}
reportLoadingState={undefined}
route={getRouteMock(reportID)}
/>
</LocaleContextProvider>
</CurrentUserPersonalDetailsContext.Provider>
</OnyxListItemProvider>,
);

await waitForBatchedUpdatesWithAct();

fireEvent.press(screen.getByLabelText('Delete'), {type: 'press'});

expect(mockShowConfirmModal).toHaveBeenCalled();

await act(async () => {
resolveShowConfirmModal();
});
await waitForBatchedUpdatesWithAct();

expect(goBackSpy).toHaveBeenCalledWith(searchBackTo);
});
});
Loading