From 8af05e06d6597d6f740a6aa25aae4e3664d9ff0d Mon Sep 17 00:00:00 2001 From: Situ Chandra Shil <108292595+situchan@users.noreply.github.com> Date: Fri, 23 Jan 2026 05:13:19 +1100 Subject: [PATCH] Revert "[No-QA] test: Clean up SettlementButton" --- src/components/SettlementButton/index.tsx | 411 +++--- tests/ui/components/SettlementButtonTest.tsx | 1169 ------------------ 2 files changed, 242 insertions(+), 1338 deletions(-) delete mode 100644 tests/ui/components/SettlementButtonTest.tsx diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index 2d7071ba7b899..ab5ae68d523c1 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -1,6 +1,7 @@ import {isUserValidatedSelector} from '@selectors/Account'; +import isEmpty from 'lodash/isEmpty'; import truncate from 'lodash/truncate'; -import React, {useContext} from 'react'; +import React, {useCallback, useContext, useMemo} from 'react'; import type {GestureResponderEvent} from 'react-native'; import type {TupleToUnion} from 'type-fest'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; @@ -41,11 +42,16 @@ import {approveMoneyRequest} from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {AccountData, BankAccount, LastPaymentMethodType} from '@src/types/onyx'; +import type {AccountData, BankAccount, LastPaymentMethodType, Policy} from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import type SettlementButtonProps from './types'; +type KYCFlowEvent = GestureResponderEvent | KeyboardEvent | undefined; + +type TriggerKYCFlow = (params: ContinueActionParams) => void; + type CurrencyType = TupleToUnion; function SettlementButton({ addDebitCardRoute = ROUTES.IOU_SEND_ADD_DEBIT_CARD, @@ -107,12 +113,16 @@ function SettlementButton({ const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET, {canBeMissing: true}); const hasActivatedWallet = ([CONST.WALLET.TIER_NAME.GOLD, CONST.WALLET.TIER_NAME.PLATINUM] as string[]).includes(userWallet?.tierName ?? ''); const paymentMethods = useSettlementButtonPaymentMethods(hasActivatedWallet, translate); - const [lastPaymentMethods] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true}); + const [lastPaymentMethods, lastPaymentMethodResult] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true}); const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID, {canBeMissing: true}); - const lastPaymentMethod = iouReport?.type - ? getLastPolicyPaymentMethod(policyIDKey, personalPolicyID, lastPaymentMethods, iouReport?.type as keyof LastPaymentMethodType, isIOUReport(iouReport)) - : undefined; + const lastPaymentMethod = useMemo(() => { + if (!iouReport?.type) { + return; + } + + return getLastPolicyPaymentMethod(policyIDKey, personalPolicyID, lastPaymentMethods, iouReport?.type as keyof LastPaymentMethodType, isIOUReport(iouReport)); + }, [policyIDKey, iouReport, lastPaymentMethods, personalPolicyID]); const lastBankAccountID = getLastPolicyBankAccountID(policyIDKey, lastPaymentMethods, iouReport?.type as keyof LastPaymentMethodType); const [fundList] = useOnyx(ONYXKEYS.FUND_LIST, {canBeMissing: true}); @@ -126,6 +136,7 @@ function SettlementButton({ const personalPolicy = usePolicy(personalPolicyID); const hasPreferredPaymentMethod = !!lastPaymentMethod; + const isLoadingLastPaymentMethod = isLoadingOnyxValue(lastPaymentMethodResult); const lastPaymentPolicy = usePolicy(lastPaymentMethod); const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true}); @@ -151,35 +162,35 @@ function SettlementButton({ const shouldShowPayWithExpensifyOption = !shouldHidePaymentOptions; const shouldShowPayElsewhereOption = !shouldHidePaymentOptions && !isInvoiceReport; - const personalBankAccountList: typeof formattedPaymentMethods = []; - const policyBankAccounts: typeof formattedPaymentMethods = []; - for (const method of formattedPaymentMethods) { - const accountData = method.accountData as AccountData | undefined; - if (accountData?.type === CONST.BANK_ACCOUNT.TYPE.PERSONAL) { - personalBankAccountList.push(method); - } - if (policy?.achAccount?.bankAccountID && method.methodID === policy.achAccount.bankAccountID && accountData?.state === CONST.BANK_ACCOUNT.STATE.OPEN) { - policyBankAccounts.push(method); + function getLatestBankAccountItem() { + if (!policy?.achAccount?.bankAccountID) { + return; } + const policyBankAccounts = formattedPaymentMethods.filter( + (method) => method.methodID === policy?.achAccount?.bankAccountID && (method.accountData as AccountData)?.state === CONST.BANK_ACCOUNT.STATE.OPEN, + ); + + return policyBankAccounts.map((formattedPaymentMethod) => { + const {icon, iconStyles, iconSize, title, description, methodID} = formattedPaymentMethod ?? {}; + + return { + text: title ?? '', + description: description ?? '', + icon: typeof icon === 'number' ? icons.Bank : icon, + iconStyles: typeof icon === 'number' ? undefined : iconStyles, + iconSize: typeof icon === 'number' ? undefined : iconSize, + onSelected: () => onPress(CONST.IOU.PAYMENT_TYPE.EXPENSIFY, true, undefined), + methodID, + value: CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT, + }; + }); } - const latestBankItems = policyBankAccounts.map((formattedPaymentMethod) => { - const {icon, iconStyles, iconSize, title, description, methodID} = formattedPaymentMethod ?? {}; - - return { - text: title ?? '', - description: description ?? '', - icon: typeof icon === 'number' ? icons.Bank : icon, - iconStyles: typeof icon === 'number' ? undefined : iconStyles, - iconSize: typeof icon === 'number' ? undefined : iconSize, - onSelected: () => onPress(CONST.IOU.PAYMENT_TYPE.EXPENSIFY, true, undefined), - methodID, - value: CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT, - }; - }); - const firstBankItem = latestBankItems.at(0); + function getLatestPersonalBankAccount() { + return formattedPaymentMethods.filter((ba) => (ba.accountData as AccountData)?.type === CONST.BANK_ACCOUNT.TYPE.PERSONAL); + } - const checkForNecessaryAction = () => { + const checkForNecessaryAction = useCallback(() => { if (isDelegateAccessRestricted) { showDelegateNoAccessModal(); return true; @@ -201,75 +212,112 @@ function SettlementButton({ } return false; - }; - - const approveButtonOption = { - text: translate('iou.approve', {formattedAmount}), - icon: icons.ThumbsUp, - value: CONST.IOU.REPORT_ACTION_TYPE.APPROVE, - disabled: !!shouldDisableApproveButton, - }; + }, [policy, isAccountLocked, isUserValidated, chatReportID, reportID, showLockedAccountModal, isDelegateAccessRestricted, showDelegateNoAccessModal]); + + const getPaymentSubitems = useCallback( + (payAsBusiness: boolean) => { + const requiredAccountType = payAsBusiness ? CONST.BANK_ACCOUNT.TYPE.BUSINESS : CONST.BANK_ACCOUNT.TYPE.PERSONAL; + + return formattedPaymentMethods + .filter((method) => { + const accountData = method?.accountData as AccountData; + return accountData?.type === requiredAccountType; + }) + .map((formattedPaymentMethod) => ({ + text: formattedPaymentMethod?.title ?? '', + description: formattedPaymentMethod?.description ?? '', + icon: formattedPaymentMethod?.icon, + shouldUpdateSelectedIndex: true, + onSelected: () => { + if (checkForNecessaryAction()) { + return; + } + onPress(CONST.IOU.PAYMENT_TYPE.EXPENSIFY, payAsBusiness, formattedPaymentMethod.methodID, formattedPaymentMethod.accountType, undefined); + }, + iconStyles: formattedPaymentMethod?.iconStyles, + iconHeight: formattedPaymentMethod?.iconSize, + iconWidth: formattedPaymentMethod?.iconSize, + value: CONST.IOU.PAYMENT_TYPE.EXPENSIFY, + })); + }, + [formattedPaymentMethods, onPress, checkForNecessaryAction], + ); - const canUseWallet = !isExpenseReport && !isInvoiceReport && isCurrencySupportedForGlobalReimbursement(currency as CurrencyType); - const canUseBusinessBankAccount = isExpenseReport || (isIOUReport(iouReport) && reportID && !hasRequestFromCurrentAccount(reportID, accountID ?? CONST.DEFAULT_NUMBER_ID)); - const canUsePersonalBankAccount = shouldShowPersonalBankAccountOption || isIOUReport(iouReport); - const isPersonalOnlyOption = canUsePersonalBankAccount && !canUseBusinessBankAccount; + const personalBankAccountList = getLatestPersonalBankAccount(); + const latestBankItem = getLatestBankAccountItem(); - let paymentButtonOptions: Array>; + const paymentButtonOptions = useMemo(() => { + const buttonOptions: Array> = []; - // Only show the Approve button if the user cannot pay the expense - if (shouldHidePaymentOptions && shouldShowApproveButton) { - paymentButtonOptions = [approveButtonOption]; - } else if (onlyShowPayElsewhere) { const shortFormPayElsewhereButton = { text: translate('iou.pay'), icon: icons.CheckCircle, value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, shouldUpdateSelectedIndex: false, }; - paymentButtonOptions = [shouldUseShortForm ? shortFormPayElsewhereButton : paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]]; - } else { - paymentButtonOptions = []; + + const approveButtonOption = { + text: translate('iou.approve', {formattedAmount}), + icon: icons.ThumbsUp, + value: CONST.IOU.REPORT_ACTION_TYPE.APPROVE, + disabled: !!shouldDisableApproveButton, + }; + + const canUseWallet = !isExpenseReport && !isInvoiceReport && isCurrencySupportedForGlobalReimbursement(currency as CurrencyType); + const canUseBusinessBankAccount = isExpenseReport || (isIOUReport(iouReport) && reportID && !hasRequestFromCurrentAccount(reportID, accountID ?? CONST.DEFAULT_NUMBER_ID)); + + const canUsePersonalBankAccount = shouldShowPersonalBankAccountOption || isIOUReport(iouReport); + + const isPersonalOnlyOption = canUsePersonalBankAccount && !canUseBusinessBankAccount; + + // Only show the Approve button if the user cannot pay the expense + if (shouldHidePaymentOptions && shouldShowApproveButton) { + return [approveButtonOption]; + } + + if (onlyShowPayElsewhere) { + return [shouldUseShortForm ? shortFormPayElsewhereButton : paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE]]; + } // To achieve the one tap pay experience we need to choose the correct payment type as default. if (canUseWallet) { if (personalBankAccountList.length && canUsePersonalBankAccount) { - paymentButtonOptions.push({ + buttonOptions.push({ text: translate('iou.settleWallet', {formattedAmount: ''}), value: CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT, icon: icons.Wallet, }); } else if (canUsePersonalBankAccount) { - paymentButtonOptions.push(paymentMethods[CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT]); + buttonOptions.push(paymentMethods[CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT]); } if (activeAdminPolicies.length === 0 && !isPersonalOnlyOption) { - paymentButtonOptions.push(paymentMethods[CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT]); + buttonOptions.push(paymentMethods[CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT]); } } const shouldShowBusinessBankAccountOptions = isExpenseReport && shouldShowPayWithExpensifyOption && !isPersonalOnlyOption; if (shouldShowBusinessBankAccountOptions) { - if (firstBankItem) { - paymentButtonOptions.push({ - text: firstBankItem.text, - icon: firstBankItem.icon, - additionalIconStyles: firstBankItem.iconStyles, - iconWidth: firstBankItem.iconSize, - iconHeight: firstBankItem.iconSize, + if (!isEmpty(latestBankItem) && latestBankItem) { + buttonOptions.push({ + text: latestBankItem.at(0)?.text ?? '', + icon: latestBankItem.at(0)?.icon, + additionalIconStyles: latestBankItem.at(0)?.iconStyles, + iconWidth: latestBankItem.at(0)?.iconSize, + iconHeight: latestBankItem.at(0)?.iconSize, value: CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT, - description: firstBankItem.description, + description: latestBankItem.at(0)?.description, }); } else { - paymentButtonOptions.push(paymentMethods[CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT]); + buttonOptions.push(paymentMethods[CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT]); } } if ((hasMultiplePolicies || hasSinglePolicy) && canUseWallet && !isPersonalOnlyOption) { for (const p of activeAdminPolicies) { const policyName = p.name; - paymentButtonOptions.push({ + buttonOptions.push({ text: translate('iou.payWithPolicy', {policyName: truncate(policyName, {length: CONST.ADDITIONAL_ALLOWED_CHARACTERS}), formattedAmount: ''}), icon: icons.Building, value: p.id, @@ -279,9 +327,9 @@ function SettlementButton({ } if (shouldShowPayElsewhereOption) { - paymentButtonOptions.push({ + buttonOptions.push({ ...paymentMethods[CONST.IOU.PAYMENT_TYPE.ELSEWHERE], - ...(!paymentButtonOptions.length && shouldUseShortForm ? {text: translate('iou.pay')} : {}), + ...(!buttonOptions.length && shouldUseShortForm ? {text: translate('iou.pay')} : {}), }); } @@ -298,53 +346,29 @@ function SettlementButton({ // For individual receivers, allow if user has an active admin policy with supported currency OR user's local currency is supported const isPolicyCurrencySupported = invoiceReceiverPolicy ? isInvoiceReceiverPolicyCurrencySupported : canUseActivePolicy || isUserCurrencySupported; - const getPaymentSubitems = (payAsBusiness: boolean) => { - const requiredAccountType = payAsBusiness ? CONST.BANK_ACCOUNT.TYPE.BUSINESS : CONST.BANK_ACCOUNT.TYPE.PERSONAL; - - return formattedPaymentMethods - .filter((method) => { - const accountData = method?.accountData as AccountData; - return accountData?.type === requiredAccountType; - }) - .map((formattedPaymentMethod) => ({ - text: formattedPaymentMethod?.title ?? '', - description: formattedPaymentMethod?.description ?? '', - icon: formattedPaymentMethod?.icon, - shouldUpdateSelectedIndex: true, - onSelected: () => { - if (checkForNecessaryAction()) { - return; - } - onPress(CONST.IOU.PAYMENT_TYPE.EXPENSIFY, payAsBusiness, formattedPaymentMethod.methodID, formattedPaymentMethod.accountType, undefined); - }, - iconStyles: formattedPaymentMethod?.iconStyles, - iconHeight: formattedPaymentMethod?.iconSize, - iconWidth: formattedPaymentMethod?.iconSize, - value: CONST.IOU.PAYMENT_TYPE.EXPENSIFY, - })); - }; - const getInvoicesOptions = (payAsBusiness: boolean) => { - let invoicePolicyID: string | undefined; - if (chatReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS) { - invoicePolicyID = chatReport?.invoiceReceiver?.policyID; - } else if (canUseActivePolicy) { - invoicePolicyID = activePolicy.id; - } else { - invoicePolicyID = createWorkspace({ + const getPolicyID = () => { + if (chatReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS) { + return chatReport?.invoiceReceiver?.policyID; + } + + if (canUseActivePolicy) { + return activePolicy.id; + } + + return createWorkspace({ introSelected, activePolicyID, currentUserAccountIDParam: currentUserPersonalDetails.accountID, currentUserEmailParam: currentUserPersonalDetails.email ?? '', }).policyID; - } - + }; const addBankAccountItem = { text: translate('bankAccount.addBankAccount'), icon: icons.Bank, onSelected: () => { if (payAsBusiness) { - navigateToBankAccountRoute(invoicePolicyID); + navigateToBankAccountRoute(getPolicyID()); } else { Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT.route); } @@ -370,14 +394,14 @@ function SettlementButton({ }; if (isIndividualInvoiceRoomUtil(chatReport)) { - paymentButtonOptions.push({ + buttonOptions.push({ text: translate('iou.settlePersonal', {formattedAmount}), icon: icons.User, value: hasIntentToPay ? CONST.IOU.PAYMENT_TYPE.EXPENSIFY : (lastPaymentMethod ?? CONST.IOU.PAYMENT_TYPE.ELSEWHERE), backButtonText: translate('iou.individual'), subMenuItems: getInvoicesOptions(false), }); - paymentButtonOptions.push({ + buttonOptions.push({ text: translate('iou.settleBusiness', {formattedAmount}), icon: icons.Building, value: hasIntentToPay ? CONST.IOU.PAYMENT_TYPE.EXPENSIFY : (lastPaymentMethod ?? CONST.IOU.PAYMENT_TYPE.ELSEWHERE), @@ -386,40 +410,41 @@ function SettlementButton({ }); } else { // If there is pay as business option, we should show the submenu items instead. - paymentButtonOptions.push(...getInvoicesOptions(true)); + buttonOptions.push(...getInvoicesOptions(true)); } } if (shouldShowApproveButton) { - paymentButtonOptions.push(approveButtonOption); - } - } - - const handlePaymentSelection = (event: GestureResponderEvent | KeyboardEvent | undefined, selectedOption: string, triggerKYCFlow: (params: ContinueActionParams) => void) => { - if (checkForNecessaryAction()) { - return; + buttonOptions.push(approveButtonOption); } - const {paymentType, selectedPolicy: paymentSelectedPolicy, shouldSelectPaymentMethod} = getActivePaymentType(selectedOption, activeAdminPolicies, latestBankItems, policyIDKey); - - // Payment type for 'Pay via workspace' option is "Elsewhere" but selected option points to one of workspaces where user is admin - const isPayingViaWorkspace = paymentType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE && activeAdminPolicies.find((activeAdminPolicy) => activeAdminPolicy.id === selectedOption); - const isPayingWithMethod = paymentType !== CONST.IOU.PAYMENT_TYPE.ELSEWHERE; - - if ((!!paymentSelectedPolicy || shouldSelectPaymentMethod) && (isPayingWithMethod || isPayingViaWorkspace)) { - triggerKYCFlow({ - event, - iouPaymentType: paymentType as PaymentMethodType, - paymentMethod: selectedOption as PaymentMethod, - policy: paymentSelectedPolicy ?? (event ? lastPaymentPolicy : undefined), - }); - if (paymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || paymentType === CONST.IOU.PAYMENT_TYPE.VBBA) { - setPersonalBankAccountContinueKYCOnSuccess(ROUTES.ENABLE_PAYMENTS); - } - return; - } - - const iouPaymentType = selectedOption as PaymentMethodType; + return buttonOptions; + // We don't want to reorder the options when the preferred payment method changes while the button is still visible except for component initialization when the last payment method is not initialized yet. + // We need to be sure that onPress should be wrapped in an useCallback to prevent unnecessary updates. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + paymentMethods, + isLoadingLastPaymentMethod, + iouReport, + translate, + formattedAmount, + shouldDisableApproveButton, + isInvoiceReport, + currency, + shouldHidePaymentOptions, + shouldShowApproveButton, + shouldShowPayWithExpensifyOption, + shouldShowPayElsewhereOption, + chatReport, + onPress, + onlyShowPayElsewhere, + latestBankItem, + activeAdminPolicies, + checkForNecessaryAction, + icons, + ]); + + const selectPaymentType = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType) => { if (iouPaymentType === CONST.IOU.REPORT_ACTION_TYPE.APPROVE) { if (confirmApproval) { confirmApproval(); @@ -453,28 +478,44 @@ function SettlementButton({ onPress(iouPaymentType, false); } }; + const selectPaymentMethod = (event: KYCFlowEvent, paymentType: string, triggerKYCFlow: TriggerKYCFlow, paymentMethod?: PaymentMethod, selectedPolicy?: Policy) => { + triggerKYCFlow({ + event, + iouPaymentType: paymentType as PaymentMethodType, + paymentMethod, + policy: selectedPolicy ?? (event ? lastPaymentPolicy : undefined), + }); + if (paymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || paymentType === CONST.IOU.PAYMENT_TYPE.VBBA) { + setPersonalBankAccountContinueKYCOnSuccess(ROUTES.ENABLE_PAYMENTS); + } + }; - let customText: string; - if (shouldUseShortForm) { - customText = translate('iou.pay'); - } else if (lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) { - customText = translate('iou.payElsewhere', {formattedAmount}); - } else { - customText = translate('iou.settlePayment', {formattedAmount}); - } + const getCustomText = () => { + if (shouldUseShortForm) { + return translate('iou.pay'); + } + + if (lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) { + return translate('iou.payElsewhere', {formattedAmount}); + } + + return translate('iou.settlePayment', {formattedAmount}); + }; + + const getSecondaryText = (): string | undefined => { + if ( + shouldUseShortForm || + lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.ELSEWHERE || + (paymentButtonOptions.length === 1 && paymentButtonOptions.every((option) => option.value === CONST.IOU.PAYMENT_TYPE.ELSEWHERE)) || + (shouldHidePaymentOptions && (shouldShowApproveButton || onlyShowPayElsewhere)) + ) { + return undefined; + } + + if (lastPaymentPolicy) { + return lastPaymentPolicy.name; + } - let secondaryTextValue: string | undefined; - const shouldHideSecondaryText = - shouldUseShortForm || - lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.ELSEWHERE || - (paymentButtonOptions.length === 1 && paymentButtonOptions.every((option) => option.value === CONST.IOU.PAYMENT_TYPE.ELSEWHERE)) || - (shouldHidePaymentOptions && (shouldShowApproveButton || onlyShowPayElsewhere)); - - if (shouldHideSecondaryText) { - secondaryTextValue = undefined; - } else if (lastPaymentPolicy) { - secondaryTextValue = lastPaymentPolicy.name; - } else { const bankAccountToDisplay = hasIntentToPay ? ((formattedPaymentMethods.find((method) => method.methodID === policy?.achAccount?.bankAccountID) ?? formattedPaymentMethods.at(0)) as BankAccount) : bankAccount; @@ -482,35 +523,66 @@ function SettlementButton({ // Handle bank account payments first (expense reports require bank account, never wallet) if ((lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.VBBA || (hasIntentToPay && isExpenseReport)) && !!policy?.achAccount) { if (policy?.achAccount?.accountNumber) { - secondaryTextValue = translate('paymentMethodList.bankAccountLastFour', policy?.achAccount?.accountNumber?.slice(-4)); - } else if (bankAccountToDisplay?.accountData?.accountNumber) { - secondaryTextValue = translate('paymentMethodList.bankAccountLastFour', bankAccountToDisplay?.accountData?.accountNumber?.slice(-4)); + return translate('paymentMethodList.bankAccountLastFour', policy?.achAccount?.accountNumber?.slice(-4)); + } + + if (!bankAccountToDisplay?.accountData?.accountNumber) { + return; } - } else if (lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || (hasIntentToPay && isInvoiceReport)) { - // Handle wallet payments for IOUs and bank account display for invoices + + return translate('paymentMethodList.bankAccountLastFour', bankAccountToDisplay?.accountData?.accountNumber?.slice(-4)); + } + + // Handle wallet payments for IOUs and bank account display for invoices + if (lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || (hasIntentToPay && isInvoiceReport)) { if (isInvoiceReport) { const isBusinessBankAccount = bankAccountToDisplay?.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS; - secondaryTextValue = translate( - isBusinessBankAccount ? 'iou.invoiceBusinessBank' : 'iou.invoicePersonalBank', - bankAccountToDisplay?.accountData?.accountNumber?.slice(-4) ?? '', - ); - } else if (personalBankAccountList.length) { - secondaryTextValue = translate('common.wallet'); + return translate(isBusinessBankAccount ? 'iou.invoiceBusinessBank' : 'iou.invoicePersonalBank', bankAccountToDisplay?.accountData?.accountNumber?.slice(-4) ?? ''); } - } else if (bankAccount?.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && bankAccount?.methodID === policy?.achAccount?.bankAccountID && isExpenseReportUtil(iouReport)) { - secondaryTextValue = translate('paymentMethodList.bankAccountLastFour', bankAccount?.accountData?.accountNumber?.slice(-4) ?? ''); + + if (!personalBankAccountList.length) { + return; + } + + return translate('common.wallet'); + } + + if (bankAccount?.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && bankAccount?.methodID === policy?.achAccount?.bankAccountID && isExpenseReportUtil(iouReport)) { + return translate('paymentMethodList.bankAccountLastFour', bankAccount?.accountData?.accountNumber?.slice(-4) ?? ''); } - } - const secondaryText = truncate(secondaryTextValue, {length: CONST.FORM_CHARACTER_LIMIT}); + return undefined; + }; + + const handlePaymentSelection = (event: GestureResponderEvent | KeyboardEvent | undefined, selectedOption: string, triggerKYCFlow: (params: ContinueActionParams) => void) => { + if (checkForNecessaryAction()) { + return; + } + + const {paymentType, selectedPolicy, shouldSelectPaymentMethod} = getActivePaymentType(selectedOption, activeAdminPolicies, latestBankItem, policyIDKey); + + // Payment type for 'Pay via workspace' option is "Elsewhere" but selected option points to one of workspaces where user is admin + const isPayingViaWorkspace = paymentType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE && activeAdminPolicies.find((activeAdminPolicy) => activeAdminPolicy.id === selectedOption); + const isPayingWithMethod = paymentType !== CONST.IOU.PAYMENT_TYPE.ELSEWHERE; + + if ((!!selectedPolicy || shouldSelectPaymentMethod) && (isPayingWithMethod || isPayingViaWorkspace)) { + selectPaymentMethod(event, paymentType, triggerKYCFlow, selectedOption as PaymentMethod, selectedPolicy); + return; + } + + selectPaymentType(event, selectedOption as PaymentMethodType); + }; + + const customText = getCustomText(); + const secondaryText = truncate(getSecondaryText(), {length: CONST.FORM_CHARACTER_LIMIT}); const defaultSelectedIndex = paymentButtonOptions.findIndex((paymentOption) => { if (lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) { return paymentOption.value === CONST.IOU.PAYMENT_TYPE.ELSEWHERE; } - if (firstBankItem) { - return paymentOption.value === firstBankItem.value; + if (latestBankItem?.length) { + return paymentOption.value === latestBankItem.at(0)?.value; } if (lastPaymentPolicy?.id) { @@ -562,6 +634,7 @@ function SettlementButton({ if (paymentButtonOptions.length === 1) { return; } + handlePaymentSelection(undefined, option.value, triggerKYCFlow); }} style={style} diff --git a/tests/ui/components/SettlementButtonTest.tsx b/tests/ui/components/SettlementButtonTest.tsx deleted file mode 100644 index ae18bbaa0058c..0000000000000 --- a/tests/ui/components/SettlementButtonTest.tsx +++ /dev/null @@ -1,1169 +0,0 @@ -/* eslint-disable react/jsx-props-no-spreading -- Using spread for defaultProps in tests for cleaner test code */ -import {act, fireEvent, render, screen} from '@testing-library/react-native'; -import React from 'react'; -import Onyx from 'react-native-onyx'; -import {CurrentUserPersonalDetailsProvider} from '@components/CurrentUserPersonalDetailsProvider'; -import {LocaleContextProvider} from '@components/LocaleContextProvider'; -import OnyxListItemProvider from '@components/OnyxListItemProvider'; -import SettlementButton from '@components/SettlementButton'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import type {BankAccountList, LastPaymentMethod, Policy, Report} from '@src/types/onyx'; -import {signInWithTestUser, translateLocal} from '../../utils/TestHelper'; -import waitForBatchedUpdatesWithAct from '../../utils/waitForBatchedUpdatesWithAct'; - -jest.mock('@libs/Navigation/Navigation', () => { - const mockRef = { - getCurrentRoute: jest.fn(() => ({ - name: 'Report', - params: {}, - })), - getState: jest.fn(() => ({})), - }; - return { - navigate: jest.fn(), - goBack: jest.fn(), - navigationRef: mockRef, - getActiveRoute: jest.fn(() => '/r/123'), - }; -}); - -jest.mock('@libs/Navigation/navigationRef', () => ({ - getCurrentRoute: jest.fn(() => ({ - name: 'Report', - params: {}, - })), - getState: jest.fn(() => ({})), -})); - -jest.mock('@react-navigation/native', () => { - const mockRef = { - getCurrentRoute: jest.fn(() => ({ - name: 'Report', - params: {}, - })), - getState: jest.fn(() => ({})), - }; - return { - createNavigationContainerRef: jest.fn(() => mockRef), - useIsFocused: () => true, - useNavigation: () => ({navigate: jest.fn(), addListener: jest.fn()}), - useFocusEffect: jest.fn(), - usePreventRemove: jest.fn(), - }; -}); - -jest.mock('@src/hooks/useResponsiveLayout'); - -jest.mock('@components/ProductTrainingContext', () => ({ - useProductTrainingContext: () => [false], -})); - -const ACCOUNT_ID = 1; -const ACCOUNT_LOGIN = 'test@user.com'; -const REPORT_ID = '123'; -const CHAT_REPORT_ID = '456'; -const POLICY_ID = 'test-policy-id'; -const BANK_ACCOUNT_ID = 12345; - -/** - * Helper to create a basic policy for testing. - * Uses Partial internally but casts to Policy for test convenience. - * This is acceptable in tests where we only need specific fields. - */ -function createTestPolicy(overrides: Partial = {}): Policy { - const basePolicy: Partial = { - id: POLICY_ID, - name: 'Test Policy', - type: CONST.POLICY.TYPE.CORPORATE, - owner: ACCOUNT_LOGIN, - ownerAccountID: ACCOUNT_ID, - outputCurrency: 'USD', - role: CONST.POLICY.ROLE.ADMIN, - isPolicyExpenseChatEnabled: true, - }; - return {...basePolicy, ...overrides} as Policy; -} - -/** - * Helper to create IOU report for testing personal money requests. - */ -function createIOUReport(overrides: Partial = {}): Report { - const baseReport: Partial = { - reportID: REPORT_ID, - type: CONST.REPORT.TYPE.IOU, - ownerAccountID: ACCOUNT_ID, - managerID: 2, - currency: 'USD', - total: 10000, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, - statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, - }; - return {...baseReport, ...overrides} as Report; -} - -/** - * Helper to create expense report for testing workspace reimbursements. - */ -function createExpenseReport(overrides: Partial = {}): Report { - const baseReport: Partial = { - reportID: REPORT_ID, - type: CONST.REPORT.TYPE.EXPENSE, - policyID: POLICY_ID, - ownerAccountID: ACCOUNT_ID, - managerID: 2, - currency: 'USD', - total: 10000, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, - statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, - }; - return {...baseReport, ...overrides} as Report; -} - -/** - * Helper to create invoice report for testing billing. - */ -function createInvoiceReport(overrides: Partial = {}): Report { - const baseReport: Partial = { - reportID: REPORT_ID, - type: CONST.REPORT.TYPE.INVOICE, - policyID: POLICY_ID, - ownerAccountID: ACCOUNT_ID, - managerID: 2, - currency: 'USD', - total: 10000, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, - statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, - }; - return {...baseReport, ...overrides} as Report; -} - -/** - * Helper to create chat report for testing report relationships. - */ -function createChatReport(overrides: Partial = {}): Report { - const baseReport: Partial = { - reportID: CHAT_REPORT_ID, - type: CONST.REPORT.TYPE.CHAT, - policyID: POLICY_ID, - chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, - }; - return {...baseReport, ...overrides} as Report; -} - -/** - * Helper to create business bank account list for testing. - */ -function createBankAccountList(accountNumber = '1234'): BankAccountList { - return { - [BANK_ACCOUNT_ID]: { - accountData: { - accountNumber, - bankAccountID: BANK_ACCOUNT_ID, - state: CONST.BANK_ACCOUNT.STATE.OPEN, - type: CONST.BANK_ACCOUNT.TYPE.BUSINESS, - routingNumber: '123456789', - }, - isDefault: true, - methodID: BANK_ACCOUNT_ID, - accountType: CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT, - title: `Bank Account ****${accountNumber}`, - description: 'Business Account', - bankCurrency: 'USD', - bankCountry: 'US', - }, - }; -} - -/** - * Helper to create personal bank account for testing wallet payments. - */ -function createPersonalBankAccount(accountNumber = '5678'): BankAccountList { - const personalBankAccountID = 67890; - return { - [personalBankAccountID]: { - accountData: { - accountNumber, - bankAccountID: personalBankAccountID, - state: CONST.BANK_ACCOUNT.STATE.OPEN, - type: CONST.BANK_ACCOUNT.TYPE.PERSONAL, - routingNumber: '987654321', - }, - isDefault: false, - methodID: personalBankAccountID, - accountType: CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT, - title: `Personal Account ****${accountNumber}`, - description: 'Personal Account', - bankCurrency: 'USD', - bankCountry: 'US', - }, - }; -} - -/** - * Wrapper component with all required providers for rendering SettlementButton. - */ -function SettlementButtonWrapper({children}: {children: React.ReactNode}) { - return ( - - - {children} - - - ); -} - -type OnyxSetupParams = { - report?: Report; - chatReport?: Report; - policy?: Policy; - bankAccountList?: BankAccountList; - lastPaymentMethod?: LastPaymentMethod; -}; - -/** - * Helper to set up Onyx state for tests, reducing boilerplate. - */ -async function setupOnyxState({report, chatReport, policy, bankAccountList, lastPaymentMethod}: OnyxSetupParams) { - await act(async () => { - if (report) { - await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report); - } - if (chatReport) { - await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, chatReport); - } - if (policy) { - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy); - } - if (bankAccountList) { - await Onyx.merge(ONYXKEYS.BANK_ACCOUNT_LIST, bankAccountList); - } - if (lastPaymentMethod) { - await Onyx.merge(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, lastPaymentMethod); - } - }); -} - -const defaultProps = { - onPress: jest.fn(), - enablePaymentsRoute: ROUTES.ENABLE_PAYMENTS as typeof ROUTES.ENABLE_PAYMENTS, - policyID: POLICY_ID, - chatReportID: CHAT_REPORT_ID, - formattedAmount: '$100.00', -}; - -describe('SettlementButton', () => { - beforeEach(async () => { - jest.clearAllMocks(); - Onyx.init({ - keys: ONYXKEYS, - evictableKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], - }); - await signInWithTestUser(ACCOUNT_ID, ACCOUNT_LOGIN); - }); - - afterEach(async () => { - await Onyx.clear(); - }); - - describe('button label', () => { - it('displays short form "Pay" when shouldUseShortForm is true', async () => { - const iouReport = createIOUReport(); - - await setupOnyxState({ - report: iouReport, - chatReport: createChatReport(), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('iou.pay'))).toBeTruthy(); - }); - - it('displays "Pay elsewhere" (iou.payElsewhere) when last payment method is ELSEWHERE', async () => { - const iouReport = createIOUReport(); - - await setupOnyxState({ - report: iouReport, - chatReport: createChatReport(), - lastPaymentMethod: { - [CONST.POLICY.ID_FAKE]: { - lastUsed: {name: CONST.IOU.PAYMENT_TYPE.ELSEWHERE}, - iou: {name: CONST.IOU.PAYMENT_TYPE.ELSEWHERE}, - expense: {name: CONST.IOU.PAYMENT_TYPE.ELSEWHERE}, - invoice: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, - }, - }, - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('iou.payElsewhere', {formattedAmount: '$100.00'}))).toBeTruthy(); - }); - - it('displays "Pay $100.00" by default', async () => { - const iouReport = createIOUReport(); - - await setupOnyxState({ - report: iouReport, - chatReport: createChatReport(), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('iou.settlePayment', {formattedAmount: '$100.00'}))).toBeTruthy(); - }); - }); - - describe('button subtitle', () => { - describe('IOU reports', () => { - it('shows no subtitle when shouldUseShortForm is true', async () => { - const iouReport = createIOUReport(); - - await setupOnyxState({ - report: iouReport, - chatReport: createChatReport(), - bankAccountList: createPersonalBankAccount(), - lastPaymentMethod: { - [CONST.POLICY.ID_FAKE]: { - lastUsed: {name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY}, - iou: {name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY}, - expense: {name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY}, - invoice: CONST.IOU.PAYMENT_TYPE.EXPENSIFY, - }, - }, - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.queryByText(translateLocal('common.wallet'))).toBeNull(); - }); - - it('shows no subtitle when last payment method is ELSEWHERE', async () => { - const iouReport = createIOUReport(); - - await setupOnyxState({ - report: iouReport, - chatReport: createChatReport(), - lastPaymentMethod: { - [CONST.POLICY.ID_FAKE]: { - lastUsed: {name: CONST.IOU.PAYMENT_TYPE.ELSEWHERE}, - iou: {name: CONST.IOU.PAYMENT_TYPE.ELSEWHERE}, - expense: {name: CONST.IOU.PAYMENT_TYPE.ELSEWHERE}, - invoice: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, - }, - }, - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.queryByText(translateLocal('common.wallet'))).toBeNull(); - }); - - it('shows no subtitle when only ELSEWHERE option is available', async () => { - const iouReport = createIOUReport(); - - await setupOnyxState({ - report: iouReport, - chatReport: createChatReport(), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.queryByText(translateLocal('common.wallet'))).toBeNull(); - }); - - it('shows "Wallet" when payment method is EXPENSIFY and personal bank accounts exist', async () => { - const iouReport = createIOUReport(); - - await setupOnyxState({ - report: iouReport, - chatReport: createChatReport(), - bankAccountList: createPersonalBankAccount(), - lastPaymentMethod: { - [CONST.POLICY.ID_FAKE]: { - lastUsed: {name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY}, - iou: {name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY}, - expense: {name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY}, - invoice: CONST.IOU.PAYMENT_TYPE.EXPENSIFY, - }, - }, - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('common.wallet'))).toBeTruthy(); - }); - - it('shows no wallet when payment method is EXPENSIFY but no personal bank accounts exist', async () => { - const iouReport = createIOUReport(); - - await setupOnyxState({ - report: iouReport, - chatReport: createChatReport(), - lastPaymentMethod: { - [CONST.POLICY.ID_FAKE]: { - lastUsed: {name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY}, - iou: {name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY}, - expense: {name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY}, - invoice: CONST.IOU.PAYMENT_TYPE.EXPENSIFY, - }, - }, - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.queryByText(translateLocal('common.wallet'))).toBeNull(); - }); - - it('shows no wallet when only business bank accounts exist', async () => { - const iouReport = createIOUReport(); - - await setupOnyxState({ - report: iouReport, - chatReport: createChatReport(), - bankAccountList: createBankAccountList('9999'), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.queryByText(translateLocal('common.wallet'))).toBeNull(); - expect(screen.queryByText(translateLocal('iou.settleWallet', {formattedAmount: ''}))).toBeNull(); - }); - - it('shows policy name when last payment method references a policy', async () => { - const iouReport = createIOUReport(); - - await setupOnyxState({ - report: iouReport, - chatReport: createChatReport(), - policy: createTestPolicy({name: 'My Workspace'}), - lastPaymentMethod: { - [CONST.POLICY.ID_FAKE]: { - lastUsed: {name: POLICY_ID}, - iou: {name: POLICY_ID}, - expense: {name: POLICY_ID}, - invoice: POLICY_ID, - }, - }, - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText('My Workspace')).toBeTruthy(); - }); - - it('does not show bank account last four digits (only expense reports show this)', async () => { - const iouReport = createIOUReport(); - - await setupOnyxState({ - report: iouReport, - chatReport: createChatReport(), - bankAccountList: createBankAccountList('7777'), - lastPaymentMethod: { - [CONST.POLICY.ID_FAKE]: { - lastUsed: {name: CONST.IOU.PAYMENT_TYPE.VBBA}, - iou: {name: CONST.IOU.PAYMENT_TYPE.VBBA}, - expense: {name: CONST.IOU.PAYMENT_TYPE.VBBA}, - invoice: CONST.IOU.PAYMENT_TYPE.VBBA, - }, - }, - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.queryByText(translateLocal('paymentMethodList.bankAccountLastFour', '7777'))).toBeNull(); - }); - }); - - describe('expense reports', () => { - it('shows bank account last four digits when policy has achAccount', async () => { - const expenseReport = createExpenseReport(); - - await setupOnyxState({ - report: expenseReport, - chatReport: createChatReport(), - policy: createTestPolicy({ - achAccount: { - bankAccountID: BANK_ACCOUNT_ID, - accountNumber: '9876', - routingNumber: '123456789', - addressName: 'Test Business', - bankName: 'Test Bank', - reimburser: 'reimburser@test.com', - state: CONST.BANK_ACCOUNT.STATE.OPEN, - }, - }), - bankAccountList: createBankAccountList('9876'), - lastPaymentMethod: { - [POLICY_ID]: { - lastUsed: {name: CONST.IOU.PAYMENT_TYPE.VBBA}, - iou: {name: CONST.IOU.PAYMENT_TYPE.VBBA}, - expense: {name: CONST.IOU.PAYMENT_TYPE.VBBA}, - invoice: CONST.IOU.PAYMENT_TYPE.VBBA, - }, - }, - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('paymentMethodList.bankAccountLastFour', '9876'))).toBeTruthy(); - }); - - it('shows bank account not wallet even with personal bank accounts', async () => { - const expenseReport = createExpenseReport(); - - await setupOnyxState({ - report: expenseReport, - chatReport: createChatReport(), - policy: createTestPolicy({ - achAccount: { - bankAccountID: BANK_ACCOUNT_ID, - accountNumber: '9876', - routingNumber: '123456789', - addressName: 'Test Business', - bankName: 'Test Bank', - reimburser: 'reimburser@test.com', - state: CONST.BANK_ACCOUNT.STATE.OPEN, - }, - }), - bankAccountList: { - ...createBankAccountList('9876'), - ...createPersonalBankAccount('5678'), - }, - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('paymentMethodList.bankAccountLastFour', '9876'))).toBeTruthy(); - expect(screen.queryByText(translateLocal('common.wallet'))).toBeNull(); - }); - - it('shows bank account when hasIntentToPay is true with policy achAccount', async () => { - const expenseReport = createExpenseReport(); - - await setupOnyxState({ - report: expenseReport, - chatReport: createChatReport(), - policy: createTestPolicy({ - achAccount: { - bankAccountID: BANK_ACCOUNT_ID, - accountNumber: '4321', - routingNumber: '123456789', - addressName: 'Test Business', - bankName: 'Test Bank', - reimburser: 'reimburser@test.com', - state: CONST.BANK_ACCOUNT.STATE.OPEN, - }, - }), - bankAccountList: createBankAccountList('4321'), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('paymentMethodList.bankAccountLastFour', '4321'))).toBeTruthy(); - expect(screen.queryByText(translateLocal('common.wallet'))).toBeNull(); - }); - - it('shows bank account even when hasIntentToPay but no personal bank accounts', async () => { - const expenseReport = createExpenseReport(); - - await setupOnyxState({ - report: expenseReport, - chatReport: createChatReport(), - policy: createTestPolicy({ - achAccount: { - bankAccountID: BANK_ACCOUNT_ID, - accountNumber: '1111', - routingNumber: '123456789', - addressName: 'Test Business', - bankName: 'Test Bank', - reimburser: 'reimburser@test.com', - state: CONST.BANK_ACCOUNT.STATE.OPEN, - }, - }), - bankAccountList: createBankAccountList('1111'), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('paymentMethodList.bankAccountLastFour', '1111'))).toBeTruthy(); - expect(screen.queryByText(translateLocal('common.wallet'))).toBeNull(); - }); - - it('renders button when VBBA payment method but achAccount has no account number', async () => { - const expenseReport = createExpenseReport(); - - await setupOnyxState({ - report: expenseReport, - chatReport: createChatReport(), - policy: createTestPolicy({ - achAccount: { - bankAccountID: BANK_ACCOUNT_ID, - routingNumber: '123456789', - addressName: 'Test Business', - bankName: 'Test Bank', - reimburser: 'reimburser@test.com', - state: CONST.BANK_ACCOUNT.STATE.OPEN, - } as Policy['achAccount'], - }), - lastPaymentMethod: { - [POLICY_ID]: { - lastUsed: {name: CONST.IOU.PAYMENT_TYPE.VBBA}, - iou: {name: CONST.IOU.PAYMENT_TYPE.VBBA}, - expense: {name: CONST.IOU.PAYMENT_TYPE.VBBA}, - invoice: CONST.IOU.PAYMENT_TYPE.VBBA, - }, - }, - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('iou.settlePayment', {formattedAmount: '$100.00'}))).toBeTruthy(); - }); - - it('shows bank account from formattedPaymentMethods when achAccount has no account number but hasIntentToPay', async () => { - const expenseReport = createExpenseReport(); - - await setupOnyxState({ - report: expenseReport, - chatReport: createChatReport(), - policy: createTestPolicy({ - achAccount: { - bankAccountID: BANK_ACCOUNT_ID, - routingNumber: '123456789', - addressName: 'Test Business', - bankName: 'Test Bank', - reimburser: 'reimburser@test.com', - state: CONST.BANK_ACCOUNT.STATE.OPEN, - } as Policy['achAccount'], - }), - bankAccountList: createBankAccountList('3333'), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('paymentMethodList.bankAccountLastFour', '3333'))).toBeTruthy(); - }); - - it('shows bank account when business bank matches policy achAccount via lastBankAccountID', async () => { - const expenseReport = createExpenseReport(); - - await setupOnyxState({ - report: expenseReport, - chatReport: createChatReport(), - policy: createTestPolicy({ - achAccount: { - bankAccountID: BANK_ACCOUNT_ID, - accountNumber: '2222', - routingNumber: '123456789', - addressName: 'Test Business', - bankName: 'Test Bank', - reimburser: 'reimburser@test.com', - state: CONST.BANK_ACCOUNT.STATE.OPEN, - }, - }), - bankAccountList: createBankAccountList('2222'), - lastPaymentMethod: { - [POLICY_ID]: { - lastUsed: {name: 'customPaymentMethod', bankAccountID: BANK_ACCOUNT_ID}, - iou: {name: 'customPaymentMethod', bankAccountID: BANK_ACCOUNT_ID}, - expense: {name: 'customPaymentMethod', bankAccountID: BANK_ACCOUNT_ID}, - invoice: 'customPaymentMethod', - }, - }, - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('paymentMethodList.bankAccountLastFour', '2222'))).toBeTruthy(); - }); - }); - - describe('invoice reports', () => { - it('renders payment button for invoice report', async () => { - const invoiceReport = createInvoiceReport(); - - await setupOnyxState({ - report: invoiceReport, - chatReport: createChatReport({ - invoiceReceiver: { - type: CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL, - accountID: 2, - }, - }), - policy: createTestPolicy(), - bankAccountList: createBankAccountList('8888'), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('iou.settlePayment', {formattedAmount: '$100.00'}))).toBeTruthy(); - }); - - it('shows business bank label for invoice with business bank account and hasIntentToPay', async () => { - const invoiceReport = createInvoiceReport(); - - await setupOnyxState({ - report: invoiceReport, - chatReport: createChatReport({ - invoiceReceiver: { - type: CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL, - accountID: 2, - }, - }), - policy: createTestPolicy({ - achAccount: { - bankAccountID: BANK_ACCOUNT_ID, - accountNumber: '4444', - routingNumber: '123456789', - addressName: 'Test Business', - bankName: 'Test Bank', - reimburser: 'reimburser@test.com', - state: CONST.BANK_ACCOUNT.STATE.OPEN, - }, - }), - bankAccountList: createBankAccountList('4444'), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('iou.invoiceBusinessBank', '4444'))).toBeTruthy(); - }); - - it('shows personal bank label for invoice with personal bank account and hasIntentToPay', async () => { - const invoiceReport = createInvoiceReport(); - - await setupOnyxState({ - report: invoiceReport, - chatReport: createChatReport({ - invoiceReceiver: { - type: CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL, - accountID: 2, - }, - }), - policy: createTestPolicy({ - achAccount: { - bankAccountID: BANK_ACCOUNT_ID, - accountNumber: '5555', - routingNumber: '123456789', - addressName: 'Test Business', - bankName: 'Test Bank', - reimburser: 'reimburser@test.com', - state: CONST.BANK_ACCOUNT.STATE.OPEN, - }, - }), - bankAccountList: createPersonalBankAccount('5555'), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('iou.invoicePersonalBank', '5555'))).toBeTruthy(); - }); - }); - }); - - describe('payment options dropdown', () => { - it('shows approve button when shouldHidePaymentOptions and shouldShowApproveButton are true', async () => { - const expenseReport = createExpenseReport(); - - await setupOnyxState({ - report: expenseReport, - chatReport: createChatReport(), - policy: createTestPolicy(), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('iou.approve', {formattedAmount: '$100.00'}))).toBeTruthy(); - }); - - it('does not show approve button when shouldShowApproveButton is false', async () => { - const expenseReport = createExpenseReport(); - - await setupOnyxState({ - report: expenseReport, - chatReport: createChatReport(), - policy: createTestPolicy(), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.queryByText(translateLocal('iou.approve', {formattedAmount: '$100.00'}))).toBeNull(); - }); - - it('does not show payment options when shouldHidePaymentOptions is true without approve button', async () => { - const expenseReport = createExpenseReport(); - - await setupOnyxState({ - report: expenseReport, - chatReport: createChatReport(), - policy: createTestPolicy(), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.queryByText(translateLocal('iou.approve', {formattedAmount: '$100.00'}))).toBeNull(); - }); - - it('shows pay elsewhere button when onlyShowPayElsewhere is true', async () => { - const iouReport = createIOUReport(); - - await setupOnyxState({ - report: iouReport, - chatReport: createChatReport(), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('iou.payElsewhere', {formattedAmount: ''}))).toBeTruthy(); - }); - - it('shows no subtitle when shouldHidePaymentOptions and onlyShowPayElsewhere are true', async () => { - const iouReport = createIOUReport(); - - await setupOnyxState({ - report: iouReport, - chatReport: createChatReport(), - bankAccountList: createPersonalBankAccount(), - lastPaymentMethod: { - [CONST.POLICY.ID_FAKE]: { - lastUsed: {name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY}, - iou: {name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY}, - expense: {name: CONST.IOU.PAYMENT_TYPE.EXPENSIFY}, - invoice: CONST.IOU.PAYMENT_TYPE.EXPENSIFY, - }, - }, - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.queryByText(translateLocal('common.wallet'))).toBeNull(); - }); - - it('shows short form pay button when onlyShowPayElsewhere and shouldUseShortForm are true', async () => { - const iouReport = createIOUReport(); - - await setupOnyxState({ - report: iouReport, - chatReport: createChatReport(), - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('iou.pay'))).toBeTruthy(); - }); - - it('does not show wallet option for expense reports (subtitle shows bank account)', async () => { - const expenseReport = createExpenseReport(); - const policy = createTestPolicy({ - achAccount: { - bankAccountID: BANK_ACCOUNT_ID, - accountNumber: '9876', - routingNumber: '123456789', - addressName: 'Test Business', - bankName: 'Test Bank', - reimburser: 'reimburser@test.com', - state: CONST.BANK_ACCOUNT.STATE.OPEN, - }, - }); - const bankAccountList = { - ...createBankAccountList('9876'), - ...createPersonalBankAccount('5678'), - }; - - await setupOnyxState({ - report: expenseReport, - chatReport: createChatReport(), - policy, - bankAccountList, - }); - - render( - - - , - ); - - await waitForBatchedUpdatesWithAct(); - - expect(screen.getByText(translateLocal('paymentMethodList.bankAccountLastFour', '9876'))).toBeTruthy(); - expect(screen.queryByText(translateLocal('common.wallet'))).toBeNull(); - - const payButton = screen.getByText(translateLocal('iou.settlePayment', {formattedAmount: '$100.00'})); - fireEvent.press(payButton); - await waitForBatchedUpdatesWithAct(); - - expect(screen.queryByText(translateLocal('iou.settleWallet', {formattedAmount: ''}))).toBeNull(); - }); - }); -});