diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx
index e756308f32da5..97b87d2885506 100644
--- a/src/pages/iou/request/IOURequestStartPage.tsx
+++ b/src/pages/iou/request/IOURequestStartPage.tsx
@@ -97,7 +97,6 @@ function IOURequestStartPage({
});
const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES);
- const [isMultiScanEnabled, setIsMultiScanEnabled] = useState(false);
const [currentDate] = useOnyx(ONYXKEYS.CURRENT_DATE);
const {isOffline} = useNetwork();
const [hasUserSubmittedExpenseOrScannedReceipt] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {selector: isTestReceiptTooltipDismissedSelector});
@@ -186,7 +185,6 @@ function IOURequestStartPage({
if (transaction?.iouRequestType === newIOUType) {
return;
}
- setIsMultiScanEnabled(false);
initMoneyRequest({
reportID,
policy,
@@ -340,14 +338,12 @@ function IOURequestStartPage({
{() => (
{
setTestReceiptAndNavigateRef.current = setTestReceiptAndNavigate;
}}
- isMultiScanEnabled={isMultiScanEnabled}
- setIsMultiScanEnabled={setIsMultiScanEnabled}
- isStartingScan
/>
)}
diff --git a/src/pages/iou/request/step/IOURequestStepScan/components/MobileWebCameraView.tsx b/src/pages/iou/request/step/IOURequestStepScan/components/MobileWebCameraView.tsx
index 84b8d6b351a14..da3ff64a13c58 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/components/MobileWebCameraView.tsx
+++ b/src/pages/iou/request/step/IOURequestStepScan/components/MobileWebCameraView.tsx
@@ -43,10 +43,10 @@ type MobileWebCameraViewProps = {
iouType: IOUType;
currentUserPersonalDetails: CurrentUserPersonalDetails;
reportID: string;
- isMultiScanEnabled?: boolean;
- isStartingScan?: boolean;
+ isMultiScanEnabled: boolean;
+ isStartingScan: boolean;
updateScanAndNavigate: (file: FileObject, source: string) => void;
- setIsMultiScanEnabled?: (value: boolean) => void;
+ setIsMultiScanEnabled: (value: boolean) => void;
PDFValidationComponent: React.ReactNode;
shouldAcceptMultipleFiles: boolean;
receiptFiles: ReceiptFile[];
@@ -56,9 +56,9 @@ type MobileWebCameraViewProps = {
navigateToConfirmationStep: (files: ReceiptFile[], locationPermissionGranted?: boolean, isTestTransaction?: boolean) => void;
shouldSkipConfirmation: boolean;
setStartLocationPermissionFlow: (value: boolean) => void;
- onLayout?: () => void;
onBackButtonPress: () => void;
shouldShowWrapper: boolean;
+ onLayout?: () => void;
};
/**
@@ -103,7 +103,7 @@ function MobileWebCameraView({
iouType,
currentUserPersonalDetails,
reportID,
- isMultiScanEnabled = false,
+ isMultiScanEnabled,
isStartingScan,
updateScanAndNavigate,
setIsMultiScanEnabled,
@@ -116,9 +116,9 @@ function MobileWebCameraView({
navigateToConfirmationStep,
shouldSkipConfirmation,
setStartLocationPermissionFlow,
- onLayout,
onBackButtonPress,
shouldShowWrapper,
+ onLayout,
}: MobileWebCameraViewProps) {
const {blinkStyle, canUseMultiScan, shouldShowMultiScanEducationalPopup, showBlink, toggleMultiScan, dismissMultiScanEducationalPopup, submitReceipts, submitMultiScanReceipts} =
useMobileReceiptScan({
@@ -131,6 +131,7 @@ function MobileWebCameraView({
shouldSkipConfirmation,
setStartLocationPermissionFlow,
setIsMultiScanEnabled,
+ setReceiptFiles,
});
const theme = useTheme();
const styles = useThemeStyles();
diff --git a/src/pages/iou/request/step/IOURequestStepScan/hooks/useMobileReceiptScan.ts b/src/pages/iou/request/step/IOURequestStepScan/hooks/useMobileReceiptScan.ts
index 0a7240f3bb3be..aa89b94faa713 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/hooks/useMobileReceiptScan.ts
+++ b/src/pages/iou/request/step/IOURequestStepScan/hooks/useMobileReceiptScan.ts
@@ -19,12 +19,13 @@ function useMobileReceiptScan({
initialTransaction,
iouType,
isMultiScanEnabled = false,
- isStartingScan = false,
+ isStartingScan,
receiptFiles,
navigateToConfirmationStep,
shouldSkipConfirmation,
setStartLocationPermissionFlow,
setIsMultiScanEnabled,
+ setReceiptFiles,
}: UseMobileReceiptScanParams) {
const [shouldStartLocationPermissionFlow] = useOnyx(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, {
selector: shouldStartLocationPermissionFlowSelector,
@@ -78,7 +79,10 @@ function useMobileReceiptScan({
}
removeTransactionReceipt(CONST.IOU.OPTIMISTIC_TRANSACTION_ID);
removeDraftTransactionsByIDs(draftTransactionIDs, true);
- setIsMultiScanEnabled?.(!isMultiScanEnabled);
+ if (isMultiScanEnabled) {
+ setReceiptFiles([]);
+ }
+ setIsMultiScanEnabled(!isMultiScanEnabled);
}
function dismissMultiScanEducationalPopup() {
diff --git a/src/pages/iou/request/step/IOURequestStepScan/hooks/useReceiptScan.ts b/src/pages/iou/request/step/IOURequestStepScan/hooks/useReceiptScan.ts
index 6b9f64605d8ce..d8ea3695b1b18 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/hooks/useReceiptScan.ts
+++ b/src/pages/iou/request/step/IOURequestStepScan/hooks/useReceiptScan.ts
@@ -1,6 +1,6 @@
import shouldStartLocationPermissionFlowSelector from '@selectors/LocationPermission';
import {hasSeenTourSelector} from '@selectors/Onboarding';
-import {useEffect, useMemo, useState} from 'react';
+import {useMemo, useState} from 'react';
import TestReceipt from '@assets/images/fake-receipt.png';
import useDefaultExpensePolicy from '@hooks/useDefaultExpensePolicy';
import useFilesValidation from '@hooks/useFilesValidation';
@@ -38,8 +38,6 @@ function useReceiptScan({
currentUserPersonalDetails,
backTo,
backToReport,
- isMultiScanEnabled = false,
- isStartingScan = false,
updateScanAndNavigate,
getSource,
}: UseReceiptScanParams) {
@@ -70,9 +68,11 @@ function useReceiptScan({
const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END);
const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END);
const draftTransactionIDs = Object.keys(allTransactionDrafts ?? {});
+ const [isMultiScanEnabled, setIsMultiScanEnabled] = useState(false);
const isEditing = action === CONST.IOU.ACTION.EDIT;
const isReplacingReceipt = (isEditing && hasReceipt(initialTransaction)) || (!!initialTransaction?.receipt && !!backTo);
+ const isStartingScan = action === CONST.IOU.ACTION.CREATE && !isReplacingReceipt;
const shouldAcceptMultipleFiles = !isEditing && !backTo;
const shouldGenerateTransactionThreadReport = !isBetaEnabled(CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS);
const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
@@ -90,15 +90,6 @@ function useReceiptScan({
const [startLocationPermissionFlow, setStartLocationPermissionFlow] = useState(false);
const [receiptFiles, setReceiptFiles] = useState([]);
- // Clear receipt files when multi-scan is disabled
- useEffect(() => {
- if (isMultiScanEnabled) {
- return;
- }
- // eslint-disable-next-line react-hooks/set-state-in-effect
- setReceiptFiles([]);
- }, [isMultiScanEnabled]);
-
const [recentWaypoints] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS);
const participants = useMemo(
@@ -232,6 +223,9 @@ function useReceiptScan({
});
return {
transactions,
+ isMultiScanEnabled,
+ setIsMultiScanEnabled,
+ isStartingScan,
isEditing,
isReplacingReceipt,
shouldAcceptMultipleFiles,
diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
index 2c70bc91da24a..66b5b5b3c62b0 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
+++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
@@ -64,9 +64,6 @@ function IOURequestStepScan({
transaction: initialTransaction,
currentUserPersonalDetails,
onLayout,
- isMultiScanEnabled = false,
- isStartingScan = false,
- setIsMultiScanEnabled,
}: IOURequestStepScanProps) {
const theme = useTheme();
const styles = useThemeStyles();
@@ -288,6 +285,9 @@ function IOURequestStepScan({
const getSource = useCallback((file: FileObject) => file.uri ?? '', []);
const {
+ isMultiScanEnabled,
+ setIsMultiScanEnabled,
+ isStartingScan,
isEditing,
shouldAcceptMultipleFiles,
shouldSkipConfirmation,
@@ -310,8 +310,6 @@ function IOURequestStepScan({
currentUserPersonalDetails,
backTo,
backToReport,
- isMultiScanEnabled,
- isStartingScan,
updateScanAndNavigate,
getSource,
});
@@ -327,6 +325,7 @@ function IOURequestStepScan({
shouldSkipConfirmation,
setStartLocationPermissionFlow,
setIsMultiScanEnabled,
+ setReceiptFiles,
});
const maybeCancelShutterSpan = useCallback(() => {
diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx
index a3d87108e32c9..ed3175bb9feec 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx
+++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx
@@ -32,9 +32,6 @@ function IOURequestStepScan({
transaction: initialTransaction,
currentUserPersonalDetails,
onLayout,
- isMultiScanEnabled = false,
- isStartingScan = false,
- setIsMultiScanEnabled,
}: Omit) {
const isMobileWeb = isMobile();
const policy = usePolicy(report?.policyID);
@@ -62,6 +59,9 @@ function IOURequestStepScan({
const {
transactions,
+ isMultiScanEnabled,
+ setIsMultiScanEnabled,
+ isStartingScan,
isEditing,
isReplacingReceipt,
shouldAcceptMultipleFiles,
@@ -85,8 +85,6 @@ function IOURequestStepScan({
currentUserPersonalDetails,
backTo,
backToReport,
- isMultiScanEnabled,
- isStartingScan,
updateScanAndNavigate,
getSource,
});
@@ -127,11 +125,10 @@ function IOURequestStepScan({
if (isAllScanFilesCanBeRead) {
return;
}
- setIsMultiScanEnabled?.(false);
removeTransactionReceipt(CONST.IOU.OPTIMISTIC_TRANSACTION_ID);
removeDraftTransactionsByIDs(draftTransactionIDs, true);
});
- }, [setIsMultiScanEnabled, transactions, draftTransactionIDs]);
+ }, [transactions, draftTransactionIDs]);
// this effect will pre-fetch location in web if the location permission is already granted to optimize the flow
useEffect(() => {
diff --git a/src/pages/iou/request/step/IOURequestStepScan/types.ts b/src/pages/iou/request/step/IOURequestStepScan/types.ts
index 280107572e1ca..6772c8792729e 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/types.ts
+++ b/src/pages/iou/request/step/IOURequestStepScan/types.ts
@@ -38,12 +38,6 @@ type UseReceiptScanParams = {
/** Report ID to navigate back to */
backToReport: string | undefined;
- /** Whether multi-scan is enabled */
- isMultiScanEnabled: boolean | undefined;
-
- /** Whether the user is starting a scan request */
- isStartingScan: boolean | undefined;
-
/** Callback to replace receipt and navigate back when editing */
updateScanAndNavigate: (file: FileObject, source: string) => void;
@@ -62,7 +56,7 @@ type UseMobileReceiptScanParams = {
isMultiScanEnabled?: boolean;
/** Whether the user is starting a scan request */
- isStartingScan?: boolean;
+ isStartingScan: boolean;
/** The current receipt files being scanned */
receiptFiles: ReceiptFile[];
@@ -76,8 +70,11 @@ type UseMobileReceiptScanParams = {
/** Callback to start the location permission flow */
setStartLocationPermissionFlow: (value: boolean) => void;
- /** Callback to update multi-scan enabled state in parent */
- setIsMultiScanEnabled: ((value: boolean) => void) | undefined;
+ /** Callback to update multi-scan enabled state */
+ setIsMultiScanEnabled: (value: boolean) => void;
+
+ /** Callback to update scanned receipt files */
+ setReceiptFiles: (value: ReceiptFile[]) => void;
};
type IOURequestStepScanProps = WithCurrentUserPersonalDetailsProps &
@@ -90,15 +87,6 @@ type IOURequestStepScanProps = WithCurrentUserPersonalDetailsProps &
* Receives a function (`setTestReceiptAndNavigate`) as an argument,
*/
onLayout?: (setTestReceiptAndNavigate: () => void) => void;
-
- /** If the receipts preview should be shown */
- isMultiScanEnabled?: boolean;
-
- /** Updates isMultiScanEnabled flag */
- setIsMultiScanEnabled?: (value: boolean) => void;
-
- /** Indicates whether users start to create scan request */
- isStartingScan?: boolean;
};
type ReceiptFile = {
diff --git a/tests/ui/IOURequestStepScanTest.tsx b/tests/ui/IOURequestStepScanTest.tsx
index e531a8029e64a..82ee8173bf5da 100644
--- a/tests/ui/IOURequestStepScanTest.tsx
+++ b/tests/ui/IOURequestStepScanTest.tsx
@@ -1,5 +1,5 @@
import {NavigationContainer} from '@react-navigation/native';
-import {act, render} from '@testing-library/react-native';
+import {act, fireEvent, render, screen} from '@testing-library/react-native';
import React from 'react';
import Onyx from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
@@ -172,17 +172,6 @@ describe('IOURequestStepScan', () => {
const POLICY_ID = 'policy-1';
const TRANSACTION_ID_1 = '101';
- const transaction1 = createRandomTransaction(1);
- transaction1.reportID = REPORT_ID;
- transaction1.transactionID = TRANSACTION_ID_1;
- transaction1.receipt = {source: 'file://first-receipt.png', state: CONST.IOU.RECEIPT_STATE.OPEN};
-
- await act(async () => {
- await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, createMinimalReport(REPORT_ID, POLICY_ID));
- await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID_1}`, transaction1);
- });
- await waitForBatchedUpdates();
-
render(
@@ -202,9 +191,6 @@ describe('IOURequestStepScan', () => {
} as unknown as PlatformStackScreenProps['route']
}
navigation={{} as never}
- isMultiScanEnabled
- isStartingScan
- setIsMultiScanEnabled={jest.fn()}
/>
@@ -213,6 +199,19 @@ describe('IOURequestStepScan', () => {
await waitForBatchedUpdatesWithAct();
+ fireEvent.press(screen.getByLabelText('multi-scan'));
+ await waitForBatchedUpdates();
+
+ const transaction1 = createRandomTransaction(1);
+ transaction1.reportID = REPORT_ID;
+ transaction1.transactionID = TRANSACTION_ID_1;
+ transaction1.receipt = {source: 'file://first-receipt.png', state: CONST.IOU.RECEIPT_STATE.OPEN};
+ await act(async () => {
+ await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, createMinimalReport(REPORT_ID, POLICY_ID));
+ await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID_1}`, transaction1);
+ });
+ await waitForBatchedUpdates();
+
expect(triggerFileSelection).not.toBeNull();
const secondFile = {name: 'second-receipt.png', type: 'image/png', size: 200, uri: 'file://second-receipt.png'} as FileObject;
diff --git a/tests/unit/hooks/useMobileReceiptScan.test.ts b/tests/unit/hooks/useMobileReceiptScan.test.ts
index 12b46d41b4a0e..a278b1e1d20ea 100644
--- a/tests/unit/hooks/useMobileReceiptScan.test.ts
+++ b/tests/unit/hooks/useMobileReceiptScan.test.ts
@@ -37,6 +37,7 @@ function createDefaultParams(): UseMobileReceiptScanParams {
shouldSkipConfirmation: false,
setStartLocationPermissionFlow: jest.fn(),
setIsMultiScanEnabled: jest.fn(),
+ setReceiptFiles: jest.fn(),
};
}
@@ -129,6 +130,36 @@ describe('useMobileReceiptScan', () => {
expect(mockRemoveTransactionReceipt).toHaveBeenCalledWith(CONST.IOU.OPTIMISTIC_TRANSACTION_ID);
expect(mockRemoveDraftTransactions).toHaveBeenCalledWith(expect.anything(), true);
});
+
+ it('should clear receiptFiles when disabling multi-scan', async () => {
+ const setIsMultiScanEnabled = jest.fn();
+ const setReceiptFiles = jest.fn();
+ const toggleParams = {...params, setIsMultiScanEnabled, setReceiptFiles, isMultiScanEnabled: true};
+ const {result} = renderHook(() => useMobileReceiptScan(toggleParams));
+ await waitForBatchedUpdatesWithAct();
+
+ await act(async () => {
+ result.current.toggleMultiScan();
+ });
+
+ expect(setReceiptFiles).toHaveBeenCalledWith([]);
+ expect(setIsMultiScanEnabled).toHaveBeenCalledWith(false);
+ });
+
+ it('should not clear receiptFiles when enabling multi-scan', async () => {
+ const setIsMultiScanEnabled = jest.fn();
+ const setReceiptFiles = jest.fn();
+ const toggleParams = {...params, setIsMultiScanEnabled, setReceiptFiles, isMultiScanEnabled: false};
+ const {result} = renderHook(() => useMobileReceiptScan(toggleParams));
+ await waitForBatchedUpdatesWithAct();
+
+ await act(async () => {
+ result.current.toggleMultiScan();
+ });
+
+ expect(setReceiptFiles).not.toHaveBeenCalled();
+ expect(setIsMultiScanEnabled).toHaveBeenCalledWith(true);
+ });
});
describe('dismissMultiScanEducationalPopup', () => {
diff --git a/tests/unit/hooks/useReceiptScan.test.ts b/tests/unit/hooks/useReceiptScan.test.ts
index 6acc3be489b9f..b6d82af878954 100644
--- a/tests/unit/hooks/useReceiptScan.test.ts
+++ b/tests/unit/hooks/useReceiptScan.test.ts
@@ -62,8 +62,6 @@ function createDefaultParams(): Parameters[0] {
getSource: (file: {uri?: string}) => file?.uri ?? 'file://image.png',
backTo: undefined,
backToReport: undefined,
- isMultiScanEnabled: false,
- isStartingScan: true,
};
}
@@ -296,25 +294,6 @@ describe('useReceiptScan', () => {
expect(result.current.receiptFiles).toHaveLength(1);
expect(result.current.receiptFiles.at(0)).toEqual(receiptFile);
});
-
- it('should clear receiptFiles when isMultiScanEnabled changes from true to false', async () => {
- const multiScanParams = {...params, isMultiScanEnabled: true};
- const {result, rerender} = renderHook((p: Parameters[0]) => useReceiptScan(p), {
- initialProps: multiScanParams,
- });
- await waitForBatchedUpdatesWithAct();
-
- const receiptFile = {file: {uri: 'picture.jpg'}, source: 'file://picture.jpg', transactionID: INITIAL_TRANSACTION_ID};
- await act(async () => {
- result.current.setReceiptFiles([receiptFile]);
- });
- await waitForBatchedUpdatesWithAct();
- expect(result.current.receiptFiles).toHaveLength(1);
-
- rerender({...multiScanParams, isMultiScanEnabled: false});
- await waitForBatchedUpdatesWithAct();
- expect(result.current.receiptFiles).toEqual([]);
- });
});
describe('processReceipts', () => {
@@ -382,6 +361,11 @@ describe('useReceiptScan', () => {
const {result} = renderHook(() => useReceiptScan(params));
await waitForBatchedUpdatesWithAct();
+ await act(async () => {
+ result.current.setIsMultiScanEnabled(false);
+ });
+ await waitForBatchedUpdatesWithAct();
+
const files = [{uri: 'file://receipt.jpg', name: 'receipt.jpg', type: 'image/jpeg'}];
await act(async () => {
result.current.validateFiles(files);
@@ -407,8 +391,12 @@ describe('useReceiptScan', () => {
});
it('should not call removeDraftTransactionsByIDs when multi-scan is enabled', async () => {
- const multiScanParams = {...params, isMultiScanEnabled: true, setIsMultiScanEnabled: jest.fn()};
- const {result} = renderHook(() => useReceiptScan(multiScanParams));
+ const {result} = renderHook(() => useReceiptScan(params));
+ await waitForBatchedUpdatesWithAct();
+
+ await act(async () => {
+ result.current.setIsMultiScanEnabled(true);
+ });
await waitForBatchedUpdatesWithAct();
const files = [{uri: 'file://receipt.jpg', name: 'receipt.jpg', type: 'image/jpeg'}];
@@ -420,7 +408,7 @@ describe('useReceiptScan', () => {
});
it('should not call removeDraftTransactionsByIDs when isStartingScan is false', async () => {
- const nonStartingParams = {...params, isStartingScan: false};
+ const nonStartingParams = {...params, action: CONST.IOU.ACTION.SUBMIT};
const {result} = renderHook(() => useReceiptScan(nonStartingParams));
await waitForBatchedUpdatesWithAct();
@@ -443,6 +431,11 @@ describe('useReceiptScan', () => {
const {result} = renderHook(() => useReceiptScan(params));
await waitForBatchedUpdatesWithAct();
+ await act(async () => {
+ result.current.setIsMultiScanEnabled(false);
+ });
+ await waitForBatchedUpdatesWithAct();
+
const files = [{uri: 'file://receipt.jpg', name: 'receipt.jpg', type: 'image/jpeg'}];
await act(async () => {
result.current.validateFiles(files);