Web:
- runtime.ts: use import.meta.env (process.env is undefined in Vite browser bundle)
- tradingApiUrl local fallback: drop /api suffix (API libs already append /api/*)
- useWebSocket: deriveSocketParams() — correctly splits origin + socket path for
Caddy handle_path /invttrdg/* proxy (io(origin, {path}), not io(url-with-path))
- App.tsx: pass socket prop to AdminTab; pass connected prop to SignalsTab
- AdminTab: remove duplicate useWebSocket; accept socket as prop
- SignalsTab: connection-aware empty state message
- backtest/flags: default to disabled when VITE_BACKTEST_ENABLED unset
- EntryForm: NaN guard before live trade execution
- MarketplaceTab: null-safety on symbols.rules access
- Tests: pass socket prop to AdminTab; update empty state assertion
Mobile:
- TradingDataProvider: same deriveSocketParams fix — EXPO_PUBLIC_SOCKET_PATH
overrides auto-derived path from tradingApiUrl
- strategies: replace mock data with real GET /api/profiles + PATCH active toggle
- chat: wire to real POST /api/chat; remove hardcoded mock reply
- marketplace: fetch GET /api/marketplace-presets; USE STRATEGY calls POST /api/profiles
- settings: sign-out confirmation dialog; execution mode read-only hint;
version from expo-constants instead of hardcoded v2.3
- positions/history: empty state UI when no data
- CustomTabBar: always show tab labels (not only when focused)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
153 lines
4.9 KiB
TypeScript
153 lines
4.9 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 { SignalsTab } from './SignalsTab';
|
|
import { SettingsTab } from './SettingsTab';
|
|
import { EntriesTab } from './EntriesTab';
|
|
import { AdminTab } from './AdminTab';
|
|
import { ConfigTab } from './ConfigTab';
|
|
import { HistoryTab } from './HistoryTab';
|
|
import { DEFAULT_BOT_STATE } from '../hooks/useWebSocket';
|
|
|
|
vi.mock('../components/AuthContext', () => ({
|
|
useAuth: () => ({
|
|
user: null,
|
|
profile: { role: 'admin', first_name: 'Admin', last_name: 'User' },
|
|
refreshProfile: vi.fn()
|
|
})
|
|
}));
|
|
|
|
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 })),
|
|
insert: vi.fn(() => Promise.resolve({ error: null })),
|
|
update: vi.fn(() => query),
|
|
upsert: vi.fn(() => Promise.resolve({ error: null })),
|
|
delete: vi.fn(() => query)
|
|
};
|
|
|
|
return {
|
|
supabase: {
|
|
from: vi.fn(() => query),
|
|
auth: {
|
|
getSession: vi.fn(async () => ({ data: { session: null } }))
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
vi.mock('../components/EntryForm', () => ({
|
|
EntryForm: () => React.createElement('div', null, 'EntryFormMock')
|
|
}));
|
|
|
|
vi.mock('../components/SymbolCard', () => ({
|
|
SymbolCard: ({ symbol }: { symbol: string }) => React.createElement('div', null, `SymbolCard-${symbol}`)
|
|
}));
|
|
|
|
const baseBotState: BotState = {
|
|
symbols: {
|
|
'BTC/USDT': {
|
|
price: 70100,
|
|
change24h: 2.1,
|
|
changeToday: 1.2,
|
|
session: 'NY',
|
|
volatility: 'High',
|
|
signal: 'BUY',
|
|
activePosition: null,
|
|
priceHistory: [],
|
|
rules: {
|
|
TrendBiasRule: { passed: true, reason: 'aligned' }
|
|
},
|
|
indicators: {
|
|
ema50_4h: 68000,
|
|
ema200_4h: 66000,
|
|
rsi_1h: 55
|
|
}
|
|
}
|
|
},
|
|
alerts: [],
|
|
positions: [],
|
|
orders: [],
|
|
history: [],
|
|
settings: {
|
|
executionMode: 'Paper',
|
|
riskPerTrade: 0.01,
|
|
totalCapital: 15000,
|
|
maxOpenTrades: 6,
|
|
isAlgoEnabled: true,
|
|
enabledRules: ['TrendBiasRule', 'RiskManagementRule']
|
|
},
|
|
uptime: 600000
|
|
};
|
|
|
|
describe('dashboard tabs smoke coverage', () => {
|
|
it('renders SignalsTab with symbol cards and empty state branch', () => {
|
|
const withSymbols = renderToStaticMarkup(
|
|
React.createElement(SignalsTab, { botState: baseBotState })
|
|
);
|
|
expect(withSymbols).toContain('Market Signals');
|
|
expect(withSymbols).toContain('SymbolCard-BTC/USDT');
|
|
|
|
const emptyState = { ...baseBotState, symbols: {} };
|
|
const emptyHtml = renderToStaticMarkup(
|
|
React.createElement(SignalsTab, { botState: emptyState })
|
|
);
|
|
expect(emptyHtml).toContain('No symbols available');
|
|
});
|
|
|
|
it('renders SettingsTab user config and bot status blocks', () => {
|
|
const html = renderToStaticMarkup(
|
|
React.createElement(SettingsTab, { botState: baseBotState })
|
|
);
|
|
|
|
expect(html).toContain('Settings & Configuration');
|
|
expect(html).toContain('User Configuration');
|
|
expect(html).toContain('Bot Internal Status (WebSocket)');
|
|
expect(html).toContain('Algorithm: ENABLED');
|
|
});
|
|
|
|
it('renders EntriesTab empty-state and primary controls', () => {
|
|
const html = renderToStaticMarkup(React.createElement(EntriesTab));
|
|
|
|
expect(html).toContain('Watchlist & Entries');
|
|
expect(html).toContain('Manual Entry Injection');
|
|
expect(html).toContain('Cluster Context Null');
|
|
});
|
|
|
|
it('renders AdminTab strategy pipeline and ConfigTab loading shell', () => {
|
|
const adminState = {
|
|
...DEFAULT_BOT_STATE,
|
|
settings: {
|
|
...DEFAULT_BOT_STATE.settings,
|
|
enabledRules: ['TrendBiasRule', 'MomentumRule', 'AIAnalysisRule']
|
|
}
|
|
};
|
|
|
|
const adminHtml = renderToStaticMarkup(
|
|
React.createElement(AdminTab, {
|
|
botState: adminState,
|
|
socket: null
|
|
})
|
|
);
|
|
|
|
expect(adminHtml).toContain('Admin Panel');
|
|
expect(adminHtml).toContain('Strategy Pipeline');
|
|
expect(adminHtml).toContain('TrendBias');
|
|
expect(adminHtml).toContain('AIAnalysis');
|
|
|
|
const configHtml = renderToStaticMarkup(React.createElement(ConfigTab));
|
|
expect(configHtml).toContain('Calibrating Infrastructure');
|
|
});
|
|
|
|
it('renders HistoryTab loading state shell', () => {
|
|
const html = renderToStaticMarkup(
|
|
React.createElement(HistoryTab, { botState: { history: [] } })
|
|
);
|
|
expect(html).toContain('animate-spin');
|
|
});
|
|
});
|