diff --git a/src/components/ExportDownloadStatusModal.tsx b/src/components/ExportDownloadStatusModal.tsx
new file mode 100644
index 000000000000..53ca1ce64e16
--- /dev/null
+++ b/src/components/ExportDownloadStatusModal.tsx
@@ -0,0 +1,186 @@
+import {hasSeenTourSelector} from '@selectors/Onboarding';
+import React, {useCallback, useEffect} from 'react';
+import {View} from 'react-native';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
+import useLocalize from '@hooks/useLocalize';
+import useOnyx from '@hooks/useOnyx';
+import usePreviousDefined from '@hooks/usePreviousDefined';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useThemeStyles from '@hooks/useThemeStyles';
+import fileDownload from '@libs/fileDownload';
+import {clearExportDownload, sendExportFileFromConcierge} from '@userActions/Export';
+import {navigateToConciergeChat} from '@userActions/Report';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ActivityIndicator from './ActivityIndicator';
+import Button from './Button';
+import Modal from './Modal';
+import Text from './Text';
+
+type ExportDownloadStatusModalProps = {
+ /** The export ID to subscribe to */
+ exportID: string;
+
+ /** Whether the modal is visible */
+ isVisible: boolean;
+
+ /** Callback when the modal is closed */
+ onClose: () => void;
+
+ /** Body text for the failed state — PDF and CSV use different copy */
+ failedBody?: string;
+};
+
+function ExportDownloadStatusModal({exportID, isVisible, onClose, failedBody}: ExportDownloadStatusModalProps) {
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+ // isSmallScreenWidth is needed here because the modal type depends on actual screen width, not layout mode
+ // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
+ const {isSmallScreenWidth} = useResponsiveLayout();
+ const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();
+
+ const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
+ const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
+ const [betas] = useOnyx(ONYXKEYS.BETAS);
+ const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
+
+ const [exportDownload] = useOnyx(`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${exportID}`);
+ const displayedExport = usePreviousDefined(exportDownload);
+
+ const state = displayedExport?.state;
+ const shouldSendFromConcierge = displayedExport?.shouldSendFromConcierge;
+ const downloadURL = displayedExport?.downloadURL;
+ const isPreparing = state === CONST.EXPORT_DOWNLOAD.STATE.PREPARING && !shouldSendFromConcierge;
+ const isConcierge = !!shouldSendFromConcierge;
+ const isReady = state === CONST.EXPORT_DOWNLOAD.STATE.READY;
+ const isFailed = state === CONST.EXPORT_DOWNLOAD.STATE.FAILED;
+
+ useEffect(() => {
+ if (!isReady || !downloadURL || shouldSendFromConcierge) {
+ return;
+ }
+ fileDownload(translate, downloadURL);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isReady]);
+
+ const handleSendFromConcierge = useCallback(() => {
+ sendExportFileFromConcierge(exportID, displayedExport ?? undefined);
+ }, [exportID, displayedExport]);
+
+ const handleGoToConcierge = useCallback(() => {
+ onClose();
+ navigateToConciergeChat(conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas);
+ }, [onClose, conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas]);
+
+ const handleDismiss = useCallback(() => {
+ onClose();
+ }, [onClose]);
+
+ const handleDownloadFile = useCallback(() => {
+ if (!downloadURL) {
+ return;
+ }
+ fileDownload(translate, downloadURL);
+ }, [downloadURL, translate]);
+
+ const handleClose = useCallback(() => {
+ clearExportDownload(exportID, displayedExport ?? undefined);
+ onClose();
+ }, [exportID, displayedExport, onClose]);
+
+ const isNonDismissible = isPreparing;
+
+ const renderContent = () => {
+ if (isPreparing) {
+ return (
+ <>
+
+ {translate('exportDownload.preparingTitle')}
+ {translate('exportDownload.preparingBody')}
+
+ >
+ );
+ }
+
+ if (isConcierge) {
+ return (
+ <>
+ {translate('exportDownload.conciergeTitle')}
+ {translate('exportDownload.conciergeBody')}
+
+
+ >
+ );
+ }
+
+ if (isReady) {
+ return (
+ <>
+ {translate('exportDownload.readyTitle')}
+ {translate('exportDownload.readyBody')}
+
+
+ >
+ );
+ }
+
+ if (isFailed) {
+ return (
+ <>
+ {translate('exportDownload.failedTitle')}
+ {!!failedBody && {failedBody}}
+
+ >
+ );
+ }
+
+ return null;
+ };
+
+ return (
+ {} : onClose}
+ onBackdropPress={isNonDismissible ? () => {} : undefined}
+ type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.CONFIRM}
+ innerContainerStyle={styles.pv0}
+ >
+ {renderContent()}
+
+ );
+}
+
+ExportDownloadStatusModal.displayName = 'ExportDownloadStatusModal';
+
+export default ExportDownloadStatusModal;
diff --git a/src/languages/de.ts b/src/languages/de.ts
index be489bbd549e..0183e02956b4 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -9282,6 +9282,20 @@ Hier ist ein *Testbeleg*, um dir zu zeigen, wie es funktioniert:`,
exportInProgress: 'Export wird ausgeführt',
conciergeWillSend: 'Concierge wird dir die Datei in Kürze senden.',
},
+ exportDownload: {
+ preparingTitle: 'Preparing download...',
+ preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
+ sendFromConcierge: "Send me the file when it's ready",
+ conciergeTitle: 'You bet!',
+ conciergeBody: 'Concierge will send you a message when the file is ready.',
+ goToConcierge: 'Go to Concierge',
+ dismiss: 'Dismiss',
+ readyTitle: 'Your file is ready!',
+ readyBody: "If it didn't automatically download, use the button below.",
+ downloadFile: 'Download file',
+ failedTitle: 'Export failed',
+ close: 'Close',
+ },
domain: {
notVerified: 'Nicht verifiziert',
retry: 'Wiederholen',
diff --git a/src/languages/en.ts b/src/languages/en.ts
index f1a4e2669a2e..d6a497619097 100644
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -9285,6 +9285,20 @@ const translations = {
exportInProgress: 'Export in progress',
conciergeWillSend: 'Concierge will send you the file shortly.',
},
+ exportDownload: {
+ preparingTitle: 'Preparing download...',
+ preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
+ sendFromConcierge: "Send me the file when it's ready",
+ conciergeTitle: 'You bet!',
+ conciergeBody: 'Concierge will send you a message when the file is ready.',
+ goToConcierge: 'Go to Concierge',
+ dismiss: 'Dismiss',
+ readyTitle: 'Your file is ready!',
+ readyBody: "If it didn't automatically download, use the button below.",
+ downloadFile: 'Download file',
+ failedTitle: 'Export failed',
+ close: 'Close',
+ },
domain: {
notVerified: 'Not verified',
retry: 'Retry',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index b863321ecfd5..30303b0e064a 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -9435,6 +9435,20 @@ ${amount} para ${merchant} - ${date}`,
exportInProgress: 'Exportación en curso',
conciergeWillSend: 'Concierge te enviará el archivo en breve.',
},
+ exportDownload: {
+ preparingTitle: 'Preparando descarga...',
+ preparingBody: 'Puedes esperar a que termine la descarga o Concierge puede enviártelo por chat.',
+ sendFromConcierge: 'Envíame el archivo cuando esté listo',
+ conciergeTitle: '¡Por supuesto!',
+ conciergeBody: 'Concierge te enviará un mensaje cuando el archivo esté listo.',
+ goToConcierge: 'Ir a Concierge',
+ dismiss: 'Descartar',
+ readyTitle: '¡Tu archivo está listo!',
+ readyBody: 'Si no se descargó automáticamente, usa el botón de abajo.',
+ downloadFile: 'Descargar archivo',
+ failedTitle: 'Exportación fallida',
+ close: 'Cerrar',
+ },
openAppFailureModal: {
title: 'Algo salió mal...',
subtitle: `No hemos podido cargar todos sus datos. Hemos sido notificados y estamos investigando el problema. Si esto persiste, por favor comuníquese con`,
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 554e4a16b5be..e5161f7d23f1 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -9311,6 +9311,20 @@ Voici un *reçu test* pour vous montrer comment ça fonctionne :`,
exportInProgress: 'Export en cours',
conciergeWillSend: 'Concierge vous enverra le fichier sous peu.',
},
+ exportDownload: {
+ preparingTitle: 'Preparing download...',
+ preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
+ sendFromConcierge: "Send me the file when it's ready",
+ conciergeTitle: 'You bet!',
+ conciergeBody: 'Concierge will send you a message when the file is ready.',
+ goToConcierge: 'Go to Concierge',
+ dismiss: 'Dismiss',
+ readyTitle: 'Your file is ready!',
+ readyBody: "If it didn't automatically download, use the button below.",
+ downloadFile: 'Download file',
+ failedTitle: 'Export failed',
+ close: 'Close',
+ },
domain: {
notVerified: 'Non vérifié',
retry: 'Réessayer',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 6b623afded90..a1296e3b8dff 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -9273,6 +9273,20 @@ Ecco una *ricevuta di prova* per mostrarti come funziona:`,
exportInProgress: 'Esportazione in corso',
conciergeWillSend: 'Concierge ti invierà il file a breve.',
},
+ exportDownload: {
+ preparingTitle: 'Preparing download...',
+ preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
+ sendFromConcierge: "Send me the file when it's ready",
+ conciergeTitle: 'You bet!',
+ conciergeBody: 'Concierge will send you a message when the file is ready.',
+ goToConcierge: 'Go to Concierge',
+ dismiss: 'Dismiss',
+ readyTitle: 'Your file is ready!',
+ readyBody: "If it didn't automatically download, use the button below.",
+ downloadFile: 'Download file',
+ failedTitle: 'Export failed',
+ close: 'Close',
+ },
domain: {
notVerified: 'Non verificato',
retry: 'Riprova',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 6979a4002a61..ee91aa4ca521 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -9157,6 +9157,20 @@ ${reportName}
exportInProgress: 'エクスポート処理中',
conciergeWillSend: 'Conciergeがまもなくファイルを送信します。',
},
+ exportDownload: {
+ preparingTitle: 'Preparing download...',
+ preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
+ sendFromConcierge: "Send me the file when it's ready",
+ conciergeTitle: 'You bet!',
+ conciergeBody: 'Concierge will send you a message when the file is ready.',
+ goToConcierge: 'Go to Concierge',
+ dismiss: 'Dismiss',
+ readyTitle: 'Your file is ready!',
+ readyBody: "If it didn't automatically download, use the button below.",
+ downloadFile: 'Download file',
+ failedTitle: 'Export failed',
+ close: 'Close',
+ },
domain: {
notVerified: '未確認',
retry: '再試行',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 1509a397fc94..f052ee39f7db 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -9242,6 +9242,20 @@ Hier is een *proefbon* om je te laten zien hoe het werkt:`,
exportInProgress: 'Export bezig',
conciergeWillSend: 'Concierge stuurt je het bestand zo meteen.',
},
+ exportDownload: {
+ preparingTitle: 'Preparing download...',
+ preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
+ sendFromConcierge: "Send me the file when it's ready",
+ conciergeTitle: 'You bet!',
+ conciergeBody: 'Concierge will send you a message when the file is ready.',
+ goToConcierge: 'Go to Concierge',
+ dismiss: 'Dismiss',
+ readyTitle: 'Your file is ready!',
+ readyBody: "If it didn't automatically download, use the button below.",
+ downloadFile: 'Download file',
+ failedTitle: 'Export failed',
+ close: 'Close',
+ },
domain: {
notVerified: 'Niet geverifieerd',
retry: 'Opnieuw proberen',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index a4f26a850b73..21e29de3ede2 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -9226,6 +9226,20 @@ Oto *paragon testowy*, żeby pokazać Ci, jak to działa:`,
exportInProgress: 'Trwa eksport',
conciergeWillSend: 'Concierge wkrótce wyśle Ci plik.',
},
+ exportDownload: {
+ preparingTitle: 'Preparing download...',
+ preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
+ sendFromConcierge: "Send me the file when it's ready",
+ conciergeTitle: 'You bet!',
+ conciergeBody: 'Concierge will send you a message when the file is ready.',
+ goToConcierge: 'Go to Concierge',
+ dismiss: 'Dismiss',
+ readyTitle: 'Your file is ready!',
+ readyBody: "If it didn't automatically download, use the button below.",
+ downloadFile: 'Download file',
+ failedTitle: 'Export failed',
+ close: 'Close',
+ },
domain: {
notVerified: 'Niezweryfikowane',
retry: 'Ponów próbę',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 1b1ffcee8e14..219046bfce56 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -9233,6 +9233,20 @@ Aqui está um *comprovante de teste* para mostrar como funciona:`,
exportInProgress: 'Exportação em andamento',
conciergeWillSend: 'O Concierge enviará o arquivo para você em breve.',
},
+ exportDownload: {
+ preparingTitle: 'Preparing download...',
+ preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
+ sendFromConcierge: "Send me the file when it's ready",
+ conciergeTitle: 'You bet!',
+ conciergeBody: 'Concierge will send you a message when the file is ready.',
+ goToConcierge: 'Go to Concierge',
+ dismiss: 'Dismiss',
+ readyTitle: 'Your file is ready!',
+ readyBody: "If it didn't automatically download, use the button below.",
+ downloadFile: 'Download file',
+ failedTitle: 'Export failed',
+ close: 'Close',
+ },
domain: {
notVerified: 'Não verificado',
retry: 'Tentar novamente',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 023f23b2cfb3..51dba4977ec8 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -9001,6 +9001,20 @@ ${reportName}
exportInProgress: '导出进行中',
conciergeWillSend: 'Concierge 将很快把文件发送给你。',
},
+ exportDownload: {
+ preparingTitle: 'Preparing download...',
+ preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
+ sendFromConcierge: "Send me the file when it's ready",
+ conciergeTitle: 'You bet!',
+ conciergeBody: 'Concierge will send you a message when the file is ready.',
+ goToConcierge: 'Go to Concierge',
+ dismiss: 'Dismiss',
+ readyTitle: 'Your file is ready!',
+ readyBody: "If it didn't automatically download, use the button below.",
+ downloadFile: 'Download file',
+ failedTitle: 'Export failed',
+ close: 'Close',
+ },
domain: {
notVerified: '未验证',
retry: '重试',
diff --git a/src/types/onyx/ExportDownload.ts b/src/types/onyx/ExportDownload.ts
index 1845fd94d219..400d73b4235d 100644
--- a/src/types/onyx/ExportDownload.ts
+++ b/src/types/onyx/ExportDownload.ts
@@ -9,6 +9,9 @@ type ExportDownload = {
/** Current state of the export download */
state: ExportDownloadState;
+ /** URL to download the exported file when state is ready */
+ downloadURL?: string;
+
/** Number of reports included in the export (PDF exports only) */
reportCount?: number;
diff --git a/tests/unit/ExportDownloadStatusModalTest.tsx b/tests/unit/ExportDownloadStatusModalTest.tsx
new file mode 100644
index 000000000000..f8ac94c6e5f6
--- /dev/null
+++ b/tests/unit/ExportDownloadStatusModalTest.tsx
@@ -0,0 +1,178 @@
+import {fireEvent, render, screen} from '@testing-library/react-native';
+import React from 'react';
+import Onyx from 'react-native-onyx';
+import ExportDownloadStatusModal from '@components/ExportDownloadStatusModal';
+import fileDownload from '@libs/fileDownload';
+import {clearExportDownload, sendExportFileFromConcierge} from '@userActions/Export';
+import {navigateToConciergeChat} from '@userActions/Report';
+import ONYXKEYS from '@src/ONYXKEYS';
+import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct';
+
+jest.mock('@libs/fileDownload');
+jest.mock('@userActions/Export', () => ({
+ sendExportFileFromConcierge: jest.fn(),
+ clearExportDownload: jest.fn(),
+}));
+jest.mock('@userActions/Report', () => ({
+ navigateToConciergeChat: jest.fn(),
+}));
+jest.mock('@libs/Navigation/Navigation', () => ({
+ navigate: jest.fn(),
+ isNavigationReady: jest.fn(() => Promise.resolve()),
+ isTopmostRouteModalScreen: jest.fn(() => false),
+ getActiveRouteWithoutParams: jest.fn(() => ''),
+}));
+jest.mock('@hooks/useLocalize', () => ({
+ __esModule: true,
+ default: () => ({
+ translate: (key: string) => key,
+ }),
+}));
+
+const mockFileDownload = fileDownload as jest.MockedFunction;
+const mockSendFromConcierge = sendExportFileFromConcierge as jest.MockedFunction;
+const mockClearExportDownload = clearExportDownload as jest.MockedFunction;
+const mockNavigateToConcierge = navigateToConciergeChat as jest.MockedFunction;
+
+const EXPORT_ID = 'test-export-123';
+const DOWNLOAD_URL = 'https://example.com/export.csv';
+
+function renderModal(props: Partial> = {}) {
+ return render(
+ ,
+ );
+}
+
+describe('ExportDownloadStatusModal', () => {
+ beforeAll(() => {
+ Onyx.init({keys: ONYXKEYS});
+ });
+
+ beforeEach(async () => {
+ jest.clearAllMocks();
+ await Onyx.clear();
+ });
+
+ it('renders preparing state with spinner and Send button', async () => {
+ await Onyx.set(`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${EXPORT_ID}`, {state: 'preparing'});
+
+ renderModal();
+ await waitForBatchedUpdatesWithAct();
+
+ expect(screen.getByText('exportDownload.preparingTitle')).toBeTruthy();
+ expect(screen.getByText('exportDownload.preparingBody')).toBeTruthy();
+ expect(screen.getByText('exportDownload.sendFromConcierge')).toBeTruthy();
+ });
+
+ it('is non-dismissible during preparing state', async () => {
+ const onClose = jest.fn();
+ await Onyx.set(`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${EXPORT_ID}`, {state: 'preparing'});
+
+ renderModal({onClose});
+ await waitForBatchedUpdatesWithAct();
+
+ expect(screen.getByText('exportDownload.preparingTitle')).toBeTruthy();
+ expect(onClose).not.toHaveBeenCalled();
+ });
+
+ it('transitions to Concierge state on Send button press', async () => {
+ await Onyx.set(`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${EXPORT_ID}`, {state: 'preparing'});
+
+ renderModal();
+ await waitForBatchedUpdatesWithAct();
+
+ fireEvent.press(screen.getByText('exportDownload.sendFromConcierge'));
+
+ expect(mockSendFromConcierge).toHaveBeenCalledWith(EXPORT_ID, expect.objectContaining({state: 'preparing'}));
+ });
+
+ it('shows Concierge state when shouldSendFromConcierge is true', async () => {
+ await Onyx.set(`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${EXPORT_ID}`, {state: 'preparing', shouldSendFromConcierge: true});
+
+ renderModal();
+ await waitForBatchedUpdatesWithAct();
+
+ expect(screen.getByText('exportDownload.conciergeTitle')).toBeTruthy();
+ expect(screen.getByText('exportDownload.conciergeBody')).toBeTruthy();
+ expect(screen.getByText('exportDownload.goToConcierge')).toBeTruthy();
+ expect(screen.getByText('exportDownload.dismiss')).toBeTruthy();
+ });
+
+ it('auto-downloads on ready state transition', async () => {
+ await Onyx.set(`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${EXPORT_ID}`, {state: 'ready', downloadURL: DOWNLOAD_URL});
+
+ renderModal();
+ await waitForBatchedUpdatesWithAct();
+
+ expect(mockFileDownload).toHaveBeenCalledWith(expect.anything(), DOWNLOAD_URL);
+ });
+
+ it('shows ready state with Download and Close buttons', async () => {
+ await Onyx.set(`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${EXPORT_ID}`, {state: 'ready', downloadURL: DOWNLOAD_URL});
+
+ renderModal();
+ await waitForBatchedUpdatesWithAct();
+
+ expect(screen.getByText('exportDownload.readyTitle')).toBeTruthy();
+ expect(screen.getByText('exportDownload.readyBody')).toBeTruthy();
+ expect(screen.getByText('exportDownload.downloadFile')).toBeTruthy();
+ expect(screen.getByText('exportDownload.close')).toBeTruthy();
+ });
+
+ it('shows failed state with correct failedBody prop', async () => {
+ const failedBody = 'The CSV export encountered an error.';
+ await Onyx.set(`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${EXPORT_ID}`, {state: 'failed'});
+
+ renderModal({failedBody});
+ await waitForBatchedUpdatesWithAct();
+
+ expect(screen.getByText('exportDownload.failedTitle')).toBeTruthy();
+ expect(screen.getByText(failedBody)).toBeTruthy();
+ expect(screen.getByText('exportDownload.close')).toBeTruthy();
+ });
+
+ it('retains last state when Onyx key becomes null', async () => {
+ await Onyx.set(`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${EXPORT_ID}`, {state: 'ready', downloadURL: DOWNLOAD_URL});
+
+ renderModal();
+ await waitForBatchedUpdatesWithAct();
+
+ expect(screen.getByText('exportDownload.readyTitle')).toBeTruthy();
+
+ await Onyx.set(`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${EXPORT_ID}`, null);
+ await waitForBatchedUpdatesWithAct();
+
+ expect(screen.getByText('exportDownload.readyTitle')).toBeTruthy();
+ });
+
+ it('"Go to Concierge" navigates and closes', async () => {
+ const onClose = jest.fn();
+ await Onyx.set(`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${EXPORT_ID}`, {state: 'preparing', shouldSendFromConcierge: true});
+
+ renderModal({onClose});
+ await waitForBatchedUpdatesWithAct();
+
+ fireEvent.press(screen.getByText('exportDownload.goToConcierge'));
+
+ expect(onClose).toHaveBeenCalled();
+ expect(mockNavigateToConcierge).toHaveBeenCalled();
+ });
+
+ it('Close button calls clearExportDownload', async () => {
+ const onClose = jest.fn();
+ await Onyx.set(`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${EXPORT_ID}`, {state: 'ready', downloadURL: DOWNLOAD_URL});
+
+ renderModal({onClose});
+ await waitForBatchedUpdatesWithAct();
+
+ fireEvent.press(screen.getByText('exportDownload.close'));
+
+ expect(mockClearExportDownload).toHaveBeenCalledWith(EXPORT_ID, expect.objectContaining({state: 'ready'}));
+ expect(onClose).toHaveBeenCalled();
+ });
+});