learning_ai_invt_trdg/web/src/tabs/OverviewTab.test.ts

163 lines
4.8 KiB
TypeScript

import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { describe, expect, it, vi } from 'vitest';
import type { BotState } from '../hooks/useWebSocket';
import { dedupeLivePositions, OverviewTab } from './OverviewTab';
vi.mock('../components/AuthContext', () => ({
useAuth: () => ({
user: null,
profile: { role: 'admin' }
})
}));
vi.mock('../lib/supabaseClient', () => {
const query: any = {
select: vi.fn(() => query),
order: vi.fn(() => query),
eq: vi.fn(() => query),
limit: vi.fn(() => Promise.resolve({ data: [], error: null }))
};
return {
supabase: {
from: vi.fn(() => query)
}
};
});
const createBotState = (): BotState => ({
symbols: {
'BTC/USDT': {
price: 70123.12,
change24h: 1.25,
changeToday: 0.44,
session: 'NY',
volatility: 'High',
signal: 'BUY',
signalTime: Date.now() - 120000,
tradingMode: 'Paper',
activePosition: null,
priceHistory: [
{ timestamp: Date.now() - 600000, price: 69990 },
{ timestamp: Date.now(), price: 70123.12 }
],
rules: {
TrendBiasRule: { passed: true, reason: 'Trend aligned' }
},
profileSignals: {
profileA: {
profileName: 'High Risk Scalper',
signal: 'BUY',
passed: true,
reason: 'Rule stack passed'
}
},
indicators: {
ema50_4h: 68000,
ema200_4h: 66000,
rsi_1h: 58
}
}
},
alerts: [],
positions: [
{
id: 'pos-1',
symbol: 'BTC/USDT',
side: 'BUY',
size: 0.15,
entryPrice: 70000,
currentPrice: 70123.12,
stopLoss: 69000,
takeProfit: 71500,
unrealizedPnl: 18.47,
unrealizedPnlPercent: 0.17,
marketValue: 10518.47,
profileId: 'profileA',
profileName: 'High Risk Scalper',
tradeId: 'TRD-OVR-1'
}
],
orders: [],
history: [
{
symbol: 'BTC/USDT',
side: 'BUY',
entryPrice: 69000,
exitPrice: 70000,
size: 0.1,
pnl: 100,
pnlPercent: 1.45,
reason: 'Target hit',
timestamp: Date.now() - 3600000,
profileId: 'profileA',
trade_id: 'TRD-HIST-1',
source: 'BOT'
}
],
settings: {
executionMode: 'Paper',
riskPerTrade: 0.01,
totalCapital: 15000,
maxOpenTrades: 6,
isAlgoEnabled: true,
enabledRules: ['TrendBiasRule', 'MomentumRule']
},
uptime: 3600000
});
describe('OverviewTab', () => {
it('dedupes runtime positions by trade id and preserves profile metadata', () => {
const baseState = createBotState();
baseState.positions = [
{
...baseState.positions[0],
id: 'pos-no-profile',
profileId: undefined,
profileName: undefined
},
{
...baseState.positions[0],
id: 'pos-profile',
profileId: 'profileA',
profileName: 'High Risk Scalper'
}
];
const deduped = dedupeLivePositions(baseState.positions);
expect(deduped).toHaveLength(1);
expect(deduped[0].profileId).toBe('profileA');
expect(deduped[0].tradeId).toBe('TRD-OVR-1');
});
it('renders market readiness with capital, pnl, and signal cards', () => {
const html = renderToStaticMarkup(
React.createElement(OverviewTab, { botState: createBotState() })
);
expect(html).toContain('Market Readiness');
expect(html).toContain('Capital Used');
expect(html).toContain('Net P&L');
expect(html).toContain('P&L Duration');
expect(html).toContain('BTC/USDT');
expect(html).toContain('SIGNAL ACTIVE');
expect(html).toContain('Win Rate Window');
});
it('renders stable fallback values when symbols and history are empty', () => {
const emptyState = createBotState();
emptyState.symbols = {};
emptyState.positions = [];
emptyState.history = [];
const html = renderToStaticMarkup(
React.createElement(OverviewTab, { botState: emptyState })
);
expect(html).toContain('Account (Fallback)');
expect(html).toContain('No signal');
expect(html).toContain('Awaiting trade history');
});
});