From ffb21f156543aa780d9ab15585952a916aea2c9d Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 9 Mar 2024 03:25:09 +0530 Subject: [PATCH 001/174] add POC for fallingback to default attachment view when pdf fails to load --- .../BaseAttachmentViewPdf.js | 4 + .../DefaultAttachmentView.tsx | 59 +++++++++++++ .../AttachmentView/AttachmentViewPdf/index.js | 3 +- .../AttachmentViewPdf/propTypes.js | 8 ++ .../Attachments/AttachmentView/index.js | 86 ++++++++++++------- .../InvertedFlatList/BaseInvertedFlatList.tsx | 2 +- src/components/PDFView/WebPDFDocument.js | 4 +- src/components/PDFView/index.js | 1 + src/components/PDFView/index.native.js | 59 ++++++++++--- src/styles/index.ts | 14 +++ 10 files changed, 192 insertions(+), 48 deletions(-) create mode 100644 src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js index 2f16b63aacc6..3f3b995c02ba 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js @@ -28,6 +28,8 @@ function BaseAttachmentViewPdf({ onLoadComplete, errorLabelStyles, style, + isUsedAsChatAttachment, + onError, }) { const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const isScrollEnabled = attachmentCarouselPagerContext === null ? undefined : attachmentCarouselPagerContext.isScrollEnabled; @@ -89,6 +91,8 @@ function BaseAttachmentViewPdf({ onScaleChanged={onScaleChanged} onLoadComplete={onLoadComplete} errorLabelStyles={errorLabelStyles} + isUsedAsChatAttachment={isUsedAsChatAttachment} + onError={onError} /> ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx new file mode 100644 index 000000000000..ad102fd5caad --- /dev/null +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import {ActivityIndicator, View} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import _ from 'underscore'; +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 = { + file: File; + shouldShowLoadingSpinnerIcon: boolean; + shouldShowDownloadIcon: boolean; + containerStyles: StyleProp[]; +}; + +function DefaultAttachmentView({file, shouldShowLoadingSpinnerIcon, shouldShowDownloadIcon, containerStyles}: DefaultAttachmentViewProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + return ( + + + + + + {file && file.name} + {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( + + + + + + )} + {shouldShowLoadingSpinnerIcon && ( + + + + + + )} + + ); +} + +export default DefaultAttachmentView; diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js index d6a402613c34..de740f4fdd1f 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js @@ -2,7 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style}) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, isUsedAsChatAttachment, onError}) { return ( ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js index a34010f0ba8b..bcb0fabde352 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js @@ -15,6 +15,12 @@ const attachmentViewPdfPropTypes = { /** Styles for the error label */ errorLabelStyles: stylePropTypes, + + /** Callback when the pdf fails to load */ + onError: PropTypes.func, + + /** Whether the attachment is used as a chat attachment */ + isUsedAsChatAttachment: PropTypes.bool, }; const attachmentViewPdfDefaultProps = { @@ -23,6 +29,8 @@ const attachmentViewPdfDefaultProps = { }, style: [], errorLabelStyles: [], + onError: () => {}, + isUsedAsChatAttachment: false, }; export {attachmentViewPdfPropTypes, attachmentViewPdfDefaultProps}; diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index f6a56dc73088..76e546be8930 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -1,16 +1,13 @@ import Str from 'expensify-common/lib/str'; import PropTypes from 'prop-types'; import React, {memo, useEffect, useState} from 'react'; -import {ActivityIndicator, ScrollView, View} from 'react-native'; +import {ScrollView, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import * as AttachmentsPropTypes from '@components/Attachments/propTypes'; import DistanceEReceipt from '@components/DistanceEReceipt'; import EReceipt from '@components/EReceipt'; import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import Text from '@components/Text'; -import Tooltip from '@components/Tooltip'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -25,6 +22,7 @@ import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; import AttachmentViewImage from './AttachmentViewImage'; import AttachmentViewPdf from './AttachmentViewPdf'; +import DefaultAttachmentView from './AttachmentViewPdf/DefaultAttachmentView'; import AttachmentViewVideo from './AttachmentViewVideo'; import {attachmentViewDefaultProps, attachmentViewPropTypes} from './propTypes'; @@ -107,6 +105,7 @@ function AttachmentView({ const StyleUtils = useStyleUtils(); const [loadComplete, setLoadComplete] = useState(false); const isVideo = (typeof source === 'string' && Str.isVideo(source)) || (file && Str.isVideo(file.name)); + const [isPdfFailedToLoad, setIsPdfFailedToLoad] = useState(false); useEffect(() => { if (!isFocused && !(file && isUsedInAttachmentModal)) { @@ -169,6 +168,17 @@ function AttachmentView({ } }; + if (isPdfFailedToLoad) { + return ( + + ); + } + // 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. @@ -186,6 +196,10 @@ function AttachmentView({ errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [styles.cursorAuto]} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedInCarousel={isUsedInCarousel} + isUsedAsChatAttachment={!(isUsedInAttachmentModal || isUsedInCarousel)} + onError={() => { + setIsPdfFailedToLoad(true); + }} /> ); @@ -229,36 +243,42 @@ function AttachmentView({ } return ( - - - - + + // + // + // + // - {file && file.name} - {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( - - - - - - )} - {shouldShowLoadingSpinnerIcon && ( - - - - - - )} - + // {file && file.name} + // {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( + // + // + // + // + // + // )} + // {shouldShowLoadingSpinnerIcon && ( + // + // + // + // + // + // )} + // ); } diff --git a/src/components/InvertedFlatList/BaseInvertedFlatList.tsx b/src/components/InvertedFlatList/BaseInvertedFlatList.tsx index e28400505280..d0a4322300c3 100644 --- a/src/components/InvertedFlatList/BaseInvertedFlatList.tsx +++ b/src/components/InvertedFlatList/BaseInvertedFlatList.tsx @@ -4,7 +4,7 @@ import type {FlatListProps} from 'react-native'; import FlatList from '@components/FlatList'; const WINDOW_SIZE = 15; -const AUTOSCROLL_TO_TOP_THRESHOLD = 128; +const AUTOSCROLL_TO_TOP_THRESHOLD = 250; function BaseInvertedFlatList(props: FlatListProps, ref: ForwardedRef) { return ( diff --git a/src/components/PDFView/WebPDFDocument.js b/src/components/PDFView/WebPDFDocument.js index dd9d1e066b19..d2e93e3ba506 100644 --- a/src/components/PDFView/WebPDFDocument.js +++ b/src/components/PDFView/WebPDFDocument.js @@ -48,6 +48,8 @@ const propTypes = { * - `undefined` if password isn't needed to view the PDF file * - `null` if the password is required but hasn't been provided yet */ password: PropTypes.string, + /** Callback invoked when the PDF document fails to load */ + onError: PropTypes.func.isRequired, }; const defaultProps = { @@ -95,7 +97,7 @@ const WebPDFDocument = memo( return ( } - error={{translate('attachmentView.failedToLoadPDF')}} + onLoadError={onError} file={sourceURL} options={{ cMapUrl: 'cmaps/', diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 9706f8e06cc1..2e39594fc210 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -270,6 +270,7 @@ class PDFView extends Component { pageWidth={pageWidth} password={this.state.password} initiatePasswordChallenge={this.initiatePasswordChallenge} + onError={this.props.onError} /> {(this.state.password === PDFViewConstants.REQUIRED_PASSWORD_MISSING || this.state.isCheckingPassword) && ( diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 558f6636a325..6c0f5acd1081 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -2,17 +2,22 @@ import React, {useCallback, useEffect, useState} from 'react'; import {View} from 'react-native'; import PDF from 'react-native-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; 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 useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import variables from '@styles/variables'; import CONST from '@src/CONST'; import PDFPasswordForm from './PDFPasswordForm'; import {defaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; +import Text from '@components/Text'; const propTypes = { ...pdfViewPropTypes, @@ -33,7 +38,10 @@ const propTypes = { * is (temporarily) rendered. */ -function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles}) { +const THUMBNAIL_HEIGHT = 250; +const THUMBNAIL_WIDTH = 250; + +function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles, onError, isUsedAsChatAttachment}) { const [shouldRequestPassword, setShouldRequestPassword] = useState(false); const [shouldAttemptPDFLoad, setShouldAttemptPDFLoad] = useState(true); const [shouldShowLoadingIndicator, setShouldShowLoadingIndicator] = useState(true); @@ -46,6 +54,8 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused const themeStyles = useThemeStyles(); const {isKeyboardShown} = useKeyboardState(); const StyleUtils = useStyleUtils(); + const {isOffline} = useNetwork(); + const theme = useTheme(); useEffect(() => { onToggleKeyboard(isKeyboardShown); @@ -84,6 +94,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused setShouldShowLoadingIndicator(false); setShouldRequestPassword(false); setShouldAttemptPDFLoad(false); + onError(error); }; /** @@ -113,9 +124,33 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused setSuccessToLoadPDF(true); onLoadComplete(path); }; + const rendeFailedToLoadPDF = () => { + if (!isUsedAsChatAttachment) { + return ( + + {translate('attachmentView.failedToLoadPDF')} + + ); + } + + return ( + + + + + + ); + }; function renderPDFView() { - const pdfStyles = [themeStyles.imageModalPDF, StyleUtils.getWidthAndHeightStyle(windowWidth, windowHeight)]; + const pdfHeight = isUsedAsChatAttachment ? THUMBNAIL_HEIGHT : windowHeight; + const pdfWeight = isUsedAsChatAttachment ? THUMBNAIL_WIDTH : windowWidth; + const pdfStyles = [StyleUtils.getWidthAndHeightStyle(pdfWeight, pdfHeight), themeStyles.imageModalPDF]; // 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 @@ -124,22 +159,18 @@ 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 = + (shouldRequestPassword && isSmallScreenWidth) || isUsedAsChatAttachment ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; + const loadingIndicatorStyles = isUsedAsChatAttachment ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)] : []; return ( - {failedToLoadPDF && ( - - {translate('attachmentView.failedToLoadPDF')} - - )} {shouldAttemptPDFLoad && ( } - source={{uri: sourceURL, cache: true, expiration: 864000}} + renderActivityIndicator={() => } + source={{uri: sourceURL}} style={pdfStyles} onError={handleFailureToLoadPDF} password={password} @@ -162,6 +193,10 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused ); } + // // uncomment this code to see failedToLoadPDF + // if (failedToLoadPDF) { + // return rendeFailedToLoadPDF(); + // } return onPress && !successToLoadPDF ? ( borderRadius: variables.buttonBorderRadius, }, + componentBorderRadiusNormal: { + borderRadius: variables.componentBorderRadiusNormal, + }, + bottomTabBarContainer: { flexDirection: 'row', height: variables.bottomTabHeight, @@ -2111,6 +2115,16 @@ const styles = (theme: ThemeColors) => width: 200, }, + chatItemPDFAttachmentLoading: { + backgroundColor: 'transparent', + borderColor: theme.border, + borderWidth: 1, + borderRadius: variables.componentBorderRadiusNormal, + textAlign: 'center', + verticalAlign: 'middle', + opacity: 1, + }, + sidebarVisible: { borderRightWidth: 1, }, From bde48faf072512f5d79d29ce293ae83b44241171 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 9 Mar 2024 03:26:09 +0530 Subject: [PATCH 002/174] adds chnages for attachement view --- .../Attachments/AttachmentView/index.js | 45 +------------------ 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 76e546be8930..f15b235144cb 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -12,7 +12,6 @@ import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContex import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CachedPDFPaths from '@libs/actions/CachedPDFPaths'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; @@ -100,7 +99,6 @@ function AttachmentView({ optionalVideoDuration, }) { const {updateCurrentlyPlayingURL} = usePlaybackContext(); - const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [loadComplete, setLoadComplete] = useState(false); @@ -155,7 +153,7 @@ 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 ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) { + if (!isPdfFailedToLoad && ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename'))))) { const encryptedSourceUrl = isAuthTokenRequired ? addEncryptedAuthTokenToURL(source) : source; const onPDFLoadComplete = (path) => { @@ -168,17 +166,6 @@ function AttachmentView({ } }; - if (isPdfFailedToLoad) { - return ( - - ); - } - // 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. @@ -249,36 +236,6 @@ function AttachmentView({ shouldShowLoadingSpinnerIcon={shouldShowLoadingSpinnerIcon} containerStyles={containerStyles} /> - // - // - // - // - - // {file && file.name} - // {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( - // - // - // - // - // - // )} - // {shouldShowLoadingSpinnerIcon && ( - // - // - // - // - // - // )} - // ); } From 757661bf2c41ad8f7bc0abeed6102202890c7679 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 9 Mar 2024 03:32:34 +0530 Subject: [PATCH 003/174] formatting --- src/components/PDFView/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 6c0f5acd1081..492a12e1dc66 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -6,6 +6,7 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; 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 useNetwork from '@hooks/useNetwork'; @@ -17,7 +18,6 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import PDFPasswordForm from './PDFPasswordForm'; import {defaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; -import Text from '@components/Text'; const propTypes = { ...pdfViewPropTypes, From 26c1f13c9fd6b7ede36ecf38c1d17e0ebb69fd7b Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 9 Mar 2024 03:41:08 +0530 Subject: [PATCH 004/174] fixes conflicts --- .../Attachments/AttachmentView/index.js | 435 ++++++++++-------- 1 file changed, 247 insertions(+), 188 deletions(-) diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 492a12e1dc66..461548f0d2b1 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -1,219 +1,278 @@ -import React, {useCallback, useEffect, useState} from 'react'; -import {View} from 'react-native'; -import PDF from 'react-native-pdf'; -import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import Str from 'expensify-common/lib/str'; +import PropTypes from 'prop-types'; +import React, {memo, useEffect, useState} from 'react'; +import {ActivityIndicator, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; +import * as AttachmentsPropTypes from '@components/Attachments/propTypes'; +import DistanceEReceipt from '@components/DistanceEReceipt'; +import EReceipt from '@components/EReceipt'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; -import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; -import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; -import useKeyboardState from '@hooks/useKeyboardState'; -import useLocalize from '@hooks/useLocalize'; +import Tooltip from '@components/Tooltip'; +import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; +import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; +import * as CachedPDFPaths from '@libs/actions/CachedPDFPaths'; +import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; +import compose from '@libs/compose'; +import * as TransactionUtils from '@libs/TransactionUtils'; import variables from '@styles/variables'; -import CONST from '@src/CONST'; -import PDFPasswordForm from './PDFPasswordForm'; -import {defaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; +import ONYXKEYS from '@src/ONYXKEYS'; +import AttachmentViewImage from './AttachmentViewImage'; +import AttachmentViewPdf from './AttachmentViewPdf'; +import AttachmentViewVideo from './AttachmentViewVideo'; +import {attachmentViewDefaultProps, attachmentViewPropTypes} from './propTypes'; const propTypes = { - ...pdfViewPropTypes, + ...attachmentViewPropTypes, + ...withLocalizePropTypes, + + /** URL to full-sized attachment, SVG function, or numeric static image on native platforms */ + source: AttachmentsPropTypes.attachmentSourcePropType.isRequired, + + /** Flag to show/hide download icon */ + shouldShowDownloadIcon: PropTypes.bool, + + /** Flag to show the loading indicator */ + shouldShowLoadingSpinnerIcon: PropTypes.bool, + + /** Notify parent that the UI should be modified to accommodate keyboard */ + onToggleKeyboard: PropTypes.func, + + /** Extra styles to pass to View wrapper */ + // eslint-disable-next-line react/forbid-prop-types + containerStyles: PropTypes.arrayOf(PropTypes.object), + + /** Denotes whether it is a workspace avatar or not */ + isWorkspaceAvatar: PropTypes.bool, + + /** Denotes whether it is an icon (ex: SVG) */ + maybeIcon: PropTypes.bool, + + /** The id of the transaction related to the attachment */ + // eslint-disable-next-line react/no-unused-prop-types + transactionID: PropTypes.string, + + /** The id of the report action related to the attachment */ + reportActionID: PropTypes.string, + + isHovered: PropTypes.bool, + + optionalVideoDuration: PropTypes.number, }; -/** - * On the native layer, we use react-native-pdf/PDF to display PDFs. If a PDF is - * password-protected we render a PDFPasswordForm to request a password - * from the user. - * - * In order to render things nicely during a password challenge we need - * to keep track of additional state. In particular, the - * react-native-pdf/PDF component is both conditionally rendered and hidden - * depending upon the situation. It needs to be rerendered on each password - * submission because it doesn't dynamically handle updates to its - * password property. And we need to hide it during password challenges - * so that PDFPasswordForm doesn't bounce when react-native-pdf/PDF - * is (temporarily) rendered. - */ - -const THUMBNAIL_HEIGHT = 250; -const THUMBNAIL_WIDTH = 250; - -function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles, onError, isUsedAsChatAttachment}) { - const [shouldRequestPassword, setShouldRequestPassword] = useState(false); - const [shouldAttemptPDFLoad, setShouldAttemptPDFLoad] = useState(true); - const [shouldShowLoadingIndicator, setShouldShowLoadingIndicator] = useState(true); - const [isPasswordInvalid, setIsPasswordInvalid] = useState(false); - const [failedToLoadPDF, setFailedToLoadPDF] = useState(false); - const [successToLoadPDF, setSuccessToLoadPDF] = useState(false); - const [password, setPassword] = useState(''); - const {windowWidth, windowHeight, isSmallScreenWidth} = useWindowDimensions(); - const {translate} = useLocalize(); - const themeStyles = useThemeStyles(); - const {isKeyboardShown} = useKeyboardState(); - const StyleUtils = useStyleUtils(); - const {isOffline} = useNetwork(); +const defaultProps = { + ...attachmentViewDefaultProps, + shouldShowDownloadIcon: false, + shouldShowLoadingSpinnerIcon: false, + onToggleKeyboard: () => {}, + containerStyles: [], + isWorkspaceAvatar: false, + maybeIcon: false, + transactionID: '', + reportActionID: '', + isHovered: false, + optionalVideoDuration: 0, +}; + +function AttachmentView({ + source, + file, + isAuthTokenRequired, + onPress, + shouldShowLoadingSpinnerIcon, + shouldShowDownloadIcon, + containerStyles, + onToggleKeyboard, + translate, + isFocused, + isUsedInCarousel, + isUsedInAttachmentModal, + isWorkspaceAvatar, + maybeIcon, + fallbackSource, + transaction, + reportActionID, + isHovered, + optionalVideoDuration, +}) { + const {updateCurrentlyPlayingURL} = usePlaybackContext(); const theme = useTheme(); + const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); + const [loadComplete, setLoadComplete] = useState(false); + const isVideo = (typeof source === 'string' && Str.isVideo(source)) || (file && Str.isVideo(file.name)); useEffect(() => { - onToggleKeyboard(isKeyboardShown); - }); - - /** - * Initiate password challenge if message received from react-native-pdf/PDF - * indicates that a password is required or invalid. - * - * For a password challenge the message is "Password required or incorrect password." - * Note that the message doesn't specify whether the password is simply empty or - * invalid. - */ - - const initiatePasswordChallenge = useCallback(() => { - setShouldShowLoadingIndicator(false); - - // Render password form, and don't render PDF and loading indicator. - setShouldRequestPassword(true); - setShouldAttemptPDFLoad(false); - - // The message provided by react-native-pdf doesn't indicate whether this - // is an initial password request or if the password is invalid. So we just assume - // that if a password was already entered then it's an invalid password error. - if (password) { - setIsPasswordInvalid(true); - } - }, [password]); - - const handleFailureToLoadPDF = (error) => { - if (error.message.match(/password/i)) { - initiatePasswordChallenge(); + if (!isFocused && !(file && isUsedInAttachmentModal)) { return; } - setFailedToLoadPDF(true); - setShouldShowLoadingIndicator(false); - setShouldRequestPassword(false); - setShouldAttemptPDFLoad(false); - onError(error); - }; - - /** - * When the password is submitted via PDFPasswordForm, save the password - * in state and attempt to load the PDF. Also show the loading indicator - * since react-native-pdf/PDF will need to reload the PDF. - * - * @param {String} pdfPassword Password submitted via PDFPasswordForm - */ - const attemptPDFLoadWithPassword = (pdfPassword) => { - // Render react-native-pdf/PDF so that it can validate the password. - // Note that at this point in the password challenge, shouldRequestPassword is true. - // Thus react-native-pdf/PDF will be rendered - but not visible. - setPassword(pdfPassword); - setShouldAttemptPDFLoad(true); - setShouldShowLoadingIndicator(true); - }; - /** - * After the PDF is successfully loaded hide PDFPasswordForm and the loading - * indicator. - * @param {Number} numberOfPages - * @param {Number} path - Path to cache location - */ - const finishPDFLoad = (numberOfPages, path) => { - setShouldRequestPassword(false); - setShouldShowLoadingIndicator(false); - setSuccessToLoadPDF(true); - onLoadComplete(path); - }; - const rendeFailedToLoadPDF = () => { - if (!isUsedAsChatAttachment) { - return ( - - {translate('attachmentView.failedToLoadPDF')} - - ); + updateCurrentlyPlayingURL(isVideo ? source : null); + }, [isFocused, isVideo, source, updateCurrentlyPlayingURL, file, isUsedInAttachmentModal]); + + const [imageError, setImageError] = useState(false); + + useNetwork({onReconnect: () => setImageError(false)}); + + // Handles case where source is a component (ex: SVG) or a number + // Number may represent a SVG or an image + if ((maybeIcon && typeof source === 'number') || _.isFunction(source)) { + let iconFillColor = ''; + let additionalStyles = []; + if (isWorkspaceAvatar) { + const defaultWorkspaceAvatarColor = StyleUtils.getDefaultWorkspaceAvatarColor(file.name); + iconFillColor = defaultWorkspaceAvatarColor.fill; + additionalStyles = [defaultWorkspaceAvatarColor]; } return ( - - - - + + ); + } + + if (TransactionUtils.hasEReceipt(transaction)) { + return ( + + + + ); - }; - - function renderPDFView() { - const pdfHeight = isUsedAsChatAttachment ? THUMBNAIL_HEIGHT : windowHeight; - const pdfWeight = isUsedAsChatAttachment ? THUMBNAIL_WIDTH : windowWidth; - const pdfStyles = [StyleUtils.getWidthAndHeightStyle(pdfWeight, pdfHeight), themeStyles.imageModalPDF]; - - // 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 - // is positioned nicely. We're specifically hiding it because we still need to render - // the PDF component so that it can validate the password. - if (shouldRequestPassword) { - pdfStyles.push(themeStyles.invisible); - } - const containerStyles = - (shouldRequestPassword && isSmallScreenWidth) || isUsedAsChatAttachment ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; - const loadingIndicatorStyles = isUsedAsChatAttachment ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)] : []; + } + + // Check both source and file.name since PDFs dragged into the text field + // will appear with a source that is a blob + if ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) { + const encryptedSourceUrl = isAuthTokenRequired ? addEncryptedAuthTokenToURL(source) : source; + + const onPDFLoadComplete = (path) => { + const id = (transaction && transaction.transactionID) || reportActionID; + if (path && id) { + CachedPDFPaths.add(id, path); + } + if (!loadComplete) { + setLoadComplete(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. return ( - - {shouldAttemptPDFLoad && ( - } - source={{uri: sourceURL}} - style={pdfStyles} - onError={handleFailureToLoadPDF} - password={password} - onLoadComplete={finishPDFLoad} - onPageSingleTap={onPress} - onScaleChanged={onScaleChanged} - /> - )} - {shouldRequestPassword && ( - - setIsPasswordInvalid(false)} - isPasswordInvalid={isPasswordInvalid} - shouldShowLoadingIndicator={shouldShowLoadingIndicator} - /> - - )} + + ); } - // // uncomment this code to see failedToLoadPDF - // if (failedToLoadPDF) { - // return rendeFailedToLoadPDF(); - // } - - return onPress && !successToLoadPDF ? ( - - {renderPDFView()} - - ) : ( - renderPDFView() + + if (TransactionUtils.isDistanceRequest(transaction)) { + return ; + } + + // For this check we use both source and file.name since temporary file source is a blob + // both PDFs and images will appear as images when pasted into the text field. + // We also check for numeric source since this is how static images (used for preview) are represented in RN. + const isImage = typeof source === 'number' || Str.isImage(source); + if (isImage || (file && Str.isImage(file.name))) { + return ( + { + setImageError(true); + }} + /> + ); + } + + if (isVideo) { + return ( + + ); + } + + return ( + + + + + + {file && file.name} + {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( + + + + + + )} + {shouldShowLoadingSpinnerIcon && ( + + + + + + )} + ); } -PDFView.displayName = 'PDFView'; -PDFView.propTypes = propTypes; -PDFView.defaultProps = defaultProps; +AttachmentView.propTypes = propTypes; +AttachmentView.defaultProps = defaultProps; +AttachmentView.displayName = 'AttachmentView'; -export default PDFView; +export default compose( + memo, + withLocalize, + withOnyx({ + transaction: { + key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + }, + }), +)(AttachmentView); From 36b581464aa1c9f6fbbb08dbc70f939b55017756 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 9 Mar 2024 03:57:23 +0530 Subject: [PATCH 005/174] fix conflict after merge --- .../DefaultAttachmentView.tsx | 59 ------------------- .../Attachments/AttachmentView/index.js | 7 ++- src/components/PDFView/index.native.js | 1 + 3 files changed, 7 insertions(+), 60 deletions(-) delete mode 100644 src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx deleted file mode 100644 index ad102fd5caad..000000000000 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/DefaultAttachmentView.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import {ActivityIndicator, View} from 'react-native'; -import type {StyleProp, ViewStyle} from 'react-native'; -import _ from 'underscore'; -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 = { - file: File; - shouldShowLoadingSpinnerIcon: boolean; - shouldShowDownloadIcon: boolean; - containerStyles: StyleProp[]; -}; - -function DefaultAttachmentView({file, shouldShowLoadingSpinnerIcon, shouldShowDownloadIcon, containerStyles}: DefaultAttachmentViewProps) { - const theme = useTheme(); - const styles = useThemeStyles(); - const {translate} = useLocalize(); - - return ( - - - - - - {file && file.name} - {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( - - - - - - )} - {shouldShowLoadingSpinnerIcon && ( - - - - - - )} - - ); -} - -export default DefaultAttachmentView; diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 461548f0d2b1..fc11ccfd8fac 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -107,6 +107,7 @@ function AttachmentView({ const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [loadComplete, setLoadComplete] = useState(false); + const [isPdfFailedToLoad, setIsPdfFailedToLoad] = useState(false); const isVideo = (typeof source === 'string' && Str.isVideo(source)) || (file && Str.isVideo(file.name)); useEffect(() => { @@ -157,7 +158,7 @@ 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 ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) { + if (!isPdfFailedToLoad && ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename'))))) { const encryptedSourceUrl = isAuthTokenRequired ? addEncryptedAuthTokenToURL(source) : source; const onPDFLoadComplete = (path) => { @@ -187,6 +188,10 @@ function AttachmentView({ errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [styles.cursorAuto]} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedInCarousel={isUsedInCarousel} + isUsedAsChatAttachment={!(isUsedInAttachmentModal || isUsedInCarousel)} + onError={() => { + setIsPdfFailedToLoad(true); + }} /> ); diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 492a12e1dc66..f7901b035d8b 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -124,6 +124,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused setSuccessToLoadPDF(true); onLoadComplete(path); }; + // eslint-disable-next-line no-unused-vars const rendeFailedToLoadPDF = () => { if (!isUsedAsChatAttachment) { return ( From 75a733c60b7f82a3221bb24a10b744da622978db Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 14 Mar 2024 01:32:31 +0530 Subject: [PATCH 006/174] clean up and lint fix --- .../AttachmentViewPdf/BaseAttachmentViewPdf.js | 2 -- .../AttachmentView/AttachmentViewPdf/index.js | 4 ++-- .../AttachmentView/AttachmentViewPdf/propTypes.js | 3 --- src/components/Attachments/AttachmentView/index.js | 6 +----- src/components/PDFView/index.native.js | 11 +++++------ 5 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js index 3f3b995c02ba..289909a66dab 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js @@ -29,7 +29,6 @@ function BaseAttachmentViewPdf({ errorLabelStyles, style, isUsedAsChatAttachment, - onError, }) { const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const isScrollEnabled = attachmentCarouselPagerContext === null ? undefined : attachmentCarouselPagerContext.isScrollEnabled; @@ -92,7 +91,6 @@ function BaseAttachmentViewPdf({ onLoadComplete={onLoadComplete} errorLabelStyles={errorLabelStyles} isUsedAsChatAttachment={isUsedAsChatAttachment} - onError={onError} /> ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js index de740f4fdd1f..af665d73a3ba 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js @@ -2,7 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, isUsedAsChatAttachment, onError}) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, isUsedAsChatAttachment}) { return ( ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js index bcb0fabde352..b6ad86124f63 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js @@ -16,9 +16,6 @@ const attachmentViewPdfPropTypes = { /** Styles for the error label */ errorLabelStyles: stylePropTypes, - /** Callback when the pdf fails to load */ - onError: PropTypes.func, - /** Whether the attachment is used as a chat attachment */ isUsedAsChatAttachment: PropTypes.bool, }; diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index fc11ccfd8fac..6ba3035afc86 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -107,7 +107,6 @@ function AttachmentView({ const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [loadComplete, setLoadComplete] = useState(false); - const [isPdfFailedToLoad, setIsPdfFailedToLoad] = useState(false); const isVideo = (typeof source === 'string' && Str.isVideo(source)) || (file && Str.isVideo(file.name)); useEffect(() => { @@ -158,7 +157,7 @@ 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 (!isPdfFailedToLoad && ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename'))))) { + if ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) { const encryptedSourceUrl = isAuthTokenRequired ? addEncryptedAuthTokenToURL(source) : source; const onPDFLoadComplete = (path) => { @@ -189,9 +188,6 @@ function AttachmentView({ style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedInCarousel={isUsedInCarousel} isUsedAsChatAttachment={!(isUsedInAttachmentModal || isUsedInCarousel)} - onError={() => { - setIsPdfFailedToLoad(true); - }} /> ); diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index f7901b035d8b..addbcec9b3bf 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -41,7 +41,7 @@ const propTypes = { const THUMBNAIL_HEIGHT = 250; const THUMBNAIL_WIDTH = 250; -function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles, onError, isUsedAsChatAttachment}) { +function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles, isUsedAsChatAttachment}) { const [shouldRequestPassword, setShouldRequestPassword] = useState(false); const [shouldAttemptPDFLoad, setShouldAttemptPDFLoad] = useState(true); const [shouldShowLoadingIndicator, setShouldShowLoadingIndicator] = useState(true); @@ -94,7 +94,6 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused setShouldShowLoadingIndicator(false); setShouldRequestPassword(false); setShouldAttemptPDFLoad(false); - onError(error); }; /** @@ -194,10 +193,10 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused ); } - // // uncomment this code to see failedToLoadPDF - // if (failedToLoadPDF) { - // return rendeFailedToLoadPDF(); - // } + + if (failedToLoadPDF) { + return rendeFailedToLoadPDF(); + } return onPress && !successToLoadPDF ? ( Date: Sat, 9 Mar 2024 03:25:09 +0530 Subject: [PATCH 007/174] add POC for fallingback to default attachment view when pdf fails to load --- .../BaseAttachmentViewPdf.js | 2 + .../AttachmentView/AttachmentViewPdf/index.js | 4 +- .../AttachmentViewPdf/propTypes.js | 3 ++ .../Attachments/AttachmentView/index.js | 16 ++++++++ src/components/PDFView/index.native.js | 38 +------------------ 5 files changed, 25 insertions(+), 38 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js index 289909a66dab..3f3b995c02ba 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js @@ -29,6 +29,7 @@ function BaseAttachmentViewPdf({ errorLabelStyles, style, isUsedAsChatAttachment, + onError, }) { const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const isScrollEnabled = attachmentCarouselPagerContext === null ? undefined : attachmentCarouselPagerContext.isScrollEnabled; @@ -91,6 +92,7 @@ function BaseAttachmentViewPdf({ onLoadComplete={onLoadComplete} errorLabelStyles={errorLabelStyles} isUsedAsChatAttachment={isUsedAsChatAttachment} + onError={onError} /> ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js index af665d73a3ba..6377bc6e585a 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js @@ -2,7 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, isUsedAsChatAttachment}) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, onError}) { return ( ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js index b6ad86124f63..bcb0fabde352 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js @@ -16,6 +16,9 @@ const attachmentViewPdfPropTypes = { /** Styles for the error label */ errorLabelStyles: stylePropTypes, + /** Callback when the pdf fails to load */ + onError: PropTypes.func, + /** Whether the attachment is used as a chat attachment */ isUsedAsChatAttachment: PropTypes.bool, }; diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 6ba3035afc86..863f37733972 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -26,6 +26,7 @@ import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; import AttachmentViewImage from './AttachmentViewImage'; import AttachmentViewPdf from './AttachmentViewPdf'; +import DefaultAttachmentView from './AttachmentViewPdf/DefaultAttachmentView'; import AttachmentViewVideo from './AttachmentViewVideo'; import {attachmentViewDefaultProps, attachmentViewPropTypes} from './propTypes'; @@ -108,6 +109,7 @@ function AttachmentView({ const StyleUtils = useStyleUtils(); const [loadComplete, setLoadComplete] = useState(false); const isVideo = (typeof source === 'string' && Str.isVideo(source)) || (file && Str.isVideo(file.name)); + const [isPdfFailedToLoad, setIsPdfFailedToLoad] = useState(false); useEffect(() => { if (!isFocused && !(file && isUsedInAttachmentModal)) { @@ -170,6 +172,17 @@ function AttachmentView({ } }; + if (isPdfFailedToLoad) { + return ( + + ); + } + // 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. @@ -188,6 +201,9 @@ function AttachmentView({ style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedInCarousel={isUsedInCarousel} isUsedAsChatAttachment={!(isUsedInAttachmentModal || isUsedInCarousel)} + onError={() => { + setIsPdfFailedToLoad(true); + }} /> ); diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index addbcec9b3bf..2f9bb1a40573 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -2,19 +2,13 @@ import React, {useCallback, useEffect, useState} from 'react'; import {View} from 'react-native'; import PDF from 'react-native-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; 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 useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import variables from '@styles/variables'; import CONST from '@src/CONST'; import PDFPasswordForm from './PDFPasswordForm'; import {defaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; @@ -41,7 +35,7 @@ const propTypes = { const THUMBNAIL_HEIGHT = 250; const THUMBNAIL_WIDTH = 250; -function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, errorLabelStyles, isUsedAsChatAttachment}) { +function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, onError, isUsedAsChatAttachment}) { const [shouldRequestPassword, setShouldRequestPassword] = useState(false); const [shouldAttemptPDFLoad, setShouldAttemptPDFLoad] = useState(true); const [shouldShowLoadingIndicator, setShouldShowLoadingIndicator] = useState(true); @@ -54,8 +48,6 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused const themeStyles = useThemeStyles(); const {isKeyboardShown} = useKeyboardState(); const StyleUtils = useStyleUtils(); - const {isOffline} = useNetwork(); - const theme = useTheme(); useEffect(() => { onToggleKeyboard(isKeyboardShown); @@ -94,6 +86,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused setShouldShowLoadingIndicator(false); setShouldRequestPassword(false); setShouldAttemptPDFLoad(false); + onError(error); }; /** @@ -123,29 +116,6 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused setSuccessToLoadPDF(true); onLoadComplete(path); }; - // eslint-disable-next-line no-unused-vars - const rendeFailedToLoadPDF = () => { - if (!isUsedAsChatAttachment) { - return ( - - {translate('attachmentView.failedToLoadPDF')} - - ); - } - - return ( - - - - - - ); - }; function renderPDFView() { const pdfHeight = isUsedAsChatAttachment ? THUMBNAIL_HEIGHT : windowHeight; @@ -194,10 +164,6 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused ); } - if (failedToLoadPDF) { - return rendeFailedToLoadPDF(); - } - return onPress && !successToLoadPDF ? ( Date: Sat, 16 Mar 2024 03:17:27 +0530 Subject: [PATCH 008/174] fixes missing prop --- .../Attachments/AttachmentView/AttachmentViewPdf/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js index 6377bc6e585a..f151bf84b7a8 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js @@ -2,7 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, onError}) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, onError, isUsedAsChatAttachment}) { return ( ); } From b4ac9a1cb3518627a16a401654a80ef47ef6625d Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 16 Mar 2024 03:20:24 +0530 Subject: [PATCH 009/174] remove unused import --- src/components/Attachments/AttachmentView/index.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 863f37733972..56ebad34e963 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -26,7 +26,6 @@ import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; import AttachmentViewImage from './AttachmentViewImage'; import AttachmentViewPdf from './AttachmentViewPdf'; -import DefaultAttachmentView from './AttachmentViewPdf/DefaultAttachmentView'; import AttachmentViewVideo from './AttachmentViewVideo'; import {attachmentViewDefaultProps, attachmentViewPropTypes} from './propTypes'; @@ -159,7 +158,7 @@ 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 ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) { + if (!isPdfFailedToLoad && ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename'))))) { const encryptedSourceUrl = isAuthTokenRequired ? addEncryptedAuthTokenToURL(source) : source; const onPDFLoadComplete = (path) => { @@ -172,17 +171,6 @@ function AttachmentView({ } }; - if (isPdfFailedToLoad) { - return ( - - ); - } - // 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. From 03726012f10ad31c29f082ba614a9175d0110b62 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 16 Mar 2024 03:23:31 +0530 Subject: [PATCH 010/174] change variable name --- src/components/PDFView/index.native.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 2f9bb1a40573..c0f36f310346 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -32,8 +32,8 @@ const propTypes = { * is (temporarily) rendered. */ -const THUMBNAIL_HEIGHT = 250; -const THUMBNAIL_WIDTH = 250; +const LOADING_THUMBNAIL_HEIGHT = 250; +const LOADING_THUMBNAIL_WIDTH = 250; function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, onError, isUsedAsChatAttachment}) { const [shouldRequestPassword, setShouldRequestPassword] = useState(false); @@ -118,8 +118,8 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused }; function renderPDFView() { - const pdfHeight = isUsedAsChatAttachment ? THUMBNAIL_HEIGHT : windowHeight; - const pdfWeight = isUsedAsChatAttachment ? THUMBNAIL_WIDTH : windowWidth; + const pdfHeight = isUsedAsChatAttachment ? LOADING_THUMBNAIL_HEIGHT : windowHeight; + const pdfWeight = isUsedAsChatAttachment ? LOADING_THUMBNAIL_WIDTH : windowWidth; const pdfStyles = [StyleUtils.getWidthAndHeightStyle(pdfWeight, pdfHeight), themeStyles.imageModalPDF]; // If we haven't yet successfully validated the password and loaded the PDF, @@ -131,7 +131,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused } const containerStyles = (shouldRequestPassword && isSmallScreenWidth) || isUsedAsChatAttachment ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; - const loadingIndicatorStyles = isUsedAsChatAttachment ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)] : []; + const loadingIndicatorStyles = isUsedAsChatAttachment ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT)] : []; return ( From a20811ae16bab37acca9005f8b481a52f5be6d31 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 16 Mar 2024 03:57:40 +0530 Subject: [PATCH 011/174] fixes lint --- .../AttachmentCarousel/CarouselItem.js | 1 + .../Attachments/AttachmentView/index.js | 2 +- src/components/PDFView/WebPDFDocument.js | 9 +-------- src/components/PDFView/index.native.js | 18 +++++++++--------- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index b2c9fed64467..e924cb8c13e9 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -109,6 +109,7 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}) { isHovered={isModalHovered} isFocused={isFocused} optionalVideoDuration={item.duration} + isUsedInCarousel /> diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 56ebad34e963..4a167ba35c5d 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -188,7 +188,7 @@ function AttachmentView({ errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [styles.cursorAuto]} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedInCarousel={isUsedInCarousel} - isUsedAsChatAttachment={!(isUsedInAttachmentModal || isUsedInCarousel)} + isUsedAsChatAttachment={!isUsedInAttachmentModal && !isUsedInCarousel} onError={() => { setIsPdfFailedToLoad(true); }} diff --git a/src/components/PDFView/WebPDFDocument.js b/src/components/PDFView/WebPDFDocument.js index d2e93e3ba506..3b1b3ba7c765 100644 --- a/src/components/PDFView/WebPDFDocument.js +++ b/src/components/PDFView/WebPDFDocument.js @@ -5,16 +5,11 @@ import {Document} from 'react-pdf'; import {VariableSizeList as List} from 'react-window'; import _ from 'underscore'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import Text from '@components/Text'; import stylePropTypes from '@styles/stylePropTypes'; import CONST from '@src/CONST'; import PageRenderer from './WebPDFPageRenderer'; const propTypes = { - /** Index of the PDF page to be displayed passed by VariableSizeList */ - errorLabelStyles: stylePropTypes, - /** Returns translated string for given locale and phrase */ - translate: PropTypes.func.isRequired, /** The source URL from which to load PDF file to be displayed */ sourceURL: PropTypes.string.isRequired, /** Callback invoked when the PDF document is loaded successfully */ @@ -53,7 +48,6 @@ const propTypes = { }; const defaultProps = { - errorLabelStyles: [], numPages: null, listStyle: undefined, password: undefined, @@ -61,8 +55,6 @@ const defaultProps = { const WebPDFDocument = memo( ({ - errorLabelStyles, - translate, sourceURL, onDocumentLoadSuccess, pageViewportsLength, @@ -78,6 +70,7 @@ const WebPDFDocument = memo( listStyle, initiatePasswordChallenge, password, + onError, }) => { const onPassword = useCallback( (callback, reason) => { diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index c0f36f310346..87262a566bc7 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -1,5 +1,5 @@ -import React, {useCallback, useEffect, useState} from 'react'; -import {View} from 'react-native'; +import React, { useCallback, useEffect, useState } from 'react'; +import { View } from 'react-native'; import PDF from 'react-native-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; @@ -11,7 +11,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import CONST from '@src/CONST'; import PDFPasswordForm from './PDFPasswordForm'; -import {defaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; +import { defaultProps, propTypes as pdfViewPropTypes } from './pdfViewPropTypes'; const propTypes = { ...pdfViewPropTypes, @@ -35,7 +35,7 @@ const propTypes = { const LOADING_THUMBNAIL_HEIGHT = 250; const LOADING_THUMBNAIL_WIDTH = 250; -function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, onError, isUsedAsChatAttachment}) { +function PDFView({ onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, onError, isUsedAsChatAttachment }) { const [shouldRequestPassword, setShouldRequestPassword] = useState(false); const [shouldAttemptPDFLoad, setShouldAttemptPDFLoad] = useState(true); const [shouldShowLoadingIndicator, setShouldShowLoadingIndicator] = useState(true); @@ -43,10 +43,10 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused const [failedToLoadPDF, setFailedToLoadPDF] = useState(false); const [successToLoadPDF, setSuccessToLoadPDF] = useState(false); const [password, setPassword] = useState(''); - const {windowWidth, windowHeight, isSmallScreenWidth} = useWindowDimensions(); - const {translate} = useLocalize(); + const { windowWidth, windowHeight, isSmallScreenWidth } = useWindowDimensions(); + const { translate } = useLocalize(); const themeStyles = useThemeStyles(); - const {isKeyboardShown} = useKeyboardState(); + const { isKeyboardShown } = useKeyboardState(); const StyleUtils = useStyleUtils(); useEffect(() => { @@ -130,7 +130,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused pdfStyles.push(themeStyles.invisible); } const containerStyles = - (shouldRequestPassword && isSmallScreenWidth) || isUsedAsChatAttachment ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; + 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 ( @@ -140,7 +140,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused fitPolicy={0} trustAllCerts={false} renderActivityIndicator={() => } - source={{uri: sourceURL}} + source={{ uri: sourceURL, cache: true, expiration: 864000 }} style={pdfStyles} onError={handleFailureToLoadPDF} password={password} From 3ace4d305de71b924bd53a82d9e54f864fc07461 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 16 Mar 2024 04:00:16 +0530 Subject: [PATCH 012/174] use correct conditional and use js docs for props --- .../BaseAnchorForAttachmentsOnly.tsx | 1 + src/components/Attachments/AttachmentView/index.js | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx index d389ac4b92f0..3e7189ce0304 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx @@ -64,6 +64,7 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow file={{name: displayName}} shouldShowDownloadIcon={!isOffline} shouldShowLoadingSpinnerIcon={isDownloading} + isUsedAsChatAttachment /> )} diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 4a167ba35c5d..0d25de1bd799 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -62,9 +62,14 @@ const propTypes = { /** The id of the report action related to the attachment */ reportActionID: PropTypes.string, + /** Whether the attachment is currently being hovered over */ isHovered: PropTypes.bool, + /** The duration of the video */ optionalVideoDuration: PropTypes.number, + + /** Whether the attachment is being used as a chat attachment */ + isUsedAsChatAttachment: PropTypes.bool, }; const defaultProps = { @@ -79,6 +84,7 @@ const defaultProps = { reportActionID: '', isHovered: false, optionalVideoDuration: 0, + isUsedAsChatAttachment: false, }; function AttachmentView({ @@ -101,6 +107,7 @@ function AttachmentView({ reportActionID, isHovered, optionalVideoDuration, + isUsedAsChatAttachment, }) { const {updateCurrentlyPlayingURL} = usePlaybackContext(); const theme = useTheme(); @@ -188,7 +195,7 @@ function AttachmentView({ errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [styles.cursorAuto]} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedInCarousel={isUsedInCarousel} - isUsedAsChatAttachment={!isUsedInAttachmentModal && !isUsedInCarousel} + isUsedAsChatAttachment={isUsedAsChatAttachment} onError={() => { setIsPdfFailedToLoad(true); }} From 830a392f5097a91c9d3689c7f500bb54ec0c28e2 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 16 Mar 2024 04:06:08 +0530 Subject: [PATCH 013/174] prettier diffs --- src/components/PDFView/index.native.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js index 87262a566bc7..1c3cd02308fb 100644 --- a/src/components/PDFView/index.native.js +++ b/src/components/PDFView/index.native.js @@ -1,5 +1,5 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { View } from 'react-native'; +import React, {useCallback, useEffect, useState} from 'react'; +import {View} from 'react-native'; import PDF from 'react-native-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; @@ -11,7 +11,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import CONST from '@src/CONST'; import PDFPasswordForm from './PDFPasswordForm'; -import { defaultProps, propTypes as pdfViewPropTypes } from './pdfViewPropTypes'; +import {defaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; const propTypes = { ...pdfViewPropTypes, @@ -35,7 +35,7 @@ const propTypes = { const LOADING_THUMBNAIL_HEIGHT = 250; const LOADING_THUMBNAIL_WIDTH = 250; -function PDFView({ onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, onError, isUsedAsChatAttachment }) { +function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused, onScaleChanged, sourceURL, onError, isUsedAsChatAttachment}) { const [shouldRequestPassword, setShouldRequestPassword] = useState(false); const [shouldAttemptPDFLoad, setShouldAttemptPDFLoad] = useState(true); const [shouldShowLoadingIndicator, setShouldShowLoadingIndicator] = useState(true); @@ -43,10 +43,10 @@ function PDFView({ onToggleKeyboard, onLoadComplete, fileName, onPress, isFocuse const [failedToLoadPDF, setFailedToLoadPDF] = useState(false); const [successToLoadPDF, setSuccessToLoadPDF] = useState(false); const [password, setPassword] = useState(''); - const { windowWidth, windowHeight, isSmallScreenWidth } = useWindowDimensions(); - const { translate } = useLocalize(); + const {windowWidth, windowHeight, isSmallScreenWidth} = useWindowDimensions(); + const {translate} = useLocalize(); const themeStyles = useThemeStyles(); - const { isKeyboardShown } = useKeyboardState(); + const {isKeyboardShown} = useKeyboardState(); const StyleUtils = useStyleUtils(); useEffect(() => { @@ -131,7 +131,9 @@ function PDFView({ onToggleKeyboard, onLoadComplete, fileName, onPress, isFocuse } const containerStyles = isUsedAsChatAttachment || (shouldRequestPassword && isSmallScreenWidth) ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; - const loadingIndicatorStyles = isUsedAsChatAttachment ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT)] : []; + const loadingIndicatorStyles = isUsedAsChatAttachment + ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT)] + : []; return ( @@ -140,7 +142,7 @@ function PDFView({ onToggleKeyboard, onLoadComplete, fileName, onPress, isFocuse fitPolicy={0} trustAllCerts={false} renderActivityIndicator={() => } - source={{ uri: sourceURL, cache: true, expiration: 864000 }} + source={{uri: sourceURL, cache: true, expiration: 864000}} style={pdfStyles} onError={handleFailureToLoadPDF} password={password} From 3813026c23140b62b6b566ec587f4d1da9bdc15c Mon Sep 17 00:00:00 2001 From: Ishpaul Singh <104348397+ishpaul777@users.noreply.github.com> Date: Sat, 16 Mar 2024 04:10:54 +0530 Subject: [PATCH 014/174] Update src/components/Attachments/AttachmentCarousel/CarouselItem.js --- src/components/Attachments/AttachmentCarousel/CarouselItem.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index e924cb8c13e9..b2c9fed64467 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -109,7 +109,6 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}) { isHovered={isModalHovered} isFocused={isFocused} optionalVideoDuration={item.duration} - isUsedInCarousel /> From a8cfafea38386727c62d8cdb4081fae9d0b0d62a Mon Sep 17 00:00:00 2001 From: Ishpaul Singh <104348397+ishpaul777@users.noreply.github.com> Date: Sat, 16 Mar 2024 04:21:58 +0530 Subject: [PATCH 015/174] remove unused styles --- src/styles/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index ffd05b97473d..2efb29a95ea5 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -516,10 +516,6 @@ const styles = (theme: ThemeColors) => borderRadius: variables.buttonBorderRadius, }, - componentBorderRadiusNormal: { - borderRadius: variables.componentBorderRadiusNormal, - }, - bottomTabBarContainer: { flexDirection: 'row', height: variables.bottomTabHeight, From a45f6f0ba20490a03bbc5c74d77097eb30a3cd1b Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Tue, 19 Mar 2024 13:37:32 +0530 Subject: [PATCH 016/174] remove error label styles --- .../AttachmentViewPdf/BaseAttachmentViewPdf.js | 2 -- .../Attachments/AttachmentView/AttachmentViewPdf/index.js | 3 +-- .../AttachmentView/AttachmentViewPdf/propTypes.js | 4 ---- src/components/Attachments/AttachmentView/index.js | 1 - src/components/PDFView/index.js | 1 - src/components/PDFView/pdfViewPropTypes.js | 8 +++++--- 6 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js index 3f3b995c02ba..8ae5a483c9fa 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js @@ -26,7 +26,6 @@ function BaseAttachmentViewPdf({ onScaleChanged: onScaleChangedProp, onToggleKeyboard, onLoadComplete, - errorLabelStyles, style, isUsedAsChatAttachment, onError, @@ -90,7 +89,6 @@ function BaseAttachmentViewPdf({ onToggleKeyboard={onToggleKeyboard} onScaleChanged={onScaleChanged} onLoadComplete={onLoadComplete} - errorLabelStyles={errorLabelStyles} isUsedAsChatAttachment={isUsedAsChatAttachment} onError={onError} /> diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js index f151bf84b7a8..1d5ffbccf40c 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.js @@ -2,7 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, errorLabelStyles, style, onError, isUsedAsChatAttachment}) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, onError, isUsedAsChatAttachment}) { return ( diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js index bcb0fabde352..75984e6ac031 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js @@ -13,9 +13,6 @@ const attachmentViewPdfPropTypes = { /** Additional style props */ style: stylePropTypes, - /** Styles for the error label */ - errorLabelStyles: stylePropTypes, - /** Callback when the pdf fails to load */ onError: PropTypes.func, @@ -28,7 +25,6 @@ const attachmentViewPdfDefaultProps = { name: '', }, style: [], - errorLabelStyles: [], onError: () => {}, isUsedAsChatAttachment: false, }; diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 0d25de1bd799..701f6343eb25 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -192,7 +192,6 @@ function AttachmentView({ onPress={onPress} onToggleKeyboard={onToggleKeyboard} onLoadComplete={onPDFLoadComplete} - errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [styles.cursorAuto]} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedInCarousel={isUsedInCarousel} isUsedAsChatAttachment={isUsedAsChatAttachment} diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 2e39594fc210..838fd843b089 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -254,7 +254,6 @@ class PDFView extends Component { > {}, onLoadComplete: () => {}, isFocused: false, - errorLabelStyles: [], }; export {propTypes, defaultProps}; From 32f350390c152890ab3caa67a47fc2bae9833e8b Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 29 Mar 2024 02:03:33 +0530 Subject: [PATCH 017/174] fix conflicts --- .../Attachments/AttachmentView/AttachmentViewPdf/types.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts index f1f141a625db..da48480e8479 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts @@ -13,6 +13,10 @@ type AttachmentViewPdfProps = Pick void; + + onError?: () => void; + + isUsedAsChatAttachment?: boolean; }; export default AttachmentViewPdfProps; From 1c814c51a14085a59de008c08db62912a7fe9476 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 29 Mar 2024 02:09:57 +0530 Subject: [PATCH 018/174] fix lint --- .../Attachments/AttachmentView/AttachmentViewPdf/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index d38ffcf7abab..78265c3a9a6a 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, style, onError, isUsedAsChatAttachment} : AttachmentViewPdfProps) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, onError, isUsedAsChatAttachment}: AttachmentViewPdfProps) { return ( Date: Fri, 29 Mar 2024 03:38:54 +0530 Subject: [PATCH 019/174] fallback to defaultview component --- .../AttachmentViewPdf/index.tsx | 13 +++- .../AttachmentView/AttachmentViewPdf/types.ts | 5 ++ .../DefaultAttachmentView/index.tsx | 58 ++++++++++++++++ .../Attachments/AttachmentView/index.tsx | 68 ++++++------------- src/components/PDFView/index.js | 3 +- 5 files changed, 96 insertions(+), 51 deletions(-) create mode 100644 src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index 78265c3a9a6a..10bcf28e7797 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -2,7 +2,17 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import type AttachmentViewPdfProps from './types'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, onError, isUsedAsChatAttachment}: AttachmentViewPdfProps) { +function AttachmentViewPdf({ + file, + encryptedSourceUrl, + isFocused, + onPress, + onToggleKeyboard, + onLoadComplete, + style, + renderFallbackAttachmentView, + isUsedAsChatAttachment, +}: AttachmentViewPdfProps) { return ( ); diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts index da48480e8479..ecc6e5016446 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts @@ -14,9 +14,14 @@ type AttachmentViewPdfProps = Pick void; + /** Triggered when the PDF fails to load */ onError?: () => void; + /** Whether the PDF is used as a chat attachment */ isUsedAsChatAttachment?: boolean; + + /** Render a fallback view when the PDF fails to load */ + renderFallbackAttachmentView?: () => JSX.Element; }; 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..7360144bf09c --- /dev/null +++ b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx @@ -0,0 +1,58 @@ +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 = { + fileName: string; + shouldShowDownloadIcon?: boolean; + shouldShowLoadingSpinnerIcon?: boolean; + containerStyles?: StyleProp; +}; + +function DefaultAttachmentView({fileName, shouldShowDownloadIcon, shouldShowLoadingSpinnerIcon, containerStyles}: DefaultAttachmentViewProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + return ( + + + + + + {fileName} + {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( + + + + + + )} + {shouldShowLoadingSpinnerIcon && ( + + + + + + )} + + ); +} + +export default DefaultAttachmentView; diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index e3b609852e5e..1621d8d9509d 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -1,18 +1,15 @@ 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 type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; -import type {Attachment, AttachmentSource} from '@components/Attachments/types'; +import React, { memo, useEffect, useState } from 'react'; +import type { GestureResponderEvent, StyleProp, ViewStyle } 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 { usePlaybackContext } from '@components/VideoPlayerContexts/PlaybackContext'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -21,13 +18,14 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as CachedPDFPaths from '@libs/actions/CachedPDFPaths'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import * as TransactionUtils from '@libs/TransactionUtils'; -import type {ColorValue} from '@styles/utils/types'; +import type { ColorValue } from '@styles/utils/types'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Transaction} from '@src/types/onyx'; +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; @@ -95,8 +93,8 @@ function AttachmentView({ duration, isUsedAsChatAttachment, }: AttachmentViewProps) { - const {translate} = useLocalize(); - const {updateCurrentlyPlayingURL} = usePlaybackContext(); + const { translate } = useLocalize(); + const { updateCurrentlyPlayingURL } = usePlaybackContext(); const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -113,7 +111,7 @@ function AttachmentView({ const [imageError, setImageError] = useState(false); - useNetwork({onReconnect: () => setImageError(false)}); + useNetwork({ onReconnect: () => setImageError(false) }); // Handles case where source is a component (ex: SVG) or a number // Number may represent a SVG or an image @@ -239,36 +237,12 @@ function AttachmentView({ } return ( - - - - - - {file?.name} - {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( - - - - - - )} - {shouldShowLoadingSpinnerIcon && ( - - - - - - )} - + ); } @@ -277,9 +251,9 @@ AttachmentView.displayName = 'AttachmentView'; export default memo( withOnyx({ transaction: { - key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + key: ({ transactionID }) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, }, })(AttachmentView), ); -export type {AttachmentViewProps}; +export type { AttachmentViewProps }; diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index e69b52b74e95..bd4f9a584378 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -6,7 +6,6 @@ import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import Text from '@components/Text'; import withLocalize from '@components/withLocalize'; import withThemeStyles from '@components/withThemeStyles'; import withWindowDimensions from '@components/withWindowDimensions'; @@ -94,7 +93,7 @@ class PDFView extends Component { maxCanvasHeight={this.props.maxCanvasHeight} maxCanvasArea={this.props.maxCanvasArea} LoadingComponent={} - ErrorComponent={{this.props.translate('attachmentView.failedToLoadPDF')}} + ErrorComponent={this.props.renderFallbackAttachmentView} renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( Date: Fri, 29 Mar 2024 04:38:22 +0530 Subject: [PATCH 020/174] fix prop pass --- .../Attachments/AttachmentView/AttachmentViewPdf/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index 10bcf28e7797..3afb02db5068 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -24,6 +24,7 @@ function AttachmentViewPdf({ onToggleKeyboard={onToggleKeyboard} onLoadComplete={onLoadComplete} isUsedAsChatAttachment={isUsedAsChatAttachment} + renderFallbackAttachmentView={renderFallbackAttachmentView} /> ); } From 7d5d22a60c27c70622f097ae0ebd92c0e8dcf03e Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 29 Mar 2024 05:01:20 +0530 Subject: [PATCH 021/174] prettier diffs --- .../Attachments/AttachmentView/index.tsx | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index 1621d8d9509d..d08f10582c52 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -1,15 +1,15 @@ import Str from 'expensify-common/lib/str'; -import React, { memo, useEffect, useState } from 'react'; -import type { GestureResponderEvent, StyleProp, ViewStyle } 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 React, {memo, useEffect, useState} from 'react'; +import type {GestureResponderEvent, StyleProp, ViewStyle} 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 ScrollView from '@components/ScrollView'; -import { usePlaybackContext } from '@components/VideoPlayerContexts/PlaybackContext'; +import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -18,10 +18,10 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as CachedPDFPaths from '@libs/actions/CachedPDFPaths'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import * as TransactionUtils from '@libs/TransactionUtils'; -import type { ColorValue } from '@styles/utils/types'; +import type {ColorValue} from '@styles/utils/types'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; -import type { Transaction } from '@src/types/onyx'; +import type {Transaction} from '@src/types/onyx'; import AttachmentViewImage from './AttachmentViewImage'; import AttachmentViewPdf from './AttachmentViewPdf'; import AttachmentViewVideo from './AttachmentViewVideo'; @@ -93,8 +93,8 @@ function AttachmentView({ duration, isUsedAsChatAttachment, }: AttachmentViewProps) { - const { translate } = useLocalize(); - const { updateCurrentlyPlayingURL } = usePlaybackContext(); + const {translate} = useLocalize(); + const {updateCurrentlyPlayingURL} = usePlaybackContext(); const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -111,7 +111,7 @@ function AttachmentView({ const [imageError, setImageError] = useState(false); - useNetwork({ onReconnect: () => setImageError(false) }); + useNetwork({onReconnect: () => setImageError(false)}); // Handles case where source is a component (ex: SVG) or a number // Number may represent a SVG or an image @@ -251,9 +251,9 @@ AttachmentView.displayName = 'AttachmentView'; export default memo( withOnyx({ transaction: { - key: ({ transactionID }) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, }, })(AttachmentView), ); -export type { AttachmentViewProps }; +export type {AttachmentViewProps}; From 0af56c671213f1b2ade9f045259edcf27db8a7ef Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 30 Mar 2024 01:50:13 +0530 Subject: [PATCH 022/174] remove renderfallbackview prop --- .../AttachmentViewPdf/BaseAttachmentViewPdf.tsx | 4 ++-- .../AttachmentView/AttachmentViewPdf/index.tsx | 14 ++------------ .../AttachmentView/AttachmentViewPdf/types.ts | 9 +++------ .../AttachmentView/DefaultAttachmentView/index.tsx | 4 ++-- .../Attachments/AttachmentView/index.tsx | 6 +++--- src/components/PDFView/index.js | 9 ++++++++- src/components/PDFView/index.native.js | 4 ++-- src/components/PDFView/pdfViewPropTypes.js | 5 ++++- 8 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx index 9971c2d03820..eb22535d1d5f 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx @@ -15,7 +15,7 @@ function BaseAttachmentViewPdf({ onLoadComplete, style, isUsedAsChatAttachment, - onError, + onLoadError, }: AttachmentViewPdfProps) { const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const isScrollEnabled = attachmentCarouselPagerContext === null ? undefined : attachmentCarouselPagerContext.isScrollEnabled; @@ -78,7 +78,7 @@ function BaseAttachmentViewPdf({ onScaleChanged={onScaleChanged} onLoadComplete={onLoadComplete} isUsedAsChatAttachment={isUsedAsChatAttachment} - onError={onError} + onLoadError={onLoadError} /> ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index 3afb02db5068..c1ded9782a7a 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -2,17 +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, - style, - renderFallbackAttachmentView, - isUsedAsChatAttachment, -}: AttachmentViewPdfProps) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, isUsedAsChatAttachment, containerStyles}: AttachmentViewPdfProps) { return ( ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts index ecc6e5016446..1c3cd8adfba4 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts @@ -8,20 +8,17 @@ 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 */ - onError?: () => void; + onLoadError?: () => void; /** Whether the PDF is used as a chat attachment */ isUsedAsChatAttachment?: boolean; - /** Render a fallback view when the PDF fails to load */ - renderFallbackAttachmentView?: () => JSX.Element; + /** Additional container styles */ + containerStyles?: StyleProp; }; export default AttachmentViewPdfProps; diff --git a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx index 7360144bf09c..23d39ec096b5 100644 --- a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx @@ -10,13 +10,13 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; type DefaultAttachmentViewProps = { - fileName: string; + fileName?: string; shouldShowDownloadIcon?: boolean; shouldShowLoadingSpinnerIcon?: boolean; containerStyles?: StyleProp; }; -function DefaultAttachmentView({fileName, shouldShowDownloadIcon, shouldShowLoadingSpinnerIcon, containerStyles}: DefaultAttachmentViewProps) { +function DefaultAttachmentView({fileName = '', shouldShowLoadingSpinnerIcon = false, shouldShowDownloadIcon, containerStyles}: DefaultAttachmentViewProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index d08f10582c52..5e9a1b2bf4f6 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -176,9 +176,9 @@ function AttachmentView({ onToggleKeyboard={onToggleKeyboard} onLoadComplete={onPDFLoadComplete} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} - isUsedInCarousel={isUsedInCarousel} isUsedAsChatAttachment={isUsedAsChatAttachment} - onError={() => { + containerStyles={containerStyles} + onLoadError={() => { setIsPdfFailedToLoad(true); }} /> @@ -238,7 +238,7 @@ function AttachmentView({ return ( } - ErrorComponent={this.props.renderFallbackAttachmentView} + ErrorComponent={ + + } renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( Date: Sat, 30 Mar 2024 01:57:32 +0530 Subject: [PATCH 023/174] fix the comment for the prop --- src/components/Attachments/AttachmentView/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index 5e9a1b2bf4f6..f32265bdc01f 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -68,7 +68,7 @@ type AttachmentViewProps = AttachmentViewOnyxProps & /* Whether it is hovered or not */ isHovered?: boolean; - /** Duration of the video */ + /** Whether the attachment is used as a chat attachment */ isUsedAsChatAttachment?: boolean; }; From 6436ddbef5f2928851b20ba4448489fbb2c74b8b Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 30 Mar 2024 02:32:19 +0530 Subject: [PATCH 024/174] fixes loading for pdf on web --- src/components/FullscreenLoadingIndicator.tsx | 5 +++-- src/components/PDFView/index.js | 9 ++++++++- src/components/PDFView/pdfViewPropTypes.js | 3 +++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/FullscreenLoadingIndicator.tsx b/src/components/FullscreenLoadingIndicator.tsx index bd3082db5fa4..57c2d7dc955e 100644 --- a/src/components/FullscreenLoadingIndicator.tsx +++ b/src/components/FullscreenLoadingIndicator.tsx @@ -6,13 +6,14 @@ import useThemeStyles from '@hooks/useThemeStyles'; type FullScreenLoadingIndicatorProps = { style?: StyleProp; + isFullScreen?: boolean; }; -function FullScreenLoadingIndicator({style}: FullScreenLoadingIndicatorProps) { +function FullScreenLoadingIndicator({style, isFullScreen = true}: FullScreenLoadingIndicatorProps) { const theme = useTheme(); const styles = useThemeStyles(); return ( - + } + LoadingComponent={} ErrorComponent={ Date: Sat, 30 Mar 2024 02:40:55 +0530 Subject: [PATCH 025/174] fixes proptype warning --- src/components/PDFView/index.js | 30 +++++++++++++++++----- src/components/PDFView/pdfViewPropTypes.js | 3 --- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 64904fc84b3e..d2ee3d448718 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -8,8 +8,8 @@ import DefaultAttachmentView from '@components/Attachments/AttachmentView/Defaul import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import withLocalize from '@components/withLocalize'; +import withStyleUtils, {withStyleUtilsPropTypes} from '@components/withStyleUtils'; import withThemeStyles from '@components/withThemeStyles'; -import withStyleUtils from '@components/withStyleUtils'; import withWindowDimensions from '@components/withWindowDimensions'; import compose from '@libs/compose'; import variables from '@styles/variables'; @@ -17,10 +17,16 @@ import * as CanvasSize from '@userActions/CanvasSize'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import PDFPasswordForm from './PDFPasswordForm'; -import * as pdfViewPropTypes from './pdfViewPropTypes'; +import {propTypes as pdfViewPropTypes, defaultProps as pdfViewDefaultProps} from './pdfViewPropTypes'; const LOADING_THUMBNAIL_HEIGHT = 250; const LOADING_THUMBNAIL_WIDTH = 250; + +const PDFViewPropTypes = { + ...pdfViewPropTypes, + ...withStyleUtilsPropTypes, +}; + class PDFView extends Component { constructor(props) { super(props); @@ -83,8 +89,13 @@ class PDFView extends Component { const styles = this.props.themeStyles; const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; const loadingIndicatorStyles = this.props.isUsedAsChatAttachment - ? [this.props.themeStyles.chatItemPDFAttachmentLoading, this.props.StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT), styles.justifyContentCenter, styles.alignItemsCenter] - : []; + ? [ + this.props.themeStyles.chatItemPDFAttachmentLoading, + this.props.StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT), + styles.justifyContentCenter, + styles.alignItemsCenter, + ] + : []; return ( } + LoadingComponent={ + + } ErrorComponent={ Date: Sat, 30 Mar 2024 02:47:58 +0530 Subject: [PATCH 026/174] prettier diffs --- src/components/PDFView/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index d2ee3d448718..5051e75266cb 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -17,7 +17,7 @@ import * as CanvasSize from '@userActions/CanvasSize'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import PDFPasswordForm from './PDFPasswordForm'; -import {propTypes as pdfViewPropTypes, defaultProps as pdfViewDefaultProps} from './pdfViewPropTypes'; +import {defaultProps as pdfViewDefaultProps, propTypes as pdfViewPropTypes} from './pdfViewPropTypes'; const LOADING_THUMBNAIL_HEIGHT = 250; const LOADING_THUMBNAIL_WIDTH = 250; From 9bd4c96250b25ac591c8904fa9709510dbcfda50 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Tue, 2 Apr 2024 23:32:51 +0530 Subject: [PATCH 027/174] adds onError prop in react-fast-pdf --- package-lock.json | 246 +++++++----------- package.json | 4 +- .../AttachmentViewPdf/index.tsx | 3 +- .../DefaultAttachmentView/index.tsx | 6 + src/components/FullscreenLoadingIndicator.tsx | 5 +- src/components/PDFView/index.js | 48 ++-- 6 files changed, 130 insertions(+), 182 deletions(-) diff --git a/package-lock.json b/package-lock.json index 809b4bd5c4df..7fc027a16e9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,7 +72,7 @@ "react-content-loader": "^7.0.0", "react-dom": "18.1.0", "react-error-boundary": "^4.0.11", - "react-fast-pdf": "^1.0.6", + "react-fast-pdf": "https://github.com/ishpaul777/react-fast-pdf/tree/fix-blankArea-after-loading-pdf", "react-map-gl": "^7.1.3", "react-native": "0.73.2", "react-native-android-location-enabler": "^2.0.1", @@ -3098,8 +3098,10 @@ }, "node_modules/@expensify/react-native-live-markdown": { "version": "0.1.35", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.35.tgz", - "integrity": "sha512-W0FFIiU/sT+AwIrIOUHiNAHYjODAkEdYsf75tfBbkA6v2byHPxUlbzaJrZEQc0HgbvtAfTf9iQQqGWjNqe4pog==", + "license": "MIT", + "workspaces": [ + "example" + ], "engines": { "node": ">= 18.0.0" }, @@ -6402,8 +6404,6 @@ }, "node_modules/@jest/types": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "license": "MIT", "optional": true, @@ -6795,8 +6795,9 @@ "license": "BSD-3-Clause" }, "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.10", - "license": "BSD-3-Clause", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", "optional": true, "dependencies": { "detect-libc": "^2.0.0", @@ -6815,7 +6816,8 @@ }, "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { "version": "3.1.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "optional": true, "dependencies": { "semver": "^6.0.0" @@ -6829,26 +6831,13 @@ }, "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { "version": "6.3.1", - "license": "ISC", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "optional": true, "bin": { "semver": "bin/semver.js" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { - "version": "5.0.0", - "license": "ISC", - "optional": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@mapbox/point-geometry": { "version": "0.1.0", "license": "ISC" @@ -8187,8 +8176,7 @@ }, "node_modules/@react-native-community/cli-doctor/node_modules/ip": { "version": "1.1.9", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==" + "license": "MIT" }, "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { "version": "5.2.0", @@ -8271,8 +8259,7 @@ }, "node_modules/@react-native-community/cli-hermes/node_modules/ip": { "version": "1.1.9", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", - "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==" + "license": "MIT" }, "node_modules/@react-native-community/cli-hermes/node_modules/supports-color": { "version": "7.2.0", @@ -8445,8 +8432,7 @@ }, "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -8460,8 +8446,7 @@ }, "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { "version": "15.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", - "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } @@ -8488,8 +8473,7 @@ }, "node_modules/@react-native-community/cli-server-api/node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -8517,8 +8501,7 @@ }, "node_modules/@react-native-community/cli-server-api/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -8542,8 +8525,7 @@ }, "node_modules/@react-native-community/cli-server-api/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -19039,8 +19021,6 @@ }, "node_modules/@types/yargs": { "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "license": "MIT", "optional": true, @@ -19051,8 +19031,6 @@ }, "node_modules/@types/yargs-parser": { "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "license": "MIT" }, "node_modules/@types/yauzl": { @@ -19991,8 +19969,6 @@ }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "dev": true, "license": "BSD-2-Clause" }, @@ -20007,7 +19983,8 @@ }, "node_modules/abbrev": { "version": "1.1.1", - "license": "ISC", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, "node_modules/abort-controller": { @@ -20934,8 +20911,6 @@ }, "node_modules/async": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", "dev": true, "license": "MIT" }, @@ -22399,9 +22374,8 @@ }, "node_modules/bs-logger": { "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, + "license": "MIT", "dependencies": { "fast-json-stable-stringify": "2.x" }, @@ -22920,8 +22894,9 @@ }, "node_modules/canvas": { "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", "hasInstallScript": true, - "license": "MIT", "optional": true, "dependencies": { "@mapbox/node-pre-gyp": "^1.0.0", @@ -23334,8 +23309,7 @@ }, "node_modules/cliui": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -23661,9 +23635,8 @@ }, "node_modules/concurrently": { "version": "8.2.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", - "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.2", "date-fns": "^2.30.0", @@ -23688,9 +23661,8 @@ }, "node_modules/concurrently/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -23703,9 +23675,8 @@ }, "node_modules/concurrently/node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -23719,9 +23690,8 @@ }, "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -23731,9 +23701,8 @@ }, "node_modules/concurrently/node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -23743,24 +23712,21 @@ }, "node_modules/concurrently/node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/concurrently/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/concurrently/node_modules/supports-color": { "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -25335,8 +25301,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.1", - "license": "Apache-2.0", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "optional": true, "engines": { "node": ">=8" @@ -25671,8 +25638,6 @@ }, "node_modules/duplexer": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true, "license": "MIT" }, @@ -27640,8 +27605,7 @@ }, "node_modules/expo-image": { "version": "1.11.0", - "resolved": "https://registry.npmjs.org/expo-image/-/expo-image-1.11.0.tgz", - "integrity": "sha512-CfkSGWIGidxxqzErke16bCS9aefS04qvgjk+T9Nc34LAb3ysBAqCv5hoCnvylHOvi/7jTCC/ouLm5oLLqkDSRQ==", + "license": "MIT", "dependencies": { "@react-native/assets-registry": "~0.73.1" }, @@ -27651,16 +27615,14 @@ }, "node_modules/expo-image-loader": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-4.6.0.tgz", - "integrity": "sha512-RHQTDak7/KyhWUxikn2yNzXL7i2cs16cMp6gEAgkHOjVhoCJQoOJ0Ljrt4cKQ3IowxgCuOrAgSUzGkqs7omj8Q==", + "license": "MIT", "peerDependencies": { "expo": "*" } }, "node_modules/expo-image-manipulator": { "version": "11.8.0", - "resolved": "https://registry.npmjs.org/expo-image-manipulator/-/expo-image-manipulator-11.8.0.tgz", - "integrity": "sha512-ZWVrHnYmwJq6h7auk+ropsxcNi+LyZcPFKQc8oy+JA0SaJosfShvkCm7RADWAunHmfPCmjHrhwPGEu/rs7WG/A==", + "license": "MIT", "dependencies": { "expo-image-loader": "~4.6.0" }, @@ -29657,8 +29619,6 @@ }, "node_modules/html-entities": { "version": "2.5.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", - "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", "funding": [ { "type": "github", @@ -29668,7 +29628,8 @@ "type": "patreon", "url": "https://patreon.com/mdevils" } - ] + ], + "license": "MIT" }, "node_modules/html-escaper": { "version": "2.0.2", @@ -30573,9 +30534,8 @@ }, "node_modules/ip": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", - "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ip-regex": { "version": "2.1.0", @@ -34355,8 +34315,6 @@ }, "node_modules/klaw-sync": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", - "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", "dev": true, "license": "MIT", "dependencies": { @@ -34676,9 +34634,8 @@ }, "node_modules/lodash.memoize": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -36710,6 +36667,21 @@ "url": "https://github.com/sponsors/antelle" } }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "license": "BSD-2-Clause", @@ -37624,8 +37596,6 @@ }, "node_modules/patch-package": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", - "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", "dev": true, "license": "MIT", "dependencies": { @@ -37655,8 +37625,6 @@ }, "node_modules/patch-package/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -37671,8 +37639,6 @@ }, "node_modules/patch-package/node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -37688,8 +37654,6 @@ }, "node_modules/patch-package/node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -37701,15 +37665,11 @@ }, "node_modules/patch-package/node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, "node_modules/patch-package/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -37718,8 +37678,6 @@ }, "node_modules/patch-package/node_modules/open": { "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "dev": true, "license": "MIT", "dependencies": { @@ -37735,8 +37693,6 @@ }, "node_modules/patch-package/node_modules/rimraf": { "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "license": "ISC", "dependencies": { @@ -37748,8 +37704,6 @@ }, "node_modules/patch-package/node_modules/slash": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true, "license": "MIT", "engines": { @@ -37758,8 +37712,6 @@ }, "node_modules/patch-package/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -37771,8 +37723,6 @@ }, "node_modules/patch-package/node_modules/tmp": { "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "license": "MIT", "dependencies": { @@ -38956,8 +38906,7 @@ }, "node_modules/react-content-loader": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/react-content-loader/-/react-content-loader-7.0.0.tgz", - "integrity": "sha512-xaBwpO7eiJyEc4ndym+g6wcruV9W2y3DKqbw4U48QFBsv0IeAVZO+aCUb8GptlDLWM8n5zi2HcFSGlj5r+53Tg==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -39051,8 +39000,8 @@ }, "node_modules/react-fast-pdf": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/react-fast-pdf/-/react-fast-pdf-1.0.6.tgz", - "integrity": "sha512-CdAnBSZaLCGLSEuiqWLzzXhV9Wvdf1VRixaXCrb3NFrXyeltahF7PY+u7eU6ynrWZGmNI6g0cMLPv0DQhJEeew==", + "resolved": "git+ssh://git@github.com/ishpaul777/react-fast-pdf.git#0a8a073ad5f0d376c1827a2dacfafd197500bf3b", + "license": "MIT", "dependencies": { "react-pdf": "^7.7.0", "react-window": "^1.8.10" @@ -39109,14 +39058,6 @@ } } }, - "node_modules/react-fast-pdf/node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/react-freeze": { "version": "1.0.3", "license": "MIT", @@ -39407,6 +39348,7 @@ "node_modules/react-native-image-size": { "version": "1.1.3", "resolved": "git+ssh://git@github.com/Expensify/react-native-image-size.git#bf3ad41a61c4f6f80ed4d497599ef5247a2dd002", + "integrity": "sha512-uIZLaqqjSivO+iBGx3VpZRSn/cYy18ct6S1H35gK8n74eJFb/Ds6qUJ+jGw5PUt0KEzw+aXLgPq6gHDXT5Q29A==", "license": "MIT" }, "node_modules/react-native-key-command": { @@ -39836,8 +39778,7 @@ }, "node_modules/react-native/node_modules/@jest/types": { "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -39851,8 +39792,7 @@ }, "node_modules/react-native/node_modules/@types/yargs": { "version": "15.0.19", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", - "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } @@ -39879,8 +39819,7 @@ }, "node_modules/react-native/node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -39920,8 +39859,7 @@ }, "node_modules/react-native/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -39976,8 +39914,7 @@ }, "node_modules/react-native/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -40593,8 +40530,7 @@ }, "node_modules/react-window": { "version": "1.8.10", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz", - "integrity": "sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", "memoize-one": ">=3.1.1 <6" @@ -41551,9 +41487,8 @@ }, "node_modules/rxjs": { "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -42048,6 +41983,8 @@ }, "node_modules/simple-concat": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", "funding": [ { "type": "github", @@ -42062,12 +41999,12 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "optional": true }, "node_modules/simple-get": { "version": "3.1.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", "optional": true, "dependencies": { "decompress-response": "^4.2.0", @@ -42077,7 +42014,8 @@ }, "node_modules/simple-get/node_modules/decompress-response": { "version": "4.2.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", "optional": true, "dependencies": { "mimic-response": "^2.0.0" @@ -42088,7 +42026,8 @@ }, "node_modules/simple-get/node_modules/mimic-response": { "version": "2.1.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", "optional": true, "engines": { "node": ">=8" @@ -42537,8 +42476,6 @@ }, "node_modules/spawn-command": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", - "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", "dev": true }, "node_modules/spdx-correct": { @@ -44234,9 +44171,8 @@ }, "node_modules/ts-jest": { "version": "29.1.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", - "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", "dev": true, + "license": "MIT", "dependencies": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", @@ -44277,9 +44213,8 @@ }, "node_modules/ts-jest/node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } @@ -45340,6 +45275,14 @@ "version": "0.1.1", "license": "MIT" }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "license": "MIT", @@ -46625,8 +46568,7 @@ }, "node_modules/yargs": { "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -46650,16 +46592,14 @@ }, "node_modules/yargs/node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yargs/node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", "engines": { "node": ">=12" } diff --git a/package.json b/package.json index 9f1fbc894e1f..3b770debe4f0 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "react-content-loader": "^7.0.0", "react-dom": "18.1.0", "react-error-boundary": "^4.0.11", - "react-fast-pdf": "^1.0.6", + "react-fast-pdf": "https://github.com/ishpaul777/react-fast-pdf/tree/fix-blankArea-after-loading-pdf", "react-map-gl": "^7.1.3", "react-native": "0.73.2", "react-native-android-location-enabler": "^2.0.1", @@ -248,8 +248,8 @@ "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-remove-console": "^6.9.4", "clean-webpack-plugin": "^4.0.0", - "copy-webpack-plugin": "^10.1.0", "concurrently": "^8.2.2", + "copy-webpack-plugin": "^10.1.0", "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index c1ded9782a7a..500d14082be7 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, style, isUsedAsChatAttachment, containerStyles}: AttachmentViewPdfProps) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, isUsedAsChatAttachment, containerStyles, onLoadError}: AttachmentViewPdfProps) { return ( ); } diff --git a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx index 23d39ec096b5..6130aeed866c 100644 --- a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx @@ -10,9 +10,13 @@ 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; }; @@ -55,4 +59,6 @@ function DefaultAttachmentView({fileName = '', shouldShowLoadingSpinnerIcon = fa ); } +DefaultAttachmentView.displayName = 'DefaultAttachmentView'; + export default DefaultAttachmentView; diff --git a/src/components/FullscreenLoadingIndicator.tsx b/src/components/FullscreenLoadingIndicator.tsx index 57c2d7dc955e..bd3082db5fa4 100644 --- a/src/components/FullscreenLoadingIndicator.tsx +++ b/src/components/FullscreenLoadingIndicator.tsx @@ -6,14 +6,13 @@ import useThemeStyles from '@hooks/useThemeStyles'; type FullScreenLoadingIndicatorProps = { style?: StyleProp; - isFullScreen?: boolean; }; -function FullScreenLoadingIndicator({style, isFullScreen = true}: FullScreenLoadingIndicatorProps) { +function FullScreenLoadingIndicator({style}: FullScreenLoadingIndicatorProps) { const theme = useTheme(); const styles = useThemeStyles(); return ( - + + + + ); + } + + return ; + } + renderPDFView() { const styles = this.props.themeStyles; const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; - const loadingIndicatorStyles = this.props.isUsedAsChatAttachment - ? [ - this.props.themeStyles.chatItemPDFAttachmentLoading, - this.props.StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT), - styles.justifyContentCenter, - styles.alignItemsCenter, - ] - : []; return ( - } - ErrorComponent={ - - } + LoadingComponent={this.renderLoadingIndicator()} + shouldShowErrorComponent={false} + onLoadError={this.props.onLoadError} renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( Date: Sun, 7 Apr 2024 01:51:43 +0530 Subject: [PATCH 028/174] resolve conflicts --- src/components/PDFView/index.tsx | 48 +++++++++++++++++++++++--------- src/components/PDFView/types.ts | 9 ++++-- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx index ff28a8f88849..df6543290b96 100644 --- a/src/components/PDFView/index.tsx +++ b/src/components/PDFView/index.tsx @@ -1,30 +1,36 @@ import 'core-js/features/array/at'; // eslint-disable-next-line no-restricted-imports -import type {CSSProperties} from 'react'; -import React, {memo, useCallback, useEffect, useState} from 'react'; -import {PDFPreviewer} from 'react-fast-pdf'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import type { CSSProperties } from 'react'; +import React, { memo, useCallback, useEffect, useState } from 'react'; +import { PDFPreviewer } from 'react-fast-pdf'; +import { ActivityIndicator, 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 useThemeStyles from '@hooks/useThemeStyles'; +import useTheme from '@hooks/useTheme'; +import useStyleUtils from '@hooks/useStyleUtils'; import useWindowDimensions from '@hooks/useWindowDimensions'; import variables from '@styles/variables'; import * as CanvasSize from '@userActions/CanvasSize'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import PDFPasswordForm from './PDFPasswordForm'; -import type {PDFViewOnyxProps, PDFViewProps} from './types'; +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 {windowHeight, isSmallScreenWidth} = useWindowDimensions(); + const StyleUtils = useStyleUtils(); + const theme = useTheme(); + const { windowHeight, isSmallScreenWidth } = useWindowDimensions(); const prevWindowHeight = usePrevious(windowHeight); - const {translate} = useLocalize(); + const { translate } = useLocalize(); /** * On small screens notify parent that the keyboard has opened or closed. @@ -79,6 +85,21 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, err } }, [isKeyboardOpen, prevWindowHeight, toggleKeyboardOnSmallScreens, windowHeight]); + const renderLoadingIndicator = () => { + if (isUsedAsChatAttachment) { + return ( + + + + ); + } + + return ; + } + const renderPDFView = () => { const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; @@ -95,9 +116,10 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, err maxCanvasWidth={maxCanvasWidth} maxCanvasHeight={maxCanvasHeight} maxCanvasArea={maxCanvasArea} - LoadingComponent={} - ErrorComponent={{translate('attachmentView.failedToLoadPDF')}} - renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( + LoadingComponent={renderLoadingIndicator()} + 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 = { From 15eea966597d1465650cfe61dd3dec2face0d9f5 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 7 Apr 2024 01:52:50 +0530 Subject: [PATCH 029/174] code formatting --- .../AttachmentViewPdf/index.tsx | 13 ++++++- src/components/PDFView/index.native.tsx | 5 +-- src/components/PDFView/index.tsx | 35 +++++++++++-------- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index 9984779557eb..ba108092ab8f 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -2,7 +2,18 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import type AttachmentViewPdfProps from './types'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, isUsedAsChatAttachment, containerStyles, onLoadError}: AttachmentViewPdfProps) { +function AttachmentViewPdf({ + file, + encryptedSourceUrl, + isFocused, + onPress, + onToggleKeyboard, + onLoadComplete, + style, + isUsedAsChatAttachment, + containerStyles, + onLoadError, +}: AttachmentViewPdfProps) { return ( { if (isUsedAsChatAttachment) { return ( - + ; - } + }; const renderPDFView = () => { const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; @@ -119,7 +126,7 @@ function PDFView({ onToggleKeyboard, fileName, onPress, isFocused, sourceURL, ma LoadingComponent={renderLoadingIndicator()} shouldShowErrorComponent={false} onLoadError={onLoadError} - renderPasswordForm={({ isPasswordInvalid, onSubmit, onPasswordChange }) => ( + renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( Date: Sun, 7 Apr 2024 02:16:30 +0530 Subject: [PATCH 030/174] remove unnecessary change --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 79f0a58cc10d..5e00815f1e3e 100644 --- a/package.json +++ b/package.json @@ -248,8 +248,8 @@ "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-remove-console": "^6.9.4", "clean-webpack-plugin": "^4.0.0", - "concurrently": "^8.2.2", "copy-webpack-plugin": "^10.1.0", + "concurrently": "^8.2.2", "css-loader": "^6.7.2", "diff-so-fancy": "^1.3.0", "dotenv": "^16.0.3", From 69129794b08fe9d66175d41bb0c35d9083965562 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 7 Apr 2024 02:21:23 +0530 Subject: [PATCH 031/174] fix type checks --- .../Attachments/AttachmentView/AttachmentViewPdf/index.tsx | 2 -- .../Attachments/AttachmentView/AttachmentViewPdf/types.ts | 3 --- src/components/Attachments/AttachmentView/index.tsx | 1 - 3 files changed, 6 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index ba108092ab8f..3cae1ee45f1b 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -11,7 +11,6 @@ function AttachmentViewPdf({ onLoadComplete, style, isUsedAsChatAttachment, - containerStyles, onLoadError, }: AttachmentViewPdfProps) { return ( @@ -24,7 +23,6 @@ function AttachmentViewPdf({ onToggleKeyboard={onToggleKeyboard} onLoadComplete={onLoadComplete} isUsedAsChatAttachment={isUsedAsChatAttachment} - containerStyles={containerStyles} onLoadError={onLoadError} /> ); diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts index 1c3cd8adfba4..c20593449d58 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts @@ -16,9 +16,6 @@ type AttachmentViewPdfProps = Pick; }; export default AttachmentViewPdfProps; diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index f32265bdc01f..bc8f7476b4d5 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -177,7 +177,6 @@ function AttachmentView({ onLoadComplete={onPDFLoadComplete} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedAsChatAttachment={isUsedAsChatAttachment} - containerStyles={containerStyles} onLoadError={() => { setIsPdfFailedToLoad(true); }} From 917095c5f6f29feb6c5b9d1bc29d56acd2cc9c06 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 7 Apr 2024 02:23:07 +0530 Subject: [PATCH 032/174] lint code --- src/components/Attachments/AttachmentView/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index bc8f7476b4d5..1743a1904ad3 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -163,6 +163,10 @@ function AttachmentView({ } }; + const onPDFLoadError = () => { + setIsPdfFailedToLoad(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. @@ -177,9 +181,7 @@ function AttachmentView({ onLoadComplete={onPDFLoadComplete} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedAsChatAttachment={isUsedAsChatAttachment} - onLoadError={() => { - setIsPdfFailedToLoad(true); - }} + onLoadError={onPDFLoadError} /> ); From c9681471de4445131308598d7c644f8ffd9c7b1d Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 7 Apr 2024 02:24:22 +0530 Subject: [PATCH 033/174] prettier diffs --- .../AttachmentView/AttachmentViewPdf/index.tsx | 12 +----------- src/components/Attachments/AttachmentView/index.tsx | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index 3cae1ee45f1b..e8f6568f98c0 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -2,17 +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, - style, - isUsedAsChatAttachment, - onLoadError, -}: AttachmentViewPdfProps) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, isUsedAsChatAttachment, onLoadError}: AttachmentViewPdfProps) { return ( { setIsPdfFailedToLoad(true); - } + }; // We need the following View component on android native // So that the event will propagate properly and From cadf1f1dee2058299d5ed4e792b75ebb7fbaac6d Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 7 Apr 2024 02:32:19 +0530 Subject: [PATCH 034/174] resolve lint failure --- src/components/PDFView/index.native.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PDFView/index.native.tsx b/src/components/PDFView/index.native.tsx index 0fb063f57e8b..db76c005b37a 100644 --- a/src/components/PDFView/index.native.tsx +++ b/src/components/PDFView/index.native.tsx @@ -127,9 +127,9 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused if (shouldRequestPassword) { pdfStyles.push(themeStyles.invisible); } - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const containerStyles = - isUsedAsChatAttachment || (shouldRequestPassword && isSmallScreenWidth) ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; + // 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)] : []; From 54eb29517e53a484d21a7a9a5a37d2bfdf91f05c Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 7 Apr 2024 02:39:32 +0530 Subject: [PATCH 035/174] prettier diffs --- src/components/PDFView/index.native.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PDFView/index.native.tsx b/src/components/PDFView/index.native.tsx index db76c005b37a..7d523ef13805 100644 --- a/src/components/PDFView/index.native.tsx +++ b/src/components/PDFView/index.native.tsx @@ -129,7 +129,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused } const containerStyles = // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - (isUsedAsChatAttachment || (shouldRequestPassword && isSmallScreenWidth)) ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; + isUsedAsChatAttachment || (shouldRequestPassword && isSmallScreenWidth) ? [themeStyles.w100, themeStyles.flex1] : [themeStyles.alignItemsCenter, themeStyles.flex1]; const loadingIndicatorStyles = isUsedAsChatAttachment ? [themeStyles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT)] : []; From 9c1527be8952d96dbc1d758b08717b346833eb8d Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 11 Apr 2024 22:34:06 +0530 Subject: [PATCH 036/174] remove unnecessary styles --- src/components/PDFView/index.tsx | 2 -- src/styles/index.ts | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx index 2725e3c2a6aa..e63a4b5ac4c1 100644 --- a/src/components/PDFView/index.tsx +++ b/src/components/PDFView/index.tsx @@ -92,8 +92,6 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, max style={[ styles.chatItemPDFAttachmentLoading, StyleUtils.getWidthAndHeightStyle(LOADING_THUMBNAIL_WIDTH, LOADING_THUMBNAIL_HEIGHT), - styles.alignItemsCenter, - styles.justifyContentCenter, ]} > borderColor: theme.border, borderWidth: 1, borderRadius: variables.componentBorderRadiusNormal, - textAlign: 'center', - verticalAlign: 'middle', - opacity: 1, + ...flex.alignItemsCenter, + ...flex.justifyContentCenter, }, sidebarVisible: { From 68d480ddc07f37d0644c9bfe4d42f62abf195093 Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 17 Apr 2024 14:31:32 +0200 Subject: [PATCH 037/174] Display rter violation in MoneyRequestHeader & ReportPreview --- src/components/MoneyRequestHeader.tsx | 66 ++++++++++++------- .../ReportActionItem/ReportPreview.tsx | 21 +++++- src/languages/en.ts | 2 + src/languages/es.ts | 2 + src/libs/TransactionUtils.ts | 24 ++++++- src/types/onyx/TransactionViolation.ts | 1 + 6 files changed, 88 insertions(+), 28 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 2f80cf3c6e59..a7511b44f049 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -1,7 +1,7 @@ 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,7 +16,7 @@ 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 ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; @@ -37,6 +37,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; @@ -56,7 +59,7 @@ type MoneyRequestHeaderProps = MoneyRequestHeaderOnyxProps & { parentReportAction: OnyxEntry; }; -function MoneyRequestHeader({session, parentReport, report, parentReportAction, transaction, shownHoldUseExplanation = false, policy}: MoneyRequestHeaderProps) { +function MoneyRequestHeader({session, parentReport, report, parentReportAction, transactionViolations, transaction, shownHoldUseExplanation = false, policy}: MoneyRequestHeaderProps) { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); @@ -86,9 +89,6 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, setIsDeleteModalVisible(false); }, [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; @@ -106,6 +106,21 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, } }; + const getPendingType = () => { + if (TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction)) { + return {pendingType: 'PENDING', pendingTitle: translate('iou.pending'), pendingDescription: translate('iou.transactionPendingText')}; + } + if (TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction)) { + return {pendingType: 'SCANNING', pendingTitle: ReceiptScan, pendingDescription: translate('iou.receiptScanInProgressDescription')}; + } + if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) { + return {pendingType: 'RTER', pendingTitle: Expensicons.Hourglass, pendingDescription: translate('iou.pendingMatchWithCreditCardDescription')}; + } + return {}; + }; + + const {pendingType, pendingTitle, pendingDescription} = getPendingType(); + useEffect(() => { if (canDeleteRequest) { return; @@ -170,7 +185,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, <> Navigation.goBack(undefined, false, true)} /> - {isPending && ( - - )} - {isScanning && ( + {pendingType && ( + typeof pendingTitle === 'string' ? ( + pendingTitle + ) : ( + + ) } - description={translate('iou.receiptScanInProgressDescription')} + description={pendingDescription} shouldShowBorderBottom /> )} @@ -230,7 +242,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, MoneyRequestHeader.displayName = 'MoneyRequestHeader'; -const MoneyRequestHeaderWithTransaction = withOnyx>({ +const MoneyRequestHeaderWithTransaction = withOnyx>({ transaction: { key: ({report, parentReportActions}) => { const parentReportAction = (report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : {}) as ReportAction & OriginalMessageIOU; @@ -241,9 +253,15 @@ const MoneyRequestHeaderWithTransaction = withOnyx, Omit>({ +export default withOnyx< + Omit, + Omit +>({ session: { key: ONYXKEYS.SESSION, }, diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 190343e48abd..98f4cae7dee4 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -26,6 +26,7 @@ import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; +import variables from '@styles/variables'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; @@ -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 = + allTransactions.length === 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 @@ -235,6 +238,9 @@ function ReportPreview({ const parsedSubtitle = new ExpensiMark().replace(formattedDescription ?? moneyRequestComment); return {isSupportTextHtml: !!parsedSubtitle, supportText: parsedSubtitle ? `${parsedSubtitle}` : ''}; } + // if (true) { + // return {isSupportTextHtml: false, supportText: 'ABC'}; + // } return { isSupportTextHtml: false, supportText: translate('iou.requestCount', { @@ -318,6 +324,19 @@ function ReportPreview({ )} + {showRTERViolationMessage && ( + + + + {translate('iou.pendingMatchWithCreditCard')} + + + )} {shouldShowSettlementButton && ( diff --git a/src/languages/en.ts b/src/languages/en.ts index 314f4f990a49..5667b3fc83e7 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -633,6 +633,8 @@ export default { canceled: 'Canceled', posted: 'Posted', deleteReceipt: 'Delete receipt', + pendingMatchWithCreditCard: 'Receipt pending match with credit card.', + pendingMatchWithCreditCardDescription: 'Receipt pending match with credit card. Marks as cash to ignore and request payment.', routePending: 'Route pending...', receiptScanning: 'Receipt scanning…', receiptScanInProgress: 'Receipt scan in progress.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 387f2b4c1841..4366d61425aa 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -629,6 +629,8 @@ export default { canceled: 'Canceló', posted: 'Contabilizado', deleteReceipt: 'Eliminar recibo', + pendingMatchWithCreditCard: 'Recibo pendiente de conciliar con la tarjeta de crédito.', + pendingMatchWithCreditCardDescription: 'Recibo pendiente de conciliar con tarjeta de crédito. Marcar como efectivo para ignorar y solicitar pago.', routePending: 'Ruta pendiente...', receiptScanning: 'Escaneo en curso…', receiptScanInProgress: 'Escaneo en curso…', diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 89b89ff7b584..a54fa66191a4 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -5,7 +5,7 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {RecentWaypoint, Report, TaxRate, TaxRates, TaxRatesWithDefault, Transaction, TransactionViolation} from '@src/types/onyx'; +import type {RecentWaypoint, Report, TaxRate, TaxRates, TaxRatesWithDefault, 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'; @@ -514,6 +514,22 @@ function hasMissingSmartscanFields(transaction: OnyxEntry): boolean return Boolean(transaction && !isDistanceRequest(transaction) && !isReceiptBeingScanned(transaction) && areRequiredFieldsEmpty(transaction)); } +/** + * 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 the transaction is pending or has a pending rter violation. + */ +function hasPendingUI(transaction: OnyxEntry, transactionViolations?: TransactionViolations | null): boolean { + return Boolean(isReceiptBeingScanned(transaction) || isPending(transaction) || (transaction && hasPendingRTERViolation(transactionViolations))); +} + /** * Check if the transaction has a defined route */ @@ -605,13 +621,13 @@ function isOnHold(transaction: OnyxEntry): 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), ); } -function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection): TransactionViolation[] | null { +function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection | null): TransactionViolations | null { return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null; } @@ -698,6 +714,8 @@ export { isCreatedMissing, areRequiredFieldsEmpty, hasMissingSmartscanFields, + hasPendingRTERViolation, + hasPendingUI, getWaypointIndex, waypointHasValidAddress, getRecentTransactions, 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; }; }; From 3409d9cfcfb71aefbfa985ac0834f7b4115ba88b Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 17 Apr 2024 16:36:57 +0200 Subject: [PATCH 038/174] Display rter violations in MoneyReportHeader --- src/components/MoneyReportHeader.tsx | 29 +++++++++++++++++++++++++--- src/libs/TransactionUtils.ts | 24 ++++++++++++++++++----- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 14227d6a2051..b68cbd464137 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -3,6 +3,7 @@ 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'; @@ -10,6 +11,8 @@ import * as HeaderUtils from '@libs/HeaderUtils'; import Navigation from '@libs/Navigation/Navigation'; 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'; @@ -20,8 +23,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'; @@ -56,6 +61,7 @@ type MoneyReportHeaderProps = MoneyReportHeaderOnyxProps & { function MoneyReportHeader({session, policy, chatReport, nextStep, report: moneyRequestReport, transactionThreadReport, reportActions}: MoneyReportHeaderProps) { const styles = useThemeStyles(); + const theme = useTheme(); const [isDeleteRequestModalVisible, setIsDeleteRequestModalVisible] = useState(false); const {translate} = useLocalize(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); @@ -83,6 +89,9 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport); const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); + const transactionIDs = TransactionUtils.getAllReportTransactions(chatReport?.reportID).map((transaction) => transaction.transactionID); + const haveAllPendingRTERViolation = TransactionUtils.haveAllPendingRTERViolation(transactionIDs); + const cancelPayment = useCallback(() => { if (!chatReport) { return; @@ -97,9 +106,9 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(moneyRequestReport); - const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton; + const shouldShowSettlementButton = (shouldShowPayButton || shouldShowApproveButton) && !haveAllPendingRTERViolation; - const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0; + const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !haveAllPendingRTERViolation; 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; @@ -188,7 +197,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money shouldShowBackButton={isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(undefined, false, true)} // Shows border if no buttons or next steps are showing below the header - shouldShowBorderBottom={!(shouldShowAnyButton && isSmallScreenWidth) && !(shouldShowNextStep && !isSmallScreenWidth)} + shouldShowBorderBottom={!(shouldShowAnyButton && isSmallScreenWidth) && !(shouldShowNextStep && !isSmallScreenWidth) && !haveAllPendingRTERViolation} shouldShowThreeDotsButton threeDotsMenuItems={threeDotsMenuItems} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)} @@ -226,6 +235,20 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money )} + {haveAllPendingRTERViolation && ( + + } + description={translate('iou.pendingMatchWithCreditCardDescription')} + shouldShowBorderBottom + /> + )} {shouldShowSettlementButton && isSmallScreenWidth && ( diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index a54fa66191a4..df21a992a047 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -16,7 +16,6 @@ import * as NumberUtils from './NumberUtils'; import {getCleanedTagName} from './PolicyUtils'; let allTransactions: OnyxCollection = {}; - Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, @@ -28,6 +27,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, @@ -514,6 +520,10 @@ function hasMissingSmartscanFields(transaction: OnyxEntry): boolean return Boolean(transaction && !isDistanceRequest(transaction) && !isReceiptBeingScanned(transaction) && areRequiredFieldsEmpty(transaction)); } +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. */ @@ -523,6 +533,13 @@ function hasPendingRTERViolation(transactionViolations?: TransactionViolations | ); } +/** + * Check if there is pending rter violation in transactionViolations. + */ +function haveAllPendingRTERViolation(transactionIds: string[]): boolean { + return transactionIds.map((transactionId) => hasPendingRTERViolation(getTransactionViolations(transactionId, allTransactionViolations))).every((value) => value); +} + /** * Check if the transaction is pending or has a pending rter violation. */ @@ -627,10 +644,6 @@ function hasViolation(transactionID: string, transactionViolations: OnyxCollecti ); } -function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection | null): TransactionViolations | null { - return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null; -} - /** * this is the formulae to calculate tax */ @@ -715,6 +728,7 @@ export { areRequiredFieldsEmpty, hasMissingSmartscanFields, hasPendingRTERViolation, + haveAllPendingRTERViolation, hasPendingUI, getWaypointIndex, waypointHasValidAddress, From 5275089ec8167f03667de8e53ecef56532a492ee Mon Sep 17 00:00:00 2001 From: smelaa Date: Thu, 18 Apr 2024 14:33:15 +0200 Subject: [PATCH 039/174] Fix util function --- src/libs/TransactionUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 44ccd387e670..3f374d46a6de 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -535,7 +535,8 @@ function hasPendingRTERViolation(transactionViolations?: TransactionViolations | * Check if there is pending rter violation in transactionViolations. */ function haveAllPendingRTERViolation(transactionIds: string[]): boolean { - return transactionIds.map((transactionId) => hasPendingRTERViolation(getTransactionViolations(transactionId, allTransactionViolations))).every((value) => value); + const transactionsWithRTERViolations = transactionIds.map((transactionId) => hasPendingRTERViolation(getTransactionViolations(transactionId, allTransactionViolations))); + return transactionsWithRTERViolations.length !== 0 && transactionsWithRTERViolations.every((value) => value === true); } /** From 046dbc2d40d1c88603b2f6eb2c408b42e0a0b283 Mon Sep 17 00:00:00 2001 From: smelaa Date: Thu, 18 Apr 2024 16:14:20 +0200 Subject: [PATCH 040/174] Add rter violation information to MoneyRequestPreviewContent header --- .../MoneyRequestPreviewContent.tsx | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 9a7549b9aac6..a3a8bfd131c3 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -4,6 +4,7 @@ import lodashSortBy from 'lodash/sortBy'; import React from 'react'; import {View} from 'react-native'; import type {GestureResponderEvent} from 'react-native'; +import type {SvgProps} from 'react-native-svg'; import ConfirmedRoute from '@components/ConfirmedRoute'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -134,7 +135,11 @@ function MoneyRequestPreviewContent({ showContextMenuForReport(event, contextMenuAnchor, reportID, action, checkIfContextMenuActive); }; - const getPreviewHeaderText = (): string => { + const getPreviewHeaderTextAndIcon: () => {headerMessage: string; headerIcon?: React.FC} = () => { + if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) { + return {headerMessage: translate('iou.pendingMatchWithCreditCard'), headerIcon: Expensicons.Hourglass}; + } + let message = translate('iou.cash'); if (isDistanceRequest) { @@ -149,13 +154,13 @@ function MoneyRequestPreviewContent({ message = translate('iou.card'); if (TransactionUtils.isPending(transaction)) { message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.pending')}`; - return message; + return {headerMessage: message}; } } if (isSettled && !iouReport?.isCancelledIOU && !isPartialHold) { message += ` ${CONST.DOT_SEPARATOR} ${getSettledMessage()}`; - return message; + return {headerMessage: message}; } if (shouldShowRBR && transaction) { @@ -166,7 +171,8 @@ function MoneyRequestPreviewContent({ const isTooLong = violationsCount > 1 || violationMessage.length > 15; const hasViolationsAndFieldErrors = violationsCount > 0 && hasFieldErrors; - return `${message} ${CONST.DOT_SEPARATOR} ${isTooLong || hasViolationsAndFieldErrors ? translate('violations.reviewRequired') : violationMessage}`; + message = `${message} ${CONST.DOT_SEPARATOR} ${isTooLong || hasViolationsAndFieldErrors ? translate('violations.reviewRequired') : violationMessage}`; + return {headerMessage: message, headerIcon: undefined}; } const isMerchantMissing = TransactionUtils.isMerchantMissing(transaction); @@ -189,9 +195,11 @@ function MoneyRequestPreviewContent({ } else if (!(isSettled && !isSettlementOrApprovalPartial) && isOnHold) { message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.hold')}`; } - return message; + return {headerMessage: message}; }; + const {headerMessage, headerIcon} = getPreviewHeaderTextAndIcon(); + const getDisplayAmountText = (): string => { if (isScanning) { return translate('iou.receiptScanning'); @@ -249,7 +257,15 @@ function MoneyRequestPreviewContent({ - {getPreviewHeaderText()} + {headerIcon && ( + + )} + {headerMessage} {!isSettled && shouldShowRBR && ( Date: Sat, 20 Apr 2024 15:23:25 +0200 Subject: [PATCH 041/174] Check and remove unnecessary @ts-expect-error suppressions --- __mocks__/@react-navigation/native/index.ts | 3 +- src/Expensify.tsx | 2 +- .../BaseAnchorForAttachmentsOnly.tsx | 3 +- src/hooks/useAutoFocusInput.ts | 1 - src/libs/ObjectUtils.ts | 5 +- src/libs/migrations/NVPMigration.ts | 20 +- .../EnablePayments/AdditionalDetailsStep.tsx | 3 +- .../DateOfBirthUBO.tsx | 3 +- .../substeps/IncorporationDateBusiness.tsx | 3 +- .../PersonalInfo/substeps/DateOfBirth.tsx | 3 +- .../ReportActionCompose.tsx | 10 +- src/pages/home/report/ReportFooter.tsx | 7 +- .../PersonalDetails/StateSelectionPage.tsx | 5 +- tests/actions/IOUTest.ts | 1884 ++++++++--------- tests/actions/PolicyTest.ts | 8 +- tests/actions/ReportTest.ts | 6 - tests/perf-test/ReportScreen.perf-test.tsx | 9 +- tests/perf-test/SearchPage.perf-test.tsx | 11 +- tests/perf-test/SignInPage.perf-test.tsx | 2 +- tests/ui/UnreadIndicatorsTest.tsx | 1 - tests/unit/APITest.ts | 83 +- tests/unit/EmojiTest.ts | 1 - tests/unit/MiddlewareTest.ts | 1 - tests/unit/NetworkTest.ts | 1 - tests/unit/ReportActionsUtilsTest.ts | 4 +- tests/unit/RequestTest.ts | 1 - tests/unit/awaitStagingDeploysTest.ts | 1 - tests/utils/TestHelper.ts | 3 +- 28 files changed, 1001 insertions(+), 1083 deletions(-) diff --git a/__mocks__/@react-navigation/native/index.ts b/__mocks__/@react-navigation/native/index.ts index 9a6680ba0b6e..86772991ddac 100644 --- a/__mocks__/@react-navigation/native/index.ts +++ b/__mocks__/@react-navigation/native/index.ts @@ -3,8 +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'; diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 6a57d6fdcc10..4920205d731d 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -74,7 +74,7 @@ type ExpensifyOnyxProps = { type ExpensifyProps = ExpensifyOnyxProps; -const SplashScreenHiddenContext = React.createContext({}); +const SplashScreenHiddenContext = React.createContext<{isSplashHidden?: boolean}>({}); function Expensify({ isCheckingPublicRoom = true, diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx index da8e3694a7d2..3ce6ef4b36a9 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} 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/libs/ObjectUtils.ts b/src/libs/ObjectUtils.ts index 9ffa461506c8..fd4c0956e877 100644 --- a/src/libs/ObjectUtils.ts +++ b/src/libs/ObjectUtils.ts @@ -4,8 +4,9 @@ const shallowCompare = (obj1?: object, obj2?: object) => { 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) as Array; + const keys2 = Object.keys(obj2) as Array; + return keys1.length === keys2.length && keys1.every((key) => obj1[key] === obj2[key]); } return false; }; 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 46e7191f222b..8dd15915df2e 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. */} - + {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. */} - + void; focus: (shouldDelay?: boolean) => void; - replaceSelectionWithText: (text: string, shouldAddTrailSpace: boolean) => void; + replaceSelectionWithText: (text: string, shouldAddTrailSpace: Emoji) => void; prepareCommentAndResetComposer: () => string; isFocused: () => boolean; }; @@ -73,7 +73,7 @@ type ReportActionComposeOnyxProps = { type ReportActionComposeProps = ReportActionComposeOnyxProps & WithCurrentUserPersonalDetailsProps & - Pick & { + Pick & { /** A method to call when the form is submitted */ onSubmit: (newComment: string | undefined) => void; @@ -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, @@ -479,7 +482,6 @@ function ReportActionCompose({ composerRef.current?.replaceSelectionWithText(...args)} emojiPickerID={report?.reportID} shiftVertical={emojiShiftVertical} diff --git a/src/pages/home/report/ReportFooter.tsx b/src/pages/home/report/ReportFooter.tsx index 04fbd0308390..9f9b45c18b96 100644 --- a/src/pages/home/report/ReportFooter.tsx +++ b/src/pages/home/report/ReportFooter.tsx @@ -117,7 +117,11 @@ function ReportFooter({ ); const onSubmitComment = useCallback( - (text: string) => { + (text: string | undefined) => { + if (text === undefined) { + return; + } + const isTaskCreated = handleCreateTask(text); if (isTaskCreated) { return; @@ -146,7 +150,6 @@ function ReportFooter({ { const backTo = params?.backTo ?? ''; - let backToRoute = ''; + let backToRoute = '' as Route; if (backTo) { - backToRoute = appendParam(backTo, 'state', currentState ?? ''); + backToRoute = appendParam(backTo, 'state', currentState ?? '') as Route; } - // @ts-expect-error Navigation.goBack does take a param Navigation.goBack(backToRoute); }} /> diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index aa0af7e5f037..d2c108a4260a 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -24,6 +24,7 @@ import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import PusherHelper from '../utils/PusherHelper'; import * as TestHelper from '../utils/TestHelper'; +import type {MockFetch} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForNetworkPromises from '../utils/waitForNetworkPromises'; @@ -51,9 +52,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); }); @@ -68,179 +70,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?.participantAccountIDs).toEqual([CARLOS_ACCOUNT_ID]); - expect(chatReport?.iouReportID).toBe(iouReport?.reportID); + // They should be linked together + expect(chatReport?.participantAccountIDs).toEqual([CARLOS_ACCOUNT_ID]); + 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', () => { @@ -260,152 +258,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', () => { @@ -460,146 +453,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', () => { @@ -612,8 +601,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() @@ -724,10 +712,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( () => @@ -915,8 +901,8 @@ 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) ); }); }); @@ -1054,349 +1040,344 @@ 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.participantAccountIDs, [VIT_ACCOUNT_ID]), - ) ?? 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.participantAccountIDs, [CARLOS_ACCOUNT_ID, JULES_ACCOUNT_ID, VIT_ACCOUNT_ID, RORY_ACCOUNT_ID]), - ) ?? 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); - - /* 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); + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (allReports) => { + Onyx.disconnect(connectionID); - expect(carlosTransaction?.comment.comment).toBe(comment); - expect(julesTransaction?.comment.comment).toBe(comment); - expect(vitTransaction?.comment.comment).toBe(comment); - expect(groupTransaction?.comment.comment).toBe(comment); + // 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.participantAccountIDs, [VIT_ACCOUNT_ID]), + ) ?? 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.participantAccountIDs, [CARLOS_ACCOUNT_ID, JULES_ACCOUNT_ID, VIT_ACCOUNT_ID, RORY_ACCOUNT_ID]), + ) ?? 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); - 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(); + }, + }); + }), + ); }); }); @@ -1410,189 +1391,186 @@ 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(); + }, + }); + }), + ) - expect(Object.values(allReports ?? {}).length).toBe(3); + .then(mockFetch.resume) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (allReports) => { + Onyx.disconnect(connectionID); - 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(Object.values(allReports ?? {}).length).toBe(3); - expect(chatReport?.iouReportID).toBeFalsy(); + 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(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.REIMBURSED); - // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED); + expect(chatReport?.iouReportID).toBeFalsy(); - resolve(); - }, - }); - }), - ) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - waitForCollectionCallback: true, - callback: (allReportActions) => { - Onyx.disconnect(connectionID); + // 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); - 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(); + }, + }); + }), + ); }); }); @@ -1602,8 +1580,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', () => { @@ -1612,8 +1589,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(() => { @@ -1756,8 +1732,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?.(); }); }); @@ -1823,8 +1798,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( @@ -1917,16 +1891,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(() => { @@ -2083,8 +2055,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); } @@ -2233,9 +2204,9 @@ describe('actions/IOU', () => { afterEach(PusherHelper.teardown); 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(); + // Given the mockFetch operations are paused and an expense is initiated + + mockFetch.pause?.(); if (transaction && createIOUAction) { // When the expense is deleted @@ -2273,9 +2244,9 @@ 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(); + // Given mockFetch operations are resumed + + mockFetch.resume?.(); await waitForBatchedUpdates(); // Then we recheck the IOU report action from the report actions collection @@ -2309,9 +2280,9 @@ 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(); + // Given an IOU report and a paused mockFetch state + + mockFetch.pause?.(); if (transaction && createIOUAction) { // When the IOU expense is deleted @@ -2333,9 +2304,9 @@ describe('actions/IOU', () => { // Then the report should be truthy for offline support 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(); + // Given the resumed mockFetch state + + mockFetch.resume?.(); await waitForBatchedUpdates(); report = await new Promise>((resolve) => { @@ -2387,8 +2358,8 @@ 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); } @@ -2413,9 +2384,9 @@ describe('actions/IOU', () => { expect(iouReport).toHaveProperty('reportID'); 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(); + // Given the resumed mockFetch state + + mockFetch.resume?.(); allReports = await new Promise>((resolve) => { const connectionID = Onyx.connect({ @@ -2478,8 +2449,8 @@ 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) { @@ -2501,10 +2472,10 @@ 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(); - // Then After resuming fetch, the report for the given thread ID still does not exist + mockFetch.resume?.(); + + // Then After resuming mockFetch, the report for the given thread ID still does not exist report = await new Promise>((resolve) => { const connectionID = Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`, @@ -2668,8 +2639,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 @@ -2690,10 +2660,10 @@ describe('actions/IOU', () => { }); }); - // When fetch resumes + // When mockFetch 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}`, @@ -2815,8 +2785,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); @@ -2838,9 +2807,9 @@ 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(); + // When we resume mockFetch + + mockFetch.resume?.(); // Then we expect the moneyRequestPreview to show [Deleted expense] @@ -2889,8 +2858,8 @@ 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); @@ -2905,8 +2874,8 @@ 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(); @@ -2981,8 +2950,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); @@ -3007,8 +2975,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({ @@ -3334,8 +3301,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/PolicyTest.ts b/tests/actions/PolicyTest.ts index 44b69bcb86fe..0c6f035ca388 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -6,6 +6,7 @@ import * as Policy from '@src/libs/actions/Policy'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy as PolicyType, Report, ReportAction, ReportActions} from '@src/types/onyx'; import * as TestHelper from '../utils/TestHelper'; +import type {MockFetch} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; const ESH_EMAIL = 'eshgupta1217@gmail.com'; @@ -21,15 +22,13 @@ describe('actions/Policy', () => { }); 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('createWorkspace', () => { it('creates a new workspace', async () => { - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. - fetch.pause(); + (fetch as MockFetch)?.pause?.(); Onyx.set(ONYXKEYS.SESSION, {email: ESH_EMAIL, accountID: ESH_ACCOUNT_ID}); await waitForBatchedUpdates(); @@ -122,8 +121,7 @@ describe('actions/Policy', () => { }); // Check for success data - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. - fetch.resume(); + (fetch as MockFetch)?.resume?.(); await waitForBatchedUpdates(); policy = await new Promise((resolve) => { diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 6f1ea2c5ee7f..8f6e66dd1fe8 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -52,7 +52,6 @@ describe('actions/Report', () => { afterEach(PusherHelper.teardown); it('should store a new report action in Onyx when onyxApiUpdate event is handled via Pusher', () => { - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); const TEST_USER_ACCOUNT_ID = 1; @@ -168,7 +167,6 @@ describe('actions/Report', () => { return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN) .then(() => TestHelper.setPersonalDetails(TEST_USER_LOGIN, TEST_USER_ACCOUNT_ID)) .then(() => { - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); // WHEN we add enough logs to send a packet @@ -195,7 +193,6 @@ describe('actions/Report', () => { it('should be updated correctly when new comments are added, deleted or marked as unread', () => { jest.useFakeTimers(); - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); const REPORT_ID = '1'; let report: OnyxEntry; @@ -432,7 +429,6 @@ describe('actions/Report', () => { * already in the comment and the user deleted it on purpose. */ - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); // User edits comment to add link @@ -545,7 +541,6 @@ describe('actions/Report', () => { }); it('should properly toggle reactions on a message', () => { - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); const TEST_USER_ACCOUNT_ID = 1; @@ -666,7 +661,6 @@ describe('actions/Report', () => { }); it("shouldn't add the same reaction twice when changing preferred skin color and reaction doesn't support skin colors", () => { - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); const TEST_USER_ACCOUNT_ID = 1; diff --git a/tests/perf-test/ReportScreen.perf-test.tsx b/tests/perf-test/ReportScreen.perf-test.tsx index c89b7fe54b5f..9084d0141838 100644 --- a/tests/perf-test/ReportScreen.perf-test.tsx +++ b/tests/perf-test/ReportScreen.perf-test.tsx @@ -108,7 +108,6 @@ beforeAll(() => // Initialize the network key for OfflineWithFeedback beforeEach(() => { - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); wrapOnyxWithWaitForBatchedUpdates(Onyx); Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); @@ -217,9 +216,9 @@ test('[ReportScreen] should render ReportScreen with composer interactions', () .then(() => measurePerformance( , {scenario}, @@ -276,9 +275,9 @@ test.skip('[ReportScreen] should press of the report item', () => { .then(() => measurePerformance( , {scenario}, diff --git a/tests/perf-test/SearchPage.perf-test.tsx b/tests/perf-test/SearchPage.perf-test.tsx index 6ccecf0f735b..d804cff1444b 100644 --- a/tests/perf-test/SearchPage.perf-test.tsx +++ b/tests/perf-test/SearchPage.perf-test.tsx @@ -114,7 +114,6 @@ beforeAll(() => // Initialize the network key for OfflineWithFeedback beforeEach(() => { - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); wrapOnyxWithWaitForBatchedUpdates(Onyx); Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); @@ -177,7 +176,7 @@ test('[Search Page] should render list with cached options', async () => { [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, }), ) - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. + // @ts-expect-error Navigation prop is only used within this test .then(() => measurePerformance(, {scenario})) ); }); @@ -206,7 +205,7 @@ test('[Search Page] should interact when text input changes', async () => { [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, }), ) - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. + // @ts-expect-error Navigation prop is only used within this test .then(() => measurePerformance(, {scenario})) ); }); @@ -235,7 +234,7 @@ test.skip('[Search Page] should render selection list', async () => { [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, }), ) - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. + // @ts-expect-error Navigation prop is only used within this test .then(() => measurePerformance(, {scenario})) ); }); @@ -266,7 +265,7 @@ test('[Search Page] should search in selection list', async () => { [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, }), ) - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. + // @ts-expect-error Navigation prop is only used within this test .then(() => measurePerformance(, {scenario})) ); }); @@ -297,7 +296,7 @@ test('[Search Page] should click on list item', async () => { [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, }), ) - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. + // @ts-expect-error Navigation prop is only used within this test .then(() => measurePerformance(, {scenario})) ); }); diff --git a/tests/perf-test/SignInPage.perf-test.tsx b/tests/perf-test/SignInPage.perf-test.tsx index 67a8723192d2..a8b6a1475e94 100644 --- a/tests/perf-test/SignInPage.perf-test.tsx +++ b/tests/perf-test/SignInPage.perf-test.tsx @@ -74,7 +74,7 @@ describe('SignInPage', () => { // Initialize the network key for OfflineWithFeedback beforeEach(() => { - global.fetch = TestHelper.getGlobalFetchMock() as typeof fetch; + global.fetch = TestHelper.getGlobalFetchMock(); wrapOnyxWithWaitForBatchedUpdates(Onyx); Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); }); diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 2aeee2cc77bf..0917772e37c9 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -120,7 +120,6 @@ beforeAll(() => { // fetch() never gets called so it does not need mocking) or we might have fetch throw an error to test error handling // behavior. But here we just want to treat all API requests as a generic "success" and in the cases where we need to // simulate data arriving we will just set it into Onyx directly with Onyx.merge() or Onyx.set() etc. - // @ts-expect-error -- TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated global.fetch = TestHelper.getGlobalFetchMock(); Linking.setInitialURL('https://new.expensify.com/'); diff --git a/tests/unit/APITest.ts b/tests/unit/APITest.ts index 359288b2a1ef..f0900cfbae3e 100644 --- a/tests/unit/APITest.ts +++ b/tests/unit/APITest.ts @@ -1,5 +1,6 @@ import MockedOnyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; +import type {ApiRequestCommandParameters, ReadCommand, WriteCommand} from '@libs/API/types'; import CONST from '@src/CONST'; import * as PersistedRequests from '@src/libs/actions/PersistedRequests'; import * as API from '@src/libs/API'; @@ -40,7 +41,6 @@ type XhrCalls = Array<{ const originalXHR = HttpUtils.xhr; beforeEach(() => { - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); HttpUtils.xhr = originalXHR; MainQueue.clear(); @@ -71,12 +71,9 @@ describe('APITests', () => { return Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}) .then(() => { // When API Writes and Reads are called - // @ts-expect-error - mocking the parameter - API.write('mock command', {param1: 'value1'}); - // @ts-expect-error - mocking the parameter - API.read('mock command', {param2: 'value2'}); - // @ts-expect-error - mocking the parameter - API.write('mock command', {param3: 'value3'}); + API.write('mock command' as WriteCommand, {param1: 'value1'} as ApiRequestCommandParameters[WriteCommand]); + API.write('mock command' as WriteCommand, {param2: 'value2'} as ApiRequestCommandParameters[WriteCommand]); + API.write('mock command' as WriteCommand, {param3: 'value3'} as ApiRequestCommandParameters[WriteCommand]); return waitForBatchedUpdates(); }) .then(() => { @@ -110,10 +107,8 @@ describe('APITests', () => { }) .then(() => { // When API Write commands are made - // @ts-expect-error - mocking the parameter - API.write('mock command', {param1: 'value1'}); - // @ts-expect-error - mocking the parameter - API.write('mock command', {param2: 'value2'}); + API.write('mock command' as WriteCommand, {param1: 'value1'} as ApiRequestCommandParameters[WriteCommand]); + API.write('mock command' as WriteCommand, {param2: 'value2'} as ApiRequestCommandParameters[WriteCommand]); return waitForBatchedUpdates(); }) .then(() => { @@ -161,10 +156,8 @@ describe('APITests', () => { Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}) .then(() => { // When API Write commands are made - // @ts-expect-error - mocking the parameter - API.write('mock command', {param1: 'value1'}); - // @ts-expect-error - mocking the parameter - API.write('mock command', {param2: 'value2'}); + API.write('mock command' as WriteCommand, {param1: 'value1'} as ApiRequestCommandParameters[WriteCommand]); + API.write('mock command' as WriteCommand, {param2: 'value2'} as ApiRequestCommandParameters[WriteCommand]); return waitForBatchedUpdates(); }) @@ -225,8 +218,7 @@ describe('APITests', () => { Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}) .then(() => { // When API Write commands are made - // @ts-expect-error - mocking the parameter - API.write('mock command', {param1: 'value1'}); + API.write('mock command' as WriteCommand, {param1: 'value1'} as ApiRequestCommandParameters[WriteCommand]); return waitForNetworkPromises(); }) @@ -312,8 +304,7 @@ describe('APITests', () => { waitForBatchedUpdates() .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: true})) .then(() => { - // @ts-expect-error - mocking the parameter - API.write('Mock', {param1: 'value1'}); + API.write('Mock' as WriteCommand, {param1: 'value1'} as ApiRequestCommandParameters[WriteCommand]); return waitForBatchedUpdates(); }) @@ -346,20 +337,13 @@ describe('APITests', () => { }) .then(() => { // When we queue 6 persistable commands and one not persistable - // @ts-expect-error - mocking the parameter - API.write('MockCommand', {content: 'value1'}); - // @ts-expect-error - mocking the parameter - API.write('MockCommand', {content: 'value2'}); - // @ts-expect-error - mocking the parameter - API.write('MockCommand', {content: 'value3'}); - // @ts-expect-error - mocking the parameter - API.read('MockCommand', {content: 'not-persisted'}); - // @ts-expect-error - mocking the parameter - API.write('MockCommand', {content: 'value4'}); - // @ts-expect-error - mocking the parameter - API.write('MockCommand', {content: 'value5'}); - // @ts-expect-error - mocking the parameter - API.write('MockCommand', {content: 'value6'}); + API.write('MockCommand' as WriteCommand, {content: 'value1'} as ApiRequestCommandParameters[WriteCommand]); + API.write('MockCommand' as WriteCommand, {content: 'value2'} as ApiRequestCommandParameters[WriteCommand]); + API.write('MockCommand' as WriteCommand, {content: 'value3'} as ApiRequestCommandParameters[WriteCommand]); + API.read('MockCommand' as ReadCommand, {content: 'not-persisted'} as unknown as ApiRequestCommandParameters[ReadCommand]); + API.write('MockCommand' as WriteCommand, {content: 'value4'} as ApiRequestCommandParameters[WriteCommand]); + API.write('MockCommand' as WriteCommand, {content: 'value5'} as ApiRequestCommandParameters[WriteCommand]); + API.write('MockCommand' as WriteCommand, {content: 'value6'} as ApiRequestCommandParameters[WriteCommand]); return waitForBatchedUpdates(); }) @@ -390,18 +374,12 @@ describe('APITests', () => { }) .then(() => { // When we queue 6 persistable commands - // @ts-expect-error - mocking the parameter - API.write('MockCommand', {content: 'value1'}); - // @ts-expect-error - mocking the parameter - API.write('MockCommand', {content: 'value2'}); - // @ts-expect-error - mocking the parameter - API.write('MockCommand', {content: 'value3'}); - // @ts-expect-error - mocking the parameter - API.write('MockCommand', {content: 'value4'}); - // @ts-expect-error - mocking the parameter - API.write('MockCommand', {content: 'value5'}); - // @ts-expect-error - mocking the parameter - API.write('MockCommand', {content: 'value6'}); + API.write('MockCommand' as WriteCommand, {content: 'value1'} as ApiRequestCommandParameters[WriteCommand]); + API.write('MockCommand' as WriteCommand, {content: 'value2'} as ApiRequestCommandParameters[WriteCommand]); + API.write('MockCommand' as WriteCommand, {content: 'value3'} as ApiRequestCommandParameters[WriteCommand]); + API.write('MockCommand' as WriteCommand, {content: 'value4'} as ApiRequestCommandParameters[WriteCommand]); + API.write('MockCommand' as WriteCommand, {content: 'value5'} as ApiRequestCommandParameters[WriteCommand]); + API.write('MockCommand' as WriteCommand, {content: 'value6'} as ApiRequestCommandParameters[WriteCommand]); return waitForBatchedUpdates(); }) .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) @@ -454,8 +432,7 @@ describe('APITests', () => { return waitForBatchedUpdates(); }) .then(() => { - // @ts-expect-error - mocking the parameter - API.write('MockCommand'); + API.write('MockCommand' as WriteCommand, {}); expect(PersistedRequests.getAll().length).toBe(1); expect(NetworkStore.isOffline()).toBe(true); expect(SequentialQueue.isRunning()).toBe(false); @@ -524,8 +501,7 @@ describe('APITests', () => { NetworkStore.resetHasReadRequiredDataFromStorage(); // And queue a Write request while offline - // @ts-expect-error - mocking the parameter - API.write('MockCommand', {content: 'value1'}); + API.write('MockCommand' as WriteCommand, {content: 'value1'} as ApiRequestCommandParameters[WriteCommand]); // Then we should expect the request to get persisted expect(PersistedRequests.getAll().length).toBe(1); @@ -561,12 +537,9 @@ describe('APITests', () => { expect(NetworkStore.isOffline()).toBe(false); // WHEN we make a request that should be retried, one that should not, and another that should - // @ts-expect-error - mocking the parameter - API.write('MockCommandOne'); - // @ts-expect-error - mocking the parameter - API.read('MockCommandTwo'); - // @ts-expect-error - mocking the parameter - API.write('MockCommandThree'); + API.write('MockCommandOne' as WriteCommand, {}); + API.read('MockCommandTwo' as ReadCommand, {}); + API.write('MockCommandThree' as WriteCommand, {}); // THEN the retryable requests should immediately be added to the persisted requests expect(PersistedRequests.getAll().length).toBe(2); diff --git a/tests/unit/EmojiTest.ts b/tests/unit/EmojiTest.ts index 954d561598e8..9d1b4b20cf31 100644 --- a/tests/unit/EmojiTest.ts +++ b/tests/unit/EmojiTest.ts @@ -194,7 +194,6 @@ describe('EmojiTest', () => { beforeAll(() => { Onyx.init({keys: ONYXKEYS}); - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); spy = jest.spyOn(User, 'updateFrequentlyUsedEmojis'); }); diff --git a/tests/unit/MiddlewareTest.ts b/tests/unit/MiddlewareTest.ts index 78a54461ae20..cc4bf7d0df6b 100644 --- a/tests/unit/MiddlewareTest.ts +++ b/tests/unit/MiddlewareTest.ts @@ -14,7 +14,6 @@ Onyx.init({ }); beforeAll(() => { - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); }); diff --git a/tests/unit/NetworkTest.ts b/tests/unit/NetworkTest.ts index 01689de89037..b6a705b33296 100644 --- a/tests/unit/NetworkTest.ts +++ b/tests/unit/NetworkTest.ts @@ -28,7 +28,6 @@ OnyxUpdateManager(); const originalXHR = HttpUtils.xhr; beforeEach(() => { - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); HttpUtils.xhr = originalXHR; MainQueue.clear(); diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index a926d0589e80..fc6c67b5c560 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -1,4 +1,5 @@ import Onyx from 'react-native-onyx'; +import type {KeyValueMapping} from 'react-native-onyx'; import CONST from '../../src/CONST'; import * as ReportActionsUtils from '../../src/libs/ReportActionsUtils'; import ONYXKEYS from '../../src/ONYXKEYS'; @@ -1952,11 +1953,10 @@ describe('ReportActionsUtils', () => { waitForBatchedUpdates() // When Onyx is updated with the data and the sidebar re-renders .then(() => - // @ts-expect-error Preset necessary values Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]: report, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`]: {[action.reportActionID]: action, [action2.reportActionID]: action2}, - }), + } as unknown as KeyValueMapping), ) .then( () => diff --git a/tests/unit/RequestTest.ts b/tests/unit/RequestTest.ts index de05f9cab2fa..bb444717ad41 100644 --- a/tests/unit/RequestTest.ts +++ b/tests/unit/RequestTest.ts @@ -5,7 +5,6 @@ import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; beforeAll(() => { - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. global.fetch = TestHelper.getGlobalFetchMock(); }); diff --git a/tests/unit/awaitStagingDeploysTest.ts b/tests/unit/awaitStagingDeploysTest.ts index e4f7d6494b15..d65872b1593b 100644 --- a/tests/unit/awaitStagingDeploysTest.ts +++ b/tests/unit/awaitStagingDeploysTest.ts @@ -70,7 +70,6 @@ beforeAll(() => { // Mock octokit module const moctokit: InternalOctokit = { rest: { - // @ts-expect-error This error was removed because getting the rest of the data from internalOctokit makes the test to break actions: { listWorkflowRuns: mockListWorkflowRuns as unknown as typeof GithubUtils.octokit.actions.listWorkflowRuns, }, diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts index 9f3b28973923..f89ed51e28d0 100644 --- a/tests/utils/TestHelper.ts +++ b/tests/utils/TestHelper.ts @@ -190,7 +190,7 @@ function getGlobalFetchMock() { mockFetch.fail = () => (shouldFail = true); mockFetch.succeed = () => (shouldFail = false); - return mockFetch; + return mockFetch as typeof fetch; } function setPersonalDetails(login: string, accountID: number) { @@ -242,4 +242,5 @@ const createAddListenerMock = () => { return {triggerTransitionEnd, addListener}; }; +export type {MockFetch}; export {assertFormDataMatchesObject, buildPersonalDetails, buildTestReportComment, createAddListenerMock, getGlobalFetchMock, setPersonalDetails, signInWithTestUser, signOutTestUser}; From 5bed3792bb9a79e783dff795c9a0b5253d5672e1 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Sat, 20 Apr 2024 15:40:44 +0200 Subject: [PATCH 042/174] Fix ts and unit test issues --- tests/unit/APITest.ts | 2 +- tests/unit/awaitStagingDeploysTest.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/APITest.ts b/tests/unit/APITest.ts index f0900cfbae3e..9688b0890c34 100644 --- a/tests/unit/APITest.ts +++ b/tests/unit/APITest.ts @@ -72,7 +72,7 @@ describe('APITests', () => { .then(() => { // When API Writes and Reads are called API.write('mock command' as WriteCommand, {param1: 'value1'} as ApiRequestCommandParameters[WriteCommand]); - API.write('mock command' as WriteCommand, {param2: 'value2'} as ApiRequestCommandParameters[WriteCommand]); + API.read('mock command' as ReadCommand, {param2: 'value2'} as unknown as ApiRequestCommandParameters[ReadCommand]); API.write('mock command' as WriteCommand, {param3: 'value3'} as ApiRequestCommandParameters[WriteCommand]); return waitForBatchedUpdates(); }) diff --git a/tests/unit/awaitStagingDeploysTest.ts b/tests/unit/awaitStagingDeploysTest.ts index d65872b1593b..e4f7d6494b15 100644 --- a/tests/unit/awaitStagingDeploysTest.ts +++ b/tests/unit/awaitStagingDeploysTest.ts @@ -70,6 +70,7 @@ beforeAll(() => { // Mock octokit module const moctokit: InternalOctokit = { rest: { + // @ts-expect-error This error was removed because getting the rest of the data from internalOctokit makes the test to break actions: { listWorkflowRuns: mockListWorkflowRuns as unknown as typeof GithubUtils.octokit.actions.listWorkflowRuns, }, From 9c45c2554dd82b8171007730b04def1a8747201a Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 22 Apr 2024 17:01:39 +0200 Subject: [PATCH 043/174] Check and remove unnecessary @ts-expect-error suppressions x2 --- src/Expensify.tsx | 4 +++- src/libs/Url.ts | 5 +++-- .../home/report/ReportActionCompose/ReportActionCompose.tsx | 2 +- .../settings/Profile/PersonalDetails/StateSelectionPage.tsx | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 4920205d731d..a2293fa23f24 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -74,7 +74,9 @@ type ExpensifyOnyxProps = { type ExpensifyProps = ExpensifyOnyxProps; -const SplashScreenHiddenContext = React.createContext<{isSplashHidden?: boolean}>({}); +type SplashScreenHiddenContextType = {isSplashHidden?: boolean}; + +const SplashScreenHiddenContext = React.createContext({}); function Expensify({ isCheckingPublicRoom = true, diff --git a/src/libs/Url.ts b/src/libs/Url.ts index 4e3282e7bdb3..09d632f32653 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; } export {addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL, appendParam}; diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 55c56910827f..6b398bc3cb5d 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: Emoji) => void; + replaceSelectionWithText: EmojiPickerActions.OnEmojiSelected; prepareCommentAndResetComposer: () => string; isFocused: () => boolean; }; diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index 826e165acb08..bf1bb8ad197e 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -91,10 +91,10 @@ function StateSelectionPage() { shouldShowBackButton onBackButtonPress={() => { const backTo = params?.backTo ?? ''; - let backToRoute = '' as Route; + let backToRoute: Route | undefined; if (backTo) { - backToRoute = appendParam(backTo, 'state', currentState ?? '') as Route; + backToRoute = appendParam(backTo, 'state', currentState ?? ''); } Navigation.goBack(backToRoute); From 68b9e49ba87bf19eef53ecfa3c21207a0c1ec30b Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 24 Apr 2024 13:27:06 +0200 Subject: [PATCH 044/174] Mocking data --- src/components/MoneyReportHeader.tsx | 2 +- src/components/ReportActionItem/ReportPreview.tsx | 5 +---- src/pages/home/report/ReportActionsList.tsx | 15 ++++++++++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 07c78623fce8..027bb0f0d28c 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -89,7 +89,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport); const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); - const transactionIDs = TransactionUtils.getAllReportTransactions(chatReport?.reportID).map((transaction) => transaction.transactionID); + const transactionIDs = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID).map((transaction) => transaction.transactionID); const haveAllPendingRTERViolation = TransactionUtils.haveAllPendingRTERViolation(transactionIDs); const cancelPayment = useCallback(() => { diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 4d89e728aae6..f43dd429394f 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -213,7 +213,7 @@ function ReportPreview({ const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(iouReport); - const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton; + const shouldShowSettlementButton = (shouldShowPayButton || shouldShowApproveButton) && !showRTERViolationMessage; const shouldPromptUserToAddBankAccount = ReportUtils.hasMissingPaymentMethod(userWallet, iouReportID); const shouldShowRBR = !iouSettled && hasErrors; @@ -238,9 +238,6 @@ function ReportPreview({ const parsedSubtitle = new ExpensiMark().replace(formattedDescription ?? moneyRequestComment); return {isSupportTextHtml: !!parsedSubtitle, supportText: parsedSubtitle ? `${parsedSubtitle}` : ''}; } - // if (true) { - // return {isSupportTextHtml: false, supportText: 'ABC'}; - // } return { isSupportTextHtml: false, supportText: translate('iou.expenseCount', { diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 4c7346e1355c..849783944f27 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -27,6 +27,7 @@ import type {CentralPaneNavigatorParamList} from '@navigation/types'; import variables from '@styles/variables'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; @@ -191,7 +192,19 @@ function ReportActionsList({ const hasFooterRendered = useRef(false); const lastVisibleActionCreatedRef = useRef(report.lastVisibleActionCreated); const lastReadTimeRef = useRef(report.lastReadTime); - Onyx.merge('transactions_8811441407757684730', {cardID: 1, merchant: 'Google', hasEReceipt: true, status: 'Pending'}); + //Single MoneyRequest + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}6196867412357270168`, {cardID: 1, merchant: 'single MoneyRequest test'}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}6196867412357270168`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); + //Multiple MoneyRequests test + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}1304796714991934480`, {cardID: 1, merchant: 'multiple MoneyRequests test 1'}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}1304796714991934480`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}6286508495235425496`, {cardID: 1, merchant: 'multiple MoneyRequests test 2'}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}6286508495235425496`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}2150079702326626524`, {cardID: 1, merchant: 'multiple MoneyRequests test 3'}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}2150079702326626524`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); + //One-Expense Chat test + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}2438117170083649063`, {cardID: 1, merchant: 'One-Expense Chat test'}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}2438117170083649063`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); const sortedVisibleReportActions = useMemo( () => From ed94034f4dc70370055716535b73660d6d2aaab9 Mon Sep 17 00:00:00 2001 From: kmichel Date: Thu, 25 Apr 2024 04:19:04 -0700 Subject: [PATCH 045/174] fix playback speed error --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 10 ++++++++++ .../VideoPlayerContexts/VideoPopoverMenuContext.tsx | 12 +++++++----- src/components/VideoPlayerContexts/types.ts | 2 ++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 0138e2f870e1..8124e4502ea0 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -10,6 +10,8 @@ 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'; import useNetwork from '@hooks/useNetwork'; @@ -79,6 +81,7 @@ function BaseVideoPlayer({ const isUploading = CONST.ATTACHMENT_LOCAL_URL_PREFIX.some((prefix) => url.startsWith(prefix)); const videoStateRef = useRef(null); const {updateVolume} = useVolumeContext(); + const {playerRef, setCurrentPlaybackSpeed} = useVideoPopoverMenuContext(); const togglePlayCurrentVideo = useCallback(() => { videoResumeTryNumber.current = 0; @@ -93,6 +96,13 @@ function BaseVideoPlayer({ const showPopoverMenu = (event?: GestureResponderEvent | KeyboardEvent) => { setIsPopoverVisible(true); + playerRef.current = videoPlayerRef.current; + videoPlayerRef.current?.getStatusAsync().then((status) => { + if (!('rate' in status && status.rate)) { + return; + } + 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 f953ed802623..e6deb498bd42 100644 --- a/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx +++ b/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx @@ -1,6 +1,7 @@ -import React, {useCallback, useContext, useMemo, useState} from 'react'; +import React, {useCallback, useContext, useMemo, useRef, useState} from 'react'; import * as Expensicons from '@components/Icon/Expensicons'; import type {PopoverMenuItem} from '@components/PopoverMenu'; +import type {VideoWithOnFullScreenUpdate} from '@components/VideoPlayer/types'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; @@ -13,18 +14,19 @@ 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(); const isLocalFile = currentlyPlayingURL && CONST.ATTACHMENT_LOCAL_URL_PREFIX.some((prefix) => currentlyPlayingURL.startsWith(prefix)); + const playerRef = useRef(null); const updatePlaybackSpeed = useCallback( (speed: PlaybackSpeed) => { setCurrentPlaybackSpeed(speed); - currentVideoPlayerRef.current?.setStatusAsync?.({rate: speed}); + playerRef.current?.setStatusAsync?.({rate: speed}); }, - [currentVideoPlayerRef], + [playerRef], ); const downloadAttachment = useCallback(() => { @@ -63,7 +65,7 @@ function VideoPopoverMenuContextProvider({children}: ChildrenProps) { return items; }, [currentPlaybackSpeed, downloadAttachment, translate, updatePlaybackSpeed, isOffline, isLocalFile]); - const contextValue = useMemo(() => ({menuItems, updatePlaybackSpeed}), [menuItems, updatePlaybackSpeed]); + const contextValue = useMemo(() => ({menuItems, playerRef, updatePlaybackSpeed, setCurrentPlaybackSpeed}), [menuItems, playerRef, updatePlaybackSpeed, setCurrentPlaybackSpeed]); return {children}; } diff --git a/src/components/VideoPlayerContexts/types.ts b/src/components/VideoPlayerContexts/types.ts index ff8d9378caf7..9dc5cbcfd628 100644 --- a/src/components/VideoPlayerContexts/types.ts +++ b/src/components/VideoPlayerContexts/types.ts @@ -28,7 +28,9 @@ type VolumeContext = { type VideoPopoverMenuContext = { menuItems: PopoverMenuItem[]; + playerRef: MutableRefObject; updatePlaybackSpeed: (speed: PlaybackSpeed) => void; + setCurrentPlaybackSpeed: (speed: PlaybackSpeed) => void; }; type FullScreenContext = { From 1f52f44d58a0195c9ce4696b4398a260782c2379 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 26 Apr 2024 08:40:04 +0700 Subject: [PATCH 046/174] fix tests for Workspace Profile Members and Categories pages --- tests/actions/PolicyCategoryTest.ts | 310 ++++++++++++++++++++++ tests/actions/PolicyMemberTest.ts | 291 ++++++++++++++++++++ tests/actions/PolicyProfileTest.ts | 78 ++++++ tests/utils/collections/policyCategory.ts | 20 ++ 4 files changed, 699 insertions(+) create mode 100644 tests/actions/PolicyCategoryTest.ts create mode 100644 tests/actions/PolicyMemberTest.ts create mode 100644 tests/actions/PolicyProfileTest.ts create mode 100644 tests/utils/collections/policyCategory.ts diff --git a/tests/actions/PolicyCategoryTest.ts b/tests/actions/PolicyCategoryTest.ts new file mode 100644 index 000000000000..014281ec3690 --- /dev/null +++ b/tests/actions/PolicyCategoryTest.ts @@ -0,0 +1,310 @@ +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', () => { + 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(); + return ( + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy) + .then(() => { + Policy.setWorkspaceRequiresCategory(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); + // 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. + .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?.pendingFields?.requiresCategory).toBeFalsy(); + resolve(); + }, + }); + }), + ) + ); + }); + }); + describe('CreateWorkspaceCategories', () => { + it('Create a new policy category', () => { + 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) + .then(() => { + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories); + }) + .then(() => { + Policy.createPolicyCategory(fakePolicy.id, newCategoryName); + return waitForBatchedUpdates(); + }) + .then( + () => + 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. + .then(fetch.resume) + .then(waitForBatchedUpdates) + .then( + () => + 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', () => { + 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(); + return ( + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy) + .then(() => { + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories); + }) + .then(() => { + Policy.renamePolicyCategory(fakePolicy.id, { + oldName: oldCategoryName, + newName: newCategoryName, + }); + return waitForBatchedUpdates(); + }) + .then( + () => + 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. + .then(fetch.resume) + .then(waitForBatchedUpdates) + .then( + () => + 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', () => { + 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(); + return ( + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy) + .then(() => { + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories); + }) + .then(() => { + Policy.setWorkspaceCategoryEnabled(fakePolicy.id, categoriesToUpdate); + return waitForBatchedUpdates(); + }) + .then( + () => + 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. + .then(fetch.resume) + .then(waitForBatchedUpdates) + .then( + () => + 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', () => { + 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(); + return ( + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy) + .then(() => { + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories); + }) + .then(() => { + Policy.deleteWorkspaceCategories(fakePolicy.id, categoriesToDelete); + return waitForBatchedUpdates(); + }) + .then( + () => + 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. + .then(fetch.resume) + .then(waitForBatchedUpdates) + .then( + () => + 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..594a3addfc9f --- /dev/null +++ b/tests/actions/PolicyMemberTest.ts @@ -0,0 +1,291 @@ +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 {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', () => { + 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(); + return ( + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy) + .then(() => { + Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${fakeReport.reportID}`, fakeReport); + }) + .then(() => { + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fakeReport.reportID}`, { + [fakeReportAction.reportActionID]: fakeReportAction, + }); + }) + .then(() => { + Policy.acceptJoinRequest(fakeReport.reportID, fakeReportAction); + return waitForBatchedUpdates(); + }) + .then( + () => + 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. + .then(fetch.resume) + .then(waitForBatchedUpdates) + .then( + () => + 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', () => { + const fakeUser2 = { + ...createPersonalDetails(2), + role: CONST.POLICY.ROLE.USER, + }; + const fakePolicy: PolicyType = { + ...createRandomPolicy(0), + employeeList: { + [fakeUser2.login ?? '']: { + email: fakeUser2.login, + role: fakeUser2.role, + }, + }, + }; + + // @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.updateWorkspaceMembersRole(fakePolicy.id, [fakeUser2.accountID], CONST.POLICY.ROLE.ADMIN); + return waitForBatchedUpdates(); + }) + .then( + () => + 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); + expect(employee?.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. + .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 employee = policy?.employeeList?.[fakeUser2?.login ?? '']; + expect(employee?.pendingAction).toBeFalsy(); + resolve(); + }, + }); + }), + ) + ); + }); + }); + describe('RequestWorkspaceOwnerChange', () => { + it('Change the workspace`s owner', () => { + 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(); + return ( + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy) + .then(() => { + Onyx.merge(ONYXKEYS.SESSION, {email: fakeEmail, accountID: fakeAccountID}); + }) + .then(() => { + Policy.requestWorkspaceOwnerChange(fakePolicy.id); + 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?.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. + .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?.isLoading).toBeFalsy(); + expect(policy?.isChangeOwnerSuccessful).toBeTruthy(); + expect(policy?.isChangeOwnerFailed)?.toBeFalsy(); + expect(policy?.owner).toBe(fakeEmail); + expect(policy?.ownerAccountID).toBe(fakeAccountID); + resolve(); + }, + }); + }), + ) + ); + }); + }); + describe('AddBillingCardAndRequestPolicyOwnerChange', () => { + it('Add billing card and change the workspace`s owner', () => { + 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(); + return ( + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy) + .then(() => { + Onyx.merge(ONYXKEYS.SESSION, {email: fakeEmail, accountID: fakeAccountID}); + }) + .then(() => { + Policy.addBillingCardAndRequestPolicyOwnerChange(fakePolicy.id, fakeCard); + 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?.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. + .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?.isLoading).toBeFalsy(); + expect(policy?.isChangeOwnerSuccessful).toBeTruthy(); + expect(policy?.isChangeOwnerFailed)?.toBeFalsy(); + expect(policy?.owner).toBe(fakeEmail); + expect(policy?.ownerAccountID).toBe(fakeAccountID); + resolve(); + }, + }); + }), + ) + ); + }); + }); +}); diff --git a/tests/actions/PolicyProfileTest.ts b/tests/actions/PolicyProfileTest.ts new file mode 100644 index 000000000000..f8b898d71da0 --- /dev/null +++ b/tests/actions/PolicyProfileTest.ts @@ -0,0 +1,78 @@ +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', () => { + 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(); + return ( + Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy) + .then(() => { + Policy.updateWorkspaceDescription(fakePolicy.id, newDescription, oldDescription); + 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?.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. + .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?.description).toBeFalsy(); + + resolve(); + }, + }); + }), + ) + ); + }); + }); +}); diff --git a/tests/utils/collections/policyCategory.ts b/tests/utils/collections/policyCategory.ts new file mode 100644 index 000000000000..d112e5b7d4f7 --- /dev/null +++ b/tests/utils/collections/policyCategory.ts @@ -0,0 +1,20 @@ +import {randWord} from '@ngneat/falso'; +import type {PolicyCategories} from '@src/types/onyx'; + +export default function createRandomPolicyCategories(numberOfCategories = 0): PolicyCategories { + const categories: PolicyCategories = {}; + for (let i = 0; i < numberOfCategories; i++) { + const categoryName = randWord(); + categories[categoryName] = { + name: categoryName, + enabled: false, + 'GL Code': '', + unencodedName: categoryName, + externalID: '', + areCommentsRequired: false, + origin: '', + }; + } + + return categories; +} From 6141c72f50beea5908c9db3edea36b4928d4f663 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 26 Apr 2024 08:45:46 +0700 Subject: [PATCH 047/174] fix lint --- tests/actions/PolicyMemberTest.ts | 2 +- tests/utils/collections/policyCategory.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/actions/PolicyMemberTest.ts b/tests/actions/PolicyMemberTest.ts index 594a3addfc9f..7288afeee0c1 100644 --- a/tests/actions/PolicyMemberTest.ts +++ b/tests/actions/PolicyMemberTest.ts @@ -4,7 +4,7 @@ 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 {OriginalMessageJoinPolicyChangeLog} from '@src/types/onyx/OriginalMessage'; +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'; diff --git a/tests/utils/collections/policyCategory.ts b/tests/utils/collections/policyCategory.ts index d112e5b7d4f7..37a5ecb4f21f 100644 --- a/tests/utils/collections/policyCategory.ts +++ b/tests/utils/collections/policyCategory.ts @@ -8,6 +8,7 @@ export default function createRandomPolicyCategories(numberOfCategories = 0): Po categories[categoryName] = { name: categoryName, enabled: false, + // eslint-disable-next-line @typescript-eslint/naming-convention 'GL Code': '', unencodedName: categoryName, externalID: '', From 3559e392935ca86874bd172f732cf3f00109260e Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 26 Apr 2024 10:58:39 +0700 Subject: [PATCH 048/174] fix test 3 --- tests/actions/PolicyMemberTest.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/actions/PolicyMemberTest.ts b/tests/actions/PolicyMemberTest.ts index 7288afeee0c1..d541b398b042 100644 --- a/tests/actions/PolicyMemberTest.ts +++ b/tests/actions/PolicyMemberTest.ts @@ -1,4 +1,5 @@ import Onyx from 'react-native-onyx'; +import Log from '@libs/Log'; import CONST from '@src/CONST'; import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager'; import * as Policy from '@src/libs/actions/Policy'; @@ -104,16 +105,13 @@ describe('actions/PolicyMember', () => { }); describe('UpdateWorkspaceMembersRole', () => { it('Update member to admin role', () => { - const fakeUser2 = { - ...createPersonalDetails(2), - role: CONST.POLICY.ROLE.USER, - }; + const fakeUser2 = createPersonalDetails(2); const fakePolicy: PolicyType = { ...createRandomPolicy(0), employeeList: { [fakeUser2.login ?? '']: { email: fakeUser2.login, - role: fakeUser2.role, + role: CONST.POLICY.ROLE.USER, }, }, }; @@ -122,6 +120,9 @@ describe('actions/PolicyMember', () => { fetch.pause(); return ( Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy) + .then(() => { + Onyx.set(`${ONYXKEYS.PERSONAL_DETAILS_LIST}`, {[fakeUser2.accountID]: fakeUser2}); + }) .then(() => { Policy.updateWorkspaceMembersRole(fakePolicy.id, [fakeUser2.accountID], CONST.POLICY.ROLE.ADMIN); return waitForBatchedUpdates(); @@ -136,7 +137,7 @@ describe('actions/PolicyMember', () => { Onyx.disconnect(connectionID); const employee = policy?.employeeList?.[fakeUser2?.login ?? '']; expect(employee?.role).toBe(CONST.POLICY.ROLE.ADMIN); - expect(employee?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); + resolve(); }, }); @@ -211,8 +212,6 @@ describe('actions/PolicyMember', () => { expect(policy?.isLoading).toBeFalsy(); expect(policy?.isChangeOwnerSuccessful).toBeTruthy(); expect(policy?.isChangeOwnerFailed)?.toBeFalsy(); - expect(policy?.owner).toBe(fakeEmail); - expect(policy?.ownerAccountID).toBe(fakeAccountID); resolve(); }, }); @@ -278,8 +277,6 @@ describe('actions/PolicyMember', () => { expect(policy?.isLoading).toBeFalsy(); expect(policy?.isChangeOwnerSuccessful).toBeTruthy(); expect(policy?.isChangeOwnerFailed)?.toBeFalsy(); - expect(policy?.owner).toBe(fakeEmail); - expect(policy?.ownerAccountID).toBe(fakeAccountID); resolve(); }, }); From 88ce9bc186be1b5d55c16041eb22f2b3b220fd4e Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 26 Apr 2024 11:07:24 +0700 Subject: [PATCH 049/174] fix lint --- tests/actions/PolicyMemberTest.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/actions/PolicyMemberTest.ts b/tests/actions/PolicyMemberTest.ts index d541b398b042..b70ed0c1800e 100644 --- a/tests/actions/PolicyMemberTest.ts +++ b/tests/actions/PolicyMemberTest.ts @@ -1,5 +1,4 @@ import Onyx from 'react-native-onyx'; -import Log from '@libs/Log'; import CONST from '@src/CONST'; import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager'; import * as Policy from '@src/libs/actions/Policy'; From 07d3bc51810bc5b695e1e93a1800dcc81bc76e7c Mon Sep 17 00:00:00 2001 From: smelaa Date: Fri, 26 Apr 2024 14:24:02 +0200 Subject: [PATCH 050/174] Disable eslint on mocking data --- src/pages/home/report/ReportActionsList.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 0d9c5d31253f..4b217668802b 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -190,18 +190,28 @@ function ReportActionsList({ const hasFooterRendered = useRef(false); const lastVisibleActionCreatedRef = useRef(report.lastVisibleActionCreated); const lastReadTimeRef = useRef(report.lastReadTime); - //Single MoneyRequest + // Single MoneyRequest + // eslint-disable-next-line rulesdir/prefer-actions-set-data Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}6196867412357270168`, {cardID: 1, merchant: 'single MoneyRequest test'}); + // eslint-disable-next-line rulesdir/prefer-actions-set-data Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}6196867412357270168`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); - //Multiple MoneyRequests test + // Multiple MoneyRequests test + // eslint-disable-next-line rulesdir/prefer-actions-set-data Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}1304796714991934480`, {cardID: 1, merchant: 'multiple MoneyRequests test 1'}); + // eslint-disable-next-line rulesdir/prefer-actions-set-data Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}1304796714991934480`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); + // eslint-disable-next-line rulesdir/prefer-actions-set-data Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}6286508495235425496`, {cardID: 1, merchant: 'multiple MoneyRequests test 2'}); + // eslint-disable-next-line rulesdir/prefer-actions-set-data Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}6286508495235425496`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); + // eslint-disable-next-line rulesdir/prefer-actions-set-data Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}2150079702326626524`, {cardID: 1, merchant: 'multiple MoneyRequests test 3'}); + // eslint-disable-next-line rulesdir/prefer-actions-set-data Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}2150079702326626524`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); - //One-Expense Chat test + // One-Expense Chat test + // eslint-disable-next-line rulesdir/prefer-actions-set-data Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}2438117170083649063`, {cardID: 1, merchant: 'One-Expense Chat test'}); + // eslint-disable-next-line rulesdir/prefer-actions-set-data Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}2438117170083649063`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); const sortedVisibleReportActions = useMemo( From 29a23c26a36a32d914fd86fd13735a1401335212 Mon Sep 17 00:00:00 2001 From: kmichel Date: Fri, 26 Apr 2024 08:11:30 -0700 Subject: [PATCH 051/174] update ref name --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 4 ++-- .../VideoPlayerContexts/VideoPopoverMenuContext.tsx | 8 ++++---- src/components/VideoPlayerContexts/types.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 04cfdf11d0e9..2ce33cb442de 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -81,7 +81,7 @@ function BaseVideoPlayer({ const isUploading = CONST.ATTACHMENT_LOCAL_URL_PREFIX.some((prefix) => url.startsWith(prefix)); const videoStateRef = useRef(null); const {updateVolume} = useVolumeContext(); - const {playerRef, setCurrentPlaybackSpeed} = useVideoPopoverMenuContext(); + const {videoPopoverMenuPlayerRef, setCurrentPlaybackSpeed} = useVideoPopoverMenuContext(); const togglePlayCurrentVideo = useCallback(() => { videoResumeTryNumber.current = 0; @@ -96,7 +96,7 @@ function BaseVideoPlayer({ const showPopoverMenu = (event?: GestureResponderEvent | KeyboardEvent) => { setIsPopoverVisible(true); - playerRef.current = videoPlayerRef.current; + videoPopoverMenuPlayerRef.current = videoPlayerRef.current; videoPlayerRef.current?.getStatusAsync().then((status) => { if (!('rate' in status && status.rate)) { return; diff --git a/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx b/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx index e6deb498bd42..79c503494b19 100644 --- a/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx +++ b/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx @@ -19,14 +19,14 @@ function VideoPopoverMenuContextProvider({children}: ChildrenProps) { const [currentPlaybackSpeed, setCurrentPlaybackSpeed] = useState(CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS[2]); const {isOffline} = useNetwork(); const isLocalFile = currentlyPlayingURL && CONST.ATTACHMENT_LOCAL_URL_PREFIX.some((prefix) => currentlyPlayingURL.startsWith(prefix)); - const playerRef = useRef(null); + const videoPopoverMenuPlayerRef = useRef(null); const updatePlaybackSpeed = useCallback( (speed: PlaybackSpeed) => { setCurrentPlaybackSpeed(speed); - playerRef.current?.setStatusAsync?.({rate: speed}); + videoPopoverMenuPlayerRef.current?.setStatusAsync?.({rate: speed}); }, - [playerRef], + [videoPopoverMenuPlayerRef], ); const downloadAttachment = useCallback(() => { @@ -65,7 +65,7 @@ function VideoPopoverMenuContextProvider({children}: ChildrenProps) { return items; }, [currentPlaybackSpeed, downloadAttachment, translate, updatePlaybackSpeed, isOffline, isLocalFile]); - const contextValue = useMemo(() => ({menuItems, playerRef, updatePlaybackSpeed, setCurrentPlaybackSpeed}), [menuItems, playerRef, updatePlaybackSpeed, setCurrentPlaybackSpeed]); + 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 9dc5cbcfd628..6216fd5dc85b 100644 --- a/src/components/VideoPlayerContexts/types.ts +++ b/src/components/VideoPlayerContexts/types.ts @@ -28,7 +28,7 @@ type VolumeContext = { type VideoPopoverMenuContext = { menuItems: PopoverMenuItem[]; - playerRef: MutableRefObject; + videoPopoverMenuPlayerRef: MutableRefObject; updatePlaybackSpeed: (speed: PlaybackSpeed) => void; setCurrentPlaybackSpeed: (speed: PlaybackSpeed) => void; }; From daaf9fac72da97df4c394341a55cadc0394fe19f Mon Sep 17 00:00:00 2001 From: kmichel Date: Fri, 26 Apr 2024 09:12:47 -0700 Subject: [PATCH 052/174] prettier --- .../VideoPlayerContexts/VideoPopoverMenuContext.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx b/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx index 79c503494b19..188764f73e91 100644 --- a/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx +++ b/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx @@ -65,7 +65,10 @@ function VideoPopoverMenuContextProvider({children}: ChildrenProps) { return items; }, [currentPlaybackSpeed, downloadAttachment, translate, updatePlaybackSpeed, isOffline, isLocalFile]); - const contextValue = useMemo(() => ({menuItems, videoPopoverMenuPlayerRef, updatePlaybackSpeed, setCurrentPlaybackSpeed}), [menuItems, videoPopoverMenuPlayerRef, updatePlaybackSpeed, setCurrentPlaybackSpeed]); + const contextValue = useMemo( + () => ({menuItems, videoPopoverMenuPlayerRef, updatePlaybackSpeed, setCurrentPlaybackSpeed}), + [menuItems, videoPopoverMenuPlayerRef, updatePlaybackSpeed, setCurrentPlaybackSpeed], + ); return {children}; } From c20119d9036d22a680771b1dcaa8a890a05f9919 Mon Sep 17 00:00:00 2001 From: smelaa Date: Mon, 29 Apr 2024 14:39:45 +0200 Subject: [PATCH 053/174] Move message from header to footer --- .../MoneyRequestPreviewContent.tsx | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 7b6c0122003c..71b735d8e744 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -98,6 +98,7 @@ function MoneyRequestPreviewContent({ const isCardTransaction = TransactionUtils.isCardTransaction(transaction); const isSettled = ReportUtils.isSettled(iouReport?.reportID); const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + const hasPendingUI = TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations)); const shouldShowRBR = hasViolations || hasFieldErrors || (!(isSettled && !isSettlementOrApprovalPartial) && !(ReportUtils.isReportApproved(iouReport) && !isSettlementOrApprovalPartial) && isOnHold); @@ -135,11 +136,7 @@ function MoneyRequestPreviewContent({ showContextMenuForReport(event, contextMenuAnchor, reportID, action, checkIfContextMenuActive); }; - const getPreviewHeaderTextAndIcon: () => {headerMessage: string; headerIcon?: React.FC} = () => { - if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) { - return {headerMessage: translate('iou.pendingMatchWithCreditCard'), headerIcon: Expensicons.Hourglass}; - } - + const getPreviewHeaderText = (): string => { let message = translate('iou.cash'); if (isDistanceRequest) { @@ -154,13 +151,13 @@ function MoneyRequestPreviewContent({ message = translate('iou.card'); if (TransactionUtils.isPending(transaction)) { message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.pending')}`; - return {headerMessage: message}; + return message; } } if (isSettled && !iouReport?.isCancelledIOU && !isPartialHold) { message += ` ${CONST.DOT_SEPARATOR} ${getSettledMessage()}`; - return {headerMessage: message}; + return message; } if (shouldShowRBR && transaction) { @@ -171,8 +168,7 @@ function MoneyRequestPreviewContent({ const isTooLong = violationsCount > 1 || violationMessage.length > 15; const hasViolationsAndFieldErrors = violationsCount > 0 && hasFieldErrors; - message = `${message} ${CONST.DOT_SEPARATOR} ${isTooLong || hasViolationsAndFieldErrors ? translate('violations.reviewRequired') : violationMessage}`; - return {headerMessage: message, headerIcon: undefined}; + return `${message} ${CONST.DOT_SEPARATOR} ${isTooLong || hasViolationsAndFieldErrors ? translate('violations.reviewRequired') : violationMessage}`; } const isMerchantMissing = TransactionUtils.isMerchantMissing(transaction); @@ -195,11 +191,9 @@ function MoneyRequestPreviewContent({ } else if (!(isSettled && !isSettlementOrApprovalPartial) && isOnHold) { message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.hold')}`; } - return {headerMessage: message}; + return message; }; - const {headerMessage, headerIcon} = getPreviewHeaderTextAndIcon(); - const getDisplayAmountText = (): string => { if (isScanning) { return translate('iou.receiptScanning'); @@ -257,15 +251,7 @@ function MoneyRequestPreviewContent({ - {headerIcon && ( - - )} - {headerMessage} + {getPreviewHeaderText()} {!isSettled && shouldShowRBR && ( {translate('iou.receiptScanInProgress')} )} + {!isScanning && hasPendingUI && ( + + + + {translate('iou.pendingMatchWithCreditCard')} + + + )} From 8f00196a080820628bbca9fd9b9d1f258625164e Mon Sep 17 00:00:00 2001 From: smelaa Date: Mon, 29 Apr 2024 14:54:02 +0200 Subject: [PATCH 054/174] Fix eslint error --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 71b735d8e744..fd5dc69f618a 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -4,7 +4,6 @@ import lodashSortBy from 'lodash/sortBy'; import React from 'react'; import {View} from 'react-native'; import type {GestureResponderEvent} from 'react-native'; -import type {SvgProps} from 'react-native-svg'; import ConfirmedRoute from '@components/ConfirmedRoute'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; From 1c65f0098a92c4ba06660d61bd9bf5f060c9eec7 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 29 Apr 2024 14:56:03 +0200 Subject: [PATCH 055/174] Remove some ts-expect-error --- src/libs/ReportActionComposeFocusManager.ts | 3 ++- .../ComposerWithSuggestions.tsx | 2 -- src/pages/home/report/ReportActionItemMessageEdit.tsx | 1 - .../{index.native.tsx => Camera.tsx} | 10 +++++----- .../NavigationAwareCamera/{index.tsx => WebCamera.tsx} | 6 +++--- .../request/step/IOURequestStepScan/index.native.tsx | 6 +++--- .../iou/request/step/IOURequestStepScan/index.tsx | 2 +- src/types/modules/react-native.d.ts | 8 ++++++++ 8 files changed, 22 insertions(+), 16 deletions(-) rename src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/{index.native.tsx => Camera.tsx} (66%) rename src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/{index.tsx => WebCamera.tsx} (75%) 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/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 8f42da5a1575..c8ccec909805 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -321,7 +321,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') { @@ -651,7 +650,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(); }); diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index fc3c92434fc4..f800beda5954 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -448,7 +448,6 @@ function ReportActionItemMessageEdit( }} onBlur={(event: NativeSyntheticEvent) => { setIsFocused(false); - // @ts-expect-error TODO: TextInputFocusEventData doesn't contain relatedTarget. const relatedTargetId = event.nativeEvent?.relatedTarget?.id; if (relatedTargetId && [messageEditInput, emojiButtonID].includes(relatedTargetId)) { return; 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/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index 5a95b78779e5..463243807dd7 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -35,7 +35,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({ @@ -415,8 +415,8 @@ function IOURequestStepScan({ Date: Mon, 29 Apr 2024 16:05:28 +0200 Subject: [PATCH 056/174] Update dom.d types --- .../request/step/IOURequestStepScan/index.tsx | 5 --- src/types/modules/dom.d.ts | 45 +++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index d3505c87b59d..b5e989442bac 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -98,7 +98,6 @@ function IOURequestStepScan({ const defaultConstraints = {facingMode: {exact: 'environment'}}; navigator.mediaDevices - // @ts-expect-error there is a type mismatch in typescipt types for MediaStreamTrack microsoft/TypeScript#39010 .getUserMedia({video: {facingMode: {exact: 'environment'}, zoom: {ideal: 1}}}) .then((stream) => { setCameraPermissionState('granted'); @@ -108,7 +107,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; @@ -152,7 +150,6 @@ function IOURequestStepScan({ } navigator.permissions .query({ - // @ts-expect-error camera does exist in PermissionName name: 'camera', }) .then((permissionState) => { @@ -398,7 +395,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}], }); }, []); @@ -407,7 +403,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/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}; From 07da9f91a2daab6c3e5082f34a546c3e2b368cfb Mon Sep 17 00:00:00 2001 From: Yauheni Date: Tue, 30 Apr 2024 14:54:14 +0200 Subject: [PATCH 057/174] Update react-native-onyx.d and react-navigation.d-Part-1 --- __mocks__/@react-navigation/native/index.ts | 1 - .../Navigators/BottomTabNavigator.tsx | 4 +- .../CustomRouter.ts | 3 -- .../FloatingActionButtonAndPopover.tsx | 3 +- src/setup/addUtilsToWindow.ts | 6 +-- src/types/modules/react-native-onyx.d.ts | 7 ++- src/types/modules/react-navigation.d.ts | 43 +++++++++++++++++++ 7 files changed, 54 insertions(+), 13 deletions(-) diff --git a/__mocks__/@react-navigation/native/index.ts b/__mocks__/@react-navigation/native/index.ts index 86772991ddac..0b7dda4621ad 100644 --- a/__mocks__/@react-navigation/native/index.ts +++ b/__mocks__/@react-navigation/native/index.ts @@ -6,5 +6,4 @@ const useIsFocused: typeof realUseIsFocused = process.env.NODE_ENV === 'test' ? const useTheme = process.env.NODE_ENV === 'test' ? realUseTheme : () => ({}); export * from '@react-navigation/core'; -export * from '@react-navigation/native'; export {useIsFocused, useTheme}; 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/createCustomStackNavigator/CustomRouter.ts b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts index 504803026994..1e475b6b1ff9 100644 --- a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts +++ b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts @@ -17,15 +17,12 @@ function insertRootRoute(state: State, routeToInsert: Naviga // It's safe to modify this state before returning in getRehydratedState. - // @ts-expect-error Updating read only property // noinspection JSConstantReassignment state.routes = [...nonModalRoutes, routeToInsert, ...modalRoutes]; // eslint-disable-line - // @ts-expect-error Updating read only property // noinspection JSConstantReassignment state.index = state.routes.length - 1; // eslint-disable-line - // @ts-expect-error Updating read only property // noinspection JSConstantReassignment state.stale = true; // eslint-disable-line } diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index d74ab3fbfb58..78f2da50d943 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -19,6 +19,7 @@ import getIconForAction from '@libs/getIconForAction'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute'; import Navigation from '@libs/Navigation/Navigation'; +import type {CentralPaneName, NavigationPartialRoute, RootStackParamList} from '@libs/Navigation/types'; import * as ReportUtils from '@libs/ReportUtils'; import * as App from '@userActions/App'; import * as IOU from '@userActions/IOU'; @@ -39,7 +40,7 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; const useIsFocused = () => { 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/setup/addUtilsToWindow.ts b/src/setup/addUtilsToWindow.ts index d2d11e138431..eb34d33364eb 100644 --- a/src/setup/addUtilsToWindow.ts +++ b/src/setup/addUtilsToWindow.ts @@ -2,6 +2,7 @@ import Onyx from 'react-native-onyx'; import * as Environment from '@libs/Environment/Environment'; import markAllPolicyReportsAsRead from '@libs/markAllPolicyReportsAsRead'; import * as Session from '@userActions/Session'; +import type {OnyxKey} from '@src/ONYXKEYS'; /** * This is used to inject development/debugging utilities into the window object on web and desktop. @@ -17,10 +18,9 @@ export default function addUtilsToWindow() { return; } - window.Onyx = Onyx; + window.Onyx = Onyx as typeof Onyx & {get: (key: OnyxKey) => 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/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-navigation.d.ts b/src/types/modules/react-navigation.d.ts index f6a47f3cdb9a..4e0efc975c89 100644 --- a/src/types/modules/react-navigation.d.ts +++ b/src/types/modules/react-navigation.d.ts @@ -1,3 +1,4 @@ +import type {ParamListBase, PartialRoute, Route} from '@react-navigation/native'; import type {RootStackParamList} from '@libs/Navigation/types'; declare global { @@ -6,3 +7,45 @@ declare global { interface RootParamList extends RootStackParamList {} } } + +declare module '@react-navigation/native' { + type PartialState = Partial> & { + stale?: true; + routes: Array>>; + }; + type NavigationRoute = Route, ParamList[RouteName]> & { + state?: NavigationState | PartialState; + }; + type NavigationState = { + /** + * Unique key for the navigation state. + */ + key: string; + /** + * Index of the currently focused route. + */ + index: number; + /** + * List of valid route names as defined in the screen components. + */ + routeNames: Array>; + /** + * Alternative entries for history. + */ + history?: unknown[]; + /** + * List of rendered routes. + */ + routes: Array>; + /** + * Custom type for the state, whether it's for tab, stack, drawer etc. + * During rehydration, the state will be discarded if type doesn't match with router type. + * It can also be used to detect the type of the navigator we're dealing with. + */ + type: string; + /** + * Whether the navigation state has been rehydrated. + */ + stale: false; + }; +} From 85ec0d677a5010edfc9574e02e15b609490e22f2 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 1 May 2024 11:31:40 +0200 Subject: [PATCH 058/174] Fix comments --- .../Navigation/AppNavigator/getPartialStateDiff.ts | 4 ++-- src/libs/Navigation/linkTo.ts | 5 ++++- src/libs/ObjectUtils.ts | 7 +++---- tests/unit/MigrationTest.ts | 14 ++++++-------- 4 files changed, 15 insertions(+), 15 deletions(-) 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 863cb102add4..55a561e22d65 100644 --- a/src/libs/Navigation/linkTo.ts +++ b/src/libs/Navigation/linkTo.ts @@ -153,7 +153,10 @@ export default function linkTo(navigation: NavigationContainerRef | undefined, + action.payload.params?.params as Record | 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/ObjectUtils.ts b/src/libs/ObjectUtils.ts index fd4c0956e877..644fe1c7596e 100644 --- a/src/libs/ObjectUtils.ts +++ b/src/libs/ObjectUtils.ts @@ -1,11 +1,10 @@ -// 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) { - const keys1 = Object.keys(obj1) as Array; - const keys2 = Object.keys(obj2) as Array; + 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/tests/unit/MigrationTest.ts b/tests/unit/MigrationTest.ts index 69e0e80e589a..9f0a3433932c 100644 --- a/tests/unit/MigrationTest.ts +++ b/tests/unit/MigrationTest.ts @@ -1,10 +1,12 @@ /* eslint-disable @typescript-eslint/naming-convention */ import Onyx from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; import Log from '@src/libs/Log'; import CheckForPreviousReportActionID from '@src/libs/migrations/CheckForPreviousReportActionID'; import KeyReportActionsDraftByReportActionID from '@src/libs/migrations/KeyReportActionsDraftByReportActionID'; import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; import type {ReportActionsDraftCollectionDataSet} from '@src/types/onyx/ReportActionsDrafts'; import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet'; import type CollectionDataSet from '@src/types/utils/CollectionDataSet'; @@ -260,13 +262,10 @@ describe('Migrations', () => { it('Should move individual draft to a draft collection of report', () => { const setQueries: ReportActionsDraftCollectionDataSet = {}; - // @ts-expect-error preset invalid value - setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`] = 'a'; - // @ts-expect-error preset invalid value - setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_2`] = 'b'; + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`] = 'a' as unknown as OnyxEntry; + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_2`] = 'b' as unknown as OnyxEntry; setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2`] = {3: 'c'}; - // @ts-expect-error preset invalid value - setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2_4`] = 'd'; + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}2_4`] = 'd' as unknown as OnyxEntry; return Onyx.multiSet(setQueries) .then(KeyReportActionsDraftByReportActionID) @@ -321,8 +320,7 @@ describe('Migrations', () => { it("Shouldn't move empty individual draft to a draft collection of report", () => { const setQueries: ReportActionsDraftCollectionDataSet = {}; - // @ts-expect-error preset empty string value - setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`] = ''; + setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1_1`] = '' as unknown as OnyxEntry; setQueries[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}1`] = {}; return Onyx.multiSet(setQueries) From 5f422b0906ece7f57373e35a93a2cd4127d844d8 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 1 May 2024 12:35:28 +0200 Subject: [PATCH 059/174] Fix issues related with react-native-animation and pdf.worker --- src/components/PDFThumbnail/index.tsx | 1 - src/hooks/useTabNavigatorFocus/index.ts | 1 - src/types/modules/pdf.worker.d.ts | 1 + src/types/modules/react-native.d.ts | 7 +++++++ 4 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 src/types/modules/pdf.worker.d.ts 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/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/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.d.ts b/src/types/modules/react-native.d.ts index e3db5db21e3b..a9ff35c97343 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -368,4 +368,11 @@ declare module 'react-native' { BootSplash: BootSplashModule; HybridAppModule: HybridAppModule; } + + namespace Animated { + interface AnimatedInterpolation extends AnimatedWithChildren { + interpolate(config: InterpolationConfigType): AnimatedInterpolation; + __getValue: () => OutputT; + } + } } From 6d01dd47bd4baa94b19ecdc8293be201222b32c5 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 1 May 2024 13:03:30 +0200 Subject: [PATCH 060/174] Fix ts-issue with this --- src/libs/Console/index.ts | 1 - tsconfig.json | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Console/index.ts b/src/libs/Console/index.ts index f03d33674bde..8546c079098e 100644 --- a/src/libs/Console/index.ts +++ b/src/libs/Console/index.ts @@ -100,7 +100,6 @@ function sanitizeConsoleInput(text: string) { function createLog(text: string) { const time = new Date(); try { - // @ts-expect-error Any code inside `sanitizedInput` that gets evaluated by `eval()` will be executed in the context of the current this value. // eslint-disable-next-line no-eval, no-invalid-this const result = eval.call(this, text); diff --git a/tsconfig.json b/tsconfig.json index 7f7b7479f44b..591e847c3da8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,6 +30,7 @@ "strict": true, "moduleResolution": "node", "resolveJsonModule": true, + "noImplicitThis": false, "allowSyntheticDefaultImports": true, "skipLibCheck": true, "incremental": true, From 579668d01c72d3d40b5913d38f23d6fd3c8a7f76 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 1 May 2024 13:33:39 +0200 Subject: [PATCH 061/174] Fix spaces --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 591e847c3da8..67acd3cb18fe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,7 @@ "strict": true, "moduleResolution": "node", "resolveJsonModule": true, - "noImplicitThis": false, + "noImplicitThis": false, "allowSyntheticDefaultImports": true, "skipLibCheck": true, "incremental": true, From 966771660bb5cb9eae4ca3f3779b64f6745190c8 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 2 May 2024 13:46:04 +0800 Subject: [PATCH 062/174] show the correct route error message --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/actions/Transaction.ts | 2 +- src/pages/home/report/ReportActionItem.tsx | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 73bc7898c6df..40a2a84984b0 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -345,7 +345,7 @@ function MoneyRequestView({ {shouldShowMapOrReceipt && ( { if (!transaction?.transactionID) { diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index a95bf9a825f0..584881990442 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -261,7 +261,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}}); } export {addStop, createInitialWaypoints, saveWaypoint, removeWaypoint, getRoute, updateWaypoints, clearError}; diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 64cc31fca993..323faf5b613a 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 type {OriginalMessageActionableMentionWhisper, OriginalMessageActionableTrackedExpenseWhisper, OriginalMessageJoinPolicyChangeLog} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; @@ -106,6 +107,9 @@ type ReportActionItemOnyxProps = { /** Transaction associated with this report, if any */ transaction: OnyxEntry; + + /** The transaction (linked with the report action) route error */ + linkedTransactionRouteError: Errors; }; type ReportActionItemProps = { @@ -184,6 +188,7 @@ function ReportActionItem({ onPress = undefined, isFirstVisibleReportAction = false, shouldUseThreadDividerLine = false, + linkedTransactionRouteError, }: ReportActionItemProps) { const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -961,7 +966,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 @@ -1033,6 +1038,10 @@ export default withOnyx({ return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`; }, }, + linkedTransactionRouteError: { + key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${(action as OnyxTypes.OriginalMessageIOU)?.originalMessage?.IOUTransactionID ?? 0}`, + selector: (transaction) => transaction?.errorFields?.route ?? null, + }, })( memo(ReportActionItem, (prevProps, nextProps) => { const prevParentReportAction = prevProps.parentReportAction; @@ -1067,6 +1076,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) ); }), From 153aa5247df550f00b1295659e21384074960744 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 2 May 2024 13:59:49 +0800 Subject: [PATCH 063/174] fix type --- src/pages/home/report/ReportActionItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 323faf5b613a..73e80c0dc591 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -109,7 +109,7 @@ type ReportActionItemOnyxProps = { transaction: OnyxEntry; /** The transaction (linked with the report action) route error */ - linkedTransactionRouteError: Errors; + linkedTransactionRouteError: OnyxEntry; }; type ReportActionItemProps = { @@ -1040,7 +1040,7 @@ export default withOnyx({ }, linkedTransactionRouteError: { key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${(action as OnyxTypes.OriginalMessageIOU)?.originalMessage?.IOUTransactionID ?? 0}`, - selector: (transaction) => transaction?.errorFields?.route ?? null, + selector: (transaction: OnyxEntry) => transaction?.errorFields?.route ?? null, }, })( memo(ReportActionItem, (prevProps, nextProps) => { From e7244936217c189237783a3382da548c8d14cca6 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 2 May 2024 18:46:46 +0700 Subject: [PATCH 064/174] fix: admin can access member details page after member leaves --- .../members/WorkspaceMemberDetailsPage.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index 7b2aa78545f2..bcabf5645f0f 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -4,6 +4,7 @@ import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import Avatar from '@components/Avatar'; +import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import Button from '@components/Button'; import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -16,6 +17,7 @@ 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'; @@ -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 ( Date: Thu, 2 May 2024 17:12:18 +0100 Subject: [PATCH 065/174] don not toggle switch on when export is set to jounal entry --- .../workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx index 9cd6e9e1653f..db310d7e6c7d 100644 --- a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx +++ b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx @@ -19,7 +19,8 @@ function QuickbooksTaxesPage({policy}: WithPolicyProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const policyID = policy?.id ?? ''; - const {syncTax, pendingFields} = policy?.connections?.quickbooksOnline?.config ?? {}; + const {syncTax, pendingFields, exportEntity} = policy?.connections?.quickbooksOnline?.config ?? {}; + const isTaxSwitchOn = !!syncTax && exportEntity !== CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY; return ( Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.QBO, CONST.QUICK_BOOKS_CONFIG.SYNC_TAX, !syncTax)} /> From d04a7a0ae2f469cce44ce3a78ab2c34f7df3c73a Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 2 May 2024 17:26:19 +0100 Subject: [PATCH 066/174] fix QBO undefined error for country check --- .../workspace/accounting/qbo/import/QuickbooksImportPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx index 2376614fcee3..3a96b00ba4ba 100644 --- a/src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx +++ b/src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx @@ -59,7 +59,7 @@ function QuickbooksImportPage({policy}: WithPolicyProps) { }, ]; - if (policy?.connections?.quickbooksOnline.data.country !== CONST.COUNTRY.US) { + if (policy?.connections?.quickbooksOnline?.data?.country !== CONST.COUNTRY.US) { sections.push({ description: translate('workspace.accounting.taxes'), action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_TAXES.getRoute(policyID)), From 0453720132896ee8f7da9dab1580aef3ae8bfff7 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 2 May 2024 17:48:01 +0100 Subject: [PATCH 067/174] add taxesJournalEntrySwitchNote for english and spanish translations --- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 89bdea70a64e..dd77a129942b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1897,6 +1897,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', diff --git a/src/languages/es.ts b/src/languages/es.ts index 64b74c6749fc..dc3f4c5ff247 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1921,6 +1921,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: + '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: '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', From a2eae458afc9e14aa3357c3b9e183c11f6557686 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 2 May 2024 17:48:27 +0100 Subject: [PATCH 068/174] add reason why taxes can't toggle --- .../workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx index db310d7e6c7d..8f74fcc63229 100644 --- a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx +++ b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx @@ -20,7 +20,9 @@ function QuickbooksTaxesPage({policy}: WithPolicyProps) { const styles = useThemeStyles(); const policyID = policy?.id ?? ''; const {syncTax, pendingFields, exportEntity} = policy?.connections?.quickbooksOnline?.config ?? {}; - const isTaxSwitchOn = !!syncTax && exportEntity !== CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY; + const isJournalExportEntity = exportEntity === CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY; + const isTaxSwitchOn = !!syncTax && !isJournalExportEntity; + return ( + {isJournalExportEntity && {translate('workspace.qbo.taxesJournalEntrySwitchNote')}} From 1ae5a2083ffa8f982204f9b38d4ccfbce628dfbc Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 2 May 2024 17:50:31 +0100 Subject: [PATCH 069/174] disable toggle when journal entry is selected --- .../workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx index 8f74fcc63229..38bb264a16a5 100644 --- a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx +++ b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx @@ -22,7 +22,7 @@ function QuickbooksTaxesPage({policy}: WithPolicyProps) { const {syncTax, pendingFields, exportEntity} = policy?.connections?.quickbooksOnline?.config ?? {}; const isJournalExportEntity = exportEntity === CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY; const isTaxSwitchOn = !!syncTax && !isJournalExportEntity; - + return ( Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.QBO, CONST.QUICK_BOOKS_CONFIG.SYNC_TAX, !syncTax)} + disabled={isJournalExportEntity} /> From 85a8447b02046df2ac5dfee2988fea32216ec25b Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 2 May 2024 18:34:22 +0100 Subject: [PATCH 070/174] translate first part taxesJournalEntrySwitchNote --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index dc3f4c5ff247..7a758ec30fcb 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1922,7 +1922,7 @@ export default { 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: - '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.', + 'Nota: QuickBooks Online no admite un campo para impuestos en las exportaciones de Anotación en el diario. Change your export preference to Vendor Bill or Check to import taxes.', 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', From 02763209cee3f28bd53fbf5bcf85d457b0ba228e Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 2 May 2024 18:36:29 +0100 Subject: [PATCH 071/174] add full translation for taxesJournalEntrySwitchNote --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 7a758ec30fcb..84b46701b137 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1922,7 +1922,7 @@ export default { 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 en las exportaciones de Anotación en el diario. Change your export preference to Vendor Bill or Check to import taxes.', + 'Nota: QuickBooks Online no admite un campo para impuestos en las exportaciones de Anotación en el diario. Cambie su 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', From f0cf4ef5fa3302123bf7f436786f358499a130e5 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Fri, 3 May 2024 00:51:28 +0100 Subject: [PATCH 072/174] fix tsc --- .../workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx index 38bb264a16a5..68074ad90d55 100644 --- a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx +++ b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx @@ -19,8 +19,8 @@ function QuickbooksTaxesPage({policy}: WithPolicyProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const policyID = policy?.id ?? ''; - const {syncTax, pendingFields, exportEntity} = policy?.connections?.quickbooksOnline?.config ?? {}; - const isJournalExportEntity = exportEntity === CONST.QUICKBOOKS_OUT_OF_POCKET_EXPENSE_ACCOUNT_TYPE.JOURNAL_ENTRY; + const {syncTax, pendingFields, reimbursableExpensesExportDestination} = policy?.connections?.quickbooksOnline?.config ?? {}; + const isJournalExportEntity = reimbursableExpensesExportDestination === CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY; const isTaxSwitchOn = !!syncTax && !isJournalExportEntity; return ( From df6a3dd8496eff7dacd559e32da4b392cfa389ae Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Fri, 3 May 2024 01:15:27 +0100 Subject: [PATCH 073/174] update error field --- .../export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseConfigurationPage.tsx index bc4741e00b48..c33c161454bb 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 ( Date: Fri, 3 May 2024 16:57:47 +0530 Subject: [PATCH 074/174] prettier diffs --- src/components/PDFView/index.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx index e63a4b5ac4c1..52f883113234 100644 --- a/src/components/PDFView/index.tsx +++ b/src/components/PDFView/index.tsx @@ -88,12 +88,7 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, max const renderLoadingIndicator = () => { if (isUsedAsChatAttachment) { return ( - + Date: Fri, 3 May 2024 13:47:16 -0700 Subject: [PATCH 075/174] Add new command UpdateManyPolicyConnectionConfigurations --- ...anyPolicyConnectionConfigurationsParams.ts | 8 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/connections/index.ts | 73 ++++++++++++++++++- 4 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/libs/API/parameters/UpdateManyPolicyConnectionConfigurationsParams.ts 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/index.ts b/src/libs/API/parameters/index.ts index 30261051c0e5..55298a0ead05 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -208,6 +208,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 c61e1278ff8a..6a26a8cc7075 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -195,6 +195,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', @@ -419,6 +420,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/actions/connections/index.ts b/src/libs/actions/connections/index.ts index 5ce806c7d4be..fcdbc4e3ac0b 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'; @@ -42,6 +42,7 @@ function removePolicyConnection(policyID: string, connectionName: PolicyConnecti }; API.write(WRITE_COMMANDS.REMOVE_POLICY_CONNECTION, parameters, {optimisticData, failureData}); } + function updatePolicyConnectionConfig( policyID: string, connectionName: TConnectionName, @@ -124,4 +125,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}; From 97729bc4677360691ccd1a615e5e2f32dc5b0054 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Fri, 3 May 2024 13:47:24 -0700 Subject: [PATCH 076/174] settingValue is not optional --- src/libs/API/parameters/UpdatePolicyConnectionConfigParams.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; }; From 2f5273f78675cb10357d193fc3c6365739a83f13 Mon Sep 17 00:00:00 2001 From: Daniel Gale-Rosen Date: Fri, 3 May 2024 17:02:07 -0400 Subject: [PATCH 077/174] allow flags of all non-Concierge whispers --- src/libs/ReportUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9c5e437a874e..7be02e76ea30 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5180,8 +5180,8 @@ function canFlagReportAction(reportAction: OnyxEntry, reportID: st 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 (reportAction?.actorAccountID !== CONST.ACCOUNT_ID.CONCIERGE) { return true; } From 05d65dfc8f8039571879dd9f68cc6eb1b51f3539 Mon Sep 17 00:00:00 2001 From: Daniel Gale-Rosen Date: Fri, 3 May 2024 17:05:09 -0400 Subject: [PATCH 078/174] prevent self-flagging whoops --- src/libs/ReportUtils.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7be02e76ea30..8678c488473d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5175,13 +5175,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 whispers that are sent by other users - if (reportAction?.actorAccountID !== CONST.ACCOUNT_ID.CONCIERGE) { + if (!isCurrentUserAction && reportAction?.actorAccountID !== CONST.ACCOUNT_ID.CONCIERGE) { return true; } From 544ef0462a69bd045bb9e96ee00d49953be14fb5 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Fri, 3 May 2024 14:23:04 -0700 Subject: [PATCH 079/174] Reset selected account when destination type is changed --- ...ooksOutOfPocketExpenseEntitySelectPage.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx index 35e06f6734e2..356a9a364ef2 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx @@ -16,18 +16,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 {QBOReimbursableExportAccountType} from '@src/types/onyx/Policy'; +import type {Account, QBOReimbursableExportAccountType} from '@src/types/onyx/Policy'; type CardListItem = ListItem & { value: QBOReimbursableExportAccountType; isShown: boolean; + accounts: Account[]; }; type CardsSection = SectionListData>; 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,6 +42,7 @@ 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, @@ -47,6 +50,7 @@ function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyConnec keyForList: CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY, isSelected: reimbursableExpensesExportDestination === CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY, isShown: !isTaxesEnabled || isLocationsEnabled, + 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,7 +69,18 @@ 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)); }, From 1206bbc36fd0b79bc1d81f7a55cc0eabec101f26 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Fri, 3 May 2024 14:23:34 -0700 Subject: [PATCH 080/174] style --- .../qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx index 356a9a364ef2..51f53744de16 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx @@ -50,7 +50,7 @@ function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyConnec keyForList: CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY, isSelected: reimbursableExpensesExportDestination === CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY, isShown: !isTaxesEnabled || isLocationsEnabled, - accounts: journalEntryAccounts || [] + accounts: journalEntryAccounts || [], }, { value: CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.VENDOR_BILL, From cfa0aa14b827ebaa52f709726ddd685caef99a3d Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Fri, 3 May 2024 22:24:36 +0100 Subject: [PATCH 081/174] Update src/languages/es.ts Co-authored-by: Rocio Perez-Cano --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 6597fb1813b5..c2e38de14e89 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1920,7 +1920,7 @@ export default { 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 en las exportaciones de Anotación en el diario. Cambie su preferencia de exportación a Factura de proveedor o Cheque para importar impuestos.', + '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', From 09a2bb1417cd4424e45b0cecec7f08b17fd19814 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Fri, 3 May 2024 23:30:04 +0100 Subject: [PATCH 082/174] Update src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx Co-authored-by: Aldo Canepa Garay <87341702+aldo-expensify@users.noreply.github.com> --- .../workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx index 68074ad90d55..c5b83d6c3074 100644 --- a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx +++ b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx @@ -45,7 +45,7 @@ function QuickbooksTaxesPage({policy}: WithPolicyProps) { Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.QBO, CONST.QUICK_BOOKS_CONFIG.SYNC_TAX, !syncTax)} disabled={isJournalExportEntity} /> From c1f4c3c42ac75330908a999f91c7c897f259f1a0 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Fri, 3 May 2024 23:32:03 +0100 Subject: [PATCH 083/174] remove isTaxSwitchOn condition --- .../workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx index c5b83d6c3074..6c8e9ccb92bb 100644 --- a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx +++ b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx @@ -21,7 +21,6 @@ function QuickbooksTaxesPage({policy}: WithPolicyProps) { const policyID = policy?.id ?? ''; const {syncTax, pendingFields, reimbursableExpensesExportDestination} = policy?.connections?.quickbooksOnline?.config ?? {}; const isJournalExportEntity = reimbursableExpensesExportDestination === CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY; - const isTaxSwitchOn = !!syncTax && !isJournalExportEntity; return ( Date: Fri, 3 May 2024 23:51:14 +0100 Subject: [PATCH 084/174] Journal entry should not show even when locations are enabled --- .../qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx index 35e06f6734e2..1a1047a7fc55 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx @@ -46,7 +46,7 @@ function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyConnec 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, }, { value: CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.VENDOR_BILL, From f886c04ac1295816f30815ac2d5e6e689d0d9ffa Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Fri, 3 May 2024 17:34:54 -0700 Subject: [PATCH 085/174] Fix non-reimbursable export config --- ...ompanyCardExpenseAccountSelectCardPage.tsx | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx index 9b7aace8ebcf..ba75217aa2b3 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,15 +56,30 @@ 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)); }, From 01afe3c20ab23d83f0cb13ebdae711e3c7ff2bc5 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Fri, 3 May 2024 17:49:03 -0700 Subject: [PATCH 086/174] Remove unused account payable page --- src/ROUTES.ts | 4 - src/SCREENS.ts | 1 - .../ModalStackNavigators/index.tsx | 2 - .../FULL_SCREEN_TO_RHP_MAPPING.ts | 1 - src/libs/Navigation/linkingConfig/config.ts | 3 - src/libs/Navigation/types.ts | 3 - ...uickbooksCompanyCardExpenseAccountPage.tsx | 43 ++++++----- ...anyCardExpenseAccountPayableSelectPage.tsx | 73 ------------------- 8 files changed, 21 insertions(+), 109 deletions(-) delete mode 100644 src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPayableSelectPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 341d058d2cd1..f08f7eea707c 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -508,10 +508,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 45c9906e8ca3..9e499f6f14bd 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -230,7 +230,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/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 77e1337d04ce..ab9261f7d0c9 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -264,8 +264,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/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index f5a45b2ece19..f3de02950149 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 618805f3c02a..73bc08f3fae3 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -293,9 +293,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 eabdb29db495..45180e7239ee 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -305,9 +305,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/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx index b3fd41d9a342..e59fc6beb303 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.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)} pendingAction={pendingFields?.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 + error={errorFields?.nonReimbursableBillDefaultVendor ? translate('common.genericErrorMessage') : undefined} + /> + )} - - 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 - error={errorFields?.nonReimbursableBillDefaultVendor ? 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); From 12455630df189a642837e597e9a9426c1f3be7ec Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Fri, 3 May 2024 17:55:17 -0700 Subject: [PATCH 087/174] Fix dependencies --- .../QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx index ba75217aa2b3..4f757eab253f 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectCardPage.tsx @@ -83,7 +83,7 @@ function QuickbooksCompanyCardExpenseAccountSelectCardPage({policy}: WithPolicyC } Navigation.goBack(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT.getRoute(policyID)); }, - [nonReimbursableExpensesExportDestination, policyID], + [nonReimbursableExpensesExportDestination, policyID, nonReimbursableExpensesAccount, nonReimbursableBillDefaultVendor], ); return ( From 2b7ca49e2b5325c8a5bfffa55c510d772ce24d71 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Fri, 3 May 2024 17:58:05 -0700 Subject: [PATCH 088/174] Fix non-reimbursable account pages --- .../qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx | 2 +- .../QuickbooksCompanyCardExpenseAccountSelectPage.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx index e59fc6beb303..00bce0939be2 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx @@ -57,7 +57,7 @@ function QuickbooksCompanyCardExpenseAccountPage({policy}: WithPolicyConnections title={nonReimbursableExpensesAccount?.name} description={translate('workspace.qbo.accountsPayable')} error={errorFields?.nonReimbursableExpensesAccount ? translate('common.genericErrorMessage') : undefined} - onPress={() => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT.getRoute(policyID))} + onPress={() => 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 /> diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx index f5c8020db929..4cd7617b4319 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) => { From f1d86f031ae38863ec22a82e95daf1adda9cb09d Mon Sep 17 00:00:00 2001 From: tienifr Date: Sat, 4 May 2024 23:09:41 +0700 Subject: [PATCH 089/174] not found page with wrapper --- src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index bcabf5645f0f..4f1cb550cf05 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -4,7 +4,6 @@ import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import Avatar from '@components/Avatar'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import Button from '@components/Button'; import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -23,6 +22,7 @@ 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'; @@ -141,7 +141,7 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM !member || (member.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && prevMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); if (shouldShowNotFoundPage) { - return ; + return ; } return ( From 9f48a57a360b7874740be8c3daaa273f0bfa82da Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 6 May 2024 14:30:55 +0700 Subject: [PATCH 090/174] fix using camel case --- tests/actions/PolicyCategoryTest.ts | 10 +++++----- tests/actions/PolicyMemberTest.ts | 8 ++++---- tests/actions/PolicyProfileTest.ts | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/actions/PolicyCategoryTest.ts b/tests/actions/PolicyCategoryTest.ts index 014281ec3690..f959bfe654dd 100644 --- a/tests/actions/PolicyCategoryTest.ts +++ b/tests/actions/PolicyCategoryTest.ts @@ -22,7 +22,7 @@ describe('actions/PolicyCategory', () => { return Onyx.clear().then(waitForBatchedUpdates); }); - describe('SetWorkspaceRequiresCategory', () => { + describe('setWorkspaceRequiresCategory', () => { it('Enable require category', () => { const fakePolicy = createRandomPolicy(0); fakePolicy.requiresCategory = false; @@ -73,7 +73,7 @@ describe('actions/PolicyCategory', () => { ); }); }); - describe('CreateWorkspaceCategories', () => { + describe('createWorkspaceCategories', () => { it('Create a new policy category', () => { const fakePolicy = createRandomPolicy(0); const fakeCategories = createRandomPolicyCategories(3); @@ -128,7 +128,7 @@ describe('actions/PolicyCategory', () => { ); }); }); - describe('RenameWorkspaceCategory', () => { + describe('renameWorkspaceCategory', () => { it('Rename category', () => { const fakePolicy = createRandomPolicy(0); const fakeCategories = createRandomPolicyCategories(3); @@ -190,7 +190,7 @@ describe('actions/PolicyCategory', () => { ); }); }); - describe('SetWorkspaceCategoriesEnabled', () => { + describe('setWorkspaceCategoriesEnabled', () => { it('Enable category', () => { const fakePolicy = createRandomPolicy(0); const fakeCategories = createRandomPolicyCategories(3); @@ -254,7 +254,7 @@ describe('actions/PolicyCategory', () => { }); }); - describe('DeleteWorkspaceCategories', () => { + describe('deleteWorkspaceCategories', () => { it('Delete category', () => { const fakePolicy = createRandomPolicy(0); const fakeCategories = createRandomPolicyCategories(3); diff --git a/tests/actions/PolicyMemberTest.ts b/tests/actions/PolicyMemberTest.ts index b70ed0c1800e..17f4d6306ee1 100644 --- a/tests/actions/PolicyMemberTest.ts +++ b/tests/actions/PolicyMemberTest.ts @@ -27,7 +27,7 @@ describe('actions/PolicyMember', () => { return Onyx.clear().then(waitForBatchedUpdates); }); - describe('AcceptJoinRequest', () => { + describe('acceptJoinRequest', () => { it('Accept user join request to a workspace', () => { const fakePolicy = createRandomPolicy(0); const fakeReport: Report = { @@ -102,7 +102,7 @@ describe('actions/PolicyMember', () => { ); }); }); - describe('UpdateWorkspaceMembersRole', () => { + describe('updateWorkspaceMembersRole', () => { it('Update member to admin role', () => { const fakeUser2 = createPersonalDetails(2); const fakePolicy: PolicyType = { @@ -163,7 +163,7 @@ describe('actions/PolicyMember', () => { ); }); }); - describe('RequestWorkspaceOwnerChange', () => { + describe('requestWorkspaceOwnerChange', () => { it('Change the workspace`s owner', () => { const fakePolicy: PolicyType = createRandomPolicy(0); const fakeEmail = 'fake@gmail.com'; @@ -219,7 +219,7 @@ describe('actions/PolicyMember', () => { ); }); }); - describe('AddBillingCardAndRequestPolicyOwnerChange', () => { + describe('addBillingCardAndRequestPolicyOwnerChange', () => { it('Add billing card and change the workspace`s owner', () => { const fakePolicy: PolicyType = createRandomPolicy(0); const fakeEmail = 'fake@gmail.com'; diff --git a/tests/actions/PolicyProfileTest.ts b/tests/actions/PolicyProfileTest.ts index f8b898d71da0..6fd17b6ce499 100644 --- a/tests/actions/PolicyProfileTest.ts +++ b/tests/actions/PolicyProfileTest.ts @@ -22,7 +22,7 @@ describe('actions/PolicyProfile', () => { return Onyx.clear().then(waitForBatchedUpdates); }); - describe('UpdateWorkspaceDescription', () => { + describe('updateWorkspaceDescription', () => { it('Update workspace`s description', () => { const fakePolicy = createRandomPolicy(0); From 7a89a590fc6c037611c84efb07478564e5fae4a2 Mon Sep 17 00:00:00 2001 From: smelaa Date: Mon, 6 May 2024 15:23:08 +0200 Subject: [PATCH 091/174] Styling fixes --- src/components/MoneyReportHeader.tsx | 5 +++-- src/components/MoneyRequestHeader.tsx | 5 +++-- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 1 + src/components/ReportActionItem/ReportPreview.tsx | 1 + 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 8067c38b873b..2cd1d91d59fb 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -252,9 +252,10 @@ function MoneyReportHeader({ title={ } description={translate('iou.pendingMatchWithCreditCardDescription')} diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 52c0da5d63c9..7a38d2403a65 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -219,9 +219,10 @@ function MoneyRequestHeader({ ) : ( ) } diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index fd5dc69f618a..790bc9b0db53 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -333,6 +333,7 @@ function MoneyRequestPreviewContent({ height={variables.iconSizeExtraSmall} width={variables.iconSizeExtraSmall} fill={theme.textSupporting} + additionalStyles={[styles.mr1]} /> {translate('iou.pendingMatchWithCreditCard')} diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 9b1e2eef50d7..ecad6aa54228 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -328,6 +328,7 @@ function ReportPreview({ height={variables.iconSizeExtraSmall} width={variables.iconSizeExtraSmall} fill={theme.textSupporting} + additionalStyles={[styles.mr1]} /> {translate('iou.pendingMatchWithCreditCard')} From c47e30bd2e72819a4817879b1338b01b3384f1d1 Mon Sep 17 00:00:00 2001 From: smelaa Date: Mon, 6 May 2024 16:58:57 +0200 Subject: [PATCH 092/174] Fixes after merging with main --- src/components/MoneyRequestHeader.tsx | 8 +++++++- src/components/MoneyRequestHeaderStatusBar.tsx | 14 +++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 7a4a3f34fbf1..b1aadd61b278 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -100,6 +100,8 @@ function MoneyRequestHeader({ setIsDeleteModalVisible(false); }, [parentReport?.reportID, parentReportAction, setIsDeleteModalVisible]); + const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); + const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); const canHoldOrUnholdRequest = !isSettled && !isApproved && !isDeletedParentAction; @@ -121,12 +123,15 @@ function MoneyRequestHeader({ if (TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction)) { return {pendingType: 'PENDING', pendingTitle: translate('iou.pending'), pendingDescription: translate('iou.transactionPendingText')}; } - if (TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction)) { + if (isScanning) { return {pendingType: 'SCANNING', pendingTitle: ReceiptScan, pendingDescription: translate('iou.receiptScanInProgressDescription')}; } if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) { return {pendingType: 'RTER', pendingTitle: Expensicons.Hourglass, pendingDescription: translate('iou.pendingMatchWithCreditCardDescription')}; } + if (isOnHold) { + return {pendingType: 'HOLD', pendingTitle: translate('iou.hold'), pendingDescription: translate('iou.expenseOnHold')}; + } return {}; }; @@ -227,6 +232,7 @@ function MoneyRequestHeader({ } description={pendingDescription} shouldShowBorderBottom + danger={isOnHold} /> )} diff --git a/src/components/MoneyRequestHeaderStatusBar.tsx b/src/components/MoneyRequestHeaderStatusBar.tsx index 40214aaaf382..1e4c19a61f03 100644 --- a/src/components/MoneyRequestHeaderStatusBar.tsx +++ b/src/components/MoneyRequestHeaderStatusBar.tsx @@ -25,11 +25,15 @@ function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom return ( - + {typeof title === 'string' ? ( + + ) : ( + {title} + )} {description} From e2eb991325186542295bd27cd1de53c764c9bb75 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 6 May 2024 21:00:56 +0200 Subject: [PATCH 093/174] Remove some @ts-expect-errors --- .github/libs/sanitizeStringForJSONParse.ts | 2 +- src/stories/Form.stories.tsx | 27 +- tests/actions/PolicyTagTest.ts | 1147 +++++++++--------- tests/unit/sanitizeStringForJSONParseTest.ts | 1 - 4 files changed, 555 insertions(+), 622 deletions(-) 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/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/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/unit/sanitizeStringForJSONParseTest.ts b/tests/unit/sanitizeStringForJSONParseTest.ts index 9d39685eb1f7..f9e647eb9a2a 100644 --- a/tests/unit/sanitizeStringForJSONParseTest.ts +++ b/tests/unit/sanitizeStringForJSONParseTest.ts @@ -30,7 +30,6 @@ const validJSONData: Array<[string, string]> = [ describe('santizeStringForJSONParse', () => { describe.each(badInputs)('willDetectBadInputs', (input) => { test('sanitizeStringForJSONParse', () => { - // @ts-expect-error TODO: Remove this once sanitizeStringForJSONParse (https://github.com/Expensify/App/issues/25360) is migrated to TypeScript. expect(() => sanitizeStringForJSONParse(input)).toThrow(); }); }); From 0a8ecc7ca207e2df93442e98426dfdf88b07da5d Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 6 May 2024 22:52:40 +0200 Subject: [PATCH 094/174] Revert react-navigation changes --- src/libs/Console/index.ts | 1 + .../CustomRouter.ts | 3 ++ .../report/ReportActionItemMessageEdit.tsx | 1 + src/types/modules/react-navigation.d.ts | 43 ------------------- tsconfig.json | 1 - 5 files changed, 5 insertions(+), 44 deletions(-) diff --git a/src/libs/Console/index.ts b/src/libs/Console/index.ts index 8546c079098e..f03d33674bde 100644 --- a/src/libs/Console/index.ts +++ b/src/libs/Console/index.ts @@ -100,6 +100,7 @@ function sanitizeConsoleInput(text: string) { function createLog(text: string) { const time = new Date(); try { + // @ts-expect-error Any code inside `sanitizedInput` that gets evaluated by `eval()` will be executed in the context of the current this value. // eslint-disable-next-line no-eval, no-invalid-this const result = eval.call(this, text); diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts index 1e475b6b1ff9..504803026994 100644 --- a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts +++ b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts @@ -17,12 +17,15 @@ function insertRootRoute(state: State, routeToInsert: Naviga // It's safe to modify this state before returning in getRehydratedState. + // @ts-expect-error Updating read only property // noinspection JSConstantReassignment state.routes = [...nonModalRoutes, routeToInsert, ...modalRoutes]; // eslint-disable-line + // @ts-expect-error Updating read only property // noinspection JSConstantReassignment state.index = state.routes.length - 1; // eslint-disable-line + // @ts-expect-error Updating read only property // noinspection JSConstantReassignment state.stale = true; // eslint-disable-line } diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 8b629877e84d..d94807771566 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -449,6 +449,7 @@ function ReportActionItemMessageEdit( onBlur={(event: NativeSyntheticEvent) => { setIsFocused(false); 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/types/modules/react-navigation.d.ts b/src/types/modules/react-navigation.d.ts index 4e0efc975c89..f6a47f3cdb9a 100644 --- a/src/types/modules/react-navigation.d.ts +++ b/src/types/modules/react-navigation.d.ts @@ -1,4 +1,3 @@ -import type {ParamListBase, PartialRoute, Route} from '@react-navigation/native'; import type {RootStackParamList} from '@libs/Navigation/types'; declare global { @@ -7,45 +6,3 @@ declare global { interface RootParamList extends RootStackParamList {} } } - -declare module '@react-navigation/native' { - type PartialState = Partial> & { - stale?: true; - routes: Array>>; - }; - type NavigationRoute = Route, ParamList[RouteName]> & { - state?: NavigationState | PartialState; - }; - type NavigationState = { - /** - * Unique key for the navigation state. - */ - key: string; - /** - * Index of the currently focused route. - */ - index: number; - /** - * List of valid route names as defined in the screen components. - */ - routeNames: Array>; - /** - * Alternative entries for history. - */ - history?: unknown[]; - /** - * List of rendered routes. - */ - routes: Array>; - /** - * Custom type for the state, whether it's for tab, stack, drawer etc. - * During rehydration, the state will be discarded if type doesn't match with router type. - * It can also be used to detect the type of the navigator we're dealing with. - */ - type: string; - /** - * Whether the navigation state has been rehydrated. - */ - stale: false; - }; -} diff --git a/tsconfig.json b/tsconfig.json index 67acd3cb18fe..7f7b7479f44b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,6 @@ "strict": true, "moduleResolution": "node", "resolveJsonModule": true, - "noImplicitThis": false, "allowSyntheticDefaultImports": true, "skipLibCheck": true, "incremental": true, From 90249acfe832e9de8d0f6ccd728ea7012dfda248 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 6 May 2024 23:04:30 +0200 Subject: [PATCH 095/174] Fix types for onSubmitComment inside ReportFooter --- .../home/report/ReportActionCompose/ReportActionCompose.tsx | 2 +- src/pages/home/report/ReportFooter.tsx | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index b356cbeb3a42..7226f04812b1 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -75,7 +75,7 @@ type ReportActionComposeProps = ReportActionComposeOnyxProps & WithCurrentUserPersonalDetailsProps & 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; diff --git a/src/pages/home/report/ReportFooter.tsx b/src/pages/home/report/ReportFooter.tsx index 01a985efdadb..4f890bafb6e4 100644 --- a/src/pages/home/report/ReportFooter.tsx +++ b/src/pages/home/report/ReportFooter.tsx @@ -128,11 +128,7 @@ function ReportFooter({ ); const onSubmitComment = useCallback( - (text: string | undefined) => { - if (text === undefined) { - return; - } - + (text: string) => { const isTaskCreated = handleCreateTask(text); if (isTaskCreated) { return; From a96ebf522b2a4f6b0e7bb989cf1c9f6528566787 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Mon, 6 May 2024 23:19:37 +0200 Subject: [PATCH 096/174] Fix types for TabSelector --- src/components/TabSelector/TabSelector.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/TabSelector/TabSelector.tsx b/src/components/TabSelector/TabSelector.tsx index 9fe1665f6b19..caddc201d1a6 100644 --- a/src/components/TabSelector/TabSelector.tsx +++ b/src/components/TabSelector/TabSelector.tsx @@ -38,7 +38,7 @@ function getIconAndTitle(route: string, translate: LocaleContextProps['translate } } -function getOpacity(position: Animated.AnimatedInterpolation, routesLength: number, tabIndex: number, active: boolean, affectedTabs: number[]) { +function getOpacity(position: Animated.AnimatedInterpolation, routesLength: number, tabIndex: number, active: boolean, affectedTabs: number[]) { const activeValue = active ? 1 : 0; const inactiveValue = active ? 0 : 1; @@ -118,9 +118,9 @@ function TabSelector({state, navigation, onTabPress = () => {}, position}: TabSe icon={icon} title={title} onPress={onPress} - activeOpacity={activeOpacity} - inactiveOpacity={inactiveOpacity} - backgroundColor={backgroundColor} + activeOpacity={activeOpacity as number} + inactiveOpacity={inactiveOpacity as number} + backgroundColor={backgroundColor as string} isActive={isActive} /> ); From 81d200e182074400de2d6462ba45bf04cc464636 Mon Sep 17 00:00:00 2001 From: smelaa Date: Tue, 7 May 2024 10:11:34 +0200 Subject: [PATCH 097/174] Styling --- src/components/MoneyReportHeader.tsx | 2 +- src/components/MoneyRequestHeader.tsx | 14 +++++++++----- src/components/MoneyRequestHeaderStatusBar.tsx | 11 +++++++---- .../MoneyRequestPreviewContent.tsx | 1 - src/components/ReportActionItem/ReportPreview.tsx | 1 - 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 6d7ec7c1d4ce..f58b571cb816 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -255,11 +255,11 @@ function MoneyReportHeader({ height={variables.iconSizeSmall} width={variables.iconSizeSmall} fill={theme.textSupporting} - additionalStyles={[styles.mr2]} /> } description={translate('iou.pendingMatchWithCreditCardDescription')} shouldShowBorderBottom + additionalViewStyle={[styles.mr2]} /> )} diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index b1aadd61b278..d19a4e4ac8fd 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -129,9 +129,6 @@ function MoneyRequestHeader({ if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) { return {pendingType: 'RTER', pendingTitle: Expensicons.Hourglass, pendingDescription: translate('iou.pendingMatchWithCreditCardDescription')}; } - if (isOnHold) { - return {pendingType: 'HOLD', pendingTitle: translate('iou.hold'), pendingDescription: translate('iou.expenseOnHold')}; - } return {}; }; @@ -226,13 +223,20 @@ function MoneyRequestHeader({ height={variables.iconSizeSmall} width={variables.iconSizeSmall} fill={theme.textSupporting} - additionalStyles={[styles.mr2]} /> ) } description={pendingDescription} + shouldShowBorderBottom={!isOnHold} + additionalViewStyle={[styles.mr2]} + /> + )} + {isOnHold && ( + )} diff --git a/src/components/MoneyRequestHeaderStatusBar.tsx b/src/components/MoneyRequestHeaderStatusBar.tsx index 1e4c19a61f03..16152fa53123 100644 --- a/src/components/MoneyRequestHeaderStatusBar.tsx +++ b/src/components/MoneyRequestHeaderStatusBar.tsx @@ -1,6 +1,6 @@ import type {ReactNode} from 'react'; import React from 'react'; -import {View} from 'react-native'; +import {StyleProp, View, ViewStyle} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import Badge from './Badge'; import Text from './Text'; @@ -17,14 +17,17 @@ type MoneyRequestHeaderStatusBarProps = { /** Whether we should use the danger theme color */ danger?: boolean; + + /** Additional style for the banner */ + additionalViewStyle?: StyleProp; }; -function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom, danger = false}: MoneyRequestHeaderStatusBarProps) { +function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom, danger = false, additionalViewStyle}: MoneyRequestHeaderStatusBarProps) { const styles = useThemeStyles(); const borderBottomStyle = shouldShowBorderBottom ? styles.borderBottom : {}; return ( - + {typeof title === 'string' ? ( ) : ( - {title} + {title} )} diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 4a0de338392f..118f1b2aeb9c 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -334,7 +334,6 @@ function MoneyRequestPreviewContent({ height={variables.iconSizeExtraSmall} width={variables.iconSizeExtraSmall} fill={theme.textSupporting} - additionalStyles={[styles.mr1]} /> {translate('iou.pendingMatchWithCreditCard')} diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 2d9b6d8b362c..90c22f3863f6 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -320,7 +320,6 @@ function ReportPreview({ height={variables.iconSizeExtraSmall} width={variables.iconSizeExtraSmall} fill={theme.textSupporting} - additionalStyles={[styles.mr1]} /> {translate('iou.pendingMatchWithCreditCard')} From 8d72c93cab4a5f3f90ad81fa48740b900d44ce82 Mon Sep 17 00:00:00 2001 From: smelaa Date: Tue, 7 May 2024 10:40:47 +0200 Subject: [PATCH 098/174] Eslint --- src/components/MoneyRequestHeaderStatusBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestHeaderStatusBar.tsx b/src/components/MoneyRequestHeaderStatusBar.tsx index b295dfe4783e..31d7cbf22733 100644 --- a/src/components/MoneyRequestHeaderStatusBar.tsx +++ b/src/components/MoneyRequestHeaderStatusBar.tsx @@ -39,7 +39,7 @@ function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom styles.headerStatusBarContainer, ]} > - + {typeof title === 'string' ? ( Date: Tue, 7 May 2024 15:43:09 +0700 Subject: [PATCH 099/174] fix using async await syntax --- tests/actions/PolicyCategoryTest.ts | 417 ++++++++++++---------------- tests/actions/PolicyMemberTest.ts | 339 ++++++++++------------ tests/actions/PolicyProfileTest.ts | 72 +++-- 3 files changed, 357 insertions(+), 471 deletions(-) diff --git a/tests/actions/PolicyCategoryTest.ts b/tests/actions/PolicyCategoryTest.ts index f959bfe654dd..2817a1661db4 100644 --- a/tests/actions/PolicyCategoryTest.ts +++ b/tests/actions/PolicyCategoryTest.ts @@ -23,175 +23,144 @@ describe('actions/PolicyCategory', () => { }); describe('setWorkspaceRequiresCategory', () => { - it('Enable require category', () => { + 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(); - return ( - Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy) - .then(() => { - Policy.setWorkspaceRequiresCategory(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); - // 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. - .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?.pendingFields?.requiresCategory).toBeFalsy(); - resolve(); - }, - }); - }), - ) - ); + 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', () => { + 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) - .then(() => { - Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories); - }) - .then(() => { - Policy.createPolicyCategory(fakePolicy.id, newCategoryName); - return waitForBatchedUpdates(); - }) - .then( - () => - 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. - .then(fetch.resume) - .then(waitForBatchedUpdates) - .then( - () => - 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(); - }, - }); - }), - ); + 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', () => { + 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(); - return ( - Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy) - .then(() => { - Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories); - }) - .then(() => { - Policy.renamePolicyCategory(fakePolicy.id, { - oldName: oldCategoryName, - newName: newCategoryName, - }); - return waitForBatchedUpdates(); - }) - .then( - () => - 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. - .then(fetch.resume) - .then(waitForBatchedUpdates) - .then( - () => - 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(); - }, - }); - }), - ) - ); + 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', () => { + it('Enable category', async () => { const fakePolicy = createRandomPolicy(0); const fakeCategories = createRandomPolicyCategories(3); const categoryNameToUpdate = Object.keys(fakeCategories)[0]; @@ -203,108 +172,84 @@ describe('actions/PolicyCategory', () => { }; // @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_CATEGORIES}${fakePolicy.id}`, fakeCategories); - }) - .then(() => { - Policy.setWorkspaceCategoryEnabled(fakePolicy.id, categoriesToUpdate); - return waitForBatchedUpdates(); - }) - .then( - () => - 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. - .then(fetch.resume) - .then(waitForBatchedUpdates) - .then( - () => - 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(); - }, - }); - }), - ) - ); + 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', () => { + 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(); - return ( - Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy) - .then(() => { - Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories); - }) - .then(() => { - Policy.deleteWorkspaceCategories(fakePolicy.id, categoriesToDelete); - return waitForBatchedUpdates(); - }) - .then( - () => - 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. - .then(fetch.resume) - .then(waitForBatchedUpdates) - .then( - () => - 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(); - }, - }); - }), - ) - ); + 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 index 17f4d6306ee1..30948c38c425 100644 --- a/tests/actions/PolicyMemberTest.ts +++ b/tests/actions/PolicyMemberTest.ts @@ -28,7 +28,7 @@ describe('actions/PolicyMember', () => { }); describe('acceptJoinRequest', () => { - it('Accept user join request to a workspace', () => { + it('Accept user join request to a workspace', async () => { const fakePolicy = createRandomPolicy(0); const fakeReport: Report = { ...createRandomReport(0), @@ -41,69 +41,55 @@ describe('actions/PolicyMember', () => { // @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.REPORT}${fakeReport.reportID}`, fakeReport); - }) - .then(() => { - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fakeReport.reportID}`, { - [fakeReportAction.reportActionID]: fakeReportAction, - }); - }) - .then(() => { - Policy.acceptJoinRequest(fakeReport.reportID, fakeReportAction); - return waitForBatchedUpdates(); - }) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fakeReport.reportID}`, - waitForCollectionCallback: false, - callback: (reportActions) => { - Onyx.disconnect(connectionID); + 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]; + 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. - .then(fetch.resume) - .then(waitForBatchedUpdates) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fakeReport.reportID}`, - waitForCollectionCallback: false, - callback: (reportActions) => { - Onyx.disconnect(connectionID); + 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]; + const reportAction = reportActions?.[fakeReportAction.reportActionID]; - if (!isEmptyObject(reportAction)) { - expect(reportAction?.pendingAction).toBeFalsy(); - } - resolve(); - }, - }); - }), - ) - ); + if (!isEmptyObject(reportAction)) { + expect(reportAction?.pendingAction).toBeFalsy(); + } + resolve(); + }, + }); + }); }); }); describe('updateWorkspaceMembersRole', () => { - it('Update member to admin role', () => { + it('Update member to admin role', async () => { const fakeUser2 = createPersonalDetails(2); const fakePolicy: PolicyType = { ...createRandomPolicy(0), @@ -117,110 +103,87 @@ describe('actions/PolicyMember', () => { // @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.PERSONAL_DETAILS_LIST}`, {[fakeUser2.accountID]: fakeUser2}); - }) - .then(() => { - Policy.updateWorkspaceMembersRole(fakePolicy.id, [fakeUser2.accountID], CONST.POLICY.ROLE.ADMIN); - return waitForBatchedUpdates(); - }) - .then( - () => - 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); + 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. - .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 employee = policy?.employeeList?.[fakeUser2?.login ?? '']; - expect(employee?.pendingAction).toBeFalsy(); - resolve(); - }, - }); - }), - ) - ); + 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', () => { + 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(); - return ( - Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy) - .then(() => { - Onyx.merge(ONYXKEYS.SESSION, {email: fakeEmail, accountID: fakeAccountID}); - }) - .then(() => { - Policy.requestWorkspaceOwnerChange(fakePolicy.id); - 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?.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. - .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?.isLoading).toBeFalsy(); - expect(policy?.isChangeOwnerSuccessful).toBeTruthy(); - expect(policy?.isChangeOwnerFailed)?.toBeFalsy(); - resolve(); - }, - }); - }), - ) - ); + 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(); + 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', () => { + it('Add billing card and change the workspace`s owner', async () => { const fakePolicy: PolicyType = createRandomPolicy(0); const fakeEmail = 'fake@gmail.com'; const fakeCard = { @@ -236,52 +199,40 @@ describe('actions/PolicyMember', () => { // @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.merge(ONYXKEYS.SESSION, {email: fakeEmail, accountID: fakeAccountID}); - }) - .then(() => { - Policy.addBillingCardAndRequestPolicyOwnerChange(fakePolicy.id, fakeCard); - 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?.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. - .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?.isLoading).toBeFalsy(); - expect(policy?.isChangeOwnerSuccessful).toBeTruthy(); - expect(policy?.isChangeOwnerFailed)?.toBeFalsy(); - resolve(); - }, - }); - }), - ) - ); + 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 index 6fd17b6ce499..21ee34568100 100644 --- a/tests/actions/PolicyProfileTest.ts +++ b/tests/actions/PolicyProfileTest.ts @@ -23,7 +23,7 @@ describe('actions/PolicyProfile', () => { }); describe('updateWorkspaceDescription', () => { - it('Update workspace`s description', () => { + it('Update workspace`s description', async () => { const fakePolicy = createRandomPolicy(0); const oldDescription = fakePolicy.description ?? ''; @@ -31,48 +31,38 @@ describe('actions/PolicyProfile', () => { 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(); - return ( - Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy) - .then(() => { - Policy.updateWorkspaceDescription(fakePolicy.id, newDescription, oldDescription); - return waitForBatchedUpdates(); - }) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, - waitForCollectionCallback: false, - callback: (policy) => { - Onyx.disconnect(connectionID); + 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. - .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?.description).toBeFalsy(); + 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(); - }, - }); - }), - ) - ); + resolve(); + }, + }); + }); }); }); }); From 0105dd6fe45c4bb39e52538f95c45f4a66ca7438 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 7 May 2024 15:53:24 +0700 Subject: [PATCH 100/174] fix lint --- tests/actions/PolicyMemberTest.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/actions/PolicyMemberTest.ts b/tests/actions/PolicyMemberTest.ts index 30948c38c425..4ca8d04568eb 100644 --- a/tests/actions/PolicyMemberTest.ts +++ b/tests/actions/PolicyMemberTest.ts @@ -163,9 +163,9 @@ describe('actions/PolicyMember', () => { resolve(); }, }); - }), - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. - await fetch.resume(); + }); + // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. + await fetch.resume(); await waitForBatchedUpdates(); new Promise((resolve) => { const connectionID = Onyx.connect({ From 8463360f7ef986dc3e3541cf87bf2a2cac47db85 Mon Sep 17 00:00:00 2001 From: smelaa Date: Tue, 7 May 2024 10:54:12 +0200 Subject: [PATCH 101/174] Eslint --- src/components/MoneyRequestHeaderStatusBar.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyRequestHeaderStatusBar.tsx b/src/components/MoneyRequestHeaderStatusBar.tsx index 31d7cbf22733..7a8259e92863 100644 --- a/src/components/MoneyRequestHeaderStatusBar.tsx +++ b/src/components/MoneyRequestHeaderStatusBar.tsx @@ -1,6 +1,7 @@ import type {ReactNode} from 'react'; import React from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import Badge from './Badge'; import Text from './Text'; From 2fbdb39dfb9069ca3de4cec57008be273ae0bf77 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 7 May 2024 16:03:27 +0700 Subject: [PATCH 102/174] fix lint --- tests/actions/PolicyMemberTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/actions/PolicyMemberTest.ts b/tests/actions/PolicyMemberTest.ts index 4ca8d04568eb..8d982d4a1892 100644 --- a/tests/actions/PolicyMemberTest.ts +++ b/tests/actions/PolicyMemberTest.ts @@ -167,7 +167,7 @@ describe('actions/PolicyMember', () => { // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. await fetch.resume(); await waitForBatchedUpdates(); - new Promise((resolve) => { + await new Promise((resolve) => { const connectionID = Onyx.connect({ key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, waitForCollectionCallback: false, From 9556b714e4284600bb50e4d1e0c5014072dc5b0c Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 7 May 2024 13:32:10 +0200 Subject: [PATCH 103/174] implement excluding styling rules for useMarkdownStyle --- src/hooks/useMarkdownStyle.ts | 108 +++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 46 deletions(-) diff --git a/src/hooks/useMarkdownStyle.ts b/src/hooks/useMarkdownStyle.ts index b1f430e232e4..d36b2ed9076b 100644 --- a/src/hooks/useMarkdownStyle.ts +++ b/src/hooks/useMarkdownStyle.ts @@ -5,56 +5,72 @@ 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( - () => ({ - syntax: { - color: theme.syntax, - }, - link: { - color: theme.link, - }, - h1: { - fontSize: variables.fontSizeLarge, - }, - emoji: { - fontSize: emojiFontSize, - }, - blockquote: { - borderColor: theme.border, - borderWidth: 4, - marginLeft: 0, - paddingLeft: 6, - }, - code: { - fontFamily: FontUtils.fontFamily.platform.MONOSPACE, - fontSize: 13, // TODO: should be 15 if inside h1, see StyleUtils.getCodeFontSize - color: theme.text, - backgroundColor: 'transparent', - }, - pre: { - fontFamily: FontUtils.fontFamily.platform.MONOSPACE, - fontSize: 13, - color: theme.text, - backgroundColor: 'transparent', - }, - mentionHere: { - color: theme.ourMentionText, - backgroundColor: theme.ourMentionBG, - }, - mentionUser: { - color: theme.mentionText, - backgroundColor: theme.mentionBG, - }, - mentionReport: { - color: theme.mentionText, - backgroundColor: theme.mentionBG, - }, - }), - [theme, emojiFontSize], + () => { + const styling = { + syntax: { + color: theme.syntax, + }, + link: { + color: theme.link, + }, + h1: { + fontSize: variables.fontSizeLarge, + }, + emoji: { + fontSize: emojiFontSize, + }, + blockquote: { + borderColor: theme.border, + borderWidth: 4, + marginLeft: 0, + paddingLeft: 6, + }, + code: { + fontFamily: FontUtils.fontFamily.platform.MONOSPACE, + fontSize: 13, // TODO: should be 15 if inside h1, see StyleUtils.getCodeFontSize + color: theme.text, + backgroundColor: 'transparent', + }, + pre: { + fontFamily: FontUtils.fontFamily.platform.MONOSPACE, + fontSize: 13, + color: theme.text, + backgroundColor: 'transparent', + }, + mentionHere: { + color: theme.ourMentionText, + backgroundColor: theme.ourMentionBG, + }, + mentionUser: { + color: theme.mentionText, + backgroundColor: theme.mentionBG, + }, + mentionReport: { + color: theme.mentionText, + backgroundColor: theme.mentionBG, + }, + }; + + if (excludeStyles.length) { + excludeStyles.forEach((key) => { + const style: Record = styling[key]; + if (style) { + Object.keys(style).forEach((styleKey) => { + style[styleKey] = undefined; + }); + } + }); + } + + return styling; + + }, + [theme, emojiFontSize, excludeStyles], ); return markdownStyle; From 84290e42da3794c621b792725222a8d7c16664b6 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 7 May 2024 13:33:59 +0200 Subject: [PATCH 104/174] exclude reportMention style for chats not from group policy --- src/components/Composer/index.native.tsx | 3 ++- src/components/Composer/index.tsx | 3 ++- src/components/Composer/types.ts | 3 +++ .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 1 + 4 files changed, 8 insertions(+), 2 deletions(-) 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/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 469a7300a84f..142becc7231c 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -753,6 +753,7 @@ function ComposerWithSuggestions( onLayout={onLayout} onScroll={hideSuggestionMenu} shouldContainScroll={Browser.isMobileSafari()} + isGroupPolicyReport={isGroupPolicyReport} /> From b1a6caf52b73a0904b398cb3dcbfec4bb2c1b3ff Mon Sep 17 00:00:00 2001 From: kmichel Date: Wed, 8 May 2024 00:19:34 -0700 Subject: [PATCH 105/174] fix issue --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 2ce33cb442de..6d2577ef5ff9 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -95,12 +95,12 @@ 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)) { From c4cdb4bef11b8bfad2c47b00b5861f77285d0fdb Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 8 May 2024 10:32:41 +0200 Subject: [PATCH 106/174] Addressing review comments --- src/components/MoneyReportHeader.tsx | 10 +++--- src/components/MoneyRequestHeader.tsx | 50 ++++++++++++++++++--------- src/libs/TransactionUtils.ts | 14 +++++--- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 68fb0f12733b..1880184aff01 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -105,7 +105,7 @@ function MoneyReportHeader({ const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); const transactionIDs = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID).map((transaction) => transaction.transactionID); - const haveAllPendingRTERViolation = TransactionUtils.haveAllPendingRTERViolation(transactionIDs); + const hasAllPendingRTERViolation = TransactionUtils.hasAllPendingRTERViolation(transactionIDs); const cancelPayment = useCallback(() => { if (!chatReport) { @@ -121,9 +121,9 @@ function MoneyReportHeader({ const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(moneyRequestReport); - const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton) && !haveAllPendingRTERViolation; + const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton) && !hasAllPendingRTERViolation; - const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !haveAllPendingRTERViolation; + const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !hasAllPendingRTERViolation; 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; @@ -212,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) && !haveAllPendingRTERViolation} + shouldShowBorderBottom={!(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout) && !hasAllPendingRTERViolation} shouldShowThreeDotsButton threeDotsMenuItems={threeDotsMenuItems} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)} @@ -250,7 +250,7 @@ function MoneyReportHeader({ )} - {haveAllPendingRTERViolation && ( + {hasAllPendingRTERViolation && ( { if (TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction)) { - return {pendingType: 'PENDING', pendingTitle: translate('iou.pending'), pendingDescription: translate('iou.transactionPendingText')}; + return 'PENDING'; } if (isScanning) { - return {pendingType: 'SCANNING', pendingTitle: ReceiptScan, pendingDescription: translate('iou.receiptScanInProgressDescription')}; + return 'SCANNING'; } if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) { - return {pendingType: 'RTER', pendingTitle: Expensicons.Hourglass, pendingDescription: translate('iou.pendingMatchWithCreditCardDescription')}; + return 'RTER'; } - return {}; }; - const {pendingType, pendingTitle, pendingDescription} = getPendingType(); + const pendingType = getPendingType(); useEffect(() => { if (canDeleteRequest) { @@ -217,21 +216,38 @@ function MoneyRequestHeader({ shouldShowBackButton={shouldUseNarrowLayout} onBackButtonPress={onBackButtonPress} /> - {pendingType && ( + {pendingType === 'PENDING' && ( + + )} + {pendingType === 'SCANNING' && ( + + } + description={translate('iou.receiptScanInProgressDescription')} + shouldShowBorderBottom={!isOnHold} + /> + )} + {pendingType === 'RTER' && ( - ) + } - description={pendingDescription} + description={translate('iou.pendingMatchWithCreditCardDescription')} shouldShowBorderBottom={!isOnHold} additionalViewStyle={[styles.mr2]} /> diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index dba6ae5178dd..8edfad9b456f 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -508,6 +508,9 @@ 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; } @@ -522,10 +525,13 @@ function hasPendingRTERViolation(transactionViolations?: TransactionViolations | } /** - * Check if there is pending rter violation in transactionViolations. + * Check if there is pending rter violation in all transactionViolations with given transactionIDs. */ -function haveAllPendingRTERViolation(transactionIds: string[]): boolean { - const transactionsWithRTERViolations = transactionIds.map((transactionId) => hasPendingRTERViolation(getTransactionViolations(transactionId, allTransactionViolations))); +function hasAllPendingRTERViolation(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); } @@ -750,7 +756,7 @@ export { areRequiredFieldsEmpty, hasMissingSmartscanFields, hasPendingRTERViolation, - haveAllPendingRTERViolation, + hasAllPendingRTERViolation, hasPendingUI, getWaypointIndex, waypointHasValidAddress, From 03fb6ee2cecba73291e14d5acfc4fd059a0dd811 Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 8 May 2024 10:34:27 +0200 Subject: [PATCH 107/174] Remove mocking data --- src/pages/home/report/ReportActionsList.tsx | 23 --------------------- 1 file changed, 23 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index c2f18e04c52e..5d9198c8752b 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -190,29 +190,6 @@ function ReportActionsList({ const hasFooterRendered = useRef(false); const lastVisibleActionCreatedRef = useRef(report.lastVisibleActionCreated); const lastReadTimeRef = useRef(report.lastReadTime); - // Single MoneyRequest - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}6196867412357270168`, {cardID: 1, merchant: 'single MoneyRequest test'}); - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}6196867412357270168`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); - // Multiple MoneyRequests test - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}1304796714991934480`, {cardID: 1, merchant: 'multiple MoneyRequests test 1'}); - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}1304796714991934480`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}6286508495235425496`, {cardID: 1, merchant: 'multiple MoneyRequests test 2'}); - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}6286508495235425496`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}2150079702326626524`, {cardID: 1, merchant: 'multiple MoneyRequests test 3'}); - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}2150079702326626524`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); - // One-Expense Chat test - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}2438117170083649063`, {cardID: 1, merchant: 'One-Expense Chat test'}); - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}2438117170083649063`, [{type: 'test', name: CONST.VIOLATIONS.RTER, data: {pendingPattern: true}}]); const sortedVisibleReportActions = useMemo( () => From 5560114f1aa5a02b3a5d2ba167f224c3cb412d12 Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 8 May 2024 10:45:50 +0200 Subject: [PATCH 108/174] Eslint --- src/pages/home/report/ReportActionsList.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 5d9198c8752b..c826e15985f5 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -6,7 +6,6 @@ import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 're import {DeviceEventEmitter, InteractionManager} from 'react-native'; import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import Onyx from 'react-native-onyx'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import InvertedFlatList from '@components/InvertedFlatList'; import {AUTOSCROLL_TO_TOP_THRESHOLD} from '@components/InvertedFlatList/BaseInvertedFlatList'; @@ -27,7 +26,6 @@ import type {CentralPaneNavigatorParamList} from '@navigation/types'; import variables from '@styles/variables'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; From 7074765f0bf876595a41770fc7b3cb3268ca3d8e Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 8 May 2024 15:59:24 +0700 Subject: [PATCH 109/174] fix: no search bar in category list --- src/components/CategoryPicker.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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]; From 70e93db6d18e21de8d7780f25ab9b0876b737f05 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 8 May 2024 11:30:56 +0200 Subject: [PATCH 110/174] Fix comments --- src/components/TabSelector/TabSelector.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/TabSelector/TabSelector.tsx b/src/components/TabSelector/TabSelector.tsx index caddc201d1a6..882d99f38e3d 100644 --- a/src/components/TabSelector/TabSelector.tsx +++ b/src/components/TabSelector/TabSelector.tsx @@ -38,7 +38,7 @@ function getIconAndTitle(route: string, translate: LocaleContextProps['translate } } -function getOpacity(position: Animated.AnimatedInterpolation, routesLength: number, tabIndex: number, active: boolean, affectedTabs: number[]) { +function getOpacity(position: Animated.AnimatedInterpolation, routesLength: number, tabIndex: number, active: boolean, affectedTabs: number[]) { const activeValue = active ? 1 : 0; const inactiveValue = active ? 0 : 1; @@ -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; }, @@ -118,9 +118,9 @@ function TabSelector({state, navigation, onTabPress = () => {}, position}: TabSe icon={icon} title={title} onPress={onPress} - activeOpacity={activeOpacity as number} - inactiveOpacity={inactiveOpacity as number} - backgroundColor={backgroundColor as string} + activeOpacity={activeOpacity} + inactiveOpacity={inactiveOpacity} + backgroundColor={backgroundColor} isActive={isActive} /> ); From 2455f960b381905e88a817388fcc9359752296e0 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 8 May 2024 12:00:13 +0200 Subject: [PATCH 111/174] Fix PolicyTaxTests --- tests/actions/PolicyTaxTest.ts | 790 +++++++++++++++------------------ 1 file changed, 366 insertions(+), 424 deletions(-) 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((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(); + }, + }); + }), + ); }); it('Remove the optimistic tax if the API returns an error', () => { @@ -371,8 +348,7 @@ 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( @@ -394,10 +370,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( @@ -420,55 +394,50 @@ describe('actions/PolicyTax', () => { describe('SetPolicyTaxesEnabled', () => { it('Disable policy`s taxes', () => { const disableTaxID = '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?.(); setPolicyTaxesEnabled(fakePolicy.id, [disableTaxID], false); - return ( - waitForBatchedUpdates() - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, - waitForCollectionCallback: false, - callback: (policy) => { - Onyx.disconnect(connectionID); - const disabledTax = policy?.taxRates?.taxes?.[disableTaxID]; - expect(disabledTax?.isDisabled).toBeTruthy(); - expect(disabledTax?.pendingFields?.isDisabled).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); - expect(disabledTax?.errorFields?.isDisabled).toBeFalsy(); + return waitForBatchedUpdates() + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, + waitForCollectionCallback: false, + callback: (policy) => { + Onyx.disconnect(connectionID); + const disabledTax = policy?.taxRates?.taxes?.[disableTaxID]; + expect(disabledTax?.isDisabled).toBeTruthy(); + expect(disabledTax?.pendingFields?.isDisabled).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); + expect(disabledTax?.errorFields?.isDisabled).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); - const disabledTax = policy?.taxRates?.taxes?.[disableTaxID]; - expect(disabledTax?.errorFields?.isDisabled).toBeFalsy(); - expect(disabledTax?.pendingFields?.isDisabled).toBeFalsy(); - resolve(); - }, - }); - }), - ) - ); + 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); + const disabledTax = policy?.taxRates?.taxes?.[disableTaxID]; + expect(disabledTax?.errorFields?.isDisabled).toBeFalsy(); + expect(disabledTax?.pendingFields?.isDisabled).toBeFalsy(); + resolve(); + }, + }); + }), + ); }); it('Disable policy`s taxes but API returns an error, then enable policy`s taxes again', () => { const disableTaxID = '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?.(); setPolicyTaxesEnabled(fakePolicy.id, [disableTaxID], false); const originalTaxes = {...fakePolicy?.taxRates?.taxes}; return waitForBatchedUpdates() @@ -491,10 +460,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( @@ -521,57 +488,52 @@ describe('actions/PolicyTax', () => { it('Rename tax', () => { const taxID = 'id_TAX_RATE_1'; const newTaxName = 'Tax rate 1 updated'; - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. - fetch.pause(); + mockFetch?.pause?.(); renamePolicyTax(fakePolicy.id, taxID, newTaxName); - return ( - waitForBatchedUpdates() - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, - waitForCollectionCallback: false, - callback: (policy) => { - Onyx.disconnect(connectionID); - const updatedTax = policy?.taxRates?.taxes?.[taxID]; - expect(updatedTax?.name).toBe(newTaxName); - expect(updatedTax?.pendingFields?.name).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); - expect(updatedTax?.errorFields?.name).toBeFalsy(); + return waitForBatchedUpdates() + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, + waitForCollectionCallback: false, + callback: (policy) => { + Onyx.disconnect(connectionID); + const updatedTax = policy?.taxRates?.taxes?.[taxID]; + expect(updatedTax?.name).toBe(newTaxName); + expect(updatedTax?.pendingFields?.name).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); + expect(updatedTax?.errorFields?.name).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); - const updatedTax = policy?.taxRates?.taxes?.[taxID]; - expect(updatedTax?.errorFields?.name).toBeFalsy(); - expect(updatedTax?.pendingFields?.name).toBeFalsy(); - resolve(); - }, - }); - }), - ) - ); + 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); + const updatedTax = policy?.taxRates?.taxes?.[taxID]; + expect(updatedTax?.errorFields?.name).toBeFalsy(); + expect(updatedTax?.pendingFields?.name).toBeFalsy(); + resolve(); + }, + }); + }), + ); }); it('Rename tax but API returns an error, then recover the original tax`s name', () => { const taxID = 'id_TAX_RATE_1'; const newTaxName = 'Tax rate 1 updated'; const originalTaxRate = {...fakePolicy?.taxRates?.taxes[taxID]}; - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. - fetch.pause(); + mockFetch?.pause?.(); renamePolicyTax(fakePolicy.id, taxID, newTaxName); return waitForBatchedUpdates() .then( @@ -593,10 +555,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( @@ -623,49 +583,45 @@ describe('actions/PolicyTax', () => { const taxID = 'id_TAX_RATE_1'; const newTaxValue = 10; const stringTaxValue = `${newTaxValue}%`; - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. - fetch.pause(); + mockFetch?.pause?.(); updatePolicyTaxValue(fakePolicy.id, taxID, newTaxValue); - return ( - waitForBatchedUpdates() - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, - waitForCollectionCallback: false, - callback: (policy) => { - Onyx.disconnect(connectionID); - const updatedTax = policy?.taxRates?.taxes?.[taxID]; - expect(updatedTax?.value).toBe(stringTaxValue); - expect(updatedTax?.pendingFields?.value).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); - expect(updatedTax?.errorFields?.value).toBeFalsy(); + return waitForBatchedUpdates() + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, + waitForCollectionCallback: false, + callback: (policy) => { + Onyx.disconnect(connectionID); + const updatedTax = policy?.taxRates?.taxes?.[taxID]; + expect(updatedTax?.value).toBe(stringTaxValue); + expect(updatedTax?.pendingFields?.value).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); + expect(updatedTax?.errorFields?.value).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); - const updatedTax = policy?.taxRates?.taxes?.[taxID]; - expect(updatedTax?.errorFields?.value).toBeFalsy(); - expect(updatedTax?.pendingFields?.value).toBeFalsy(); - resolve(); - }, - }); - }), - ) - ); + 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); + const updatedTax = policy?.taxRates?.taxes?.[taxID]; + expect(updatedTax?.errorFields?.value).toBeFalsy(); + expect(updatedTax?.pendingFields?.value).toBeFalsy(); + resolve(); + }, + }); + }), + ); }); it('Update tax`s value but API returns an error, then recover the original tax`s value', () => { @@ -673,8 +629,7 @@ describe('actions/PolicyTax', () => { const newTaxValue = 10; const originalTaxRate = {...fakePolicy?.taxRates?.taxes[taxID]}; const stringTaxValue = `${newTaxValue}%`; - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. - fetch.pause(); + mockFetch?.pause?.(); updatePolicyTaxValue(fakePolicy.id, taxID, newTaxValue); return waitForBatchedUpdates() .then( @@ -696,10 +651,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( @@ -726,113 +679,104 @@ describe('actions/PolicyTax', () => { const foreignTaxDefault = fakePolicy?.taxRates?.foreignTaxDefault; const taxID = '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?.(); deletePolicyTaxes(fakePolicy.id, [taxID]); - return ( - waitForBatchedUpdates() - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, - waitForCollectionCallback: false, - callback: (policy) => { - Onyx.disconnect(connectionID); - const taxRates = policy?.taxRates; - const deletedTax = taxRates?.taxes?.[taxID]; - expect(taxRates?.pendingFields?.foreignTaxDefault).toBeFalsy(); - expect(taxRates?.foreignTaxDefault).toBe(foreignTaxDefault); - expect(deletedTax?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); - expect(deletedTax?.errors).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); - const taxRates = policy?.taxRates; - const deletedTax = taxRates?.taxes?.[taxID]; - expect(taxRates?.pendingFields?.foreignTaxDefault).toBeFalsy(); - expect(deletedTax).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 taxRates = policy?.taxRates; + const deletedTax = taxRates?.taxes?.[taxID]; + expect(taxRates?.pendingFields?.foreignTaxDefault).toBeFalsy(); + expect(taxRates?.foreignTaxDefault).toBe(foreignTaxDefault); + expect(deletedTax?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + expect(deletedTax?.errors).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); + const taxRates = policy?.taxRates; + const deletedTax = taxRates?.taxes?.[taxID]; + expect(taxRates?.pendingFields?.foreignTaxDefault).toBeFalsy(); + expect(deletedTax).toBeFalsy(); + resolve(); + }, + }); + }), + ); }); it('Delete tax that is foreignTaxDefault', () => { const taxID = 'id_TAX_RATE_1'; const firstTaxID = 'id_TAX_EXEMPT'; - // @ts-expect-error TODO: Remove this once TestHelper (https://github.com/Expensify/App/issues/25318) is migrated to TypeScript. - fetch.pause(); - return ( - Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, {taxRates: {foreignTaxDefault: 'id_TAX_RATE_1'}}) - .then(() => { - deletePolicyTaxes(fakePolicy.id, [taxID]); - return waitForBatchedUpdates(); - }) - .then( - () => - new Promise((resolve) => { - const connectionID = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, - waitForCollectionCallback: false, - callback: (policy) => { - Onyx.disconnect(connectionID); - const taxRates = policy?.taxRates; - const deletedTax = taxRates?.taxes?.[taxID]; - expect(taxRates?.pendingFields?.foreignTaxDefault).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); - expect(taxRates?.foreignTaxDefault).toBe(firstTaxID); - expect(deletedTax?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); - expect(deletedTax?.errors).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); - const taxRates = policy?.taxRates; - const deletedTax = taxRates?.taxes?.[taxID]; - expect(taxRates?.pendingFields?.foreignTaxDefault).toBeFalsy(); - expect(deletedTax).toBeFalsy(); - resolve(); - }, - }); - }), - ) - ); + mockFetch?.pause?.(); + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, {taxRates: {foreignTaxDefault: 'id_TAX_RATE_1'}}) + .then(() => { + deletePolicyTaxes(fakePolicy.id, [taxID]); + return waitForBatchedUpdates(); + }) + .then( + () => + new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, + waitForCollectionCallback: false, + callback: (policy) => { + Onyx.disconnect(connectionID); + const taxRates = policy?.taxRates; + const deletedTax = taxRates?.taxes?.[taxID]; + expect(taxRates?.pendingFields?.foreignTaxDefault).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); + expect(taxRates?.foreignTaxDefault).toBe(firstTaxID); + expect(deletedTax?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + expect(deletedTax?.errors).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); + const taxRates = policy?.taxRates; + const deletedTax = taxRates?.taxes?.[taxID]; + expect(taxRates?.pendingFields?.foreignTaxDefault).toBeFalsy(); + expect(deletedTax).toBeFalsy(); + resolve(); + }, + }); + }), + ); }); it('Delete tax that is not foreignTaxDefault but API return an error, then recover the delated tax', () => { const foreignTaxDefault = fakePolicy?.taxRates?.foreignTaxDefault; const taxID = '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?.(); deletePolicyTaxes(fakePolicy.id, [taxID]); return waitForBatchedUpdates() .then( @@ -855,10 +799,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( From e4e35312b3623c282240f529ffe78664a7fe01f6 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 8 May 2024 12:47:05 +0200 Subject: [PATCH 112/174] Fix types for perf-tests --- tests/perf-test/ChatFinderPage.perf-test.tsx | 78 +++++++++++--------- tests/perf-test/ReportScreen.perf-test.tsx | 8 +- 2 files changed, 47 insertions(+), 39 deletions(-) diff --git a/tests/perf-test/ChatFinderPage.perf-test.tsx b/tests/perf-test/ChatFinderPage.perf-test.tsx index fa1433c872b5..02103c4a5a65 100644 --- a/tests/perf-test/ChatFinderPage.perf-test.tsx +++ b/tests/perf-test/ChatFinderPage.perf-test.tsx @@ -1,5 +1,5 @@ import type * as NativeNavigation from '@react-navigation/native'; -import type {StackScreenProps} from '@react-navigation/stack'; +import type {StackNavigationProp, StackScreenProps} from '@react-navigation/stack'; import {fireEvent, screen} from '@testing-library/react-native'; import React, {useMemo} from 'react'; import type {ComponentType} from 'react'; @@ -123,9 +123,9 @@ afterEach(() => { }); type ChatFinderPageProps = StackScreenProps & { - betas: OnyxEntry; - reports: OnyxCollection; - isSearchingForReports: OnyxEntry; + betas?: OnyxEntry; + reports?: OnyxCollection; + isSearchingForReports?: OnyxEntry; }; function ChatFinderPageWrapper(args: ChatFinderPageProps) { @@ -163,21 +163,26 @@ test('[ChatFinderPage] should render list with cached options', async () => { await screen.findByTestId('ChatFinderPage'); }; - const navigation = {addListener}; - - return ( - waitForBatchedUpdates() - .then(() => - Onyx.multiSet({ - ...mockedReports, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, - [ONYXKEYS.BETAS]: mockedBetas, - [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, - }), - ) - // @ts-expect-error Navigation prop is only used within this test - .then(() => measurePerformance(, {scenario})) - ); + const navigation = {addListener} as unknown as StackNavigationProp; + + return waitForBatchedUpdates() + .then(() => + Onyx.multiSet({ + ...mockedReports, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, + [ONYXKEYS.BETAS]: mockedBetas, + [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, + }), + ) + .then(() => + measurePerformance( + , + {scenario}, + ), + ); }); test('[ChatFinderPage] should interact when text input changes', async () => { @@ -192,19 +197,24 @@ test('[ChatFinderPage] should interact when text input changes', async () => { fireEvent.changeText(input, 'Email Five'); }; - const navigation = {addListener}; - - return ( - waitForBatchedUpdates() - .then(() => - Onyx.multiSet({ - ...mockedReports, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, - [ONYXKEYS.BETAS]: mockedBetas, - [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, - }), - ) - // @ts-expect-error Navigation prop is only used within this test - .then(() => measurePerformance(, {scenario})) - ); + const navigation = {addListener} as unknown as StackNavigationProp; + + return waitForBatchedUpdates() + .then(() => + Onyx.multiSet({ + ...mockedReports, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, + [ONYXKEYS.BETAS]: mockedBetas, + [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, + }), + ) + .then(() => + measurePerformance( + , + {scenario}, + ), + ); }); diff --git a/tests/perf-test/ReportScreen.perf-test.tsx b/tests/perf-test/ReportScreen.perf-test.tsx index 8252266bae18..379dc34489e5 100644 --- a/tests/perf-test/ReportScreen.perf-test.tsx +++ b/tests/perf-test/ReportScreen.perf-test.tsx @@ -1,4 +1,4 @@ -import type {StackScreenProps} from '@react-navigation/stack'; +import type {StackNavigationProp, StackScreenProps} from '@react-navigation/stack'; import {screen, waitFor} from '@testing-library/react-native'; import type {ComponentType} from 'react'; import React from 'react'; @@ -162,7 +162,7 @@ function ReportScreenWrapper(props: ReportScreenWrapperProps) { const report = {...createRandomReport(1), policyID: '1'}; const reportActions = ReportTestUtils.getMockedReportActionsMap(1000); -const mockRoute = {params: {reportID: '1'}}; +const mockRoute = {params: {reportID: '1', reportActionID: ''}, key: 'Report', name: 'Report' as const}; test('[ReportScreen] should render ReportScreen', () => { const {triggerTransitionEnd, addListener} = TestHelper.createAddListenerMock(); @@ -184,7 +184,7 @@ test('[ReportScreen] should render ReportScreen', () => { await screen.findByTestId('report-actions-list'); }; - const navigation = {addListener}; + const navigation = {addListener} as unknown as StackNavigationProp; return waitForBatchedUpdates() .then(() => { @@ -209,9 +209,7 @@ test('[ReportScreen] should render ReportScreen', () => { .then(() => measurePerformance( , {scenario}, From a3d217e9efa858a9ceb43e0f0d947bbb744c7e6b Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 8 May 2024 12:53:58 +0200 Subject: [PATCH 113/174] Address review comments --- src/components/MoneyReportHeader.tsx | 2 +- src/components/MoneyRequestHeader.tsx | 103 ++++++++++++++++---------- src/libs/TransactionUtils.ts | 4 +- 3 files changed, 65 insertions(+), 44 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 701811067994..7a2f6d8b4739 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -105,7 +105,7 @@ function MoneyReportHeader({ const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); const transactionIDs = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID).map((transaction) => transaction.transactionID); - const hasAllPendingRTERViolation = TransactionUtils.hasAllPendingRTERViolation(transactionIDs); + const hasAllPendingRTERViolation = TransactionUtils.hasAllPendingRTERViolations(transactionIDs); const cancelPayment = useCallback(() => { if (!chatReport) { diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 442f29f26577..c20f2904fd20 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -62,6 +62,64 @@ type MoneyRequestHeaderProps = MoneyRequestHeaderOnyxProps & { /** Method to trigger when pressing close button of the header */ onBackButtonPress: () => void; }; + +type PendingType = 'PENDING' | 'SCANNING' | 'RTER'; + +type MoneyRequestHeaderPendingStatusBarProps = {pendingType: PendingType; shouldShowBorderBottom: boolean}; + +function MoneyRequestHeaderPendingStatusBar({pendingType, shouldShowBorderBottom}: MoneyRequestHeaderPendingStatusBarProps) { + const theme = useTheme(); + const {translate} = useLocalize(); + + if (pendingType === 'PENDING') { + return ( + + } + description={translate('iou.transactionPendingDescription')} + shouldShowBorderBottom={shouldShowBorderBottom} + /> + ); + } + + if (pendingType === 'SCANNING') { + return ( + + } + description={translate('iou.receiptScanInProgressDescription')} + shouldShowBorderBottom={shouldShowBorderBottom} + /> + ); + } + + return ( + + } + description={translate('iou.pendingMatchWithCreditCardDescription')} + shouldShowBorderBottom={shouldShowBorderBottom} + /> + ); +} function MoneyRequestHeader({ session, parentReport, @@ -75,7 +133,6 @@ function MoneyRequestHeader({ onBackButtonPress, }: MoneyRequestHeaderProps) { const styles = useThemeStyles(); - const theme = useTheme(); const {translate} = useLocalize(); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const [shouldShowHoldMenu, setShouldShowHoldMenu] = useState(false); @@ -122,7 +179,7 @@ function MoneyRequestHeader({ } }; - const getPendingType = () => { + const getPendingType: () => PendingType | undefined = () => { if (TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction)) { return 'PENDING'; } @@ -215,45 +272,9 @@ function MoneyRequestHeader({ shouldShowBackButton={shouldUseNarrowLayout} onBackButtonPress={onBackButtonPress} /> - {pendingType === 'PENDING' && ( - - } - description={translate('iou.transactionPendingDescription')} - shouldShowBorderBottom={!isOnHold} - /> - )} - {pendingType === 'SCANNING' && ( - - } - description={translate('iou.receiptScanInProgressDescription')} - shouldShowBorderBottom={!isOnHold} - /> - )} - {pendingType === 'RTER' && ( - - } - description={translate('iou.pendingMatchWithCreditCardDescription')} + {pendingType && ( + )} diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 8edfad9b456f..bc33c39805c5 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -527,7 +527,7 @@ function hasPendingRTERViolation(transactionViolations?: TransactionViolations | /** * Check if there is pending rter violation in all transactionViolations with given transactionIDs. */ -function hasAllPendingRTERViolation(transactionIds: string[]): boolean { +function hasAllPendingRTERViolations(transactionIds: string[]): boolean { const transactionsWithRTERViolations = transactionIds.map((transactionId) => { const transactionViolations = getTransactionViolations(transactionId, allTransactionViolations); return hasPendingRTERViolation(transactionViolations); @@ -756,7 +756,7 @@ export { areRequiredFieldsEmpty, hasMissingSmartscanFields, hasPendingRTERViolation, - hasAllPendingRTERViolation, + hasAllPendingRTERViolations, hasPendingUI, getWaypointIndex, waypointHasValidAddress, From f62529887969e9b2b89638d96e4039eceded386a Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 8 May 2024 13:10:32 +0200 Subject: [PATCH 114/174] Fix types for awaitStagingDeploys --- tests/unit/awaitStagingDeploysTest.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/awaitStagingDeploysTest.ts b/tests/unit/awaitStagingDeploysTest.ts index e4f7d6494b15..64d79c9faf94 100644 --- a/tests/unit/awaitStagingDeploysTest.ts +++ b/tests/unit/awaitStagingDeploysTest.ts @@ -68,16 +68,16 @@ beforeAll(() => { asMutable(core).getInput = mockGetInput; // Mock octokit module - const moctokit: InternalOctokit = { + const moctokit = { rest: { - // @ts-expect-error This error was removed because getting the rest of the data from internalOctokit makes the test to break actions: { + ...(GithubUtils.internalOctokit as unknown as typeof GithubUtils.octokit.actions), listWorkflowRuns: mockListWorkflowRuns as unknown as typeof GithubUtils.octokit.actions.listWorkflowRuns, }, }, }; - GithubUtils.internalOctokit = moctokit; + GithubUtils.internalOctokit = moctokit as InternalOctokit; }); beforeEach(() => { From 5b46503aae591dd3d4e77f5b746ef1bab9411c80 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Fri, 10 May 2024 00:31:04 +0200 Subject: [PATCH 115/174] run prettier on useMarkdownStyle --- src/hooks/useMarkdownStyle.ts | 118 ++++++++++++++++------------------ 1 file changed, 57 insertions(+), 61 deletions(-) diff --git a/src/hooks/useMarkdownStyle.ts b/src/hooks/useMarkdownStyle.ts index d36b2ed9076b..2820f593128e 100644 --- a/src/hooks/useMarkdownStyle.ts +++ b/src/hooks/useMarkdownStyle.ts @@ -9,69 +9,65 @@ function useMarkdownStyle(message: string | null = null, excludeStyles: Array { - const styling = { - syntax: { - color: theme.syntax, - }, - link: { - color: theme.link, - }, - h1: { - fontSize: variables.fontSizeLarge, - }, - emoji: { - fontSize: emojiFontSize, - }, - blockquote: { - borderColor: theme.border, - borderWidth: 4, - marginLeft: 0, - paddingLeft: 6, - }, - code: { - fontFamily: FontUtils.fontFamily.platform.MONOSPACE, - fontSize: 13, // TODO: should be 15 if inside h1, see StyleUtils.getCodeFontSize - color: theme.text, - backgroundColor: 'transparent', - }, - pre: { - fontFamily: FontUtils.fontFamily.platform.MONOSPACE, - fontSize: 13, - color: theme.text, - backgroundColor: 'transparent', - }, - mentionHere: { - color: theme.ourMentionText, - backgroundColor: theme.ourMentionBG, - }, - mentionUser: { - color: theme.mentionText, - backgroundColor: theme.mentionBG, - }, - mentionReport: { - color: theme.mentionText, - backgroundColor: theme.mentionBG, - }, - }; + const markdownStyle = useMemo(() => { + const styling = { + syntax: { + color: theme.syntax, + }, + link: { + color: theme.link, + }, + h1: { + fontSize: variables.fontSizeLarge, + }, + emoji: { + fontSize: emojiFontSize, + }, + blockquote: { + borderColor: theme.border, + borderWidth: 4, + marginLeft: 0, + paddingLeft: 6, + }, + code: { + fontFamily: FontUtils.fontFamily.platform.MONOSPACE, + fontSize: 13, // TODO: should be 15 if inside h1, see StyleUtils.getCodeFontSize + color: theme.text, + backgroundColor: 'transparent', + }, + pre: { + fontFamily: FontUtils.fontFamily.platform.MONOSPACE, + fontSize: 13, + color: theme.text, + backgroundColor: 'transparent', + }, + mentionHere: { + color: theme.ourMentionText, + backgroundColor: theme.ourMentionBG, + }, + mentionUser: { + color: theme.mentionText, + backgroundColor: theme.mentionBG, + }, + mentionReport: { + color: theme.mentionText, + backgroundColor: theme.mentionBG, + }, + }; - if (excludeStyles.length) { - excludeStyles.forEach((key) => { - const style: Record = styling[key]; - if (style) { - Object.keys(style).forEach((styleKey) => { - style[styleKey] = undefined; - }); - } - }); - } + if (excludeStyles.length) { + excludeStyles.forEach((key) => { + const style: Record = styling[key]; + if (style) { + Object.keys(style).forEach((styleKey) => { + style[styleKey] = undefined; + }); + } + }); + } - return styling; - - }, - [theme, emojiFontSize, excludeStyles], - ); + return styling; + }, [theme, emojiFontSize, excludeStyles]); return markdownStyle; } From 530549ec4d9be81e83f79b03a13323c3930d73b1 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Fri, 10 May 2024 08:54:58 +0200 Subject: [PATCH 116/174] Update types for camera --- .../step/IOURequestStepScan/NavigationAwareCamera/types.ts | 2 +- src/pages/iou/request/step/IOURequestStepScan/index.native.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) 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 ba7492ad8416..c022a079df65 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -480,7 +480,6 @@ function IOURequestStepScan({ Date: Fri, 10 May 2024 09:50:05 +0200 Subject: [PATCH 117/174] Update Spanish translations --- src/languages/es.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 09beaa981303..08d136de68a2 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -638,14 +638,14 @@ export default { canceled: 'Canceló', posted: 'Contabilizado', deleteReceipt: 'Eliminar recibo', - pendingMatchWithCreditCard: 'Recibo pendiente de conciliar con la tarjeta de crédito.', - pendingMatchWithCreditCardDescription: 'Recibo pendiente de conciliar con tarjeta de crédito. Marcar como efectivo para ignorar y solicitar pago.', + 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...', receiptScanInProgress: 'Escaneo en curso.', - receiptScanInProgressDescription: 'Escaneando recibo. Vuelva a comprobarlo más tarde o introduzca los detalles ahora.', + receiptScanInProgressDescription: 'Escaneado de recibo en proceso. Vuelva a comprobar más tarde o introduzca los detalles ahora.', defaultRate: 'Tasa predeterminada', receiptMissingDetails: 'Recibo con campos vacíos', missingAmount: 'Falta importe', @@ -653,7 +653,7 @@ export default { receiptStatusTitle: 'Escaneando…', receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o introduce los detalles ahora.', receiptScanningFailed: 'El escaneo de recibo ha fallado. Introduce los detalles manualmente.', - transactionPendingDescription: 'Transacción pendiente. Esto puede tardar algunos días en registrarse a partir de la fecha en que se utilizó la tarjeta.', + transactionPendingDescription: 'Transacción pendiente. La transacción tarda unos días en contabilizarse desde la fecha en que se utilizó la tarjeta.', expenseCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => `${count} ${Str.pluralize('gasto', 'gastos', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${ pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : '' From e6e515496e370351b0f3c819bfed5632de253db2 Mon Sep 17 00:00:00 2001 From: Francois Laithier Date: Fri, 10 May 2024 13:56:27 -0700 Subject: [PATCH 118/174] Add intro videos with sound --- src/CONST.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index edceb11edd85..4fe5f9742c29 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3548,7 +3548,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`, }, }, @@ -3761,7 +3761,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, @@ -3804,7 +3804,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, @@ -3890,7 +3890,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, @@ -3918,7 +3918,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, From df94ad095e0b3d7341c8d0f5942a4d12b67a27ca Mon Sep 17 00:00:00 2001 From: cretadn22 Date: Sat, 11 May 2024 10:32:46 +0700 Subject: [PATCH 119/174] update the initial policy instead of creating multiple policies --- src/ONYXKEYS.ts | 4 ++++ src/libs/actions/Policy.ts | 3 ++- src/libs/actions/Welcome.ts | 6 +++++- .../BaseOnboardingPersonalDetails.tsx | 1 + src/pages/OnboardingWork/BaseOnboardingWork.tsx | 17 +++++++++++++---- src/pages/OnboardingWork/types.ts | 3 +++ 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 804c8dadd553..549a98499b23 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', @@ -662,6 +665,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/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index b4022b287d05..cd2930dca47c 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1620,10 +1620,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/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/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index 7fcb5c1c5de6..3a8212cad5a5 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -62,6 +62,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat ); Welcome.setOnboardingAdminsChatReportID(); + Welcome.setOnboardingPolicyID(); Navigation.dismissModal(); diff --git a/src/pages/OnboardingWork/BaseOnboardingWork.tsx b/src/pages/OnboardingWork/BaseOnboardingWork.tsx index 8fdbed73111e..dc840dfa480f 100644 --- a/src/pages/OnboardingWork/BaseOnboardingWork.tsx +++ b/src/pages/OnboardingWork/BaseOnboardingWork.tsx @@ -27,7 +27,7 @@ import type {BaseOnboardingWorkOnyxProps, BaseOnboardingWorkProps} from './types const OPEN_WORK_PAGE_PURPOSES = [CONST.ONBOARDING_CHOICES.MANAGE_TEAM]; -function BaseOnboardingWork({shouldUseNativeStyles, onboardingPurposeSelected}: BaseOnboardingWorkProps) { +function BaseOnboardingWork({shouldUseNativeStyles, onboardingPurposeSelected, onboardingPolicyID}: BaseOnboardingWorkProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -41,11 +41,17 @@ function BaseOnboardingWork({shouldUseNativeStyles, onboardingPurposeSelected}: return; } const work = values.work.trim(); - const {adminsChatReportID} = Policy.createWorkspace(undefined, true, work); - Welcome.setOnboardingAdminsChatReportID(adminsChatReportID); + if (!onboardingPolicyID) { + const {adminsChatReportID, policyID} = Policy.createWorkspace(undefined, true, work); + Welcome.setOnboardingAdminsChatReportID(adminsChatReportID); + Welcome.setOnboardingPolicyID(policyID); + } + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style + Policy.updateGeneralSettings(onboardingPolicyID as string, work); + Navigation.navigate(ROUTES.ONBOARDING_PERSONAL_DETAILS); }, - [onboardingPurposeSelected], + [onboardingPurposeSelected, onboardingPolicyID], ); const validate = (values: FormOnyxValues<'onboardingWorkForm'>) => { @@ -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 & { From 573a159f8695e737cd33feb1b10d43037e3200ee Mon Sep 17 00:00:00 2001 From: Anusha Date: Sat, 11 May 2024 14:36:23 +0500 Subject: [PATCH 120/174] Fix navigation to concierge after leaving the group --- src/libs/actions/Report.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 43b241102875..5c824225de92 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'; @@ -2517,10 +2517,12 @@ function navigateToMostRecentReport(currentReport: OnyxEntry) { const isChatThread = ReportUtils.isChatThread(currentReport); if (lastAccessedReportID) { const lastAccessedReportRoute = ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID ?? ''); + 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 (!isFirstRoute) { + Navigation.goBack(); + } } Navigation.navigate(lastAccessedReportRoute, CONST.NAVIGATION.TYPE.FORCED_UP); } else { From 75d88b1c8f94b6fccb434cc3bc0480f6d115962a Mon Sep 17 00:00:00 2001 From: smelaa Date: Mon, 13 May 2024 12:11:49 +0200 Subject: [PATCH 121/174] Address review comments --- src/components/MoneyReportHeader.tsx | 10 +-- src/components/MoneyRequestHeader.tsx | 90 +++++++++------------------ src/libs/TransactionUtils.ts | 2 +- 3 files changed, 35 insertions(+), 67 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 7a2f6d8b4739..c8d4610873e6 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -105,7 +105,7 @@ function MoneyReportHeader({ const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); const transactionIDs = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID).map((transaction) => transaction.transactionID); - const hasAllPendingRTERViolation = TransactionUtils.hasAllPendingRTERViolations(transactionIDs); + const hasAllPendingRTERViolations = TransactionUtils.hasAllPendingRTERViolations(transactionIDs); const cancelPayment = useCallback(() => { if (!chatReport) { @@ -121,9 +121,9 @@ function MoneyReportHeader({ const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(moneyRequestReport); - const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton) && !hasAllPendingRTERViolation; + const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton) && !hasAllPendingRTERViolations; - const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !hasAllPendingRTERViolation; + const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !hasAllPendingRTERViolations; 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; @@ -212,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) && !hasAllPendingRTERViolation} + shouldShowBorderBottom={!(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout) && !hasAllPendingRTERViolations} shouldShowThreeDotsButton threeDotsMenuItems={threeDotsMenuItems} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)} @@ -250,7 +250,7 @@ function MoneyReportHeader({ )} - {hasAllPendingRTERViolation && ( + {hasAllPendingRTERViolations && ( void; }; -type PendingType = 'PENDING' | 'SCANNING' | 'RTER'; +type NoPendingStatusBarProps = {isPending: false}; -type MoneyRequestHeaderPendingStatusBarProps = {pendingType: PendingType; shouldShowBorderBottom: boolean}; +type PendingStatusBarProps = { + isPending: true; -function MoneyRequestHeaderPendingStatusBar({pendingType, shouldShowBorderBottom}: MoneyRequestHeaderPendingStatusBarProps) { - const theme = useTheme(); - const {translate} = useLocalize(); + /** The icon to be displayed in status bar */ + pendingIcon: React.FC; - if (pendingType === 'PENDING') { - return ( - - } - description={translate('iou.transactionPendingDescription')} - shouldShowBorderBottom={shouldShowBorderBottom} - /> - ); - } + /** The description to be displayed in status bar */ + pendingDescription: string; +}; - if (pendingType === 'SCANNING') { - return ( - - } - description={translate('iou.receiptScanInProgressDescription')} - shouldShowBorderBottom={shouldShowBorderBottom} - /> - ); - } +type StatusBarProps = NoPendingStatusBarProps | PendingStatusBarProps; - return ( - - } - description={translate('iou.pendingMatchWithCreditCardDescription')} - shouldShowBorderBottom={shouldShowBorderBottom} - /> - ); -} function MoneyRequestHeader({ session, parentReport, @@ -133,6 +91,7 @@ function MoneyRequestHeader({ onBackButtonPress, }: MoneyRequestHeaderProps) { const styles = useThemeStyles(); + const theme = useTheme(); const {translate} = useLocalize(); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const [shouldShowHoldMenu, setShouldShowHoldMenu] = useState(false); @@ -179,19 +138,20 @@ function MoneyRequestHeader({ } }; - const getPendingType: () => PendingType | undefined = () => { + const getStatusBarProps: () => StatusBarProps = () => { if (TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction)) { - return 'PENDING'; + return {isPending: true, pendingIcon: Expensicons.CreditCardHourglass, pendingDescription: translate('iou.transactionPendingDescription')}; } if (isScanning) { - return 'SCANNING'; + return {isPending: true, pendingIcon: Expensicons.ReceiptScan, pendingDescription: translate('iou.receiptScanInProgressDescription')}; } if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) { - return 'RTER'; + return {isPending: true, pendingIcon: Expensicons.Hourglass, pendingDescription: translate('iou.pendingMatchWithCreditCardDescription')}; } + return {isPending: false}; }; - const pendingType = getPendingType(); + const statusBarProps = getStatusBarProps(); useEffect(() => { if (canDeleteRequest) { @@ -257,7 +217,7 @@ function MoneyRequestHeader({ <> - {pendingType && ( - + } + description={statusBarProps.pendingDescription} shouldShowBorderBottom={!isOnHold} /> )} diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 936be8a1f32d..51d35af17de9 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -543,7 +543,7 @@ function hasAllPendingRTERViolations(transactionIds: string[]): boolean { * Check if the transaction is pending or has a pending rter violation. */ function hasPendingUI(transaction: OnyxEntry, transactionViolations?: TransactionViolations | null): boolean { - return Boolean(isReceiptBeingScanned(transaction) || isPending(transaction) || (transaction && hasPendingRTERViolation(transactionViolations))); + return !!(isReceiptBeingScanned(transaction) || isPending(transaction) || (transaction && hasPendingRTERViolation(transactionViolations))); } /** From 48b387c397f0e0cd225bbf82b391e985d7ff174d Mon Sep 17 00:00:00 2001 From: smelaa Date: Mon, 13 May 2024 15:20:08 +0200 Subject: [PATCH 122/174] Rename variables & change icon color --- src/components/MoneyRequestHeader.tsx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 5c04c815db3b..ee98b275b9c2 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -64,16 +64,16 @@ type MoneyRequestHeaderProps = MoneyRequestHeaderOnyxProps & { onBackButtonPress: () => void; }; -type NoPendingStatusBarProps = {isPending: false}; +type NoPendingStatusBarProps = {shouldShowStatusBar: false}; type PendingStatusBarProps = { - isPending: true; + shouldShowStatusBar: true; /** The icon to be displayed in status bar */ - pendingIcon: React.FC; + statusBarIcon: React.FC; /** The description to be displayed in status bar */ - pendingDescription: string; + statusBarDescription: string; }; type StatusBarProps = NoPendingStatusBarProps | PendingStatusBarProps; @@ -140,15 +140,15 @@ function MoneyRequestHeader({ const getStatusBarProps: () => StatusBarProps = () => { if (TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction)) { - return {isPending: true, pendingIcon: Expensicons.CreditCardHourglass, pendingDescription: translate('iou.transactionPendingDescription')}; + return {shouldShowStatusBar: true, statusBarIcon: Expensicons.CreditCardHourglass, statusBarDescription: translate('iou.transactionPendingDescription')}; } if (isScanning) { - return {isPending: true, pendingIcon: Expensicons.ReceiptScan, pendingDescription: translate('iou.receiptScanInProgressDescription')}; + return {shouldShowStatusBar: true, statusBarIcon: Expensicons.ReceiptScan, statusBarDescription: translate('iou.receiptScanInProgressDescription')}; } if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) { - return {isPending: true, pendingIcon: Expensicons.Hourglass, pendingDescription: translate('iou.pendingMatchWithCreditCardDescription')}; + return {shouldShowStatusBar: true, statusBarIcon: Expensicons.Hourglass, statusBarDescription: translate('iou.pendingMatchWithCreditCardDescription')}; } - return {isPending: false}; + return {shouldShowStatusBar: false}; }; const statusBarProps = getStatusBarProps(); @@ -217,7 +217,7 @@ function MoneyRequestHeader({ <> - {statusBarProps.isPending && ( + {statusBarProps.shouldShowStatusBar && ( } - description={statusBarProps.pendingDescription} + description={statusBarProps.statusBarDescription} shouldShowBorderBottom={!isOnHold} /> )} From 3763d32cfadd07aa830107a440fa3719474103c9 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Tue, 14 May 2024 03:07:10 +0530 Subject: [PATCH 123/174] fixes preview not centered in carousel --- src/components/Attachments/AttachmentCarousel/CarouselItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ( - + Date: Tue, 14 May 2024 03:25:43 +0530 Subject: [PATCH 124/174] fix loading component --- src/components/PDFView/index.tsx | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx index 52f883113234..bb30817fb9ef 100644 --- a/src/components/PDFView/index.tsx +++ b/src/components/PDFView/index.tsx @@ -85,21 +85,6 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, max } }, [isKeyboardOpen, prevWindowHeight, toggleKeyboardOnSmallScreens, windowHeight]); - const renderLoadingIndicator = () => { - if (isUsedAsChatAttachment) { - return ( - - - - ); - } - - return ; - }; - const renderPDFView = () => { const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; @@ -116,7 +101,7 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, max maxCanvasWidth={maxCanvasWidth} maxCanvasHeight={maxCanvasHeight} maxCanvasArea={maxCanvasArea} - LoadingComponent={renderLoadingIndicator()} + LoadingComponent={} shouldShowErrorComponent={false} onLoadError={onLoadError} renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( From c134239c57c20a2f90d66322aaab115b729484f5 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Tue, 14 May 2024 03:40:00 +0530 Subject: [PATCH 125/174] prettier --- src/components/PDFView/index.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx index bb30817fb9ef..76daba9ce0f6 100644 --- a/src/components/PDFView/index.tsx +++ b/src/components/PDFView/index.tsx @@ -3,14 +3,13 @@ import 'core-js/features/array/at'; import type {CSSProperties} from 'react'; import React, {memo, useCallback, useEffect, useState} from 'react'; import {PDFPreviewer} from 'react-fast-pdf'; -import {ActivityIndicator, View} from 'react-native'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useStyleUtils from '@hooks/useStyleUtils'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import variables from '@styles/variables'; @@ -27,7 +26,6 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, max const [isKeyboardOpen, setIsKeyboardOpen] = useState(false); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const theme = useTheme(); const {windowHeight, isSmallScreenWidth} = useWindowDimensions(); const prevWindowHeight = usePrevious(windowHeight); const {translate} = useLocalize(); @@ -101,7 +99,17 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, max maxCanvasWidth={maxCanvasWidth} maxCanvasHeight={maxCanvasHeight} maxCanvasArea={maxCanvasArea} - LoadingComponent={} + LoadingComponent={ + + } shouldShowErrorComponent={false} onLoadError={onLoadError} renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( From b44b90797077ec5f4e79e7f16196f4529580f9f0 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Tue, 14 May 2024 08:30:59 +0100 Subject: [PATCH 126/174] update check for disabled toggle --- .../workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx index 6c8e9ccb92bb..9b8cec10d100 100644 --- a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx +++ b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx @@ -46,7 +46,7 @@ function QuickbooksTaxesPage({policy}: WithPolicyProps) { accessibilityLabel={translate('workspace.accounting.taxes')} isOn={!!syncTax} onToggle={() => Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.QBO, CONST.QUICK_BOOKS_CONFIG.SYNC_TAX, !syncTax)} - disabled={isJournalExportEntity} + disabled={!syncTax && isJournalExportEntity} /> From 5500e45d30161665d9763e9b66256479bf732fc1 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 14 May 2024 10:39:51 +0200 Subject: [PATCH 127/174] use non styling values for overwriting excluded styles --- src/hooks/useMarkdownStyle.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/hooks/useMarkdownStyle.ts b/src/hooks/useMarkdownStyle.ts index 2820f593128e..ef331ca31c9e 100644 --- a/src/hooks/useMarkdownStyle.ts +++ b/src/hooks/useMarkdownStyle.ts @@ -5,6 +5,16 @@ import FontUtils from '@styles/utils/FontUtils'; import variables from '@styles/variables'; import useTheme from './useTheme'; +const nonStylingDefaultValues: Record = { + color: 'black', + backgroundColor: 'transparent', + marginLeft: 0, + paddingLeft: 0, + borderColor: 'transparent', + borderWidth: 0, +} + + function useMarkdownStyle(message: string | null = null, excludeStyles: Array = []): MarkdownStyle { const theme = useTheme(); const emojiFontSize = containsOnlyEmojis(message ?? '') ? variables.fontSizeOnlyEmojis : variables.fontSizeNormal; @@ -60,7 +70,7 @@ function useMarkdownStyle(message: string | null = null, excludeStyles: Array = styling[key]; if (style) { Object.keys(style).forEach((styleKey) => { - style[styleKey] = undefined; + style[styleKey] = nonStylingDefaultValues[styleKey] ?? style[styleKey]; }); } }); From fccc83b2b0fa5d082f78a9bc955c08c48b98efe4 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 14 May 2024 11:43:23 +0200 Subject: [PATCH 128/174] add comment for non styling map --- src/hooks/useMarkdownStyle.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hooks/useMarkdownStyle.ts b/src/hooks/useMarkdownStyle.ts index ef331ca31c9e..714a22db643a 100644 --- a/src/hooks/useMarkdownStyle.ts +++ b/src/hooks/useMarkdownStyle.ts @@ -5,6 +5,7 @@ import FontUtils from '@styles/utils/FontUtils'; import variables from '@styles/variables'; import useTheme from './useTheme'; +// this map is used to reset the styles that are not needed - passing undefined value can break the native side const nonStylingDefaultValues: Record = { color: 'black', backgroundColor: 'transparent', From e5c5fdfd6fae18b25a73b9e9f5e7303fdec61d95 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 14 May 2024 11:48:29 +0200 Subject: [PATCH 129/174] fix lint --- src/hooks/useMarkdownStyle.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/hooks/useMarkdownStyle.ts b/src/hooks/useMarkdownStyle.ts index 714a22db643a..a95124c08ed1 100644 --- a/src/hooks/useMarkdownStyle.ts +++ b/src/hooks/useMarkdownStyle.ts @@ -6,15 +6,14 @@ import variables from '@styles/variables'; import useTheme from './useTheme'; // this map is used to reset the styles that are not needed - passing undefined value can break the native side -const nonStylingDefaultValues: Record = { +const nonStylingDefaultValues: Record = { color: 'black', backgroundColor: 'transparent', marginLeft: 0, paddingLeft: 0, borderColor: 'transparent', borderWidth: 0, -} - +}; function useMarkdownStyle(message: string | null = null, excludeStyles: Array = []): MarkdownStyle { const theme = useTheme(); From e5670c0b3614bba6078564c051d98eec44d0ee58 Mon Sep 17 00:00:00 2001 From: smelaa Date: Tue, 14 May 2024 13:11:54 +0200 Subject: [PATCH 130/174] Fix a type & rename function --- src/components/MoneyReportHeader.tsx | 10 +++++----- src/languages/en.ts | 2 +- src/libs/TransactionUtils.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index c8d4610873e6..642985bceaea 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -105,7 +105,7 @@ function MoneyReportHeader({ const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); const transactionIDs = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID).map((transaction) => transaction.transactionID); - const hasAllPendingRTERViolations = TransactionUtils.hasAllPendingRTERViolations(transactionIDs); + const allHavePendingRTERViolation = TransactionUtils.allHavePendingRTERViolation(transactionIDs); const cancelPayment = useCallback(() => { if (!chatReport) { @@ -121,9 +121,9 @@ function MoneyReportHeader({ const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(moneyRequestReport); - const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton) && !hasAllPendingRTERViolations; + const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton) && !allHavePendingRTERViolation; - const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !hasAllPendingRTERViolations; + 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; @@ -212,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) && !hasAllPendingRTERViolations} + shouldShowBorderBottom={!(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout) && !allHavePendingRTERViolation} shouldShowThreeDotsButton threeDotsMenuItems={threeDotsMenuItems} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)} @@ -250,7 +250,7 @@ function MoneyReportHeader({ )} - {hasAllPendingRTERViolations && ( + {allHavePendingRTERViolation && ( { const transactionViolations = getTransactionViolations(transactionId, allTransactionViolations); return hasPendingRTERViolation(transactionViolations); @@ -800,7 +800,7 @@ export { areRequiredFieldsEmpty, hasMissingSmartscanFields, hasPendingRTERViolation, - hasAllPendingRTERViolations, + allHavePendingRTERViolation, hasPendingUI, getWaypointIndex, waypointHasValidAddress, From e90103f37fd57fc3a84f3386a714d54a0bec1d42 Mon Sep 17 00:00:00 2001 From: smelaa Date: Tue, 14 May 2024 13:50:01 +0200 Subject: [PATCH 131/174] Create icon & description getter functions --- src/components/MoneyRequestHeader.tsx | 4 +- .../MoneyRequestPreviewContent.tsx | 62 ++++++++++--------- .../ReportActionItem/ReportPreview.tsx | 62 ++++++++++--------- 3 files changed, 69 insertions(+), 59 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index ee98b275b9c2..bd0674b40607 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -2,7 +2,6 @@ import React, {useCallback, useEffect, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import type {SvgProps} from 'react-native-svg'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -19,6 +18,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; 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'; @@ -70,7 +70,7 @@ type PendingStatusBarProps = { shouldShowStatusBar: true; /** The icon to be displayed in status bar */ - statusBarIcon: React.FC; + statusBarIcon: IconAsset; /** The description to be displayed in status bar */ statusBarDescription: string; diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 366445b0f1aa..f52fa644c7da 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -36,8 +36,23 @@ 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 IconAsset from '@src/types/utils/IconAsset'; import type {MoneyRequestPreviewProps} from './types'; +type NoPendingMessageProps = {shouldShowMessage: false}; + +type PendingMessageProps = { + shouldShowMessage: true; + + /** The icon to be displayed in the preview content footer */ + messageIcon: IconAsset; + + /** The description to be displayed in the preview content footer */ + messageDescription: string; +}; + +type MessageProps = PendingMessageProps | NoPendingMessageProps; + function MoneyRequestPreviewContent({ iouReport, isBillSplit, @@ -84,7 +99,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; @@ -96,7 +110,6 @@ function MoneyRequestPreviewContent({ const isCardTransaction = TransactionUtils.isCardTransaction(transaction); const isSettled = ReportUtils.isSettled(iouReport?.reportID); const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; - const hasPendingUI = TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations)); const isFullySettled = isSettled && !isSettlementOrApprovalPartial; const isFullyApproved = ReportUtils.isReportApproved(iouReport) && !isSettlementOrApprovalPartial; const shouldShowRBR = hasNoticeTypeViolations || hasViolations || hasFieldErrors || (!isFullySettled && !isFullyApproved && isOnHold); @@ -185,6 +198,21 @@ function MoneyRequestPreviewContent({ return message; }; + const getPendingMessageProps: () => MessageProps = () => { + if (isScanning) { + return {shouldShowMessage: true, messageIcon: ReceiptScan, messageDescription: translate('iou.receiptScanInProgress')}; + } + if (TransactionUtils.isPending(transaction)) { + return {shouldShowMessage: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')}; + } + if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) { + return {shouldShowMessage: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; + } + return {shouldShowMessage: false}; + }; + + const pendingMessageProps = getPendingMessageProps(); + const getDisplayAmountText = (): string => { if (isScanning) { return translate('iou.receiptScanning'); @@ -313,39 +341,15 @@ function MoneyRequestPreviewContent({ )} - {isScanning && ( - - - {translate('iou.receiptScanInProgress')} - - )} - {isPending && ( + {pendingMessageProps.shouldShowMessage && ( - {translate('iou.transactionPending')} - - )} - {!isScanning && hasPendingUI && ( - - - - {translate('iou.pendingMatchWithCreditCard')} - + {pendingMessageProps.messageDescription} )} diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 3fc8deb86687..e2632de87e93 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 IconAsset from '@src/types/utils/IconAsset'; import ReportActionItemImages from './ReportActionItemImages'; type ReportPreviewOnyxProps = { @@ -83,6 +84,20 @@ type ReportPreviewProps = ReportPreviewOnyxProps & { isHovered?: boolean; }; +type NoPendingMessageProps = {shouldShowMessage: false}; + +type PendingMessageProps = { + shouldShowMessage: true; + + /** The icon to be displayed in the preview content footer */ + messageIcon: IconAsset; + + /** The description to be displayed in the preview content footer */ + messageDescription: string; +}; + +type MessageProps = PendingMessageProps | NoPendingMessageProps; + function ReportPreview({ iouReport, policy, @@ -231,6 +246,21 @@ function ReportPreview({ const shouldShowScanningSubtitle = numberOfScanningReceipts === 1 && numberOfRequests === 1; const shouldShowPendingSubtitle = numberOfPendingRequests === 1 && numberOfRequests === 1; + const getPendingMessageProps: () => MessageProps = () => { + if (shouldShowScanningSubtitle) { + return {shouldShowMessage: true, messageIcon: Expensicons.ReceiptScan, messageDescription: translate('iou.receiptScanInProgress')}; + } + if (shouldShowPendingSubtitle) { + return {shouldShowMessage: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')}; + } + if (showRTERViolationMessage) { + return {shouldShowMessage: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; + } + return {shouldShowMessage: false}; + }; + + const pendingMessageProps = getPendingMessageProps(); + const {supportText} = useMemo(() => { if (formattedMerchant) { return {supportText: formattedMerchant}; @@ -316,39 +346,15 @@ function ReportPreview({ )} - {showRTERViolationMessage && ( - + {pendingMessageProps.shouldShowMessage && ( + - - {translate('iou.pendingMatchWithCreditCard')} - - - )} - {shouldShowScanningSubtitle && ( - - - {translate('iou.receiptScanInProgress')} - - )} - {shouldShowPendingSubtitle && ( - - - {translate('iou.transactionPending')} + {pendingMessageProps.messageDescription} )} From 4920af0a41ff3bf4ad0765dc091383f09cf5b7e7 Mon Sep 17 00:00:00 2001 From: smelaa Date: Tue, 14 May 2024 14:12:11 +0200 Subject: [PATCH 132/174] Move types declaration to ReportUtils --- src/components/MoneyRequestHeader.tsx | 34 ++++++------------- .../MoneyRequestPreviewContent.tsx | 28 ++++----------- .../ReportActionItem/ReportPreview.tsx | 28 ++++----------- src/libs/ReportUtils.ts | 15 ++++++++ 4 files changed, 39 insertions(+), 66 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index bd0674b40607..346fd92f9613 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -10,6 +10,7 @@ import * as HeaderUtils from '@libs/HeaderUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import type {PendingMessageProps} from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import variables from '@styles/variables'; import * as IOU from '@userActions/IOU'; @@ -18,7 +19,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; 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'; @@ -64,20 +64,6 @@ type MoneyRequestHeaderProps = MoneyRequestHeaderOnyxProps & { onBackButtonPress: () => void; }; -type NoPendingStatusBarProps = {shouldShowStatusBar: false}; - -type PendingStatusBarProps = { - shouldShowStatusBar: true; - - /** The icon to be displayed in status bar */ - statusBarIcon: IconAsset; - - /** The description to be displayed in status bar */ - statusBarDescription: string; -}; - -type StatusBarProps = NoPendingStatusBarProps | PendingStatusBarProps; - function MoneyRequestHeader({ session, parentReport, @@ -138,17 +124,17 @@ function MoneyRequestHeader({ } }; - const getStatusBarProps: () => StatusBarProps = () => { + const getStatusBarProps: () => PendingMessageProps = () => { if (TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction)) { - return {shouldShowStatusBar: true, statusBarIcon: Expensicons.CreditCardHourglass, statusBarDescription: translate('iou.transactionPendingDescription')}; + return {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPendingDescription')}; } if (isScanning) { - return {shouldShowStatusBar: true, statusBarIcon: Expensicons.ReceiptScan, statusBarDescription: translate('iou.receiptScanInProgressDescription')}; + return {shouldShow: true, messageIcon: Expensicons.ReceiptScan, messageDescription: translate('iou.receiptScanInProgressDescription')}; } if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) { - return {shouldShowStatusBar: true, statusBarIcon: Expensicons.Hourglass, statusBarDescription: translate('iou.pendingMatchWithCreditCardDescription')}; + return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCardDescription')}; } - return {shouldShowStatusBar: false}; + return {shouldShow: false}; }; const statusBarProps = getStatusBarProps(); @@ -217,7 +203,7 @@ function MoneyRequestHeader({ <> - {statusBarProps.shouldShowStatusBar && ( + {statusBarProps.shouldShow && ( } - description={statusBarProps.statusBarDescription} + description={statusBarProps.messageDescription} shouldShowBorderBottom={!isOnHold} /> )} diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index f52fa644c7da..5ef7051ba1bb 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -27,6 +27,7 @@ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import type {PendingMessageProps} from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import variables from '@styles/variables'; @@ -36,23 +37,8 @@ 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 IconAsset from '@src/types/utils/IconAsset'; import type {MoneyRequestPreviewProps} from './types'; -type NoPendingMessageProps = {shouldShowMessage: false}; - -type PendingMessageProps = { - shouldShowMessage: true; - - /** The icon to be displayed in the preview content footer */ - messageIcon: IconAsset; - - /** The description to be displayed in the preview content footer */ - messageDescription: string; -}; - -type MessageProps = PendingMessageProps | NoPendingMessageProps; - function MoneyRequestPreviewContent({ iouReport, isBillSplit, @@ -198,17 +184,17 @@ function MoneyRequestPreviewContent({ return message; }; - const getPendingMessageProps: () => MessageProps = () => { + const getPendingMessageProps: () => PendingMessageProps = () => { if (isScanning) { - return {shouldShowMessage: true, messageIcon: ReceiptScan, messageDescription: translate('iou.receiptScanInProgress')}; + return {shouldShow: true, messageIcon: ReceiptScan, messageDescription: translate('iou.receiptScanInProgress')}; } if (TransactionUtils.isPending(transaction)) { - return {shouldShowMessage: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')}; + return {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')}; } if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) { - return {shouldShowMessage: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; + return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } - return {shouldShowMessage: false}; + return {shouldShow: false}; }; const pendingMessageProps = getPendingMessageProps(); @@ -341,7 +327,7 @@ function MoneyRequestPreviewContent({ )} - {pendingMessageProps.shouldShowMessage && ( + {pendingMessageProps.shouldShow && ( MessageProps = () => { + const getPendingMessageProps: () => PendingMessageProps = () => { if (shouldShowScanningSubtitle) { - return {shouldShowMessage: true, messageIcon: Expensicons.ReceiptScan, messageDescription: translate('iou.receiptScanInProgress')}; + return {shouldShow: true, messageIcon: Expensicons.ReceiptScan, messageDescription: translate('iou.receiptScanInProgress')}; } if (shouldShowPendingSubtitle) { - return {shouldShowMessage: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')}; + return {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')}; } if (showRTERViolationMessage) { - return {shouldShowMessage: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; + return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } - return {shouldShowMessage: false}; + return {shouldShow: false}; }; const pendingMessageProps = getPendingMessageProps(); @@ -346,7 +332,7 @@ function ReportPreview({ )} - {pendingMessageProps.shouldShowMessage && ( + {pendingMessageProps.shouldShow && ( Date: Tue, 14 May 2024 14:12:56 +0200 Subject: [PATCH 133/174] Address review comments --- src/libs/TransactionUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 371e66dbccbb..48c225b68086 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -543,7 +543,7 @@ function allHavePendingRTERViolation(transactionIds: string[]): boolean { * 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))); + return isReceiptBeingScanned(transaction) || isPending(transaction) || (!!transaction && hasPendingRTERViolation(transactionViolations)); } /** From 6ca77b037914b675e277b1aad096359f78e55dca Mon Sep 17 00:00:00 2001 From: Anusha Date: Tue, 14 May 2024 19:10:42 +0500 Subject: [PATCH 134/174] add comment --- src/libs/actions/Report.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 4a65bcd40a26..ffc51a2de4ed 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2530,12 +2530,11 @@ function navigateToMostRecentReport(currentReport: OnyxEntry) { const isChatThread = ReportUtils.isChatThread(currentReport); if (lastAccessedReportID) { const lastAccessedReportRoute = ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID ?? ''); - const isFirstRoute = navigationRef?.current?.getState().index === 1; + //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) { - if (!isFirstRoute) { - Navigation.goBack(); - } + if (!isChatThread && !isFirstRoute) { + Navigation.goBack(); } Navigation.navigate(lastAccessedReportRoute, CONST.NAVIGATION.TYPE.FORCED_UP); } else { From 1b7f1a800ac77b811534ebdf51e62b6683a01da2 Mon Sep 17 00:00:00 2001 From: Anusha Date: Tue, 14 May 2024 19:22:20 +0500 Subject: [PATCH 135/174] fix lint error --- src/libs/actions/Report.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index ffc51a2de4ed..8f7a7db9b7a2 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2530,7 +2530,7 @@ 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. + // 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 && !isFirstRoute) { From 5c32859e0af47f83b0d152afb6da05ddccdc826a Mon Sep 17 00:00:00 2001 From: Samil Abud Date: Tue, 14 May 2024 12:11:29 -0400 Subject: [PATCH 136/174] Fixed emoji misaligned in focus mode --- src/components/LHNOptionsList/OptionRowLHN.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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]; From 2bdb1a4efa5a5e1b8ab8aeddfd8f8055ef19beba Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 14 May 2024 18:40:17 +0200 Subject: [PATCH 137/174] use theme values for non styling object --- src/hooks/useMarkdownStyle.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/hooks/useMarkdownStyle.ts b/src/hooks/useMarkdownStyle.ts index a95124c08ed1..61f39144cd74 100644 --- a/src/hooks/useMarkdownStyle.ts +++ b/src/hooks/useMarkdownStyle.ts @@ -5,20 +5,20 @@ import FontUtils from '@styles/utils/FontUtils'; import variables from '@styles/variables'; import useTheme from './useTheme'; -// this map is used to reset the styles that are not needed - passing undefined value can break the native side -const nonStylingDefaultValues: Record = { - color: 'black', - backgroundColor: 'transparent', - marginLeft: 0, - paddingLeft: 0, - borderColor: 'transparent', - borderWidth: 0, -}; - function useMarkdownStyle(message: string | null = null, excludeStyles: Array = []): MarkdownStyle { const theme = useTheme(); const emojiFontSize = containsOnlyEmojis(message ?? '') ? variables.fontSizeOnlyEmojis : variables.fontSizeNormal; + // 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: { @@ -77,7 +77,7 @@ function useMarkdownStyle(message: string | null = null, excludeStyles: Array Date: Tue, 14 May 2024 13:56:19 -0700 Subject: [PATCH 138/174] Use coalesce / fix dependencies --- .../QuickbooksOutOfPocketExpenseEntitySelectPage.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx index 51f53744de16..a45166430a4b 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksOutOfPocketExpenseEntitySelectPage.tsx @@ -42,7 +42,7 @@ function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyConnec keyForList: CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.CHECK, isSelected: reimbursableExpensesExportDestination === CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.CHECK, isShown: !isLocationsEnabled, - accounts: bankAccounts || [], + accounts: bankAccounts ?? [], }, { value: CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY, @@ -50,7 +50,7 @@ function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyConnec keyForList: CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY, isSelected: reimbursableExpensesExportDestination === CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY, isShown: !isTaxesEnabled || isLocationsEnabled, - accounts: journalEntryAccounts || [], + accounts: journalEntryAccounts ?? [], }, { value: CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.VENDOR_BILL, @@ -58,7 +58,7 @@ 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 || [], + accounts: accountPayable ?? [], }, ], [reimbursableExpensesExportDestination, isTaxesEnabled, translate, isLocationsEnabled, bankAccounts, accountPayable, journalEntryAccounts], @@ -84,7 +84,7 @@ function QuickbooksOutOfPocketExpenseEntitySelectPage({policy}: WithPolicyConnec } Navigation.goBack(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES.getRoute(policyID)); }, - [reimbursableExpensesExportDestination, policyID], + [reimbursableExpensesExportDestination, policyID, reimbursableExpensesAccount], ); return ( From 645c47d5da22ff6b92bca370ac61c7ec24f123d7 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Tue, 14 May 2024 14:05:26 -0700 Subject: [PATCH 139/174] style --- ...uickbooksCompanyCardExpenseAccountPage.tsx | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx index 3d4e178de376..2cd9f97d9f70 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx @@ -66,19 +66,23 @@ function QuickbooksCompanyCardExpenseAccountPage({policy}: WithPolicyConnections title={translate('workspace.qbo.defaultVendor')} wrapperStyle={[styles.ph5, styles.mb3, styles.mt1]} isActive={Boolean(autoCreateVendor)} - 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, - })} + 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 && ( @@ -103,4 +107,4 @@ function QuickbooksCompanyCardExpenseAccountPage({policy}: WithPolicyConnections QuickbooksCompanyCardExpenseAccountPage.displayName = 'QuickbooksCompanyCardExpenseAccountPage'; -export default withPolicyConnections(QuickbooksCompanyCardExpenseAccountPage); \ No newline at end of file +export default withPolicyConnections(QuickbooksCompanyCardExpenseAccountPage); From 51af42e8162ec3305b97f99aadb53a35528e8f5f Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 15 May 2024 00:06:32 +0200 Subject: [PATCH 140/174] run prettier --- src/hooks/useMarkdownStyle.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/hooks/useMarkdownStyle.ts b/src/hooks/useMarkdownStyle.ts index 61f39144cd74..57d17ffefb0b 100644 --- a/src/hooks/useMarkdownStyle.ts +++ b/src/hooks/useMarkdownStyle.ts @@ -10,14 +10,17 @@ function useMarkdownStyle(message: string | null = null, excludeStyles: Array = useMemo(() =>({ - color: theme.text, - backgroundColor: 'transparent', - marginLeft: 0, - paddingLeft: 0, - borderColor: 'transparent', - borderWidth: 0, - }), [theme]); + const nonStylingDefaultValues: Record = useMemo( + () => ({ + color: theme.text, + backgroundColor: 'transparent', + marginLeft: 0, + paddingLeft: 0, + borderColor: 'transparent', + borderWidth: 0, + }), + [theme], + ); const markdownStyle = useMemo(() => { const styling = { From a1c249c09dc6c4390c1179a7954df330a6dc6b1d Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 15 May 2024 11:08:38 +0800 Subject: [PATCH 141/174] fix skin tone doesn't update --- src/pages/home/report/ReportActionItem.tsx | 10 ---------- src/pages/home/report/ReportActionItemMessageEdit.tsx | 8 +++----- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index ae62cac87d7e..e0a2aff74b2a 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -89,9 +89,6 @@ const getDraftMessage = (drafts: OnyxCollection, }; type ReportActionItemOnyxProps = { - /** Stores user's preferred skin tone */ - preferredSkinTone: OnyxEntry; - /** IOU report for this action, if any */ iouReport: OnyxEntry; @@ -173,7 +170,6 @@ function ReportActionItem({ iouReport, isMostRecentIOUReportAction, parentReportAction, - preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, shouldDisplayNewMarker, userWallet, shouldHideThreadDividerLine = false, @@ -672,8 +668,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)} /> )} @@ -1019,10 +1013,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); diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index eda4ef5b1033..7477c6c02eba 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(); From 83b9cc701b4fd5bf0164120850e38a7cd8de50bf Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 15 May 2024 16:22:02 +0700 Subject: [PATCH 142/174] do not use negated variable name --- src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index 4f1cb550cf05..fe56b1191ff3 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -136,11 +136,9 @@ 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); + const shouldShowPage = member && (member.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || prevMember?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); - if (shouldShowNotFoundPage) { + if (!shouldShowPage) { return ; } From ef878d43f4dbccc4ae1b650921239a8311d254d4 Mon Sep 17 00:00:00 2001 From: Yauheni Date: Wed, 15 May 2024 11:40:43 +0200 Subject: [PATCH 143/174] Update IOUTest and remove @ts-expect-error --- tests/actions/IOUTest.ts | 1860 +++++++++++++++++++------------------- 1 file changed, 906 insertions(+), 954 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 53753e79cbcc..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,8 +61,10 @@ describe('actions/IOU', () => { }); }); + let mockFetch: MockFetch; beforeEach(() => { global.fetch = TestHelper.getGlobalFetchMock(); + mockFetch = fetch as MockFetch; return Onyx.clear().then(waitForBatchedUpdates); }); @@ -76,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', () => { @@ -268,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', () => { @@ -468,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', () => { @@ -620,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() @@ -732,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( () => @@ -923,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) ); }); }); @@ -1062,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(); + }, + }); + }), + ); }); }); @@ -1425,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(); + }, + }); + }), + ); }); }); @@ -1617,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', () => { @@ -1627,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(() => { @@ -1771,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?.(); }); }); @@ -1838,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( @@ -1932,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(() => { @@ -2098,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); } @@ -2249,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 @@ -2289,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 @@ -2325,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 @@ -2349,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) => { @@ -2402,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); } @@ -2429,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({ @@ -2494,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) { @@ -2517,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) => { @@ -2686,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 @@ -2710,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}`, @@ -2834,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); @@ -2858,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] @@ -2908,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); @@ -2924,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(); @@ -3001,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); @@ -3027,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({ @@ -3354,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); } From 027b40e4a810c969103d8ea96188cad053ec4c12 Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 15 May 2024 13:04:25 +0200 Subject: [PATCH 144/174] Address review comments --- src/components/MoneyReportHeader.tsx | 2 +- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 2 +- src/components/ReportActionItem/ReportPreview.tsx | 4 ++-- src/libs/TransactionUtils.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 642985bceaea..69cfbc150b98 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -257,7 +257,7 @@ function MoneyReportHeader({ src={Expensicons.Hourglass} height={variables.iconSizeSmall} width={variables.iconSizeSmall} - fill={theme.textSupporting} + fill={theme.icon} /> } description={translate('iou.pendingMatchWithCreditCardDescription')} diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 5ef7051ba1bb..544851d28fbe 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -333,7 +333,7 @@ function MoneyRequestPreviewContent({ src={pendingMessageProps.messageIcon} height={variables.iconSizeExtraSmall} width={variables.iconSizeExtraSmall} - fill={theme.textSupporting} + fill={theme.icon} /> {pendingMessageProps.messageDescription} diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 2a22f696222f..3d2513517a6f 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -142,7 +142,7 @@ function ReportPreview({ const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3); const lastThreeReceipts = lastThreeTransactionsWithReceipts.map((transaction) => ReceiptUtils.getThumbnailAndImageURIs(transaction)); const showRTERViolationMessage = - allTransactions.length === 1 && TransactionUtils.hasPendingUI(allTransactions[0], TransactionUtils.getTransactionViolations(allTransactions[0].transactionID, transactionViolations)); + 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; @@ -338,7 +338,7 @@ function ReportPreview({ src={pendingMessageProps.messageIcon} height={variables.iconSizeExtraSmall} width={variables.iconSizeExtraSmall} - fill={theme.textSupporting} + fill={theme.icon} /> {pendingMessageProps.messageDescription} diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 48c225b68086..b1a8aea521b1 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -536,7 +536,7 @@ function allHavePendingRTERViolation(transactionIds: string[]): boolean { const transactionViolations = getTransactionViolations(transactionId, allTransactionViolations); return hasPendingRTERViolation(transactionViolations); }); - return transactionsWithRTERViolations.length !== 0 && transactionsWithRTERViolations.every((value) => value === true); + return transactionsWithRTERViolations.length > 0 && transactionsWithRTERViolations.every((value) => value === true); } /** From beee045e9637e617916c1eb47205c570b56a0bd6 Mon Sep 17 00:00:00 2001 From: Anusha Date: Wed, 15 May 2024 16:06:15 +0500 Subject: [PATCH 145/174] fix small screen navigation --- src/pages/home/ReportScreen.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 31fdcfcab6db..321b47867832 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -517,7 +517,6 @@ function ReportScreen({ const didReportClose = wasReportRemoved && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED; const isTopLevelPolicyRoomWithNoStatus = !report.statusNum && !prevReport.parentReportID && prevReport.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM; const isClosedTopLevelPolicyRoom = wasReportRemoved && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && isTopLevelPolicyRoomWithNoStatus; - // Navigate to the Concierge chat if the room was removed from another device (e.g. user leaving a room or removed from a room) if ( // non-optimistic case @@ -540,6 +539,10 @@ function ReportScreen({ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(prevReport.parentReportID)); return; } + // If the report isn't focused, navigation to Concierge Chat should be avoided. + if (!isFocused) { + return; + } Report.navigateToConciergeChat(); return; } @@ -567,6 +570,7 @@ function ReportScreen({ prevReport.chatType, prevReport, reportIDFromRoute, + isFocused, ]); useEffect(() => { From c1eb93d24bdf4920cdbdfc6446adf5f40f570575 Mon Sep 17 00:00:00 2001 From: Anusha Date: Wed, 15 May 2024 16:38:59 +0500 Subject: [PATCH 146/174] cleanup --- src/pages/home/ReportScreen.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 321b47867832..140ef14d7e47 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -525,6 +525,10 @@ function ReportScreen({ isRemovalExpectedForReportType || isClosedTopLevelPolicyRoom ) { + // If the report isn't focused, navigation to Concierge Chat should be avoided. + if (!isFocused) { + return; + } Navigation.dismissModal(); if (Navigation.getTopmostReportId() === prevOnyxReportID) { Navigation.setShouldPopAllStateOnUP(); @@ -539,10 +543,7 @@ function ReportScreen({ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(prevReport.parentReportID)); return; } - // If the report isn't focused, navigation to Concierge Chat should be avoided. - if (!isFocused) { - return; - } + Report.navigateToConciergeChat(); return; } From 40f471c2079e4e0bec179358dc5cda7867d58eb3 Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 15 May 2024 14:26:21 +0200 Subject: [PATCH 147/174] Reuse MoneyRequestHeaderStatusBar --- src/components/MoneyRequestHeader.tsx | 52 +++++++++---------- .../MoneyRequestHeaderStatusBar.tsx | 2 + .../MoneyRequestPreviewContent.tsx | 3 +- .../MoneyRequestPreview/types.ts | 17 +++++- .../ReportActionItem/ReportPreview.tsx | 2 +- src/libs/ReportUtils.ts | 15 ------ 6 files changed, 46 insertions(+), 45 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 346fd92f9613..98d023f28e75 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -1,3 +1,4 @@ +import type {ReactNode} from 'react'; import React, {useCallback, useEffect, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -10,7 +11,6 @@ import * as HeaderUtils from '@libs/HeaderUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import type {PendingMessageProps} from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import variables from '@styles/variables'; import * as IOU from '@userActions/IOU'; @@ -19,10 +19,12 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; 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'; @@ -124,17 +126,29 @@ function MoneyRequestHeader({ } }; - const getStatusBarProps: () => PendingMessageProps = () => { + 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 {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPendingDescription')}; + return {title: getStatusIcon(Expensicons.CreditCardHourglass), description: translate('iou.transactionPendingDescription'), shouldShowBorderBottom: true}; } if (isScanning) { - return {shouldShow: true, messageIcon: Expensicons.ReceiptScan, messageDescription: translate('iou.receiptScanInProgressDescription')}; + return {title: getStatusIcon(Expensicons.ReceiptScan), description: translate('iou.receiptScanInProgressDescription'), shouldShowBorderBottom: true}; } if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) { - return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCardDescription')}; + return {title: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription'), shouldShowBorderBottom: true}; } - return {shouldShow: false}; }; const statusBarProps = getStatusBarProps(); @@ -203,7 +217,7 @@ function MoneyRequestHeader({ <> - {statusBarProps.shouldShow && ( - - } - description={statusBarProps.messageDescription} - shouldShowBorderBottom={!isOnHold} - /> - )} - {isOnHold && ( + {statusBarProps && ( )} 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/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 544851d28fbe..4ff318bc3c47 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -27,7 +27,6 @@ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import type {PendingMessageProps} from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import variables from '@styles/variables'; @@ -37,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, diff --git a/src/components/ReportActionItem/MoneyRequestPreview/types.ts b/src/components/ReportActionItem/MoneyRequestPreview/types.ts index 0e3eb37ce6e3..2420611c44e6 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,18 @@ type MoneyRequestPreviewProps = MoneyRequestPreviewOnyxProps & { isWhisper?: boolean; }; -export type {MoneyRequestPreviewProps, MoneyRequestPreviewOnyxProps}; +type NoPendingProps = {shouldShow: false}; + +type PendingProps = { + 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/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 3d2513517a6f..dae827cdb5c0 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -22,7 +22,6 @@ import Navigation from '@libs/Navigation/Navigation'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import type {PendingMessageProps} from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import variables from '@styles/variables'; @@ -33,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 = { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 85893eebbcbc..650b7abcc98f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -488,20 +488,6 @@ type ParsingDetails = { reportID?: string; }; -type NoPendingProps = {shouldShow: false}; - -type PendingProps = { - 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; - let currentUserEmail: string | undefined; let currentUserPrivateDomain: string | undefined; let currentUserAccountID: number | undefined; @@ -6857,5 +6843,4 @@ export type { TransactionDetails, OptimisticInviteReportAction, ParsingDetails, - PendingMessageProps, }; From 6a0e24f82568527f149e146b96589238ba11cc5f Mon Sep 17 00:00:00 2001 From: Anusha Date: Wed, 15 May 2024 20:14:12 +0500 Subject: [PATCH 148/174] add comment --- src/pages/home/ReportScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 140ef14d7e47..8620d8d4866e 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -525,7 +525,7 @@ function ReportScreen({ isRemovalExpectedForReportType || isClosedTopLevelPolicyRoom ) { - // If the report isn't focused, navigation to Concierge Chat should be avoided. + // Early return if the report we're passing isn't in a focused state. We only want to navigate to Concierge if the user leaves the room from another device or gets removed from the room while the report is in a focused state. if (!isFocused) { return; } From 1855b0173913c277ed1aba05070faa91d3541019 Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 15 May 2024 17:41:43 +0200 Subject: [PATCH 149/174] Add JSDoc comment --- src/components/ReportActionItem/MoneyRequestPreview/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/types.ts b/src/components/ReportActionItem/MoneyRequestPreview/types.ts index 2420611c44e6..9dcea80fdc05 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/types.ts +++ b/src/components/ReportActionItem/MoneyRequestPreview/types.ts @@ -75,6 +75,7 @@ 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 */ From d86a8568a38088dc3e821ff5d8153c02b50feadc Mon Sep 17 00:00:00 2001 From: cretadn22 Date: Thu, 16 May 2024 00:10:09 +0700 Subject: [PATCH 150/174] using else statement --- src/pages/OnboardingWork/BaseOnboardingWork.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/OnboardingWork/BaseOnboardingWork.tsx b/src/pages/OnboardingWork/BaseOnboardingWork.tsx index dc840dfa480f..111f960431ed 100644 --- a/src/pages/OnboardingWork/BaseOnboardingWork.tsx +++ b/src/pages/OnboardingWork/BaseOnboardingWork.tsx @@ -45,9 +45,9 @@ function BaseOnboardingWork({shouldUseNativeStyles, onboardingPurposeSelected, o const {adminsChatReportID, policyID} = Policy.createWorkspace(undefined, true, work); Welcome.setOnboardingAdminsChatReportID(adminsChatReportID); Welcome.setOnboardingPolicyID(policyID); + } else { + Policy.updateGeneralSettings(onboardingPolicyID as string, work); } - // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style - Policy.updateGeneralSettings(onboardingPolicyID as string, work); Navigation.navigate(ROUTES.ONBOARDING_PERSONAL_DETAILS); }, From 5f8e897b88ad00a2289493afff3b78d64f4145cb Mon Sep 17 00:00:00 2001 From: cretadn22 Date: Thu, 16 May 2024 00:27:05 +0700 Subject: [PATCH 151/174] fix the lint error --- src/pages/OnboardingWork/BaseOnboardingWork.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/OnboardingWork/BaseOnboardingWork.tsx b/src/pages/OnboardingWork/BaseOnboardingWork.tsx index 111f960431ed..04f2f27d1df1 100644 --- a/src/pages/OnboardingWork/BaseOnboardingWork.tsx +++ b/src/pages/OnboardingWork/BaseOnboardingWork.tsx @@ -46,7 +46,7 @@ function BaseOnboardingWork({shouldUseNativeStyles, onboardingPurposeSelected, o Welcome.setOnboardingAdminsChatReportID(adminsChatReportID); Welcome.setOnboardingPolicyID(policyID); } else { - Policy.updateGeneralSettings(onboardingPolicyID as string, work); + Policy.updateGeneralSettings(onboardingPolicyID, work); } Navigation.navigate(ROUTES.ONBOARDING_PERSONAL_DETAILS); From 02d87f414d90978c18657550455e87da6bd7fdbf Mon Sep 17 00:00:00 2001 From: smelaa Date: Wed, 15 May 2024 19:27:55 +0200 Subject: [PATCH 152/174] Do not display next step banner in case of pending rter violation --- src/components/MoneyReportHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 69cfbc150b98..4eb3be871a8d 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -126,7 +126,7 @@ function MoneyReportHeader({ 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); From 89dd783b53ab9439ac53c5c63b4cfb8920fb6b70 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Wed, 15 May 2024 12:07:40 -0700 Subject: [PATCH 153/174] Fix translations used --- .../qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx | 2 +- .../export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx index 6e9ad2800c0d..46094880ad7a 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx @@ -51,7 +51,7 @@ function QuickbooksCompanyCardExpenseAccountPage({policy}: WithPolicyConnections Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT.getRoute(policyID))} brickRoadIndicator={errorFields?.nonReimbursableExpensesAccount ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx index 4cd7617b4319..98d81a480ddd 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountSelectPage.tsx @@ -72,7 +72,7 @@ function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyConne From 3c175f0da70861d6ad702aea28b534027a2ffad3 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Wed, 15 May 2024 12:07:55 -0700 Subject: [PATCH 154/174] style --- .../qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx index 46094880ad7a..f0f1246dc631 100644 --- a/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx +++ b/src/pages/workspace/accounting/qbo/export/QuickbooksCompanyCardExpenseAccountPage.tsx @@ -51,7 +51,11 @@ function QuickbooksCompanyCardExpenseAccountPage({policy}: WithPolicyConnections Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT.getRoute(policyID))} brickRoadIndicator={errorFields?.nonReimbursableExpensesAccount ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} From e1d69f64b0251b15c58e320cf99ab275f13aebcf Mon Sep 17 00:00:00 2001 From: Ishpaul Singh <104348397+ishpaul777@users.noreply.github.com> Date: Thu, 16 May 2024 01:51:44 +0530 Subject: [PATCH 155/174] Add-empty-newlines Co-authored-by: Yuwen Memon --- .../Attachments/AttachmentView/DefaultAttachmentView/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx index 6130aeed866c..06d53027b97a 100644 --- a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx @@ -12,10 +12,13 @@ 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; }; From 569b7c121e14e74fb197cbbcbf283f2fa6f39bb7 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 16 May 2024 01:58:06 +0530 Subject: [PATCH 156/174] adds requested changes --- .../AttachmentView/DefaultAttachmentView/index.tsx | 2 +- src/components/Attachments/AttachmentView/index.tsx | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx index 06d53027b97a..e06ea3064150 100644 --- a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx @@ -12,7 +12,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; type DefaultAttachmentViewProps = { /** The name of the file */ fileName?: string; - + /** Should show the download icon */ shouldShowDownloadIcon?: boolean; diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index cdec6f633ade..cee2264894a7 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -99,7 +99,7 @@ function AttachmentView({ const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [loadComplete, setLoadComplete] = useState(false); - const [isPdfFailedToLoad, setIsPdfFailedToLoad] = useState(false); + const [hasPDFFailedToLoad, setHasPDFFailedToLoad] = useState(false); const isVideo = (typeof source === 'string' && Str.isVideo(source)) || (file?.name && Str.isVideo(file.name)); useEffect(() => { @@ -150,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 (!isPdfFailedToLoad && ((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) => { @@ -164,7 +166,7 @@ function AttachmentView({ }; const onPDFLoadError = () => { - setIsPdfFailedToLoad(true); + setHasPDFFailedToLoad(true); }; // We need the following View component on android native From c109ef6e1d9ff15e536e3bbd245c47ff28c61847 Mon Sep 17 00:00:00 2001 From: Anusha Date: Thu, 16 May 2024 02:28:45 +0500 Subject: [PATCH 157/174] fix workspace switcher --- src/pages/WorkspaceSwitcherPage/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/WorkspaceSwitcherPage/index.tsx b/src/pages/WorkspaceSwitcherPage/index.tsx index b01a78b65317..1729e57a7c3b 100644 --- a/src/pages/WorkspaceSwitcherPage/index.tsx +++ b/src/pages/WorkspaceSwitcherPage/index.tsx @@ -23,7 +23,7 @@ import WorkspacesSectionHeader from './WorkspacesSectionHeader'; type WorkspaceListItem = { text: string; - policyID: string; + policyID?: string; isPolicyAdmin?: boolean; brickRoadIndicator?: BrickRoad; } & ListItem; @@ -145,7 +145,7 @@ function WorkspaceSwitcherPage() { data: [ { text: CONST.WORKSPACE_SWITCHER.NAME, - policyID: '', + policyID: undefined, icons: [{source: Expensicons.ExpensifyAppIcon, name: CONST.WORKSPACE_SWITCHER.NAME, type: CONST.ICON_TYPE_AVATAR}], brickRoadIndicator: getIndicatorTypeForPolicy(undefined), isSelected: activeWorkspaceID === undefined, From 295694f62b4d2d68340949e9f1b3b1eb312880ca Mon Sep 17 00:00:00 2001 From: Anusha Date: Thu, 16 May 2024 02:37:37 +0500 Subject: [PATCH 158/174] fix switcher --- src/pages/WorkspaceSwitcherPage/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/WorkspaceSwitcherPage/index.tsx b/src/pages/WorkspaceSwitcherPage/index.tsx index 1729e57a7c3b..d9b9d62bb539 100644 --- a/src/pages/WorkspaceSwitcherPage/index.tsx +++ b/src/pages/WorkspaceSwitcherPage/index.tsx @@ -145,7 +145,6 @@ function WorkspaceSwitcherPage() { data: [ { text: CONST.WORKSPACE_SWITCHER.NAME, - policyID: undefined, icons: [{source: Expensicons.ExpensifyAppIcon, name: CONST.WORKSPACE_SWITCHER.NAME, type: CONST.ICON_TYPE_AVATAR}], brickRoadIndicator: getIndicatorTypeForPolicy(undefined), isSelected: activeWorkspaceID === undefined, From bb5ddc1c0a29740893db0a62dd032dca2f692970 Mon Sep 17 00:00:00 2001 From: James Dean Date: Wed, 15 May 2024 14:43:17 -0700 Subject: [PATCH 159/174] Update en.ts minor copy update --- src/languages/en.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 6d1366f0e430..5d16e8f2d56a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1298,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.', From d4621a1e90e841115b05df6dfc7489df859e484e Mon Sep 17 00:00:00 2001 From: James Dean Date: Wed, 15 May 2024 14:49:03 -0700 Subject: [PATCH 160/174] Update en.ts minor copy update --- src/languages/en.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 6d1366f0e430..541d2ad3b094 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2979,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.`, From 35a2d789cac996e80a11ac972c98f2f5adc2f656 Mon Sep 17 00:00:00 2001 From: James Dean Date: Wed, 15 May 2024 14:51:43 -0700 Subject: [PATCH 161/174] Update es.ts --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 9c4d33664c98..536a6182f393 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3480,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.`, From f5fb0f8bb5750b7882e0e5ce2912e8fa76ae3360 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Wed, 15 May 2024 22:16:18 +0000 Subject: [PATCH 162/174] Update version to 1.4.74-1 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index abdc6765ec1a..2bc1c64686f8 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 1001047401 + versionName "1.4.74-1" // 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/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index e318f45b4a6f..0bdc1f6fadd5 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.74.0 + 1.4.74.1 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 065ef2041e6e..6a8a4d9744f1 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.74.0 + 1.4.74.1 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index afe45f0e2591..2d5b18353afd 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.1 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index d4169447b963..4701ff7e7c21 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-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.74-0", + "version": "1.4.74-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 6a61cffc24a5..c2dd1d2d03b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.74-0", + "version": "1.4.74-1", "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.", From 1987ad101b154bfa1f4824392785a30807066fb3 Mon Sep 17 00:00:00 2001 From: Sheena Trepanier Date: Wed, 15 May 2024 15:28:37 -0700 Subject: [PATCH 163/174] Update redirects.csv Redirects needed for https://github.com/Expensify/App/pull/42228 to delete an article. Part of https://github.com/Expensify/Expensify/issues/381301. --- docs/redirects.csv | 1 + 1 file changed, 1 insertion(+) 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 From cb11999d2c3f71aa27125f495011060e124a8708 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 16 May 2024 08:47:48 +0700 Subject: [PATCH 164/174] fix Split amount is not selected --- src/components/MoneyRequestAmountInput.tsx | 1 + src/components/TextInputWithCurrencySymbol/types.ts | 5 +++++ 2 files changed, 6 insertions(+) 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/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; From 313dbffd724b07febb2734e2f6efcb8499844c52 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 16 May 2024 09:29:16 +0700 Subject: [PATCH 165/174] fix: missing personalDetails while creating options list --- src/components/OptionListContextProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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}; From f913df57a51f5f20c1aee6feb660b047281f01e6 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 16 May 2024 10:13:50 +0700 Subject: [PATCH 166/174] Revert "do not use negated variable name" This reverts commit 83b9cc701b4fd5bf0164120850e38a7cd8de50bf. --- src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index fe56b1191ff3..4f1cb550cf05 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -136,9 +136,11 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM Navigation.navigate(ROUTES.WORKSPACE_OWNER_CHANGE_CHECK.getRoute(policyID, accountID, 'amountOwed' as ValueOf)); }, [accountID, policyID]); - const shouldShowPage = member && (member.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || prevMember?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + // 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 (!shouldShowPage) { + if (shouldShowNotFoundPage) { return ; } From 3fb73919f5635906ddcc8d1587635f7b226e2b6b Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 16 May 2024 14:48:02 +0800 Subject: [PATCH 167/174] only parse the new description that user enter --- src/libs/actions/Task.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 89d5b46408f7..5c37b5cc927c 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[] = [ { From fe02e72df4d279dc6e8e14c10d01905c07fa5b12 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 16 May 2024 15:06:20 +0800 Subject: [PATCH 168/174] prettier --- src/libs/actions/Task.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 5c37b5cc927c..1d4415f72f4b 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -440,7 +440,7 @@ 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 newDescription = description ? ReportUtils.getParsedComment(description) : report.description + const newDescription = description ? ReportUtils.getParsedComment(description) : report.description; const reportDescription = (newDescription ?? '').trim(); const optimisticData: OnyxUpdate[] = [ From 1faad15adcc79d796a8e065cc586615b755e8473 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 16 May 2024 10:54:31 +0000 Subject: [PATCH 169/174] Update version to 1.4.74-2 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2bc1c64686f8..31ab1a9c735d 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 1001047401 - versionName "1.4.74-1" + versionCode 1001047402 + versionName "1.4.74-2" // 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/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 0bdc1f6fadd5..c18abc9b412a 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.74.1 + 1.4.74.2 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 6a8a4d9744f1..b2b7d8c5af4e 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.74.1 + 1.4.74.2 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 2d5b18353afd..caea170441e6 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.74 CFBundleVersion - 1.4.74.1 + 1.4.74.2 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 4701ff7e7c21..5e6a170c59d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.74-1", + "version": "1.4.74-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.74-1", + "version": "1.4.74-2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index c2dd1d2d03b1..c8951f481804 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.74-1", + "version": "1.4.74-2", "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.", From 6bc2e31638a051ee5801e26e19844888a50d73bb Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 16 May 2024 19:08:00 +0800 Subject: [PATCH 170/174] parse the task description when creating a new task --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c71d39a47cd4..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, From fb14d5a4970374a2377483e70eb5f974354570b5 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 16 May 2024 12:24:20 +0000 Subject: [PATCH 171/174] Update version to 1.4.74-3 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 31ab1a9c735d..ea023a07bb05 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 1001047402 - versionName "1.4.74-2" + versionCode 1001047403 + versionName "1.4.74-3" // 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/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index c18abc9b412a..96258029c26a 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.74.2 + 1.4.74.3 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index b2b7d8c5af4e..1ff08e279b19 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.74.2 + 1.4.74.3 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index caea170441e6..a26aa84872c8 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.74 CFBundleVersion - 1.4.74.2 + 1.4.74.3 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 5e6a170c59d6..2cc1b783cfcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.74-2", + "version": "1.4.74-3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.74-2", + "version": "1.4.74-3", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index c8951f481804..8c6baebda42d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.74-2", + "version": "1.4.74-3", "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.", From 809a6ead60bd59d78f5fca676753846d648c4309 Mon Sep 17 00:00:00 2001 From: laurenreidexpensify <62073721+laurenreidexpensify@users.noreply.github.com> Date: Thu, 16 May 2024 13:29:12 +0100 Subject: [PATCH 172/174] Update Unlimited-Virtual-Cards.md --- .../expensify-card/Unlimited-Virtual-Cards.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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%"} From f1a2a60ad6fc049f730f6866eed8324b25a0035c Mon Sep 17 00:00:00 2001 From: laurenreidexpensify <62073721+laurenreidexpensify@users.noreply.github.com> Date: Thu, 16 May 2024 13:40:16 +0100 Subject: [PATCH 173/174] Update Referral-Program.md --- .../expensify-partner-program/Referral-Program.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From ca7dc1b35243a62dfbd7144263641b89a7da8ea1 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 16 May 2024 13:06:03 +0000 Subject: [PATCH 174/174] Update version to 1.4.74-4 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index ea023a07bb05..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 1001047403 - versionName "1.4.74-3" + 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/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 96258029c26a..9c9e78ef70b0 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.74.3 + 1.4.74.4 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 1ff08e279b19..627c6dbf826c 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.74.3 + 1.4.74.4 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index a26aa84872c8..477f83aa6d00 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.74 CFBundleVersion - 1.4.74.3 + 1.4.74.4 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 2cc1b783cfcc..a46198daa88a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.74-3", + "version": "1.4.74-4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.74-3", + "version": "1.4.74-4", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 8c6baebda42d..1a449f42bece 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.74-3", + "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.",