Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
test: Add unit tests for other Tx History hooks as well
Signed-off-by: Emre Bogazliyanlioglu <emre@wormholelabs.xyz>
  • Loading branch information
Emre Bogazliyanlioglu committed Sep 29, 2025
commit 8ed9418eb9dcd57e0221cecce7c0c059ecff9e01
7 changes: 3 additions & 4 deletions src/hooks/useTransactionHistoryLiFi.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ describe('useTransactionHistoryLiFi', () => {
expect(tx?.inProgress).toBe(false);
});

it('should handle pending transactions correctly', async () => {
it('should skip pending transactions without receiving data', async () => {
const pendingTx = {
...mockLiFiTransaction,
status: 'PENDING' as const,
Expand All @@ -185,9 +185,8 @@ describe('useTransactionHistoryLiFi', () => {
expect(result.current.isFetching).toBe(false);
});

const tx = result.current.transactions?.[0];
expect(tx?.inProgress).toBe(true);
expect(tx?.receiveAmount).toBeUndefined();
// Pending transactions without receiving data should be skipped
expect(result.current.transactions).toHaveLength(0);
});

it('should skip transactions with unsupported chains', async () => {
Expand Down
8 changes: 4 additions & 4 deletions src/hooks/useTransactionHistoryLiFi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const useTransactionHistoryLiFi = (
? lifiChainIdToChain(receiving.chainId as LifiChainId)
: undefined;

if (!fromChain) {
if (!fromChain || !toChain) {
return undefined;
}

Expand All @@ -120,9 +120,9 @@ const useTransactionHistoryLiFi = (
toToken = config.tokens.findBySymbol(toChain, receiving.token.symbol);
}

// Skip if we can't identify the from token (to token can be missing for pending transactions)
if (!fromToken) {
// Skip transactions with unrecognized from token
// Skip if we can't identify the tokens
if (!fromToken || !toToken) {
// Skip transactions with unrecognized tokens
return undefined;
}

Expand Down
243 changes: 243 additions & 0 deletions src/hooks/useTransactionHistoryMayan.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';

const mockToken = {
key: 'USDC',
chain: 'Ethereum',
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
decimals: 6,
symbol: 'USDC',
name: 'USD Coin',
};

// Mock dependencies
vi.mock('config', () => ({
default: {
mayanApi: 'https://price-api.mayan.finance',
tokens: {
get: vi.fn(() => mockToken),
findBySymbol: vi.fn(() => mockToken),
},
},
}));

vi.mock('@wormhole-foundation/sdk', () => ({
chainIdToChain: vi.fn((chainId) => {
const chainMap: Record<number, string> = {
1: 'Ethereum',
2: 'Solana',
3: 'Bsc',
4: 'Polygon',
5: 'Avalanche',
6: 'Arbitrum',
14: 'Optimism',
};
return chainMap[chainId] || undefined;
}),
toNative: vi.fn((chain, address) => ({
chain,
address,
})),
}));

import useTransactionHistoryMayan from './useTransactionHistoryMayan';

// Sample test data
const mockMayanTransaction = {
trader: '0xuser1',
destAddress: '0xuser2',
sourceTxHash: '0xabc123',
sourceChain: 1,
swapChain: 'solana',
destChain: 4,
fromAmount: '1000000',
fromTokenChain: 1,
fromTokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
fromTokenPrice: 1.0,
fromTokenSymbol: 'USDC',
toTokenPrice: 1.0,
toTokenAddress: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
toTokenChain: 4,
toTokenSymbol: 'USDC',
status: 'COMPLETED',
clientStatus: 'COMPLETED',
initiatedAt: '2023-01-01T00:00:00Z',
toAmount: '995000',
statusUpdatedAt: '2023-01-01T00:10:00Z',
};

describe('useTransactionHistoryMayan', () => {
let fetchMock: ReturnType<typeof vi.fn>;

beforeEach(() => {
fetchMock = vi.fn();
global.fetch = fetchMock;
vi.clearAllMocks();
});

afterEach(() => {
vi.restoreAllMocks();
});

it('should successfully parse and return Mayan transactions', async () => {
fetchMock.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => ({ data: [mockMayanTransaction] }),
});

const { result } = renderHook(() =>
useTransactionHistoryMayan({
address: '0xuser1',
page: 0,
pageSize: 30,
}),
);

await waitFor(() => {
expect(result.current.isFetching).toBe(false);
});

expect(result.current.transactions).toHaveLength(1);
const tx = result.current.transactions?.[0];

expect(tx?.txHash).toBe('0xabc123');
expect(tx?.sender).toBe('0xuser1');
expect(tx?.recipient).toBe('0xuser2');
expect(tx?.fromChain).toBe('Ethereum');
expect(tx?.toChain).toBe('Polygon');
expect(tx?.inProgress).toBe(false);
});

it('should handle pending transactions correctly', async () => {
const pendingTx = {
...mockMayanTransaction,
status: 'SENT_TO_SOLANA',
clientStatus: 'PENDING',
toAmount: null,
};

fetchMock.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => ({ data: [pendingTx] }),
});

const { result } = renderHook(() =>
useTransactionHistoryMayan({
address: '0xuser1',
}),
);

await waitFor(() => {
expect(result.current.isFetching).toBe(false);
});

const tx = result.current.transactions?.[0];
expect(tx?.inProgress).toBe(true);
});

it('should skip transactions with unsupported chains', async () => {
const unsupportedChainTx = {
...mockMayanTransaction,
sourceChain: 99999, // Unsupported chain
};

fetchMock.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => ({ data: [unsupportedChainTx] }),
});

const { result } = renderHook(() =>
useTransactionHistoryMayan({
address: '0xuser1',
}),
);

await waitFor(() => {
expect(result.current.isFetching).toBe(false);
});

expect(result.current.transactions).toHaveLength(0);
});

it('should handle rate limiting with user-friendly message', async () => {
fetchMock.mockResolvedValueOnce({
ok: false,
status: 429,
});

const { result } = renderHook(() =>
useTransactionHistoryMayan({
address: '0xuser1',
}),
);

await waitFor(() => {
expect(result.current.isFetching).toBe(false);
});

expect(result.current.error).toBe('');
expect(result.current.transactions).toEqual([]);
});

it('should handle server errors gracefully', async () => {
fetchMock.mockResolvedValueOnce({
ok: false,
status: 500,
});

const { result } = renderHook(() =>
useTransactionHistoryMayan({
address: '0xuser1',
}),
);

await waitFor(() => {
expect(result.current.isFetching).toBe(false);
});

expect(result.current.error).toBe('');
});

it('should handle network errors', async () => {
fetchMock.mockRejectedValueOnce(new Error('Failed to fetch'));

const { result } = renderHook(() =>
useTransactionHistoryMayan({
address: '0xuser1',
}),
);

await waitFor(() => {
expect(result.current.isFetching).toBe(false);
});

expect(result.current.error).toContain(
'Error fetching transaction history from Mayan',
);
});

it('should handle empty response', async () => {
fetchMock.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => ({ data: [] }),
});

const { result } = renderHook(() =>
useTransactionHistoryMayan({
address: '0xuser1',
}),
);

await waitFor(() => {
expect(result.current.isFetching).toBe(false);
});

expect(result.current.transactions).toEqual([]);
expect(result.current.error).toBe('');
expect(result.current.hasMore).toBe(false);
});
});
Loading