// @vitest-environment jsdom import { act } from 'react'; import { createRoot } from 'react-dom/client'; import { afterEach, expect, it, vi } from 'vitest'; import { AssessorApiClient } from '../../src/api/assessor-api-client'; import type { RegulatoryCorpusStatus } from '../../src/domain/regulatory-corpus-management'; import { IsoCorpusLibrary } from '../../src/features/project-import/IsoCorpusLibrary'; afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); }); it('uploads both selected ISO PDFs with the entered operator token and shows embedding progress', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); const initial: RegulatoryCorpusStatus = { version: 1, configured: true, active: null, job: null }; vi.spyOn(AssessorApiClient.prototype, 'getIsoCorpusStatus').mockResolvedValue(initial); const submit = vi.spyOn(AssessorApiClient.prototype, 'submitIsoCorpus').mockResolvedValue({ ...initial, job: { id: 'synthetic-job', status: 'running', phase: 'embedding', startedAt: '2026-09-23T00:00:00Z', completedChunks: 2, totalChunks: 8 } }); const container = document.createElement('div'); document.body.append(container); const root = createRoot(container); try { await act(async () => root.render()); const fileInputs = container.querySelectorAll('input[type="file"]'); const first = new File(['%PDF-synthetic-27001'], '27001.pdf', { type: 'application/pdf' }); const second = new File(['%PDF-synthetic-27002'], '27002.pdf', { type: 'application/pdf' }); await act(async () => { Object.defineProperty(fileInputs[0], 'files', { configurable: true, value: [first] }); fileInputs[0].dispatchEvent(new Event('change', { bubbles: true })); Object.defineProperty(fileInputs[1], 'files', { configurable: true, value: [second] }); fileInputs[1].dispatchEvent(new Event('change', { bubbles: true })); const token = container.querySelector('input[type="password"]')!; Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')!.set!.call(token, 'synthetic-secret'); token.dispatchEvent(new Event('input', { bubbles: true })); }); await act(async () => container.querySelector('form')!.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))); expect(submit).toHaveBeenCalledWith(first, second, 'synthetic-secret'); expect(container.textContent).toContain('Embedding 2 / 8'); expect(container.querySelector('input[type="password"]')!.value).toBe(''); } finally { await act(async () => root.unmount()); container.remove(); } }); it('clears a transient status polling error once a later poll succeeds', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); vi.useFakeTimers(); const job = { id: 'synthetic-job', status: 'running' as const, phase: 'embedding' as const, startedAt: '2026-09-23T00:00:00Z' }; vi.spyOn(AssessorApiClient.prototype, 'getIsoCorpusStatus') .mockResolvedValueOnce({ version: 1, configured: true, active: null, job }) .mockRejectedValueOnce(new Error('Synthetic transient failure.')) .mockResolvedValue({ version: 1, configured: true, active: null, job: { ...job, status: 'succeeded', phase: 'complete' } }); const container = document.createElement('div'); document.body.append(container); const root = createRoot(container); try { await act(async () => root.render()); await act(async () => { await vi.advanceTimersByTimeAsync(2_000); }); expect(container.textContent).toContain('Synthetic transient failure.'); await act(async () => { await vi.advanceTimersByTimeAsync(2_000); }); expect(container.textContent).not.toContain('Synthetic transient failure.'); expect(container.textContent).toContain('ISO corpus indexed and active.'); } finally { await act(async () => root.unmount()); container.remove(); vi.useRealTimers(); } });