import { act, cleanup, fireEvent, render, screen, } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, describe, expect, it, vi } from "vitest"; import type { RegexExecutionRequest } from "../regex/model/match"; const engineHarness = vi.hoisted(() => ({ cancelCalls: 0, disposeCalls: 0, executeRequests: [] as RegexExecutionRequest[], })); vi.mock("../regex/execution/EngineSupervisor", () => ({ EngineSupervisor: class { execute(request: RegexExecutionRequest) { engineHarness.executeRequests.push(request); return Promise.resolve({ accepted: true, engine: { flavour: "ecmascript", adapterVersion: "test", engineName: "Test engine", engineVersion: "1", runtimeVersion: "1", offsetUnit: "utf16", capabilities: { compilation: true, matching: true, replacement: true, namedCaptures: true, captureHistory: false, actualTrace: false, benchmark: false, }, }, flags: { userFlags: "", effectiveFlags: "g", internallyAddedIndicesFlag: true, internallyAddedGlobalFlag: true, }, matches: [ { matchNumber: 1, value: "42", valueStatus: "complete", range: { startUtf16: 0, endUtf16: 2 }, nativeRange: { start: 0, end: 2, unit: "utf16" }, captures: [], }, ], diagnostics: [], elapsedMs: 2, truncated: false, }); } replace(): Promise { return Promise.reject(new Error("Unexpected replacement call")); } cancel(): void { engineHarness.cancelCalls += 1; } dispose(): void { engineHarness.disposeCalls += 1; } }, })); import { CorpusPanel } from "./CorpusPanel"; afterEach(() => { cleanup(); engineHarness.cancelCalls = 0; engineHarness.disposeCalls = 0; engineHarness.executeRequests.length = 0; }); function renderPanel( overrides: Partial[0]> = {}, ) { return render( {}} timeoutMs={2_000} {...overrides} />, ); } describe("CorpusPanel", () => { it("adds pasted text and produces an exhaustive per-document summary", async () => { renderPanel(); await userEvent.type( screen.getByRole("textbox", { name: "Pasted corpus text" }), "42", ); await userEvent.click( screen.getByRole("button", { name: "Add pasted text" }), ); expect( screen.getByRole("list", { name: "Corpus documents" }), ).toHaveTextContent("pasted-text.txt"); await userEvent.click(screen.getByRole("button", { name: "Scan corpus" })); expect( await screen.findByRole("table", { name: "Corpus document results" }), ).toHaveTextContent("complete"); expect(engineHarness.executeRequests).toHaveLength(1); expect(engineHarness.executeRequests[0]).toMatchObject({ subject: "42", scanAll: true, }); expect( screen.getByRole("button", { name: "Download summary JSON" }), ).toBeEnabled(); }); it("blocks invalid runs and clears results when configuration changes", async () => { const rendered = renderPanel({ patternAccepted: false }); fireEvent.click(screen.getByRole("button", { name: "Add pasted text" })); expect(screen.getByRole("button", { name: "Scan corpus" })).toBeDisabled(); expect(screen.getByRole("alert")).toHaveTextContent( "syntax provider accepts", ); rendered.rerender( {}} timeoutMs={2_000} />, ); expect(screen.getByRole("button", { name: "Scan corpus" })).toBeEnabled(); expect(screen.getByRole("status")).toHaveTextContent( "previous corpus results were cleared", ); }); it("cancels an active batch when the workspace is left", async () => { let resolveExecution: ((value: never) => void) | undefined; const pending = new Promise((resolve) => { resolveExecution = resolve; }); const execute = vi .spyOn( (await import("../regex/execution/EngineSupervisor")).EngineSupervisor .prototype, "execute", ) .mockReturnValue(pending); const rendered = renderPanel(); fireEvent.click(screen.getByRole("button", { name: "Add pasted text" })); fireEvent.click(screen.getByRole("button", { name: "Scan corpus" })); expect( screen.getByRole("button", { name: "Cancel corpus run" }), ).toBeEnabled(); rendered.rerender( {}} timeoutMs={2_000} />, ); expect(engineHarness.cancelCalls).toBeGreaterThan(0); execute.mockRestore(); await act(async () => { resolveExecution?.(undefined as never); }); }); it("does not publish stale batch results after configuration changes", async () => { let resolveExecution: ((value: never) => void) | undefined; const pending = new Promise((resolve) => { resolveExecution = resolve; }); const execute = vi .spyOn( (await import("../regex/execution/EngineSupervisor")).EngineSupervisor .prototype, "execute", ) .mockReturnValue(pending); const rendered = renderPanel(); fireEvent.click(screen.getByRole("button", { name: "Add pasted text" })); fireEvent.click(screen.getByRole("button", { name: "Scan corpus" })); rendered.rerender( {}} timeoutMs={2_000} />, ); await act(async () => { resolveExecution?.(undefined as never); }); expect( screen.queryByRole("table", { name: "Corpus document results" }), ).not.toBeInTheDocument(); expect(screen.getByRole("status")).toHaveTextContent( "previous corpus results were cleared", ); execute.mockRestore(); }); });