feat: publish Regex Tools 0.2.0
This commit is contained in:
253
src/components/CorpusPanel.test.tsx
Normal file
253
src/components/CorpusPanel.test.tsx
Normal 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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user