From 34d353bf3f165af69ca10683bebf639f842f610c Mon Sep 17 00:00:00 2001 From: Celes Renata Date: Tue, 21 Apr 2026 07:10:30 +0000 Subject: [PATCH] feat: add 25 tests for Trading Controls and Global Events pages (100 total) --- frontend/src/test/global-events.test.tsx | 135 ++++++++++++++++ frontend/src/test/trading.test.tsx | 197 +++++++++++++++++++++++ 2 files changed, 332 insertions(+) create mode 100644 frontend/src/test/global-events.test.tsx create mode 100644 frontend/src/test/trading.test.tsx diff --git a/frontend/src/test/global-events.test.tsx b/frontend/src/test/global-events.test.tsx new file mode 100644 index 0000000..e38e4ac --- /dev/null +++ b/frontend/src/test/global-events.test.tsx @@ -0,0 +1,135 @@ +import { describe, it, expect } from 'vitest'; +import { screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { HttpResponse, http } from 'msw'; +import { renderRoute } from './render'; +import { server } from './mocks/server'; + +describe('Global Events page', () => { + // ------------------------------------------------------------------------- + // 1. Renders event list with summary + // ------------------------------------------------------------------------- + it('renders event list with summary', async () => { + renderRoute('/macro/events'); + await waitFor(() => { + expect(screen.getByText(/US tariffs on Chinese semiconductors/)).toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------------- + // 2. Shows severity badge + // ------------------------------------------------------------------------- + it('shows severity badge', async () => { + renderRoute('/macro/events'); + await waitFor(() => { + expect(screen.getByText(/US tariffs/)).toBeInTheDocument(); + }); + const row = screen.getByText(/US tariffs/).closest('tr')!; + expect(row).toHaveTextContent('high'); + }); + + // ------------------------------------------------------------------------- + // 3. Shows event type tags + // ------------------------------------------------------------------------- + it('shows event type tags', async () => { + renderRoute('/macro/events'); + await waitFor(() => { + // event_types: ['trade_barrier', 'cost_increase'] — underscores replaced with spaces + expect(screen.getByText('trade barrier')).toBeInTheDocument(); + }); + expect(screen.getByText('cost increase')).toBeInTheDocument(); + }); + + // ------------------------------------------------------------------------- + // 4. Shows affected regions + // ------------------------------------------------------------------------- + it('shows affected regions', async () => { + renderRoute('/macro/events'); + await waitFor(() => { + expect(screen.getByText('US')).toBeInTheDocument(); + }); + expect(screen.getByText('CN')).toBeInTheDocument(); + }); + + // ------------------------------------------------------------------------- + // 5. Shows affected sectors + // ------------------------------------------------------------------------- + it('shows affected sectors', async () => { + renderRoute('/macro/events'); + await waitFor(() => { + expect(screen.getByText('Technology')).toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------------- + // 6. Severity filter buttons render + // ------------------------------------------------------------------------- + it('renders severity filter buttons', async () => { + renderRoute('/macro/events'); + await waitFor(() => { + expect(screen.getByRole('group', { name: 'Severity filter' })).toBeInTheDocument(); + }); + const group = screen.getByRole('group', { name: 'Severity filter' }); + expect(group).toHaveTextContent('All'); + expect(group).toHaveTextContent('low'); + expect(group).toHaveTextContent('moderate'); + expect(group).toHaveTextContent('high'); + expect(group).toHaveTextContent('critical'); + }); + + // ------------------------------------------------------------------------- + // 7. Empty state + // ------------------------------------------------------------------------- + it('shows empty state when no events', async () => { + server.use( + http.get('/api/macro/events', () => HttpResponse.json([])), + ); + renderRoute('/macro/events'); + await waitFor(() => { + expect(screen.getByText('No data')).toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------------- + // 8. Page title renders + // ------------------------------------------------------------------------- + it('renders page title', async () => { + renderRoute('/macro/events'); + await waitFor(() => { + expect(screen.getByRole('heading', { name: 'Global Events' })).toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------------- + // 9. Severity filter is clickable + // ------------------------------------------------------------------------- + it('severity filter buttons are clickable', async () => { + const user = userEvent.setup(); + renderRoute('/macro/events'); + await waitFor(() => { + expect(screen.getByRole('group', { name: 'Severity filter' })).toBeInTheDocument(); + }); + // Click "critical" filter — should not crash + await user.click(screen.getByRole('button', { name: 'critical' })); + // Page should still be rendered + expect(screen.getByRole('heading', { name: 'Global Events' })).toBeInTheDocument(); + }); + + // ------------------------------------------------------------------------- + // 10. Multiple events render + // ------------------------------------------------------------------------- + it('renders multiple events', async () => { + server.use( + http.get('/api/macro/events', () => HttpResponse.json([ + { id: 'me1', event_types: ['trade_barrier'], severity: 'high', affected_regions: ['US'], affected_sectors: ['Technology'], affected_commodities: [], summary: 'Tariff event', key_facts: [], estimated_duration: 'short_term', confidence: 0.85, source_document_id: 'd1', created_at: '2026-05-15T14:00:00Z' }, + { id: 'me2', event_types: ['monetary_policy'], severity: 'moderate', affected_regions: ['EU'], affected_sectors: ['Financial Services'], affected_commodities: [], summary: 'ECB rate decision', key_facts: [], estimated_duration: 'medium_term', confidence: 0.70, source_document_id: 'd2', created_at: '2026-05-16T10:00:00Z' }, + ])), + ); + renderRoute('/macro/events'); + await waitFor(() => { + expect(screen.getByText('Tariff event')).toBeInTheDocument(); + }); + expect(screen.getByText('ECB rate decision')).toBeInTheDocument(); + expect(screen.getByText('EU')).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/test/trading.test.tsx b/frontend/src/test/trading.test.tsx new file mode 100644 index 0000000..b26e4a9 --- /dev/null +++ b/frontend/src/test/trading.test.tsx @@ -0,0 +1,197 @@ +import { describe, it, expect } from 'vitest'; +import { screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { renderRoute } from './render'; + +describe('Trading Controls page', () => { + // ------------------------------------------------------------------------- + // 1. Page title renders + // ------------------------------------------------------------------------- + it('renders page title', async () => { + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByRole('heading', { name: 'Trading Controls' })).toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------------- + // 2. Trading mode buttons render with paper active + // ------------------------------------------------------------------------- + it('renders trading mode buttons with paper active', async () => { + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByRole('button', { name: 'paper', pressed: true })).toBeInTheDocument(); + }); + expect(screen.getByRole('button', { name: 'live', pressed: false })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'disabled', pressed: false })).toBeInTheDocument(); + }); + + // ------------------------------------------------------------------------- + // 3. Live mode shows confirmation dialog + // ------------------------------------------------------------------------- + it('shows confirmation dialog when clicking live mode', async () => { + const user = userEvent.setup(); + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByRole('button', { name: 'live' })).toBeInTheDocument(); + }); + await user.click(screen.getByRole('button', { name: 'live' })); + await waitFor(() => { + expect(screen.getByText(/Are you sure you want to switch to/)).toBeInTheDocument(); + }); + expect(screen.getByText('Confirm')).toBeInTheDocument(); + expect(screen.getByText('Cancel')).toBeInTheDocument(); + }); + + // ------------------------------------------------------------------------- + // 4. Cancel dismisses live mode confirmation + // ------------------------------------------------------------------------- + it('dismisses live mode confirmation on cancel', async () => { + const user = userEvent.setup(); + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByRole('button', { name: 'live' })).toBeInTheDocument(); + }); + await user.click(screen.getByRole('button', { name: 'live' })); + await waitFor(() => { + expect(screen.getByText('Cancel')).toBeInTheDocument(); + }); + await user.click(screen.getByText('Cancel')); + await waitFor(() => { + expect(screen.queryByText(/Are you sure you want to switch to/)).not.toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------------- + // 5. Reset card renders with button + // ------------------------------------------------------------------------- + it('renders paper trading reset card', async () => { + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByText('Paper Trading Account')).toBeInTheDocument(); + }); + expect(screen.getByText('Reset Everything')).toBeInTheDocument(); + }); + + // ------------------------------------------------------------------------- + // 6. Reset shows confirmation dialog + // ------------------------------------------------------------------------- + it('shows reset confirmation dialog', async () => { + const user = userEvent.setup(); + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByText('Reset Everything')).toBeInTheDocument(); + }); + await user.click(screen.getByText('Reset Everything')); + await waitFor(() => { + expect(screen.getByText(/permanently delete/)).toBeInTheDocument(); + }); + expect(screen.getByText('Yes, Reset Everything')).toBeInTheDocument(); + }); + + // ------------------------------------------------------------------------- + // 7. Macro signal layer toggle renders + // ------------------------------------------------------------------------- + it('renders macro signal layer toggle', async () => { + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByText('Macro Signal Layer')).toBeInTheDocument(); + }); + expect(screen.getByLabelText('Toggle macro signal layer')).toBeInTheDocument(); + }); + + // ------------------------------------------------------------------------- + // 8. Competitive signal layer toggle renders + // ------------------------------------------------------------------------- + it('renders competitive signal layer toggle', async () => { + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByText('Competitive Signal Layer')).toBeInTheDocument(); + }); + expect(screen.getByLabelText('Toggle competitive signal layer')).toBeInTheDocument(); + }); + + // ------------------------------------------------------------------------- + // 9. Macro toggle shows confirmation + // ------------------------------------------------------------------------- + it('shows confirmation when toggling macro layer', async () => { + const user = userEvent.setup(); + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByLabelText('Toggle macro signal layer')).toBeInTheDocument(); + }); + await user.click(screen.getByLabelText('Toggle macro signal layer')); + await waitFor(() => { + expect(screen.getByText(/Are you sure you want to/)).toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------------- + // 10. Pending approvals section renders + // ------------------------------------------------------------------------- + it('renders pending approvals section with zero count', async () => { + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByText('Pending Approvals (0)')).toBeInTheDocument(); + }); + expect(screen.getByText('No pending approvals')).toBeInTheDocument(); + }); + + // ------------------------------------------------------------------------- + // 11. Active lockouts section renders + // ------------------------------------------------------------------------- + it('renders active lockouts section with zero count', async () => { + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByText('Active Lockouts (0)')).toBeInTheDocument(); + }); + expect(screen.getByText('No active lockouts')).toBeInTheDocument(); + }); + + // ------------------------------------------------------------------------- + // 12. Lockout form renders with fields + // ------------------------------------------------------------------------- + it('renders lockout creation form', async () => { + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByLabelText('Ticker')).toBeInTheDocument(); + }); + expect(screen.getByLabelText('Reason')).toBeInTheDocument(); + expect(screen.getByLabelText('Minutes')).toBeInTheDocument(); + expect(screen.getByText('Add Lockout')).toBeInTheDocument(); + }); + + // ------------------------------------------------------------------------- + // 13. Risk tier buttons render + // ------------------------------------------------------------------------- + it('renders risk tier buttons', async () => { + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByText('Risk Tier')).toBeInTheDocument(); + }); + expect(screen.getByRole('button', { name: 'conservative' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'moderate' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'aggressive' })).toBeInTheDocument(); + }); + + // ------------------------------------------------------------------------- + // 14. Override Trade button links to engine + // ------------------------------------------------------------------------- + it('renders Override Trade button', async () => { + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByTestId('override-trade-button')).toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------------- + // 15. Paper order approval toggle renders + // ------------------------------------------------------------------------- + it('renders paper order approval toggle', async () => { + renderRoute('/trading'); + await waitFor(() => { + expect(screen.getByText('Paper Order Approval')).toBeInTheDocument(); + }); + expect(screen.getByLabelText('Toggle paper order approval requirement')).toBeInTheDocument(); + }); +});