/** * @vitest-environment happy-dom */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { cleanup, render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MemoryRouter } from 'react-router-dom'; import DashboardPage, { formatShareTime } from './DashboardPage'; import { mockAgent, mockShare } from '../test/fixtures'; import { routerFuture } from '../routerFuture'; import { useWebSocket } from '../hooks/useWebSocket'; import { api } from '../api/client'; vi.mock('../hooks/useWebSocket', () => ({ useWebSocket: vi.fn(), })); vi.mock('../components/Visual/3D/FleetTopologyMap', () => ({ default: () =>
, })); vi.mock('../components/Visual/MatrixStreamOverlay', () => ({ default: () => null, })); const useWebSocketMock = vi.mocked(useWebSocket); function wsValue(overrides: Partial> = {}) { return { isConnected: true, agents: [], recentShares: [], fleetAlerts: [], poolStatus: [], aiActivity: [], agentLogs: {}, commandResults: [], latestMessage: null, ...overrides, }; } function renderDashboard() { return render( ); } describe('formatShareTime', () => { it('formats ISO timestamps as locale time strings', () => { const iso = '2026-05-30T12:00:00.000Z'; expect(formatShareTime(iso)).toBe(new Date(iso).toLocaleTimeString()); }); }); describe('DashboardPage', () => { beforeEach(() => { vi.clearAllMocks(); localStorage.clear(); useWebSocketMock.mockReturnValue(wsValue()); vi.spyOn(api, 'getRecentShares').mockResolvedValue([]); vi.spyOn(api, 'listBuilds').mockResolvedValue([]); vi.spyOn(api, 'getConfig').mockResolvedValue({ port: 8080, data_dir: '', pool: {} as never, wallet: {} as never, server: { dashboard_subtitle: 'custom subtitle from server' }, alerts: {} as never, }); vi.spyOn(api, 'getAlerts').mockResolvedValue([]); vi.spyOn(api, 'getPoolStatus').mockResolvedValue([]); vi.spyOn(api, 'getAIActivity').mockResolvedValue([]); vi.spyOn(api, 'getXmrPrice').mockResolvedValue({ usd: 165.5, updated_at: '' }); vi.spyOn(api, 'getEarningsEstimate').mockResolvedValue({ xmr_per_day: 0.01, usd_per_day: 1.65, network_hashrate: 1e9, }); vi.spyOn(api, 'sendBulkCommand').mockResolvedValue({ success: true, sent: 1, failed: 0, action: 'restart', }); }); afterEach(() => { cleanup(); }); it('renders hero heading and key section titles', () => { renderDashboard(); expect(screen.getByRole('heading', { level: 1, name: 'Command Deck' })).toBeInTheDocument(); expect(screen.getByText('Fleet Pipeline')).toBeInTheDocument(); expect(screen.getByText('Share Activity Pulse')).toBeInTheDocument(); expect(screen.getByText('Machine Roster')).toBeInTheDocument(); expect(screen.getByText('PERSONAL NETWORK ยท LIVE TELEMETRY')).toBeInTheDocument(); }); it('shows reconnecting label when websocket is down', () => { useWebSocketMock.mockReturnValue(wsValue({ isConnected: false })); renderDashboard(); expect(screen.getByText('RECONNECTING')).toBeInTheDocument(); }); it('shows signal locked when websocket is connected', () => { renderDashboard(); expect(screen.getByText('SIGNAL LOCKED')).toBeInTheDocument(); expect(screen.getByText('0 nodes registered')).toBeInTheDocument(); }); it('loads dashboard subtitle from config API', async () => { renderDashboard(); expect(await screen.findByText('custom subtitle from server')).toBeInTheDocument(); expect(api.getConfig).toHaveBeenCalled(); }); it('shows empty roster state when no agents', async () => { renderDashboard(); expect(await screen.findByText('No miners on the wire')).toBeInTheDocument(); }); it('does not show projection or fake earnings with no agents', async () => { renderDashboard(); expect(await screen.findByText('Command Deck')).toBeInTheDocument(); expect(screen.queryByText(/Projection mode/i)).not.toBeInTheDocument(); expect(screen.queryByText('Target Fleet Earnings')).not.toBeInTheDocument(); expect(screen.queryByText('Vault-01')).not.toBeInTheDocument(); }); it('renders stat labels and top agent card', async () => { const agent = mockAgent({ name: 'Alpha Node', hashrate_15m: 1200 }); useWebSocketMock.mockReturnValue(wsValue({ agents: [agent] })); renderDashboard(); expect(await screen.findByText('Top Miner')).toBeInTheDocument(); expect(screen.getByText('Fleet Compute')).toBeInTheDocument(); expect(screen.getAllByText('Fleet Hash')[0]).toBeInTheDocument(); const roster = screen.getByText('Machine Roster').closest('section') as HTMLElement; expect(within(roster).getByText('Alpha Node')).toBeInTheDocument(); expect(within(roster).getByText('online')).toBeInTheDocument(); }); it('toggles advanced mode and reveals share log section', async () => { const user = userEvent.setup(); vi.spyOn(api, 'getRecentShares').mockResolvedValue([mockShare()]); renderDashboard(); expect(screen.queryByText('Share Log')).not.toBeInTheDocument(); await user.click(screen.getByRole('button', { name: '[ADVANCED]' })); expect(screen.getByText('Share Log')).toBeInTheDocument(); expect(localStorage.getItem('aether-dash-advanced')).toBe('1'); }); it('shows share log rows in advanced mode with stable keys', async () => { const share = mockShare({ id: undefined as unknown as number, hash: 'deadbeef' }); vi.spyOn(api, 'getRecentShares').mockResolvedValue([share]); localStorage.setItem('aether-dash-advanced', '1'); useWebSocketMock.mockReturnValue(wsValue({ agents: [mockAgent()] })); renderDashboard(); expect(await screen.findByText('Share Log')).toBeInTheDocument(); expect(screen.getByText('Accepted')).toBeInTheDocument(); }); it('alerts when bulk command API fails', async () => { const agent = mockAgent(); useWebSocketMock.mockReturnValue(wsValue({ agents: [agent] })); vi.spyOn(api, 'sendBulkCommand').mockRejectedValue(new Error('network down')); const alertSpy = vi.spyOn(window, 'alert').mockImplementation(() => {}); renderDashboard(); const roster = await screen.findByText('Machine Roster'); const section = roster.closest('section') as HTMLElement; await waitFor(() => expect(within(section).getByText(agent.name)).toBeInTheDocument()); const user = userEvent.setup(); const grid = section.querySelector('.agent-grid') as HTMLElement; await user.click(within(grid).getByRole('checkbox')); const bulkBar = section.querySelector('.fleet-bulk-bar') as HTMLElement; await user.click(within(bulkBar).getByRole('button', { name: 'Pause' })); await waitFor(() => { expect(alertSpy).toHaveBeenCalledWith('network down'); }); alertSpy.mockRestore(); }); it('prefers live websocket alerts over REST fallback', async () => { useWebSocketMock.mockReturnValue( wsValue({ fleetAlerts: [{ id: '1', level: 'warn', type: 'pool_down', message: 'live alert', timestamp: '' }], }) ); vi.spyOn(api, 'getAlerts').mockResolvedValue([ { id: '2', level: 'warn', type: 'stale', message: 'rest alert', timestamp: '' }, ]); renderDashboard(); expect(await screen.findByText('live alert')).toBeInTheDocument(); expect(screen.queryByText('rest alert')).not.toBeInTheDocument(); }); });