Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {FlashListRef, ListRenderItemInfo} from '@shopify/flash-list';
import React, {useCallback, useDeferredValue, useEffect, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
import type {ViewToken} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import Animated, {useAnimatedStyle, useSharedValue, withDelay, withSpring, withTiming} from 'react-native-reanimated';
import ActivityIndicator from '@components/ActivityIndicator';
import {getButtonRole} from '@components/Button/utils';
Expand Down Expand Up @@ -65,7 +65,8 @@ import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import {reportNameSelector} from '@src/selectors/Attributes';
import type {ReportAttributesDerivedValue, Transaction} from '@src/types/onyx';
import {transactionViolationsByIDsSelector} from '@src/selectors/TransactionViolations';
import type {ReportAttributesDerivedValue, Transaction, TransactionViolations} from '@src/types/onyx';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import AccessMoneyRequestReportPreviewPlaceHolder from './AccessMoneyRequestReportPreviewPlaceHolder';
import EmptyMoneyRequestReportPreview from './EmptyMoneyRequestReportPreview';
Expand Down Expand Up @@ -130,8 +131,15 @@ function MoneyRequestReportPreviewContent({

const shouldShowLoading =
chatReportLoadingState != null && chatReportLoadingState.hasOnceLoadedReportActions !== true && transactions.length === 0 && !chatReportMetadata?.isOptimisticReport;
const transactionIDs = useMemo(() => transactions.map((transaction) => transaction.transactionID), [transactions]);
const selectTransactionViolations = useCallback(
(allViolations: OnyxCollection<TransactionViolations>) => transactionViolationsByIDsSelector(transactionIDs)(allViolations),
[transactionIDs],
);
// Pass `transactionIDs` as a dependency so the selector re-runs once the transactions hydrate (otherwise
// it stays closed over the initial empty list and violations would never be selected on first load).
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {selector: selectTransactionViolations}, [transactionIDs]);
// `hasOnceLoadedReportActions` becomes true before transactions populate fully,
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
// so we defer the loading state update to ensure transactions are loaded
const shouldShowLoadingDeferred = useDeferredValue(shouldShowLoading);
const lastTransaction = transactions?.at(0);
Expand Down Expand Up @@ -746,6 +754,7 @@ function MoneyRequestReportPreviewContent({
iouReportID={iouReportID}
chatReportID={chatReportID}
chatReport={chatReport}
iouReport={iouReport}
isPaidAnimationRunning={isPaidAnimationRunning}
isApprovedAnimationRunning={isApprovedAnimationRunning}
isSubmittingAnimationRunning={isSubmittingAnimationRunning}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ type ReportPreviewActionButtonProps = {
iouReportID: string | undefined;
chatReportID: string | undefined;
chatReport: OnyxEntry<Report>;

/** The stabilized IOU report, provided by the parent so the dispatcher does not re-subscribe to the churning report */
iouReport: OnyxEntry<Report>;
isPaidAnimationRunning: boolean;
isApprovedAnimationRunning: boolean;
isSubmittingAnimationRunning: boolean;
Expand All @@ -51,6 +54,7 @@ function ReportPreviewActionButton({
iouReportID,
chatReportID,
chatReport,
iouReport,
isPaidAnimationRunning,
isApprovedAnimationRunning,
isSubmittingAnimationRunning,
Expand All @@ -70,7 +74,6 @@ function ReportPreviewActionButton({
const currentUserDetails = useCurrentUserPersonalDetails();
const expensifyIcons = useMemoizedLazyExpensifyIcons(['Location', 'ReceiptPlus', 'Plus']);

const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`);
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${iouReport?.policyID}`);
const invoiceReceiverPolicyID = chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : undefined;
const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiverPolicyID}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import React, {useCallback, useMemo, useRef, useState} from 'react';
import type {LayoutChangeEvent} from 'react-native';
import {usePersonalDetails} from '@components/OnyxListItemProvider';
import TransactionPreview from '@components/ReportActionItem/TransactionPreview';
import useNetwork from '@hooks/useNetwork';
import useNewTransactions from '@hooks/useNewTransactions';
import useOnyx from '@hooks/useOnyx';
import usePolicy from '@hooks/usePolicy';
import useReportWithTransactionsAndViolations from '@hooks/useReportWithTransactionsAndViolations';
import useReportTransactionsCollection from '@hooks/useReportTransactionsCollection';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
Expand All @@ -28,6 +29,7 @@ import type {MoneyRequestReportPreviewProps} from './types';

function MoneyRequestReportPreview({
iouReportID,
iouReport,
policyID,
chatReportID,
chatReport,
Expand All @@ -46,7 +48,15 @@ function MoneyRequestReportPreview({
const invoiceReceiverPolicyID = chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : undefined;
const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(invoiceReceiverPolicyID)}`);
const invoiceReceiverPersonalDetail = chatReport?.invoiceReceiver && 'accountID' in chatReport.invoiceReceiver ? personalDetailsList?.[chatReport.invoiceReceiver.accountID] : null;
const [iouReport, transactions] = useReportWithTransactionsAndViolations(iouReportID);
const reportTransactionsCollection = useReportTransactionsCollection(iouReportID);
const {isOffline} = useNetwork();
const transactions = useMemo(
() =>
Object.values(reportTransactionsCollection ?? {}).filter(
(transaction): transaction is Transaction => !!transaction && (isOffline || transaction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE),
),
[reportTransactionsCollection, isOffline],
);
const policy = usePolicy(policyID);
const lastTransaction = transactions?.at(0);
const lastTransactionViolations = useTransactionViolations(lastTransaction?.transactionID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ type MoneyRequestReportPreviewProps = {
/** The active IOUReport, used for Onyx subscription */
iouReportID: string | undefined;

/** The stabilized IOU report, provided by the parent so the preview does not re-subscribe to the churning report */
iouReport: OnyxEntry<Report>;

/** Callback when the payment options popover is shown */
onPaymentOptionsShow?: () => void;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ function ActionContentRouter({
return (
<MoneyRequestReportPreview
iouReportID={getIOUReportIDFromReportActionPreview(action)}
iouReport={iouReport}
policyID={policyID}
chatReportID={reportID}
chatReport={report}
Expand Down
25 changes: 18 additions & 7 deletions tests/ui/MoneyRequestReportPreview.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import * as ReportUtils from '@src/libs/ReportUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Report, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet';
import {actionR14932 as mockAction} from '../../__mocks__/reportData/actions';
import {chatReportR14932 as mockChatReport, iouReportR14932 as mockIOUReport} from '../../__mocks__/reportData/reports';
import {transactionR14932 as mockTransaction} from '../../__mocks__/reportData/transactions';
Expand Down Expand Up @@ -68,6 +69,17 @@ jest.mock('@src/hooks/useReportWithTransactionsAndViolations', () => ({
default: (...args: Parameters<typeof mockUseReportWithTransactionsAndViolations>) => mockUseReportWithTransactionsAndViolations(...args),
}));

// The preview reads `iouReport` from a prop (provided stable by the parent) and its transactions from the
// scoped `useReportTransactionsCollection` hook, so the test drives those two sources directly.
let mockIOUReportProp: OnyxEntry<Report> = mockIOUReport;

const mockUseReportTransactionsCollection = jest.fn(() => toCollectionDataSet(ONYXKEYS.COLLECTION.TRANSACTION, defaultPreviewTransactions, (transaction) => transaction.transactionID));

jest.mock('@hooks/useReportTransactionsCollection', () => ({
__esModule: true,
default: () => mockUseReportTransactionsCollection(),
}));

type OnHoldMenuOpen = (requestType: string, paymentType?: PaymentMethodType, canPay?: boolean, methodID?: number) => void;

// Capture the onHoldMenuOpen callback the preview passes to the pay button so a held-expense payment can be triggered
Expand Down Expand Up @@ -141,6 +153,7 @@ const renderPage = ({isWhisper = false, isHovered = false}: Partial<MoneyRequest
policyID={mockChatReport.policyID}
action={mockAction}
iouReportID={mockIOUReport.reportID}
iouReport={mockIOUReportProp}
chatReportID={mockChatReport.reportID}
chatReport={mockChatReport}
onPaymentOptionsShow={() => {}}
Expand Down Expand Up @@ -198,17 +211,15 @@ const setReportPreviewData = (
overrides: {
iouReport?: OnyxEntry<Report>;
transactions?: Transaction[];
violations?: OnyxCollection<TransactionViolation[]>;
} = {},
) => {
const {iouReport, transactions, violations} = overrides;
const {iouReport, transactions} = overrides;
const hasIouReportOverride = Object.prototype.hasOwnProperty.call(overrides, 'iouReport');

mockUseReportWithTransactionsAndViolations.mockImplementation(() => [
hasIouReportOverride ? iouReport : mockIOUReport,
transactions ?? defaultPreviewTransactions,
violations ?? {violations: mockViolations},
]);
mockIOUReportProp = hasIouReportOverride ? iouReport : mockIOUReport;
mockUseReportTransactionsCollection.mockImplementation(() =>
toCollectionDataSet(ONYXKEYS.COLLECTION.TRANSACTION, transactions ?? defaultPreviewTransactions, (transaction) => transaction.transactionID),
);
};

const setHasOnceLoadedReportActions = async (hasOnceLoadedReportActions: boolean) => {
Expand Down
Loading