Skip to content

Commit

Permalink
Add unit test, wrap grouping functions
Browse files Browse the repository at this point in the history
  • Loading branch information
jmealy committed May 29, 2024
1 parent 641b82c commit aa7f713
Show file tree
Hide file tree
Showing 9 changed files with 42 additions and 38 deletions.
4 changes: 2 additions & 2 deletions src/components/transactions/BulkTxListGroup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const GroupedTxListItems = ({
<Grid item xs={1} sm={0.5} display="flex" alignItems="center">
<SvgIcon className={css.icon} component={BatchIcon} inheritViewBox fontSize="medium" />
</Grid>
<Grid item xs md={6} lg={3} mr={3} alignContent="center">
<Grid item md={6} lg={3} mr={3} alignContent="center">
<Typography noWrap>Transactions executed in bulk</Typography>
</Grid>

Expand All @@ -37,7 +37,7 @@ const GroupedTxListItems = ({
: ''
return (
<Box display="flex" key={tx.transaction.id}>
<Grid item xs={1} sm={0.5} mt={2} mr={1}>
<Grid item xs={1} sm={0.5} mt={2}>
<Typography className={css.nonce}>{nonce}</Typography>
</Grid>

Expand Down
2 changes: 1 addition & 1 deletion src/components/transactions/GroupedTxListItems/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const TxGroup = ({ groupedListItems }: { groupedListItems: Transaction[] }): Rea
key={tx.transaction.id}
className={replacedTxIds.includes(tx.transaction.id) ? css.willBeReplaced : undefined}
>
<ExpandableTransactionItem item={tx} isGrouped />
<ExpandableTransactionItem item={tx} isConflictGroup />
</div>
))}
</Box>
Expand Down
18 changes: 5 additions & 13 deletions src/components/transactions/TxList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import GroupedTxListItems from '@/components/transactions/GroupedTxListItems'
import BulkTxListGroup from '@/components/transactions//BulkTxListGroup'
import { groupBulkTxs, groupConflictingTxs } from '@/utils/tx-list'
import { groupTxs } from '@/utils/tx-list'
import { Box } from '@mui/material'
import type { Transaction, TransactionDetails, TransactionListPage } from '@safe-global/safe-gateway-typescript-sdk'
import type { ReactElement, ReactNode } from 'react'
Expand All @@ -17,13 +17,6 @@ type TxListProps = {
items: TransactionListPage['results']
}

// const isConflictGroup = (group: Transaction[]) => {
// const nonceList = group
// .map((item) => item.transaction.executionInfo)
// .filter(isMultisigExecutionInfo)
// .map((info) => info.nonce)
// return uniq(nonceList).length === 1
// }
const getBulkGroupTxHash = (group: Transaction[], txHashes: Record<string, string>) => {
const hashList = group.map((item) => {
const txId = item.transaction.id
Expand All @@ -39,7 +32,7 @@ export const TxListGrid = ({ children }: { children: ReactNode }): ReactElement
const TxList = ({ items }: TxListProps): ReactElement => {
const chainId = useChainId()

const [txHashes] = useAsync(async () => {
const [txHashesById] = useAsync(async () => {
const transactions = items.filter(isTransactionListItem)
return await getTxDetailsWithBackoff(transactions, chainId).then((txDetailsList) => {
return txDetailsList.reduce((accumulator: Record<string, string>, txDetails: TransactionDetails) => {
Expand All @@ -51,15 +44,14 @@ const TxList = ({ items }: TxListProps): ReactElement => {
})
}, [chainId, items])

const groupedByConflicts = useMemo(() => groupConflictingTxs(items), [items])
const groupedByBulks = useMemo(() => groupBulkTxs(groupedByConflicts, txHashes), [groupedByConflicts, txHashes])
const groupedTransactions = useMemo(() => groupTxs(items, txHashesById), [items, txHashesById])

const transactions = groupedByBulks.map((item, index) => {
const transactions = groupedTransactions.map((item, index) => {
if (!Array.isArray(item)) {
return <TxListItem key={index} item={item} />
}

const bulkTransactionHash = txHashes && getBulkGroupTxHash(item, txHashes)
const bulkTransactionHash = txHashesById && getBulkGroupTxHash(item, txHashesById)
if (bulkTransactionHash) {
return <BulkTxListGroup key={index} groupedListItems={item} transactionHash={bulkTransactionHash} />
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import classNames from 'classnames'
import { trackEvent, TX_LIST_EVENTS } from '@/services/analytics'

type ExpandableTransactionItemProps = {
isGrouped?: boolean
isConflictGroup?: boolean
isBulkGroup?: boolean
item: Transaction
txDetails?: TransactionDetails
}

export const ExpandableTransactionItem = ({
isGrouped = false,
isConflictGroup = false,
isBulkGroup = false,
item,
txDetails,
Expand Down Expand Up @@ -58,7 +58,7 @@ export const ExpandableTransactionItem = ({
},
}}
>
<TxSummary item={item} isGrouped={isGrouped} isBulkGroup={isBulkGroup} />
<TxSummary item={item} isConflictGroup={isConflictGroup} isBulkGroup={isBulkGroup} />
</AccordionSummary>

<AccordionDetails data-testid="accordion-details" sx={{ padding: 0 }}>
Expand Down
28 changes: 18 additions & 10 deletions src/components/transactions/TxSummary/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,64 +54,72 @@ const mockTransactionInHistory = {

describe('TxSummary', () => {
it('should display a nonce if transaction is not grouped', () => {
const { getByText } = render(<TxSummary item={mockTransaction} isGrouped={false} />)
const { getByText } = render(<TxSummary item={mockTransaction} isConflictGroup={false} />)

expect(getByText('7')).toBeInTheDocument()
})

it('should not display a nonce if transaction is grouped', () => {
const { queryByText } = render(<TxSummary item={mockTransaction} isGrouped={true} />)
const { queryByText } = render(<TxSummary item={mockTransaction} isConflictGroup={true} />)

expect(queryByText('7')).not.toBeInTheDocument()
})

it('should not display a nonce if there is no executionInfo', () => {
const { queryByText } = render(<TxSummary item={mockTransactionWithoutExecutionInfo} isGrouped={true} />)
const { queryByText } = render(<TxSummary item={mockTransactionWithoutExecutionInfo} isConflictGroup={true} />)

expect(queryByText('7')).not.toBeInTheDocument()
})

it('should not display a nonce for items in bulk execution group', () => {
const { queryByText } = render(
<TxSummary item={mockTransactionWithoutExecutionInfo} isBulkGroup={true} isConflictGroup={false} />,
)

expect(queryByText('7')).not.toBeInTheDocument()
})

it('should display confirmations if transactions is in queue', () => {
const { getByText } = render(<TxSummary item={mockTransaction} isGrouped={false} />)
const { getByText } = render(<TxSummary item={mockTransaction} isConflictGroup={false} />)

expect(getByText('1 out of 3')).toBeInTheDocument()
})

it('should not display confirmations if transactions is already executed', () => {
const { queryByText } = render(<TxSummary item={mockTransactionInHistory} isGrouped={false} />)
const { queryByText } = render(<TxSummary item={mockTransactionInHistory} isConflictGroup={false} />)

expect(queryByText('1 out of 3')).not.toBeInTheDocument()
})

it('should not display confirmations if there is no executionInfo', () => {
const { queryByText } = render(<TxSummary item={mockTransactionWithoutExecutionInfo} isGrouped={false} />)
const { queryByText } = render(<TxSummary item={mockTransactionWithoutExecutionInfo} isConflictGroup={false} />)

expect(queryByText('1 out of 3')).not.toBeInTheDocument()
})

it('should display a Sign button if confirmations are missing', () => {
const { getByText } = render(<TxSummary item={mockTransaction} isGrouped={false} />)
const { getByText } = render(<TxSummary item={mockTransaction} isConflictGroup={false} />)

expect(getByText('Confirm')).toBeInTheDocument()
})

it('should display a status label if transaction is in queue and pending', () => {
jest.spyOn(pending, 'default').mockReturnValue(true)
const { getByTestId } = render(<TxSummary item={mockTransaction} isGrouped={false} />)
const { getByTestId } = render(<TxSummary item={mockTransaction} isConflictGroup={false} />)

expect(getByTestId('tx-status-label')).toBeInTheDocument()
})

it('should display a status label if transaction is not in queue', () => {
jest.spyOn(pending, 'default').mockReturnValue(true)
const { getByTestId } = render(<TxSummary item={mockTransactionInHistory} isGrouped={false} />)
const { getByTestId } = render(<TxSummary item={mockTransactionInHistory} isConflictGroup={false} />)

expect(getByTestId('tx-status-label')).toBeInTheDocument()
})

it('should not display a status label if transaction is in queue and not pending', () => {
jest.spyOn(pending, 'default').mockReturnValue(false)
const { queryByTestId } = render(<TxSummary item={mockTransaction} isGrouped={false} />)
const { queryByTestId } = render(<TxSummary item={mockTransaction} isConflictGroup={false} />)

expect(queryByTestId('tx-status-label')).not.toBeInTheDocument()
})
Expand Down
8 changes: 4 additions & 4 deletions src/components/transactions/TxSummary/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import { FEATURES } from '@/utils/chains'
import TxStatusLabel from '@/components/transactions/TxStatusLabel'

type TxSummaryProps = {
isGrouped?: boolean
isConflictGroup?: boolean
isBulkGroup?: boolean
item: Transaction
}

const TxSummary = ({ item, isGrouped, isBulkGroup }: TxSummaryProps): ReactElement => {
const TxSummary = ({ item, isConflictGroup, isBulkGroup }: TxSummaryProps): ReactElement => {
const hasDefaultTokenlist = useHasFeature(FEATURES.DEFAULT_TOKENLIST)

const tx = item.transaction
Expand All @@ -41,13 +41,13 @@ const TxSummary = ({ item, isGrouped, isBulkGroup }: TxSummaryProps): ReactEleme
data-testid="transaction-item"
className={classNames(css.gridContainer, {
[css.history]: !isQueue,
[css.grouped]: isGrouped,
[css.grouped]: isConflictGroup,
[css.isBulkGroup]: isBulkGroup,
[css.untrusted]: !isTrusted,
})}
id={tx.id}
>
{nonce !== undefined && !isGrouped && !isBulkGroup && (
{nonce !== undefined && !isConflictGroup && !isBulkGroup && (
<Box gridArea="nonce" data-testid="nonce" className={css.nonce}>
{nonce}
</Box>
Expand Down
2 changes: 1 addition & 1 deletion src/components/transactions/TxSummary/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
grid-template-areas: 'nonce type info date status';
}

.gridContainer.grouped {
.gridContainer.conflictGroup {
grid-template-columns: var(--grid-type) var(--grid-info) var(--grid-date) var(--grid-confirmations) var(--grid-status) var(
--grid-actions
);
Expand Down
3 changes: 1 addition & 2 deletions src/utils/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,8 @@ export const getTxsWithDetails = (txs: Transaction[], chainId: string) => {
export const getTxDetailsWithBackoff = (txs: Transaction[], chainId: string) => {
return Promise.all(
txs.map(async (tx) => {
// return await getTransactionDetails(chainId, tx.transaction.id)
return await backOff(() => getTransactionDetails(chainId, tx.transaction.id), {
numOfAttempts: 19,
numOfAttempts: 10,
})
}),
)
Expand Down
9 changes: 7 additions & 2 deletions src/utils/tx-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ import type { RecoveryQueueItem } from '@/features/recovery/services/recovery-st

type GroupedTxs = Array<TransactionListItem | Transaction[]>

export const groupTxs = (list: TransactionListItem[], txHashes: Record<string, string> | undefined) => {
const groupedByConflicts = _groupConflictingTxs(list)
return _groupBulkTxs(groupedByConflicts, txHashes)
}

/**
* Group txs by conflict header
*/
export const groupConflictingTxs = (list: TransactionListItem[]): GroupedTxs => {
const _groupConflictingTxs = (list: TransactionListItem[]): GroupedTxs => {
return list
.reduce<GroupedTxs>((resultItems, item) => {
if (isConflictHeaderListItem(item)) {
Expand All @@ -36,7 +41,7 @@ export const groupConflictingTxs = (list: TransactionListItem[]): GroupedTxs =>
/**
* Group txs by tx hash
*/
export const groupBulkTxs = (list: GroupedTxs, txHashes: Record<string, string> | undefined): GroupedTxs => {
const _groupBulkTxs = (list: GroupedTxs, txHashes: Record<string, string> | undefined): GroupedTxs => {
if (!txHashes) return list

return list
Expand Down

0 comments on commit aa7f713

Please sign in to comment.