@@ -11,6 +11,8 @@ import {addComment, notifyNewAction} from '@libs/actions/Report';
1111import initSplitExpense from '@libs/actions/SplitExpenses' ;
1212import { WRITE_COMMANDS } from '@libs/API/types' ;
1313import { getCurrencyDecimals , getCurrencySymbol } from '@libs/CurrencyUtils' ;
14+ import { deferOrExecuteWrite , reserveDeferredWriteChannel } from '@libs/deferredLayoutWrite' ;
15+ import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute' ;
1416import { rand64 } from '@libs/NumberUtils' ;
1517import { getIOUActionForReportID , getIOUActionForTransactionID , getOriginalMessage , isActionOfType , isAddCommentAction , isDeletedAction , isMoneyRequestAction } from '@libs/ReportActionsUtils' ;
1618import { buildOptimisticIOUReportAction , getAncestors , getReportOrDraftReport } from '@libs/ReportUtils' ;
@@ -105,7 +107,7 @@ jest.mock('@libs/deferredLayoutWrite', () => ({
105107 cancelDeferredWrite : jest . fn ( ) ,
106108 hasDeferredWrite : ( ) => false ,
107109 getOptimisticWatchKey : ( ) => undefined ,
108- deferOrExecuteWrite : ( apiWrite : ( ) => void ) => apiWrite ( ) ,
110+ deferOrExecuteWrite : jest . fn ( ( apiWrite : ( ) => void ) => apiWrite ( ) ) ,
109111 reserveDeferredWriteChannel : jest . fn ( ) ,
110112} ) ) ;
111113jest . mock ( '@hooks/useCardFeedsForDisplay' , ( ) => jest . fn ( ( ) => ( { defaultCardFeed : null , cardFeedsByPolicy : { } } ) ) ) ;
@@ -9490,3 +9492,144 @@ describe('startSplitBill delegateAccountID forwarding', () => {
94909492 expect ( splitIOUAction ?. delegateAccountID ) . toBe ( DELEGATE_ACCOUNT_ID ) ;
94919493 } ) ;
94929494} ) ;
9495+
9496+ /**
9497+ * Minimal fixture for the split-expenses save flow: one expense report holding one transaction,
9498+ * with a draft that splits it in two.
9499+ */
9500+ const buildSplitFlowParams = async ( ) => {
9501+ const expenseReport : Report = {
9502+ ...createRandomReport ( 9001 , undefined ) ,
9503+ type : CONST . REPORT . TYPE . EXPENSE ,
9504+ } ;
9505+ const transaction : Transaction = {
9506+ amount : 100 ,
9507+ currency : 'USD' ,
9508+ transactionID : '9001' ,
9509+ reportID : expenseReport . reportID ,
9510+ created : DateUtils . getDBTime ( ) ,
9511+ merchant : 'test' ,
9512+ } ;
9513+ const transactionThread : Report = { ...createRandomReport ( 9002 , undefined ) } ;
9514+ const iouAction : ReportAction = {
9515+ ...buildOptimisticIOUReportAction ( {
9516+ type : CONST . IOU . REPORT_ACTION_TYPE . CREATE ,
9517+ amount : transaction . amount ,
9518+ currency : transaction . currency ,
9519+ comment : '' ,
9520+ participants : [ ] ,
9521+ transactionID : transaction . transactionID ,
9522+ iouReportID : expenseReport . reportID ,
9523+ } ) ,
9524+ childReportID : transactionThread . reportID ,
9525+ } ;
9526+
9527+ await Onyx . merge ( `${ ONYXKEYS . COLLECTION . REPORT } ${ expenseReport . reportID } ` , expenseReport ) ;
9528+ await Onyx . merge ( `${ ONYXKEYS . COLLECTION . REPORT } ${ transactionThread . reportID } ` , transactionThread ) ;
9529+ await Onyx . merge ( `${ ONYXKEYS . COLLECTION . REPORT_ACTIONS } ${ expenseReport . reportID } ` , { [ iouAction . reportActionID ] : iouAction } ) ;
9530+ await Onyx . merge ( `${ ONYXKEYS . COLLECTION . TRANSACTION } ${ transaction . transactionID } ` , transaction ) ;
9531+ await waitForBatchedUpdates ( ) ;
9532+
9533+ let allTransactions : OnyxCollection < Transaction > ;
9534+ let allReports : OnyxCollection < Report > ;
9535+ await getOnyxData ( {
9536+ key : ONYXKEYS . COLLECTION . TRANSACTION ,
9537+ callback : ( value ) => {
9538+ allTransactions = value ;
9539+ } ,
9540+ } ) ;
9541+ await getOnyxData ( {
9542+ key : ONYXKEYS . COLLECTION . REPORT ,
9543+ callback : ( value ) => {
9544+ allReports = value ;
9545+ } ,
9546+ } ) ;
9547+
9548+ const reports = getTransactionAndExpenseReports ( expenseReport . reportID ) ;
9549+ const params = {
9550+ allTransactionsList : allTransactions ,
9551+ allReportsList : allReports ,
9552+ allReportActionsList : undefined ,
9553+ allReportNameValuePairsList : undefined ,
9554+ transactionData : {
9555+ reportID : expenseReport . reportID ,
9556+ originalTransactionID : transaction . transactionID ,
9557+ splitExpenses : [
9558+ { transactionID : '9003' , amount : 50 , currency : 'USD' , description : '' , category : '' , tags : [ '' ] , created : transaction . created , reportID : expenseReport . reportID } ,
9559+ { transactionID : '9004' , amount : 50 , currency : 'USD' , description : '' , category : '' , tags : [ '' ] , created : transaction . created , reportID : expenseReport . reportID } ,
9560+ ] ,
9561+ splitExpensesTotal : 100 ,
9562+ } ,
9563+ searchContext : { currentSearchHash : - 2 } ,
9564+ policyCategories : undefined ,
9565+ policy : undefined ,
9566+ policyRecentlyUsedCategories : [ ] ,
9567+ iouReport : expenseReport ,
9568+ firstIOU : iouAction ,
9569+ isASAPSubmitBetaEnabled : false ,
9570+ currentUserPersonalDetails,
9571+ transactionViolations : { } ,
9572+ policyRecentlyUsedCurrencies : [ ] ,
9573+ quickAction : undefined ,
9574+ betas : [ CONST . BETAS . ALL ] ,
9575+ allPolicyTags : undefined ,
9576+ personalDetails : { [ RORY_ACCOUNT_ID ] : { accountID : RORY_ACCOUNT_ID , login : RORY_EMAIL } } ,
9577+ transactionReport : reports . transactionReport ,
9578+ expenseReport : reports . expenseReport ,
9579+ isOffline : false ,
9580+ delegateAccountID : undefined ,
9581+ isTrackIntentUser : false ,
9582+ } ;
9583+
9584+ return { expenseReport, iouAction, params} ;
9585+ } ;
9586+
9587+ describe ( 'split save deferred write' , ( ) => {
9588+ beforeEach ( ( ) => {
9589+ jest . mocked ( isSearchTopmostFullScreenRoute ) . mockReturnValue ( false ) ;
9590+ } ) ;
9591+
9592+ it ( 'reserves the SEARCH channel and defers the write when saving from the Search page' , async ( ) => {
9593+ // Given a split saved while the Search page is the topmost full screen route
9594+ jest . mocked ( isSearchTopmostFullScreenRoute ) . mockReturnValue ( true ) ;
9595+ const { expenseReport, iouAction, params} = await buildSplitFlowParams ( ) ;
9596+
9597+ // When the split is saved
9598+ updateSplitTransactionsFromSplitExpensesFlow ( params ) ;
9599+ await waitForBatchedUpdates ( ) ;
9600+
9601+ // Then the SEARCH channel is reserved before navigating, so a flush fired by the
9602+ // destination's layout is remembered rather than dropped
9603+ expect ( reserveDeferredWriteChannel ) . toHaveBeenCalledWith ( CONST . DEFERRED_LAYOUT_WRITE_KEYS . SEARCH ) ;
9604+
9605+ // And the write is routed through the deferral rather than executed inline
9606+ expect ( deferOrExecuteWrite ) . toHaveBeenCalledWith ( expect . any ( Function ) , expect . objectContaining ( { shouldDeferForSearch : true } ) ) ;
9607+ expect ( expenseReport . reportID ) . toBeTruthy ( ) ;
9608+ expect ( iouAction . reportActionID ) . toBeTruthy ( ) ;
9609+ } ) ;
9610+
9611+ it ( 'reserves the DISMISS_MODAL channel with the destination report when saving from a report' , async ( ) => {
9612+ // Given a split saved from a report rather than the Search page
9613+ const { params} = await buildSplitFlowParams ( ) ;
9614+
9615+ // When the split is saved
9616+ updateSplitTransactionsFromSplitExpensesFlow ( params ) ;
9617+ await waitForBatchedUpdates ( ) ;
9618+
9619+ // Then the write is still deferred, but never onto the SEARCH channel
9620+ expect ( deferOrExecuteWrite ) . toHaveBeenCalledWith ( expect . any ( Function ) , expect . objectContaining ( { shouldDeferForSearch : false } ) ) ;
9621+ expect ( reserveDeferredWriteChannel ) . not . toHaveBeenCalledWith ( CONST . DEFERRED_LAYOUT_WRITE_KEYS . SEARCH ) ;
9622+ } ) ;
9623+
9624+ it ( 'writes immediately when the caller is not the split-expenses flow' , async ( ) => {
9625+ // Given a direct updateSplitTransactions call, as useDeleteTransactions makes
9626+ const { params} = await buildSplitFlowParams ( ) ;
9627+
9628+ // When it runs outside the split-expenses flow
9629+ updateSplitTransactions ( { ...params , isFromSplitExpensesFlow : false } ) ;
9630+ await waitForBatchedUpdates ( ) ;
9631+
9632+ // Then there is no navigation to hide behind, so the write is not deferred
9633+ expect ( deferOrExecuteWrite ) . not . toHaveBeenCalled ( ) ;
9634+ } ) ;
9635+ } ) ;
0 commit comments