148 lines
6.6 KiB
TypeScript
148 lines
6.6 KiB
TypeScript
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('Orders page', () => {
|
|
// -------------------------------------------------------------------------
|
|
// 1. Renders order list with data
|
|
// -------------------------------------------------------------------------
|
|
it('renders order list with ticker and status', async () => {
|
|
renderRoute('/orders');
|
|
await waitFor(() => {
|
|
expect(screen.getByText('AAPL')).toBeInTheDocument();
|
|
});
|
|
expect(screen.getByText('filled')).toBeInTheDocument();
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 2. Displays order details — side, type, quantity
|
|
// -------------------------------------------------------------------------
|
|
it('displays side, type, and quantity', async () => {
|
|
renderRoute('/orders');
|
|
await waitFor(() => {
|
|
expect(screen.getByText('AAPL')).toBeInTheDocument();
|
|
});
|
|
const row = screen.getByText('AAPL').closest('tr')!;
|
|
expect(row).toHaveTextContent('buy');
|
|
expect(row).toHaveTextContent('market');
|
|
expect(row).toHaveTextContent('10');
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 3. Displays fill price in USD format
|
|
// -------------------------------------------------------------------------
|
|
it('displays fill price formatted as USD', async () => {
|
|
renderRoute('/orders');
|
|
await waitFor(() => {
|
|
expect(screen.getByText('AAPL')).toBeInTheDocument();
|
|
});
|
|
expect(screen.getByText('$185.50')).toBeInTheDocument();
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 4. Empty state
|
|
// -------------------------------------------------------------------------
|
|
it('shows empty state when no orders exist', async () => {
|
|
server.use(
|
|
http.get('/api/orders', () => HttpResponse.json([])),
|
|
);
|
|
renderRoute('/orders');
|
|
await waitFor(() => {
|
|
expect(screen.getByText('No data')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 5. Page title and ticker filter render
|
|
// -------------------------------------------------------------------------
|
|
it('renders page title and ticker filter', async () => {
|
|
renderRoute('/orders');
|
|
await waitFor(() => {
|
|
expect(screen.getByRole('heading', { name: 'Orders' })).toBeInTheDocument();
|
|
});
|
|
expect(screen.getByPlaceholderText('Ticker…')).toBeInTheDocument();
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 6. Row click navigates to order detail
|
|
// -------------------------------------------------------------------------
|
|
it('navigates to order detail on row click', async () => {
|
|
const user = userEvent.setup();
|
|
renderRoute('/orders');
|
|
await waitFor(() => {
|
|
expect(screen.getByText('AAPL')).toBeInTheDocument();
|
|
});
|
|
const row = screen.getByText('AAPL').closest('tr')!;
|
|
await user.click(row);
|
|
// Order detail page should render with the order ticker
|
|
await waitFor(() => {
|
|
// OrderDetail shows the ticker as h1 plus side and status badges
|
|
expect(screen.getByText('Type')).toBeInTheDocument();
|
|
expect(screen.getByText('Fill Price')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 7. Order detail shows all fields
|
|
// -------------------------------------------------------------------------
|
|
it('renders order detail with all fields', async () => {
|
|
renderRoute('/orders/o1');
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Type')).toBeInTheDocument();
|
|
});
|
|
expect(screen.getByText('Quantity')).toBeInTheDocument();
|
|
expect(screen.getByText('Fill Price')).toBeInTheDocument();
|
|
expect(screen.getByText('Fill Qty')).toBeInTheDocument();
|
|
expect(screen.getByText('$185.50')).toBeInTheDocument();
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 8. Order detail shows events section
|
|
// -------------------------------------------------------------------------
|
|
it('renders events section on order detail', async () => {
|
|
renderRoute('/orders/o1');
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Events (0)')).toBeInTheDocument();
|
|
});
|
|
expect(screen.getByText('No events')).toBeInTheDocument();
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 9. Multiple orders render correctly
|
|
// -------------------------------------------------------------------------
|
|
it('renders multiple orders', async () => {
|
|
server.use(
|
|
http.get('/api/orders', () => HttpResponse.json([
|
|
{ id: 'o1', recommendation_id: 'r1', broker_account_id: null, ticker: 'AAPL', side: 'buy', order_type: 'market', quantity: 10, limit_price: null, stop_price: null, status: 'filled', broker_order_id: null, submitted_at: '2026-04-10T19:01:00Z', fill_price: 185.50, fill_quantity: 10, created_at: '2026-04-10T19:01:00Z' },
|
|
{ id: 'o2', recommendation_id: 'r2', broker_account_id: null, ticker: 'MSFT', side: 'sell', order_type: 'limit', quantity: 5, limit_price: 420.00, stop_price: null, status: 'pending', broker_order_id: null, submitted_at: null, fill_price: null, fill_quantity: null, created_at: '2026-04-11T10:00:00Z' },
|
|
])),
|
|
);
|
|
renderRoute('/orders');
|
|
await waitFor(() => {
|
|
expect(screen.getByText('AAPL')).toBeInTheDocument();
|
|
});
|
|
expect(screen.getByText('MSFT')).toBeInTheDocument();
|
|
expect(screen.getByText('pending')).toBeInTheDocument();
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 10. Null fill price shows dash
|
|
// -------------------------------------------------------------------------
|
|
it('shows dash for null fill price', async () => {
|
|
server.use(
|
|
http.get('/api/orders', () => HttpResponse.json([
|
|
{ id: 'o2', recommendation_id: 'r2', broker_account_id: null, ticker: 'GOOG', side: 'buy', order_type: 'limit', quantity: 3, limit_price: 170.00, stop_price: null, status: 'pending', broker_order_id: null, submitted_at: null, fill_price: null, fill_quantity: null, created_at: '2026-04-11T10:00:00Z' },
|
|
])),
|
|
);
|
|
renderRoute('/orders');
|
|
await waitFor(() => {
|
|
expect(screen.getByText('GOOG')).toBeInTheDocument();
|
|
});
|
|
const row = screen.getByText('GOOG').closest('tr')!;
|
|
expect(row).toHaveTextContent('—');
|
|
});
|
|
});
|