163 lines
4.8 KiB
TypeScript
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');
|
|
});
|
|
});
|