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( , ); 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 = ''; const { container } = render( , ); expect(container.querySelector(".source-visual--text")?.textContent).toBe( malicious, ); expect(container.querySelector("script, image")).toBeNull(); expect( container.querySelector("[href], [src], [onclick], [onload]"), ).toBeNull(); }); });