diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 001a1ba5d96e..974c409a5dcb 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -849,6 +849,7 @@ const ONYXKEYS = { }, DERIVED: { REPORT_ATTRIBUTES: 'reportAttributes', + REPORT_TRANSACTIONS_AND_VIOLATIONS: 'reportTransactionsAndViolations', }, } as const; @@ -1202,6 +1203,7 @@ type OnyxValuesMapping = { type OnyxDerivedValuesMapping = { [ONYXKEYS.DERIVED.REPORT_ATTRIBUTES]: OnyxTypes.ReportAttributesDerivedValue; + [ONYXKEYS.DERIVED.REPORT_TRANSACTIONS_AND_VIOLATIONS]: OnyxTypes.ReportTransactionsAndViolationsDerivedValue; }; type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping & OnyxDerivedValuesMapping; diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index c974d45758ce..1b8336089824 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -82,8 +82,8 @@ type MoneyRequestReportListProps = { /** Array of report actions for this report */ reportActions?: OnyxTypes.ReportAction[]; - /** List of transactions belonging to this report */ - transactions?: OnyxTypes.Transaction[]; + /** All transactions grouped by reportID */ + transactionsAndViolationsByReport: OnyxTypes.ReportTransactionsAndViolationsDerivedValue; /** List of transactions that arrived when the report was open */ newTransactions: OnyxTypes.Transaction[]; @@ -109,7 +109,7 @@ function MoneyRequestReportActionsList({ report, policy, reportActions = [], - transactions = [], + transactionsAndViolationsByReport, newTransactions, hasNewerActions, hasOlderActions, @@ -124,7 +124,9 @@ function MoneyRequestReportActionsList({ const [isVisible, setIsVisible] = useState(Visibility.isVisible); const isFocused = useIsFocused(); const route = useRoute>(); - const reportTransactionIDs = transactions.map((transaction) => transaction.transactionID); + const {transactions: reportTransactions} = transactionsAndViolationsByReport[report.reportID] ?? {}; + const transactions = useMemo(() => Object.values(reportTransactions ?? {}) ?? [], [reportTransactions]); + const reportTransactionIDs = useMemo(() => transactions.map((transaction) => transaction.transactionID), [transactions]); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.chatReportID)}`, {canBeMissing: true}); const reportID = report?.reportID; @@ -484,6 +486,7 @@ function MoneyRequestReportActionsList({ isFirstVisibleReportAction={firstVisibleReportActionID === reportAction.reportActionID} shouldHideThreadDividerLine linkedReportActionID={linkedReportActionID} + transactionsAndViolationsByReport={transactionsAndViolationsByReport} /> ); }, @@ -497,6 +500,7 @@ function MoneyRequestReportActionsList({ unreadMarkerReportActionID, firstVisibleReportActionID, linkedReportActionID, + transactionsAndViolationsByReport, allReports, ], ); diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx index 121722a32907..001878c7182f 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx @@ -1,7 +1,7 @@ import {PortalHost} from '@gorhom/portal'; import React, {useCallback, useMemo} from 'react'; import {InteractionManager, View} from 'react-native'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import HeaderGap from '@components/HeaderGap'; import MoneyReportHeader from '@components/MoneyReportHeader'; @@ -16,7 +16,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {removeFailedReport} from '@libs/actions/Report'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import Log from '@libs/Log'; -import {selectAllTransactionsForReport, shouldDisplayReportTableView, shouldWaitForTransactions as shouldWaitForTransactionsUtil} from '@libs/MoneyRequestReportUtils'; +import {shouldDisplayReportTableView, shouldWaitForTransactions as shouldWaitForTransactionsUtil} from '@libs/MoneyRequestReportUtils'; import navigationRef from '@libs/Navigation/navigationRef'; import {getFilteredReportActionsForReportView, getOneTransactionThreadReportID, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import {canEditReportAction, getReportOfflinePendingActionAndErrors, isReportTransactionThread} from '@libs/ReportUtils'; @@ -24,6 +24,7 @@ import {buildCannedSearchQuery} from '@libs/SearchQueryUtils'; import Navigation from '@navigation/Navigation'; import ReportActionsView from '@pages/home/report/ReportActionsView'; import ReportFooter from '@pages/home/report/ReportFooter'; +import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; @@ -47,6 +48,9 @@ type MoneyRequestReportViewProps = { /** The `backTo` route that should be used when clicking back button */ backToRoute: Route | undefined; + + /** All transactions grouped by reportID */ + transactionsAndViolationsByReport: OnyxTypes.ReportTransactionsAndViolationsDerivedValue; }; function goBackFromSearchMoneyRequest() { @@ -84,7 +88,7 @@ function getParentReportAction(parentReportActions: OnyxEntry) => selectAllTransactionsForReport(allTransactions, reportID, reportActions), - canBeMissing: true, - }); - + const {transactions: reportTransactions} = transactionsAndViolationsByReport[reportID ?? CONST.DEFAULT_NUMBER_ID]; + const transactions = useMemo(() => Object.values(reportTransactions ?? {}) ?? [], [reportTransactions]); const reportTransactionIDs = transactions?.map((transaction) => transaction.transactionID); const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActions ?? [], isOffline, reportTransactionIDs); @@ -217,7 +218,7 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe )} {shouldDisplayReportFooter ? ( diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx index 033d6a0babe0..d69a803aa864 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx @@ -2,8 +2,8 @@ import React, {useCallback, useMemo, useState} from 'react'; import type {LayoutChangeEvent, ListRenderItem} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import TransactionPreview from '@components/ReportActionItem/TransactionPreview'; +import useNetwork from '@hooks/useNetwork'; import usePolicy from '@hooks/usePolicy'; -import useReportWithTransactionsAndViolations from '@hooks/useReportWithTransactionsAndViolations'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -35,6 +35,7 @@ function MoneyRequestReportPreview({ shouldDisplayContextMenu = true, isInvoice = false, shouldShowBorder, + transactionsAndViolationsByReport, }: MoneyRequestReportPreviewProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -49,8 +50,12 @@ function MoneyRequestReportPreview({ personalDetails?.[chatReport?.invoiceReceiver && 'accountID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.accountID : CONST.DEFAULT_NUMBER_ID], canBeMissing: true, }); - const [iouReport, transactions, violations] = useReportWithTransactionsAndViolations(iouReportID); + const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, {canBeMissing: true}); + const {transactions: reportTransactions, violations} = transactionsAndViolationsByReport[iouReportID ?? CONST.DEFAULT_NUMBER_ID] ?? {}; + const {isOffline} = useNetwork(); + const filteredTransactions = Object.values(reportTransactions ?? {}).filter((transaction) => isOffline || transaction?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); const policy = usePolicy(policyID); + const transactions = useMemo(() => Object.values(filteredTransactions ?? {}) ?? [], [filteredTransactions]); const lastTransaction = transactions?.at(0); const lastTransactionViolations = useTransactionViolations(lastTransaction?.transactionID); const isTrackExpenseAction = isTrackExpenseActionReportActionsUtils(action); diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts b/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts index cf9c6bf59178..6cd1ec21eed0 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts @@ -2,7 +2,7 @@ import type {LayoutChangeEvent, ListRenderItem, StyleProp, ViewStyle} from 'reac import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {TransactionPreviewStyleType} from '@components/ReportActionItem/TransactionPreview/types'; import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; -import type {PersonalDetails, Policy, Report, ReportAction, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx'; +import type {PersonalDetails, Policy, Report, ReportAction, ReportTransactionsAndViolationsDerivedValue, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx'; type TransactionPreviewStyle = { [key in keyof TransactionPreviewStyleType]: number; @@ -56,6 +56,9 @@ type MoneyRequestReportPreviewProps = { /** Whether to show a border to separate Reports Chat Item and Money Request Report Preview */ shouldShowBorder?: boolean; + + /** All transactions grouped by reportID */ + transactionsAndViolationsByReport: ReportTransactionsAndViolationsDerivedValue; }; type MoneyRequestReportPreviewContentOnyxProps = { @@ -70,7 +73,7 @@ type MoneyRequestReportPreviewContentOnyxProps = { }; type MoneyRequestReportPreviewContentProps = MoneyRequestReportPreviewContentOnyxProps & - Omit & { + Omit & { /** Extra styles passed used by MoneyRequestReportPreviewContent */ reportPreviewStyles: MoneyRequestReportPreviewStyleType; diff --git a/src/components/ReportActionItem/TripRoomPreview.tsx b/src/components/ReportActionItem/TripRoomPreview.tsx index 0beca701d154..bad097f7fee7 100644 --- a/src/components/ReportActionItem/TripRoomPreview.tsx +++ b/src/components/ReportActionItem/TripRoomPreview.tsx @@ -28,7 +28,7 @@ import variables from '@styles/variables'; import * as Expensicons from '@src/components/Icon/Expensicons'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; -import type {Report, ReportAction} from '@src/types/onyx'; +import type {Report, ReportAction, ReportTransactionsAndViolationsDerivedValue} from '@src/types/onyx'; import type {Reservation} from '@src/types/onyx/Transaction'; type TripRoomPreviewProps = { @@ -55,6 +55,9 @@ type TripRoomPreviewProps = { /** Whether context menu should be shown on press */ shouldDisplayContextMenu?: boolean; + + /** The transactions for the report */ + transactionsAndViolationsByReport: ReportTransactionsAndViolationsDerivedValue; }; type ReservationViewProps = { @@ -148,11 +151,12 @@ function TripRoomPreview({ isHovered = false, checkIfContextMenuActive = () => {}, shouldDisplayContextMenu = true, + transactionsAndViolationsByReport, }: TripRoomPreviewProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const chatReportID = chatReport?.reportID; - const tripTransactions = useTripTransactions(chatReportID); + const tripTransactions = useTripTransactions(chatReportID, transactionsAndViolationsByReport); const reservationsData: ReservationData[] = getReservationsFromTripTransactions(tripTransactions); const dateInfo = diff --git a/src/hooks/useReportWithTransactionsAndViolations.ts b/src/hooks/useReportWithTransactionsAndViolations.ts deleted file mode 100644 index 12a20b1f764c..000000000000 --- a/src/hooks/useReportWithTransactionsAndViolations.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {reportTransactionsSelector} from '@libs/ReportUtils'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type {Report, Transaction, TransactionViolation} from '@src/types/onyx'; -import useNetwork from './useNetwork'; -import useOnyx from './useOnyx'; - -const DEFAULT_TRANSACTIONS: Transaction[] = []; -const DEFAULT_VIOLATIONS: Record = {}; - -function useReportWithTransactionsAndViolations(reportID?: string): [OnyxEntry, Transaction[], OnyxCollection] { - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {canBeMissing: false}); - const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { - selector: (_transactions) => reportTransactionsSelector(_transactions, reportID), - canBeMissing: true, - }); - const {isOffline} = useNetwork(); - const filteredTransactions = transactions?.filter((transaction) => isOffline || transaction?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); - const [violations] = useOnyx( - ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - { - selector: (allViolations) => - Object.fromEntries( - Object.entries(allViolations ?? {}).filter(([key]) => - filteredTransactions?.some((transaction) => transaction.transactionID === key.replace(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, '')), - ), - ), - canBeMissing: true, - }, - [filteredTransactions], - ); - return [report, filteredTransactions ?? DEFAULT_TRANSACTIONS, violations ?? DEFAULT_VIOLATIONS]; -} - -export default useReportWithTransactionsAndViolations; diff --git a/src/hooks/useTripTransactions.ts b/src/hooks/useTripTransactions.ts index 2260097a4112..470cb3f65da5 100644 --- a/src/hooks/useTripTransactions.ts +++ b/src/hooks/useTripTransactions.ts @@ -1,6 +1,7 @@ import {useOnyx} from 'react-native-onyx'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Transaction} from '@src/types/onyx'; +import type {ReportTransactionsAndViolationsDerivedValue, Transaction} from '@src/types/onyx'; /** * Hook to fetch transactions associated with a specific `tripRoom` report. @@ -12,26 +13,17 @@ import type {Transaction} from '@src/types/onyx'; * @param reportID - The trip room's reportID. * @returns Transactions linked to the specified trip room. */ -function useTripTransactions(reportID: string | undefined): Transaction[] { +function useTripTransactions(reportID: string | undefined, transactionsAndViolationsByReport: ReportTransactionsAndViolationsDerivedValue): Transaction[] { const [tripTransactionReportIDs = []] = useOnyx(ONYXKEYS.COLLECTION.REPORT, { selector: (reports) => Object.values(reports ?? {}) .filter((report) => report && report.chatReportID === reportID) .map((report) => report?.reportID), + canBeMissing: true, }); - const [tripTransactions = []] = useOnyx( - ONYXKEYS.COLLECTION.TRANSACTION, - { - selector: (transactions) => { - if (!tripTransactionReportIDs.length) { - return []; - } + const {transactions} = transactionsAndViolationsByReport[reportID ?? CONST.DEFAULT_NUMBER_ID] ?? {}; + const tripTransactions = tripTransactionReportIDs.flatMap((transactionReportID) => transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionReportID}`] ?? []); - return Object.values(transactions ?? {}).filter((transaction): transaction is Transaction => !!transaction && tripTransactionReportIDs.includes(transaction.reportID)); - }, - }, - [tripTransactionReportIDs], - ); return tripTransactions; } diff --git a/src/libs/MoneyRequestReportUtils.ts b/src/libs/MoneyRequestReportUtils.ts index 08e4e7455cbc..3a81ea63e0bc 100644 --- a/src/libs/MoneyRequestReportUtils.ts +++ b/src/libs/MoneyRequestReportUtils.ts @@ -1,4 +1,4 @@ -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {TransactionListItemType} from '@components/SelectionList/types'; import CONST from '@src/CONST'; @@ -69,20 +69,15 @@ function getReportIDForTransaction(transactionItem: TransactionListItemType) { } /** - * Filters all available transactions and returns the ones that belong to a specific report (by `reportID`). - * It is used as an onyx selector, to make sure that report related views do not process all transactions in onyx. + * Filters all available transactions and returns the ones that belong to not removed parent action. */ -function selectAllTransactionsForReport(transactions: OnyxCollection, reportID: string | undefined, reportActions: ReportAction[]) { - if (!reportID) { - return []; - } - - return Object.values(transactions ?? {}).filter((transaction): transaction is Transaction => { +function getAllNonDeletedTransactions(transactions: Transaction[], reportActions: ReportAction[]) { + return transactions.filter((transaction): transaction is Transaction => { if (!transaction) { return false; } const action = getIOUActionForTransactionID(reportActions, transaction.transactionID); - return transaction.reportID === reportID && !isDeletedParentAction(action); + return !isDeletedParentAction(action); }); } @@ -164,7 +159,7 @@ export { getThreadReportIDsForTransactions, getReportIDForTransaction, getTotalAmountForIOUReportPreviewButton, - selectAllTransactionsForReport, + getAllNonDeletedTransactions, isSingleTransactionReport, shouldDisplayReportTableView, shouldWaitForTransactions, diff --git a/src/libs/actions/OnyxDerived/ONYX_DERIVED_VALUES.ts b/src/libs/actions/OnyxDerived/ONYX_DERIVED_VALUES.ts index ff9758d3645e..6869c09dfad5 100644 --- a/src/libs/actions/OnyxDerived/ONYX_DERIVED_VALUES.ts +++ b/src/libs/actions/OnyxDerived/ONYX_DERIVED_VALUES.ts @@ -1,6 +1,7 @@ import type {ValueOf} from 'type-fest'; import ONYXKEYS from '@src/ONYXKEYS'; import reportAttributesConfig from './configs/reportAttributes'; +import reportTransactionsAndViolationsConfig from './configs/reportTransactionsAndViolations'; import type {OnyxDerivedValueConfig} from './types'; /** @@ -9,6 +10,7 @@ import type {OnyxDerivedValueConfig} from './types'; */ const ONYX_DERIVED_VALUES = { [ONYXKEYS.DERIVED.REPORT_ATTRIBUTES]: reportAttributesConfig, + [ONYXKEYS.DERIVED.REPORT_TRANSACTIONS_AND_VIOLATIONS]: reportTransactionsAndViolationsConfig, } as const satisfies { // eslint-disable-next-line @typescript-eslint/no-explicit-any [Key in ValueOf]: OnyxDerivedValueConfig; diff --git a/src/libs/actions/OnyxDerived/configs/reportTransactionsAndViolations.ts b/src/libs/actions/OnyxDerived/configs/reportTransactionsAndViolations.ts new file mode 100644 index 000000000000..fcd450428316 --- /dev/null +++ b/src/libs/actions/OnyxDerived/configs/reportTransactionsAndViolations.ts @@ -0,0 +1,51 @@ +import createOnyxDerivedValueConfig from '@userActions/OnyxDerived/createOnyxDerivedValueConfig'; +import ONYXKEYS from '@src/ONYXKEYS'; + +export default createOnyxDerivedValueConfig({ + key: ONYXKEYS.DERIVED.REPORT_TRANSACTIONS_AND_VIOLATIONS, + dependencies: [ONYXKEYS.COLLECTION.TRANSACTION, ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS], + compute: ([transactions, violations], {sourceValues, currentValue}) => { + if (!transactions) { + return {}; + } + + const transactionsUpdates = sourceValues?.[ONYXKEYS.COLLECTION.TRANSACTION]; + const transactionViolationsUpdates = sourceValues?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]; + let transactionsToProcess = Object.keys(transactions); + if (transactionsUpdates) { + transactionsToProcess = Object.keys(transactionsUpdates); + } else if (transactionViolationsUpdates) { + transactionsToProcess = Object.keys(transactionViolationsUpdates).map((transactionViolation) => + transactionViolation.replace(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, ONYXKEYS.COLLECTION.TRANSACTION), + ); + } + + const reportTransactionsAndViolations = currentValue ?? {}; + for (const transactionKey of transactionsToProcess) { + const transaction = transactions[transactionKey]; + const reportID = transaction?.reportID; + if (!reportID) { + // eslint-disable-next-line no-continue + continue; + } + + if (!reportTransactionsAndViolations[reportID]) { + reportTransactionsAndViolations[reportID] = { + transactions: {}, + violations: {}, + }; + } + + const transactionID = transaction.transactionID; + const transactionViolations = violations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; + + if (transactionViolations && transactionViolations.length > 0) { + reportTransactionsAndViolations[reportID].violations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] = transactionViolations; + } + + reportTransactionsAndViolations[reportID].transactions[transactionKey] = transaction; + } + + return reportTransactionsAndViolations; + }, +}); diff --git a/src/libs/actions/OnyxDerived/index.ts b/src/libs/actions/OnyxDerived/index.ts index 16cbe70857a3..a40624a1692e 100644 --- a/src/libs/actions/OnyxDerived/index.ts +++ b/src/libs/actions/OnyxDerived/index.ts @@ -39,6 +39,7 @@ function init() { sourceValues: undefined, areAllConnectionsSet: false, }; + // @ts-expect-error TypeScript can't confirm the shape of dependencyValues matches the compute function's parameters derivedValue = compute(dependencyValues, initialContext); dependencyValues = values; Onyx.set(key, derivedValue ?? null); @@ -79,6 +80,7 @@ function init() { [sourceKey]: sourceValue, }; } + // @ts-expect-error TypeScript can't confirm the shape of dependencyValues matches the compute function's parameters const newDerivedValue = compute(dependencyValues, context); Log.info(`[OnyxDerived] updating value for ${key} in Onyx`); derivedValue = newDerivedValue; diff --git a/src/pages/Search/SearchMoneyRequestReportPage.tsx b/src/pages/Search/SearchMoneyRequestReportPage.tsx index 8dee241f6a43..3fbbfcddf6ea 100644 --- a/src/pages/Search/SearchMoneyRequestReportPage.tsx +++ b/src/pages/Search/SearchMoneyRequestReportPage.tsx @@ -43,6 +43,7 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) { const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {allowStaleData: true, initialValue: {}, canBeMissing: false}); const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]; const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {canBeMissing: true}); + const [transactionsAndViolationsByReport] = useOnyx(ONYXKEYS.DERIVED.REPORT_TRANSACTIONS_AND_VIOLATIONS, {canBeMissing: true}); const {isEditingDisabled, isCurrentReportLoadedFromOnyx} = useIsReportReadyToDisplay(report, reportIDFromRoute); @@ -100,6 +101,7 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) { policy={policy} shouldDisplayReportFooter={isCurrentReportLoadedFromOnyx} backToRoute={route.params.backTo} + transactionsAndViolationsByReport={transactionsAndViolationsByReport ?? {}} /> @@ -135,6 +137,7 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) { policy={policy} shouldDisplayReportFooter={isCurrentReportLoadedFromOnyx} backToRoute={route.params.backTo} + transactionsAndViolationsByReport={transactionsAndViolationsByReport ?? {}} /> diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 0889c192b6a5..5e3b8c1a9171 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -32,7 +32,7 @@ import useViewportOffsetTop from '@hooks/useViewportOffsetTop'; import {hideEmojiPicker} from '@libs/actions/EmojiPickerAction'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import Log from '@libs/Log'; -import {selectAllTransactionsForReport, shouldDisplayReportTableView, shouldWaitForTransactions as shouldWaitForTransactionsUtil} from '@libs/MoneyRequestReportUtils'; +import {getAllNonDeletedTransactions, shouldDisplayReportTableView, shouldWaitForTransactions as shouldWaitForTransactionsUtil} from '@libs/MoneyRequestReportUtils'; import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import clearReportNotifications from '@libs/Notification/clearReportNotifications'; @@ -297,10 +297,11 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // OpenReport will be called each time the user scrolls up the report a bit, clicks on report preview, and then goes back. const isLinkedMessagePageReady = isLinkedMessageAvailable && (reportActions.length - indexOfLinkedMessage >= CONST.REPORT.MIN_INITIAL_REPORT_ACTION_COUNT || doesCreatedActionExists()); - const [reportTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { - selector: (allTransactions): OnyxTypes.Transaction[] => selectAllTransactionsForReport(allTransactions, reportIDFromRoute, reportActions), + const [transactionsAndViolationsByReport] = useOnyx(ONYXKEYS.DERIVED.REPORT_TRANSACTIONS_AND_VIOLATIONS, { canBeMissing: false, }); + const {transactions} = transactionsAndViolationsByReport?.[reportID ?? CONST.DEFAULT_NUMBER_ID] ?? {}; + const reportTransactions = getAllNonDeletedTransactions(Object.values(transactions ?? {}) ?? [], reportActions); const reportTransactionIDs = reportTransactions?.map((transaction) => transaction.transactionID); const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActions ?? [], isOffline, reportTransactionIDs); const [transactionThreadReportActions = {}] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`, {canBeMissing: true}); @@ -819,6 +820,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { hasOlderActions={hasOlderActions} parentReportAction={parentReportAction} transactionThreadReportID={transactionThreadReportID} + transactionsAndViolationsByReport={transactionsAndViolationsByReport ?? {}} /> ) : null} {!!report && shouldDisplayMoneyRequestActionsList && !shouldWaitForTransactions ? ( @@ -826,7 +828,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { report={report} policy={policy} reportActions={reportActions} - transactions={reportTransactions} + transactionsAndViolationsByReport={transactionsAndViolationsByReport ?? {}} newTransactions={newTransactions} hasOlderActions={hasOlderActions} hasNewerActions={hasNewerActions} diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index ad124bbc20f4..065cf7cc6bcd 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -194,6 +194,9 @@ type PureReportActionItemProps = { /** The transaction thread report associated with the report for this action, if any */ transactionThreadReport?: OnyxEntry; + /** All transactions grouped by reportID */ + transactionsAndViolationsByReport?: OnyxTypes.ReportTransactionsAndViolationsDerivedValue; + /** Array of report actions for the report for this action */ // eslint-disable-next-line react/no-unused-prop-types reportActions: OnyxTypes.ReportAction[]; @@ -376,6 +379,7 @@ function PureReportActionItem({ report, policy, transactionThreadReport, + transactionsAndViolationsByReport = {}, linkedReportActionID, displayAsGroup, index, @@ -907,6 +911,7 @@ function PureReportActionItem({ containerStyles={displayAsGroup ? [] : [styles.mt2]} checkIfContextMenuActive={toggleContextMenuFromActiveReportAction} shouldDisplayContextMenu={shouldDisplayContextMenu} + transactionsAndViolationsByReport={transactionsAndViolationsByReport} /> ); } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW && isClosedExpenseReportWithNoExpenses) { @@ -927,6 +932,7 @@ function PureReportActionItem({ onPaymentOptionsHide={() => setIsPaymentMethodPopoverActive(false)} shouldDisplayContextMenu={shouldDisplayContextMenu} shouldShowBorder={shouldShowBorder} + transactionsAndViolationsByReport={transactionsAndViolationsByReport} /> ); } else if (isTaskAction(action)) { @@ -1361,7 +1367,12 @@ function PureReportActionItem({ } if (isTripPreview(action) && isThreadReportParentAction) { - return ; + return ( + + ); } if (isChronosOOOListAction(action)) { diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx index fee3b63e65fd..1d1e720a77a0 100644 --- a/src/pages/home/report/ReportActionItemParentAction.tsx +++ b/src/pages/home/report/ReportActionItemParentAction.tsx @@ -57,6 +57,9 @@ type ReportActionItemParentActionProps = { /** If the thread divider line will be used */ shouldUseThreadDividerLine?: boolean; + + /** All transactions grouped by reportID */ + transactionsAndViolationsByReport: OnyxTypes.ReportTransactionsAndViolationsDerivedValue; }; function ReportActionItemParentAction({ @@ -70,6 +73,7 @@ function ReportActionItemParentAction({ shouldDisplayReplyDivider, isFirstVisibleReportAction = false, shouldUseThreadDividerLine = false, + transactionsAndViolationsByReport, }: ReportActionItemParentActionProps) { const styles = useThemeStyles(); const ancestorIDs = useRef(getAllAncestorReportActionIDs(report)); @@ -155,6 +159,7 @@ function ReportActionItemParentAction({ isFirstVisibleReportAction={isFirstVisibleReportAction} shouldUseThreadDividerLine={shouldUseThreadDividerLine} isThreadReportParentAction + transactionsAndViolationsByReport={transactionsAndViolationsByReport} /> ); diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index d238d29b8a64..35d4ba034b5c 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -108,6 +108,9 @@ type ReportActionsListProps = { /** Should enable auto scroll to top threshold */ shouldEnableAutoScrollToTopThreshold?: boolean; + + /** All transactions grouped by reportID */ + transactionsAndViolationsByReport: OnyxTypes.ReportTransactionsAndViolationsDerivedValue; }; const IS_CLOSE_TO_NEWEST_THRESHOLD = 15; @@ -148,6 +151,7 @@ function ReportActionsList({ listID, shouldEnableAutoScrollToTopThreshold, parentReportActionForTransactionThread, + transactionsAndViolationsByReport, }: ReportActionsListProps) { const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const personalDetailsList = usePersonalDetails(); @@ -586,6 +590,7 @@ function ReportActionsList({ index={index} report={report} transactionThreadReport={transactionThreadReport} + transactionsAndViolationsByReport={transactionsAndViolationsByReport} linkedReportActionID={linkedReportActionID} displayAsGroup={ !isConsecutiveChronosAutomaticTimerAction(sortedVisibleReportActions, index, chatIncludesChronosWithID(reportAction?.reportID)) && @@ -602,20 +607,21 @@ function ReportActionsList({ ); }, [ + sortedReportActions, + parentReportAction, + parentReportActionForTransactionThread, report, + transactionThreadReport, + transactionsAndViolationsByReport, allReports, transactions, linkedReportActionID, sortedVisibleReportActions, mostRecentIOUReportActionID, shouldHideThreadDividerLine, - parentReportAction, - sortedReportActions, - transactionThreadReport, - parentReportActionForTransactionThread, - shouldUseThreadDividerLine, - firstVisibleReportActionID, unreadMarkerReportActionID, + firstVisibleReportActionID, + shouldUseThreadDividerLine, ], ); diff --git a/src/pages/home/report/ReportActionsListItemRenderer.tsx b/src/pages/home/report/ReportActionsListItemRenderer.tsx index 154062f4f268..9f8e9af055f0 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.tsx +++ b/src/pages/home/report/ReportActionsListItemRenderer.tsx @@ -3,7 +3,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {getOriginalMessage, isSentMoneyReportAction, isTransactionThread} from '@libs/ReportActionsUtils'; import {isChatThread, isInvoiceRoom, isPolicyExpenseChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; -import type {Report, ReportAction, Transaction} from '@src/types/onyx'; +import type {Report, ReportAction, ReportTransactionsAndViolationsDerivedValue, Transaction} from '@src/types/onyx'; import ReportActionItem from './ReportActionItem'; import ReportActionItemParentAction from './ReportActionItemParentAction'; @@ -35,6 +35,9 @@ type ReportActionsListItemRendererProps = { /** The transaction thread report associated with the report for this action, if any */ transactionThreadReport: OnyxEntry; + /** All transactions grouped by reportID */ + transactionsAndViolationsByReport: ReportTransactionsAndViolationsDerivedValue; + /** Should the comment have the appearance of being grouped with the previous comment? */ displayAsGroup: boolean; @@ -69,6 +72,7 @@ function ReportActionsListItemRenderer({ index, report, transactionThreadReport, + transactionsAndViolationsByReport, displayAsGroup, mostRecentIOUReportActionID = '', shouldHideThreadDividerLine, @@ -166,6 +170,7 @@ function ReportActionsListItemRenderer({ index={index} isFirstVisibleReportAction={isFirstVisibleReportAction} shouldUseThreadDividerLine={shouldUseThreadDividerLine} + transactionsAndViolationsByReport={transactionsAndViolationsByReport} /> ); } @@ -198,6 +203,7 @@ function ReportActionsListItemRenderer({ index={index} isFirstVisibleReportAction={isFirstVisibleReportAction} shouldUseThreadDividerLine={shouldUseThreadDividerLine} + transactionsAndViolationsByReport={transactionsAndViolationsByReport} /> ); } diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index c0679a28f2b1..a31aff22d2ac 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -1,7 +1,7 @@ import {useIsFocused, useRoute} from '@react-navigation/native'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {InteractionManager} from 'react-native'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; @@ -13,7 +13,7 @@ import {updateLoadingInitialReportAction} from '@libs/actions/Report'; import Timing from '@libs/actions/Timing'; import DateUtils from '@libs/DateUtils'; import getIsReportFullyVisible from '@libs/getIsReportFullyVisible'; -import {selectAllTransactionsForReport} from '@libs/MoneyRequestReportUtils'; +import {getAllNonDeletedTransactions} from '@libs/MoneyRequestReportUtils'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {ReportsSplitNavigatorParamList} from '@libs/Navigation/types'; import {generateNewRandomInt, rand64} from '@libs/NumberUtils'; @@ -61,6 +61,9 @@ type ReportActionsViewProps = { /** If the report has older actions to load */ hasOlderActions: boolean; + + /** All transactions grouped by reportID */ + transactionsAndViolationsByReport: OnyxTypes.ReportTransactionsAndViolationsDerivedValue; }; let listOldID = Math.round(Math.random() * 100); @@ -73,6 +76,7 @@ function ReportActionsView({ transactionThreadReportID, hasNewerActions, hasOlderActions, + transactionsAndViolationsByReport, }: ReportActionsViewProps) { useCopySelectionHelper(); const route = useRoute>(); @@ -95,11 +99,9 @@ function ReportActionsView({ const prevShouldUseNarrowLayoutRef = useRef(shouldUseNarrowLayout); const reportID = report.reportID; const isReportFullyVisible = useMemo((): boolean => getIsReportFullyVisible(isFocused), [isFocused]); - const [reportTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { - selector: (allTransactions: OnyxCollection) => - selectAllTransactionsForReport(allTransactions, reportID, allReportActions ?? []).map((transaction) => transaction.transactionID), - canBeMissing: true, - }); + const {transactions} = transactionsAndViolationsByReport[reportID ?? CONST.DEFAULT_NUMBER_ID] ?? {}; + const reportTransactions = getAllNonDeletedTransactions(Object.values(transactions ?? {}) ?? [], allReportActions ?? []); + const reportTransactionIDs = reportTransactions?.map((transaction) => transaction.transactionID); useEffect(() => { // When we linked to message - we do not need to wait for initial actions - they already exists @@ -307,6 +309,7 @@ function ReportActionsView({ loadNewerChats={loadNewerChats} listID={listID} shouldEnableAutoScrollToTopThreshold={shouldEnableAutoScroll} + transactionsAndViolationsByReport={transactionsAndViolationsByReport} /> diff --git a/src/pages/home/report/TripSummary.tsx b/src/pages/home/report/TripSummary.tsx index 89abed699cc7..c51f3d621619 100644 --- a/src/pages/home/report/TripSummary.tsx +++ b/src/pages/home/report/TripSummary.tsx @@ -3,17 +3,20 @@ import {useOnyx} from 'react-native-onyx'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import TripDetailsView from '@components/ReportActionItem/TripDetailsView'; import useTripTransactions from '@hooks/useTripTransactions'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {ReportTransactionsAndViolationsDerivedValue} from '@src/types/onyx'; type TripSummaryProps = { /** The report ID */ reportID: string | undefined; + + /** The transactions for the report */ + transactionsAndViolationsByReport: ReportTransactionsAndViolationsDerivedValue; }; -function TripSummary({reportID}: TripSummaryProps) { - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID ?? CONST.DEFAULT_NUMBER_ID}`); - const tripTransactions = useTripTransactions(reportID); +function TripSummary({reportID, transactionsAndViolationsByReport}: TripSummaryProps) { + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {canBeMissing: true}); + const tripTransactions = useTripTransactions(reportID, transactionsAndViolationsByReport); if (!reportID || tripTransactions.length === 0) { return null; diff --git a/src/types/onyx/DerivedValues.ts b/src/types/onyx/DerivedValues.ts index 1a396e81fb4c..6afd8bb66eb0 100644 --- a/src/types/onyx/DerivedValues.ts +++ b/src/types/onyx/DerivedValues.ts @@ -1,6 +1,8 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; import type {Errors} from './OnyxCommon'; +import type Transaction from './Transaction'; +import type TransactionViolations from './TransactionViolation'; /** * The attributes of a report. @@ -42,5 +44,24 @@ type ReportAttributesDerivedValue = { locale: string | null; }; +/** + * + */ +type ReportTransactionsAndViolations = { + /** + * The transactions of the report. + */ + transactions: Record; + /** + * The violations of the report. + */ + violations: Record; +}; + +/** + * The derived value for report transactions. + */ +type ReportTransactionsAndViolationsDerivedValue = Record; + export default ReportAttributesDerivedValue; -export type {ReportAttributes}; +export type {ReportAttributes, ReportAttributesDerivedValue, ReportTransactionsAndViolationsDerivedValue, ReportTransactionsAndViolations}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index cfc04551f58a..09ed6afdae47 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -23,7 +23,7 @@ import type Credentials from './Credentials'; import type Currency from './Currency'; import type {CurrencyList} from './Currency'; import type CustomStatusDraft from './CustomStatusDraft'; -import type ReportAttributesDerivedValue from './DerivedValues'; +import type {ReportAttributesDerivedValue, ReportTransactionsAndViolationsDerivedValue} from './DerivedValues'; import type DismissedProductTraining from './DismissedProductTraining'; import type DismissedReferralBanners from './DismissedReferralBanners'; import type Download from './Download'; @@ -261,6 +261,7 @@ export type { SidePanel, LastPaymentMethodType, ReportAttributesDerivedValue, + ReportTransactionsAndViolationsDerivedValue, ScheduleCallDraft, ValidateUserAndGetAccessiblePolicies, BillingReceiptDetails, diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 685bc2dfdd4c..605596863ddd 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -1,9 +1,8 @@ -import {renderHook} from '@testing-library/react-native'; import {format} from 'date-fns'; import {deepEqual} from 'fast-equals'; import type {OnyxCollection, OnyxEntry, OnyxInputValue} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import useReportWithTransactionsAndViolations from '@hooks/useReportWithTransactionsAndViolations'; +import OnyxUtils from 'react-native-onyx/dist/OnyxUtils'; import { addSplitExpenseField, calculateDiffAmount, @@ -56,6 +55,7 @@ import { import {buildOptimisticIOUReport, buildOptimisticIOUReportAction, buildTransactionThread, createDraftTransactionAndNavigateToParticipantSelector, isIOUReport} from '@libs/ReportUtils'; import type {OptimisticChatReport} from '@libs/ReportUtils'; import {buildOptimisticTransaction, getValidWaypoints, isDistanceRequest as isDistanceRequestUtil} from '@libs/TransactionUtils'; +import initOnyxDerivedValues from '@userActions/OnyxDerived'; import CONST from '@src/CONST'; import type {IOUAction} from '@src/CONST'; import IntlStore from '@src/languages/IntlStore'; @@ -154,6 +154,7 @@ describe('actions/IOU', () => { [ONYXKEYS.PERSONAL_DETAILS_LIST]: {[RORY_ACCOUNT_ID]: {accountID: RORY_ACCOUNT_ID, login: RORY_EMAIL}}, }, }); + initOnyxDerivedValues(); IntlStore.load(CONST.LOCALES.EN); return waitForBatchedUpdates(); }); @@ -5206,9 +5207,10 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); expect(canApproveIOU(fakeReport, fakePolicy)).toBeFalsy(); - // Then should return false when passing transactions directly as the third parameter instead of relying on Onyx data - const {result} = renderHook(() => useReportWithTransactionsAndViolations(reportID)); - expect(canApproveIOU(result.current.at(0) as Report, fakePolicy, result.current.at(1) as Transaction[])).toBeFalsy(); + const transactionsAndViolations = await OnyxUtils.get(ONYXKEYS.DERIVED.REPORT_TRANSACTIONS_AND_VIOLATIONS); + const report = await OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT}${fakeReport.reportID}`); + const reportTransactionsAndViolations = transactionsAndViolations?.[fakeReport.reportID]; + expect(canApproveIOU(report, fakePolicy, Object.values(reportTransactionsAndViolations?.transactions ?? {}))).toBeFalsy(); }); it('should return false if we have only scan failure transactions', async () => { const policyID = '2'; @@ -5261,9 +5263,10 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); expect(canApproveIOU(fakeReport, fakePolicy)).toBeFalsy(); - // Then should return false when passing transactions directly as the third parameter instead of relying on Onyx data - const {result} = renderHook(() => useReportWithTransactionsAndViolations(reportID)); - expect(canApproveIOU(result.current.at(0) as Report, fakePolicy, result.current.at(1) as Transaction[])).toBeFalsy(); + const transactionsAndViolations = await OnyxUtils.get(ONYXKEYS.DERIVED.REPORT_TRANSACTIONS_AND_VIOLATIONS); + const report = await OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT}${fakeReport.reportID}`); + const reportTransactionsAndViolations = transactionsAndViolations?.[fakeReport.reportID]; + expect(canApproveIOU(report, fakePolicy, Object.values(reportTransactionsAndViolations?.transactions ?? {}))).toBeFalsy(); }); it('should return false if all transactions are pending card or scan failure transaction', async () => { const policyID = '2'; @@ -5307,9 +5310,10 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); expect(canApproveIOU(fakeReport, fakePolicy)).toBeFalsy(); - // Then should return false when passing transactions directly as the third parameter instead of relying on Onyx data - const {result} = renderHook(() => useReportWithTransactionsAndViolations(reportID)); - expect(canApproveIOU(result.current.at(0) as Report, fakePolicy, result.current.at(1) as Transaction[])).toBeFalsy(); + const transactionsAndViolations = await OnyxUtils.get(ONYXKEYS.DERIVED.REPORT_TRANSACTIONS_AND_VIOLATIONS); + const report = await OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT}${fakeReport.reportID}`); + const reportTransactionsAndViolations = transactionsAndViolations?.[fakeReport.reportID]; + expect(canApproveIOU(report, fakePolicy, Object.values(reportTransactionsAndViolations?.transactions ?? {}))).toBeFalsy(); }); it('should return true if at least one transactions is not pending card or scan failure transaction', async () => { const policyID = '2'; @@ -5358,9 +5362,10 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); expect(canApproveIOU(fakeReport, fakePolicy)).toBeTruthy(); - // Then should return true when passing transactions directly as the third parameter instead of relying on Onyx data - const {result} = renderHook(() => useReportWithTransactionsAndViolations(reportID)); - expect(canApproveIOU(result.current.at(0) as Report, fakePolicy, result.current.at(1) as Transaction[])).toBeTruthy(); + const transactionsAndViolations = await OnyxUtils.get(ONYXKEYS.DERIVED.REPORT_TRANSACTIONS_AND_VIOLATIONS); + const report = await OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT}${fakeReport.reportID}`); + const reportTransactionsAndViolations = transactionsAndViolations?.[fakeReport.reportID]; + expect(canApproveIOU(report, fakePolicy, Object.values(reportTransactionsAndViolations?.transactions ?? {}))).toBeTruthy(); }); it('should return false if the report is closed', async () => { diff --git a/tests/perf-test/ReportActionsList.perf-test.tsx b/tests/perf-test/ReportActionsList.perf-test.tsx index 42026e63f6a8..d3546621a654 100644 --- a/tests/perf-test/ReportActionsList.perf-test.tsx +++ b/tests/perf-test/ReportActionsList.perf-test.tsx @@ -121,6 +121,7 @@ function ReportActionsListWrapper() { loadOlderChats={mockLoadChats} loadNewerChats={mockLoadChats} transactionThreadReport={report} + transactionsAndViolationsByReport={{}} /> diff --git a/tests/ui/MoneyRequestReportPreview.test.tsx b/tests/ui/MoneyRequestReportPreview.test.tsx index d837d2063b7d..0c82e630c957 100644 --- a/tests/ui/MoneyRequestReportPreview.test.tsx +++ b/tests/ui/MoneyRequestReportPreview.test.tsx @@ -1,7 +1,7 @@ import {PortalProvider} from '@gorhom/portal'; import * as NativeNavigation from '@react-navigation/native'; import {fireEvent, render, screen} from '@testing-library/react-native'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import ComposeProviders from '@components/ComposeProviders'; import {LocaleContextProvider} from '@components/LocaleContextProvider'; @@ -19,9 +19,10 @@ import CONST from '@src/CONST'; import * as ReportActionUtils from '@src/libs/ReportActionsUtils'; import * as ReportUtils from '@src/libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Report, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx'; +import type {Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx'; +import type {ReportTransactionsAndViolationsDerivedValue} from '@src/types/onyx/DerivedValues'; import {actionR14932 as mockAction} from '../../__mocks__/reportData/actions'; -import {chatReportR14932 as mockChatReport, iouReportR14932 as mockIOUReport} from '../../__mocks__/reportData/reports'; +import {chatReportR14932 as mockChatReport} from '../../__mocks__/reportData/reports'; import {transactionR14932 as mockTransaction} from '../../__mocks__/reportData/transactions'; import {violationsR14932 as mockViolations} from '../../__mocks__/reportData/violations'; import * as TestHelper from '../utils/TestHelper'; @@ -44,12 +45,6 @@ jest.mock('@react-native-community/geolocation', () => ({ setRNConfiguration: jest.fn(), })); -jest.mock('@src/hooks/useReportWithTransactionsAndViolations', () => - jest.fn((): [OnyxEntry, Transaction[], OnyxCollection] => { - return [mockChatReport, [mockTransaction, {...mockTransaction, transactionID: mockSecondTransactionID}], {violations: mockViolations}]; - }), -); - const getIOUActionForReportID = (reportID: string | undefined, transactionID: string | undefined) => { if (!reportID || !transactionID) { return undefined; @@ -60,7 +55,7 @@ const getIOUActionForReportID = (reportID: string | undefined, transactionID: st const hasViolations = (reportID: string | undefined, transactionViolations: OnyxCollection, shouldShowInReview?: boolean) => (shouldShowInReview === undefined || shouldShowInReview) && Object.values(transactionViolations ?? {}).length > 0; -const renderPage = ({isWhisper = false, isHovered = false, contextMenuAnchor = null}: Partial) => { +const renderPage = ({isWhisper = false, isHovered = false, contextMenuAnchor = null, transactionsAndViolationsByReport = {}}: Partial) => { return render( @@ -69,7 +64,7 @@ const renderPage = ({isWhisper = false, isHovered = false, contextMenuAnchor = n {}} @@ -77,6 +72,7 @@ const renderPage = ({isWhisper = false, isHovered = false, contextMenuAnchor = n onPaymentOptionsHide={() => {}} isHovered={isHovered} isWhisper={isWhisper} + transactionsAndViolationsByReport={transactionsAndViolationsByReport} /> @@ -119,6 +115,16 @@ const mockOnyxViolations: Record<`${typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLA const arrayOfTransactions = Object.values(mockOnyxTransactions); +const buildTransactionsAndViolationsByReport = (): ReportTransactionsAndViolationsDerivedValue => { + const reportID = mockChatReport.iouReportID; + return { + [String(reportID)]: { + transactions: mockOnyxTransactions, + violations: mockOnyxViolations, + }, + }; +}; + TestHelper.setupApp(); TestHelper.setupGlobalFetchMock(); @@ -139,10 +145,11 @@ describe('MoneyRequestReportPreview', () => { }); it('renders transaction details and associated report name correctly', async () => { - renderPage({}); + renderPage({transactionsAndViolationsByReport: buildTransactionsAndViolationsByReport()}); await waitForBatchedUpdatesWithAct(); setCurrentWidth(); await Onyx.mergeCollection(ONYXKEYS.COLLECTION.TRANSACTION, mockOnyxTransactions).then(waitForBatchedUpdates); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${mockChatReport.iouReportID}`, mockChatReport).then(waitForBatchedUpdates); const {reportName: moneyRequestReportPreviewName = ''} = mockChatReport; for (const transaction of arrayOfTransactions) { const {transactionDisplayAmount, transactionHeaderText} = getTransactionDisplayAmountAndHeaderText(transaction); @@ -155,7 +162,7 @@ describe('MoneyRequestReportPreview', () => { }); it('renders RBR for every transaction with violations', async () => { - renderPage({}); + renderPage({transactionsAndViolationsByReport: buildTransactionsAndViolationsByReport()}); await waitForBatchedUpdatesWithAct(); setCurrentWidth(); await Onyx.multiSet({...mockOnyxTransactions, ...mockOnyxViolations}); @@ -163,7 +170,7 @@ describe('MoneyRequestReportPreview', () => { }); it('renders a skeleton if the transaction is empty', async () => { - renderPage({}); + renderPage({transactionsAndViolationsByReport: buildTransactionsAndViolationsByReport()}); await waitForBatchedUpdatesWithAct(); setCurrentWidth(); expect(screen.getAllByTestId(TransactionPreviewSkeletonView.displayName)).toHaveLength(2);