feat: introduce local-first SVG workbench

This commit is contained in:
2026-08-02 16:31:49 +02:00
commit 39d9802daa
97 changed files with 20702 additions and 0 deletions
+148
View File
@@ -0,0 +1,148 @@
import { expect, test } from "@playwright/test";
test("loads from a deep path and keeps the last valid projection while source is invalid", async ({
page,
}) => {
const pageErrors: string[] = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
await page.goto("/deep/nested/svg/");
await expect(page).toHaveTitle("SVG Tools");
await expect(
page.getByText("Source synchronized", { exact: true }),
).toBeVisible();
await expect(
page.getByRole("tree", { name: "SVG element structure" }),
).toBeVisible();
await expect(page.getByTitle("Sanitized SVG preview")).toHaveAttribute(
"sandbox",
"allow-scripts",
);
const treeItems = page.getByRole("treeitem");
const initialTreeCount = await treeItems.count();
expect(initialTreeCount).toBeGreaterThan(1);
await page
.frameLocator('iframe[title="Sanitized SVG preview"]')
.locator("#sun")
.click();
await expect(page.locator('[data-node-key="id:sun"]')).toHaveAttribute(
"aria-selected",
"true",
);
const source = page.locator(".cm-content");
await source.click();
await page.keyboard.press("Control+A");
// insertText bypasses CodeMirror's helpful XML close-tag completion so this
// exercise really leaves the canonical source in an incomplete state.
await page.keyboard.insertText('<svg xmlns="http://www.w3.org/2000/svg"><g>');
await expect(page.getByText("Source invalid", { exact: true })).toBeVisible();
await expect(
page.getByText("Showing the last valid canvas revision."),
).toBeVisible();
expect(await treeItems.count()).toBe(initialTreeCount);
await expect(page.getByRole("treeitem").first()).toBeVisible();
await expect(
page.getByRole("button", { name: "Download SVG" }).last(),
).toBeDisabled();
await source.click();
await page.keyboard.press("Control+z");
await expect(
page.getByText("Source synchronized", { exact: true }),
).toBeVisible();
expect(pageErrors).toEqual([]);
});
test("synchronizes tree selection, source patches, undo and redo", async ({
page,
}) => {
await page.goto("/deep/nested/svg/");
await expect(
page.getByText("Source synchronized", { exact: true }),
).toBeVisible();
await page.getByPlaceholder("Filter elements…").fill("path");
const pathRow = page.getByRole("treeitem").filter({ hasText: "path" }).last();
await pathRow.click();
await expect(pathRow).toHaveAttribute("aria-selected", "true");
await page.getByRole("tab", { name: "Element" }).click();
const fill = page.getByLabel("Fill", { exact: true });
await fill.fill("#123456");
await fill.press("Enter");
await expect(page.locator(".cm-content")).toContainText('fill="#123456"');
await page.getByRole("button", { name: "Undo" }).click();
await expect(page.locator(".cm-content")).not.toContainText('fill="#123456"');
await page.getByRole("button", { name: "Redo" }).click();
await expect(page.locator(".cm-content")).toContainText('fill="#123456"');
});
test("connects to a valid same-origin Toolbox catalogue from the nested build", async ({
page,
}) => {
await page.goto("/deep/nested/svg/?toolbox=%2Ftoolbox.catalog.json");
await expect(
page.getByText("Source synchronized", { exact: true }),
).toBeVisible();
await expect(page.locator(".toolbox-shell")).toHaveAttribute(
"data-toolbox-context",
"connected",
);
await page.getByRole("button", { name: "Apps" }).click();
const switcher = page.getByRole("navigation", {
name: "Toolbox applications",
});
await expect(switcher).toBeVisible();
await expect(
switcher.getByRole("link", { name: "SVG Tools" }),
).toHaveAttribute("aria-current", "page");
});
test("opens hostile SVG locally without executing scripts or fetching external URLs", async ({
page,
}) => {
const forbiddenRequests: string[] = [];
let executed = false;
page.on("request", (request) => {
if (request.url().includes("invalid.example"))
forbiddenRequests.push(request.url());
});
await page.exposeFunction("svgToolsExecuted", () => {
executed = true;
});
await page.goto("/deep/nested/svg/");
const hostile = `
<!DOCTYPE svg SYSTEM "https://invalid.example/tracker.dtd">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<script>parent.svgToolsExecuted()</script>
<image href="https://invalid.example/tracker.png" width="20" height="20"/>
<rect width="20" height="20" onclick="parent.svgToolsExecuted()"/>
</svg>`;
await page.locator('input[type="file"]').evaluate((element, contents) => {
const transfer = new DataTransfer();
transfer.items.add(
new File([contents], "hostile.svg", { type: "image/svg+xml" }),
);
(element as HTMLInputElement).files = transfer.files;
element.dispatchEvent(new Event("change", { bubbles: true }));
}, hostile);
await expect(
page.getByText("Source synchronized", { exact: true }),
).toBeVisible();
await expect(
page.getByText(/blocked-script|event-handler|unsafe-url/).first(),
).toBeVisible();
const frame = page.frameLocator('iframe[title="Sanitized SVG preview"]');
await expect(frame.locator("script")).toHaveCount(0);
await expect(frame.locator("image")).not.toHaveAttribute(
"href",
/invalid\.example/u,
);
expect(executed).toBe(false);
expect(forbiddenRequests).toEqual([]);
});