feat: publish Regex Tools 0.2.0

This commit is contained in:
2026-07-27 01:06:57 +02:00
parent 873b9b218d
commit 4951c966d4
180 changed files with 35344 additions and 503 deletions

View File

@@ -0,0 +1,253 @@
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<never> {
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<Parameters<typeof CorpusPanel>[0]> = {},
) {
return render(
<CorpusPanel
active
flavour="ecmascript"
flavourVersion="2025"
options={{}}
pattern="\\d+"
flags={[]}
captureMetadata={[]}
patternAccepted
replacement="[$&]"
replacementAccepted
onReplacementChange={() => {}}
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(
<CorpusPanel
active
flavour="ecmascript"
flavourVersion="2025"
options={{}}
pattern="changed"
flags={[]}
captureMetadata={[]}
patternAccepted
replacement="[$&]"
replacementAccepted
onReplacementChange={() => {}}
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<never>((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(
<CorpusPanel
active={false}
flavour="ecmascript"
flavourVersion="2025"
options={{}}
pattern="\\d+"
flags={[]}
captureMetadata={[]}
patternAccepted
replacement="[$&]"
replacementAccepted
onReplacementChange={() => {}}
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<never>((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(
<CorpusPanel
active
flavour="ecmascript"
flavourVersion="2025"
options={{}}
pattern="changed"
flags={[]}
captureMetadata={[]}
patternAccepted
replacement="[$&]"
replacementAccepted
onReplacementChange={() => {}}
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();
});
});