diff --git a/src/pages/workspace/copyPolicySettings/CopyPolicySettingsProgressModal.tsx b/src/pages/workspace/copyPolicySettings/CopyPolicySettingsProgressModal.tsx index e8395139a286..0132a816883f 100644 --- a/src/pages/workspace/copyPolicySettings/CopyPolicySettingsProgressModal.tsx +++ b/src/pages/workspace/copyPolicySettings/CopyPolicySettingsProgressModal.tsx @@ -101,6 +101,9 @@ function useCopyPolicySettingsProgressModal() { confirmText: translate('workspace.copyPolicySettings.progress.letMeKnowPrompt'), cancelText: '', shouldShowCancelButton: false, + // The X is the only way to close this step. Without it onCancel is reachable only by backdrop press, + // Escape, or browser back, so a user who walks away from the copy never asks to be told if it fails. + shouldShowDismissIcon: true, isTitleLoading: true, onConfirm: () => { requestCopyPolicySettingsNotification(); @@ -144,7 +147,8 @@ function useCopyPolicySettingsProgressModal() { } function CopyPolicySettingsProgressModal() { - const {isVisible, title, prompt, confirmText, cancelText, shouldShowCancelButton, isTitleLoading, danger, onConfirm, onCancel} = useCopyPolicySettingsProgressModal(); + const {isVisible, title, prompt, confirmText, cancelText, shouldShowCancelButton, shouldShowDismissIcon, isTitleLoading, danger, onConfirm, onCancel} = + useCopyPolicySettingsProgressModal(); return ( // eslint-disable-next-line @typescript-eslint/no-deprecated -- The global useConfirmModal()/showConfirmModal() API is one-shot (its promise resolves on the first confirm/cancel and the modal unmounts). This progress modal must stay open across multiple Onyx state transitions ('loading' → notify-requested → 'complete') and update its content in place, which the global system does not support. @@ -157,6 +161,7 @@ function CopyPolicySettingsProgressModal() { confirmText={confirmText} cancelText={cancelText} shouldShowCancelButton={shouldShowCancelButton} + shouldShowDismissIcon={shouldShowDismissIcon} isTitleLoading={isTitleLoading} shouldHandleNavigationBack buttonVariant={danger ? CONST.BUTTON_VARIANT.DANGER : CONST.BUTTON_VARIANT.SUCCESS} diff --git a/tests/unit/CopyPolicySettingsProgressModalTest.tsx b/tests/unit/CopyPolicySettingsProgressModalTest.tsx index 4029a5335da9..6bd351d6130c 100644 --- a/tests/unit/CopyPolicySettingsProgressModalTest.tsx +++ b/tests/unit/CopyPolicySettingsProgressModalTest.tsx @@ -22,6 +22,7 @@ type MockConfirmModalProps = { confirmText?: string; cancelText?: string; shouldShowCancelButton?: boolean; + shouldShowDismissIcon?: boolean; shouldHandleNavigationBack?: boolean; buttonVariant?: string; }; @@ -171,6 +172,12 @@ describe('CopyPolicySettingsProgressModal', () => { expect(lastModalProps?.shouldShowCancelButton).toBe(false); }); + it('should show the dismiss icon so the modal can be closed while the copy runs', async () => { + await renderModal(); + + expect(lastModalProps?.shouldShowDismissIcon).toBe(true); + }); + it('should call requestCopyPolicySettingsNotification on confirm', async () => { await renderModal(); @@ -179,6 +186,20 @@ describe('CopyPolicySettingsProgressModal', () => { }); expect(mockRequestNotification).toHaveBeenCalledTimes(1); + // "Let me know when it's done" asks to be notified either way, not only on failure + expect(mockRequestNotification).not.toHaveBeenCalledWith(true); + }); + + it('should request a failure-only Concierge notification on cancel (dismiss)', async () => { + await renderModal(); + + act(() => { + lastModalProps?.onCancel?.(); + }); + + expect(mockRequestNotification).toHaveBeenCalledTimes(1); + expect(mockRequestNotification).toHaveBeenCalledWith(true); + expect(mockClearCopyPolicySettings).toHaveBeenCalledTimes(1); }); }); @@ -374,6 +395,8 @@ describe('CopyPolicySettingsProgressModal', () => { expect(mockClearCopyPolicySettings).toHaveBeenCalledTimes(1); expect(mockNavigate).not.toHaveBeenCalled(); + // The failure is already on screen, so there is nothing for Concierge to report + expect(mockRequestNotification).not.toHaveBeenCalled(); }); it('should not navigate when sourcePolicyID is not available', async () => {