Files
regex-tools/tests/browser/minimization.spec.ts

103 lines
4.2 KiB
TypeScript

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([]);
});