// @vitest-environment jsdom import '@testing-library/jest-dom/vitest'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { cleanup, render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { HttpResponse, http } from 'msw'; import { setupServer } from 'msw/node'; import type { PropsWithChildren, ReactElement } from 'react'; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; import { OperationsPage } from '../../apps/dashboard/src/features/operations/operations-page.js'; import { PerformancePage } from '../../apps/dashboard/src/features/performance/performance-page.js'; const server = setupServer(); function TestProviders({ children }: PropsWithChildren) { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, }); return {children}; } function renderPage(element: ReactElement) { return render(element, { wrapper: TestProviders }); } beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); afterEach(() => { cleanup(); server.resetHandlers(); }); afterAll(() => server.close()); describe('PerformancePage', () => { it('shows sample sizes, confidence warnings, useful comparisons, and successful examples', async () => { server.use( http.get('/api/analytics/by-subreddit', () => HttpResponse.json([ { subreddit: 'askuk', totalSent: 40, totalResponses: 12, responseRate: 30 }, { subreddit: 'tiny', totalSent: 3, totalResponses: 2, responseRate: 67 }, ]), ), http.get('/api/analytics/by-strategy', () => HttpResponse.json([ { strategyId: 'friendly', totalSent: 25, totalResponses: 10, responseRate: 40, configured: true }, { strategyId: 'new-test', totalSent: 2, totalResponses: 1, responseRate: 50 }, ]), ), http.get('/api/analytics/trend', ({ request }) => { expect(new URL(request.url).searchParams.get('days')).toBe('30'); return HttpResponse.json([ { date: '2026-08-07', totalSent: 10, totalResponses: 2, responseRate: 20 }, { date: '2026-08-08', totalSent: 8, totalResponses: 4, responseRate: 50 }, ]); }), http.get('/api/successful', () => HttpResponse.json([ { id: 7, sentMessageId: 22, originalMessage: 'A thoughtful opener', imageAnalysis: null, postTitle: 'Weekend plans', responseSnippet: 'That sounds lovely!', subreddit: 'askuk', createdAt: Date.parse('2026-08-08T10:00:00Z'), }, ]), ), ); renderPage(); expect(await screen.findByRole('heading', { name: 'Performance' })).toBeInTheDocument(); const subredditTable = screen.getByRole('table', { name: 'Subreddit performance' }); expect(within(subredditTable).getByText('n = 40')).toBeInTheDocument(); expect(within(subredditTable).getByText('n = 3')).toBeInTheDocument(); expect(within(subredditTable).getByText('Low confidence')).toBeInTheDocument(); const strategyTable = screen.getByRole('table', { name: 'Strategy performance' }); expect(within(strategyTable).getByText('n = 25')).toBeInTheDocument(); expect(within(strategyTable).getByText('Configured')).toBeInTheDocument(); expect(screen.getByRole('list', { name: '30 day response trend' })).toHaveTextContent('50%'); expect(screen.getByRole('article', { name: 'Successful example from askuk' })).toHaveTextContent('A thoughtful opener'); expect(screen.getByRole('article', { name: 'Successful example from askuk' })).toHaveTextContent('That sounds lovely!'); }); }); describe('OperationsPage', () => { it('keeps readable activity, job state, and raw searchable logs separate', async () => { server.use( http.get('/api/activity', () => HttpResponse.json([ { timestamp: Date.parse('2026-08-08T12:00:00Z'), type: 'response', message: 'Reply from u/alex', recipient: 'alex' }, ]), ), http.get('/api/logs', ({ request }) => { expect(new URL(request.url).searchParams.get('limit')).toBe('200'); return HttpResponse.json([ { timestamp: Date.parse('2026-08-08T12:00:01Z'), level: 'info', message: 'Sync complete' }, { timestamp: Date.parse('2026-08-08T12:00:02Z'), level: 'error', message: 'Matrix request failed' }, ]); }), http.get('/api/chats/sync', () => HttpResponse.json({ syncing: true, syncingKind: 'delta', lastFullSyncAt: null, lastDeltaSyncAt: null, hasEverFullSynced: false, cachedChats: 18, progress: { kind: 'delta', phase: 'loading-details', current: 4, total: 10, label: 'Loading room history', startedAt: Date.parse('2026-08-08T11:59:00Z'), }, }), ), ); const user = userEvent.setup(); renderPage(); expect(await screen.findByRole('heading', { name: 'Operations' })).toBeInTheDocument(); const activityRegion = screen.getByRole('region', { name: 'Activity' }); expect(await within(activityRegion).findByText('Reply from u/alex')).toBeInTheDocument(); const jobsRegion = screen.getByRole('region', { name: 'Sync jobs' }); expect(await within(jobsRegion).findByText('4 of 10')).toBeInTheDocument(); for (const name of ['Sync latest chats', 'Full cache sync', 'Fetch all chats']) { expect(within(jobsRegion).getByRole('button', { name })).toHaveClass('button', 'secondary'); } expect(screen.getByRole('status', { name: 'Realtime connection' })).toHaveTextContent('State may be stale'); const consoleRegion = screen.getByRole('region', { name: 'Live console' }); expect(consoleRegion).toHaveTextContent('Sync complete'); expect(consoleRegion).toHaveTextContent('Matrix request failed'); await user.selectOptions(within(consoleRegion).getByLabelText('Log level'), 'error'); expect(consoleRegion).not.toHaveTextContent('Sync complete'); expect(consoleRegion).toHaveTextContent('Matrix request failed'); await user.type(within(consoleRegion).getByLabelText('Search logs'), 'no match'); expect(consoleRegion).toHaveTextContent('No logs match the current filters.'); expect(within(consoleRegion).getByRole('button', { name: 'Pause console' })).toHaveAttribute('aria-pressed', 'false'); expect(within(consoleRegion).getByLabelText('Auto-scroll')).toBeChecked(); await user.clear(within(consoleRegion).getByLabelText('Search logs')); await user.click(within(consoleRegion).getByRole('button', { name: 'Clear local console' })); expect(consoleRegion).toHaveTextContent('Server logs were not deleted'); }); it('starts delta sync directly and confirms expensive chat cache operations', async () => { let deltaCalls = 0; let fullCalls = 0; server.use( http.get('/api/activity', () => HttpResponse.json([])), http.get('/api/logs', () => HttpResponse.json([])), http.get('/api/chats/sync', () => HttpResponse.json({ syncing: false, syncingKind: null, lastFullSyncAt: null, lastDeltaSyncAt: null, hasEverFullSynced: false, cachedChats: 2, progress: null })), http.post('/api/chats/sync/delta', () => { deltaCalls += 1; return HttpResponse.json({ success: true, kind: 'delta' }); }), http.post('/api/chats/sync/full', () => { fullCalls += 1; return HttpResponse.json({ success: true, kind: 'full' }); }), http.post('/api/chats/fetch-all', () => HttpResponse.json({ success: true, count: 7 })), ); const confirmSpy = vi.spyOn(globalThis, 'confirm').mockReturnValue(false); const user = userEvent.setup(); renderPage(); await user.click(await screen.findByRole('button', { name: 'Sync latest chats' })); expect(await screen.findByText(/Delta sync started/)).toBeInTheDocument(); expect(deltaCalls).toBe(1); await user.click(screen.getByRole('button', { name: 'Full cache sync' })); expect(fullCalls).toBe(0); confirmSpy.mockReturnValue(true); await user.click(screen.getByRole('button', { name: 'Full cache sync' })); expect(await screen.findByText(/Full sync started/)).toBeInTheDocument(); expect(fullCalls).toBe(1); await user.click(screen.getByRole('button', { name: 'Fetch all chats' })); expect(await screen.findByText('Fetched 7 chats.')).toBeInTheDocument(); }); });