Config.ts
import { Module, registerModule } from '@txp-core/runtime'; import { totalAmountSideEffectPattern } from './totalAmountSideEffectPatterns'; import { totalAmountHandler } from './totalAmountSaga'; export const totalAmountModule: Module = registerModule({ id: 'totalAmount', sideEffects: [ { id: 'totalAmount', description: 'update total amount when line item gets updated/added', pattern: totalAmountSideEffectPattern, handler: totalAmountHandler } ], withModuleDependencies: ['coreTransactionState', 'coreUIState'] });

Totlamount.tsx
import React from 'react'; import { useFormatMessage } from '@txp-core/intl'; import { useTXPStateRead } from '@txp-core/runtime'; import { getMoney } from '@txp-core/basic-utils'; import { Demi } from '@ids-ts/typography'; import styles from 'src/js/modules/TotalAmount/TotalAmount.module.css'; import { TotalsSelectors } from '@txp-core/transactions-calculations'; const TotalAmount = () => { const total = useTXPStateRead(TotalsSelectors.getSubTotal); const formatMessage = useFormatMessage(); const localizedTotalAmount = getMoney(total).toCurrencyString(); const totalLabel = formatMessage({ id: 'total', defaultMessage: 'Total' }); return ( <> <div className={styles.totalRow}> <Demi data-automation-id='total-lable' className={styles.totalRowLabel}> {totalLabel} </Demi> <Demi data-testid='total-amount-text' className={styles.totalRowAmount}> {localizedTotalAmount} </Demi> </div> </> ); };export default TotalAmount;

Total amountsaga.ts
import { ResultType, StepResponse } from '@txp-core/runtime'; import { Effect, select, put } from 'redux-saga/effects'; import { TotalsSelectors } from '@txp-core/transactions-calculations'; import { genericTxnUpdate } from '@txp-core/transactions-core'; export function* totalAmountHandler(): Generator<Effect, StepResponse, string> { const totalAmount = yield select(TotalsSelectors.getSubTotal); yield put(genericTxnUpdate({ amount: totalAmount }, true)); return { result: ResultType.SUCCESS }; }

Totalamountsideeffectpatterns.ts
import { genericLineUpdated, itemLineAdded, ITEM_LINE_ADDED, lineTouched, LINE_TOUCHED, LINE_UPDATED, AllActions, ACCOUNT_LINES_ADDED } from '@txp-core/transactions-core'; type TotalAmountSideEffectTypes = | ReturnType<typeof lineTouched> | ReturnType<typeof genericLineUpdated> | ReturnType<typeof itemLineAdded>; export const totalAmountSideEffectPattern = (action: AllActions | TotalAmountSideEffectTypes) => { switch (action.type) { case ITEM_LINE_ADDED: case ACCOUNT_LINES_ADDED: case LINE_UPDATED: case LINE_TOUCHED: return true; default: return false; } };

The above codes are totalamouunt component implementation done by side effect and more..

Test cases for above codes
import * as React from 'react'; import { fireEvent, screen, waitFor } from '@testing-library/react'; import { TransactionTypes } from '@txp-core/transactions-utils'; import { runtimeTestUtils } from '@txp-core/runtime'; import { testUtils as TransactionsCoreTestUtils } from '@txp-core/transactions-core'; import CoreLineAmountField from '@txp-core/line-amount-field'; import TotalAmount from 'src/js/modules/TotalAmount/TotalAmount'; import TransactionHandler from '@txp-core/transaction-handler'; import { totalAmountModule } from '../config'; import { TotalsSelectors } from '@txp-core/transactions-calculations'; describe('test TotalAmount', () => { beforeEach(() => { jest.spyOn(TotalsSelectors, 'getSubTotal').mockImplementation(() => '22.00'); }); test('Total add up to 0.00 when no lines total', () => { jest.spyOn(TotalsSelectors, 'getSubTotal').mockImplementation(() => '0.00'); runtimeTestUtils.renderTXPComponent(<TotalAmount />, { variability: { environment: { txnType: TransactionTypes.PURCHASE_BILL } } }); expect(screen.getByTestId('total-amount-text').textContent).toEqual('0.00'); }); test('Total add up to correct amount when lines exist', () => { runtimeTestUtils.renderTXPComponent(<TotalAmount />, { variability: { environment: { txnType: TransactionTypes.PURCHASE_BILL } } }); expect(screen.getByTestId('total-amount-text').textContent).toEqual('22.00'); }); describe('integration test to check genTxnUpdate on sideEffects', () => { test('should dispatch generic txn action when line items added/updated', async () => { const variability = { environment: { txnType: TransactionTypes.PURCHASE_BILL } }; const v4Txn = { lines: { accountLines: [ { description: 'line 1', id: 'EXISTING_LINE_1' }, { description: 'line 2', id: 'EXISTING_LINE_2' } ] } }; const txpInstance = TransactionsCoreTestUtils.createMinimumTransactionTXPInstance( [totalAmountModule], variability ); const { mockedTXPHooks } = await runtimeTestUtils.renderTXPLayout( <TransactionHandler transaction={v4Txn}> <CoreLineAmountField idx={0} id='EXISTING_LINE_1' idsProps={{}} /> <TotalAmount /> </TransactionHandler>, txpInstance ); const lineAmount = screen.getByTestId('amount line 1'); // because of MoneyTextField's implementation using local state, we have to fire change + blur // in order for the text field value to update fireEvent.change(lineAmount, { target: { value: '$100.00' } }); fireEvent.blur(lineAmount); await waitFor(() => { expect(mockedTXPHooks.writeSpy).toBeCalled(); }); }); }); });


Below are code for other component outstanding
Config.ts
import { Module, registerModule } from '@txp-core/runtime'; import { outstandingTableSideEffectPattern } from './outstandingTableSideEffectPattern'; import { outstandingTransactionsHandler } from './outstandingTransactionsSaga'; export const outstandingTransactionsModule: Module = registerModule({ id: 'outstandingTransactions', sideEffects: [ { id: 'outstandingTransactions', description: 'update amount when payment amount get updated', pattern: outstandingTableSideEffectPattern, handler: outstandingTransactionsHandler } ], withModuleDependencies: ['coreTransactionState', 'coreUIState'] });

outstandingTransactionsSaga.ts
import { ResultType, StepResponse } from '@txp-core/runtime'; import { Effect, put } from 'redux-saga/effects'; import { paymentTableSelectors } from '@txp-core/payment-transactions-table'; import { TransactionSelectors, genericTxnUpdate } from '@txp-core/transactions-core'; import { getAmount, select } from '@txp-core/basic-utils'; export function* outstandingTransactionsHandler(): Generator<Effect, StepResponse, string> { const isNewTransaction = yield* select(TransactionSelectors.getIsNewTransaction); const chargesTable = yield* select(paymentTableSelectors.getCharges); if (chargesTable) { let totalLinkedPaymentAmount; if (isNewTransaction) { totalLinkedPaymentAmount = chargesTable.reduce((total, charge) => { if (charge.isLinkedPaymentAmount) { return getAmount(total).add(getAmount(charge.linkedPaymentAmount)); } return total; }, 0); } else { totalLinkedPaymentAmount = chargesTable.reduce((total, charge) => { return getAmount(total).add(getAmount(charge.linkedPaymentAmount)); }, 0); } const updatedAmount = Number(totalLinkedPaymentAmount).toFixed(2).toString(); yield put(genericTxnUpdate({ amount: updatedAmount })); } return { result: ResultType.SUCCESS }; }

outstandingTableSideEffectPattern.ts
import { CHARGES_TABLE_DATA_UPDATE, chargesTableDataUpdate } from '@txp-core/payment-transactions-table'; import { AllActions } from '@txp-core/transactions-core'; type OutstandingTableSideEffectTypes = ReturnType<typeof chargesTableDataUpdate>; export const outstandingTableSideEffectPattern = ( action: OutstandingTableSideEffectTypes | AllActions ) => { switch (action.type) { case CHARGES_TABLE_DATA_UPDATE: return true; default: return false; } };

Please write test cases for above codes