learning_ai_invt_trdg/web/src/tabs/HistoryTab.dom.test.tsx

168 lines
5.8 KiB
TypeScript

// @vitest-environment jsdom
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { HistoryTab } from './HistoryTab';
const {
authState,
fetchTradeProfilesMock,
fetchTradeHistoryMock,
fetchPositionsBootstrapMock
} = vi.hoisted(() => ({
authState: {
user: { id: 'u1' },
profile: { role: 'user' },
refreshProfile: vi.fn(),
session: { access_token: 'session-token' }
},
fetchTradeProfilesMock: vi.fn(),
fetchTradeHistoryMock: vi.fn(),
fetchPositionsBootstrapMock: vi.fn()
}));
vi.mock('../components/AuthContext', () => ({
useAuth: () => authState
}));
vi.mock('../lib/profileApi', () => ({
fetchTradeProfiles: fetchTradeProfilesMock
}));
vi.mock('../lib/tradeHistoryApi', () => ({
fetchTradeHistory: fetchTradeHistoryMock
}));
vi.mock('../lib/positionsApi', () => ({
fetchPositionsBootstrap: fetchPositionsBootstrapMock
}));
vi.mock('../hooks/useCanonicalLifecycle', () => ({
useCanonicalLifecycle: () => ({
snapshot: {
lifecycleRows: [{ tradeId: 'TRD-1' }],
realizedTrades: [],
openPositions: [],
diagnostics: { truncated: false }
},
loading: false,
error: null
})
}));
describe('HistoryTab Master Suite', () => {
const historyData = [
{ id: 'h1', symbol: 'BTC', entry_price: 50000, exit_price: 51000, pnl: 1000, pnl_percent: 2, created_at: '2024-01-01T10:00:00Z', side: 'BUY', source: 'BOT', profile_id: 'p1', size: 1, reason: 'Trend' }
];
beforeEach(() => {
vi.clearAllMocks();
authState.user = { id: 'u1' };
authState.profile = { role: 'user' };
authState.session = { access_token: 'session-token' };
fetchTradeProfilesMock.mockResolvedValue([{ id: 'p1', name: 'Alpha', allocated_capital: 1000 }]);
fetchTradeHistoryMock.mockResolvedValue(historyData);
fetchPositionsBootstrapMock.mockResolvedValue({
entries: [],
orders: [{
id: 'o1',
order_id: 'o1',
profile_id: 'p1',
symbol: 'BTC',
side: 'BUY',
qty: 1,
price: 50000,
status: 'filled',
timestamp: '2024-01-01T09:00:00Z',
trade_id: 'TRD-1',
action: 'ENTRY',
source: 'BOT'
}],
historyTradeKeys: [{ trade_id: 'TRD-1', profile_id: 'p1' }],
profiles: [{ id: 'p1', name: 'Alpha' }]
});
});
it('processes metrics and handles sort toggles', async () => {
const user = userEvent.setup();
render(<HistoryTab />);
await waitFor(() => expect(screen.getByText(/Total Trades/i)).toBeInTheDocument());
await waitFor(() => expect(screen.getByText('BTC')).toBeInTheDocument());
const sortBtn = await screen.findByRole('button', { name: /Newest/i });
await user.click(sortBtn);
await waitFor(() => expect(screen.queryByText(/Oldest/i)).toBeInTheDocument());
await user.click(screen.getByText(/Oldest/i));
await waitFor(() => expect(screen.queryByText(/Newest/i)).toBeInTheDocument());
});
it('covers date filtering and pagination', async () => {
const user = userEvent.setup();
const manyRecords = Array.from({ length: 25 }, (_, i) => ({
id: `h${i}`,
symbol: 'BTC',
entry_price: 50000,
exit_price: 51000,
pnl: 1000,
pnl_percent: 2,
created_at: `2024-01-01T12:${String(i % 60).padStart(2, '0')}:00`,
side: 'BUY',
source: 'BOT',
profile_id: 'p1',
size: 1,
reason: 'Trend',
trade_id: `TRD-${i}`
}));
fetchTradeHistoryMock.mockResolvedValue(manyRecords);
fetchPositionsBootstrapMock.mockResolvedValue({
entries: [],
orders: manyRecords.map((row) => ({
id: row.id,
order_id: row.id,
profile_id: 'p1',
symbol: row.symbol,
side: row.side,
qty: row.size,
price: row.entry_price,
status: 'filled',
timestamp: row.created_at,
trade_id: row.trade_id,
action: 'ENTRY',
source: 'BOT'
})),
historyTradeKeys: manyRecords.map((row) => ({ trade_id: row.trade_id, profile_id: 'p1' })),
profiles: [{ id: 'p1', name: 'Alpha' }]
});
const { container } = render(<HistoryTab />);
await screen.findByText(/Page 1 \/ 2/i, {}, { timeout: 5000 });
const dateInputs = container.querySelectorAll('input[type="date"]');
if (dateInputs.length >= 2) {
fireEvent.change(dateInputs[0], { target: { value: '2024-01-01' } });
fireEvent.change(dateInputs[1], { target: { value: '2024-01-02' } });
}
const nextBtn = await screen.findByRole('button', { name: /Next/i });
await user.click(nextBtn);
await screen.findByText(/Page 2 \/ 2/i);
const prevBtn = await screen.findByRole('button', { name: /Prev/i });
await user.click(prevBtn);
await screen.findByText(/Page 1 \/ 2/i);
await user.click(screen.getByText(/Clear/i));
});
it('covers empty fallback and source mapping', async () => {
fetchTradeHistoryMock.mockResolvedValue([
{ ...historyData[0], source: 'MANUAL', profile_id: null }
]);
render(<HistoryTab />);
await waitFor(() => expect(screen.queryByText(/MANUAL/i)).toBeInTheDocument());
});
});