feat: publish Regex Tools 0.1.0

This commit is contained in:
2026-07-24 18:04:21 +02:00
commit 873b9b218d
136 changed files with 24212 additions and 0 deletions

View File

@@ -0,0 +1,383 @@
import {
act,
cleanup,
fireEvent,
render,
screen,
} from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { RegexExecutionRequest } from "../regex/model/match";
import type {
CaptureDefinition,
RegexSyntaxRequest,
RegexSyntaxResult,
ReplacementSyntaxResult,
} from "../regex/model/syntax";
interface PendingPattern {
readonly request: RegexSyntaxRequest;
readonly resolve: (result: RegexSyntaxResult) => void;
}
interface PendingReplacement {
readonly replacement: string;
readonly resolve: (result: ReplacementSyntaxResult) => void;
}
interface SyntaxInstance {
readonly label: string;
cancelCalls: number;
}
const syntaxHarness = vi.hoisted(() => ({
instances: [] as SyntaxInstance[],
patterns: [] as PendingPattern[],
replacements: [] as PendingReplacement[],
}));
const engineHarness = vi.hoisted(() => ({
requests: [] as RegexExecutionRequest[],
}));
vi.mock("../editors/CodeEditor", () => ({
CodeEditor({
value,
onChange,
label,
readOnly,
}: {
readonly value: string;
readonly onChange?: (value: string) => void;
readonly label: string;
readonly readOnly?: boolean;
}) {
return (
<textarea
aria-label={label}
value={value}
readOnly={readOnly}
onChange={(event) => onChange?.(event.target.value)}
/>
);
},
}));
vi.mock("../regex/execution/SyntaxSupervisor", () => ({
SyntaxSupervisor: class {
readonly instance: SyntaxInstance;
constructor(label = "Syntax parser") {
this.instance = { label, cancelCalls: 0 };
syntaxHarness.instances.push(this.instance);
}
parsePattern(request: RegexSyntaxRequest): Promise<RegexSyntaxResult> {
return new Promise((resolve) => {
syntaxHarness.patterns.push({ request, resolve });
});
}
parseReplacement(request: {
readonly replacement: string;
}): Promise<ReplacementSyntaxResult> {
return new Promise((resolve) => {
syntaxHarness.replacements.push({
replacement: request.replacement,
resolve,
});
});
}
cancel(): void {
this.instance.cancelCalls += 1;
}
dispose(): void {}
},
}));
vi.mock("../regex/execution/EngineSupervisor", () => ({
EngineSupervisor: class {
result(request: RegexExecutionRequest) {
return {
accepted: true,
engine: {
id: "test-engine",
displayName: "Test engine",
version: "1",
flavour: "ecmascript",
offsetUnit: "utf16",
runtime: "worker",
},
flags: {
userFlags: request.flags.join(""),
effectiveFlags: request.flags.join(""),
internallyAddedIndicesFlag: false,
internallyAddedGlobalFlag: false,
},
matches: [],
diagnostics: [],
elapsedMs: 1,
truncated: false,
};
}
execute(request: RegexExecutionRequest) {
engineHarness.requests.push(request);
return Promise.resolve(this.result(request));
}
replace(
request: RegexExecutionRequest & {
readonly replacement: string;
},
) {
engineHarness.requests.push(request);
return Promise.resolve({
execution: this.result(request),
output: request.replacement,
outputBytes: request.replacement.length,
outputTruncated: false,
truncated: false,
});
}
cancel(): void {}
dispose(): void {}
},
}));
import { Workbench } from "./Workbench";
function syntaxResult(
pattern: string,
captures: readonly CaptureDefinition[],
): RegexSyntaxResult {
return {
accepted: true,
root: {
id: `root-${pattern}`,
kind: "pattern",
range: { startUtf16: 0, endUtf16: pattern.length },
raw: pattern,
explanation: "Test syntax",
children: [],
properties: {
zeroWidth: false,
nullable: false,
consumesInput: true,
},
support: {
flavour: "ecmascript",
status: "supported",
notes: [],
},
provenance: {
provider: "test",
providerVersion: "1",
source: "parsed",
},
},
tokens: [],
captures,
diagnostics: [],
provider: { id: "test", version: "1" },
coverage: {
status: "full-tested",
summary: "Test coverage",
unsupportedConstructs: [],
},
};
}
async function advanceParseDebounce(): Promise<void> {
await act(async () => {
await vi.advanceTimersByTimeAsync(120);
});
}
async function resolvePattern(
index: number,
captures: readonly CaptureDefinition[],
): Promise<void> {
await act(async () => {
const pending = syntaxHarness.patterns[index];
if (!pending) throw new Error(`Pattern request ${index} is unavailable`);
pending.resolve(syntaxResult(pending.request.pattern, captures));
await Promise.resolve();
});
}
async function resolveLatestReplacement(): Promise<void> {
await act(async () => {
const pending = syntaxHarness.replacements.at(-1);
if (!pending) throw new Error("Replacement request is unavailable");
pending.resolve({
accepted: true,
tokens: [],
diagnostics: [],
totalDiagnostics: 0,
diagnosticsTruncated: false,
});
await Promise.resolve();
});
}
afterEach(() => {
cleanup();
vi.useRealTimers();
syntaxHarness.instances.length = 0;
syntaxHarness.patterns.length = 0;
syntaxHarness.replacements.length = 0;
engineHarness.requests.length = 0;
});
describe("Workbench syntax coordination", () => {
it("blocks Run until current syntax arrives and isolates replacement parsing", async () => {
vi.useFakeTimers();
render(<Workbench />);
fireEvent.click(screen.getByRole("checkbox", { name: "Live" }));
await advanceParseDebounce();
await resolvePattern(0, [
{
number: 1,
name: "old",
range: { startUtf16: 0, endUtf16: 9 },
repeated: false,
},
]);
expect(syntaxHarness.instances.map((instance) => instance.label)).toEqual([
"Pattern syntax parser",
"Replacement syntax parser",
]);
fireEvent.click(screen.getByRole("button", { name: "Replace" }));
const patternInput = screen.getByRole("textbox", {
name: "Regular expression pattern",
});
const replacementInput = screen.getByRole("textbox", {
name: "Replacement template",
});
fireEvent.change(patternInput, { target: { value: "(?<new>b)" } });
fireEvent.change(replacementInput, { target: { value: "$<new>" } });
const runButton = screen.getByRole("button", { name: "Run" });
expect(runButton).toBeDisabled();
fireEvent.click(runButton);
expect(engineHarness.requests).toHaveLength(0);
await advanceParseDebounce();
expect(syntaxHarness.patterns[1]?.request.pattern).toBe("(?<new>b)");
await resolvePattern(1, [
{
number: 1,
name: "new",
range: { startUtf16: 0, endUtf16: 9 },
repeated: false,
},
]);
expect(runButton).toBeDisabled();
await resolveLatestReplacement();
expect(runButton).toBeEnabled();
const patternParser = syntaxHarness.instances[0];
const replacementParser = syntaxHarness.instances[1];
if (!patternParser || !replacementParser) {
throw new Error("Expected both syntax parser instances");
}
const patternCancelCount = patternParser.cancelCalls;
fireEvent.change(replacementInput, { target: { value: "$<new>!" } });
fireEvent.change(replacementInput, { target: { value: "[$<new>]" } });
expect(patternParser.cancelCalls).toBe(patternCancelCount);
expect(replacementParser.cancelCalls).toBeGreaterThan(0);
expect(
syntaxHarness.replacements.map((request) => request.replacement),
).toContain("[$<new>]");
expect(runButton).toBeDisabled();
await resolveLatestReplacement();
expect(runButton).toBeEnabled();
fireEvent.click(runButton);
await act(async () => {
await Promise.resolve();
});
expect(engineHarness.requests).toHaveLength(1);
expect(engineHarness.requests[0]).toMatchObject({
pattern: "(?<new>b)",
captureMetadata: [{ number: 1, name: "new" }],
});
});
it("captures a complete result and reruns only the selected saved test", async () => {
vi.useFakeTimers();
render(<Workbench />);
fireEvent.click(screen.getByRole("checkbox", { name: "Live" }));
await advanceParseDebounce();
await resolvePattern(0, []);
fireEvent.click(screen.getByRole("button", { name: "Run" }));
await act(async () => {
await Promise.resolve();
});
fireEvent.click(screen.getByRole("button", { name: "Unit tests" }));
const addCurrent = screen.getByRole("button", {
name: "Add current result",
});
expect(addCurrent).toBeEnabled();
fireEvent.click(addCurrent);
expect(screen.getByText("Current match result")).toBeVisible();
expect(screen.getByText("match count: 0", { exact: false })).toBeVisible();
fireEvent.click(
screen.getByRole("checkbox", { name: "Select Current match result" }),
);
fireEvent.click(screen.getByRole("button", { name: "Run selected" }));
const testPatternIndex = syntaxHarness.patterns.length - 1;
await resolvePattern(testPatternIndex, []);
await act(async () => {
await Promise.resolve();
});
expect(screen.getByText(/^Pass —/)).toBeVisible();
expect(engineHarness.requests.at(-1)?.pattern).toBe(
syntaxHarness.patterns[0]?.request.pattern,
);
expect(engineHarness.requests.at(-1)?.scanAll).toBe(false);
});
it("hard-stops an active suite at the aggregate wall deadline", async () => {
vi.useFakeTimers();
render(<Workbench />);
fireEvent.click(screen.getByRole("checkbox", { name: "Live" }));
await advanceParseDebounce();
await resolvePattern(0, []);
fireEvent.click(screen.getByRole("button", { name: "Run" }));
await act(async () => {
await Promise.resolve();
});
fireEvent.click(screen.getByRole("button", { name: "Unit tests" }));
fireEvent.click(screen.getByRole("button", { name: "Add current result" }));
fireEvent.click(screen.getByRole("button", { name: "Run all" }));
expect(screen.getByRole("button", { name: "Cancel" })).toBeVisible();
await act(async () => {
await vi.advanceTimersByTimeAsync(60_000);
});
expect(screen.getByText(/Suite stopped at the 60 second/u)).toBeVisible();
expect(screen.getByRole("button", { name: "Run all" })).toBeEnabled();
expect(
syntaxHarness.instances.find(
(instance) => instance.label === "Syntax parser",
)?.cancelCalls,
).toBeGreaterThan(0);
});
});