diff --git a/.github/libs/sanitizeStringForJSONParse.ts b/.github/libs/sanitizeStringForJSONParse.ts
index 3fbdaa8661f8..ddb7549b0186 100644
--- a/.github/libs/sanitizeStringForJSONParse.ts
+++ b/.github/libs/sanitizeStringForJSONParse.ts
@@ -16,7 +16,7 @@ const replacer = (str: string): string =>
* Solution partly taken from SO user Gabriel RodrÃguez Flores 🙇
* https://stackoverflow.com/questions/52789718/how-to-remove-special-characters-before-json-parse-while-file-reading
*/
-const sanitizeStringForJSONParse = (inputString: string): string => {
+const sanitizeStringForJSONParse = (inputString: string | number | boolean | null | undefined): string => {
if (typeof inputString !== 'string') {
throw new TypeError('Input must me of type String');
}
diff --git a/__mocks__/@react-navigation/native/index.ts b/__mocks__/@react-navigation/native/index.ts
index 9a6680ba0b6e..0b7dda4621ad 100644
--- a/__mocks__/@react-navigation/native/index.ts
+++ b/__mocks__/@react-navigation/native/index.ts
@@ -3,9 +3,7 @@ import {useIsFocused as realUseIsFocused, useTheme as realUseTheme} from '@react
// We only want these mocked for storybook, not jest
const useIsFocused: typeof realUseIsFocused = process.env.NODE_ENV === 'test' ? realUseIsFocused : () => true;
-// @ts-expect-error as we're mocking this function
-const useTheme: typeof realUseTheme = process.env.NODE_ENV === 'test' ? realUseTheme : () => ({});
+const useTheme = process.env.NODE_ENV === 'test' ? realUseTheme : () => ({});
export * from '@react-navigation/core';
-export * from '@react-navigation/native';
export {useIsFocused, useTheme};
diff --git a/android/app/build.gradle b/android/app/build.gradle
index abdc6765ec1a..9e5a237afd1e 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -98,8 +98,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001047400
- versionName "1.4.74-0"
+ versionCode 1001047404
+ versionName "1.4.74-4"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
diff --git a/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md b/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md
index c5578249289a..239da6518be7 100644
--- a/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md
+++ b/docs/articles/expensify-classic/expensify-card/Unlimited-Virtual-Cards.md
@@ -26,13 +26,13 @@ After adopting the new Expensify Card, domain admins can issue virtual cards to
**To assign a virtual card:**
-1. Head to **Settings** > **Domains** > [**Company Cards**](https://www.expensify.com/domain_companycards).
-2. Click the **Issue Virtual Cards** button.
-3. Enter a card name (i.e., "Google Ads").
-4. Select a domain member to assign the card to.
-5. Enter a card limit.
-6. Select a **Limit Type** of _Fixed_ or _Monthly_.
-7. Click **Issue Card**.
+Head to **Settings** > **Domains** > [**Company Cards**](https://www.expensify.com/domain_companycards) and click the **Issue Virtual Cards** button. From there:
+
+1. Enter a card name (i.e., "Google Ads").
+2. Select a domain member to assign the card to.
+3. Enter a card limit.
+4. Select a **Limit Type** of _Fixed_ or _Monthly_.
+5. Click **Issue Card**.
![The Issue Virtual Cards modal is open in the middle of the screen. There are four options to set; Card Name, Assignee, Card Limit, and Limit type. A cancel (left) and save (right) button are at the bottom right of the modal.]({{site.url}}/assets/images/AdminissuedVirtualCards.png){:width="100%"}
diff --git a/docs/articles/expensify-classic/expensify-partner-program/Referral-Program.md b/docs/articles/expensify-classic/expensify-partner-program/Referral-Program.md
index a1b1043dff47..424c8dc9d107 100644
--- a/docs/articles/expensify-classic/expensify-partner-program/Referral-Program.md
+++ b/docs/articles/expensify-classic/expensify-partner-program/Referral-Program.md
@@ -16,9 +16,9 @@ The sky's the limit for this referral program! Your referral can be anyone - a f
1. There are a bunch of different ways to refer someone to New Expensify:
- Start a chat
- - Request money
- - Send money
- - Split a bill
+ - Submit an expense to them
+ - Split an expense with them
+ - Pay someone (them)
- Assign them a task
- @ mention them
- Invite them to a room
diff --git a/docs/redirects.csv b/docs/redirects.csv
index c79c07ee3cf4..74fa9c697f5f 100644
--- a/docs/redirects.csv
+++ b/docs/redirects.csv
@@ -172,3 +172,4 @@ https://help.expensify.com/articles/new-expensify/getting-started/Free-plan-upgr
https://help.expensify.com/articles/new-expensify/bank-accounts-and-payments/Connect-a-Bank-Account,https://help.expensify.com/new-expensify/hubs/expenses/Connect-a-Bank-Account
https://help.expensify.com/articles/new-expensify/settings/Profile,https://help.expensify.com/new-expensify/hubs/settings/
https://help.expensify.com/articles/new-expensify/expenses/Referral-Program.html,https://help.expensify.com/articles/expensify-classic/expensify-partner-program/Referral-Program
+https://help.expensify.com/articles/expensify-classic/workspaces/reports/Currency,https://help.expensify.com/articles/expensify-classic/workspaces/Currency
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index e318f45b4a6f..9c9e78ef70b0 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.74.0
+ 1.4.74.4
FullStory
OrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 065ef2041e6e..627c6dbf826c 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.4.74.0
+ 1.4.74.4
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index afe45f0e2591..477f83aa6d00 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString
1.4.74
CFBundleVersion
- 1.4.74.0
+ 1.4.74.4
NSExtension
NSExtensionPointIdentifier
diff --git a/package-lock.json b/package-lock.json
index d4169447b963..a46198daa88a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.74-0",
+ "version": "1.4.74-4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.74-0",
+ "version": "1.4.74-4",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 6a61cffc24a5..1a449f42bece 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.74-0",
+ "version": "1.4.74-4",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
diff --git a/src/CONST.ts b/src/CONST.ts
index c7bdbb2b35e7..a983a18e3d6a 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -3568,7 +3568,7 @@ const CONST = {
TRACK_EXPENSE: 'track-expenses',
},
'track-expenses': {
- VIDEO_URL: `${CLOUDFRONT_URL}/videos/guided-setup-track-business.mp4`,
+ VIDEO_URL: `${CLOUDFRONT_URL}/videos/guided-setup-track-business-v2.mp4`,
LEARN_MORE_LINK: `${USE_EXPENSIFY_URL}/track-expenses`,
},
},
@@ -3781,7 +3781,7 @@ const CONST = {
[onboardingChoices.EMPLOYER]: {
message: 'Getting paid back is as easy as sending a message. Let’s go over the basics.',
video: {
- url: `${CLOUDFRONT_URL}/videos/guided-setup-get-paid-back.mp4`,
+ url: `${CLOUDFRONT_URL}/videos/guided-setup-get-paid-back-v2.mp4`,
thumbnailUrl: `${CLOUDFRONT_URL}/images/guided-setup-get-paid-back.jpg`,
duration: 55,
width: 1280,
@@ -3824,7 +3824,7 @@ const CONST = {
[onboardingChoices.MANAGE_TEAM]: {
message: 'Here are some important tasks to help get your team’s expenses under control.',
video: {
- url: `${CLOUDFRONT_URL}/videos/guided-setup-manage-team.mp4`,
+ url: `${CLOUDFRONT_URL}/videos/guided-setup-manage-team-v2.mp4`,
thumbnailUrl: `${CLOUDFRONT_URL}/images/guided-setup-manage-team.jpg`,
duration: 55,
width: 1280,
@@ -3910,7 +3910,7 @@ const CONST = {
[onboardingChoices.PERSONAL_SPEND]: {
message: 'Here’s how to track your spend in a few clicks.',
video: {
- url: `${CLOUDFRONT_URL}/videos/guided-setup-track-personal.mp4`,
+ url: `${CLOUDFRONT_URL}/videos/guided-setup-track-personal-v2.mp4`,
thumbnailUrl: `${CLOUDFRONT_URL}/images/guided-setup-track-personal.jpg`,
duration: 55,
width: 1280,
@@ -3938,7 +3938,7 @@ const CONST = {
[onboardingChoices.CHAT_SPLIT]: {
message: 'Splitting bills with friends is as easy as sending a message. Here’s how.',
video: {
- url: `${CLOUDFRONT_URL}/videos/guided-setup-chat-split-bills.mp4`,
+ url: `${CLOUDFRONT_URL}/videos/guided-setup-chat-split-bills-v2.mp4`,
thumbnailUrl: `${CLOUDFRONT_URL}/images/guided-setup-chat-split-bills.jpg`,
duration: 55,
width: 1280,
diff --git a/src/Expensify.tsx b/src/Expensify.tsx
index e91a5223d7b6..6205afb9c03c 100644
--- a/src/Expensify.tsx
+++ b/src/Expensify.tsx
@@ -76,7 +76,9 @@ type ExpensifyOnyxProps = {
type ExpensifyProps = ExpensifyOnyxProps;
-const SplashScreenHiddenContext = React.createContext({});
+type SplashScreenHiddenContextType = {isSplashHidden?: boolean};
+
+const SplashScreenHiddenContext = React.createContext({});
function Expensify({
isCheckingPublicRoom = true,
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 0405edbd4189..ddf37fba2354 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -282,6 +282,9 @@ const ONYXKEYS = {
/** Onboarding Purpose selected by the user during Onboarding flow */
ONBOARDING_PURPOSE_SELECTED: 'onboardingPurposeSelected',
+ /** Onboarding policyID selected by the user during Onboarding flow */
+ ONBOARDING_POLICY_ID: 'onboardingPolicyID',
+
/** Onboarding Purpose selected by the user during Onboarding flow */
ONBOARDING_ADMINS_CHAT_REPORT_ID: 'onboardingAdminsChatReportID',
@@ -664,6 +667,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.MAX_CANVAS_HEIGHT]: number;
[ONYXKEYS.MAX_CANVAS_WIDTH]: number;
[ONYXKEYS.ONBOARDING_PURPOSE_SELECTED]: string;
+ [ONYXKEYS.ONBOARDING_POLICY_ID]: string;
[ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID]: string;
[ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: boolean;
[ONYXKEYS.LAST_VISITED_PATH]: string | undefined;
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 0dff2992fe91..2bc04c4a99ea 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -515,10 +515,6 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/invoice-account-select',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export/invoice-account-select` as const,
},
- POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_PAYABLE_SELECT: {
- route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/company-card-expense-account/account-payable-select',
- getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export/company-card-expense-account/account-payable-select` as const,
- },
POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_PREFERRED_EXPORTER: {
route: 'settings/workspaces/:policyID/accounting/quickbooks-online/export/preferred-exporter',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/export/preferred-exporter` as const,
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index e25b9776919a..f74002312623 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -229,7 +229,6 @@ const SCREENS = {
QUICKBOOKS_ONLINE_EXPORT_INVOICE_ACCOUNT_SELECT: 'Workspace_Accounting_Quickbooks_Online_Export_Invoice_Account_Select',
QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT: 'Workspace_Accounting_Quickbooks_Online_Export_Company_Card_Expense',
QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT: 'Workspace_Accounting_Quickbooks_Online_Export_Company_Card_Expense_Account_Select',
- QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_PAYABLE_SELECT: 'Workspace_Accounting_Quickbooks_Online_Export_Company_Card_Expense_Account_Payable_Select',
QUICKBOOKS_ONLINE_NON_REIMBURSABLE_DEFAULT_VENDOR_SELECT: 'Workspace_Accounting_Quickbooks_Online_Export_Non_Reimbursable_Default_Vendor_Select',
QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_COMPANY_CARD_SELECT: 'Workspace_Accounting_Quickbooks_Online_Export_Company_Card_Expense_Select',
QUICKBOOKS_ONLINE_EXPORT_PREFERRED_EXPORTER: 'Workspace_Accounting_Quickbooks_Online_Export_Preferred_Exporter',
diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx
index da8e3694a7d2..0c8af3dfc826 100644
--- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx
+++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx
@@ -52,8 +52,7 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow
}}
onPressIn={onPressIn}
onPressOut={onPressOut}
- // @ts-expect-error TODO: Remove this once ShowContextMenuContext (https://github.com/Expensify/App/issues/24980) is migrated to TypeScript.
- onLongPress={(event) => showContextMenuForReport(event, anchor, report.reportID, action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))}
+ onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))}
shouldUseHapticsOnLongPress
accessibilityLabel={displayName}
role={CONST.ROLE.BUTTON}
@@ -63,6 +62,7 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow
file={{name: displayName}}
shouldShowDownloadIcon={!isOffline}
shouldShowLoadingSpinnerIcon={isDownloading}
+ isUsedAsChatAttachment
/>
)}
diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.tsx b/src/components/Attachments/AttachmentCarousel/CarouselItem.tsx
index ec2687d634bb..2ec1883fd7de 100644
--- a/src/components/Attachments/AttachmentCarousel/CarouselItem.tsx
+++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.tsx
@@ -71,7 +71,7 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}: CarouselItemPr
return (
-
+
);
}
diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx
index e16a81702f66..e8f6568f98c0 100644
--- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx
+++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx
@@ -2,7 +2,7 @@ import React, {memo} from 'react';
import PDFView from '@components/PDFView';
import type AttachmentViewPdfProps from './types';
-function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style}: AttachmentViewPdfProps) {
+function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, isUsedAsChatAttachment, onLoadError}: AttachmentViewPdfProps) {
return (
);
}
diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts
index 605be3b25ec4..c20593449d58 100644
--- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts
+++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts
@@ -1,4 +1,4 @@
-import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
+import type {StyleProp, ViewStyle} from 'react-native';
import type {AttachmentViewProps} from '..';
type AttachmentViewPdfProps = Pick & {
@@ -8,11 +8,14 @@ type AttachmentViewPdfProps = Pick;
- /** Styles for the error label */
- errorLabelStyles?: StyleProp;
-
/** Triggered when the PDF's onScaleChanged event is triggered */
onScaleChanged?: (scale: number) => void;
+
+ /** Triggered when the PDF fails to load */
+ onLoadError?: () => void;
+
+ /** Whether the PDF is used as a chat attachment */
+ isUsedAsChatAttachment?: boolean;
};
export default AttachmentViewPdfProps;
diff --git a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx
new file mode 100644
index 000000000000..e06ea3064150
--- /dev/null
+++ b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx
@@ -0,0 +1,67 @@
+import React from 'react';
+import type {StyleProp, ViewStyle} from 'react-native';
+import {ActivityIndicator, View} from 'react-native';
+import Icon from '@components/Icon';
+import * as Expensicons from '@components/Icon/Expensicons';
+import Text from '@components/Text';
+import Tooltip from '@components/Tooltip';
+import useLocalize from '@hooks/useLocalize';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+
+type DefaultAttachmentViewProps = {
+ /** The name of the file */
+ fileName?: string;
+
+ /** Should show the download icon */
+ shouldShowDownloadIcon?: boolean;
+
+ /** Should show the loading spinner icon */
+ shouldShowLoadingSpinnerIcon?: boolean;
+
+ /** Additional styles for the container */
+ containerStyles?: StyleProp;
+};
+
+function DefaultAttachmentView({fileName = '', shouldShowLoadingSpinnerIcon = false, shouldShowDownloadIcon, containerStyles}: DefaultAttachmentViewProps) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+
+ return (
+
+
+
+
+
+ {fileName}
+ {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && (
+
+
+
+
+
+ )}
+ {shouldShowLoadingSpinnerIcon && (
+
+
+
+
+
+ )}
+
+ );
+}
+
+DefaultAttachmentView.displayName = 'DefaultAttachmentView';
+
+export default DefaultAttachmentView;
diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx
index 2685a5cef407..cee2264894a7 100644
--- a/src/components/Attachments/AttachmentView/index.tsx
+++ b/src/components/Attachments/AttachmentView/index.tsx
@@ -1,17 +1,14 @@
import Str from 'expensify-common/lib/str';
import React, {memo, useEffect, useState} from 'react';
import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native';
-import {ActivityIndicator, View} from 'react-native';
+import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import type {Attachment, AttachmentSource} from '@components/Attachments/types';
import DistanceEReceipt from '@components/DistanceEReceipt';
import EReceipt from '@components/EReceipt';
import Icon from '@components/Icon';
-import * as Expensicons from '@components/Icon/Expensicons';
import ScrollView from '@components/ScrollView';
-import Text from '@components/Text';
-import Tooltip from '@components/Tooltip';
import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
@@ -28,6 +25,7 @@ import type {Transaction} from '@src/types/onyx';
import AttachmentViewImage from './AttachmentViewImage';
import AttachmentViewPdf from './AttachmentViewPdf';
import AttachmentViewVideo from './AttachmentViewVideo';
+import DefaultAttachmentView from './DefaultAttachmentView';
type AttachmentViewOnyxProps = {
transaction: OnyxEntry;
@@ -64,9 +62,14 @@ type AttachmentViewProps = AttachmentViewOnyxProps &
/** Denotes whether it is an icon (ex: SVG) */
maybeIcon?: boolean;
+ /** Fallback source to use in case of error */
fallbackSource?: AttachmentSource;
+ /* Whether it is hovered or not */
isHovered?: boolean;
+
+ /** Whether the attachment is used as a chat attachment */
+ isUsedAsChatAttachment?: boolean;
};
function AttachmentView({
@@ -88,6 +91,7 @@ function AttachmentView({
reportActionID,
isHovered,
duration,
+ isUsedAsChatAttachment,
}: AttachmentViewProps) {
const {translate} = useLocalize();
const {updateCurrentlyPlayingURL} = usePlaybackContext();
@@ -95,6 +99,7 @@ function AttachmentView({
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const [loadComplete, setLoadComplete] = useState(false);
+ const [hasPDFFailedToLoad, setHasPDFFailedToLoad] = useState(false);
const isVideo = (typeof source === 'string' && Str.isVideo(source)) || (file?.name && Str.isVideo(file.name));
useEffect(() => {
@@ -145,7 +150,9 @@ function AttachmentView({
// Check both source and file.name since PDFs dragged into the text field
// will appear with a source that is a blob
- if ((typeof source === 'string' && Str.isPDF(source)) || (file && Str.isPDF(file.name ?? translate('attachmentView.unknownFilename')))) {
+ const isSourcePDF = typeof source === 'string' && Str.isPDF(source);
+ const isFilePDF = file && Str.isPDF(file.name ?? translate('attachmentView.unknownFilename'));
+ if (!hasPDFFailedToLoad && (isSourcePDF || isFilePDF)) {
const encryptedSourceUrl = isAuthTokenRequired ? addEncryptedAuthTokenToURL(source as string) : (source as string);
const onPDFLoadComplete = (path: string) => {
@@ -158,6 +165,10 @@ function AttachmentView({
}
};
+ const onPDFLoadError = () => {
+ setHasPDFFailedToLoad(true);
+ };
+
// We need the following View component on android native
// So that the event will propagate properly and
// the Password protected preview will be shown for pdf attachement we are about to send.
@@ -170,8 +181,9 @@ function AttachmentView({
onPress={onPress}
onToggleKeyboard={onToggleKeyboard}
onLoadComplete={onPDFLoadComplete}
- errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [styles.cursorAuto]}
style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1}
+ isUsedAsChatAttachment={isUsedAsChatAttachment}
+ onLoadError={onPDFLoadError}
/>
);
@@ -228,36 +240,12 @@ function AttachmentView({
}
return (
-
-
-
-
-
- {file?.name}
- {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && (
-
-
-
-
-
- )}
- {shouldShowLoadingSpinnerIcon && (
-
-
-
-
-
- )}
-
+
);
}
diff --git a/src/components/CategoryPicker.tsx b/src/components/CategoryPicker.tsx
index 40ebebdd0488..e5c85a8f5f6d 100644
--- a/src/components/CategoryPicker.tsx
+++ b/src/components/CategoryPicker.tsx
@@ -45,6 +45,7 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC
}, [selectedCategory]);
const [sections, headerMessage, shouldShowTextInput] = useMemo(() => {
+ const categories = policyCategories ?? policyCategoriesDraft ?? {};
const validPolicyRecentlyUsedCategories = policyRecentlyUsedCategories?.filter?.((p) => !isEmptyObject(p));
const {categoryOptions} = OptionsListUtils.getFilteredOptions(
[],
@@ -56,15 +57,15 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC
false,
false,
true,
- policyCategories ?? policyCategoriesDraft ?? {},
+ categories,
validPolicyRecentlyUsedCategories,
false,
);
const categoryData = categoryOptions?.[0]?.data ?? [];
const header = OptionsListUtils.getHeaderMessageForNonUserList(categoryData.length > 0, debouncedSearchValue);
- const policiesCount = OptionsListUtils.getEnabledCategoriesCount(policyCategories ?? {});
- const isCategoriesCountBelowThreshold = policiesCount < CONST.CATEGORY_LIST_THRESHOLD;
+ const categoriesCount = OptionsListUtils.getEnabledCategoriesCount(categories);
+ const isCategoriesCountBelowThreshold = categoriesCount < CONST.CATEGORY_LIST_THRESHOLD;
const showInput = !isCategoriesCountBelowThreshold;
return [categoryOptions, header, showInput];
diff --git a/src/components/Composer/index.native.tsx b/src/components/Composer/index.native.tsx
index 4d135cdd88e2..ac7eb95191ee 100644
--- a/src/components/Composer/index.native.tsx
+++ b/src/components/Composer/index.native.tsx
@@ -27,6 +27,7 @@ function Composer(
// On Android the selection prop is required on the TextInput but this prop has issues on IOS
selection,
value,
+ isGroupPolicyReport = false,
...props
}: ComposerProps,
ref: ForwardedRef,
@@ -34,7 +35,7 @@ function Composer(
const textInput = useRef(null);
const {isFocused, shouldResetFocus} = useResetComposerFocus(textInput);
const theme = useTheme();
- const markdownStyle = useMarkdownStyle(value);
+ const markdownStyle = useMarkdownStyle(value, !isGroupPolicyReport ? ['mentionReport'] : []);
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx
index 4bc54d13b056..f7bf277050a2 100755
--- a/src/components/Composer/index.tsx
+++ b/src/components/Composer/index.tsx
@@ -70,13 +70,14 @@ function Composer(
isReportActionCompose = false,
isComposerFullSize = false,
shouldContainScroll = false,
+ isGroupPolicyReport = false,
...props
}: ComposerProps,
ref: ForwardedRef,
) {
const theme = useTheme();
const styles = useThemeStyles();
- const markdownStyle = useMarkdownStyle(value);
+ const markdownStyle = useMarkdownStyle(value, !isGroupPolicyReport ? ['mentionReport'] : []);
const StyleUtils = useStyleUtils();
const textRef = useRef(null);
const textInput = useRef(null);
diff --git a/src/components/Composer/types.ts b/src/components/Composer/types.ts
index 531bcd03f8bf..0ff91111bd07 100644
--- a/src/components/Composer/types.ts
+++ b/src/components/Composer/types.ts
@@ -70,6 +70,9 @@ type ComposerProps = TextInputProps & {
/** Should make the input only scroll inside the element avoid scroll out to parent */
shouldContainScroll?: boolean;
+
+ /** Indicates whether the composer is in a group policy report. Used for disabling report mentioning style in markdown input */
+ isGroupPolicyReport?: boolean;
};
export type {TextSelection, ComposerProps};
diff --git a/src/components/InvertedFlatList/BaseInvertedFlatList/index.tsx b/src/components/InvertedFlatList/BaseInvertedFlatList/index.tsx
index 54f6748986f4..5c48652f8cc5 100644
--- a/src/components/InvertedFlatList/BaseInvertedFlatList/index.tsx
+++ b/src/components/InvertedFlatList/BaseInvertedFlatList/index.tsx
@@ -7,7 +7,7 @@ type BaseInvertedFlatListProps = FlatListProps & {
shouldEnableAutoScrollToTopThreshold?: boolean;
};
-const AUTOSCROLL_TO_TOP_THRESHOLD = 128;
+const AUTOSCROLL_TO_TOP_THRESHOLD = 250;
function BaseInvertedFlatList(props: BaseInvertedFlatListProps, ref: ForwardedRef) {
const {shouldEnableAutoScrollToTopThreshold, ...rest} = props;
diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx
index b665c305f5cf..6bb900ecb489 100644
--- a/src/components/LHNOptionsList/OptionRowLHN.tsx
+++ b/src/components/LHNOptionsList/OptionRowLHN.tsx
@@ -71,7 +71,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti
const textUnreadStyle = optionItem?.isUnread && optionItem.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE ? [textStyle, styles.sidebarLinkTextBold] : [textStyle];
const displayNameStyle = [styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, textUnreadStyle, style];
const alternateTextStyle = isInFocusMode
- ? [textStyle, styles.optionAlternateText, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2, style]
+ ? [textStyle, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2, style]
: [textStyle, styles.optionAlternateText, styles.textLabelSupporting, style];
const contentContainerStyles = isInFocusMode ? [styles.flex1, styles.flexRow, styles.overflowHidden, StyleUtils.getCompactContentContainerStyles()] : [styles.flex1];
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index e1a998f78cab..4eb3be871a8d 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -3,12 +3,15 @@ import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
+import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as HeaderUtils from '@libs/HeaderUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
+import * as TransactionUtils from '@libs/TransactionUtils';
+import variables from '@styles/variables';
import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -19,8 +22,10 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
import Button from './Button';
import ConfirmModal from './ConfirmModal';
import HeaderWithBackButton from './HeaderWithBackButton';
+import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar';
+import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar';
import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu';
import SettlementButton from './SettlementButton';
@@ -71,6 +76,7 @@ function MoneyReportHeader({
onBackButtonPress,
}: MoneyReportHeaderProps) {
const styles = useThemeStyles();
+ const theme = useTheme();
const [isDeleteRequestModalVisible, setIsDeleteRequestModalVisible] = useState(false);
const {translate} = useLocalize();
const {windowWidth} = useWindowDimensions();
@@ -98,6 +104,9 @@ function MoneyReportHeader({
const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport);
const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false);
+ const transactionIDs = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID).map((transaction) => transaction.transactionID);
+ const allHavePendingRTERViolation = TransactionUtils.allHavePendingRTERViolation(transactionIDs);
+
const cancelPayment = useCallback(() => {
if (!chatReport) {
return;
@@ -112,12 +121,12 @@ function MoneyReportHeader({
const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(moneyRequestReport);
- const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton);
+ const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton) && !allHavePendingRTERViolation;
- const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0;
+ const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !allHavePendingRTERViolation;
const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport);
const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE;
- const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length;
+ const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !allHavePendingRTERViolation;
const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep;
const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport);
const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency);
@@ -203,7 +212,7 @@ function MoneyReportHeader({
shouldShowBackButton={shouldUseNarrowLayout}
onBackButtonPress={onBackButtonPress}
// Shows border if no buttons or next steps are showing below the header
- shouldShowBorderBottom={!(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout)}
+ shouldShowBorderBottom={!(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout) && !allHavePendingRTERViolation}
shouldShowThreeDotsButton
threeDotsMenuItems={threeDotsMenuItems}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)}
@@ -241,6 +250,20 @@ function MoneyReportHeader({
)}
+ {allHavePendingRTERViolation && (
+
+ }
+ description={translate('iou.pendingMatchWithCreditCardDescription')}
+ shouldShowBorderBottom
+ />
+ )}
{shouldShowSettlementButton && shouldUseNarrowLayout && (
diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx
index a59b50e5bdb7..9acd73beadba 100644
--- a/src/components/MoneyRequestAmountInput.tsx
+++ b/src/components/MoneyRequestAmountInput.tsx
@@ -285,6 +285,7 @@ function MoneyRequestAmountInput(
touchableInputWrapperStyle={props.touchableInputWrapperStyle}
maxLength={maxLength}
hideFocusedState={hideFocusedState}
+ onMouseDown={(event) => event.stopPropagation()}
/>
);
}
diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx
index 65c9d800607d..7c122f120e8a 100644
--- a/src/components/MoneyRequestHeader.tsx
+++ b/src/components/MoneyRequestHeader.tsx
@@ -1,7 +1,8 @@
+import type {ReactNode} from 'react';
import React, {useCallback, useEffect, useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
-import type {OnyxEntry} from 'react-native-onyx';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -16,12 +17,14 @@ import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import type {Policy, Report, ReportAction, ReportActions, Session, Transaction} from '@src/types/onyx';
+import type {Policy, Report, ReportAction, ReportActions, Session, Transaction, TransactionViolations} from '@src/types/onyx';
import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage';
+import type IconAsset from '@src/types/utils/IconAsset';
import ConfirmModal from './ConfirmModal';
import HeaderWithBackButton from './HeaderWithBackButton';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
+import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar';
import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar';
import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu';
@@ -35,6 +38,9 @@ type MoneyRequestHeaderOnyxProps = {
/** All the data for the transaction */
transaction: OnyxEntry;
+ /** The violations of the transaction */
+ transactionViolations: OnyxCollection;
+
/** All report actions */
// eslint-disable-next-line react/no-unused-prop-types
parentReportActions: OnyxEntry;
@@ -65,6 +71,7 @@ function MoneyRequestHeader({
parentReport,
report,
parentReportAction,
+ transactionViolations,
transaction,
shownHoldUseExplanation = false,
policy,
@@ -101,7 +108,6 @@ function MoneyRequestHeader({
}, [parentReport?.reportID, parentReportAction, setIsDeleteModalVisible]);
const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction);
- const isPending = TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction);
const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction);
const canHoldOrUnholdRequest = !isSettled && !isApproved && !isDeletedParentAction;
@@ -120,6 +126,33 @@ function MoneyRequestHeader({
}
};
+ const getStatusIcon: (src: IconAsset) => ReactNode = (src) => (
+
+ );
+
+ const getStatusBarProps: () => MoneyRequestHeaderStatusBarProps | undefined = () => {
+ if (isOnHold) {
+ return {title: translate('iou.hold'), description: translate('iou.expenseOnHold'), danger: true, shouldShowBorderBottom: true};
+ }
+
+ if (TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction)) {
+ return {title: getStatusIcon(Expensicons.CreditCardHourglass), description: translate('iou.transactionPendingDescription'), shouldShowBorderBottom: true};
+ }
+ if (isScanning) {
+ return {title: getStatusIcon(Expensicons.ReceiptScan), description: translate('iou.receiptScanInProgressDescription'), shouldShowBorderBottom: true};
+ }
+ if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) {
+ return {title: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription'), shouldShowBorderBottom: true};
+ }
+ };
+
+ const statusBarProps = getStatusBarProps();
+
useEffect(() => {
if (canDeleteRequest) {
return;
@@ -184,7 +217,7 @@ function MoneyRequestHeader({
<>
- {isPending && (
-
- }
- description={translate('iou.transactionPendingDescription')}
- shouldShowBorderBottom={!isScanning}
- />
- )}
- {isScanning && (
+ {statusBarProps && (
- }
- description={translate('iou.receiptScanInProgressDescription')}
- shouldShowBorderBottom
- />
- )}
- {isOnHold && (
-
)}
@@ -259,7 +264,7 @@ function MoneyRequestHeader({
MoneyRequestHeader.displayName = 'MoneyRequestHeader';
-const MoneyRequestHeaderWithTransaction = withOnyx>({
+const MoneyRequestHeaderWithTransaction = withOnyx>({
transaction: {
key: ({report, parentReportActions}) => {
const parentReportAction = (report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : {}) as ReportAction & OriginalMessageIOU;
@@ -270,9 +275,15 @@ const MoneyRequestHeaderWithTransaction = withOnyx, Omit>({
+export default withOnyx<
+ Omit,
+ Omit
+>({
session: {
key: ONYXKEYS.SESSION,
},
diff --git a/src/components/MoneyRequestHeaderStatusBar.tsx b/src/components/MoneyRequestHeaderStatusBar.tsx
index b7bba0223656..4ee3079d5f1f 100644
--- a/src/components/MoneyRequestHeaderStatusBar.tsx
+++ b/src/components/MoneyRequestHeaderStatusBar.tsx
@@ -57,3 +57,5 @@ function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom
MoneyRequestHeaderStatusBar.displayName = 'MoneyRequestHeaderStatusBar';
export default MoneyRequestHeaderStatusBar;
+
+export type {MoneyRequestHeaderStatusBarProps};
diff --git a/src/components/OptionListContextProvider.tsx b/src/components/OptionListContextProvider.tsx
index 16bc9aef21eb..c9bf5d7b9db2 100644
--- a/src/components/OptionListContextProvider.tsx
+++ b/src/components/OptionListContextProvider.tsx
@@ -64,7 +64,7 @@ function OptionsListContextProvider({reports, children}: OptionsListProviderProp
return;
}
- const newReports = OptionsListUtils.createOptionList({}, reports).reports;
+ const newReports = OptionsListUtils.createOptionList(personalDetails, reports).reports;
setOptions((prevOptions) => {
const newOptions = {...prevOptions};
diff --git a/src/components/PDFThumbnail/index.tsx b/src/components/PDFThumbnail/index.tsx
index a5b911deb6ff..ce631f3b611f 100644
--- a/src/components/PDFThumbnail/index.tsx
+++ b/src/components/PDFThumbnail/index.tsx
@@ -1,4 +1,3 @@
-// @ts-expect-error - This line imports a module from 'pdfjs-dist' package which lacks TypeScript typings.
import pdfWorkerSource from 'pdfjs-dist/legacy/build/pdf.worker';
import React, {useMemo} from 'react';
import {View} from 'react-native';
diff --git a/src/components/PDFView/index.native.tsx b/src/components/PDFView/index.native.tsx
index f0edba5de7d0..7d523ef13805 100644
--- a/src/components/PDFView/index.native.tsx
+++ b/src/components/PDFView/index.native.tsx
@@ -5,7 +5,6 @@ import PDF from 'react-native-pdf';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import KeyboardAvoidingView from '@components/KeyboardAvoidingView';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
-import Text from '@components/Text';
import useKeyboardState from '@hooks/useKeyboardState';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
@@ -29,7 +28,11 @@ import type {PDFViewNativeProps} from './types';
* so that PDFPasswordForm doesn't bounce when react-native-pdf/PDF
* is (temporarily) rendered.
*/
-function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles}: PDFViewNativeProps) {
+
+const LOADING_THUMBNAIL_HEIGHT = 250;
+const LOADING_THUMBNAIL_WIDTH = 250;
+
+function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, onLoadError, isUsedAsChatAttachment}: PDFViewNativeProps) {
const [shouldRequestPassword, setShouldRequestPassword] = useState(false);
const [shouldAttemptPDFLoad, setShouldAttemptPDFLoad] = useState(true);
const [shouldShowLoadingIndicator, setShouldShowLoadingIndicator] = useState(true);
@@ -80,6 +83,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused
setShouldShowLoadingIndicator(false);
setShouldRequestPassword(false);
setShouldAttemptPDFLoad(false);
+ onLoadError?.();
// eslint-disable-next-line @typescript-eslint/ban-types
}) as (error: object) => void;
@@ -112,7 +116,9 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused
};
function renderPDFView() {
- const pdfStyles: StyleProp = [themeStyles.imageModalPDF, StyleUtils.getWidthAndHeightStyle(windowWidth, windowHeight)];
+ const pdfWidth = isUsedAsChatAttachment ? LOADING_THUMBNAIL_WIDTH : windowWidth;
+ const pdfHeight = isUsedAsChatAttachment ? LOADING_THUMBNAIL_HEIGHT : windowHeight;
+ const pdfStyles: StyleProp = [themeStyles.imageModalPDF, StyleUtils.getWidthAndHeightStyle(pdfWidth, pdfHeight)];
// If we haven't yet successfully validated the password and loaded the PDF,
// then we need to hide the react-native-pdf/PDF component so that PDFPasswordForm
@@ -121,21 +127,20 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused
if (shouldRequestPassword) {
pdfStyles.push(themeStyles.invisible);
}
-
- const containerStyles = shouldRequestPassword && isSmallScreenWidth ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1];
+ const containerStyles =
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ isUsedAsChatAttachment || (shouldRequestPassword && isSmallScreenWidth) ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1];
+ const loadingIndicatorStyles = isUsedAsChatAttachment
+ ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT)]
+ : [];
return (
- {failedToLoadPDF && (
-
- {translate('attachmentView.failedToLoadPDF')}
-
- )}
{shouldAttemptPDFLoad && (
}
+ renderActivityIndicator={() => }
source={{uri: sourceURL, cache: true, expiration: 864000}}
style={pdfStyles}
onError={handleFailureToLoadPDF}
diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx
index 99b5be1c8f53..76daba9ce0f6 100644
--- a/src/components/PDFView/index.tsx
+++ b/src/components/PDFView/index.tsx
@@ -7,9 +7,9 @@ import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
-import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import usePrevious from '@hooks/usePrevious';
+import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import variables from '@styles/variables';
@@ -19,9 +19,13 @@ import ONYXKEYS from '@src/ONYXKEYS';
import PDFPasswordForm from './PDFPasswordForm';
import type {PDFViewOnyxProps, PDFViewProps} from './types';
-function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, errorLabelStyles, maxCanvasArea, maxCanvasHeight, maxCanvasWidth, style}: PDFViewProps) {
+const LOADING_THUMBNAIL_HEIGHT = 250;
+const LOADING_THUMBNAIL_WIDTH = 250;
+
+function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, maxCanvasArea, maxCanvasHeight, maxCanvasWidth, style, isUsedAsChatAttachment, onLoadError}: PDFViewProps) {
const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
const styles = useThemeStyles();
+ const StyleUtils = useStyleUtils();
const {windowHeight, isSmallScreenWidth} = useWindowDimensions();
const prevWindowHeight = usePrevious(windowHeight);
const {translate} = useLocalize();
@@ -95,8 +99,19 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, err
maxCanvasWidth={maxCanvasWidth}
maxCanvasHeight={maxCanvasHeight}
maxCanvasArea={maxCanvasArea}
- LoadingComponent={}
- ErrorComponent={{translate('attachmentView.failedToLoadPDF')}}
+ LoadingComponent={
+
+ }
+ shouldShowErrorComponent={false}
+ onLoadError={onLoadError}
renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => (
;
+ /** Callback to call when the PDF fails to load */
+ onLoadError?: () => void;
+
+ /** Whether the PDF is used as a chat attachment */
+ isUsedAsChatAttachment?: boolean;
};
type PDFViewOnyxProps = {
diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx
index 45893de809df..4ff318bc3c47 100644
--- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx
+++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx
@@ -36,7 +36,7 @@ import CONST from '@src/CONST';
import type {IOUMessage} from '@src/types/onyx/OriginalMessage';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
-import type {MoneyRequestPreviewProps} from './types';
+import type {MoneyRequestPreviewProps, PendingMessageProps} from './types';
function MoneyRequestPreviewContent({
iouReport,
@@ -84,7 +84,6 @@ function MoneyRequestPreviewContent({
const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH});
const hasReceipt = TransactionUtils.hasReceipt(transaction);
const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction);
- const isPending = TransactionUtils.isPending(transaction);
const isOnHold = TransactionUtils.isOnHold(transaction);
const isSettlementOrApprovalPartial = Boolean(iouReport?.pendingFields?.partial);
const isPartialHold = isSettlementOrApprovalPartial && isOnHold;
@@ -184,6 +183,21 @@ function MoneyRequestPreviewContent({
return message;
};
+ const getPendingMessageProps: () => PendingMessageProps = () => {
+ if (isScanning) {
+ return {shouldShow: true, messageIcon: ReceiptScan, messageDescription: translate('iou.receiptScanInProgress')};
+ }
+ if (TransactionUtils.isPending(transaction)) {
+ return {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')};
+ }
+ if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) {
+ return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')};
+ }
+ return {shouldShow: false};
+ };
+
+ const pendingMessageProps = getPendingMessageProps();
+
const getDisplayAmountText = (): string => {
if (isScanning) {
return translate('iou.receiptScanning');
@@ -312,26 +326,15 @@ function MoneyRequestPreviewContent({
)}
- {isScanning && (
-
-
- {translate('iou.receiptScanInProgress')}
-
- )}
- {isPending && (
+ {pendingMessageProps.shouldShow && (
- {translate('iou.transactionPending')}
+ {pendingMessageProps.messageDescription}
)}
diff --git a/src/components/ReportActionItem/MoneyRequestPreview/types.ts b/src/components/ReportActionItem/MoneyRequestPreview/types.ts
index 0e3eb37ce6e3..9dcea80fdc05 100644
--- a/src/components/ReportActionItem/MoneyRequestPreview/types.ts
+++ b/src/components/ReportActionItem/MoneyRequestPreview/types.ts
@@ -2,6 +2,7 @@ import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
import type * as OnyxTypes from '@src/types/onyx';
+import type IconAsset from '@src/types/utils/IconAsset';
type MoneyRequestPreviewOnyxProps = {
/** All of the personal details for everyone */
@@ -71,4 +72,19 @@ type MoneyRequestPreviewProps = MoneyRequestPreviewOnyxProps & {
isWhisper?: boolean;
};
-export type {MoneyRequestPreviewProps, MoneyRequestPreviewOnyxProps};
+type NoPendingProps = {shouldShow: false};
+
+type PendingProps = {
+ /** Whether to show the pending message or not */
+ shouldShow: true;
+
+ /** The icon to be displayed if a request is pending */
+ messageIcon: IconAsset;
+
+ /** The description to be displayed if a request is pending */
+ messageDescription: string;
+};
+
+type PendingMessageProps = PendingProps | NoPendingProps;
+
+export type {MoneyRequestPreviewProps, MoneyRequestPreviewOnyxProps, PendingMessageProps};
diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 013e8e8e47e7..0380780e0ccb 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -335,7 +335,7 @@ function MoneyRequestView({
{shouldShowMapOrReceipt && (
{
if (!transaction?.transactionID) {
diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx
index 9c5821329e1c..dae827cdb5c0 100644
--- a/src/components/ReportActionItem/ReportPreview.tsx
+++ b/src/components/ReportActionItem/ReportPreview.tsx
@@ -32,6 +32,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Policy, Report, ReportAction, Transaction, TransactionViolations, UserWallet} from '@src/types/onyx';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
+import type {PendingMessageProps} from './MoneyRequestPreview/types';
import ReportActionItemImages from './ReportActionItemImages';
type ReportPreviewOnyxProps = {
@@ -140,6 +141,8 @@ function ReportPreview({
const hasErrors = hasMissingSmartscanFields || (canUseViolations && ReportUtils.hasViolations(iouReportID, transactionViolations)) || ReportUtils.hasActionsWithErrors(iouReportID);
const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3);
const lastThreeReceipts = lastThreeTransactionsWithReceipts.map((transaction) => ReceiptUtils.getThumbnailAndImageURIs(transaction));
+ const showRTERViolationMessage =
+ numberOfRequests === 1 && TransactionUtils.hasPendingUI(allTransactions[0], TransactionUtils.getTransactionViolations(allTransactions[0].transactionID, transactionViolations));
let formattedMerchant = numberOfRequests === 1 ? TransactionUtils.getMerchant(allTransactions[0]) : null;
const formattedDescription = numberOfRequests === 1 ? TransactionUtils.getDescription(allTransactions[0]) : null;
@@ -148,7 +151,7 @@ function ReportPreview({
formattedMerchant = null;
}
- const shouldShowSubmitButton = isOpenExpenseReport && reimbursableSpend !== 0;
+ const shouldShowSubmitButton = isOpenExpenseReport && reimbursableSpend !== 0 && !showRTERViolationMessage;
const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(iouReport);
// The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on
@@ -210,7 +213,7 @@ function ReportPreview({
const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(iouReport);
- const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(iouReport) && (shouldShowPayButton || shouldShowApproveButton);
+ const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(iouReport) && (shouldShowPayButton || shouldShowApproveButton) && !showRTERViolationMessage;
const shouldPromptUserToAddBankAccount = ReportUtils.hasMissingPaymentMethod(userWallet, iouReportID);
const shouldShowRBR = !iouSettled && hasErrors;
@@ -229,6 +232,21 @@ function ReportPreview({
const shouldShowScanningSubtitle = numberOfScanningReceipts === 1 && numberOfRequests === 1;
const shouldShowPendingSubtitle = numberOfPendingRequests === 1 && numberOfRequests === 1;
+ const getPendingMessageProps: () => PendingMessageProps = () => {
+ if (shouldShowScanningSubtitle) {
+ return {shouldShow: true, messageIcon: Expensicons.ReceiptScan, messageDescription: translate('iou.receiptScanInProgress')};
+ }
+ if (shouldShowPendingSubtitle) {
+ return {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')};
+ }
+ if (showRTERViolationMessage) {
+ return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')};
+ }
+ return {shouldShow: false};
+ };
+
+ const pendingMessageProps = getPendingMessageProps();
+
const {supportText} = useMemo(() => {
if (formattedMerchant) {
return {supportText: formattedMerchant};
@@ -314,26 +332,15 @@ function ReportPreview({
)}
- {shouldShowScanningSubtitle && (
-
-
- {translate('iou.receiptScanInProgress')}
-
- )}
- {shouldShowPendingSubtitle && (
-
+ {pendingMessageProps.shouldShow && (
+
- {translate('iou.transactionPending')}
+ {pendingMessageProps.messageDescription}
)}
diff --git a/src/components/TabSelector/TabSelector.tsx b/src/components/TabSelector/TabSelector.tsx
index 9fe1665f6b19..882d99f38e3d 100644
--- a/src/components/TabSelector/TabSelector.tsx
+++ b/src/components/TabSelector/TabSelector.tsx
@@ -68,7 +68,7 @@ function TabSelector({state, navigation, onTabPress = () => {}, position}: TabSe
return position.interpolate({
inputRange,
outputRange: inputRange.map((i) => (affectedTabs.includes(tabIndex) && i === tabIndex ? theme.border : theme.appBG)),
- });
+ }) as unknown as Animated.AnimatedInterpolation;
}
return theme.border;
},
diff --git a/src/components/TextInputWithCurrencySymbol/types.ts b/src/components/TextInputWithCurrencySymbol/types.ts
index b31f27aef786..27c8498f253c 100644
--- a/src/components/TextInputWithCurrencySymbol/types.ts
+++ b/src/components/TextInputWithCurrencySymbol/types.ts
@@ -32,6 +32,11 @@ type TextInputWithCurrencySymbolProps = {
*/
onBlur?: ((e: NativeSyntheticEvent) => void) | undefined;
+ /**
+ * Callback that is called when the text input is pressed down
+ */
+ onMouseDown?: ((e: React.MouseEvent) => void) | undefined;
+
/** Whether the currency symbol is pressable */
isCurrencyPressable: boolean;
diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx
index 462911968000..fe343b900724 100644
--- a/src/components/VideoPlayer/BaseVideoPlayer.tsx
+++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx
@@ -11,6 +11,7 @@ import Hoverable from '@components/Hoverable';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext';
import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
+import type {PlaybackSpeed} from '@components/VideoPlayerContexts/types';
import {useVideoPopoverMenuContext} from '@components/VideoPlayerContexts/VideoPopoverMenuContext';
import {useVolumeContext} from '@components/VideoPlayerContexts/VolumeContext';
import VideoPopoverMenu from '@components/VideoPopoverMenu';
@@ -82,7 +83,7 @@ function BaseVideoPlayer({
const isUploading = CONST.ATTACHMENT_LOCAL_URL_PREFIX.some((prefix) => url.startsWith(prefix));
const videoStateRef = useRef(null);
const {updateVolume} = useVolumeContext();
- const {videoPopoverMenuPlayerRef} = useVideoPopoverMenuContext();
+ const {videoPopoverMenuPlayerRef, setCurrentPlaybackSpeed} = useVideoPopoverMenuContext();
const togglePlayCurrentVideo = useCallback(() => {
videoResumeTryNumber.current = 0;
@@ -96,8 +97,14 @@ function BaseVideoPlayer({
}, [isCurrentlyURLSet, isPlaying, pauseVideo, playVideo, updateCurrentlyPlayingURL, url, videoResumeTryNumber]);
const showPopoverMenu = (event?: GestureResponderEvent | KeyboardEvent) => {
- setIsPopoverVisible(true);
videoPopoverMenuPlayerRef.current = videoPlayerRef.current;
+ videoPlayerRef.current?.getStatusAsync().then((status) => {
+ if (!('rate' in status && status.rate)) {
+ return;
+ }
+ setIsPopoverVisible(true);
+ setCurrentPlaybackSpeed(status.rate as PlaybackSpeed);
+ });
if (!event || !('nativeEvent' in event)) {
return;
}
diff --git a/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx b/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx
index 29156c438d3d..9742c613de2f 100644
--- a/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx
+++ b/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx
@@ -14,7 +14,7 @@ import type {PlaybackSpeed, VideoPopoverMenuContext} from './types';
const Context = React.createContext(null);
function VideoPopoverMenuContextProvider({children}: ChildrenProps) {
- const {currentVideoPlayerRef, currentlyPlayingURL} = usePlaybackContext();
+ const {currentlyPlayingURL} = usePlaybackContext();
const {translate} = useLocalize();
const [currentPlaybackSpeed, setCurrentPlaybackSpeed] = useState(CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS[2]);
const {isOffline} = useNetwork();
@@ -24,9 +24,9 @@ function VideoPopoverMenuContextProvider({children}: ChildrenProps) {
const updatePlaybackSpeed = useCallback(
(speed: PlaybackSpeed) => {
setCurrentPlaybackSpeed(speed);
- currentVideoPlayerRef.current?.setStatusAsync?.({rate: speed});
+ videoPopoverMenuPlayerRef.current?.setStatusAsync?.({rate: speed});
},
- [currentVideoPlayerRef],
+ [videoPopoverMenuPlayerRef],
);
const downloadAttachment = useCallback(() => {
@@ -69,7 +69,10 @@ function VideoPopoverMenuContextProvider({children}: ChildrenProps) {
return items;
}, [currentPlaybackSpeed, downloadAttachment, translate, updatePlaybackSpeed, isOffline, isLocalFile]);
- const contextValue = useMemo(() => ({menuItems, videoPopoverMenuPlayerRef, updatePlaybackSpeed}), [menuItems, videoPopoverMenuPlayerRef, updatePlaybackSpeed]);
+ const contextValue = useMemo(
+ () => ({menuItems, videoPopoverMenuPlayerRef, updatePlaybackSpeed, setCurrentPlaybackSpeed}),
+ [menuItems, videoPopoverMenuPlayerRef, updatePlaybackSpeed, setCurrentPlaybackSpeed],
+ );
return {children};
}
diff --git a/src/components/VideoPlayerContexts/types.ts b/src/components/VideoPlayerContexts/types.ts
index ea09281d7676..6216fd5dc85b 100644
--- a/src/components/VideoPlayerContexts/types.ts
+++ b/src/components/VideoPlayerContexts/types.ts
@@ -30,6 +30,7 @@ type VideoPopoverMenuContext = {
menuItems: PopoverMenuItem[];
videoPopoverMenuPlayerRef: MutableRefObject;
updatePlaybackSpeed: (speed: PlaybackSpeed) => void;
+ setCurrentPlaybackSpeed: (speed: PlaybackSpeed) => void;
};
type FullScreenContext = {
diff --git a/src/hooks/useAutoFocusInput.ts b/src/hooks/useAutoFocusInput.ts
index e8e7c1187a63..c1f58901cee0 100644
--- a/src/hooks/useAutoFocusInput.ts
+++ b/src/hooks/useAutoFocusInput.ts
@@ -13,7 +13,6 @@ export default function useAutoFocusInput(): UseAutoFocusInput {
const [isInputInitialized, setIsInputInitialized] = useState(false);
const [isScreenTransitionEnded, setIsScreenTransitionEnded] = useState(false);
- // @ts-expect-error TODO: Remove this when Expensify.js is migrated.
const {isSplashHidden} = useContext(Expensify.SplashScreenHiddenContext);
const inputRef = useRef(null);
diff --git a/src/hooks/useMarkdownStyle.ts b/src/hooks/useMarkdownStyle.ts
index b1f430e232e4..57d17ffefb0b 100644
--- a/src/hooks/useMarkdownStyle.ts
+++ b/src/hooks/useMarkdownStyle.ts
@@ -5,12 +5,25 @@ import FontUtils from '@styles/utils/FontUtils';
import variables from '@styles/variables';
import useTheme from './useTheme';
-function useMarkdownStyle(message: string | null = null): MarkdownStyle {
+function useMarkdownStyle(message: string | null = null, excludeStyles: Array = []): MarkdownStyle {
const theme = useTheme();
const emojiFontSize = containsOnlyEmojis(message ?? '') ? variables.fontSizeOnlyEmojis : variables.fontSizeNormal;
- const markdownStyle = useMemo(
+ // this map is used to reset the styles that are not needed - passing undefined value can break the native side
+ const nonStylingDefaultValues: Record = useMemo(
() => ({
+ color: theme.text,
+ backgroundColor: 'transparent',
+ marginLeft: 0,
+ paddingLeft: 0,
+ borderColor: 'transparent',
+ borderWidth: 0,
+ }),
+ [theme],
+ );
+
+ const markdownStyle = useMemo(() => {
+ const styling = {
syntax: {
color: theme.syntax,
},
@@ -53,9 +66,21 @@ function useMarkdownStyle(message: string | null = null): MarkdownStyle {
color: theme.mentionText,
backgroundColor: theme.mentionBG,
},
- }),
- [theme, emojiFontSize],
- );
+ };
+
+ if (excludeStyles.length) {
+ excludeStyles.forEach((key) => {
+ const style: Record = styling[key];
+ if (style) {
+ Object.keys(style).forEach((styleKey) => {
+ style[styleKey] = nonStylingDefaultValues[styleKey] ?? style[styleKey];
+ });
+ }
+ });
+ }
+
+ return styling;
+ }, [theme, emojiFontSize, excludeStyles, nonStylingDefaultValues]);
return markdownStyle;
}
diff --git a/src/hooks/useTabNavigatorFocus/index.ts b/src/hooks/useTabNavigatorFocus/index.ts
index 3fef0e53774f..f160f4670b26 100644
--- a/src/hooks/useTabNavigatorFocus/index.ts
+++ b/src/hooks/useTabNavigatorFocus/index.ts
@@ -64,7 +64,6 @@ function useTabNavigatorFocus({tabIndex}: UseTabNavigatorFocusParams): boolean {
// We need to get the position animation value on component initialization to determine
// if the tab is focused or not. Since it's an Animated.Value the only synchronous way
// to retrieve the value is to use a private method.
- // @ts-expect-error -- __getValue is a private method
// eslint-disable-next-line no-underscore-dangle
const initialTabPositionValue = tabPositionAnimation.__getValue();
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 0f075fcca616..f8d122f3b69a 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -647,6 +647,9 @@ export default {
canceled: 'Canceled',
posted: 'Posted',
deleteReceipt: 'Delete receipt',
+ pendingMatchWithCreditCard: 'Receipt pending match with credit card.',
+ pendingMatchWithCreditCardDescription: 'Receipt pending match with credit card. Mark as cash to ignore and request payment.',
+ routePending: 'Route pending...',
receiptScanning: 'Receipt scanning...',
receiptScanInProgress: 'Receipt scan in progress.',
receiptScanInProgressDescription: 'Receipt scan in progress. Check back later or enter the details now.',
@@ -1295,7 +1298,7 @@ export default {
label: 'Light',
},
system: {
- label: 'Use Device Settings',
+ label: 'Use device settings',
},
},
chooseThemeBelowOrSync: 'Choose a theme below, or sync with your device settings.',
@@ -1931,6 +1934,8 @@ export default {
customersDescription: 'Choose whether to import customers/projects and see where customers/projects are displayed.',
locationsDescription: 'Choose whether to import locations, and see where locations are displayed.',
taxesDescription: 'Choose whether to import tax rates and tax defaults from your accounting integration.',
+ taxesJournalEntrySwitchNote:
+ 'Note: QuickBooks Online does not support a field for tax on Journal Entry exports. Change your export preference to Vendor Bill or Check to import taxes.',
locationsAdditionalDescription:
'Locations are imported as Tags. This limits exporting expense reports as Vendor Bills or Checks to QuickBooks Online. To unlock these export options, either disable Locations import or upgrade to the Control Plan to export Locations encoded as a Report Field.',
export: 'Export',
@@ -2974,7 +2979,7 @@ export default {
body: `Get paid to talk to your friends! Start a chat with a new Expensify account and get $${CONST.REFERRAL_PROGRAM.REVENUE} when they become a customer.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SUBMIT_EXPENSE]: {
- buttonText1: 'Submit expense, ',
+ buttonText1: 'Submit an expense, ',
buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}.`,
header: `Submit an expense, get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
body: `It pays to get paid! Submit an expense to a new Expensify account and get $${CONST.REFERRAL_PROGRAM.REVENUE} when they become a customer.`,
diff --git a/src/languages/es.ts b/src/languages/es.ts
index ce89a67175d2..536a6182f393 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -640,6 +640,9 @@ export default {
canceled: 'Canceló',
posted: 'Contabilizado',
deleteReceipt: 'Eliminar recibo',
+ pendingMatchWithCreditCard: 'Recibo pendiente de adjuntar con la tarjeta de crédito.',
+ pendingMatchWithCreditCardDescription: 'Recibo pendiente de adjuntar con tarjeta de crédito. Marca como efectivo para ignorar y solicitar pago.',
+ routePending: 'Ruta pendiente...',
receiptIssuesFound: (count: number) => `${count === 1 ? 'Problema encontrado' : 'Problemas encontrados'}`,
fieldPending: 'Pendiente...',
receiptScanning: 'Escaneando recibo...',
@@ -1955,6 +1958,8 @@ export default {
customersDescription: 'Elige si queres importar clientes/proyectos y donde los clientes/proyectos son mostrados.',
locationsDescription: 'Elige si quieres importar lugares y donde los lugares son mostrados.',
taxesDescription: 'Elige si quires importar las tasas de impuestos y los impuestos por defecto de tu integración de contadurÃa.',
+ taxesJournalEntrySwitchNote:
+ 'Nota: QuickBooks Online no admite un campo para impuestos al exportar entradas en el libro diario. Cambia tu preferencia de exportación a Factura de Proveedor o Cheque para importar impuestos.',
locationsAdditionalDescription:
'Los lugares son importados como Etiquegas. Esto limita a exportar los informes de gastos como Factura del Proveedor o Cheques a Quicbooks Online. Para desbloquear estas opciones de exportación desactiva la importación de Lugares o cambia al Plan Control para exportar Lugares como Campos de Informes.',
export: 'Exportar',
@@ -3475,7 +3480,7 @@ export default {
body: `¡Gana dinero por hablar con tus amigos! Inicia un chat con una cuenta nueva de Expensify y recibe $${CONST.REFERRAL_PROGRAM.REVENUE} cuando se conviertan en clientes.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SUBMIT_EXPENSE]: {
- buttonText1: 'Presentar gasto, ',
+ buttonText1: 'Presenta un gasto, ',
buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
header: `Presenta un gasto y consigue $${CONST.REFERRAL_PROGRAM.REVENUE}`,
body: `¡Vale la pena cobrar! Envia un gasto a una cuenta nueva de Expensify y recibe $${CONST.REFERRAL_PROGRAM.REVENUE} cuando se conviertan en clientes.`,
diff --git a/src/libs/API/parameters/UpdateManyPolicyConnectionConfigurationsParams.ts b/src/libs/API/parameters/UpdateManyPolicyConnectionConfigurationsParams.ts
new file mode 100644
index 000000000000..6496d3e94266
--- /dev/null
+++ b/src/libs/API/parameters/UpdateManyPolicyConnectionConfigurationsParams.ts
@@ -0,0 +1,8 @@
+type UpdateManyPolicyConnectionConfigurationsParams = {
+ policyID: string;
+ connectionName: string;
+ configUpdate: string;
+ idempotencyKey: string;
+};
+
+export default UpdateManyPolicyConnectionConfigurationsParams;
diff --git a/src/libs/API/parameters/UpdatePolicyConnectionConfigParams.ts b/src/libs/API/parameters/UpdatePolicyConnectionConfigParams.ts
index 2a5c6da51cec..111674b8ffb6 100644
--- a/src/libs/API/parameters/UpdatePolicyConnectionConfigParams.ts
+++ b/src/libs/API/parameters/UpdatePolicyConnectionConfigParams.ts
@@ -2,7 +2,7 @@ type UpdatePolicyConnectionConfigParams = {
policyID: string;
connectionName: string;
settingName: string;
- settingValue?: string;
+ settingValue: string;
idempotencyKey: string;
};
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index 29044d4fac16..52f130a28dd1 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -209,6 +209,7 @@ export type {default as SetPolicyCustomTaxNameParams} from './SetPolicyCustomTax
export type {default as SetPolicyForeignCurrencyDefaultParams} from './SetPolicyForeignCurrencyDefaultParams';
export type {default as SetPolicyCurrencyDefaultParams} from './SetPolicyCurrencyDefaultParams';
export type {default as UpdatePolicyConnectionConfigParams} from './UpdatePolicyConnectionConfigParams';
+export type {default as UpdateManyPolicyConnectionConfigurationsParams} from './UpdateManyPolicyConnectionConfigurationsParams';
export type {default as RemovePolicyConnectionParams} from './RemovePolicyConnectionParams';
export type {default as RenamePolicyTaxParams} from './RenamePolicyTaxParams';
export type {default as CompleteGuidedSetupParams} from './CompleteGuidedSetupParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 8fe704ff220e..08bc5eddd087 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -196,6 +196,7 @@ const WRITE_COMMANDS = {
DECLINE_JOIN_REQUEST: 'DeclineJoinRequest',
CREATE_POLICY_TAX: 'CreatePolicyTax',
UPDATE_POLICY_CONNECTION_CONFIG: 'UpdatePolicyConnectionConfiguration',
+ UPDATE_MANY_POLICY_CONNECTION_CONFIGS: 'UpdateManyPolicyConnectionConfigurations',
REMOVE_POLICY_CONNECTION: 'RemovePolicyConnection',
SET_POLICY_TAXES_ENABLED: 'SetPolicyTaxesEnabled',
DELETE_POLICY_TAXES: 'DeletePolicyTaxes',
@@ -422,6 +423,7 @@ type WriteCommandParameters = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[WRITE_COMMANDS.UPDATE_POLICY_CONNECTION_CONFIG]: Parameters.UpdatePolicyConnectionConfigParams;
+ [WRITE_COMMANDS.UPDATE_MANY_POLICY_CONNECTION_CONFIGS]: Parameters.UpdateManyPolicyConnectionConfigurationsParams;
[WRITE_COMMANDS.REMOVE_POLICY_CONNECTION]: Parameters.RemovePolicyConnectionParams;
[WRITE_COMMANDS.UPDATE_POLICY_DISTANCE_RATE_VALUE]: Parameters.UpdatePolicyDistanceRateValueParams;
[WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_ENABLED]: Parameters.SetPolicyDistanceRatesEnabledParams;
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index 47d170ce5cd5..462145e56907 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -257,8 +257,6 @@ const SettingsModalStackNavigator = createModalStackNavigator
require('../../../../pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseAccountSelectPage').default as React.ComponentType,
- [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_PAYABLE_SELECT]: () =>
- require('../../../../pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPayableSelectPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES]: () =>
require('../../../../pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_SELECT]: () =>
diff --git a/src/libs/Navigation/AppNavigator/Navigators/BottomTabNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/BottomTabNavigator.tsx
index 6680ea302441..6e1d154ff350 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/BottomTabNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/BottomTabNavigator.tsx
@@ -3,7 +3,7 @@ import type {StackNavigationOptions} from '@react-navigation/stack';
import React from 'react';
import createCustomBottomTabNavigator from '@libs/Navigation/AppNavigator/createCustomBottomTabNavigator';
import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute';
-import type {BottomTabNavigatorParamList} from '@libs/Navigation/types';
+import type {BottomTabNavigatorParamList, CentralPaneName, NavigationPartialRoute, RootStackParamList} from '@libs/Navigation/types';
import SidebarScreen from '@pages/home/sidebar/SidebarScreen';
import SearchPageBottomTab from '@pages/Search/SearchPageBottomTab';
import SCREENS from '@src/SCREENS';
@@ -19,7 +19,7 @@ const screenOptions: StackNavigationOptions = {
};
function BottomTabNavigator() {
- const activeRoute = useNavigationState(getTopmostCentralPaneRoute);
+ const activeRoute = useNavigationState | undefined>(getTopmostCentralPaneRoute);
return (
diff --git a/src/libs/Navigation/AppNavigator/getPartialStateDiff.ts b/src/libs/Navigation/AppNavigator/getPartialStateDiff.ts
index 4c18e161c9a9..5061c7500742 100644
--- a/src/libs/Navigation/AppNavigator/getPartialStateDiff.ts
+++ b/src/libs/Navigation/AppNavigator/getPartialStateDiff.ts
@@ -53,7 +53,7 @@ function getPartialStateDiff(state: State, templateState: St
(stateTopmostCentralPane &&
templateStateTopmostCentralPane &&
stateTopmostCentralPane.name !== templateStateTopmostCentralPane.name &&
- !shallowCompare(stateTopmostCentralPane.params, templateStateTopmostCentralPane.params))
+ !shallowCompare(stateTopmostCentralPane.params as Record | undefined, templateStateTopmostCentralPane.params as Record | undefined))
) {
// We need to wrap central pane routes in the central pane navigator.
diff[NAVIGATORS.CENTRAL_PANE_NAVIGATOR] = templateStateTopmostCentralPane;
@@ -73,7 +73,7 @@ function getPartialStateDiff(state: State, templateState: St
(stateTopmostFullScreen &&
templateStateTopmostFullScreen &&
stateTopmostFullScreen.name !== templateStateTopmostFullScreen.name &&
- !shallowCompare(stateTopmostFullScreen.params, templateStateTopmostFullScreen.params))
+ !shallowCompare(stateTopmostFullScreen.params as Record | undefined, templateStateTopmostFullScreen.params as Record | undefined))
) {
diff[NAVIGATORS.FULL_SCREEN_NAVIGATOR] = fullScreenDiff;
}
diff --git a/src/libs/Navigation/linkTo.ts b/src/libs/Navigation/linkTo.ts
index 45a8609482ca..50050e4b8ff4 100644
--- a/src/libs/Navigation/linkTo.ts
+++ b/src/libs/Navigation/linkTo.ts
@@ -160,8 +160,8 @@ export default function linkTo(navigation: NavigationContainerRef value === undefined),
- omitBy(action.payload.params?.params, (value) => value === undefined),
+ omitBy(topmostCentralPaneRoute?.params as Record | undefined, (value) => value === undefined),
+ omitBy(action.payload.params?.params as Record | undefined, (value) => value === undefined),
);
// In case if type is 'FORCED_UP' we replace current screen with the provided. This means the current screen no longer exists in the stack
if (type === CONST.NAVIGATION.TYPE.FORCED_UP) {
diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
index d7808497916f..b1dd11849833 100755
--- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
@@ -28,7 +28,6 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = {
SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT,
SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_DATE_SELECT,
SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_INVOICE_ACCOUNT_SELECT,
- SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_PAYABLE_SELECT,
SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_ACCOUNT_SELECT,
SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT,
SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT,
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 92482c39e2d7..a093b778360e 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -291,9 +291,6 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT.route},
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_DATE_SELECT]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_DATE_SELECT.route},
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_INVOICE_ACCOUNT_SELECT]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_INVOICE_ACCOUNT_SELECT.route},
- [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_PAYABLE_SELECT]: {
- path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_PAYABLE_SELECT.route,
- },
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_ACCOUNT_SELECT]: {
path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_ACCOUNT_SELECT.route,
},
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index a0cd76637716..e4820df10df9 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -308,9 +308,6 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_COMPANY_CARD_SELECT]: {
policyID: string;
};
- [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_PAYABLE_SELECT]: {
- policyID: string;
- };
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_PREFERRED_EXPORTER]: {
policyID: string;
};
diff --git a/src/libs/ObjectUtils.ts b/src/libs/ObjectUtils.ts
index 9ffa461506c8..644fe1c7596e 100644
--- a/src/libs/ObjectUtils.ts
+++ b/src/libs/ObjectUtils.ts
@@ -1,11 +1,11 @@
-// eslint-disable-next-line @typescript-eslint/ban-types
-const shallowCompare = (obj1?: object, obj2?: object) => {
+const shallowCompare = (obj1?: Record, obj2?: Record): boolean => {
if (!obj1 && !obj2) {
return true;
}
if (obj1 && obj2) {
- // @ts-expect-error we know that obj1 and obj2 are params of a route.
- return Object.keys(obj1).length === Object.keys(obj2).length && Object.keys(obj1).every((key) => obj1[key] === obj2[key]);
+ const keys1 = Object.keys(obj1);
+ const keys2 = Object.keys(obj2);
+ return keys1.length === keys2.length && keys1.every((key) => obj1[key] === obj2[key]);
}
return false;
};
diff --git a/src/libs/ReportActionComposeFocusManager.ts b/src/libs/ReportActionComposeFocusManager.ts
index 11c1fd04329f..b25274ef2946 100644
--- a/src/libs/ReportActionComposeFocusManager.ts
+++ b/src/libs/ReportActionComposeFocusManager.ts
@@ -1,11 +1,12 @@
import React from 'react';
+import type {MutableRefObject} from 'react';
import type {TextInput} from 'react-native';
import ROUTES from '@src/ROUTES';
import Navigation from './Navigation/Navigation';
type FocusCallback = (shouldFocusForNonBlurInputOnTapOutside?: boolean) => void;
-const composerRef = React.createRef();
+const composerRef: MutableRefObject = React.createRef();
const editComposerRef = React.createRef();
// There are two types of composer: general composer (edit composer) and main composer.
// The general composer callback will take priority if it exists.
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 8911a0623479..b8e4c448fdc2 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -4808,7 +4808,7 @@ function buildOptimisticTaskReport(
return {
reportID: generateReportID(),
reportName: title,
- description,
+ description: getParsedComment(description ?? ''),
ownerAccountID,
participants,
managerID: assigneeAccountID,
@@ -5292,13 +5292,9 @@ function canFlagReportAction(reportAction: OnyxEntry, reportID: st
report = getReport(report?.parentReportID);
}
const isCurrentUserAction = reportAction?.actorAccountID === currentUserAccountID;
- const isOriginalMessageHaveHtml =
- reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT ||
- reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.RENAMED ||
- reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CHRONOS_OOO_LIST;
if (ReportActionsUtils.isWhisperAction(reportAction)) {
- // Allow flagging welcome message whispers as they can be set by any room creator
- if (report?.description && !isCurrentUserAction && isOriginalMessageHaveHtml && reportAction?.originalMessage?.html === report.description) {
+ // Allow flagging whispers that are sent by other users
+ if (!isCurrentUserAction && reportAction?.actorAccountID !== CONST.ACCOUNT_ID.CONCIERGE) {
return true;
}
diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts
index aa9a87404924..79e73f1585d2 100644
--- a/src/libs/TransactionUtils.ts
+++ b/src/libs/TransactionUtils.ts
@@ -4,7 +4,7 @@ import Onyx from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {Policy, RecentWaypoint, Report, TaxRate, TaxRates, Transaction, TransactionViolation} from '@src/types/onyx';
+import type {Policy, RecentWaypoint, Report, TaxRate, TaxRates, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx';
import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {IOURequestType} from './actions/IOU';
@@ -15,7 +15,6 @@ import * as NumberUtils from './NumberUtils';
import {getCleanedTagName} from './PolicyUtils';
let allTransactions: OnyxCollection = {};
-
Onyx.connect({
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
@@ -27,6 +26,13 @@ Onyx.connect({
},
});
+let allTransactionViolations: OnyxCollection = {};
+Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
+ waitForCollectionCallback: true,
+ callback: (value) => (allTransactionViolations = value),
+});
+
let allReports: OnyxCollection;
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT,
@@ -506,6 +512,40 @@ function hasMissingSmartscanFields(transaction: OnyxEntry): boolean
return Boolean(transaction && !isDistanceRequest(transaction) && !isReceiptBeingScanned(transaction) && areRequiredFieldsEmpty(transaction));
}
+/**
+ * Get all transaction violations of the transaction with given tranactionID.
+ */
+function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection | null): TransactionViolations | null {
+ return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null;
+}
+
+/**
+ * Check if there is pending rter violation in transactionViolations.
+ */
+function hasPendingRTERViolation(transactionViolations?: TransactionViolations | null): boolean {
+ return Boolean(
+ transactionViolations?.some((transactionViolation: TransactionViolation) => transactionViolation.name === CONST.VIOLATIONS.RTER && transactionViolation.data?.pendingPattern),
+ );
+}
+
+/**
+ * Check if there is pending rter violation in all transactionViolations with given transactionIDs.
+ */
+function allHavePendingRTERViolation(transactionIds: string[]): boolean {
+ const transactionsWithRTERViolations = transactionIds.map((transactionId) => {
+ const transactionViolations = getTransactionViolations(transactionId, allTransactionViolations);
+ return hasPendingRTERViolation(transactionViolations);
+ });
+ return transactionsWithRTERViolations.length > 0 && transactionsWithRTERViolations.every((value) => value === true);
+}
+
+/**
+ * Check if the transaction is pending or has a pending rter violation.
+ */
+function hasPendingUI(transaction: OnyxEntry, transactionViolations?: TransactionViolations | null): boolean {
+ return isReceiptBeingScanned(transaction) || isPending(transaction) || (!!transaction && hasPendingRTERViolation(transactionViolations));
+}
+
/**
* Check if the transaction has a defined route
*/
@@ -608,7 +648,7 @@ function isOnHoldByTransactionID(transactionID: string): boolean {
/**
* Checks if any violations for the provided transaction are of type 'violation'
*/
-function hasViolation(transactionID: string, transactionViolations: OnyxCollection): boolean {
+function hasViolation(transactionID: string, transactionViolations: OnyxCollection): boolean {
return Boolean(
transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION),
);
@@ -621,10 +661,6 @@ function hasNoticeTypeViolation(transactionID: string, transactionViolations: On
return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'notice'));
}
-function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection): TransactionViolation[] | null {
- return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null;
-}
-
/**
* this is the formulae to calculate tax
*/
@@ -763,6 +799,9 @@ export {
isCreatedMissing,
areRequiredFieldsEmpty,
hasMissingSmartscanFields,
+ hasPendingRTERViolation,
+ allHavePendingRTERViolation,
+ hasPendingUI,
getWaypointIndex,
waypointHasValidAddress,
getRecentTransactions,
diff --git a/src/libs/Url.ts b/src/libs/Url.ts
index 80ca98c712cf..69894147a242 100644
--- a/src/libs/Url.ts
+++ b/src/libs/Url.ts
@@ -1,4 +1,5 @@
import 'react-native-url-polyfill/auto';
+import type {Route} from '@src/ROUTES';
/**
* Add / to the end of any URL if not present
@@ -48,12 +49,12 @@ function appendParam(url: string, paramName: string, paramValue: string) {
// If parameter exists, replace it
if (url.includes(`${paramName}=`)) {
const regex = new RegExp(`${paramName}=([^&]*)`);
- return url.replace(regex, `${paramName}=${paramValue}`);
+ return url.replace(regex, `${paramName}=${paramValue}`) as Route;
}
// If parameter doesn't exist, append it
const separator = url.includes('?') ? '&' : '?';
- return `${url}${separator}${paramName}=${paramValue}`;
+ return `${url}${separator}${paramName}=${paramValue}` as Route;
}
function hasURL(text: string) {
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 7f346933873d..3e09524e1dd1 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -1611,10 +1611,11 @@ function clearAvatarErrors(policyID: string) {
* Optimistically update the general settings. Set the general settings as pending until the response succeeds.
* If the response fails set a general error message. Clear the error message when updating.
*/
-function updateGeneralSettings(policyID: string, name: string, currency: string) {
+function updateGeneralSettings(policyID: string, name: string, currencyValue?: string) {
const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
const distanceUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE);
const customUnitID = distanceUnit?.customUnitID;
+ const currency = currencyValue ?? policy?.outputCurrency ?? CONST.CURRENCY.USD;
if (!policy) {
return;
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index 8d337df4feba..91a0ce5da930 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -59,7 +59,7 @@ import * as Environment from '@libs/Environment/Environment';
import * as ErrorUtils from '@libs/ErrorUtils';
import Log from '@libs/Log';
import * as LoginUtils from '@libs/LoginUtils';
-import Navigation from '@libs/Navigation/Navigation';
+import Navigation, {navigationRef} from '@libs/Navigation/Navigation';
import type {NetworkStatus} from '@libs/NetworkConnection';
import LocalNotification from '@libs/Notification/LocalNotification';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
@@ -2530,10 +2530,11 @@ function navigateToMostRecentReport(currentReport: OnyxEntry) {
const isChatThread = ReportUtils.isChatThread(currentReport);
if (lastAccessedReportID) {
const lastAccessedReportRoute = ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID ?? '');
+ // We are using index 1 here because on index 0, we'll always have the bottomTabNavigator.
+ const isFirstRoute = navigationRef?.current?.getState()?.index === 1;
// If it is not a chat thread we should call Navigation.goBack to pop the current route first before navigating to last accessed report.
- if (!isChatThread) {
- // Fallback to the lastAccessedReportID route, if this is first route in the navigator
- Navigation.goBack(lastAccessedReportRoute);
+ if (!isChatThread && !isFirstRoute) {
+ Navigation.goBack();
}
Navigation.navigate(lastAccessedReportRoute, CONST.NAVIGATION.TYPE.FORCED_UP);
} else {
diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts
index 89d5b46408f7..1d4415f72f4b 100644
--- a/src/libs/actions/Task.ts
+++ b/src/libs/actions/Task.ts
@@ -440,7 +440,8 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task
const reportName = (title ?? report?.reportName)?.trim();
// Description can be unset, so we default to an empty string if so
- const reportDescription = ReportUtils.getParsedComment((description ?? report.description ?? '').trim());
+ const newDescription = description ? ReportUtils.getParsedComment(description) : report.description;
+ const reportDescription = (newDescription ?? '').trim();
const optimisticData: OnyxUpdate[] = [
{
diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts
index f403ed5a86bc..8274a4af20d6 100644
--- a/src/libs/actions/Transaction.ts
+++ b/src/libs/actions/Transaction.ts
@@ -262,7 +262,7 @@ function updateWaypoints(transactionID: string, waypoints: WaypointCollection, i
}
function clearError(transactionID: string) {
- Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {errors: null});
+ Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {errors: null, errorFields: {route: null}});
}
function markAsCash(transactionID: string, transactionThreadReportID: string, existingViolations: TransactionViolation[]) {
diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts
index 3f4c50924e9a..119b7da42e21 100644
--- a/src/libs/actions/Welcome.ts
+++ b/src/libs/actions/Welcome.ts
@@ -73,6 +73,10 @@ function setOnboardingAdminsChatReportID(adminsChatReportID?: string) {
Onyx.set(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID, adminsChatReportID ?? null);
}
+function setOnboardingPolicyID(policyID?: string) {
+ Onyx.set(ONYXKEYS.ONBOARDING_POLICY_ID, policyID ?? null);
+}
+
Onyx.connect({
key: ONYXKEYS.NVP_ONBOARDING,
initWithStoredValues: false,
@@ -134,4 +138,4 @@ function resetAllChecks() {
isLoadingReportData = true;
}
-export {onServerDataReady, isOnboardingFlowCompleted, setOnboardingPurposeSelected, resetAllChecks, setOnboardingAdminsChatReportID};
+export {onServerDataReady, isOnboardingFlowCompleted, setOnboardingPurposeSelected, resetAllChecks, setOnboardingAdminsChatReportID, setOnboardingPolicyID};
diff --git a/src/libs/actions/connections/index.ts b/src/libs/actions/connections/index.ts
index f9d16692aedd..4a501910548b 100644
--- a/src/libs/actions/connections/index.ts
+++ b/src/libs/actions/connections/index.ts
@@ -1,7 +1,7 @@
import Onyx from 'react-native-onyx';
import type {OnyxUpdate} from 'react-native-onyx';
import * as API from '@libs/API';
-import type {RemovePolicyConnectionParams, UpdatePolicyConnectionConfigParams} from '@libs/API/parameters';
+import type {RemovePolicyConnectionParams, UpdateManyPolicyConnectionConfigurationsParams, UpdatePolicyConnectionConfigParams} from '@libs/API/parameters';
import {WRITE_COMMANDS} from '@libs/API/types';
import * as ErrorUtils from '@libs/ErrorUtils';
import CONST from '@src/CONST';
@@ -32,6 +32,7 @@ function removePolicyConnection(policyID: string, connectionName: PolicyConnecti
};
API.write(WRITE_COMMANDS.REMOVE_POLICY_CONNECTION, parameters, {optimisticData});
}
+
function updatePolicyConnectionConfig(
policyID: string,
connectionName: TConnectionName,
@@ -114,4 +115,72 @@ function updatePolicyConnectionConfig>(
+ policyID: string,
+ connectionName: TConnectionName,
+ configUpdate: TConfigUpdate,
+ configCurrentData: TConfigUpdate,
+) {
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ [connectionName]: {
+ config: {
+ configUpdate,
+ pendingFields: Object.fromEntries(Object.keys(configUpdate).map((settingName) => [settingName, CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE])),
+ errorFields: Object.fromEntries(Object.keys(configUpdate).map((settingName) => [settingName, null])),
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ const failureData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ [connectionName]: {
+ config: {
+ configCurrentData,
+ pendingFields: Object.fromEntries(Object.keys(configUpdate).map((settingName) => [settingName, null])),
+ errorFields: Object.fromEntries(Object.keys(configUpdate).map((settingName) => [settingName, ErrorUtils.getMicroSecondOnyxError('common.genericErrorMessage')])),
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ const successData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ [connectionName]: {
+ config: {
+ pendingFields: Object.fromEntries(Object.keys(configUpdate).map((settingName) => [settingName, null])),
+ errorFields: Object.fromEntries(Object.keys(configUpdate).map((settingName) => [settingName, null])),
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ const parameters: UpdateManyPolicyConnectionConfigurationsParams = {
+ policyID,
+ connectionName,
+ configUpdate: JSON.stringify(configUpdate),
+ idempotencyKey: Object.keys(configUpdate).join(','),
+ };
+ API.write(WRITE_COMMANDS.UPDATE_MANY_POLICY_CONNECTION_CONFIGS, parameters, {optimisticData, failureData, successData});
+}
+
+export {removePolicyConnection, updatePolicyConnectionConfig, updateManyPolicyConnectionConfigs};
diff --git a/src/libs/migrations/NVPMigration.ts b/src/libs/migrations/NVPMigration.ts
index 9ab774328f78..fc5bce22641f 100644
--- a/src/libs/migrations/NVPMigration.ts
+++ b/src/libs/migrations/NVPMigration.ts
@@ -1,6 +1,9 @@
import after from 'lodash/after';
import Onyx from 'react-native-onyx';
+import type {KeyValueMapping, OnyxEntry} from 'react-native-onyx';
+import type {Account} from 'src/types/onyx';
import ONYXKEYS from '@src/ONYXKEYS';
+import type {OnyxKey} from '@src/ONYXKEYS';
// These are the oldKeyName: newKeyName of the NVPs we can migrate without any processing
const migrations = {
@@ -27,35 +30,30 @@ export default function () {
for (const [oldKey, newKey] of Object.entries(migrations)) {
const connectionID = Onyx.connect({
- // @ts-expect-error oldKey is a variable
- key: oldKey,
+ key: oldKey as OnyxKey,
callback: (value) => {
Onyx.disconnect(connectionID);
if (value === null) {
resolveWhenDone();
return;
}
- // @ts-expect-error These keys are variables, so we can't check the type
Onyx.multiSet({
[newKey]: value,
[oldKey]: null,
- }).then(resolveWhenDone);
+ } as KeyValueMapping).then(resolveWhenDone);
},
});
}
const connectionIDAccount = Onyx.connect({
key: ONYXKEYS.ACCOUNT,
- callback: (value) => {
+ callback: (value: OnyxEntry) => {
Onyx.disconnect(connectionIDAccount);
- // @ts-expect-error we are removing this property, so it is not in the type anymore
if (!value?.activePolicyID) {
resolveWhenDone();
return;
}
- // @ts-expect-error we are removing this property, so it is not in the type anymore
const activePolicyID = value.activePolicyID;
const newValue = {...value};
- // @ts-expect-error we are removing this property, so it is not in the type anymore
delete newValue.activePolicyID;
Onyx.multiSet({
[ONYXKEYS.NVP_ACTIVE_POLICY_ID]: activePolicyID,
@@ -72,14 +70,12 @@ export default function () {
resolveWhenDone();
return;
}
- const newValue = {};
+ const newValue = {} as Record;
for (const key of Object.keys(value)) {
- // @ts-expect-error We have no fixed types here
newValue[`nvp_${key}`] = value[key];
- // @ts-expect-error We have no fixed types here
newValue[key] = null;
}
- Onyx.multiSet(newValue).then(resolveWhenDone);
+ Onyx.multiSet(newValue as KeyValueMapping).then(resolveWhenDone);
},
});
});
diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.tsx b/src/pages/EnablePayments/AdditionalDetailsStep.tsx
index 867bbadf9ea7..4756db4d43ec 100644
--- a/src/pages/EnablePayments/AdditionalDetailsStep.tsx
+++ b/src/pages/EnablePayments/AdditionalDetailsStep.tsx
@@ -206,8 +206,7 @@ function AdditionalDetailsStep({walletAdditionalDetails = DEFAULT_WALLET_ADDITIO
placeholder={translate('common.phoneNumberPlaceholder')}
shouldSaveDraft
/>
- {/* @ts-expect-error TODO: Remove this once DatePicker (https://github.com/Expensify/App/issues/25148) is migrated to TypeScript. */}
-
+ ) => {
@@ -118,4 +124,7 @@ export default withOnyx({
onboardingPurposeSelected: {
key: ONYXKEYS.ONBOARDING_PURPOSE_SELECTED,
},
+ onboardingPolicyID: {
+ key: ONYXKEYS.ONBOARDING_POLICY_ID,
+ },
})(BaseOnboardingWork);
diff --git a/src/pages/OnboardingWork/types.ts b/src/pages/OnboardingWork/types.ts
index 954c8c15b31d..6c06b28259e8 100644
--- a/src/pages/OnboardingWork/types.ts
+++ b/src/pages/OnboardingWork/types.ts
@@ -6,6 +6,9 @@ type OnboardingWorkProps = Record;
type BaseOnboardingWorkOnyxProps = {
/** Saved onboarding purpose selected by the user */
onboardingPurposeSelected: OnyxEntry;
+
+ /** Saved onboarding purpose selected by the user */
+ onboardingPolicyID: OnyxEntry;
};
type BaseOnboardingWorkProps = BaseOnboardingWorkOnyxProps & {
diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx
index b60b19193dbc..f563692f0ae9 100644
--- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx
+++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx
@@ -65,8 +65,7 @@ function DateOfBirthUBO({reimbursementAccountDraft, onNext, isEditing, beneficia
submitButtonStyles={[styles.pb5, styles.mb0]}
>
{translate('beneficialOwnerInfoStep.enterTheDateOfBirthOfTheOwner')}
- {/* @ts-expect-error TODO: Remove this once DatePicker (https://github.com/Expensify/App/issues/25148) is migrated to TypeScript. */}
-
+
{translate('businessInfoStep.selectYourCompanysIncorporationDate')}
- {/* @ts-expect-error TODO: Remove this once DatePicker (https://github.com/Expensify/App/issues/25148) is migrated to TypeScript. */}
-
+
{translate('personalInfoStep.enterYourDateOfBirth')}
- {/* @ts-expect-error TODO: Remove this once DatePicker (https://github.com/Expensify/App/issues/25148) is migrated to TypeScript. */}
-
+ {
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
index 3120bbe9bed2..29b25828d24b 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
@@ -318,7 +318,6 @@ function ComposerWithSuggestions(
*/
const setTextInputRef = useCallback(
(el: TextInput) => {
- // @ts-expect-error need to reassign this ref
ReportActionComposeFocusManager.composerRef.current = el;
textInputRef.current = el;
if (typeof animatedRef === 'function') {
@@ -649,7 +648,6 @@ function ComposerWithSuggestions(
const unsubscribeNavigationFocus = navigation.addListener('focus', () => {
KeyDownListener.addKeyDownPressListener(focusComposerOnKeyPress);
// The report isn't unmounted and can be focused again after going back from another report so we should update the composerRef again
- // @ts-expect-error need to reassign this ref
ReportActionComposeFocusManager.composerRef.current = textInputRef.current;
setUpComposeFocusManager();
});
@@ -767,6 +765,7 @@ function ComposerWithSuggestions(
onLayout={onLayout}
onScroll={hideSuggestionMenu}
shouldContainScroll={Browser.isMobileSafari()}
+ isGroupPolicyReport={isGroupPolicyReport}
/>
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index 90d8460a133e..adf358ab416d 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -49,7 +49,7 @@ import SendButton from './SendButton';
type ComposerRef = {
blur: () => void;
focus: (shouldDelay?: boolean) => void;
- replaceSelectionWithText: (text: string, shouldAddTrailSpace: boolean) => void;
+ replaceSelectionWithText: EmojiPickerActions.OnEmojiSelected;
prepareCommentAndResetComposer: () => string;
isFocused: () => boolean;
};
@@ -73,9 +73,9 @@ type ReportActionComposeOnyxProps = {
type ReportActionComposeProps = ReportActionComposeOnyxProps &
WithCurrentUserPersonalDetailsProps &
- Pick & {
+ Pick & {
/** A method to call when the form is submitted */
- onSubmit: (newComment: string | undefined) => void;
+ onSubmit: (newComment: string) => void;
/** The report currently being looked at */
report: OnyxEntry;
@@ -91,6 +91,9 @@ type ReportActionComposeProps = ReportActionComposeOnyxProps &
/** A method to call when the input is blur */
onComposerBlur?: () => void;
+
+ /** Should the input be disabled */
+ disabled?: boolean;
};
// We want consistent auto focus behavior on input between native and mWeb so we have some auto focus management code that will
@@ -102,7 +105,7 @@ const willBlurTextInputOnTapOutside = willBlurTextInputOnTapOutsideFunc();
function ReportActionCompose({
blockedFromConcierge,
currentUserPersonalDetails = {},
- disabled,
+ disabled = false,
isComposerFullSize = false,
onSubmit,
pendingAction,
@@ -482,7 +485,6 @@ function ReportActionCompose({
composerRef.current?.replaceSelectionWithText(...args)}
emojiPickerID={report?.reportID}
shiftVertical={emojiShiftVertical}
diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx
index ae62cac87d7e..95bc4f5231d2 100644
--- a/src/pages/home/report/ReportActionItem.tsx
+++ b/src/pages/home/report/ReportActionItem.tsx
@@ -64,6 +64,7 @@ import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
+import type {Errors} from '@src/types/onyx/OnyxCommon';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground';
import {RestrictedReadOnlyContextMenuActions} from './ContextMenu/ContextMenuActions';
@@ -89,9 +90,6 @@ const getDraftMessage = (drafts: OnyxCollection,
};
type ReportActionItemOnyxProps = {
- /** Stores user's preferred skin tone */
- preferredSkinTone: OnyxEntry;
-
/** IOU report for this action, if any */
iouReport: OnyxEntry;
@@ -105,6 +103,9 @@ type ReportActionItemOnyxProps = {
/** Transaction associated with this report, if any */
transaction: OnyxEntry;
+
+ /** The transaction (linked with the report action) route error */
+ linkedTransactionRouteError: OnyxEntry;
};
type ReportActionItemProps = {
@@ -173,7 +174,6 @@ function ReportActionItem({
iouReport,
isMostRecentIOUReportAction,
parentReportAction,
- preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE,
shouldDisplayNewMarker,
userWallet,
shouldHideThreadDividerLine = false,
@@ -183,6 +183,7 @@ function ReportActionItem({
onPress = undefined,
isFirstVisibleReportAction = false,
shouldUseThreadDividerLine = false,
+ linkedTransactionRouteError,
}: ReportActionItemProps) {
const {translate} = useLocalize();
const {isSmallScreenWidth} = useWindowDimensions();
@@ -672,8 +673,6 @@ function ReportActionItem({
reportID={report.reportID}
index={index}
ref={textInputRef}
- // Avoid defining within component due to an existing Onyx bug
- preferredSkinTone={preferredSkinTone}
shouldDisableEmojiPicker={(ReportUtils.chatIncludesConcierge(report) && User.isBlockedFromConcierge(blockedFromConcierge)) || ReportUtils.isArchivedRoom(report)}
/>
)}
@@ -977,7 +976,7 @@ function ReportActionItem({
draftMessage !== undefined ? undefined : action.pendingAction ?? (action.isOptimisticAction ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : undefined)
}
shouldHideOnDelete={!ReportActionsUtils.isThreadParentMessage(action, report.reportID)}
- errors={ErrorUtils.getLatestErrorMessageField(action as ErrorUtils.OnyxDataWithErrors)}
+ errors={linkedTransactionRouteError ?? ErrorUtils.getLatestErrorMessageField(action as ErrorUtils.OnyxDataWithErrors)}
errorRowStyles={[styles.ml10, styles.mr2]}
needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(action)}
shouldDisableStrikeThrough
@@ -1019,10 +1018,6 @@ function ReportActionItem({
}
export default withOnyx({
- preferredSkinTone: {
- key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
- initialValue: CONST.EMOJI_DEFAULT_SKIN_TONE,
- },
iouReport: {
key: ({action}) => {
const iouReportID = ReportActionsUtils.getIOUReportIDFromReportActionPreview(action);
@@ -1049,6 +1044,10 @@ export default withOnyx({
return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`;
},
},
+ linkedTransactionRouteError: {
+ key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${(action as OnyxTypes.OriginalMessageIOU)?.originalMessage?.IOUTransactionID ?? 0}`,
+ selector: (transaction: OnyxEntry) => transaction?.errorFields?.route ?? null,
+ },
})(
memo(ReportActionItem, (prevProps, nextProps) => {
const prevParentReportAction = prevProps.parentReportAction;
@@ -1083,6 +1082,7 @@ export default withOnyx({
lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) &&
lodashIsEqual(prevProps.reportActions, nextProps.reportActions) &&
lodashIsEqual(prevProps.transaction, nextProps.transaction) &&
+ lodashIsEqual(prevProps.linkedTransactionRouteError, nextProps.linkedTransactionRouteError) &&
lodashIsEqual(prevParentReportAction, nextParentReportAction)
);
}),
diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx
index eda4ef5b1033..6cb03e8dae05 100644
--- a/src/pages/home/report/ReportActionItemMessageEdit.tsx
+++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx
@@ -4,7 +4,7 @@ import type {ForwardedRef} from 'react';
import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {Keyboard, View} from 'react-native';
import type {NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native';
-import type {OnyxEntry} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
import type {Emoji} from '@assets/emojis/types';
import Composer from '@components/Composer';
import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton';
@@ -58,9 +58,6 @@ type ReportActionItemMessageEditProps = {
/** Whether or not the emoji picker is disabled */
shouldDisableEmojiPicker?: boolean;
-
- /** Stores user's preferred skin tone */
- preferredSkinTone?: OnyxEntry;
};
// native ids
@@ -70,9 +67,10 @@ const messageEditInput = 'messageEditInput';
const shouldUseForcedSelectionRange = shouldUseEmojiPickerSelection();
function ReportActionItemMessageEdit(
- {action, draftMessage, reportID, index, shouldDisableEmojiPicker = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE}: ReportActionItemMessageEditProps,
+ {action, draftMessage, reportID, index, shouldDisableEmojiPicker = false}: ReportActionItemMessageEditProps,
forwardedRef: ForwardedRef,
) {
+ const [preferredSkinTone] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {initialValue: CONST.EMOJI_DEFAULT_SKIN_TONE});
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
@@ -421,8 +419,8 @@ function ReportActionItemMessageEdit(
}}
onBlur={(event: NativeSyntheticEvent) => {
setIsFocused(false);
- // @ts-expect-error TODO: TextInputFocusEventData doesn't contain relatedTarget.
const relatedTargetId = event.nativeEvent?.relatedTarget?.id;
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
if ((relatedTargetId && [messageEditInput, emojiButtonID].includes(relatedTargetId)) || EmojiPickerAction.isEmojiPickerVisible()) {
return;
}
diff --git a/src/pages/home/report/ReportFooter.tsx b/src/pages/home/report/ReportFooter.tsx
index 359d6ba93c15..b3bddb9ba499 100644
--- a/src/pages/home/report/ReportFooter.tsx
+++ b/src/pages/home/report/ReportFooter.tsx
@@ -161,7 +161,6 @@ function ReportFooter({
{
const {isSmallScreenWidth} = useWindowDimensions();
const isFocused = useIsFocusedOriginal();
- const topmostCentralPane = useNavigationState(getTopmostCentralPaneRoute);
+ const topmostCentralPane = useNavigationState | undefined>(getTopmostCentralPaneRoute);
return isFocused || (topmostCentralPane?.name === SCREENS.SEARCH.CENTRAL_PANE && isSmallScreenWidth);
};
diff --git a/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/Camera.tsx
similarity index 66%
rename from src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.native.tsx
rename to src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/Camera.tsx
index beeb8938e917..e24901dcaa51 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.native.tsx
+++ b/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/Camera.tsx
@@ -1,15 +1,15 @@
import React from 'react';
import type {ForwardedRef} from 'react';
-import {Camera} from 'react-native-vision-camera';
+import {Camera as VisionCamera} from 'react-native-vision-camera';
import useTabNavigatorFocus from '@hooks/useTabNavigatorFocus';
import type {NavigationAwareCameraNativeProps} from './types';
// Wraps a camera that will only be active when the tab is focused or as soon as it starts to become focused.
-function NavigationAwareCamera({cameraTabIndex, ...props}: NavigationAwareCameraNativeProps, ref: ForwardedRef) {
+function Camera({cameraTabIndex, ...props}: NavigationAwareCameraNativeProps, ref: ForwardedRef) {
const isCameraActive = useTabNavigatorFocus({tabIndex: cameraTabIndex});
return (
- ) {
+function WebCamera({torchOn, onTorchAvailability, cameraTabIndex, ...props}: NavigationAwareCameraProps, ref: ForwardedRef) {
const shouldShowCamera = useTabNavigatorFocus({
tabIndex: cameraTabIndex,
});
@@ -26,6 +26,6 @@ function NavigationAwareCamera({torchOn, onTorchAvailability, cameraTabIndex, ..
);
}
-NavigationAwareCamera.displayName = 'NavigationAwareCamera';
+WebCamera.displayName = 'NavigationAwareCamera';
-export default React.forwardRef(NavigationAwareCamera);
+export default React.forwardRef(WebCamera);
diff --git a/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/types.ts b/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/types.ts
index 0e6845792122..555cb7a92367 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/types.ts
+++ b/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/types.ts
@@ -12,7 +12,7 @@ type NavigationAwareCameraProps = WebcamProps & {
cameraTabIndex: number;
};
-type NavigationAwareCameraNativeProps = CameraProps & {
+type NavigationAwareCameraNativeProps = Omit & {
cameraTabIndex: number;
};
diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
index bafad583bd74..c022a079df65 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
+++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
@@ -36,7 +36,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Receipt} from '@src/types/onyx/Transaction';
import CameraPermission from './CameraPermission';
-import NavigationAwareCamera from './NavigationAwareCamera';
+import NavigationAwareCamera from './NavigationAwareCamera/Camera';
import type {IOURequestStepOnyxProps, IOURequestStepScanProps} from './types';
function IOURequestStepScan({
@@ -480,8 +480,7 @@ function IOURequestStepScan({
{
setCameraPermissionState('granted');
@@ -110,7 +109,6 @@ function IOURequestStepScan({
let deviceId;
for (const track of stream.getTracks()) {
const setting = track.getSettings();
- // @ts-expect-error there is a type mismatch in typescipt types for MediaStreamTrack microsoft/TypeScript#39010
if (setting.zoom === 1) {
deviceId = setting.deviceId;
break;
@@ -154,7 +152,6 @@ function IOURequestStepScan({
}
navigator.permissions
.query({
- // @ts-expect-error camera does exist in PermissionName
name: 'camera',
})
.then((permissionState) => {
@@ -464,7 +461,6 @@ function IOURequestStepScan({
return;
}
trackRef.current.applyConstraints({
- // @ts-expect-error there is a type mismatch in typescipt types for MediaStreamTrack microsoft/TypeScript#39010
advanced: [{torch: false}],
});
}, []);
@@ -473,7 +469,6 @@ function IOURequestStepScan({
if (trackRef.current && isFlashLightOn) {
trackRef.current
.applyConstraints({
- // @ts-expect-error there is a type mismatch in typescipt types for MediaStreamTrack microsoft/TypeScript#39010
advanced: [{torch: true}],
})
.then(() => {
diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx
index 32f72b015fa4..bf1bb8ad197e 100644
--- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx
+++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx
@@ -91,13 +91,12 @@ function StateSelectionPage() {
shouldShowBackButton
onBackButtonPress={() => {
const backTo = params?.backTo ?? '';
- let backToRoute = '';
+ let backToRoute: Route | undefined;
if (backTo) {
backToRoute = appendParam(backTo, 'state', currentState ?? '');
}
- // @ts-expect-error Navigation.goBack does take a param
Navigation.goBack(backToRoute);
}}
/>
diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx
index 5910128b8faf..f0f1246dc631 100644
--- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx
+++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx
@@ -23,7 +23,6 @@ function QuickbooksCompanyCardExpenseAccountPage({policy}: WithPolicyConnections
const {nonReimbursableBillDefaultVendor, autoCreateVendor, errorFields, pendingFields, nonReimbursableExpensesExportDestination, nonReimbursableExpensesAccount} =
policy?.connections?.quickbooksOnline?.config ?? {};
const {vendors} = policy?.connections?.quickbooksOnline?.data ?? {};
- const isVendorSelected = nonReimbursableExpensesExportDestination === CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.VENDOR_BILL;
const nonReimbursableBillDefaultVendorObject = vendors?.find((vendor) => vendor.id === nonReimbursableBillDefaultVendor);
return (
- {isVendorSelected && (
+
+ Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT.getRoute(policyID))}
+ brickRoadIndicator={errorFields?.nonReimbursableExpensesAccount ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
+ shouldShowRightIcon
+ />
+
+ {nonReimbursableExpensesExportDestination === CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.VENDOR_BILL && (
<>
-
- Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_PAYABLE_SELECT.getRoute(policyID))}
- brickRoadIndicator={errorFields?.nonReimbursableExpensesAccount ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
- shouldShowRightIcon
- />
-
Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.QBO, CONST.QUICK_BOOKS_CONFIG.AUTO_CREATE_VENDOR, isOn)}
+ onToggle={(isOn) =>
+ Connections.updateManyPolicyConnectionConfigs(
+ policyID,
+ CONST.POLICY.CONNECTIONS.NAME.QBO,
+ {
+ [CONST.QUICK_BOOKS_CONFIG.AUTO_CREATE_VENDOR]: isOn,
+ [CONST.QUICK_BOOKS_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR]: isOn
+ ? policy?.connections?.quickbooksOnline?.data?.vendors?.[0]?.id ?? CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE
+ : CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE,
+ },
+ {
+ [CONST.QUICK_BOOKS_CONFIG.AUTO_CREATE_VENDOR]: autoCreateVendor,
+ [CONST.QUICK_BOOKS_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR]:
+ nonReimbursableBillDefaultVendorObject?.id ?? CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE,
+ },
+ )
+ }
pendingAction={pendingFields?.autoCreateVendor}
/>
+ {autoCreateVendor && (
+
+ Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_NON_REIMBURSABLE_DEFAULT_VENDOR_SELECT.getRoute(policyID))}
+ brickRoadIndicator={errorFields?.nonReimbursableBillDefaultVendor ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
+ shouldShowRightIcon
+ errorText={errorFields?.nonReimbursableBillDefaultVendor ? translate('common.genericErrorMessage') : undefined}
+ />
+
+ )}
>
)}
-
- {isVendorSelected ? (
- Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_NON_REIMBURSABLE_DEFAULT_VENDOR_SELECT.getRoute(policyID))}
- brickRoadIndicator={errorFields?.nonReimbursableBillDefaultVendor ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
- shouldShowRightIcon
- errorText={errorFields?.nonReimbursableBillDefaultVendor ? translate('common.genericErrorMessage') : undefined}
- />
- ) : (
- Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT.getRoute(policyID))}
- brickRoadIndicator={errorFields?.nonReimbursableExpensesAccount ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
- shouldShowRightIcon
- errorText={errorFields?.nonReimbursableExpensesAccount ? translate('common.genericErrorMessage') : undefined}
- />
- )}
-
diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPayableSelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPayableSelectPage.tsx
deleted file mode 100644
index d7104fd4bbaf..000000000000
--- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPayableSelectPage.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import React, {useCallback, useMemo} from 'react';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import ScreenWrapper from '@components/ScreenWrapper';
-import SelectionList from '@components/SelectionList';
-import RadioListItem from '@components/SelectionList/RadioListItem';
-import type {ListItem} from '@components/SelectionList/types';
-import Text from '@components/Text';
-import useLocalize from '@hooks/useLocalize';
-import useThemeStyles from '@hooks/useThemeStyles';
-import * as Connections from '@libs/actions/connections';
-import Navigation from '@navigation/Navigation';
-import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
-import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
-import withPolicyConnections from '@pages/workspace/withPolicyConnections';
-import CONST from '@src/CONST';
-import ROUTES from '@src/ROUTES';
-import type {Account} from '@src/types/onyx/Policy';
-
-type CardListItem = ListItem & {
- value: Account;
-};
-
-function QuickbooksCompanyCardExpenseAccountPayableSelectPage({policy}: WithPolicyConnectionsProps) {
- const {translate} = useLocalize();
- const styles = useThemeStyles();
- const {accountPayable} = policy?.connections?.quickbooksOnline?.data ?? {};
- const {nonReimbursableExpensesAccount} = policy?.connections?.quickbooksOnline?.config ?? {};
-
- const policyID = policy?.id ?? '';
- const sections = useMemo(() => {
- const data: CardListItem[] =
- accountPayable?.map((account) => ({
- value: account,
- text: account.name,
- keyForList: account.name,
- isSelected: account.id === nonReimbursableExpensesAccount?.id,
- })) ?? [];
- return [{data}];
- }, [nonReimbursableExpensesAccount, accountPayable]);
-
- const selectAccountPayable = useCallback(
- (row: CardListItem) => {
- if (row.value.id !== nonReimbursableExpensesAccount?.id) {
- Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.QBO, CONST.QUICK_BOOKS_CONFIG.NON_REIMBURSABLE_EXPENSES_ACCOUNT, row.value);
- }
- Navigation.goBack(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT.getRoute(policyID));
- },
- [nonReimbursableExpensesAccount, policyID],
- );
-
- return (
-
-
-
- {translate('workspace.qbo.accountsPayableDescription')}}
- sections={sections}
- ListItem={RadioListItem}
- onSelectRow={selectAccountPayable}
- initiallyFocusedOptionKey={sections[0].data.find((mode) => mode.isSelected)?.keyForList}
- />
-
-
- );
-}
-
-QuickbooksCompanyCardExpenseAccountPayableSelectPage.displayName = 'QuickbooksCompanyCardExpenseAccountPayableSelectPage';
-
-export default withPolicyConnections(QuickbooksCompanyCardExpenseAccountPayableSelectPage);
diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx
index 9b7aace8ebcf..4f757eab253f 100644
--- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx
+++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx
@@ -15,17 +15,20 @@ import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnec
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
-import type {QBONonReimbursableExportAccountType} from '@src/types/onyx/Policy';
+import type {Account, QBONonReimbursableExportAccountType} from '@src/types/onyx/Policy';
type AccountListItem = ListItem & {
value: QBONonReimbursableExportAccountType;
+ accounts: Account[];
+ defaultVendor: string;
};
function QuickbooksCompanyCardExpenseAccountSelectCardPage({policy}: WithPolicyConnectionsProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const policyID = policy?.id ?? '';
- const {nonReimbursableExpensesExportDestination, syncLocations} = policy?.connections?.quickbooksOnline?.config ?? {};
+ const {nonReimbursableExpensesExportDestination, nonReimbursableExpensesAccount, syncLocations, nonReimbursableBillDefaultVendor} = policy?.connections?.quickbooksOnline?.config ?? {};
+ const {creditCards, bankAccounts, accountPayable, vendors} = policy?.connections?.quickbooksOnline?.data ?? {};
const isLocationEnabled = Boolean(syncLocations && syncLocations !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE);
const sections = useMemo(() => {
@@ -35,12 +38,16 @@ function QuickbooksCompanyCardExpenseAccountSelectCardPage({policy}: WithPolicyC
value: CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CREDIT_CARD,
keyForList: CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CREDIT_CARD,
isSelected: CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CREDIT_CARD === nonReimbursableExpensesExportDestination,
+ accounts: creditCards ?? [],
+ defaultVendor: CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE,
},
{
text: translate(`workspace.qbo.accounts.debit_card`),
value: CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.DEBIT_CARD,
keyForList: CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.DEBIT_CARD,
isSelected: CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.DEBIT_CARD === nonReimbursableExpensesExportDestination,
+ accounts: bankAccounts ?? [],
+ defaultVendor: CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE,
},
];
if (!isLocationEnabled) {
@@ -49,19 +56,34 @@ function QuickbooksCompanyCardExpenseAccountSelectCardPage({policy}: WithPolicyC
value: CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.VENDOR_BILL,
keyForList: CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.VENDOR_BILL,
isSelected: CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.VENDOR_BILL === nonReimbursableExpensesExportDestination,
+ accounts: accountPayable ?? [],
+ defaultVendor: vendors?.[0]?.id ?? CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE,
});
}
return [{data: options}];
- }, [translate, nonReimbursableExpensesExportDestination, isLocationEnabled]);
+ }, [translate, nonReimbursableExpensesExportDestination, isLocationEnabled, accountPayable, bankAccounts, creditCards, vendors]);
const selectExportCompanyCard = useCallback(
(row: AccountListItem) => {
if (row.value !== nonReimbursableExpensesExportDestination) {
- Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.QBO, CONST.QUICK_BOOKS_CONFIG.NON_REIMBURSABLE_EXPENSES_EXPORT_DESTINATION, row.value);
+ Connections.updateManyPolicyConnectionConfigs(
+ policyID,
+ CONST.POLICY.CONNECTIONS.NAME.QBO,
+ {
+ [CONST.QUICK_BOOKS_CONFIG.NON_REIMBURSABLE_EXPENSES_EXPORT_DESTINATION]: row.value,
+ [CONST.QUICK_BOOKS_CONFIG.NON_REIMBURSABLE_EXPENSES_ACCOUNT]: row.accounts[0],
+ [CONST.QUICK_BOOKS_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR]: row.defaultVendor,
+ },
+ {
+ [CONST.QUICK_BOOKS_CONFIG.NON_REIMBURSABLE_EXPENSES_EXPORT_DESTINATION]: nonReimbursableExpensesExportDestination,
+ [CONST.QUICK_BOOKS_CONFIG.NON_REIMBURSABLE_EXPENSES_ACCOUNT]: nonReimbursableExpensesAccount,
+ [CONST.QUICK_BOOKS_CONFIG.NON_REIMBURSABLE_BILL_DEFAULT_VENDOR]: nonReimbursableBillDefaultVendor,
+ },
+ );
}
Navigation.goBack(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT.getRoute(policyID));
},
- [nonReimbursableExpensesExportDestination, policyID],
+ [nonReimbursableExpensesExportDestination, policyID, nonReimbursableExpensesAccount, nonReimbursableBillDefaultVendor],
);
return (
diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx
index f5c8020db929..98d81a480ddd 100644
--- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx
+++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx
@@ -24,7 +24,7 @@ function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyConne
const {translate} = useLocalize();
const styles = useThemeStyles();
const policyID = policy?.id ?? '';
- const {creditCards, vendors, bankAccounts} = policy?.connections?.quickbooksOnline?.data ?? {};
+ const {creditCards, accountPayable, bankAccounts} = policy?.connections?.quickbooksOnline?.data ?? {};
const {nonReimbursableExpensesAccount, nonReimbursableExpensesExportDestination} = policy?.connections?.quickbooksOnline?.config ?? {};
@@ -38,7 +38,7 @@ function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyConne
accounts = bankAccounts ?? [];
break;
case CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.VENDOR_BILL:
- accounts = vendors ?? [];
+ accounts = accountPayable ?? [];
break;
default:
accounts = [];
@@ -50,7 +50,7 @@ function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyConne
keyForList: card.name,
isSelected: card.name === nonReimbursableExpensesAccount?.name,
}));
- }, [nonReimbursableExpensesAccount, creditCards, bankAccounts, nonReimbursableExpensesExportDestination, vendors]);
+ }, [nonReimbursableExpensesAccount, creditCards, bankAccounts, nonReimbursableExpensesExportDestination, accountPayable]);
const selectExportAccount = useCallback(
(row: CardListItem) => {
@@ -72,7 +72,7 @@ function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyConne
diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx
index c04a8ece794f..861f7d416902 100644
--- a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx
+++ b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx
@@ -23,7 +23,7 @@ function QuickbooksOutOfPocketExpenseConfigurationPage({policy}: WithPolicyConne
const isTaxesEnabled = Boolean(syncTax);
const shouldShowTaxError = isTaxesEnabled && reimbursableExpensesExportDestination === CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY;
const shouldShowLocationError = isLocationEnabled && reimbursableExpensesExportDestination !== CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY;
- const hasErrors = Boolean(errorFields?.exportEntity) || shouldShowTaxError || shouldShowLocationError;
+ const hasErrors = Boolean(errorFields?.reimbursableExpensesExportDestination) || shouldShowTaxError || shouldShowLocationError;
return (
>;
function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyConnectionsProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
- const {reimbursableExpensesExportDestination, syncTax, syncLocations} = policy?.connections?.quickbooksOnline?.config ?? {};
+ const {reimbursableExpensesExportDestination, reimbursableExpensesAccount, syncTax, syncLocations} = policy?.connections?.quickbooksOnline?.config ?? {};
+ const {bankAccounts, accountPayable, journalEntryAccounts} = policy?.connections?.quickbooksOnline?.data ?? {};
const isLocationsEnabled = Boolean(syncLocations && syncLocations !== CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE);
const isTaxesEnabled = Boolean(syncTax);
const policyID = policy?.id ?? '';
@@ -40,13 +42,15 @@ function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyConnec
keyForList: CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.CHECK,
isSelected: reimbursableExpensesExportDestination === CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.CHECK,
isShown: !isLocationsEnabled,
+ accounts: bankAccounts ?? [],
},
{
value: CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY,
text: translate(`workspace.qbo.accounts.journal_entry`),
keyForList: CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY,
isSelected: reimbursableExpensesExportDestination === CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY,
- isShown: !isTaxesEnabled || isLocationsEnabled,
+ isShown: !isTaxesEnabled,
+ accounts: journalEntryAccounts ?? [],
},
{
value: CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.VENDOR_BILL,
@@ -54,9 +58,10 @@ function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyConnec
keyForList: CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.VENDOR_BILL,
isSelected: reimbursableExpensesExportDestination === CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.VENDOR_BILL,
isShown: !isLocationsEnabled,
+ accounts: accountPayable ?? [],
},
],
- [reimbursableExpensesExportDestination, isTaxesEnabled, translate, isLocationsEnabled],
+ [reimbursableExpensesExportDestination, isTaxesEnabled, translate, isLocationsEnabled, bankAccounts, accountPayable, journalEntryAccounts],
);
const sections: CardsSection[] = useMemo(() => [{data: data.filter((item) => item.isShown)}], [data]);
@@ -64,11 +69,22 @@ function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyConnec
const selectExportEntity = useCallback(
(row: CardListItem) => {
if (row.value !== reimbursableExpensesExportDestination) {
- Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.QBO, CONST.QUICK_BOOKS_CONFIG.REIMBURSABLE_EXPENSES_EXPORT_DESTINATION, row.value);
+ Connections.updateManyPolicyConnectionConfigs(
+ policyID,
+ CONST.POLICY.CONNECTIONS.NAME.QBO,
+ {
+ [CONST.QUICK_BOOKS_CONFIG.REIMBURSABLE_EXPENSES_EXPORT_DESTINATION]: row.value,
+ [CONST.QUICK_BOOKS_CONFIG.REIMBURSABLE_EXPENSES_ACCOUNT]: row.accounts[0],
+ },
+ {
+ [CONST.QUICK_BOOKS_CONFIG.REIMBURSABLE_EXPENSES_EXPORT_DESTINATION]: reimbursableExpensesExportDestination,
+ [CONST.QUICK_BOOKS_CONFIG.REIMBURSABLE_EXPENSES_ACCOUNT]: reimbursableExpensesAccount,
+ },
+ );
}
Navigation.goBack(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES.getRoute(policyID));
},
- [reimbursableExpensesExportDestination, policyID],
+ [reimbursableExpensesExportDestination, policyID, reimbursableExpensesAccount],
);
return (
diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx
index 9cd6e9e1653f..9b8cec10d100 100644
--- a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx
+++ b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx
@@ -19,7 +19,9 @@ function QuickbooksTaxesPage({policy}: WithPolicyProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const policyID = policy?.id ?? '';
- const {syncTax, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {};
+ const {syncTax, pendingFields, reimbursableExpensesExportDestination} = policy?.connections?.quickbooksOnline?.config ?? {};
+ const isJournalExportEntity = reimbursableExpensesExportDestination === CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY;
+
return (
Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.QBO, CONST.QUICK_BOOKS_CONFIG.SYNC_TAX, !syncTax)}
+ disabled={!syncTax && isJournalExportEntity}
/>
+ {isJournalExportEntity && {translate('workspace.qbo.taxesJournalEntrySwitchNote')}}
diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx
index 7b2aa78545f2..4f1cb550cf05 100644
--- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx
+++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx
@@ -16,11 +16,13 @@ import Text from '@components/Text';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
+import usePrevious from '@hooks/usePrevious';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import * as UserUtils from '@libs/UserUtils';
import Navigation from '@navigation/Navigation';
import type {SettingsNavigatorParamList} from '@navigation/types';
+import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading';
import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading';
@@ -56,6 +58,7 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM
const memberLogin = personalDetails?.[accountID]?.login ?? '';
const member = policy?.employeeList?.[memberLogin];
+ const prevMember = usePrevious(member);
const details = personalDetails?.[accountID] ?? ({} as PersonalDetails);
const avatar = details.avatar ?? UserUtils.getDefaultAvatar();
const fallbackIcon = details.fallbackIcon ?? '';
@@ -95,6 +98,13 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM
}
}, [accountID, policy?.errorFields?.changeOwner, policy?.isChangeOwnerSuccessful, policyID]);
+ useEffect(() => {
+ if (!prevMember || prevMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || member?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
+ return;
+ }
+ Navigation.goBack();
+ }, [member, prevMember]);
+
const askForConfirmationToRemove = () => {
setIsRemoveMemberConfirmModalVisible(true);
};
@@ -102,7 +112,6 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM
const removeUser = useCallback(() => {
Policy.removeMembers([accountID], policyID);
setIsRemoveMemberConfirmModalVisible(false);
- Navigation.goBack();
}, [accountID, policyID]);
const navigateToProfile = useCallback(() => {
@@ -127,6 +136,14 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM
Navigation.navigate(ROUTES.WORKSPACE_OWNER_CHANGE_CHECK.getRoute(policyID, accountID, 'amountOwed' as ValueOf));
}, [accountID, policyID]);
+ // eslint-disable-next-line rulesdir/no-negated-variables
+ const shouldShowNotFoundPage =
+ !member || (member.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && prevMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
+
+ if (shouldShowNotFoundPage) {
+ return ;
+ }
+
return (
Promise; log: (key: OnyxKey) => void};
// We intentionally do not offer an Onyx.get API because we believe it will lead to code patterns we don't want to use in this repo, but we can offer a workaround for the sake of debugging
- // @ts-expect-error TS233 - injecting additional utility for use in runtime debugging, should not be used in any compiled code
window.Onyx.get = function (key) {
return new Promise((resolve) => {
// eslint-disable-next-line rulesdir/prefer-onyx-connect-in-libs
@@ -35,9 +35,7 @@ export default function addUtilsToWindow() {
});
};
- // @ts-expect-error TS233 - injecting additional utility for use in runtime debugging, should not be used in any compiled code
window.Onyx.log = function (key) {
- // @ts-expect-error TS2339 - using additional utility injected above
window.Onyx.get(key).then((value) => {
/* eslint-disable-next-line no-console */
console.log(value);
diff --git a/src/stories/Form.stories.tsx b/src/stories/Form.stories.tsx
index 8a1c2ca0b8f0..f4e89f6766f0 100644
--- a/src/stories/Form.stories.tsx
+++ b/src/stories/Form.stories.tsx
@@ -1,5 +1,6 @@
import type {Meta, StoryFn} from '@storybook/react';
import React, {useState} from 'react';
+import type {ComponentType} from 'react';
import {View} from 'react-native';
import AddressSearch from '@components/AddressSearch';
import CheckboxWithLabel from '@components/CheckboxWithLabel';
@@ -36,29 +37,17 @@ type StorybookFormErrors = Partial>;
const STORYBOOK_FORM_ID = 'TestForm' as keyof OnyxFormValuesMapping;
-/**
- * We use the Component Story Format for writing stories. Follow the docs here:
- *
- * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
- */
const story: Meta = {
title: 'Components/Form',
component: FormProvider,
subcomponents: {
- // @ts-expect-error Subcomponent passes props with unknown type causing a TS error
- InputWrapper,
- // @ts-expect-error Subcomponent passes props with unknown type causing a TS error
- TextInput,
- // @ts-expect-error Subcomponent passes props with unknown type causing a TS error
- AddressSearch,
- // @ts-expect-error Subcomponent passes props with unknown type causing a TS error
- CheckboxWithLabel,
- // @ts-expect-error Subcomponent passes props with unknown type causing a TS error
- Picker,
- // @ts-expect-error Subcomponent passes props with unknown type causing a TS error
- StateSelector,
- // @ts-expect-error Subcomponent passes props with unknown type causing a TS error
- DatePicker,
+ InputWrapper: InputWrapper as ComponentType,
+ TextInput: TextInput as ComponentType,
+ AddressSearch: AddressSearch as ComponentType,
+ CheckboxWithLabel: CheckboxWithLabel as ComponentType,
+ Picker: Picker as ComponentType,
+ StateSelector: StateSelector as ComponentType,
+ DatePicker: DatePicker as ComponentType,
},
};
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 451dfc0c1820..45e11dbe6cb9 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -2235,6 +2235,15 @@ const styles = (theme: ThemeColors) =>
width: 200,
},
+ chatItemPDFAttachmentLoading: {
+ backgroundColor: 'transparent',
+ borderColor: theme.border,
+ borderWidth: 1,
+ borderRadius: variables.componentBorderRadiusNormal,
+ ...flex.alignItemsCenter,
+ ...flex.justifyContentCenter,
+ },
+
sidebarVisible: {
borderRightWidth: 1,
},
diff --git a/src/types/modules/dom.d.ts b/src/types/modules/dom.d.ts
index 60bd9c9ae983..029b4b3d6de5 100644
--- a/src/types/modules/dom.d.ts
+++ b/src/types/modules/dom.d.ts
@@ -19,6 +19,51 @@ declare global {
AppleIDSignInOnSuccess: AppleIDSignInOnSuccessEvent;
AppleIDSignInOnFailure: AppleIDSignInOnFailureEvent;
}
+
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+ interface Permissions {
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Permissions/query) */
+ query(permissionDesc: {name: 'geolocation' | 'notifications' | 'persistent-storage' | 'push' | 'screen-wake-lock' | 'xr-spatial-tracking' | 'camera'}): Promise;
+ }
+
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+ interface MediaTrackConstraintSet {
+ aspectRatio?: ConstrainDouble;
+ autoGainControl?: ConstrainBoolean;
+ channelCount?: ConstrainULong;
+ deviceId?: ConstrainDOMString;
+ displaySurface?: ConstrainDOMString;
+ echoCancellation?: ConstrainBoolean;
+ facingMode?: ConstrainDOMString;
+ frameRate?: ConstrainDouble;
+ groupId?: ConstrainDOMString;
+ height?: ConstrainULong;
+ noiseSuppression?: ConstrainBoolean;
+ sampleRate?: ConstrainULong;
+ sampleSize?: ConstrainULong;
+ width?: ConstrainULong;
+ zoom?: {ideal: number};
+ torch?: boolean;
+ }
+
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+ interface MediaTrackSettings {
+ aspectRatio?: number;
+ autoGainControl?: boolean;
+ channelCount?: number;
+ deviceId?: string;
+ displaySurface?: string;
+ echoCancellation?: boolean;
+ facingMode?: string;
+ frameRate?: number;
+ groupId?: string;
+ height?: number;
+ noiseSuppression?: boolean;
+ sampleRate?: number;
+ sampleSize?: number;
+ width?: number;
+ zoom?: number;
+ }
}
export type {AppleIDSignInOnFailureEvent, AppleIDSignInOnSuccessEvent};
diff --git a/src/types/modules/pdf.worker.d.ts b/src/types/modules/pdf.worker.d.ts
new file mode 100644
index 000000000000..307d0ff53a63
--- /dev/null
+++ b/src/types/modules/pdf.worker.d.ts
@@ -0,0 +1 @@
+declare module 'pdfjs-dist/legacy/build/pdf.worker';
diff --git a/src/types/modules/react-native-onyx.d.ts b/src/types/modules/react-native-onyx.d.ts
index 8498b03ec933..453f707165e1 100644
--- a/src/types/modules/react-native-onyx.d.ts
+++ b/src/types/modules/react-native-onyx.d.ts
@@ -1,4 +1,5 @@
import type Onyx from 'react-native-onyx';
+import type {CollectionKeyBase} from 'react-native-onyx/dist/types';
import type {OnyxCollectionKey, OnyxFormDraftKey, OnyxFormKey, OnyxValueKey, OnyxValues} from '@src/ONYXKEYS';
declare module 'react-native-onyx' {
@@ -9,11 +10,13 @@ declare module 'react-native-onyx' {
values: OnyxValues;
}
}
-
declare global {
// Global methods for Onyx key management for debugging purposes
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface Window {
- Onyx: typeof Onyx;
+ Onyx: typeof Onyx & {
+ get: (key: CollectionKeyBase) => Promise;
+ log: (key: CollectionKeyBase) => void;
+ };
}
}
diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts
index effbdd9d28fa..a9ff35c97343 100644
--- a/src/types/modules/react-native.d.ts
+++ b/src/types/modules/react-native.d.ts
@@ -196,6 +196,14 @@ declare module 'react-native' {
touchHistory: TouchHistory;
};
+ interface TextInputFocusEventData extends TargetedEvent {
+ text: string;
+ eventCount: number;
+ relatedTarget?: {
+ id?: string;
+ };
+ }
+
// https://necolas.github.io/react-native-web/docs/interactions/#responderevent-props-api
// Extracted from react-native-web, packages/react-native-web/src/modules/useResponderEvents/ResponderSystem.js
interface ResponderProps {
@@ -360,4 +368,11 @@ declare module 'react-native' {
BootSplash: BootSplashModule;
HybridAppModule: HybridAppModule;
}
+
+ namespace Animated {
+ interface AnimatedInterpolation extends AnimatedWithChildren {
+ interpolate(config: InterpolationConfigType): AnimatedInterpolation;
+ __getValue: () => OutputT;
+ }
+ }
}
diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts
index 28de4582bd5e..ab2037ff336a 100644
--- a/src/types/onyx/TransactionViolation.ts
+++ b/src/types/onyx/TransactionViolation.ts
@@ -29,6 +29,7 @@ type TransactionViolation = {
tagListIndex?: number;
tagListName?: string;
errorIndexes?: number[];
+ pendingPattern?: boolean;
};
};
diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts
index 67525f2e2318..ac320729b2b7 100644
--- a/tests/actions/IOUTest.ts
+++ b/tests/actions/IOUTest.ts
@@ -24,6 +24,7 @@ import type {ReportActionBase} from '@src/types/onyx/ReportAction';
import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import PusherHelper from '../utils/PusherHelper';
+import type {MockFetch} from '../utils/TestHelper';
import * as TestHelper from '../utils/TestHelper';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
import waitForNetworkPromises from '../utils/waitForNetworkPromises';
@@ -60,9 +61,10 @@ describe('actions/IOU', () => {
});
});
+ let mockFetch: MockFetch;
beforeEach(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
global.fetch = TestHelper.getGlobalFetchMock();
+ mockFetch = fetch as MockFetch;
return Onyx.clear().then(waitForBatchedUpdates);
});
@@ -77,179 +79,175 @@ describe('actions/IOU', () => {
let transactionID: string | undefined;
let transactionThread: OnyxEntry;
let transactionThreadCreatedAction: OnyxEntry;
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
IOU.requestMoney({reportID: ''}, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
- return (
- waitForBatchedUpdates()
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
+ return waitForBatchedUpdates()
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
- // A chat report, a transaction thread, and an iou report should be created
- const chatReports = Object.values(allReports ?? {}).filter((report) => report?.type === CONST.REPORT.TYPE.CHAT);
- const iouReports = Object.values(allReports ?? {}).filter((report) => report?.type === CONST.REPORT.TYPE.IOU);
- expect(Object.keys(chatReports).length).toBe(2);
- expect(Object.keys(iouReports).length).toBe(1);
- const chatReport = chatReports[0];
- const transactionThreadReport = chatReports[1];
- const iouReport = iouReports[0];
- iouReportID = iouReport?.reportID;
- transactionThread = transactionThreadReport;
+ // A chat report, a transaction thread, and an iou report should be created
+ const chatReports = Object.values(allReports ?? {}).filter((report) => report?.type === CONST.REPORT.TYPE.CHAT);
+ const iouReports = Object.values(allReports ?? {}).filter((report) => report?.type === CONST.REPORT.TYPE.IOU);
+ expect(Object.keys(chatReports).length).toBe(2);
+ expect(Object.keys(iouReports).length).toBe(1);
+ const chatReport = chatReports[0];
+ const transactionThreadReport = chatReports[1];
+ const iouReport = iouReports[0];
+ iouReportID = iouReport?.reportID;
+ transactionThread = transactionThreadReport;
- expect(iouReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
+ expect(iouReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
- // They should be linked together
- expect(chatReport?.participants).toEqual({[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT});
- expect(chatReport?.iouReportID).toBe(iouReport?.reportID);
+ // They should be linked together
+ expect(chatReport?.participants).toEqual({[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT});
+ expect(chatReport?.iouReportID).toBe(iouReport?.reportID);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
- waitForCollectionCallback: false,
- callback: (reportActionsForIOUReport) => {
- Onyx.disconnect(connectionID);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForIOUReport) => {
+ Onyx.disconnect(connectionID);
- // The IOU report should have a CREATED action and IOU action
- expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(2);
- const createdActions = Object.values(reportActionsForIOUReport ?? {}).filter(
- (reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
- );
- const iouActions = Object.values(reportActionsForIOUReport ?? {}).filter(
- (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
- );
- expect(Object.values(createdActions).length).toBe(1);
- expect(Object.values(iouActions).length).toBe(1);
- createdAction = createdActions?.[0] ?? null;
- iouAction = iouActions?.[0] ?? null;
+ // The IOU report should have a CREATED action and IOU action
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(2);
+ const createdActions = Object.values(reportActionsForIOUReport ?? {}).filter(
+ (reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
+ );
+ const iouActions = Object.values(reportActionsForIOUReport ?? {}).filter(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
+ );
+ expect(Object.values(createdActions).length).toBe(1);
+ expect(Object.values(iouActions).length).toBe(1);
+ createdAction = createdActions?.[0] ?? null;
+ iouAction = iouActions?.[0] ?? null;
- // The CREATED action should not be created after the IOU action
- expect(Date.parse(createdAction?.created ?? '')).toBeLessThan(Date.parse(iouAction?.created ?? ''));
+ // The CREATED action should not be created after the IOU action
+ expect(Date.parse(createdAction?.created ?? '')).toBeLessThan(Date.parse(iouAction?.created ?? ''));
- // The IOUReportID should be correct
- expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID);
+ // The IOUReportID should be correct
+ expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID);
- // The comment should be included in the IOU action
- expect(iouAction.originalMessage.comment).toBe(comment);
+ // The comment should be included in the IOU action
+ expect(iouAction.originalMessage.comment).toBe(comment);
- // The amount in the IOU action should be correct
- expect(iouAction.originalMessage.amount).toBe(amount);
+ // The amount in the IOU action should be correct
+ expect(iouAction.originalMessage.amount).toBe(amount);
- // The IOU type should be correct
- expect(iouAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
+ // The IOU type should be correct
+ expect(iouAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
- // Both actions should be pending
- expect(createdAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(iouAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ // Both actions should be pending
+ expect(createdAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(iouAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`,
- waitForCollectionCallback: false,
- callback: (reportActionsForTransactionThread) => {
- Onyx.disconnect(connectionID);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread?.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForTransactionThread) => {
+ Onyx.disconnect(connectionID);
- // The transaction thread should have a CREATED action
- expect(Object.values(reportActionsForTransactionThread ?? {}).length).toBe(1);
- const createdActions = Object.values(reportActionsForTransactionThread ?? {}).filter(
- (reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
- );
- expect(Object.values(createdActions).length).toBe(1);
- transactionThreadCreatedAction = createdActions[0];
+ // The transaction thread should have a CREATED action
+ expect(Object.values(reportActionsForTransactionThread ?? {}).length).toBe(1);
+ const createdActions = Object.values(reportActionsForTransactionThread ?? {}).filter(
+ (reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
+ );
+ expect(Object.values(createdActions).length).toBe(1);
+ transactionThreadCreatedAction = createdActions[0];
- expect(transactionThreadCreatedAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
+ expect(transactionThreadCreatedAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
- // There should be one transaction
- expect(Object.values(allTransactions ?? {}).length).toBe(1);
- const transaction = Object.values(allTransactions ?? []).find((t) => !isEmptyObject(t));
- transactionID = transaction?.transactionID;
+ // There should be one transaction
+ expect(Object.values(allTransactions ?? {}).length).toBe(1);
+ const transaction = Object.values(allTransactions ?? []).find((t) => !isEmptyObject(t));
+ transactionID = transaction?.transactionID;
- // The transaction should be attached to the IOU report
- expect(transaction?.reportID).toBe(iouReportID);
+ // The transaction should be attached to the IOU report
+ expect(transaction?.reportID).toBe(iouReportID);
- // Its amount should match the amount of the expense
- expect(transaction?.amount).toBe(amount);
+ // Its amount should match the amount of the expense
+ expect(transaction?.amount).toBe(amount);
- // The comment should be correct
- expect(transaction?.comment.comment).toBe(comment);
+ // The comment should be correct
+ expect(transaction?.comment.comment).toBe(comment);
- // It should be pending
- expect(transaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ // It should be pending
+ expect(transaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- // The transactionID on the iou action should match the one from the transactions collection
- expect((iouAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(transactionID);
+ // The transactionID on the iou action should match the one from the transactions collection
+ expect((iouAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(transactionID);
- expect(transaction?.merchant).toBe(merchant);
+ expect(transaction?.merchant).toBe(merchant);
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
- waitForCollectionCallback: false,
- callback: (reportActionsForIOUReport) => {
- Onyx.disconnect(connectionID);
- expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(2);
- Object.values(reportActionsForIOUReport ?? {}).forEach((reportAction) => expect(reportAction?.pendingAction).toBeFalsy());
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
- waitForCollectionCallback: false,
- callback: (transaction) => {
- Onyx.disconnect(connectionID);
- expect(transaction?.pendingAction).toBeFalsy();
- resolve();
- },
- });
- }),
- )
- );
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForIOUReport) => {
+ Onyx.disconnect(connectionID);
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(2);
+ Object.values(reportActionsForIOUReport ?? {}).forEach((reportAction) => expect(reportAction?.pendingAction).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
+ waitForCollectionCallback: false,
+ callback: (transaction) => {
+ Onyx.disconnect(connectionID);
+ expect(transaction?.pendingAction).toBeFalsy();
+ resolve();
+ },
+ });
+ }),
+ );
});
it('updates existing chat report if there is one', () => {
@@ -269,152 +267,147 @@ describe('actions/IOU', () => {
let iouAction: OnyxEntry;
let iouCreatedAction: OnyxEntry;
let transactionID: string | undefined;
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, chatReport)
- .then(() =>
- Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, {
- [createdAction.reportActionID]: createdAction,
- }),
- )
- .then(() => {
- IOU.requestMoney(chatReport, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
+ mockFetch?.pause?.();
+ return Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, chatReport)
+ .then(() =>
+ Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, {
+ [createdAction.reportActionID]: createdAction,
+ }),
+ )
+ .then(() => {
+ IOU.requestMoney(chatReport, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
- // The same chat report should be reused, a transaction thread and an IOU report should be created
- expect(Object.values(allReports ?? {}).length).toBe(3);
- expect(Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.CHAT)?.reportID).toBe(chatReport.reportID);
- chatReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.CHAT) ?? chatReport;
- const iouReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU);
- iouReportID = iouReport?.reportID;
+ // The same chat report should be reused, a transaction thread and an IOU report should be created
+ expect(Object.values(allReports ?? {}).length).toBe(3);
+ expect(Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.CHAT)?.reportID).toBe(chatReport.reportID);
+ chatReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.CHAT) ?? chatReport;
+ const iouReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU);
+ iouReportID = iouReport?.reportID;
- expect(iouReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
+ expect(iouReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
- // They should be linked together
- expect(chatReport.iouReportID).toBe(iouReportID);
+ // They should be linked together
+ expect(chatReport.iouReportID).toBe(iouReportID);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
- waitForCollectionCallback: false,
- callback: (allIOUReportActions) => {
- Onyx.disconnect(connectionID);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
+ waitForCollectionCallback: false,
+ callback: (allIOUReportActions) => {
+ Onyx.disconnect(connectionID);
- iouCreatedAction =
- Object.values(allIOUReportActions ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) ?? null;
- iouAction =
- Object.values(allIOUReportActions ?? {}).find(
- (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
- ) ?? null;
+ iouCreatedAction = Object.values(allIOUReportActions ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) ?? null;
+ iouAction =
+ Object.values(allIOUReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
+ ) ?? null;
- // The CREATED action should not be created after the IOU action
- expect(Date.parse(iouCreatedAction?.created ?? '')).toBeLessThan(Date.parse(iouAction?.created ?? ''));
+ // The CREATED action should not be created after the IOU action
+ expect(Date.parse(iouCreatedAction?.created ?? '')).toBeLessThan(Date.parse(iouAction?.created ?? ''));
- // The IOUReportID should be correct
- expect(iouAction?.originalMessage?.IOUReportID).toBe(iouReportID);
+ // The IOUReportID should be correct
+ expect(iouAction?.originalMessage?.IOUReportID).toBe(iouReportID);
- // The comment should be included in the IOU action
- expect(iouAction?.originalMessage?.comment).toBe(comment);
+ // The comment should be included in the IOU action
+ expect(iouAction?.originalMessage?.comment).toBe(comment);
- // The amount in the IOU action should be correct
- expect(iouAction?.originalMessage?.amount).toBe(amount);
+ // The amount in the IOU action should be correct
+ expect(iouAction?.originalMessage?.amount).toBe(amount);
- // The IOU action type should be correct
- expect(iouAction?.originalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
+ // The IOU action type should be correct
+ expect(iouAction?.originalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
- // The IOU action should be pending
- expect(iouAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ // The IOU action should be pending
+ expect(iouAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
- // There should be one transaction
- expect(Object.values(allTransactions ?? {}).length).toBe(1);
- const transaction = Object.values(allTransactions ?? {}).find((t) => !isEmptyObject(t));
- transactionID = transaction?.transactionID;
+ // There should be one transaction
+ expect(Object.values(allTransactions ?? {}).length).toBe(1);
+ const transaction = Object.values(allTransactions ?? {}).find((t) => !isEmptyObject(t));
+ transactionID = transaction?.transactionID;
- // The transaction should be attached to the IOU report
- expect(transaction?.reportID).toBe(iouReportID);
+ // The transaction should be attached to the IOU report
+ expect(transaction?.reportID).toBe(iouReportID);
- // Its amount should match the amount of the expense
- expect(transaction?.amount).toBe(amount);
+ // Its amount should match the amount of the expense
+ expect(transaction?.amount).toBe(amount);
- // The comment should be correct
- expect(transaction?.comment.comment).toBe(comment);
+ // The comment should be correct
+ expect(transaction?.comment.comment).toBe(comment);
- expect(transaction?.merchant).toBe(CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT);
+ expect(transaction?.merchant).toBe(CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT);
- // It should be pending
- expect(transaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ // It should be pending
+ expect(transaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- // The transactionID on the iou action should match the one from the transactions collection
- expect((iouAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(transactionID);
+ // The transactionID on the iou action should match the one from the transactions collection
+ expect((iouAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(transactionID);
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
- waitForCollectionCallback: false,
- callback: (reportActionsForIOUReport) => {
- Onyx.disconnect(connectionID);
- expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(2);
- Object.values(reportActionsForIOUReport ?? {}).forEach((reportAction) => expect(reportAction?.pendingAction).toBeFalsy());
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
- callback: (transaction) => {
- Onyx.disconnect(connectionID);
- expect(transaction?.pendingAction).toBeFalsy();
- resolve();
- },
- });
- }),
- )
- );
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForIOUReport) => {
+ Onyx.disconnect(connectionID);
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(2);
+ Object.values(reportActionsForIOUReport ?? {}).forEach((reportAction) => expect(reportAction?.pendingAction).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
+ callback: (transaction) => {
+ Onyx.disconnect(connectionID);
+ expect(transaction?.pendingAction).toBeFalsy();
+ resolve();
+ },
+ });
+ }),
+ );
});
it('updates existing IOU report if there is one', () => {
@@ -469,146 +462,142 @@ describe('actions/IOU', () => {
};
let newIOUAction: OnyxEntry;
let newTransaction: OnyxEntry;
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, chatReport)
- .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, iouReport))
- .then(() =>
- Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, {
- [createdAction.reportActionID]: createdAction,
- [iouAction.reportActionID]: iouAction,
- }),
- )
- .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${existingTransaction.transactionID}`, existingTransaction))
- .then(() => {
- if (chatReport) {
- IOU.requestMoney(chatReport, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
- }
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
+ mockFetch?.pause?.();
+ return Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, chatReport)
+ .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, iouReport))
+ .then(() =>
+ Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, {
+ [createdAction.reportActionID]: createdAction,
+ [iouAction.reportActionID]: iouAction,
+ }),
+ )
+ .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${existingTransaction.transactionID}`, existingTransaction))
+ .then(() => {
+ if (chatReport) {
+ IOU.requestMoney(chatReport, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
+ }
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
- // No new reports should be created
- expect(Object.values(allReports ?? {}).length).toBe(3);
- expect(Object.values(allReports ?? {}).find((report) => report?.reportID === chatReportID)).toBeTruthy();
- expect(Object.values(allReports ?? {}).find((report) => report?.reportID === iouReportID)).toBeTruthy();
+ // No new reports should be created
+ expect(Object.values(allReports ?? {}).length).toBe(3);
+ expect(Object.values(allReports ?? {}).find((report) => report?.reportID === chatReportID)).toBeTruthy();
+ expect(Object.values(allReports ?? {}).find((report) => report?.reportID === iouReportID)).toBeTruthy();
- chatReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.CHAT) ?? null;
- iouReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU) ?? null;
+ chatReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.CHAT) ?? null;
+ iouReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU) ?? null;
- // The total on the iou report should be updated
- expect(iouReport?.total).toBe(11000);
+ // The total on the iou report should be updated
+ expect(iouReport?.total).toBe(11000);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
- waitForCollectionCallback: false,
- callback: (reportActionsForIOUReport) => {
- Onyx.disconnect(connectionID);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForIOUReport) => {
+ Onyx.disconnect(connectionID);
- expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(3);
- newIOUAction =
- Object.values(reportActionsForIOUReport ?? {}).find(
- (reportAction): reportAction is ReportActionBase & OriginalMessageIOU =>
- reportAction?.reportActionID !== createdAction.reportActionID && reportAction?.reportActionID !== iouAction?.reportActionID,
- ) ?? null;
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(3);
+ newIOUAction =
+ Object.values(reportActionsForIOUReport ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU =>
+ reportAction?.reportActionID !== createdAction.reportActionID && reportAction?.reportActionID !== iouAction?.reportActionID,
+ ) ?? null;
- // The IOUReportID should be correct
- expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID);
+ // The IOUReportID should be correct
+ expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID);
- // The comment should be included in the IOU action
- expect(newIOUAction?.originalMessage.comment).toBe(comment);
+ // The comment should be included in the IOU action
+ expect(newIOUAction?.originalMessage.comment).toBe(comment);
- // The amount in the IOU action should be correct
- expect(newIOUAction?.originalMessage.amount).toBe(amount);
+ // The amount in the IOU action should be correct
+ expect(newIOUAction?.originalMessage.amount).toBe(amount);
- // The type of the IOU action should be correct
- expect(newIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
+ // The type of the IOU action should be correct
+ expect(newIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
- // The IOU action should be pending
- expect(newIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ // The IOU action should be pending
+ expect(newIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
- // There should be two transactions
- expect(Object.values(allTransactions ?? {}).length).toBe(2);
+ // There should be two transactions
+ expect(Object.values(allTransactions ?? {}).length).toBe(2);
- newTransaction = Object.values(allTransactions ?? {}).find((transaction) => transaction?.transactionID !== existingTransaction.transactionID) ?? null;
+ newTransaction = Object.values(allTransactions ?? {}).find((transaction) => transaction?.transactionID !== existingTransaction.transactionID) ?? null;
- expect(newTransaction?.reportID).toBe(iouReportID);
- expect(newTransaction?.amount).toBe(amount);
- expect(newTransaction?.comment.comment).toBe(comment);
- expect(newTransaction?.merchant).toBe(CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT);
- expect(newTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(newTransaction?.reportID).toBe(iouReportID);
+ expect(newTransaction?.amount).toBe(amount);
+ expect(newTransaction?.comment.comment).toBe(comment);
+ expect(newTransaction?.merchant).toBe(CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT);
+ expect(newTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- // The transactionID on the iou action should match the one from the transactions collection
- expect((newIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(newTransaction?.transactionID);
+ // The transactionID on the iou action should match the one from the transactions collection
+ expect((newIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(newTransaction?.transactionID);
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForNetworkPromises)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
- waitForCollectionCallback: false,
- callback: (reportActionsForIOUReport) => {
- Onyx.disconnect(connectionID);
- expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(3);
- Object.values(reportActionsForIOUReport ?? {}).forEach((reportAction) => expect(reportAction?.pendingAction).toBeFalsy());
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
- Object.values(allTransactions ?? {}).forEach((transaction) => expect(transaction?.pendingAction).toBeFalsy());
- resolve();
- },
- });
- }),
- )
- );
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(waitForNetworkPromises)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActionsForIOUReport) => {
+ Onyx.disconnect(connectionID);
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(3);
+ Object.values(reportActionsForIOUReport ?? {}).forEach((reportAction) => expect(reportAction?.pendingAction).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+ Object.values(allTransactions ?? {}).forEach((transaction) => expect(transaction?.pendingAction).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ );
});
it('correctly implements RedBrickRoad error handling', () => {
@@ -621,8 +610,7 @@ describe('actions/IOU', () => {
let transactionID: string;
let transactionThreadReport: OnyxEntry;
let transactionThreadAction: OnyxEntry;
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
IOU.requestMoney({reportID: ''}, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
return (
waitForBatchedUpdates()
@@ -733,10 +721,8 @@ describe('actions/IOU', () => {
}),
)
.then((): Promise => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.fail();
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- return fetch.resume() as Promise;
+ mockFetch?.fail?.();
+ return mockFetch?.resume?.() as Promise;
})
.then(
() =>
@@ -924,8 +910,7 @@ describe('actions/IOU', () => {
)
// Cleanup
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.succeed)
+ .then(mockFetch?.succeed)
);
});
});
@@ -1063,356 +1048,350 @@ describe('actions/IOU', () => {
(item) => item[julesChatCreatedAction.reportActionID].reportID ?? '',
);
- return (
- Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, {
- ...reportCollectionDataSet,
+ return Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, {
+ ...reportCollectionDataSet,
+ })
+ .then(() =>
+ Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {
+ ...carlosActionsCollectionDataSet,
+ ...julesCreatedActionsCollectionDataSet,
+ ...julesActionsCollectionDataSet,
+ }),
+ )
+ .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${julesExistingTransaction?.transactionID}`, julesExistingTransaction))
+ .then(() => {
+ // When we split a bill offline
+ mockFetch?.pause?.();
+ IOU.splitBill(
+ // TODO: Migrate after the backend accepts accountIDs
+ {
+ participants: [
+ [CARLOS_EMAIL, String(CARLOS_ACCOUNT_ID)],
+ [JULES_EMAIL, String(JULES_ACCOUNT_ID)],
+ [VIT_EMAIL, String(VIT_ACCOUNT_ID)],
+ ].map(([email, accountID]) => ({login: email, accountID: Number(accountID)})),
+ currentUserLogin: RORY_EMAIL,
+ currentUserAccountID: RORY_ACCOUNT_ID,
+ amount,
+ comment,
+ currency: CONST.CURRENCY.USD,
+ merchant,
+ created: '',
+ tag: '',
+ existingSplitChatReportID: '',
+ },
+ );
+ return waitForBatchedUpdates();
})
- .then(() =>
- Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {
- ...carlosActionsCollectionDataSet,
- ...julesCreatedActionsCollectionDataSet,
- ...julesActionsCollectionDataSet,
- }),
- )
- .then(() => Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${julesExistingTransaction?.transactionID}`, julesExistingTransaction))
- .then(() => {
- // When we split a bill offline
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
- IOU.splitBill(
- // TODO: Migrate after the backend accepts accountIDs
- {
- participants: [
- [CARLOS_EMAIL, String(CARLOS_ACCOUNT_ID)],
- [JULES_EMAIL, String(JULES_ACCOUNT_ID)],
- [VIT_EMAIL, String(VIT_ACCOUNT_ID)],
- ].map(([email, accountID]) => ({login: email, accountID: Number(accountID)})),
- currentUserLogin: RORY_EMAIL,
- currentUserAccountID: RORY_ACCOUNT_ID,
- amount,
- comment,
- currency: CONST.CURRENCY.USD,
- merchant,
- created: '',
- tag: '',
- existingSplitChatReportID: '',
- },
- );
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
-
- // There should now be 10 reports
- expect(Object.values(allReports ?? {}).length).toBe(10);
-
- // 1. The chat report with Rory + Carlos
- carlosChatReport = Object.values(allReports ?? {}).find((report) => report?.reportID === carlosChatReport?.reportID) ?? null;
- expect(isEmptyObject(carlosChatReport)).toBe(false);
- expect(carlosChatReport?.pendingFields).toBeFalsy();
-
- // 2. The IOU report with Rory + Carlos (new)
- carlosIOUReport =
- Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU && report.managerID === CARLOS_ACCOUNT_ID) ?? null;
- expect(isEmptyObject(carlosIOUReport)).toBe(false);
- expect(carlosIOUReport?.total).toBe(amount / 4);
-
- // 3. The chat report with Rory + Jules
- julesChatReport = Object.values(allReports ?? {}).find((report) => report?.reportID === julesChatReport?.reportID) ?? null;
- expect(isEmptyObject(julesChatReport)).toBe(false);
- expect(julesChatReport?.pendingFields).toBeFalsy();
-
- // 4. The IOU report with Rory + Jules
- julesIOUReport = Object.values(allReports ?? {}).find((report) => report?.reportID === julesIOUReport?.reportID) ?? null;
- expect(isEmptyObject(julesIOUReport)).toBe(false);
- expect(julesChatReport?.pendingFields).toBeFalsy();
- expect(julesIOUReport?.total).toBe((julesExistingTransaction?.amount ?? 0) + amount / 4);
-
- // 5. The chat report with Rory + Vit (new)
- vitChatReport =
- Object.values(allReports ?? {}).find(
- (report) =>
- report?.type === CONST.REPORT.TYPE.CHAT &&
- isEqual(report.participants, {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [VIT_ACCOUNT_ID]: VIT_PARTICIPANT}),
- ) ?? null;
- expect(isEmptyObject(vitChatReport)).toBe(false);
- expect(vitChatReport?.pendingFields).toStrictEqual({createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD});
-
- // 6. The IOU report with Rory + Vit (new)
- vitIOUReport =
- Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU && report.managerID === VIT_ACCOUNT_ID) ?? null;
- expect(isEmptyObject(vitIOUReport)).toBe(false);
- expect(vitIOUReport?.total).toBe(amount / 4);
-
- // 7. The group chat with everyone
- groupChat =
- Object.values(allReports ?? {}).find(
- (report) =>
- report?.type === CONST.REPORT.TYPE.CHAT &&
- isEqual(report.participants, {
- [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT,
- [JULES_ACCOUNT_ID]: JULES_PARTICIPANT,
- [VIT_ACCOUNT_ID]: VIT_PARTICIPANT,
- [RORY_ACCOUNT_ID]: RORY_PARTICIPANT,
- }),
- ) ?? null;
- expect(isEmptyObject(groupChat)).toBe(false);
- expect(groupChat?.pendingFields).toStrictEqual({createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD});
-
- // The 1:1 chat reports and the IOU reports should be linked together
- expect(carlosChatReport?.iouReportID).toBe(carlosIOUReport?.reportID);
- expect(carlosIOUReport?.chatReportID).toBe(carlosChatReport?.reportID);
- expect(carlosIOUReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
-
- expect(julesChatReport?.iouReportID).toBe(julesIOUReport?.reportID);
- expect(julesIOUReport?.chatReportID).toBe(julesChatReport?.reportID);
-
- expect(vitChatReport?.iouReportID).toBe(vitIOUReport?.reportID);
- expect(vitIOUReport?.chatReportID).toBe(vitChatReport?.reportID);
- expect(carlosIOUReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (allReportActions) => {
- Onyx.disconnect(connectionID);
-
- // There should be reportActions on all 7 chat reports + 3 IOU reports in each 1:1 chat
- expect(Object.values(allReportActions ?? {}).length).toBe(10);
-
- const carlosReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${carlosChatReport?.iouReportID}`];
- const julesReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${julesChatReport?.iouReportID}`];
- const vitReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${vitChatReport?.iouReportID}`];
- const groupReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${groupChat?.reportID}`];
-
- // Carlos DM should have two reportActions – the existing CREATED action and a pending IOU action
- expect(Object.values(carlosReportActions ?? {}).length).toBe(2);
- carlosIOUCreatedAction =
- Object.values(carlosReportActions ?? {}).find(
- (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
- ) ?? null;
- carlosIOUAction =
- Object.values(carlosReportActions ?? {}).find(
- (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
- ) ?? null;
- expect(carlosIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(carlosIOUAction?.originalMessage.IOUReportID).toBe(carlosIOUReport?.reportID);
- expect(carlosIOUAction?.originalMessage.amount).toBe(amount / 4);
- expect(carlosIOUAction?.originalMessage.comment).toBe(comment);
- expect(carlosIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
- expect(Date.parse(carlosIOUCreatedAction?.created ?? '')).toBeLessThan(Date.parse(carlosIOUAction?.created ?? ''));
-
- // Jules DM should have three reportActions, the existing CREATED action, the existing IOU action, and a new pending IOU action
- expect(Object.values(julesReportActions ?? {}).length).toBe(3);
- expect(julesReportActions?.[julesCreatedAction.reportActionID]).toStrictEqual(julesCreatedAction);
- julesIOUCreatedAction =
- Object.values(julesReportActions ?? {}).find(
- (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
- ) ?? null;
- julesIOUAction =
- Object.values(julesReportActions ?? {}).find(
- (reportAction): reportAction is ReportActionBase & OriginalMessageIOU =>
- reportAction.reportActionID !== julesCreatedAction.reportActionID &&
- reportAction.reportActionID !== julesExistingIOUAction.reportActionID,
- ) ?? null;
- expect(julesIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(julesIOUAction?.originalMessage.IOUReportID).toBe(julesIOUReport?.reportID);
- expect(julesIOUAction?.originalMessage.amount).toBe(amount / 4);
- expect(julesIOUAction?.originalMessage.comment).toBe(comment);
- expect(julesIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
- expect(Date.parse(julesIOUCreatedAction?.created ?? '')).toBeLessThan(Date.parse(julesIOUAction?.created ?? ''));
-
- // Vit DM should have two reportActions – a pending CREATED action and a pending IOU action
- expect(Object.values(vitReportActions ?? {}).length).toBe(2);
- vitCreatedAction =
- Object.values(vitReportActions ?? {}).find(
- (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
- ) ?? null;
- vitIOUAction =
- Object.values(vitReportActions ?? {}).find(
- (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
- ) ?? null;
- expect(vitCreatedAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(vitIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(vitIOUAction?.originalMessage.IOUReportID).toBe(vitIOUReport?.reportID);
- expect(vitIOUAction?.originalMessage.amount).toBe(amount / 4);
- expect(vitIOUAction?.originalMessage.comment).toBe(comment);
- expect(vitIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
- expect(Date.parse(vitCreatedAction?.created ?? '')).toBeLessThan(Date.parse(vitIOUAction?.created ?? ''));
-
- // Group chat should have two reportActions – a pending CREATED action and a pending IOU action w/ type SPLIT
- expect(Object.values(groupReportActions ?? {}).length).toBe(2);
- groupCreatedAction =
- Object.values(groupReportActions ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) ?? null;
- groupIOUAction =
- Object.values(groupReportActions ?? {}).find(
- (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
- ) ?? null;
- expect(groupCreatedAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(groupIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(groupIOUAction?.originalMessage).not.toHaveProperty('IOUReportID');
- expect(groupIOUAction?.originalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.SPLIT);
- expect(Date.parse(groupCreatedAction?.created ?? '')).toBeLessThanOrEqual(Date.parse(groupIOUAction?.created ?? ''));
-
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
- /* There should be 5 transactions
- * – one existing one with Jules
- * - one for each of the three IOU reports
- * - one on the group chat w/ deleted report
- */
- expect(Object.values(allTransactions ?? {}).length).toBe(5);
- expect(allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${julesExistingTransaction?.transactionID}`]).toBeTruthy();
-
- carlosTransaction =
- Object.values(allTransactions ?? {}).find(
- (transaction) => transaction?.transactionID === (carlosIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID,
- ) ?? null;
- julesTransaction =
- Object.values(allTransactions ?? {}).find(
- (transaction) => transaction?.transactionID === (julesIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID,
- ) ?? null;
- vitTransaction =
- Object.values(allTransactions ?? {}).find(
- (transaction) => transaction?.transactionID === (vitIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID,
- ) ?? null;
- groupTransaction = Object.values(allTransactions ?? {}).find((transaction) => transaction?.reportID === CONST.REPORT.SPLIT_REPORTID) ?? null;
+ // There should now be 10 reports
+ expect(Object.values(allReports ?? {}).length).toBe(10);
+
+ // 1. The chat report with Rory + Carlos
+ carlosChatReport = Object.values(allReports ?? {}).find((report) => report?.reportID === carlosChatReport?.reportID) ?? null;
+ expect(isEmptyObject(carlosChatReport)).toBe(false);
+ expect(carlosChatReport?.pendingFields).toBeFalsy();
+
+ // 2. The IOU report with Rory + Carlos (new)
+ carlosIOUReport =
+ Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU && report.managerID === CARLOS_ACCOUNT_ID) ?? null;
+ expect(isEmptyObject(carlosIOUReport)).toBe(false);
+ expect(carlosIOUReport?.total).toBe(amount / 4);
+
+ // 3. The chat report with Rory + Jules
+ julesChatReport = Object.values(allReports ?? {}).find((report) => report?.reportID === julesChatReport?.reportID) ?? null;
+ expect(isEmptyObject(julesChatReport)).toBe(false);
+ expect(julesChatReport?.pendingFields).toBeFalsy();
+
+ // 4. The IOU report with Rory + Jules
+ julesIOUReport = Object.values(allReports ?? {}).find((report) => report?.reportID === julesIOUReport?.reportID) ?? null;
+ expect(isEmptyObject(julesIOUReport)).toBe(false);
+ expect(julesChatReport?.pendingFields).toBeFalsy();
+ expect(julesIOUReport?.total).toBe((julesExistingTransaction?.amount ?? 0) + amount / 4);
+
+ // 5. The chat report with Rory + Vit (new)
+ vitChatReport =
+ Object.values(allReports ?? {}).find(
+ (report) =>
+ report?.type === CONST.REPORT.TYPE.CHAT &&
+ isEqual(report.participants, {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [VIT_ACCOUNT_ID]: VIT_PARTICIPANT}),
+ ) ?? null;
+ expect(isEmptyObject(vitChatReport)).toBe(false);
+ expect(vitChatReport?.pendingFields).toStrictEqual({createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD});
+
+ // 6. The IOU report with Rory + Vit (new)
+ vitIOUReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU && report.managerID === VIT_ACCOUNT_ID) ?? null;
+ expect(isEmptyObject(vitIOUReport)).toBe(false);
+ expect(vitIOUReport?.total).toBe(amount / 4);
+
+ // 7. The group chat with everyone
+ groupChat =
+ Object.values(allReports ?? {}).find(
+ (report) =>
+ report?.type === CONST.REPORT.TYPE.CHAT &&
+ isEqual(report.participants, {
+ [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT,
+ [JULES_ACCOUNT_ID]: JULES_PARTICIPANT,
+ [VIT_ACCOUNT_ID]: VIT_PARTICIPANT,
+ [RORY_ACCOUNT_ID]: RORY_PARTICIPANT,
+ }),
+ ) ?? null;
+ expect(isEmptyObject(groupChat)).toBe(false);
+ expect(groupChat?.pendingFields).toStrictEqual({createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD});
- expect(carlosTransaction?.reportID).toBe(carlosIOUReport?.reportID);
- expect(julesTransaction?.reportID).toBe(julesIOUReport?.reportID);
- expect(vitTransaction?.reportID).toBe(vitIOUReport?.reportID);
- expect(groupTransaction).toBeTruthy();
+ // The 1:1 chat reports and the IOU reports should be linked together
+ expect(carlosChatReport?.iouReportID).toBe(carlosIOUReport?.reportID);
+ expect(carlosIOUReport?.chatReportID).toBe(carlosChatReport?.reportID);
+ expect(carlosIOUReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
- expect(carlosTransaction?.amount).toBe(amount / 4);
- expect(julesTransaction?.amount).toBe(amount / 4);
- expect(vitTransaction?.amount).toBe(amount / 4);
- expect(groupTransaction?.amount).toBe(amount);
+ expect(julesChatReport?.iouReportID).toBe(julesIOUReport?.reportID);
+ expect(julesIOUReport?.chatReportID).toBe(julesChatReport?.reportID);
- expect(carlosTransaction?.comment.comment).toBe(comment);
- expect(julesTransaction?.comment.comment).toBe(comment);
- expect(vitTransaction?.comment.comment).toBe(comment);
- expect(groupTransaction?.comment.comment).toBe(comment);
+ expect(vitChatReport?.iouReportID).toBe(vitIOUReport?.reportID);
+ expect(vitIOUReport?.chatReportID).toBe(vitChatReport?.reportID);
+ expect(carlosIOUReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
- expect(carlosTransaction?.merchant).toBe(merchant);
- expect(julesTransaction?.merchant).toBe(merchant);
- expect(vitTransaction?.merchant).toBe(merchant);
- expect(groupTransaction?.merchant).toBe(merchant);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (allReportActions) => {
+ Onyx.disconnect(connectionID);
- expect(carlosTransaction?.comment.source).toBe(CONST.IOU.TYPE.SPLIT);
- expect(julesTransaction?.comment.source).toBe(CONST.IOU.TYPE.SPLIT);
- expect(vitTransaction?.comment.source).toBe(CONST.IOU.TYPE.SPLIT);
+ // There should be reportActions on all 7 chat reports + 3 IOU reports in each 1:1 chat
+ expect(Object.values(allReportActions ?? {}).length).toBe(10);
+
+ const carlosReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${carlosChatReport?.iouReportID}`];
+ const julesReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${julesChatReport?.iouReportID}`];
+ const vitReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${vitChatReport?.iouReportID}`];
+ const groupReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${groupChat?.reportID}`];
+
+ // Carlos DM should have two reportActions – the existing CREATED action and a pending IOU action
+ expect(Object.values(carlosReportActions ?? {}).length).toBe(2);
+ carlosIOUCreatedAction =
+ Object.values(carlosReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
+ ) ?? null;
+ carlosIOUAction =
+ Object.values(carlosReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
+ ) ?? null;
+ expect(carlosIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(carlosIOUAction?.originalMessage.IOUReportID).toBe(carlosIOUReport?.reportID);
+ expect(carlosIOUAction?.originalMessage.amount).toBe(amount / 4);
+ expect(carlosIOUAction?.originalMessage.comment).toBe(comment);
+ expect(carlosIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
+ expect(Date.parse(carlosIOUCreatedAction?.created ?? '')).toBeLessThan(Date.parse(carlosIOUAction?.created ?? ''));
+
+ // Jules DM should have three reportActions, the existing CREATED action, the existing IOU action, and a new pending IOU action
+ expect(Object.values(julesReportActions ?? {}).length).toBe(3);
+ expect(julesReportActions?.[julesCreatedAction.reportActionID]).toStrictEqual(julesCreatedAction);
+ julesIOUCreatedAction =
+ Object.values(julesReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
+ ) ?? null;
+ julesIOUAction =
+ Object.values(julesReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU =>
+ reportAction.reportActionID !== julesCreatedAction.reportActionID && reportAction.reportActionID !== julesExistingIOUAction.reportActionID,
+ ) ?? null;
+ expect(julesIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(julesIOUAction?.originalMessage.IOUReportID).toBe(julesIOUReport?.reportID);
+ expect(julesIOUAction?.originalMessage.amount).toBe(amount / 4);
+ expect(julesIOUAction?.originalMessage.comment).toBe(comment);
+ expect(julesIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
+ expect(Date.parse(julesIOUCreatedAction?.created ?? '')).toBeLessThan(Date.parse(julesIOUAction?.created ?? ''));
+
+ // Vit DM should have two reportActions – a pending CREATED action and a pending IOU action
+ expect(Object.values(vitReportActions ?? {}).length).toBe(2);
+ vitCreatedAction =
+ Object.values(vitReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED,
+ ) ?? null;
+ vitIOUAction =
+ Object.values(vitReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
+ ) ?? null;
+ expect(vitCreatedAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(vitIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(vitIOUAction?.originalMessage.IOUReportID).toBe(vitIOUReport?.reportID);
+ expect(vitIOUAction?.originalMessage.amount).toBe(amount / 4);
+ expect(vitIOUAction?.originalMessage.comment).toBe(comment);
+ expect(vitIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE);
+ expect(Date.parse(vitCreatedAction?.created ?? '')).toBeLessThan(Date.parse(vitIOUAction?.created ?? ''));
+
+ // Group chat should have two reportActions – a pending CREATED action and a pending IOU action w/ type SPLIT
+ expect(Object.values(groupReportActions ?? {}).length).toBe(2);
+ groupCreatedAction =
+ Object.values(groupReportActions ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) ?? null;
+ groupIOUAction =
+ Object.values(groupReportActions ?? {}).find(
+ (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
+ ) ?? null;
+ expect(groupCreatedAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(groupIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(groupIOUAction?.originalMessage).not.toHaveProperty('IOUReportID');
+ expect(groupIOUAction?.originalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.SPLIT);
+ expect(Date.parse(groupCreatedAction?.created ?? '')).toBeLessThanOrEqual(Date.parse(groupIOUAction?.created ?? ''));
- expect(carlosTransaction?.comment.originalTransactionID).toBe(groupTransaction?.transactionID);
- expect(julesTransaction?.comment.originalTransactionID).toBe(groupTransaction?.transactionID);
- expect(vitTransaction?.comment.originalTransactionID).toBe(groupTransaction?.transactionID);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
- expect(carlosTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(julesTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(vitTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- expect(groupTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ /* There should be 5 transactions
+ * – one existing one with Jules
+ * - one for each of the three IOU reports
+ * - one on the group chat w/ deleted report
+ */
+ expect(Object.values(allTransactions ?? {}).length).toBe(5);
+ expect(allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${julesExistingTransaction?.transactionID}`]).toBeTruthy();
+
+ carlosTransaction =
+ Object.values(allTransactions ?? {}).find(
+ (transaction) => transaction?.transactionID === (carlosIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID,
+ ) ?? null;
+ julesTransaction =
+ Object.values(allTransactions ?? {}).find(
+ (transaction) => transaction?.transactionID === (julesIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID,
+ ) ?? null;
+ vitTransaction =
+ Object.values(allTransactions ?? {}).find(
+ (transaction) => transaction?.transactionID === (vitIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID,
+ ) ?? null;
+ groupTransaction = Object.values(allTransactions ?? {}).find((transaction) => transaction?.reportID === CONST.REPORT.SPLIT_REPORTID) ?? null;
+
+ expect(carlosTransaction?.reportID).toBe(carlosIOUReport?.reportID);
+ expect(julesTransaction?.reportID).toBe(julesIOUReport?.reportID);
+ expect(vitTransaction?.reportID).toBe(vitIOUReport?.reportID);
+ expect(groupTransaction).toBeTruthy();
+
+ expect(carlosTransaction?.amount).toBe(amount / 4);
+ expect(julesTransaction?.amount).toBe(amount / 4);
+ expect(vitTransaction?.amount).toBe(amount / 4);
+ expect(groupTransaction?.amount).toBe(amount);
+
+ expect(carlosTransaction?.comment.comment).toBe(comment);
+ expect(julesTransaction?.comment.comment).toBe(comment);
+ expect(vitTransaction?.comment.comment).toBe(comment);
+ expect(groupTransaction?.comment.comment).toBe(comment);
+
+ expect(carlosTransaction?.merchant).toBe(merchant);
+ expect(julesTransaction?.merchant).toBe(merchant);
+ expect(vitTransaction?.merchant).toBe(merchant);
+ expect(groupTransaction?.merchant).toBe(merchant);
+
+ expect(carlosTransaction?.comment.source).toBe(CONST.IOU.TYPE.SPLIT);
+ expect(julesTransaction?.comment.source).toBe(CONST.IOU.TYPE.SPLIT);
+ expect(vitTransaction?.comment.source).toBe(CONST.IOU.TYPE.SPLIT);
+
+ expect(carlosTransaction?.comment.originalTransactionID).toBe(groupTransaction?.transactionID);
+ expect(julesTransaction?.comment.originalTransactionID).toBe(groupTransaction?.transactionID);
+ expect(vitTransaction?.comment.originalTransactionID).toBe(groupTransaction?.transactionID);
+
+ expect(carlosTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(julesTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(vitTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ expect(groupTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.PERSONAL_DETAILS_LIST,
- waitForCollectionCallback: false,
- callback: (allPersonalDetails) => {
- Onyx.disconnect(connectionID);
- expect(allPersonalDetails).toMatchObject({
- [VIT_ACCOUNT_ID]: {
- accountID: VIT_ACCOUNT_ID,
- displayName: VIT_EMAIL,
- login: VIT_EMAIL,
- },
- });
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForNetworkPromises)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
- Object.values(allReports ?? {}).forEach((report) => {
- if (!report?.pendingFields) {
- return;
- }
- Object.values(report?.pendingFields).forEach((pendingField) => expect(pendingField).toBeFalsy());
- });
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (allReportActions) => {
- Onyx.disconnect(connectionID);
- Object.values(allReportActions ?? {}).forEach((reportAction) => expect(reportAction?.pendingAction).toBeFalsy());
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
- Object.values(allTransactions ?? {}).forEach((transaction) => expect(transaction?.pendingAction).toBeFalsy());
- resolve();
- },
- });
- }),
- )
- );
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.PERSONAL_DETAILS_LIST,
+ waitForCollectionCallback: false,
+ callback: (allPersonalDetails) => {
+ Onyx.disconnect(connectionID);
+ expect(allPersonalDetails).toMatchObject({
+ [VIT_ACCOUNT_ID]: {
+ accountID: VIT_ACCOUNT_ID,
+ displayName: VIT_EMAIL,
+ login: VIT_EMAIL,
+ },
+ });
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(waitForNetworkPromises)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
+ Object.values(allReports ?? {}).forEach((report) => {
+ if (!report?.pendingFields) {
+ return;
+ }
+ Object.values(report?.pendingFields).forEach((pendingField) => expect(pendingField).toBeFalsy());
+ });
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (allReportActions) => {
+ Onyx.disconnect(connectionID);
+ Object.values(allReportActions ?? {}).forEach((reportAction) => expect(reportAction?.pendingAction).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+ Object.values(allTransactions ?? {}).forEach((transaction) => expect(transaction?.pendingAction).toBeFalsy());
+ resolve();
+ },
+ });
+ }),
+ );
});
});
@@ -1426,189 +1405,185 @@ describe('actions/IOU', () => {
let payIOUAction: OnyxEntry;
let transaction: OnyxEntry;
IOU.requestMoney({reportID: ''}, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
- return (
- waitForBatchedUpdates()
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
+ return waitForBatchedUpdates()
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
- expect(Object.values(allReports ?? {}).length).toBe(3);
+ expect(Object.values(allReports ?? {}).length).toBe(3);
- const chatReports = Object.values(allReports ?? {}).filter((report) => report?.type === CONST.REPORT.TYPE.CHAT);
- chatReport = chatReports[0];
- expect(chatReport).toBeTruthy();
- expect(chatReport).toHaveProperty('reportID');
- expect(chatReport).toHaveProperty('iouReportID');
+ const chatReports = Object.values(allReports ?? {}).filter((report) => report?.type === CONST.REPORT.TYPE.CHAT);
+ chatReport = chatReports[0];
+ expect(chatReport).toBeTruthy();
+ expect(chatReport).toHaveProperty('reportID');
+ expect(chatReport).toHaveProperty('iouReportID');
- iouReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU) ?? null;
- expect(iouReport).toBeTruthy();
- expect(iouReport).toHaveProperty('reportID');
- expect(iouReport).toHaveProperty('chatReportID');
+ iouReport = Object.values(allReports ?? {}).find((report) => report?.type === CONST.REPORT.TYPE.IOU) ?? null;
+ expect(iouReport).toBeTruthy();
+ expect(iouReport).toHaveProperty('reportID');
+ expect(iouReport).toHaveProperty('chatReportID');
- expect(chatReport?.iouReportID).toBe(iouReport?.reportID);
- expect(iouReport?.chatReportID).toBe(chatReport?.reportID);
+ expect(chatReport?.iouReportID).toBe(iouReport?.reportID);
+ expect(iouReport?.chatReportID).toBe(chatReport?.reportID);
- expect(chatReport?.pendingFields).toBeFalsy();
- expect(iouReport?.pendingFields).toBeFalsy();
+ expect(chatReport?.pendingFields).toBeFalsy();
+ expect(iouReport?.pendingFields).toBeFalsy();
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (allReportActions) => {
- Onyx.disconnect(connectionID);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (allReportActions) => {
+ Onyx.disconnect(connectionID);
- const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.iouReportID}`];
+ const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.iouReportID}`];
- createIOUAction =
- Object.values(reportActionsForIOUReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
- expect(createIOUAction).toBeTruthy();
- expect((createIOUAction?.originalMessage as IOUMessage)?.IOUReportID).toBe(iouReport?.reportID);
+ createIOUAction =
+ Object.values(reportActionsForIOUReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) ?? null;
+ expect(createIOUAction).toBeTruthy();
+ expect((createIOUAction?.originalMessage as IOUMessage)?.IOUReportID).toBe(iouReport?.reportID);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- waitForCollectionCallback: true,
- callback: (allTransactions) => {
- Onyx.disconnect(connectionID);
- expect(Object.values(allTransactions ?? {}).length).toBe(1);
- transaction = Object.values(allTransactions ?? {}).find((t) => t) ?? null;
- expect(transaction).toBeTruthy();
- expect(transaction?.amount).toBe(amount);
- expect(transaction?.reportID).toBe(iouReport?.reportID);
- expect((createIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(transaction?.transactionID);
- resolve();
- },
- });
- }),
- )
- .then(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
- if (chatReport && iouReport) {
- IOU.payMoneyRequest(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport);
- }
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ waitForCollectionCallback: true,
+ callback: (allTransactions) => {
+ Onyx.disconnect(connectionID);
+ expect(Object.values(allTransactions ?? {}).length).toBe(1);
+ transaction = Object.values(allTransactions ?? {}).find((t) => t) ?? null;
+ expect(transaction).toBeTruthy();
+ expect(transaction?.amount).toBe(amount);
+ expect(transaction?.reportID).toBe(iouReport?.reportID);
+ expect((createIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(transaction?.transactionID);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(() => {
+ mockFetch?.pause?.();
+ if (chatReport && iouReport) {
+ IOU.payMoneyRequest(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport);
+ }
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
- expect(Object.values(allReports ?? {}).length).toBe(3);
+ expect(Object.values(allReports ?? {}).length).toBe(3);
- chatReport = Object.values(allReports ?? {}).find((r) => r?.type === CONST.REPORT.TYPE.CHAT) ?? null;
- iouReport = Object.values(allReports ?? {}).find((r) => r?.type === CONST.REPORT.TYPE.IOU) ?? null;
+ chatReport = Object.values(allReports ?? {}).find((r) => r?.type === CONST.REPORT.TYPE.CHAT) ?? null;
+ iouReport = Object.values(allReports ?? {}).find((r) => r?.type === CONST.REPORT.TYPE.IOU) ?? null;
- expect(chatReport?.iouReportID).toBeFalsy();
+ expect(chatReport?.iouReportID).toBeFalsy();
- // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.REIMBURSED);
- // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED);
+ // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.REIMBURSED);
+ // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (allReportActions) => {
- Onyx.disconnect(connectionID);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (allReportActions) => {
+ Onyx.disconnect(connectionID);
- const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`];
- expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(3);
+ const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`];
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(3);
- payIOUAction =
- Object.values(reportActionsForIOUReport ?? {}).find(
- (reportAction) =>
- reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction?.originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY,
- ) ?? null;
- expect(payIOUAction).toBeTruthy();
- expect(payIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ payIOUAction =
+ Object.values(reportActionsForIOUReport ?? {}).find(
+ (reportAction) =>
+ reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction?.originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY,
+ ) ?? null;
+ expect(payIOUAction).toBeTruthy();
+ expect(payIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT,
- waitForCollectionCallback: true,
- callback: (allReports) => {
- Onyx.disconnect(connectionID);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
+ waitForCollectionCallback: true,
+ callback: (allReports) => {
+ Onyx.disconnect(connectionID);
- expect(Object.values(allReports ?? {}).length).toBe(3);
+ expect(Object.values(allReports ?? {}).length).toBe(3);
- chatReport = Object.values(allReports ?? {}).find((r) => r?.type === CONST.REPORT.TYPE.CHAT) ?? null;
- iouReport = Object.values(allReports ?? {}).find((r) => r?.type === CONST.REPORT.TYPE.IOU) ?? null;
+ chatReport = Object.values(allReports ?? {}).find((r) => r?.type === CONST.REPORT.TYPE.CHAT) ?? null;
+ iouReport = Object.values(allReports ?? {}).find((r) => r?.type === CONST.REPORT.TYPE.IOU) ?? null;
- expect(chatReport?.iouReportID).toBeFalsy();
+ expect(chatReport?.iouReportID).toBeFalsy();
- // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.REIMBURSED);
- // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED);
+ // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.REIMBURSED);
+ // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED);
- resolve();
- },
- });
- }),
- )
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- waitForCollectionCallback: true,
- callback: (allReportActions) => {
- Onyx.disconnect(connectionID);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ waitForCollectionCallback: true,
+ callback: (allReportActions) => {
+ Onyx.disconnect(connectionID);
- const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`];
- expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(3);
+ const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport?.reportID}`];
+ expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(3);
- payIOUAction =
- Object.values(reportActionsForIOUReport ?? {}).find(
- (reportAction) =>
- reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY,
- ) ?? null;
- expect(payIOUAction).toBeTruthy();
+ payIOUAction =
+ Object.values(reportActionsForIOUReport ?? {}).find(
+ (reportAction) =>
+ reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY,
+ ) ?? null;
+ expect(payIOUAction).toBeTruthy();
- resolve();
- },
- });
- }),
- )
- );
+ resolve();
+ },
+ });
+ }),
+ );
});
});
@@ -1618,8 +1593,7 @@ describe('actions/IOU', () => {
const merchant = 'NASDAQ';
afterEach(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.resume();
+ mockFetch?.resume?.();
});
it('updates the IOU request and IOU report when offline', () => {
@@ -1628,8 +1602,7 @@ describe('actions/IOU', () => {
let iouAction: OnyxEntry = null;
let transaction: OnyxEntry = null;
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
IOU.requestMoney({reportID: ''}, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {});
return waitForBatchedUpdates()
.then(() => {
@@ -1772,8 +1745,7 @@ describe('actions/IOU', () => {
}),
)
.then(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.resume();
+ mockFetch?.resume?.();
});
});
@@ -1839,8 +1811,7 @@ describe('actions/IOU', () => {
return waitForBatchedUpdates();
})
.then(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.fail();
+ mockFetch?.fail?.();
if (transaction) {
IOU.editMoneyRequest(
@@ -1933,16 +1904,14 @@ describe('actions/IOU', () => {
const merchant = 'NASDAQ';
afterEach(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.resume();
+ mockFetch?.resume?.();
});
it('updates the expense request and expense report when paid while offline', () => {
let expenseReport: OnyxEntry;
let chatReport: OnyxEntry;
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
Onyx.set(ONYXKEYS.SESSION, {email: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID});
return waitForBatchedUpdates()
.then(() => {
@@ -2099,8 +2068,7 @@ describe('actions/IOU', () => {
}),
)
.then(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.fail();
+ mockFetch?.fail?.();
if (chatReport && expenseReport) {
IOU.payMoneyRequest('ACH', chatReport, expenseReport);
}
@@ -2250,8 +2218,7 @@ describe('actions/IOU', () => {
it('delete an expense (IOU Action and transaction) successfully', async () => {
// Given the fetch operations are paused and an expense is initiated
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
if (transaction && createIOUAction) {
// When the expense is deleted
@@ -2290,8 +2257,7 @@ describe('actions/IOU', () => {
expect(t).toBeFalsy();
// Given fetch operations are resumed
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.resume();
+ mockFetch?.resume?.();
await waitForBatchedUpdates();
// Then we recheck the IOU report action from the report actions collection
@@ -2326,8 +2292,7 @@ describe('actions/IOU', () => {
it('delete the IOU report when there are no visible comments left in the IOU report', async () => {
// Given an IOU report and a paused fetch state
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
if (transaction && createIOUAction) {
// When the IOU expense is deleted
@@ -2350,8 +2315,7 @@ describe('actions/IOU', () => {
expect(report).toBeTruthy();
// Given the resumed fetch state
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.resume();
+ mockFetch?.resume?.();
await waitForBatchedUpdates();
report = await new Promise>((resolve) => {
@@ -2403,8 +2367,7 @@ describe('actions/IOU', () => {
expect(resultActionAfterUpdate?.pendingAction).toBeUndefined();
// When we attempt to delete an expense from the IOU report
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
if (transaction && createIOUAction) {
IOU.deleteMoneyRequest(transaction?.transactionID, createIOUAction, false);
}
@@ -2430,8 +2393,7 @@ describe('actions/IOU', () => {
expect(iouReport).toHaveProperty('chatReportID');
// Given the resumed fetch state
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.resume();
+ mockFetch?.resume?.();
allReports = await new Promise>((resolve) => {
const connectionID = Onyx.connect({
@@ -2495,8 +2457,7 @@ describe('actions/IOU', () => {
await waitForBatchedUpdates();
// Given Fetch is paused and timers have advanced
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
jest.advanceTimersByTime(10);
if (transaction && createIOUAction) {
@@ -2518,8 +2479,7 @@ describe('actions/IOU', () => {
});
expect(report).toBeFalsy();
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.resume();
+ mockFetch?.resume?.();
// Then After resuming fetch, the report for the given thread ID still does not exist
report = await new Promise>((resolve) => {
@@ -2687,8 +2647,7 @@ describe('actions/IOU', () => {
const resultActionAfter = reportActions?.[reportActionID];
expect(resultActionAfter?.pendingAction).toBeUndefined();
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
if (transaction && createIOUAction) {
// When deleting expense
@@ -2711,8 +2670,7 @@ describe('actions/IOU', () => {
// When fetch resumes
// Then the transaction thread report should still exist
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.resume();
+ mockFetch?.resume?.();
await new Promise((resolve) => {
const connectionID = Onyx.connect({
key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`,
@@ -2835,8 +2793,7 @@ describe('actions/IOU', () => {
// Verify that our action is no longer in the loading state
expect(resultActionAfterUpdate?.pendingAction).toBeUndefined();
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
if (transaction && createIOUAction) {
// When we delete the expense
IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false);
@@ -2859,8 +2816,7 @@ describe('actions/IOU', () => {
});
// When we resume fetch
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.resume();
+ mockFetch?.resume?.();
// Then we expect the moneyRequestPreview to show [Deleted expense]
@@ -2909,8 +2865,7 @@ describe('actions/IOU', () => {
expect(ioupreview?.message?.[0]?.text).toBe('rory@expensifail.com owes $300.00');
// When we delete the first expense
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
jest.advanceTimersByTime(10);
if (transaction && createIOUAction) {
IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false);
@@ -2925,8 +2880,7 @@ describe('actions/IOU', () => {
expect(iouReport?.total).toBe(20000);
// When we resume
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.resume();
+ mockFetch?.resume?.();
// Then we expect the IOU report and reportPreview to update with new totals
expect(iouReport).toBeTruthy();
@@ -3002,8 +2956,7 @@ describe('actions/IOU', () => {
// When we delete the expense in SingleTransactionView and we should not delete the IOU report
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
if (transaction && createIOUAction) {
IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, true);
@@ -3028,8 +2981,7 @@ describe('actions/IOU', () => {
expect(iouReport).toHaveProperty('reportID');
expect(iouReport).toHaveProperty('chatReportID');
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.resume();
+ mockFetch?.resume?.();
allReports = await new Promise>((resolve) => {
const connectionID = Onyx.connect({
@@ -3355,8 +3307,7 @@ describe('actions/IOU', () => {
}),
)
.then(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.fail();
+ mockFetch?.fail?.();
if (expenseReport) {
IOU.submitReport(expenseReport);
}
diff --git a/tests/actions/PolicyCategoryTest.ts b/tests/actions/PolicyCategoryTest.ts
new file mode 100644
index 000000000000..2817a1661db4
--- /dev/null
+++ b/tests/actions/PolicyCategoryTest.ts
@@ -0,0 +1,255 @@
+import Onyx from 'react-native-onyx';
+import CONST from '@src/CONST';
+import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager';
+import * as Policy from '@src/libs/actions/Policy';
+import ONYXKEYS from '@src/ONYXKEYS';
+import createRandomPolicy from '../utils/collections/policies';
+import createRandomPolicyCategories from '../utils/collections/policyCategory';
+import * as TestHelper from '../utils/TestHelper';
+import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
+
+OnyxUpdateManager();
+describe('actions/PolicyCategory', () => {
+ beforeAll(() => {
+ Onyx.init({
+ keys: ONYXKEYS,
+ });
+ });
+
+ beforeEach(() => {
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ global.fetch = TestHelper.getGlobalFetchMock();
+ return Onyx.clear().then(waitForBatchedUpdates);
+ });
+
+ describe('setWorkspaceRequiresCategory', () => {
+ it('Enable require category', async () => {
+ const fakePolicy = createRandomPolicy(0);
+ fakePolicy.requiresCategory = false;
+
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy);
+ Policy.setWorkspaceRequiresCategory(fakePolicy.id, true);
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ // Check if policy requiresCategory was updated with correct values
+ expect(policy?.requiresCategory).toBeTruthy();
+ expect(policy?.pendingFields?.requiresCategory).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+ expect(policy?.errors?.requiresCategory).toBeFalsy();
+ resolve();
+ },
+ });
+ });
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ await fetch.resume();
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ // Check if the policy pendingFields was cleared
+ expect(policy?.pendingFields?.requiresCategory).toBeFalsy();
+ resolve();
+ },
+ });
+ });
+ });
+ });
+ describe('createWorkspaceCategories', () => {
+ it('Create a new policy category', async () => {
+ const fakePolicy = createRandomPolicy(0);
+ const fakeCategories = createRandomPolicyCategories(3);
+ const newCategoryName = 'New category';
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy);
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories);
+ Policy.createPolicyCategory(fakePolicy.id, newCategoryName);
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyCategories) => {
+ Onyx.disconnect(connectionID);
+ const newCategory = policyCategories?.[newCategoryName];
+
+ expect(newCategory?.name).toBe(newCategoryName);
+ expect(newCategory?.errors).toBeFalsy();
+
+ resolve();
+ },
+ });
+ });
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ await fetch.resume();
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyCategories) => {
+ Onyx.disconnect(connectionID);
+
+ const newCategory = policyCategories?.[newCategoryName];
+ expect(newCategory?.errors).toBeFalsy();
+ expect(newCategory?.pendingAction).toBeFalsy();
+
+ resolve();
+ },
+ });
+ });
+ });
+ });
+ describe('renameWorkspaceCategory', () => {
+ it('Rename category', async () => {
+ const fakePolicy = createRandomPolicy(0);
+ const fakeCategories = createRandomPolicyCategories(3);
+ const oldCategoryName = Object.keys(fakeCategories)[0];
+ const newCategoryName = 'Updated category';
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy);
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories);
+ Policy.renamePolicyCategory(fakePolicy.id, {
+ oldName: oldCategoryName,
+ newName: newCategoryName,
+ });
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyCategories) => {
+ Onyx.disconnect(connectionID);
+
+ expect(policyCategories?.[oldCategoryName]).toBeFalsy();
+ expect(policyCategories?.[newCategoryName]?.name).toBe(newCategoryName);
+ expect(policyCategories?.[newCategoryName]?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+ expect(policyCategories?.[newCategoryName]?.pendingFields?.name).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+
+ resolve();
+ },
+ });
+ });
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ await fetch.resume();
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyCategories) => {
+ Onyx.disconnect(connectionID);
+
+ expect(policyCategories?.[newCategoryName]?.pendingAction).toBeFalsy();
+ expect(policyCategories?.[newCategoryName]?.pendingFields?.name).toBeFalsy();
+
+ resolve();
+ },
+ });
+ });
+ });
+ });
+ describe('setWorkspaceCategoriesEnabled', () => {
+ it('Enable category', async () => {
+ const fakePolicy = createRandomPolicy(0);
+ const fakeCategories = createRandomPolicyCategories(3);
+ const categoryNameToUpdate = Object.keys(fakeCategories)[0];
+ const categoriesToUpdate = {
+ [categoryNameToUpdate]: {
+ name: categoryNameToUpdate,
+ enabled: true,
+ },
+ };
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy);
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories);
+ Policy.setWorkspaceCategoryEnabled(fakePolicy.id, categoriesToUpdate);
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyCategories) => {
+ Onyx.disconnect(connectionID);
+
+ expect(policyCategories?.[categoryNameToUpdate]?.enabled).toBeTruthy();
+ expect(policyCategories?.[categoryNameToUpdate]?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+ expect(policyCategories?.[categoryNameToUpdate]?.pendingFields?.enabled).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+ expect(policyCategories?.[categoryNameToUpdate]?.errors).toBeFalsy();
+ resolve();
+ },
+ });
+ });
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ await fetch.resume();
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyCategories) => {
+ Onyx.disconnect(connectionID);
+
+ expect(policyCategories?.[categoryNameToUpdate]?.pendingAction).toBeFalsy();
+ expect(policyCategories?.[categoryNameToUpdate]?.pendingFields?.enabled).toBeFalsy();
+
+ resolve();
+ },
+ });
+ });
+ });
+ });
+
+ describe('deleteWorkspaceCategories', () => {
+ it('Delete category', async () => {
+ const fakePolicy = createRandomPolicy(0);
+ const fakeCategories = createRandomPolicyCategories(3);
+ const categoryNameToDelete = Object.keys(fakeCategories)[0];
+ const categoriesToDelete = [categoryNameToDelete];
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy);
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories);
+ Policy.deleteWorkspaceCategories(fakePolicy.id, categoriesToDelete);
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyCategories) => {
+ Onyx.disconnect(connectionID);
+
+ expect(policyCategories?.[categoryNameToDelete]?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
+ resolve();
+ },
+ });
+ });
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ await fetch.resume();
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyCategories) => {
+ Onyx.disconnect(connectionID);
+ expect(policyCategories?.[categoryNameToDelete]).toBeFalsy();
+
+ resolve();
+ },
+ });
+ });
+ });
+ });
+});
diff --git a/tests/actions/PolicyMemberTest.ts b/tests/actions/PolicyMemberTest.ts
new file mode 100644
index 000000000000..8d982d4a1892
--- /dev/null
+++ b/tests/actions/PolicyMemberTest.ts
@@ -0,0 +1,238 @@
+import Onyx from 'react-native-onyx';
+import CONST from '@src/CONST';
+import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager';
+import * as Policy from '@src/libs/actions/Policy';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Policy as PolicyType, Report, ReportAction} from '@src/types/onyx';
+import type {OriginalMessageJoinPolicyChangeLog} from '@src/types/onyx/OriginalMessage';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
+import createPersonalDetails from '../utils/collections/personalDetails';
+import createRandomPolicy from '../utils/collections/policies';
+import createRandomReportAction from '../utils/collections/reportActions';
+import createRandomReport from '../utils/collections/reports';
+import * as TestHelper from '../utils/TestHelper';
+import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
+
+OnyxUpdateManager();
+describe('actions/PolicyMember', () => {
+ beforeAll(() => {
+ Onyx.init({
+ keys: ONYXKEYS,
+ });
+ });
+
+ beforeEach(() => {
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ global.fetch = TestHelper.getGlobalFetchMock();
+ return Onyx.clear().then(waitForBatchedUpdates);
+ });
+
+ describe('acceptJoinRequest', () => {
+ it('Accept user join request to a workspace', async () => {
+ const fakePolicy = createRandomPolicy(0);
+ const fakeReport: Report = {
+ ...createRandomReport(0),
+ policyID: fakePolicy.id,
+ };
+ const fakeReportAction: ReportAction = {
+ ...createRandomReportAction(0),
+ actionName: CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_JOIN_REQUEST,
+ } as ReportAction;
+
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy);
+ Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${fakeReport.reportID}`, fakeReport);
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fakeReport.reportID}`, {
+ [fakeReportAction.reportActionID]: fakeReportAction,
+ });
+ Policy.acceptJoinRequest(fakeReport.reportID, fakeReportAction);
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fakeReport.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActions) => {
+ Onyx.disconnect(connectionID);
+
+ const reportAction = reportActions?.[fakeReportAction.reportActionID];
+
+ if (!isEmptyObject(reportAction)) {
+ expect((reportAction.originalMessage as OriginalMessageJoinPolicyChangeLog['originalMessage'])?.choice)?.toBe(
+ CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT,
+ );
+ expect(reportAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+ }
+ resolve();
+ },
+ });
+ });
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ await fetch.resume();
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fakeReport.reportID}`,
+ waitForCollectionCallback: false,
+ callback: (reportActions) => {
+ Onyx.disconnect(connectionID);
+
+ const reportAction = reportActions?.[fakeReportAction.reportActionID];
+
+ if (!isEmptyObject(reportAction)) {
+ expect(reportAction?.pendingAction).toBeFalsy();
+ }
+ resolve();
+ },
+ });
+ });
+ });
+ });
+ describe('updateWorkspaceMembersRole', () => {
+ it('Update member to admin role', async () => {
+ const fakeUser2 = createPersonalDetails(2);
+ const fakePolicy: PolicyType = {
+ ...createRandomPolicy(0),
+ employeeList: {
+ [fakeUser2.login ?? '']: {
+ email: fakeUser2.login,
+ role: CONST.POLICY.ROLE.USER,
+ },
+ },
+ };
+
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy);
+ Onyx.set(`${ONYXKEYS.PERSONAL_DETAILS_LIST}`, {[fakeUser2.accountID]: fakeUser2});
+ await waitForBatchedUpdates();
+ Policy.updateWorkspaceMembersRole(fakePolicy.id, [fakeUser2.accountID], CONST.POLICY.ROLE.ADMIN);
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ const employee = policy?.employeeList?.[fakeUser2?.login ?? ''];
+ expect(employee?.role).toBe(CONST.POLICY.ROLE.ADMIN);
+
+ resolve();
+ },
+ });
+ });
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ await fetch.resume();
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ const employee = policy?.employeeList?.[fakeUser2?.login ?? ''];
+ expect(employee?.pendingAction).toBeFalsy();
+ resolve();
+ },
+ });
+ });
+ });
+ });
+ describe('requestWorkspaceOwnerChange', () => {
+ it('Change the workspace`s owner', async () => {
+ const fakePolicy: PolicyType = createRandomPolicy(0);
+ const fakeEmail = 'fake@gmail.com';
+ const fakeAccountID = 1;
+
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy);
+ Onyx.merge(ONYXKEYS.SESSION, {email: fakeEmail, accountID: fakeAccountID});
+ Policy.requestWorkspaceOwnerChange(fakePolicy.id);
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ expect(policy?.errorFields).toBeFalsy();
+ expect(policy?.isLoading).toBeTruthy();
+ expect(policy?.isChangeOwnerSuccessful).toBeFalsy();
+ expect(policy?.isChangeOwnerFailed).toBeFalsy();
+ resolve();
+ },
+ });
+ });
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ await fetch.resume();
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ expect(policy?.isLoading).toBeFalsy();
+ expect(policy?.isChangeOwnerSuccessful).toBeTruthy();
+ expect(policy?.isChangeOwnerFailed)?.toBeFalsy();
+ resolve();
+ },
+ });
+ });
+ });
+ });
+ describe('addBillingCardAndRequestPolicyOwnerChange', () => {
+ it('Add billing card and change the workspace`s owner', async () => {
+ const fakePolicy: PolicyType = createRandomPolicy(0);
+ const fakeEmail = 'fake@gmail.com';
+ const fakeCard = {
+ cardNumber: '1234567890123456',
+ cardYear: '2023',
+ cardMonth: '05',
+ cardCVV: '123',
+ addressName: 'John Doe',
+ addressZip: '12345',
+ currency: 'USD',
+ };
+ const fakeAccountID = 1;
+
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy);
+ Onyx.merge(ONYXKEYS.SESSION, {email: fakeEmail, accountID: fakeAccountID});
+ Policy.addBillingCardAndRequestPolicyOwnerChange(fakePolicy.id, fakeCard);
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ expect(policy?.errorFields).toBeFalsy();
+ expect(policy?.isLoading).toBeTruthy();
+ expect(policy?.isChangeOwnerSuccessful).toBeFalsy();
+ expect(policy?.isChangeOwnerFailed).toBeFalsy();
+ resolve();
+ },
+ });
+ });
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ await fetch.resume();
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ expect(policy?.isLoading).toBeFalsy();
+ expect(policy?.isChangeOwnerSuccessful).toBeTruthy();
+ expect(policy?.isChangeOwnerFailed)?.toBeFalsy();
+ resolve();
+ },
+ });
+ });
+ });
+ });
+});
diff --git a/tests/actions/PolicyProfileTest.ts b/tests/actions/PolicyProfileTest.ts
new file mode 100644
index 000000000000..21ee34568100
--- /dev/null
+++ b/tests/actions/PolicyProfileTest.ts
@@ -0,0 +1,68 @@
+import Onyx from 'react-native-onyx';
+import CONST from '@src/CONST';
+import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager';
+import * as Policy from '@src/libs/actions/Policy';
+import * as ReportUtils from '@src/libs/ReportUtils';
+import ONYXKEYS from '@src/ONYXKEYS';
+import createRandomPolicy from '../utils/collections/policies';
+import * as TestHelper from '../utils/TestHelper';
+import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
+
+OnyxUpdateManager();
+describe('actions/PolicyProfile', () => {
+ beforeAll(() => {
+ Onyx.init({
+ keys: ONYXKEYS,
+ });
+ });
+
+ beforeEach(() => {
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ global.fetch = TestHelper.getGlobalFetchMock();
+ return Onyx.clear().then(waitForBatchedUpdates);
+ });
+
+ describe('updateWorkspaceDescription', () => {
+ it('Update workspace`s description', async () => {
+ const fakePolicy = createRandomPolicy(0);
+
+ const oldDescription = fakePolicy.description ?? '';
+ const newDescription = 'Updated description';
+ const parsedDescription = ReportUtils.getParsedComment(newDescription);
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ fetch.pause();
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy);
+ Policy.updateWorkspaceDescription(fakePolicy.id, newDescription, oldDescription);
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+
+ expect(policy?.description).toBe(parsedDescription);
+ expect(policy?.pendingFields?.description).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+ expect(policy?.errorFields?.description).toBeFalsy();
+ resolve();
+ },
+ });
+ });
+ // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
+ await fetch.resume();
+ await waitForBatchedUpdates();
+ await new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ expect(policy?.pendingFields?.description).toBeFalsy();
+
+ resolve();
+ },
+ });
+ });
+ });
+ });
+});
diff --git a/tests/actions/PolicyTagTest.ts b/tests/actions/PolicyTagTest.ts
index ff4439c392fa..74ea13f3d139 100644
--- a/tests/actions/PolicyTagTest.ts
+++ b/tests/actions/PolicyTagTest.ts
@@ -7,6 +7,7 @@ import type {PolicyTags} from '@src/types/onyx';
import createRandomPolicy from '../utils/collections/policies';
import createRandomPolicyTags from '../utils/collections/policyTags';
import * as TestHelper from '../utils/TestHelper';
+import type {MockFetch} from '../utils/TestHelper';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
OnyxUpdateManager();
@@ -17,9 +18,10 @@ describe('actions/Policy', () => {
});
});
+ let mockFetch: MockFetch;
beforeEach(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
global.fetch = TestHelper.getGlobalFetchMock();
+ mockFetch = fetch as MockFetch;
return Onyx.clear().then(waitForBatchedUpdates);
});
@@ -28,139 +30,127 @@ describe('actions/Policy', () => {
const fakePolicy = createRandomPolicy(0);
fakePolicy.requiresTag = false;
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
-
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
- .then(() => {
- Policy.setPolicyRequiresTag(fakePolicy.id, true);
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policy) => {
- Onyx.disconnect(connectionID);
-
- // RequiresTag is enabled and pending
- expect(policy?.requiresTag).toBeTruthy();
- expect(policy?.pendingFields?.requiresTag).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
-
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policy) => {
- Onyx.disconnect(connectionID);
- expect(policy?.pendingFields?.requiresTag).toBeFalsy();
- resolve();
- },
- });
- }),
- )
- );
+ mockFetch?.pause?.();
+
+ return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
+ .then(() => {
+ Policy.setPolicyRequiresTag(fakePolicy.id, true);
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+
+ // RequiresTag is enabled and pending
+ expect(policy?.requiresTag).toBeTruthy();
+ expect(policy?.pendingFields?.requiresTag).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ expect(policy?.pendingFields?.requiresTag).toBeFalsy();
+ resolve();
+ },
+ });
+ }),
+ );
});
it('disable require tag', () => {
const fakePolicy = createRandomPolicy(0);
fakePolicy.requiresTag = true;
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
-
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
- .then(() => {
- Policy.setPolicyRequiresTag(fakePolicy.id, false);
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policy) => {
- Onyx.disconnect(connectionID);
-
- // RequiresTag is disabled and pending
- expect(policy?.requiresTag).toBeFalsy();
- expect(policy?.pendingFields?.requiresTag).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
-
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policy) => {
- Onyx.disconnect(connectionID);
- expect(policy?.pendingFields?.requiresTag).toBeFalsy();
- resolve();
- },
- });
- }),
- )
- );
+ mockFetch?.pause?.();
+
+ return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
+ .then(() => {
+ Policy.setPolicyRequiresTag(fakePolicy.id, false);
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+
+ // RequiresTag is disabled and pending
+ expect(policy?.requiresTag).toBeFalsy();
+ expect(policy?.pendingFields?.requiresTag).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ expect(policy?.pendingFields?.requiresTag).toBeFalsy();
+ resolve();
+ },
+ });
+ }),
+ );
});
it('reset require tag when api returns an error', () => {
const fakePolicy = createRandomPolicy(0);
fakePolicy.requiresTag = true;
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
-
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
- .then(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.fail();
- Policy.setPolicyRequiresTag(fakePolicy.id, false);
- return waitForBatchedUpdates();
- })
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policy) => {
- Onyx.disconnect(connectionID);
- expect(policy?.pendingFields?.requiresTag).toBeFalsy();
- expect(policy?.errors).toBeTruthy();
- expect(policy?.requiresTag).toBeTruthy();
- resolve();
- },
- });
- }),
- )
- );
+ mockFetch?.pause?.();
+
+ return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
+ .then(() => {
+ mockFetch?.fail?.();
+ Policy.setPolicyRequiresTag(fakePolicy.id, false);
+ return waitForBatchedUpdates();
+ })
+
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ expect(policy?.pendingFields?.requiresTag).toBeFalsy();
+ expect(policy?.errors).toBeTruthy();
+ expect(policy?.requiresTag).toBeTruthy();
+ resolve();
+ },
+ });
+ }),
+ );
});
});
@@ -173,65 +163,61 @@ describe('actions/Policy', () => {
const newTagListName = 'New tag list name';
const fakePolicyTags = createRandomPolicyTags(oldTagListName);
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
-
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
- .then(() => {
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
- })
- .then(() => {
- Policy.renamePolicyTaglist(
- fakePolicy.id,
- {
- oldName: oldTagListName,
- newName: newTagListName,
- },
- fakePolicyTags,
- );
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- // Tag list name is updated and pending
- expect(Object.keys(policyTags?.[oldTagListName] ?? {}).length).toBe(0);
- expect(policyTags?.[newTagListName]?.name).toBe(newTagListName);
- expect(policyTags?.[newTagListName]?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
-
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- expect(policyTags?.[newTagListName]?.pendingAction).toBeFalsy();
- expect(Object.keys(policyTags?.[oldTagListName] ?? {}).length).toBe(0);
-
- resolve();
- },
- });
- }),
- )
- );
+ mockFetch?.pause?.();
+
+ return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
+ .then(() => {
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
+ })
+ .then(() => {
+ Policy.renamePolicyTaglist(
+ fakePolicy.id,
+ {
+ oldName: oldTagListName,
+ newName: newTagListName,
+ },
+ fakePolicyTags,
+ );
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ // Tag list name is updated and pending
+ expect(Object.keys(policyTags?.[oldTagListName] ?? {}).length).toBe(0);
+ expect(policyTags?.[newTagListName]?.name).toBe(newTagListName);
+ expect(policyTags?.[newTagListName]?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ expect(policyTags?.[newTagListName]?.pendingAction).toBeFalsy();
+ expect(Object.keys(policyTags?.[oldTagListName] ?? {}).length).toBe(0);
+
+ resolve();
+ },
+ });
+ }),
+ );
});
it('reset the policy tag list name when api returns error', () => {
@@ -242,50 +228,45 @@ describe('actions/Policy', () => {
const newTagListName = 'New tag list name';
const fakePolicyTags = createRandomPolicyTags(oldTagListName);
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
-
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
- .then(() => {
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
- })
- .then(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.fail();
-
- Policy.renamePolicyTaglist(
- fakePolicy.id,
- {
- oldName: oldTagListName,
- newName: newTagListName,
- },
- fakePolicyTags,
- );
- return waitForBatchedUpdates();
- })
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- expect(policyTags?.[newTagListName]).toBeFalsy();
- expect(policyTags?.[oldTagListName]).toBeTruthy();
- expect(policyTags?.errors).toBeTruthy();
-
- resolve();
- },
- });
- }),
- )
- );
+ mockFetch?.pause?.();
+
+ return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
+ .then(() => {
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
+ })
+ .then(() => {
+ mockFetch?.fail?.();
+
+ Policy.renamePolicyTaglist(
+ fakePolicy.id,
+ {
+ oldName: oldTagListName,
+ newName: newTagListName,
+ },
+ fakePolicyTags,
+ );
+ return waitForBatchedUpdates();
+ })
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ expect(policyTags?.[newTagListName]).toBeFalsy();
+ expect(policyTags?.[oldTagListName]).toBeTruthy();
+ expect(policyTags?.errors).toBeTruthy();
+
+ resolve();
+ },
+ });
+ }),
+ );
});
});
@@ -298,60 +279,56 @@ describe('actions/Policy', () => {
const newTagName = 'new tag';
const fakePolicyTags = createRandomPolicyTags(tagListName);
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
-
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
- .then(() => {
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
- })
- .then(() => {
- Policy.createPolicyTag(fakePolicy.id, newTagName);
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- const newTag = policyTags?.[tagListName]?.tags?.[newTagName];
- expect(newTag?.name).toBe(newTagName);
- expect(newTag?.enabled).toBe(true);
- expect(newTag?.errors).toBeFalsy();
- expect(newTag?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
-
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- const newTag = policyTags?.[tagListName]?.tags?.[newTagName];
- expect(newTag?.errors).toBeFalsy();
- expect(newTag?.pendingAction).toBeFalsy();
-
- resolve();
- },
- });
- }),
- )
- );
+ mockFetch?.pause?.();
+
+ return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
+ .then(() => {
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
+ })
+ .then(() => {
+ Policy.createPolicyTag(fakePolicy.id, newTagName);
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ const newTag = policyTags?.[tagListName]?.tags?.[newTagName];
+ expect(newTag?.name).toBe(newTagName);
+ expect(newTag?.enabled).toBe(true);
+ expect(newTag?.errors).toBeFalsy();
+ expect(newTag?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ const newTag = policyTags?.[tagListName]?.tags?.[newTagName];
+ expect(newTag?.errors).toBeFalsy();
+ expect(newTag?.pendingAction).toBeFalsy();
+
+ resolve();
+ },
+ });
+ }),
+ );
});
it('reset new policy tag when api returns error', () => {
@@ -362,42 +339,37 @@ describe('actions/Policy', () => {
const newTagName = 'new tag';
const fakePolicyTags = createRandomPolicyTags(tagListName);
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
-
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
- .then(() => {
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
- })
- .then(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.fail();
-
- Policy.createPolicyTag(fakePolicy.id, newTagName);
- return waitForBatchedUpdates();
- })
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- const newTag = policyTags?.[tagListName]?.tags?.[newTagName];
- expect(newTag?.errors).toBeTruthy();
-
- resolve();
- },
- });
- }),
- )
- );
+ mockFetch?.pause?.();
+
+ return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
+ .then(() => {
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
+ })
+ .then(() => {
+ mockFetch?.fail?.();
+
+ Policy.createPolicyTag(fakePolicy.id, newTagName);
+ return waitForBatchedUpdates();
+ })
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ const newTag = policyTags?.[tagListName]?.tags?.[newTagName];
+ expect(newTag?.errors).toBeTruthy();
+
+ resolve();
+ },
+ });
+ }),
+ );
});
});
@@ -416,65 +388,61 @@ describe('actions/Policy', () => {
return acc;
}, {});
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
-
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
- .then(() => {
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
- })
- .then(() => {
- Policy.setWorkspaceTagEnabled(fakePolicy.id, tagsToUpdate);
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- Object.keys(tagsToUpdate).forEach((key) => {
- const updatedTag = policyTags?.[tagListName]?.tags[key];
- expect(updatedTag?.enabled).toBeFalsy();
- expect(updatedTag?.errors).toBeFalsy();
- expect(updatedTag?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
- expect(updatedTag?.pendingFields?.enabled).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
- });
-
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- Object.keys(tagsToUpdate).forEach((key) => {
- const updatedTag = policyTags?.[tagListName]?.tags[key];
- expect(updatedTag?.errors).toBeFalsy();
- expect(updatedTag?.pendingAction).toBeFalsy();
- expect(updatedTag?.pendingFields?.enabled).toBeFalsy();
- });
-
- resolve();
- },
- });
- }),
- )
- );
+ mockFetch?.pause?.();
+
+ return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
+ .then(() => {
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
+ })
+ .then(() => {
+ Policy.setWorkspaceTagEnabled(fakePolicy.id, tagsToUpdate);
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ Object.keys(tagsToUpdate).forEach((key) => {
+ const updatedTag = policyTags?.[tagListName]?.tags[key];
+ expect(updatedTag?.enabled).toBeFalsy();
+ expect(updatedTag?.errors).toBeFalsy();
+ expect(updatedTag?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+ expect(updatedTag?.pendingFields?.enabled).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+ });
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ Object.keys(tagsToUpdate).forEach((key) => {
+ const updatedTag = policyTags?.[tagListName]?.tags[key];
+ expect(updatedTag?.errors).toBeFalsy();
+ expect(updatedTag?.pendingAction).toBeFalsy();
+ expect(updatedTag?.pendingFields?.enabled).toBeFalsy();
+ });
+
+ resolve();
+ },
+ });
+ }),
+ );
});
it('reset policy tag enable when api returns error', () => {
@@ -491,46 +459,41 @@ describe('actions/Policy', () => {
return acc;
}, {});
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
-
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
- .then(() => {
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
- })
- .then(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.fail();
-
- Policy.setWorkspaceTagEnabled(fakePolicy.id, tagsToUpdate);
- return waitForBatchedUpdates();
- })
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- Object.keys(tagsToUpdate).forEach((key) => {
- const updatedTag = policyTags?.[tagListName]?.tags[key];
- expect(updatedTag?.errors).toBeTruthy();
- expect(updatedTag?.pendingAction).toBeFalsy();
- expect(updatedTag?.pendingFields?.enabled).toBeFalsy();
- });
-
- resolve();
- },
- });
- }),
- )
- );
+ mockFetch?.pause?.();
+
+ return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
+ .then(() => {
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
+ })
+ .then(() => {
+ mockFetch?.fail?.();
+
+ Policy.setWorkspaceTagEnabled(fakePolicy.id, tagsToUpdate);
+ return waitForBatchedUpdates();
+ })
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ Object.keys(tagsToUpdate).forEach((key) => {
+ const updatedTag = policyTags?.[tagListName]?.tags[key];
+ expect(updatedTag?.errors).toBeTruthy();
+ expect(updatedTag?.pendingAction).toBeFalsy();
+ expect(updatedTag?.pendingFields?.enabled).toBeFalsy();
+ });
+
+ resolve();
+ },
+ });
+ }),
+ );
});
});
@@ -544,63 +507,59 @@ describe('actions/Policy', () => {
const oldTagName = Object.keys(fakePolicyTags?.[tagListName]?.tags)[0];
const newTagName = 'New tag';
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
-
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
- .then(() => {
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
- })
- .then(() => {
- Policy.renamePolicyTag(fakePolicy.id, {
- oldName: oldTagName,
- newName: newTagName,
- });
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- const tags = policyTags?.[tagListName]?.tags;
- expect(tags?.[oldTagName]).toBeFalsy();
- expect(tags?.[newTagName]?.name).toBe(newTagName);
- expect(tags?.[newTagName]?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
- expect(tags?.[newTagName]?.pendingFields?.name).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
-
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- const tags = policyTags?.[tagListName]?.tags;
- expect(tags?.[newTagName]?.pendingAction).toBeFalsy();
- expect(tags?.[newTagName]?.pendingFields?.name).toBeFalsy();
-
- resolve();
- },
- });
- }),
- )
- );
+ mockFetch?.pause?.();
+
+ return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
+ .then(() => {
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
+ })
+ .then(() => {
+ Policy.renamePolicyTag(fakePolicy.id, {
+ oldName: oldTagName,
+ newName: newTagName,
+ });
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ const tags = policyTags?.[tagListName]?.tags;
+ expect(tags?.[oldTagName]).toBeFalsy();
+ expect(tags?.[newTagName]?.name).toBe(newTagName);
+ expect(tags?.[newTagName]?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+ expect(tags?.[newTagName]?.pendingFields?.name).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ const tags = policyTags?.[tagListName]?.tags;
+ expect(tags?.[newTagName]?.pendingAction).toBeFalsy();
+ expect(tags?.[newTagName]?.pendingFields?.name).toBeFalsy();
+
+ resolve();
+ },
+ });
+ }),
+ );
});
it('reset policy tag name when api returns error', () => {
@@ -612,46 +571,41 @@ describe('actions/Policy', () => {
const oldTagName = Object.keys(fakePolicyTags?.[tagListName]?.tags)[0];
const newTagName = 'New tag';
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
-
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
- .then(() => {
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
- })
- .then(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.fail();
-
- Policy.renamePolicyTag(fakePolicy.id, {
- oldName: oldTagName,
- newName: newTagName,
- });
- return waitForBatchedUpdates();
- })
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- const tags = policyTags?.[tagListName]?.tags;
- expect(tags?.[newTagName]).toBeFalsy();
- expect(tags?.[oldTagName]?.errors).toBeTruthy();
-
- resolve();
- },
- });
- }),
- )
- );
+ mockFetch?.pause?.();
+
+ return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
+ .then(() => {
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
+ })
+ .then(() => {
+ mockFetch?.fail?.();
+
+ Policy.renamePolicyTag(fakePolicy.id, {
+ oldName: oldTagName,
+ newName: newTagName,
+ });
+ return waitForBatchedUpdates();
+ })
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ const tags = policyTags?.[tagListName]?.tags;
+ expect(tags?.[newTagName]).toBeFalsy();
+ expect(tags?.[oldTagName]?.errors).toBeTruthy();
+
+ resolve();
+ },
+ });
+ }),
+ );
});
});
@@ -664,58 +618,54 @@ describe('actions/Policy', () => {
const fakePolicyTags = createRandomPolicyTags(tagListName, 2);
const tagsToDelete = Object.keys(fakePolicyTags?.[tagListName]?.tags ?? {});
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
-
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
- .then(() => {
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
- })
- .then(() => {
- Policy.deletePolicyTags(fakePolicy.id, tagsToDelete);
- return waitForBatchedUpdates();
- })
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- tagsToDelete.forEach((tagName) => {
- expect(policyTags?.[tagListName]?.tags[tagName]?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
- });
-
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- tagsToDelete.forEach((tagName) => {
- expect(policyTags?.[tagListName]?.tags[tagName]).toBeFalsy();
- });
-
- resolve();
- },
- });
- }),
- )
- );
+ mockFetch?.pause?.();
+
+ return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
+ .then(() => {
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
+ })
+ .then(() => {
+ Policy.deletePolicyTags(fakePolicy.id, tagsToDelete);
+ return waitForBatchedUpdates();
+ })
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ tagsToDelete.forEach((tagName) => {
+ expect(policyTags?.[tagListName]?.tags[tagName]?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
+ });
+
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ tagsToDelete.forEach((tagName) => {
+ expect(policyTags?.[tagListName]?.tags[tagName]).toBeFalsy();
+ });
+
+ resolve();
+ },
+ });
+ }),
+ );
});
it('reset the deleted policy tag when api returns error', () => {
@@ -726,44 +676,39 @@ describe('actions/Policy', () => {
const fakePolicyTags = createRandomPolicyTags(tagListName, 2);
const tagsToDelete = Object.keys(fakePolicyTags?.[tagListName]?.tags ?? {});
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
-
- return (
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
- .then(() => {
- Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
- })
- .then(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.fail();
-
- Policy.deletePolicyTags(fakePolicy.id, tagsToDelete);
- return waitForBatchedUpdates();
- })
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policyTags) => {
- Onyx.disconnect(connectionID);
-
- tagsToDelete.forEach((tagName) => {
- expect(policyTags?.[tagListName]?.tags[tagName].pendingAction).toBeFalsy();
- expect(policyTags?.[tagListName]?.tags[tagName].errors).toBeTruthy();
- });
-
- resolve();
- },
- });
- }),
- )
- );
+ mockFetch?.pause?.();
+
+ return Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy)
+ .then(() => {
+ Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
+ })
+ .then(() => {
+ mockFetch?.fail?.();
+
+ Policy.deletePolicyTags(fakePolicy.id, tagsToDelete);
+ return waitForBatchedUpdates();
+ })
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policyTags) => {
+ Onyx.disconnect(connectionID);
+
+ tagsToDelete.forEach((tagName) => {
+ expect(policyTags?.[tagListName]?.tags[tagName].pendingAction).toBeFalsy();
+ expect(policyTags?.[tagListName]?.tags[tagName].errors).toBeTruthy();
+ });
+
+ resolve();
+ },
+ });
+ }),
+ );
});
});
});
diff --git a/tests/actions/PolicyTaxTest.ts b/tests/actions/PolicyTaxTest.ts
index a17179d8f7af..b1c190f9e5ac 100644
--- a/tests/actions/PolicyTaxTest.ts
+++ b/tests/actions/PolicyTaxTest.ts
@@ -7,6 +7,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import type {Policy as PolicyType, TaxRate} from '@src/types/onyx';
import createRandomPolicy from '../utils/collections/policies';
import * as TestHelper from '../utils/TestHelper';
+import type {MockFetch} from '../utils/TestHelper';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
OnyxUpdateManager();
@@ -18,9 +19,10 @@ describe('actions/PolicyTax', () => {
});
});
+ let mockFetch: MockFetch;
beforeEach(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
global.fetch = TestHelper.getGlobalFetchMock();
+ mockFetch = fetch as MockFetch;
return Onyx.clear()
.then(() => Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy))
.then(waitForBatchedUpdates);
@@ -29,53 +31,48 @@ describe('actions/PolicyTax', () => {
describe('SetPolicyCustomTaxName', () => {
it('Set policy`s custom tax name', () => {
const customTaxName = 'Custom tag name';
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
Policy.setPolicyCustomTaxName(fakePolicy.id, customTaxName);
- return (
- waitForBatchedUpdates()
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policy) => {
- Onyx.disconnect(connectionID);
- expect(policy?.taxRates?.name).toBe(customTaxName);
- expect(policy?.taxRates?.pendingFields?.name).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
- expect(policy?.taxRates?.errorFields).toBeFalsy();
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policy) => {
- Onyx.disconnect(connectionID);
- expect(policy?.taxRates?.pendingFields?.name).toBeFalsy();
- expect(policy?.taxRates?.errorFields).toBeFalsy();
- resolve();
- },
- });
- }),
- )
- );
+ return waitForBatchedUpdates()
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ expect(policy?.taxRates?.name).toBe(customTaxName);
+ expect(policy?.taxRates?.pendingFields?.name).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+ expect(policy?.taxRates?.errorFields).toBeFalsy();
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ expect(policy?.taxRates?.pendingFields?.name).toBeFalsy();
+ expect(policy?.taxRates?.errorFields).toBeFalsy();
+ resolve();
+ },
+ });
+ }),
+ );
});
it('Reset policy`s custom tax name when API returns an error', () => {
const customTaxName = 'Custom tag name';
const originalCustomTaxName = fakePolicy?.taxRates?.name;
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
Policy.setPolicyCustomTaxName(fakePolicy.id, customTaxName);
return waitForBatchedUpdates()
.then(
@@ -95,10 +92,8 @@ describe('actions/PolicyTax', () => {
}),
)
.then(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.fail();
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- return fetch.resume() as Promise;
+ mockFetch?.fail?.();
+ return mockFetch?.resume?.() as Promise;
})
.then(waitForBatchedUpdates)
.then(
@@ -124,53 +119,48 @@ describe('actions/PolicyTax', () => {
it('Set policy`s currency default tax', () => {
const taxCode = 'id_TAX_RATE_1';
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
Policy.setWorkspaceCurrencyDefault(fakePolicy.id, taxCode);
- return (
- waitForBatchedUpdates()
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policy) => {
- Onyx.disconnect(connectionID);
- expect(policy?.taxRates?.defaultExternalID).toBe(taxCode);
- expect(policy?.taxRates?.pendingFields?.defaultExternalID).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
- expect(policy?.taxRates?.errorFields).toBeFalsy();
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policy) => {
- Onyx.disconnect(connectionID);
- expect(policy?.taxRates?.pendingFields?.defaultExternalID).toBeFalsy();
- expect(policy?.taxRates?.errorFields).toBeFalsy();
- resolve();
- },
- });
- }),
- )
- );
+ return waitForBatchedUpdates()
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ expect(policy?.taxRates?.defaultExternalID).toBe(taxCode);
+ expect(policy?.taxRates?.pendingFields?.defaultExternalID).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+ expect(policy?.taxRates?.errorFields).toBeFalsy();
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ expect(policy?.taxRates?.pendingFields?.defaultExternalID).toBeFalsy();
+ expect(policy?.taxRates?.errorFields).toBeFalsy();
+ resolve();
+ },
+ });
+ }),
+ );
});
it('Reset policy`s currency default tax when API returns an error', () => {
const taxCode = 'id_TAX_RATE_1';
const originalDefaultExternalID = fakePolicy?.taxRates?.defaultExternalID;
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
Policy.setWorkspaceCurrencyDefault(fakePolicy.id, taxCode);
return waitForBatchedUpdates()
.then(
@@ -190,10 +180,8 @@ describe('actions/PolicyTax', () => {
}),
)
.then(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.fail();
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- return fetch.resume() as Promise;
+ mockFetch?.fail?.();
+ return mockFetch?.resume?.() as Promise;
})
.then(waitForBatchedUpdates)
.then(
@@ -218,54 +206,49 @@ describe('actions/PolicyTax', () => {
it('Set policy`s foreign currency default', () => {
const taxCode = 'id_TAX_RATE_1';
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
Policy.setForeignCurrencyDefault(fakePolicy.id, taxCode);
- return (
- waitForBatchedUpdates()
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policy) => {
- Onyx.disconnect(connectionID);
- expect(policy?.taxRates?.foreignTaxDefault).toBe(taxCode);
- expect(policy?.taxRates?.pendingFields?.foreignTaxDefault).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
- expect(policy?.taxRates?.errorFields).toBeFalsy();
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policy) => {
- Onyx.disconnect(connectionID);
- // Check if the policy pendingFields was cleared
- expect(policy?.taxRates?.pendingFields?.foreignTaxDefault).toBeFalsy();
- expect(policy?.taxRates?.errorFields).toBeFalsy();
- resolve();
- },
- });
- }),
- )
- );
+ return waitForBatchedUpdates()
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ expect(policy?.taxRates?.foreignTaxDefault).toBe(taxCode);
+ expect(policy?.taxRates?.pendingFields?.foreignTaxDefault).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE);
+ expect(policy?.taxRates?.errorFields).toBeFalsy();
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ // Check if the policy pendingFields was cleared
+ expect(policy?.taxRates?.pendingFields?.foreignTaxDefault).toBeFalsy();
+ expect(policy?.taxRates?.errorFields).toBeFalsy();
+ resolve();
+ },
+ });
+ }),
+ );
});
it('Reset policy`s foreign currency default when API returns an error', () => {
const taxCode = 'id_TAX_RATE_1';
const originalDefaultForeignCurrencyID = fakePolicy?.taxRates?.foreignTaxDefault;
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
Policy.setForeignCurrencyDefault(fakePolicy.id, taxCode);
return waitForBatchedUpdates()
.then(
@@ -286,10 +269,8 @@ describe('actions/PolicyTax', () => {
)
.then(() => {
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.fail();
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- return fetch.resume() as Promise;
+ mockFetch?.fail?.();
+ return mockFetch?.resume?.() as Promise;
})
.then(waitForBatchedUpdates)
.then(
@@ -319,49 +300,45 @@ describe('actions/PolicyTax', () => {
code: 'id_TAX_RATE_2',
};
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- fetch.pause();
+ mockFetch?.pause?.();
createPolicyTax(fakePolicy.id, newTaxRate);
- return (
- waitForBatchedUpdates()
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policy) => {
- Onyx.disconnect(connectionID);
- const createdTax = policy?.taxRates?.taxes?.[newTaxRate.code ?? ''];
- expect(createdTax?.code).toBe(newTaxRate.code);
- expect(createdTax?.name).toBe(newTaxRate.name);
- expect(createdTax?.value).toBe(newTaxRate.value);
- expect(createdTax?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
- resolve();
- },
- });
- }),
- )
- // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript.
- .then(fetch.resume)
- .then(waitForBatchedUpdates)
- .then(
- () =>
- new Promise((resolve) => {
- const connectionID = Onyx.connect({
- key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
- waitForCollectionCallback: false,
- callback: (policy) => {
- Onyx.disconnect(connectionID);
- const createdTax = policy?.taxRates?.taxes?.[newTaxRate.code ?? ''];
- expect(createdTax?.errors).toBeFalsy();
- expect(createdTax?.pendingFields).toBeFalsy();
- resolve();
- },
- });
- }),
- )
- );
+ return waitForBatchedUpdates()
+ .then(
+ () =>
+ new Promise((resolve) => {
+ const connectionID = Onyx.connect({
+ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`,
+ waitForCollectionCallback: false,
+ callback: (policy) => {
+ Onyx.disconnect(connectionID);
+ const createdTax = policy?.taxRates?.taxes?.[newTaxRate.code ?? ''];
+ expect(createdTax?.code).toBe(newTaxRate.code);
+ expect(createdTax?.name).toBe(newTaxRate.name);
+ expect(createdTax?.value).toBe(newTaxRate.value);
+ expect(createdTax?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+ resolve();
+ },
+ });
+ }),
+ )
+ .then(mockFetch?.resume)
+ .then(waitForBatchedUpdates)
+ .then(
+ () =>
+ new Promise