Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions src/components/dashboard/QuickStats.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen } from '@testing-library/react'
import { QuickStats } from './QuickStats'
import { useRecentTransactions } from '@/hooks/useTransactions'
import { Transaction } from '@/types'

vi.mock('@/hooks/useTransactions', () => ({
useRecentTransactions: vi.fn(),
}))

function makeTx(overrides: Partial<Transaction> = {}): Transaction {
return {
id: 'tx-1',
hash: 'hash-1',
createdAt: new Date().toISOString(),
type: 'payment',
status: 'success',
sourceAccount: 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
destinationAccount: 'GAHK7WOJWVU67MHUCHC5QUODIC2GQUQ32ZPXRKZ43D4ML5I5U2PLM6TV',
amount: '10',
assetCode: 'XLM',
assetIssuer: null,
fee: '100',
ledger: 1000,
direction: 'sent',
counterparty: 'GAHK7WOJWVU67MHUCHC5QUODIC2GQUQ32ZPXRKZ43D4ML5I5U2PLM6TV',
memo: '',
...overrides,
}
}

describe('QuickStats', () => {
beforeEach(() => {
vi.clearAllMocks()
})

it('computes average fee using only sent transactions, ignoring received transaction fees', () => {
// 1 sent transaction with fee = 100 stroops (0.0000100 XLM)
// 1 received transaction with massive fee = 10_000_000 stroops (1.0 XLM)
const transactions = [
makeTx({ id: 'tx-1', direction: 'sent', fee: '100', amount: '50' }),
makeTx({ id: 'tx-2', direction: 'received', fee: '10000000', amount: '100' }),
]

vi.mocked(useRecentTransactions).mockReturnValue({
data: { transactions, total: 2 },
isLoading: false,
} as any)

render(<QuickStats />)

// Expected avg fee = 100 / 1 / 10_000_000 = 0.0000100
expect(screen.getByText('0.0000100')).toBeInTheDocument()
expect(screen.getByText('Average Fee')).toBeInTheDocument()
})

it('handles zero sent transactions gracefully with avgFee = "0"', () => {
const transactions = [
makeTx({ id: 'tx-1', direction: 'received', fee: '5000', amount: '20' }),
]

vi.mocked(useRecentTransactions).mockReturnValue({
data: { transactions, total: 1 },
isLoading: false,
} as any)

render(<QuickStats />)

expect(screen.getByText('0')).toBeInTheDocument()
})
})
6 changes: 3 additions & 3 deletions src/components/dashboard/QuickStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ export function QuickStats() {
.reduce((sum, t) => sum + parseFloat(t.amount || '0'), 0)
.toFixed(4)

const avgFee = transactions.length
const avgFee = sent.length
? (
transactions.reduce((s, t) => s + parseFloat(t.fee || '0'), 0) /
transactions.length /
sent.reduce((s, t) => s + parseFloat(t.fee || '0'), 0) /
sent.length /
10_000_000
).toFixed(7)
: '0'
Expand Down
14 changes: 9 additions & 5 deletions src/pages/History.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@ function HistoryChart() {
day: 'numeric',
})
if (days[key]) {
if (tx.direction === 'sent') days[key].sent += parseFloat(tx.amount || '0')
else days[key].received += parseFloat(tx.amount || '0')
days[key].fees += parseFloat(tx.fee || '0') / 10_000_000
if (tx.direction === 'sent') {
days[key].sent += parseFloat(tx.amount || '0')
days[key].fees += parseFloat(tx.fee || '0') / 10_000_000
} else {
days[key].received += parseFloat(tx.amount || '0')
}
}
})

Expand Down Expand Up @@ -102,9 +105,10 @@ function HistorySummary() {
const { data } = useRecentTransactions(50)
const txs = data?.transactions ?? []

const totalSent = txs.filter((t) => t.direction === 'sent').reduce((s, t) => s + parseFloat(t.amount || '0'), 0)
const sentTxs = txs.filter((t) => t.direction === 'sent')
const totalSent = sentTxs.reduce((s, t) => s + parseFloat(t.amount || '0'), 0)
const totalReceived = txs.filter((t) => t.direction === 'received').reduce((s, t) => s + parseFloat(t.amount || '0'), 0)
const totalFees = txs.reduce((s, t) => s + parseFloat(t.fee || '0') / 10_000_000, 0)
const totalFees = sentTxs.reduce((s, t) => s + parseFloat(t.fee || '0') / 10_000_000, 0)
const successRate = txs.length
? Math.round((txs.filter((t) => t.status === 'success').length / txs.length) * 100)
: 100
Expand Down