From 7eff484434145ec86bf8cf819ce16e1afbd5f3fa Mon Sep 17 00:00:00 2001 From: Celes Renata Date: Tue, 21 Apr 2026 07:16:35 +0000 Subject: [PATCH] feat: add Documents and Companies interaction tests (121 total) --- frontend/src/test/companies.test.tsx | 125 +++++++++++++++++++++++++++ frontend/src/test/documents.test.tsx | 94 ++++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 frontend/src/test/companies.test.tsx create mode 100644 frontend/src/test/documents.test.tsx diff --git a/frontend/src/test/companies.test.tsx b/frontend/src/test/companies.test.tsx new file mode 100644 index 0000000..6f8ad37 --- /dev/null +++ b/frontend/src/test/companies.test.tsx @@ -0,0 +1,125 @@ +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('Companies page', () => { + it('renders page title', async () => { + renderRoute('/companies'); + await waitFor(() => { + expect(screen.getByRole('heading', { name: 'Companies' })).toBeInTheDocument(); + }); + }); + + it('renders company list with tickers', async () => { + renderRoute('/companies'); + await waitFor(() => { + expect(screen.getByText('AAPL')).toBeInTheDocument(); + }); + expect(screen.getByText('MSFT')).toBeInTheDocument(); + }); + + it('shows company names', async () => { + renderRoute('/companies'); + await waitFor(() => { + expect(screen.getByText('Apple Inc.')).toBeInTheDocument(); + }); + expect(screen.getByText('Microsoft Corporation')).toBeInTheDocument(); + }); + + it('shows sector column', async () => { + renderRoute('/companies'); + await waitFor(() => { + expect(screen.getByText('AAPL')).toBeInTheDocument(); + }); + const row = screen.getByText('AAPL').closest('tr')!; + expect(row).toHaveTextContent('Technology'); + }); + + it('shows active status badge', async () => { + renderRoute('/companies'); + await waitFor(() => { + expect(screen.getByText('AAPL')).toBeInTheDocument(); + }); + const row = screen.getByText('AAPL').closest('tr')!; + expect(row).toHaveTextContent('active'); + }); + + it('shows source count', async () => { + renderRoute('/companies'); + await waitFor(() => { + expect(screen.getByText('AAPL')).toBeInTheDocument(); + }); + const row = screen.getByText('AAPL').closest('tr')!; + expect(row).toHaveTextContent('3'); + }); + + it('renders Add Company button', async () => { + renderRoute('/companies'); + await waitFor(() => { + expect(screen.getByText('Add Company')).toBeInTheDocument(); + }); + }); + + it('shows add company form on button click', async () => { + const user = userEvent.setup(); + renderRoute('/companies'); + await waitFor(() => { + expect(screen.getByText('Add Company')).toBeInTheDocument(); + }); + await user.click(screen.getByText('Add Company')); + await waitFor(() => { + expect(screen.getByLabelText('Ticker')).toBeInTheDocument(); + }); + expect(screen.getByLabelText('Legal Name')).toBeInTheDocument(); + expect(screen.getByLabelText('Sector')).toBeInTheDocument(); + expect(screen.getByLabelText('Exchange')).toBeInTheDocument(); + }); + + it('submits new company form', async () => { + const user = userEvent.setup(); + renderRoute('/companies'); + await waitFor(() => { + expect(screen.getByText('Add Company')).toBeInTheDocument(); + }); + await user.click(screen.getByText('Add Company')); + await waitFor(() => { + expect(screen.getByLabelText('Ticker')).toBeInTheDocument(); + }); + await user.type(screen.getByLabelText('Ticker'), 'GOOG'); + await user.type(screen.getByLabelText('Legal Name'), 'Alphabet Inc.'); + await user.click(screen.getByText('Create')); + // Form should close on success + await waitFor(() => { + expect(screen.queryByLabelText('Ticker')).not.toBeInTheDocument(); + }); + }); + + it('cancel closes the add form', async () => { + const user = userEvent.setup(); + renderRoute('/companies'); + await waitFor(() => { + expect(screen.getByText('Add Company')).toBeInTheDocument(); + }); + await user.click(screen.getByText('Add Company')); + await waitFor(() => { + expect(screen.getByText('Cancel')).toBeInTheDocument(); + }); + await user.click(screen.getByText('Cancel')); + await waitFor(() => { + expect(screen.queryByLabelText('Ticker')).not.toBeInTheDocument(); + }); + }); + + it('shows empty state when no companies', async () => { + server.use( + http.get('/api/companies', () => HttpResponse.json([])), + ); + renderRoute('/companies'); + await waitFor(() => { + expect(screen.getByText('No data')).toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/src/test/documents.test.tsx b/frontend/src/test/documents.test.tsx new file mode 100644 index 0000000..d8d08fe --- /dev/null +++ b/frontend/src/test/documents.test.tsx @@ -0,0 +1,94 @@ +import { describe, it, expect } from 'vitest'; +import { screen, waitFor } from '@testing-library/react'; +import { HttpResponse, http } from 'msw'; +import { renderRoute } from './render'; +import { server } from './mocks/server'; + +describe('Documents page', () => { + it('renders page title', async () => { + renderRoute('/documents'); + await waitFor(() => { + expect(screen.getByRole('heading', { name: 'Documents' })).toBeInTheDocument(); + }); + }); + + it('renders document list with title', async () => { + renderRoute('/documents'); + await waitFor(() => { + expect(screen.getByText('Apple Q4 Earnings Beat')).toBeInTheDocument(); + }); + }); + + it('shows document type and source type', async () => { + renderRoute('/documents'); + await waitFor(() => { + expect(screen.getByText('Apple Q4 Earnings Beat')).toBeInTheDocument(); + }); + const row = screen.getByText('Apple Q4 Earnings Beat').closest('tr')!; + expect(row).toHaveTextContent('news'); + expect(row).toHaveTextContent('news_api'); + }); + + it('shows parse confidence badge', async () => { + renderRoute('/documents'); + await waitFor(() => { + expect(screen.getByText('high')).toBeInTheDocument(); + }); + }); + + it('shows status badge', async () => { + renderRoute('/documents'); + await waitFor(() => { + expect(screen.getByText('Apple Q4 Earnings Beat')).toBeInTheDocument(); + }); + const row = screen.getByText('Apple Q4 Earnings Beat').closest('tr')!; + expect(row).toHaveTextContent('extracted'); + }); + + it('renders status filter dropdown', async () => { + renderRoute('/documents'); + await waitFor(() => { + expect(screen.getByLabelText('Filter by status')).toBeInTheDocument(); + }); + expect(screen.getByText('All statuses')).toBeInTheDocument(); + }); + + it('renders ticker filter', async () => { + renderRoute('/documents'); + await waitFor(() => { + expect(screen.getByPlaceholderText('Ticker…')).toBeInTheDocument(); + }); + }); + + it('shows empty state when no documents', async () => { + server.use( + http.get('/api/documents', () => HttpResponse.json([])), + ); + renderRoute('/documents'); + await waitFor(() => { + expect(screen.getByText('No data')).toBeInTheDocument(); + }); + }); + + it('renders multiple documents', async () => { + server.use( + http.get('/api/documents', () => HttpResponse.json([ + { id: 'd1', document_type: 'news', source_type: 'news_api', publisher: 'Reuters', url: null, title: 'Apple Q4 Earnings Beat', published_at: '2026-04-10T12:00:00Z', retrieved_at: '2026-04-10T12:05:00Z', language: 'en', content_hash: 'abc123', parse_quality_score: 0.92, parse_confidence: 'high', status: 'extracted', created_at: '2026-04-10T12:05:00Z' }, + { id: 'd2', document_type: 'filing', source_type: 'sec_api', publisher: 'SEC', url: null, title: 'MSFT 10-K Annual Report', published_at: '2026-04-09T08:00:00Z', retrieved_at: '2026-04-09T08:05:00Z', language: 'en', content_hash: 'def456', parse_quality_score: 0.88, parse_confidence: 'medium', status: 'parsed', created_at: '2026-04-09T08:05:00Z' }, + ])), + ); + renderRoute('/documents'); + await waitFor(() => { + expect(screen.getByText('Apple Q4 Earnings Beat')).toBeInTheDocument(); + }); + expect(screen.getByText('MSFT 10-K Annual Report')).toBeInTheDocument(); + expect(screen.getByText('filing')).toBeInTheDocument(); + }); + + it('navigates to document detail on row click', async () => { + renderRoute('/documents/d1'); + await waitFor(() => { + expect(screen.getByText('Apple Q4 Earnings Beat')).toBeInTheDocument(); + }); + }); +});