// @vitest-environment jsdom import { act, type ReactNode } from 'react'; import { createRoot } from 'react-dom/client'; import { afterEach, expect, it, vi } from 'vitest'; import type { AiOverviewResponse } from '../../src/compliance/ai-overview'; afterEach(() => { vi.unstubAllGlobals(); vi.resetModules(); }); const base: AiOverviewResponse = { version: 1, projectId: 'project-a', scope: { assessmentIds: ['assessment-a'], scanRunIds: [] }, generationEnabled: true, state: 'empty', stale: false }; it('shows one generation entry and no status-poll entries while the backend generates', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); let backendState: AiOverviewResponse['state'] = 'empty'; // The real request path: the API client labels every request it logs through the activity store. vi.stubGlobal('fetch', vi.fn(async (_url: string, init?: RequestInit) => { if (init?.method === 'POST') backendState = 'running'; const body = { ...base, state: backendState, pollAfterMs: backendState === 'running' ? 10 : undefined }; return new Response(JSON.stringify(body), { status: 200, headers: { 'Content-Type': 'application/json' } }); })); const { useAiOverview } = await import('../../src/compliance/ui/AiOverviewPanel'); const { activitySnapshot } = await import('../../src/platform/activity-store'); const labels = () => activitySnapshot().map(row => `${row.label}:${row.state}`); let generate!: () => Promise; function Probe(): ReactNode { const controller = useAiOverview('project-a', ['assessment-a']); generate = controller.generate; return {controller.response?.state ?? 'none'}; } const container = document.createElement('div'); document.body.append(container); const root = createRoot(container); try { await act(async () => root.render()); expect(labels()).toEqual(['Loading AI overview status:completed']); await act(async () => { await generate(); }); await act(async () => { await new Promise(resolve => setTimeout(resolve, 50)); }); expect(container.textContent).toBe('running'); expect(labels()).toEqual(['Loading AI overview status:completed', 'Starting AI overview:completed', 'Generating AI overview:running']); backendState = 'succeeded'; await act(async () => { await new Promise(resolve => setTimeout(resolve, 50)); }); expect(container.textContent).toBe('succeeded'); expect(labels()).toEqual(['Loading AI overview status:completed', 'Starting AI overview:completed', 'Generating AI overview:completed']); } finally { await act(async () => root.unmount()); container.remove(); } });