feat: complete advanced Sudoku workbench
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import { render } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { SafeVisualLayer } from "../../src/components/SafeVisualLayer";
|
||||
import type { SafeVisualPrimitive } from "../../src/formats";
|
||||
|
||||
const visuals: readonly SafeVisualPrimitive[] = [
|
||||
{
|
||||
type: "line",
|
||||
layer: "underlay",
|
||||
start: { kind: "coordinate", x: 0.25, y: 0.75 },
|
||||
end: { kind: "cell", cell: 5, offsetX: 0.1, offsetY: -0.1 },
|
||||
style: { stroke: "#123456", strokeWidth: 0.05, opacity: 0.75 },
|
||||
},
|
||||
{
|
||||
type: "polyline",
|
||||
layer: "underlay",
|
||||
points: [
|
||||
{ kind: "cell", cell: 0 },
|
||||
{ kind: "cell", cell: 1 },
|
||||
{ kind: "cell", cell: 5 },
|
||||
],
|
||||
style: { stroke: "#abcdef", fill: "transparent" },
|
||||
},
|
||||
{
|
||||
type: "rectangle",
|
||||
layer: "overlay",
|
||||
center: { kind: "cell", cell: 6 },
|
||||
width: 0.8,
|
||||
height: 0.6,
|
||||
cornerRadius: 0.1,
|
||||
style: { fill: "#ffeecc", stroke: "#112233" },
|
||||
},
|
||||
{
|
||||
type: "ellipse",
|
||||
layer: "overlay",
|
||||
center: { kind: "coordinate", x: 2.5, y: 2.5 },
|
||||
radiusX: 0.4,
|
||||
radiusY: 0.2,
|
||||
},
|
||||
{
|
||||
type: "circle",
|
||||
layer: "overlay",
|
||||
center: { kind: "cell", cell: 10 },
|
||||
radius: 0.25,
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
layer: "overlay",
|
||||
position: { kind: "cell", cell: 15 },
|
||||
text: "source label",
|
||||
style: { fill: "#010203", fontSize: 0.4 },
|
||||
},
|
||||
];
|
||||
|
||||
describe("SafeVisualLayer", () => {
|
||||
it("renders every canonical primitive with stable classes and grid geometry", () => {
|
||||
const { container } = render(
|
||||
<svg viewBox="0 0 4 4">
|
||||
<SafeVisualLayer size={4} visuals={visuals} layer="underlay" />
|
||||
<SafeVisualLayer size={4} visuals={visuals} layer="overlay" />
|
||||
</svg>,
|
||||
);
|
||||
|
||||
const line = container.querySelector(".source-visual--line");
|
||||
expect(line).toHaveAttribute("x1", "0.25");
|
||||
expect(line).toHaveAttribute("y1", "0.75");
|
||||
expect(line).toHaveAttribute("x2", "1.6");
|
||||
expect(line).toHaveAttribute("y2", "1.4");
|
||||
expect(line).toHaveAttribute("stroke", "#123456");
|
||||
expect(line).toHaveAttribute("stroke-width", "0.05");
|
||||
expect(container.querySelector(".source-visual--polyline")).toHaveAttribute(
|
||||
"points",
|
||||
"0.5,0.5 1.5,0.5 1.5,1.5",
|
||||
);
|
||||
expect(
|
||||
container.querySelector(".source-visual--rectangle"),
|
||||
).toHaveAttribute("rx", "0.1");
|
||||
expect(container.querySelector(".source-visual--ellipse")).toHaveAttribute(
|
||||
"rx",
|
||||
"0.4",
|
||||
);
|
||||
expect(container.querySelector(".source-visual--circle")).toHaveAttribute(
|
||||
"r",
|
||||
"0.25",
|
||||
);
|
||||
expect(container.querySelector(".source-visual--text")).toHaveTextContent(
|
||||
"source label",
|
||||
);
|
||||
expect(
|
||||
container.querySelectorAll(".safe-visual-layer--underlay > *"),
|
||||
).toHaveLength(2);
|
||||
expect(
|
||||
container.querySelectorAll(".safe-visual-layer--overlay > *"),
|
||||
).toHaveLength(4);
|
||||
});
|
||||
|
||||
it("uses a React text node and never creates executable descendants", () => {
|
||||
const malicious =
|
||||
'</text><script onload="alert(1)">x</script><image href="x">';
|
||||
const { container } = render(
|
||||
<svg viewBox="0 0 4 4">
|
||||
<SafeVisualLayer
|
||||
size={4}
|
||||
layer="overlay"
|
||||
visuals={[
|
||||
{
|
||||
type: "text",
|
||||
layer: "overlay",
|
||||
position: { kind: "cell", cell: 0 },
|
||||
text: malicious,
|
||||
style: { fill: "#000000" },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</svg>,
|
||||
);
|
||||
|
||||
expect(container.querySelector(".source-visual--text")?.textContent).toBe(
|
||||
malicious,
|
||||
);
|
||||
expect(container.querySelector("script, image")).toBeNull();
|
||||
expect(
|
||||
container.querySelector("[href], [src], [onclick], [onload]"),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user