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,66 @@
import { expect, test, type Page } from "@playwright/test";
async function setEditor(page: Page, testId: string, value: string) {
await page.getByTestId(testId).locator(".cm-content").fill(value);
}
async function waitForSyntax(page: Page) {
await expect(
page.getByRole("button", { name: "Run", exact: true }),
).toBeEnabled({ timeout: 15_000 });
}
test("ECMAScript analysis runs static, benchmark and growth slices in local workers", async ({
page,
}) => {
const pageErrors: string[] = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
await page.goto("./");
await page.getByLabel("Live").uncheck();
await setEditor(page, "editor-regular-expression-pattern", "(a+)+$");
await setEditor(page, "editor-test-text", "aaaa!");
await waitForSyntax(page);
await page.getByRole("button", { name: "Analysis", exact: true }).click();
const dialog = page.getByRole("dialog", {
name: "Performance & risk analysis",
});
await expect(dialog).toBeVisible();
await expect(dialog).toBeInViewport();
await expect(
dialog.getByText("Potential nested-quantifier risk"),
).toBeVisible();
await expect(dialog).toContainText(/does not mean.*safe/iu);
await dialog.getByRole("tab", { name: "Benchmark" }).click();
await dialog.getByRole("spinbutton", { name: "Warm-up samples" }).fill("1");
await dialog.getByRole("spinbutton", { name: "Measured samples" }).fill("3");
await dialog.getByRole("button", { name: "Run bounded benchmark" }).click();
const metrics = dialog.getByRole("table", {
name: "Cold and warm benchmark metrics",
});
await expect(metrics).toBeVisible();
await expect(metrics).toContainText("Warm median");
await expect(metrics).toContainText("Warm p95");
await expect(dialog).toContainText("Native ECMAScript RegExp");
await expect(dialog).toContainText("3 measured");
await dialog.getByRole("tab", { name: "Risk & growth" }).click();
await dialog
.getByRole("spinbutton", { name: "Maximum repetitions" })
.fill("64");
await dialog.getByRole("spinbutton", { name: "Maximum steps" }).fill("3");
await dialog
.getByRole("button", { name: "Run bounded growth probe" })
.click();
await expect(
dialog.getByRole("table", { name: "Dynamic growth samples" }),
).toBeVisible();
await expect(
dialog.getByRole("img", { name: /Execution-time growth plot/u }),
).toBeVisible();
await expect(
dialog.getByRole("img", { name: /Outcome plot/u }),
).toBeVisible();
expect(pageErrors).toEqual([]);
});

View File

@@ -0,0 +1,89 @@
/// <reference types="node" />
import { expect, test, type Page } from "@playwright/test";
import { readFile } from "node:fs/promises";
async function setEditor(page: Page, testId: string, value: string) {
await page.getByTestId(testId).locator(".cm-content").fill(value);
}
test("real ECMAScript and PCRE2 workers compare exact snapshots and generate reviewed C", async ({
page,
}) => {
const pageErrors: string[] = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
await page.goto("./");
await page.getByLabel("Live").uncheck();
await setEditor(
page,
"editor-regular-expression-pattern",
"(?<word>[A-Za-z]+)",
);
await setEditor(page, "editor-test-text", "alpha beta");
await expect(
page.getByRole("button", { name: "Run", exact: true }),
).toBeEnabled({ timeout: 15_000 });
await page
.getByRole("button", { name: "Compare & code", exact: true })
.click();
const dialog = page.getByRole("dialog", { name: "Compare & generate" });
await expect(dialog).toBeVisible();
await expect(dialog).toBeInViewport();
await dialog.getByRole("button", { name: "Run comparison" }).click();
const verdict = dialog.locator(".comparison-verdict");
await expect(
verdict.getByRole("heading", { name: "same for current input" }),
).toBeVisible({ timeout: 20_000 });
await expect(verdict).toContainText("not a proof");
const sideCards = dialog.locator(".comparison-side-card");
await expect(sideCards).toHaveCount(2);
await expect(sideCards.nth(0)).toContainText("Native ECMAScript RegExp");
await expect(sideCards.nth(0)).toContainText("native utf16");
await expect(sideCards.nth(1)).toContainText("PCRE2 WebAssembly");
await expect(sideCards.nth(1)).toContainText("10.47 2025-10-21");
await expect(sideCards.nth(1)).toContainText("native utf8-byte");
await expect(
dialog.locator(".comparison-alignments table tbody tr"),
).toHaveCount(2);
await dialog.getByLabel("Shared comparison pattern").fill("(?>a+)");
await dialog.getByLabel("Comparison subject").fill("aaa");
await dialog.getByRole("button", { name: "Run comparison" }).click();
await expect(
verdict.getByRole("heading", { name: "not comparable" }),
).toBeVisible({ timeout: 20_000 });
await expect(dialog).toContainText("flavour specific syntax");
await expect(dialog).toContainText(
"no runtime equivalence claim is possible",
);
await dialog.getByLabel("Pattern model").selectOption("variants");
await dialog.getByLabel("PCRE2 pattern variant").fill("(?<word>[a-z]++)");
await dialog.getByLabel("Comparison subject").fill("alpha beta");
await dialog.getByRole("tab", { name: "PCRE2 C code" }).click();
await dialog.getByRole("button", { name: "Generate reviewed C17" }).click();
await expect(dialog).toContainText("PCRE2 10.47 8-bit · all-matches");
await expect(dialog).toContainText("Exact UTF-8 byte arrays");
const source = dialog.locator(".generated-code-result pre");
await expect(source).toContainText(
"This generated program requires PCRE2 10.47 exactly.",
);
await expect(source).toContainText("0x2b, 0x2b");
await expect(source).not.toContainText("(?<word>[a-z]++)");
const downloadPromise = page.waitForEvent("download");
await dialog.getByRole("button", { name: "Download .c" }).click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe("regex-tools-pcre2-match.c");
const downloadPath = await download.path();
if (!downloadPath) throw new Error("Generated C download is unavailable.");
const downloadedSource = await readFile(downloadPath, "utf8");
expect(downloadedSource).toContain(
'#error "This generated program requires PCRE2 10.47 exactly."',
);
expect(downloadedSource).toContain("pcre2_set_match_limit");
expect(downloadedSource).not.toContain("(?<word>[a-z]++)");
expect(pageErrors).toEqual([]);
});

View File

@@ -0,0 +1,98 @@
import { expect, test, type Page } from "@playwright/test";
async function setEditor(page: Page, testId: string, value: string) {
await page.getByTestId(testId).locator(".cm-content").fill(value);
}
async function waitForSyntax(page: Page) {
await expect(
page.getByRole("button", { name: "Run", exact: true }),
).toBeEnabled({ timeout: 15_000 });
}
test("real ECMAScript workers validate, confirm and apply an exact formatter candidate", async ({
page,
}) => {
const pageErrors: string[] = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
await page.goto("./");
await page.getByLabel("Live").uncheck();
await page.getByRole("checkbox", { name: "Scan all" }).check();
await setEditor(page, "editor-regular-expression-pattern", "a/b");
await setEditor(page, "editor-test-text", "a/b a/b");
await waitForSyntax(page);
await page.getByRole("button", { name: "Run", exact: true }).click();
await expect(page.locator(".run-status")).toHaveClass(/status-ready/u, {
timeout: 15_000,
});
await page.getByRole("button", { name: "Unit tests", exact: true }).click();
await page.getByRole("button", { name: "Add current result" }).click();
await expect(page.locator(".test-list li")).toHaveCount(1);
await page.getByRole("button", { name: "Format", exact: true }).click();
const dialog = page.getByRole("dialog", {
name: "Format pattern",
});
await expect(dialog).toBeVisible();
await expect(dialog).toBeInViewport();
await expect(dialog.getByTestId("formatted-pattern-preview")).toHaveText(
"a\\/b",
);
await dialog
.getByRole("button", { name: "Validate exact snapshots" })
.click();
await expect(dialog.locator(".formatter-status")).toContainText(
"All completed safety gates retained the same observable behavior.",
{ timeout: 20_000 },
);
await expect(dialog).toContainText("Native ECMAScript RegExp");
await expect(dialog).toContainText("1 / 1 applicable");
await expect(
dialog.getByRole("checkbox", {
name: /I understand that this bounded validation/u,
}),
).toBeEnabled();
await expect(
dialog.getByRole("button", { name: "Apply formatted pattern" }),
).toBeDisabled();
await dialog
.getByRole("checkbox", {
name: /I understand that this bounded validation/u,
})
.check();
await dialog.getByRole("button", { name: "Apply formatted pattern" }).click();
await expect(dialog).not.toBeVisible();
await expect(
page
.getByTestId("editor-regular-expression-pattern")
.locator(".cm-content"),
).toHaveText("a\\/b");
await page.getByRole("button", { name: "Match", exact: true }).click();
await waitForSyntax(page);
await page.getByRole("button", { name: "Run", exact: true }).click();
await expect(page.locator(".run-status")).toContainText("2 matches", {
timeout: 15_000,
});
expect(pageErrors).toEqual([]);
});
test("PCRE2 formatter reports unavailable without reinterpreting syntax", async ({
page,
}) => {
await page.goto("./");
await page.getByLabel("Live").uncheck();
await page.getByRole("combobox", { name: "Flavour" }).selectOption("pcre2");
await waitForSyntax(page);
await page.getByRole("button", { name: "Format", exact: true }).click();
const dialog = page.getByRole("dialog", {
name: "Format pattern",
});
await expect(dialog).toContainText("currently available only for ECMAScript");
await expect(
dialog.getByRole("button", { name: "Validate exact snapshots" }),
).toHaveCount(0);
});

View File

@@ -0,0 +1,125 @@
/// <reference types="node" />
import { expect, test, type Page } from "@playwright/test";
import { readFile } from "node:fs/promises";
async function setEditor(page: Page, testId: string, value: string) {
await page.getByTestId(testId).locator(".cm-content").fill(value);
}
async function waitForSyntax(page: Page) {
await expect(
page.getByRole("button", { name: "Run", exact: true }),
).toBeEnabled({ timeout: 15_000 });
}
test("deterministic generated cases are actual-engine verified and retain seed provenance", async ({
page,
}) => {
const pageErrors: string[] = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
await page.goto("./");
await page.getByLabel("Live").uncheck();
await setEditor(
page,
"editor-regular-expression-pattern",
"^(cat|dog)s{0,2}[A-Z]$",
);
await waitForSyntax(page);
await page
.getByRole("button", { name: "Generate cases", exact: true })
.click();
const dialog = page.getByRole("dialog", {
name: "Generated test cases",
});
await expect(dialog).toBeVisible();
await expect(dialog).toBeInViewport();
await dialog
.getByRole("textbox", { name: "Deterministic seed" })
.fill("browser-seed-42");
await dialog.getByRole("spinbutton", { name: "Seeded variants" }).fill("4");
await dialog.getByRole("button", { name: "Generate & verify" }).click();
const summary = dialog.getByRole("region", {
name: "Generation summary",
});
await expect(summary).toContainText("Native ECMAScript RegExp", {
timeout: 15_000,
});
await expect(summary).toContainText("browser-seed-42");
const coverage = dialog.locator(".generation-coverage");
await expect(
coverage.getByText("alternative coverage", { exact: true }),
).toBeVisible();
await expect(
coverage.getByText("quantifier boundary", { exact: true }),
).toBeVisible();
await expect(dialog.locator(".generated-case-list li").first()).toBeVisible();
await expect(
dialog.getByText("actual engine verified").first(),
).toBeVisible();
await expect(dialog.getByText(/should match/u).first()).toBeVisible();
await expect(dialog.getByText(/should not match/u).first()).toBeVisible();
await dialog.getByRole("button", { name: "Add selected to tests" }).click();
await expect(dialog).toContainText(
/Added \d+ generated cases to the unit-test suite with seed provenance/u,
);
await dialog
.getByRole("button", { name: "Close generated test cases" })
.click();
await page.getByRole("button", { name: "Unit tests", exact: true }).click();
await expect(page.locator(".test-list li").first()).toContainText(
"generated · seed browser-seed-42",
);
await page
.getByRole("checkbox", { name: "Include subjects in JSON" })
.check();
const downloadPromise = page.waitForEvent("download");
await page.getByRole("button", { name: "Export JSON" }).click();
const download = await downloadPromise;
const path = await download.path();
if (!path) throw new Error("Generated test-suite download path unavailable");
const suite = JSON.parse(await readFile(path, "utf8")) as {
readonly tests: readonly {
readonly subject: string;
readonly generation?: {
readonly seed?: string;
readonly generatorId?: string;
};
}[];
};
expect(
suite.tests.some(
(candidate) =>
candidate.subject.length > 0 &&
candidate.generation?.seed === "browser-seed-42" &&
candidate.generation.generatorId === "regex-tools-ast-cases",
),
).toBe(true);
expect(pageErrors).toEqual([]);
});
test("PCRE2 reports generated cases unavailable instead of reusing ECMAScript semantics", async ({
page,
}) => {
await page.goto("./");
await page.getByLabel("Live").uncheck();
await page.getByRole("combobox", { name: "Flavour" }).selectOption("pcre2");
await waitForSyntax(page);
await page
.getByRole("button", { name: "Generate cases", exact: true })
.click();
const dialog = page.getByRole("dialog", {
name: "Generated test cases",
});
await expect(dialog).toContainText(
"current PCRE2 syntax provider is intentionally partial",
);
await expect(
dialog.getByRole("button", { name: "Generate & verify" }),
).toHaveCount(0);
});

View File

@@ -0,0 +1,102 @@
import { expect, test, type Page } from "@playwright/test";
async function setEditor(page: Page, testId: string, value: string) {
await page.getByTestId(testId).locator(".cm-content").fill(value);
}
async function openMinimizer(page: Page) {
await page.getByRole("button", { name: "Minimize", exact: true }).click();
const dialog = page.getByRole("dialog", {
name: "Minimize a reproducer",
});
await expect(dialog).toBeVisible();
await expect(dialog).toBeInViewport();
return dialog;
}
test("real workers minimize exact failures and expose bounded cancellation", async ({
page,
}) => {
const pageErrors: string[] = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
await page.goto("./");
await page.getByLabel("Live").uncheck();
// A deterministic failing should-not-match assertion, verified by the
// browser's actual ECMAScript worker.
await setEditor(page, "editor-regular-expression-pattern", "X");
await setEditor(page, "editor-test-text", "abXcd");
await expect(
page.getByRole("button", { name: "Run", exact: true }),
).toBeEnabled({ timeout: 15_000 });
let dialog = await openMinimizer(page);
await dialog.getByRole("button", { name: "Verify & minimize" }).click();
await expect(
dialog.getByRole("heading", {
name: "Locally minimal under the documented transforms",
}),
).toBeVisible({ timeout: 30_000 });
await expect(
dialog.getByLabel("Minimizer evaluation progress"),
).toBeVisible();
await expect(dialog.getByText("Baseline verification")).toBeVisible();
await expect(dialog.getByText("Final accepted candidate")).toBeVisible();
await expect(dialog).toContainText("Native ECMAScript RegExp");
await expect(dialog).toContainText("expected-no-match:present");
await expect(dialog.getByLabel("Minimized subject")).toHaveValue("X");
await dialog.getByRole("button", { name: "Use minimized subject" }).click();
await expect(dialog).not.toBeVisible();
await expect(
page.getByTestId("editor-test-text").locator(".cm-content"),
).toHaveText("X");
// With mandatory PCRE2 UCP, \w matches é; ECMAScript \w does not. Padding
// is removed while preserving the exact match-count mismatch category.
await setEditor(page, "editor-regular-expression-pattern", "\\w+");
await setEditor(page, "editor-test-text", "!!é??");
await expect(
page.getByRole("button", { name: "Run", exact: true }),
).toBeEnabled({ timeout: 15_000 });
dialog = await openMinimizer(page);
await dialog
.getByLabel("Minimization failure kind")
.selectOption("comparison-mismatch");
await dialog.getByRole("button", { name: "Verify & minimize" }).click();
await expect(
dialog.getByRole("heading", {
name: "Locally minimal under the documented transforms",
}),
).toBeVisible({ timeout: 30_000 });
await expect(dialog.getByLabel("Minimized subject")).toHaveValue("é");
await expect(dialog).toContainText(
"comparison:semantic-mismatch · match-count",
);
await expect(dialog).toContainText("Native ECMAScript RegExp");
await expect(dialog).toContainText("PCRE2 WebAssembly");
await expect(dialog).toContainText("10.47 2025-10-21");
await dialog.getByRole("button", { name: "Use minimized subject" }).click();
await expect(
page.getByTestId("editor-test-text").locator(".cm-content"),
).toHaveText("é");
// Cancellation stays immediately available even for a deliberately
// pathological timeout target; it terminates the active worker set.
await setEditor(page, "editor-regular-expression-pattern", "(a+)+$");
await setEditor(page, "editor-test-text", `${"a".repeat(40_000)}!`);
dialog = await openMinimizer(page);
await dialog
.getByLabel("Minimization failure kind")
.selectOption("engine-timeout");
await dialog.getByLabel("Each candidate ms").fill("10000");
await dialog.getByRole("button", { name: "Verify & minimize" }).click();
const cancel = dialog.getByRole("button", { name: "Cancel" });
await expect(cancel).toBeVisible();
await cancel.click();
await expect(dialog).toContainText(
"Cancelled; active syntax and engine workers were terminated.",
);
await expect(
dialog.getByRole("button", { name: "Verify & minimize" }),
).toBeEnabled();
expect(pageErrors).toEqual([]);
});

View File

@@ -1,4 +1,8 @@
/// <reference types="node" />
import { expect, test, type Page } from "@playwright/test";
import { readFile } from "node:fs/promises";
import { unzipSync } from "fflate";
async function setEditor(page: Page, testId: string, value: string) {
const content = page.getByTestId(testId).locator(".cm-content");
@@ -51,6 +55,361 @@ test("standalone ECMAScript workbench parses, explains and extracts", async ({
expect(pageErrors).toEqual([]);
});
test("PCRE2 WebAssembly executes flavour-only syntax and native substitution", async ({
page,
}) => {
const pageErrors: string[] = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
await page.goto("./");
await waitForReady(page);
await page.getByLabel("Live").uncheck();
await page.getByRole("combobox", { name: "Flavour" }).selectOption("pcre2");
await expect(
page.getByRole("spinbutton", { name: "Match steps" }),
).toHaveValue("1000000");
await setEditor(
page,
"editor-regular-expression-pattern",
"(?|(a)|(b))-(?<number>\\d+)",
);
await setEditor(page, "editor-test-text", "b-42");
const run = page.getByRole("button", { name: "Run", exact: true });
await expect(run).toBeEnabled();
await run.click();
await waitForReady(page);
await expect(page.locator(".run-status")).toContainText(
"native offsets utf8-byte",
);
await expect(page.locator(".extraction-row").first()).toContainText("b-42");
await expect(page.getByText("42", { exact: true }).first()).toBeVisible();
await page.getByRole("button", { name: "Replace", exact: true }).click();
await setEditor(page, "editor-replacement-template", "${number}:$1");
await expect(run).toBeEnabled();
await run.click();
await waitForReady(page);
await expect(
page.getByTestId("editor-replacement-output").locator(".cm-content"),
).toHaveText("42:b");
await page.getByRole("button", { name: "Quick reference" }).click();
await expect(
page.getByRole("dialog", { name: "Quick reference" }),
).toContainText("Branch-reset group");
await page.keyboard.press("Escape");
await page.getByRole("button", { name: "Capabilities" }).click();
const capabilities = page.getByRole("dialog", { name: "Capabilities" });
await expect(capabilities).toContainText("PCRE2 WebAssembly");
await expect(capabilities).toContainText("10.47 2025-10-21");
expect(pageErrors).toEqual([]);
});
test("PCRE2 trace viewer reports bounded automatic callouts in its own worker", async ({
page,
}) => {
const pageErrors: string[] = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
await page.goto("./");
await waitForReady(page);
await page.getByLabel("Live").uncheck();
await page.getByRole("combobox", { name: "Flavour" }).selectOption("pcre2");
await setEditor(page, "editor-regular-expression-pattern", "(a+)+$");
await setEditor(page, "editor-test-text", "aaaa!");
await page.getByRole("button", { name: "PCRE trace" }).click();
const dialog = page.getByRole("dialog", { name: "Execution trace" });
await expect(dialog).toBeVisible();
await expect(dialog).toContainText("reported automatic callouts");
await dialog.getByRole("spinbutton", { name: "Maximum events" }).fill("1");
await dialog
.getByRole("spinbutton", { name: "Maximum serialized bytes" })
.fill("4096");
await dialog.getByRole("button", { name: "Run trace" }).click();
await expect(dialog.locator(".trace-status")).toHaveClass(/status-ready/u, {
timeout: 15_000,
});
await expect(dialog.locator(".trace-table tbody tr")).toHaveCount(1);
await expect(dialog).toContainText("native status -37");
await expect(dialog).toContainText("one exact pcre2_match() invocation");
await expect(dialog).toContainText("configured event or serialized-byte cap");
await dialog.getByRole("button", { name: "Select trace event 1" }).click();
await expect(dialog).not.toBeVisible();
expect(pageErrors).toEqual([]);
});
test("ECMAScript analysis runs bounded cold and warm samples outside trace mode", async ({
page,
}) => {
const pageErrors: string[] = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
await page.goto("./");
await waitForReady(page);
await page.getByRole("button", { name: "Analysis" }).click();
const dialog = page.getByRole("dialog", {
name: "Performance & risk analysis",
});
await expect(dialog).toBeVisible();
await expect(dialog).toContainText("Potential risk findings");
await dialog.getByRole("tab", { name: "Benchmark" }).click();
await expect(dialog).toContainText("Trace mode is never used");
await dialog.getByRole("spinbutton", { name: "Warm-up samples" }).fill("0");
await dialog.getByRole("spinbutton", { name: "Measured samples" }).fill("2");
await dialog.getByRole("button", { name: "Run bounded benchmark" }).click();
await expect(
dialog.getByRole("heading", { name: "Benchmark result" }),
).toBeVisible({ timeout: 15_000 });
await expect(dialog).toContainText("2 measured warm samples");
await expect(dialog).toContainText("Native ECMAScript RegExp");
expect(pageErrors).toEqual([]);
});
test("serves the bundled PCRE2 runtime with WebAssembly MIME", async ({
request,
}) => {
const moduleResponse = await request.get("./engines/pcre2/pcre2.mjs");
expect(moduleResponse.ok()).toBe(true);
expect(moduleResponse.headers()["content-type"]).toContain("text/javascript");
const response = await request.get("./engines/pcre2/pcre2.wasm");
expect(response.ok()).toBe(true);
expect(response.headers()["content-type"]).toContain("application/wasm");
expect((await response.body()).subarray(0, 4)).toEqual(
Buffer.from([0x00, 0x61, 0x73, 0x6d]),
);
});
test("quick reference and capabilities open as populated in-viewport dialogs", async ({
page,
}) => {
await page.goto("./");
await waitForReady(page);
await page
.getByRole("button", { name: "Quick reference", exact: true })
.click();
const referenceDialog = page.getByRole("dialog", {
name: "Quick reference",
});
await expect(referenceDialog).toBeVisible();
await expect(referenceDialog).toBeInViewport();
await expect(
referenceDialog.getByRole("button", { name: "Close quick reference" }),
).toBeFocused();
await expect(
referenceDialog.getByRole("listitem").filter({ hasText: "Literal" }),
).toBeVisible();
await referenceDialog
.getByLabel("Filter constructs")
.fill("not a reference entry");
await expect(referenceDialog.getByRole("status")).toContainText(
"No reference constructs match",
);
await page.keyboard.press("Escape");
await expect(referenceDialog).not.toBeVisible();
await expect(
page.getByRole("button", { name: "Quick reference", exact: true }),
).toHaveAttribute("aria-expanded", "false");
await page.getByRole("button", { name: "Capabilities", exact: true }).click();
const capabilitiesDialog = page.getByRole("dialog", {
name: "Capabilities",
});
await expect(capabilitiesDialog).toBeVisible();
await expect(capabilitiesDialog).toBeInViewport();
await expect(
capabilitiesDialog.getByRole("button", { name: "Close capabilities" }),
).toBeFocused();
await expect(capabilitiesDialog).toContainText("regexpp-community 4.12.2");
await expect(capabilitiesDialog).toContainText("Native ECMAScript RegExp");
await capabilitiesDialog
.getByRole("button", { name: "Close capabilities" })
.click();
await expect(capabilitiesDialog).not.toBeVisible();
});
test("corpus mode scans local text, applies exact replacements, and exports safely", async ({
page,
}) => {
await page.goto("./");
await waitForReady(page);
await page
.getByRole("button", { name: "Corpus / Apply", exact: true })
.click();
await expect(
page.getByRole("heading", { name: "Corpus documents" }),
).toBeVisible();
await expect(page.getByTestId("editor-test-text")).not.toBeVisible();
await page
.getByRole("textbox", { name: "Pasted corpus text" })
.fill("2026-07-24 alice\nnot a date");
await page.getByRole("button", { name: "Add pasted text" }).click();
await page.getByLabel("Add corpus text files").setInputFiles([
{
name: "second.txt",
mimeType: "text/plain",
buffer: Buffer.from("2025-12-31 Bérénice", "utf8"),
},
{
name: "empty.txt",
mimeType: "text/plain",
buffer: Buffer.from("no date here", "utf8"),
},
]);
await expect(
page.getByRole("list", { name: "Corpus documents" }).getByRole("listitem"),
).toHaveCount(3);
await page.getByRole("button", { name: "Scan corpus" }).click();
const results = page.getByRole("table", {
name: "Corpus document results",
});
await expect(results).toBeVisible();
await expect(results.locator("tbody tr")).toHaveCount(3);
await expect(results.locator("tbody tr").nth(0)).toContainText("complete");
await expect(results.locator("tbody tr").nth(0)).toContainText("1");
await expect(results.locator("tbody tr").nth(2)).toContainText("0");
await expect(
page.getByRole("table", {
name: "Capture summaries for pasted-text.txt",
}),
).toContainText("alice");
const reportDownload = page.waitForEvent("download");
await page.getByRole("button", { name: "Download summary JSON" }).click();
const report = await reportDownload;
expect(report.suggestedFilename()).toBe("regex-corpus-report.json");
const reportPath = await report.path();
if (!reportPath) throw new Error("Corpus report download unavailable");
const reportText = await readFile(reportPath, "utf8");
expect(reportText).not.toContain("2026-07-24 alice");
expect(JSON.parse(reportText)).toMatchObject({
schemaVersion: 1,
operation: "match",
status: "complete",
summary: { totalDocuments: 3, totalMatches: 2 },
});
await page.getByRole("radio", { name: /Apply replacement/u }).check();
await page.getByRole("button", { name: "Apply to corpus" }).click();
await expect(results.locator("tbody tr")).toHaveCount(3);
await expect(results.locator("tbody tr").first()).toContainText("complete");
const contentConfirmation = page.getByRole("checkbox", {
name: "I understand applied downloads contain corpus content",
});
await expect(
page.getByRole("button", { name: "Download applied ZIP" }),
).toBeDisabled();
await contentConfirmation.check();
await expect(
page.getByRole("heading", { name: "Replacement preview" }),
).toBeVisible();
await expect(page.locator(".corpus-output-preview pre")).toContainText(
"alice — 2026-07-24",
);
const zipDownload = page.waitForEvent("download");
await page.getByRole("button", { name: "Download applied ZIP" }).click();
const archive = await zipDownload;
expect(archive.suggestedFilename()).toBe("regex-corpus-applied.zip");
const archivePath = await archive.path();
if (!archivePath) throw new Error("Corpus ZIP download unavailable");
const files = unzipSync(await readFile(archivePath));
expect(Object.keys(files).sort()).toEqual([
"empty.replaced.txt",
"pasted-text.replaced.txt",
"second.replaced.txt",
]);
expect(new TextDecoder().decode(files["pasted-text.replaced.txt"])).toContain(
"alice — 2026-07-24",
);
await page.getByRole("button", { name: "Project", exact: true }).click();
const projectDownload = page.waitForEvent("download");
await page
.locator(".project-popover")
.getByRole("button", { name: "Export JSON" })
.click();
const projectFile = await projectDownload;
const projectPath = await projectFile.path();
if (!projectPath) throw new Error("Project download unavailable");
const projectText = await readFile(projectPath, "utf8");
expect(projectText).not.toContain("2026-07-24 alice");
expect(JSON.parse(projectText)).toMatchObject({ mode: "corpus" });
});
test("corpus independent-line mode has explicit anchor semantics and preserves separators", async ({
page,
}) => {
await page.goto("./");
await waitForReady(page);
await page.getByLabel("Live").uncheck();
await setEditor(page, "editor-regular-expression-pattern", "^.+$");
await page
.getByRole("button", { name: "Corpus / Apply", exact: true })
.click();
await page
.getByRole("textbox", { name: "Pasted corpus text" })
.fill("alpha\r\nbeta");
await page.getByRole("button", { name: "Add pasted text" }).click();
await expect(page.getByRole("button", { name: "Scan corpus" })).toBeEnabled();
await page.getByRole("button", { name: "Scan corpus" }).click();
const resultRow = page
.getByRole("table", { name: "Corpus document results" })
.locator("tbody tr")
.first();
await expect(resultRow.locator("td").nth(1)).toHaveText("0");
await page.getByRole("radio", { name: /Independent lines/u }).check();
await expect(page.getByText(/Anchors see one line at a time/u)).toBeVisible();
await page.getByRole("button", { name: "Scan corpus" }).click();
await expect(resultRow.locator("td").nth(1)).toHaveText("2");
await page.getByRole("radio", { name: /Apply replacement/u }).check();
await page
.getByRole("textbox", { name: "Replacement template" })
.fill("[$&]");
await page.getByRole("button", { name: "Apply to corpus" }).click();
await expect(page.locator(".corpus-output-preview pre")).toContainText(
"[alpha]",
);
await expect(page.locator(".corpus-output-preview pre")).toContainText(
"[beta]",
);
expect(await page.locator(".corpus-output-preview pre").textContent()).toBe(
"[alpha]\n[beta]",
);
});
test("a running corpus job can be cancelled and labels unprocessed output", async ({
page,
}) => {
await page.goto("./");
await waitForReady(page);
await page.getByLabel("Live").uncheck();
await setEditor(page, "editor-regular-expression-pattern", "^(a|aa)+$");
await page.getByLabel("Manual limit").selectOption("10000");
await page
.getByRole("button", { name: "Corpus / Apply", exact: true })
.click();
await page
.getByRole("textbox", { name: "Pasted corpus text" })
.fill(`${"a".repeat(48)}!`);
await page.getByRole("button", { name: "Add pasted text" }).click();
await expect(page.getByRole("button", { name: "Scan corpus" })).toBeEnabled();
await page.getByRole("button", { name: "Scan corpus" }).click();
await page.getByRole("button", { name: "Cancel corpus run" }).click();
await expect(
page.getByRole("table", { name: "Corpus document results" }),
).toContainText("cancelled", { timeout: 15_000 });
await expect(
page.getByRole("button", { name: "Download summary JSON" }),
).toBeEnabled();
});
test("editing reports malformed syntax and recovers with exact matches", async ({
page,
}) => {

View File

@@ -0,0 +1,348 @@
// @vitest-environment node
import path from "node:path";
import { pathToFileURL } from "node:url";
import { beforeAll, describe, expect, it } from "vitest";
import {
Pcre2EngineAdapter,
PCRE2_ENGINE_VERSION,
} from "../../src/regex/execution/adapters/pcre2/Pcre2EngineAdapter";
import { Pcre2TraceAdapter } from "../../src/regex/execution/adapters/pcre2/Pcre2TraceAdapter";
import type { Pcre2ModuleFactory } from "../../src/regex/execution/adapters/pcre2/pcre2-module";
import type {
RegexExecutionRequest,
RegexReplacementRequest,
} from "../../src/regex/model/match";
const pack = path.resolve("public/engines/pcre2");
let adapter: Pcre2EngineAdapter;
let traceAdapter: Pcre2TraceAdapter;
function request(
pattern: string,
subject: string,
overrides: Partial<RegexExecutionRequest> = {},
): RegexExecutionRequest {
return {
flavour: "pcre2",
flavourVersion: "PCRE2 10.47 8-bit WebAssembly",
pattern,
flags: ["g"],
options: {
matchLimit: 1_000_000,
depthLimit: 1_000,
heapLimitKib: 32_768,
},
subject,
captureMetadata: [],
scanAll: false,
maximumMatches: 100,
maximumCaptureRows: 1_000,
...overrides,
};
}
beforeAll(async () => {
const imported = (await import(
`${pathToFileURL(path.join(pack, "pcre2.mjs")).href}?conformance`
)) as { default: Pcre2ModuleFactory };
const module = await imported.default({
locateFile: (name) => path.join(pack, name),
print: () => undefined,
printErr: () => undefined,
});
adapter = new Pcre2EngineAdapter(module, "Node.js WebAssembly conformance");
traceAdapter = new Pcre2TraceAdapter(
module,
"Node.js WebAssembly trace conformance",
);
await adapter.load();
await traceAdapter.load();
});
describe("pinned PCRE2 10.47 WebAssembly conformance", () => {
it("loads the exact production engine identity", async () => {
await expect(adapter.load()).resolves.toEqual(
expect.objectContaining({
flavour: "pcre2",
engineName: "PCRE2 WebAssembly",
engineVersion: PCRE2_ENGINE_VERSION,
offsetUnit: "utf8-byte",
}),
);
});
it.each([
{
name: "branch-reset groups",
pattern: "(?|(a)|(b))-(\\d+)",
subject: "b-42",
match: "b-42",
captures: ["b", "42"],
},
{
name: "named recursion",
pattern: "(?<parentheses>\\((?:[^()]|(?&parentheses))*\\))",
subject: "x (a(b)c) y",
match: "(a(b)c)",
captures: ["(a(b)c)"],
},
{
name: "match-start reset",
pattern: "foo\\Kbar",
subject: "foobar",
match: "bar",
captures: [],
},
])("executes PCRE2-only $name", async (testCase) => {
const result = await adapter.execute(
request(testCase.pattern, testCase.subject),
);
expect(result.accepted).toBe(true);
expect(result.matches[0]?.value).toBe(testCase.match);
expect(
result.matches[0]?.captures
.filter((capture) => capture.status !== "did-not-participate")
.map((capture) => capture.value),
).toEqual(testCase.captures);
});
it("preserves duplicate names from the native name table", async () => {
const result = await adapter.execute(
request("(?<value>a)|(?<value>b)", "b", {
flags: ["J"],
}),
);
expect(result.accepted).toBe(true);
expect(result.matches[0]?.captures).toEqual([
expect.objectContaining({
groupNumber: 1,
groupName: "value",
status: "did-not-participate",
}),
expect.objectContaining({
groupNumber: 2,
groupName: "value",
value: "b",
}),
]);
});
it.each([
["i", "^abc$", "AbC", "AbC"],
["m", "^b$", "a\nb\nc", "b"],
["s", "a.b", "a\nb", "a\nb"],
["x", "a # ignored\n b", "ab", "ab"],
["U", "a.+b", "a1b2b", "a1b"],
] as const)(
"applies the native %s flag",
async (flag, pattern, subject, expected) => {
const result = await adapter.execute(
request(pattern, subject, { flags: [flag] }),
);
expect(result.accepted).toBe(true);
expect(result.matches[0]?.value).toBe(expected);
},
);
it("always uses PCRE2 UTF and Unicode-property semantics", async () => {
const result = await adapter.execute(
request("^\\w+$", "Grüße", { flags: [] }),
);
expect(result.accepted).toBe(true);
expect(result.matches[0]?.value).toBe("Grüße");
});
it("normalizes astral UTF-8 offsets and terminates global empty matches", async () => {
const result = await adapter.execute(request("(?=.)", "😀a"));
expect(result.accepted).toBe(true);
expect(result.matches.map((match) => match.range)).toEqual([
{ startUtf16: 0, endUtf16: 0 },
{ startUtf16: 2, endUtf16: 2 },
]);
expect(result.matches.map((match) => match.nativeRange)).toEqual([
{ start: 0, end: 0, unit: "utf8-byte" },
{ start: 4, end: 4, unit: "utf8-byte" },
]);
});
it("uses PCRE2's own CRLF-aware next-match progression", async () => {
const result = await adapter.execute(request("(*CRLF)(?m)^", "\r\nx"));
expect(result.accepted).toBe(true);
expect(result.matches.map((match) => match.nativeRange.start)).toEqual([
0, 2,
]);
});
it("executes native named substitution and reports exact output bytes", async () => {
const replacement: RegexReplacementRequest = {
...request("(?<word>\\p{L}+)", "Grüße 42"),
replacement: "<${word}>",
maximumOutputBytes: 1_024,
};
const result = await adapter.replace(replacement);
expect(result.execution.accepted).toBe(true);
expect(result.output).toBe("<Grüße> 42");
expect(result.outputBytes).toBe(
new TextEncoder().encode(result.output).length,
);
expect(result.truncated).toBe(false);
});
it("executes PCRE2 subject, highest-capture and mark substitutions", async () => {
const result = await adapter.replace({
...request("(*MARK:route)(a)(b)", "ab", { flags: [] }),
replacement: "$_:$+:$*MARK",
maximumOutputBytes: 1_024,
});
expect(result.execution.accepted).toBe(true);
expect(result.output).toBe("ab:b:route");
});
it("marks match and replacement bounds without returning invented completeness", async () => {
const limitedMatches = await adapter.execute(
request("\\d", "1 2 3", { maximumMatches: 2 }),
);
expect(limitedMatches.matches.map((match) => match.value)).toEqual([
"1",
"2",
]);
expect(limitedMatches.truncated).toBe(true);
expect(limitedMatches.diagnostics).toEqual([
expect.objectContaining({ code: "result-limit" }),
]);
const limitedOutput = await adapter.replace({
...request("a", "aaaa"),
replacement: "long",
maximumOutputBytes: 5,
});
expect(limitedOutput.outputTruncated).toBe(true);
expect(limitedOutput.truncated).toBe(true);
expect(
new TextEncoder().encode(limitedOutput.output).length,
).toBeLessThanOrEqual(5);
});
it("reports native compile and match-limit failures as failures", async () => {
const compile = await adapter.execute(request("é(", ""));
expect(compile.accepted).toBe(false);
expect(compile.diagnostics[0]).toEqual(
expect.objectContaining({
code: "compile-error",
range: { startUtf16: 2, endUtf16: 2 },
}),
);
const limited = await adapter.execute(
request("(a+)+$", `${"a".repeat(4_096)}!`, {
flags: [],
options: {
matchLimit: 100,
depthLimit: 100,
heapLimitKib: 1_024,
},
}),
);
expect(limited.accepted).toBe(false);
expect(limited.diagnostics[0]).toEqual(
expect.objectContaining({ code: "match-limit" }),
);
});
it("returns reported automatic callouts and only derived movement labels", async () => {
const result = await traceAdapter.trace({
flavour: "pcre2",
pattern: "(*MARK:route)a+b",
flags: [],
options: {
matchLimit: 1_000_000,
depthLimit: 1_000,
heapLimitKib: 32_768,
},
subject: "aaab",
maximumTraceEvents: 1_000,
maximumTraceBytes: 64 * 1024,
});
expect(result.accepted).toBe(true);
expect(result.matched).toBe(true);
expect(result.truncated).toBe(false);
expect(result.events.length).toBeGreaterThan(2);
expect(result.events.some((event) => event.reported.mark === "route")).toBe(
true,
);
expect(result.events[0]).toEqual(
expect.objectContaining({
eventNumber: 1,
reported: expect.objectContaining({
provenance: "reported",
calloutNumber: 255,
patternPosition: expect.objectContaining({
utf16: expect.any(Number),
nativeByte: expect.any(Number),
}),
}),
movement: expect.objectContaining({
provenance: "derived",
classification: "first-event",
}),
}),
);
expect(
result.events.every(
(event) =>
event.reported.nextPatternItem.startUtf16 <=
event.reported.nextPatternItem.endUtf16,
),
).toBe(true);
});
it("stops natively at the complete-event trace cap", async () => {
const result = await traceAdapter.trace({
flavour: "pcre2",
pattern: "(a+)+$",
flags: ["g"],
options: {
matchLimit: 1_000_000,
depthLimit: 1_000,
heapLimitKib: 32_768,
},
subject: "aaaa!",
maximumTraceEvents: 1,
maximumTraceBytes: 4_096,
});
expect(result.accepted).toBe(true);
expect(result.events).toHaveLength(1);
expect(result.totalEventCount).toBe(2);
expect(result.lastCompleteEvent).toBe(1);
expect(result.nativeMatchStatus).toBe(-37);
expect(result.matched).toBe(false);
expect(result.truncated).toBe(true);
expect(result.diagnostics).toEqual(
expect.arrayContaining([
expect.objectContaining({ code: "trace-single-invocation" }),
expect.objectContaining({ code: "trace-limit" }),
]),
);
});
it("normalizes trace compile errors to UTF-16", async () => {
const result = await traceAdapter.trace({
flavour: "pcre2",
pattern: "é(",
flags: [],
subject: "",
maximumTraceEvents: 100,
maximumTraceBytes: 4_096,
});
expect(result.accepted).toBe(false);
expect(result.diagnostics[0]).toEqual(
expect.objectContaining({
code: "compile-error",
range: { startUtf16: 2, endUtf16: 2 },
}),
);
});
});

View File

@@ -1,7 +1,8 @@
# Test fixture provenance
The 0.1.0 test suite uses only small, project-authored patterns and subjects.
They exercise public ECMAScript semantics without copying a third-party corpus.
The 0.2.0 test suite, including the historical 0.1.0 cases, uses only small,
project-authored patterns and subjects. They exercise public ECMAScript and
PCRE2 semantics without copying a third-party corpus.
- RegexLib was not copied because its pages do not present a clear
redistribution licence.