From 76e8116a3a4b31cbd54d8a58d3a00a1447cfe1e5 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Thu, 5 Jun 2025 12:00:53 +0200 Subject: [PATCH 1/3] Exclude PAY IOU type from transactions count --- src/libs/ReportActionsUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index f2a85f3f0269..3c6d4533a272 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1239,7 +1239,9 @@ function getOneTransactionThreadReportID( const iouRequestActions = []; for (const action of reportActionsArray) { - if (!isIOUActionMatchingTransactionList(action, reportTransactionIDs, true)) { + // If the original message is a 'pay' IOU, it shouldn't be added to the transaction count. + // However, it is excluded from the matching function in order to display it properly, so we need to compare the type here. + if (!isIOUActionMatchingTransactionList(action, reportTransactionIDs, true) || getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY) { // eslint-disable-next-line no-continue continue; } From 2c13b79ba9263d4f10ad0c76ace141e49c59e669 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Thu, 5 Jun 2025 12:27:07 +0200 Subject: [PATCH 2/3] Fix incorrect action type in primaryAction test --- tests/unit/ReportPrimaryActionUtilsTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/ReportPrimaryActionUtilsTest.ts b/tests/unit/ReportPrimaryActionUtilsTest.ts index cb7b39e9cb4a..735d24577c59 100644 --- a/tests/unit/ReportPrimaryActionUtilsTest.ts +++ b/tests/unit/ReportPrimaryActionUtilsTest.ts @@ -236,7 +236,7 @@ describe('getPrimaryAction', () => { }, ], originalMessage: { - type: CONST.IOU.REPORT_ACTION_TYPE.PAY, + type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, IOUTransactionID: TRANSACTION_ID, }, } as unknown as ReportAction; From da7cebc7f66171a462d46bf5cd6e13f294e1f6d6 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Thu, 5 Jun 2025 15:16:09 +0200 Subject: [PATCH 3/3] Add tests for getOneTransactionThreadReportID --- tests/unit/ReportActionsUtilsTest.ts | 74 +++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index a3f05912c014..fe7809f65700 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -2,9 +2,10 @@ import type {KeyValueMapping} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import {isExpenseReport} from '@libs/ReportUtils'; import {actionR14932 as mockIOUAction, originalMessageR14932 as mockOriginalMessage} from '../../__mocks__/reportData/actions'; +import {iouReportR14932 as mockIOUReport} from '../../__mocks__/reportData/reports'; import CONST from '../../src/CONST'; import * as ReportActionsUtils from '../../src/libs/ReportActionsUtils'; -import {isIOUActionMatchingTransactionList} from '../../src/libs/ReportActionsUtils'; +import {getOneTransactionThreadReportID, getOriginalMessage, isIOUActionMatchingTransactionList} from '../../src/libs/ReportActionsUtils'; import ONYXKEYS from '../../src/ONYXKEYS'; import type {Report, ReportAction} from '../../src/types/onyx'; import createRandomReport from '../utils/collections/reports'; @@ -353,6 +354,75 @@ describe('ReportActionsUtils', () => { }); }); + describe('getOneTransactionThreadReportID', () => { + const IOUReportID = 'REPORT_IOU'; + const IOUExpenseReportID = 'REPORT_EXPENSE'; + const IOUTransactionID = 'TRANSACTION_IOU'; + const IOUExpenseTransactionID = 'TRANSACTION_EXPENSE'; + + const mockedReports: Record<`${typeof ONYXKEYS.COLLECTION.REPORT}${string}`, Report> = { + [`${ONYXKEYS.COLLECTION.REPORT}${IOUReportID}`]: {...mockIOUReport, reportID: IOUReportID}, + [`${ONYXKEYS.COLLECTION.REPORT}${IOUExpenseReportID}`]: {...mockIOUReport, type: CONST.REPORT.TYPE.EXPENSE, reportID: IOUExpenseReportID}, + }; + + const linkedCreateAction = { + ...mockIOUAction, + originalMessage: {...getOriginalMessage(mockIOUAction), IOUTransactionID}, + }; + + const unlinkedCreateAction = { + ...mockIOUAction, + originalMessage: {...getOriginalMessage(mockIOUAction), IOUTransactionID: IOUExpenseTransactionID}, + }; + + const linkedDeleteAction = { + ...mockIOUAction, + originalMessage: { + ...getOriginalMessage(mockIOUAction), + IOUTransactionID, + type: CONST.IOU.REPORT_ACTION_TYPE.DELETE, + }, + }; + + const linkedPayAction = { + ...mockIOUAction, + originalMessage: { + ...getOriginalMessage(mockIOUAction), + IOUTransactionID, + type: CONST.IOU.REPORT_ACTION_TYPE.PAY, + }, + }; + + beforeEach(async () => { + await Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, mockedReports); + }); + + it('should return the childReportID for a valid single IOU action', () => { + const result = getOneTransactionThreadReportID(IOUReportID, [linkedCreateAction], false, [IOUTransactionID]); + expect(result).toEqual(linkedCreateAction.childReportID); + }); + + it('should return undefined for action with a transaction that is not linked to it', () => { + const result = getOneTransactionThreadReportID(IOUReportID, [unlinkedCreateAction], false, [IOUTransactionID]); + expect(result).toBeUndefined(); + }); + + it('should return undefined if multiple IOU actions are present', () => { + const result = getOneTransactionThreadReportID(IOUReportID, [linkedCreateAction, linkedCreateAction], false, [IOUTransactionID]); + expect(result).toBeUndefined(); + }); + + it('should skip actions where original message type is PAY', () => { + const result = getOneTransactionThreadReportID(IOUReportID, [linkedPayAction, linkedCreateAction], false, [IOUTransactionID]); + expect(result).toEqual(linkedCreateAction.childReportID); + }); + + it('should return undefined if no valid IOU actions are present', () => { + const result = getOneTransactionThreadReportID(IOUReportID, [unlinkedCreateAction, linkedDeleteAction, linkedPayAction], false, [IOUTransactionID]); + expect(result).toBeUndefined(); + }); + }); + describe('getSortedReportActionsForDisplay', () => { it('should filter out non-whitelisted actions', () => { const input: ReportAction[] = [ @@ -786,6 +856,8 @@ describe('ReportActionsUtils', () => { }); }); + describe('getOneTransactionThreadReportID', () => {}); + describe('shouldShowAddMissingDetails', () => { it('should return true if personal detail is not completed', async () => { const card = {