import { describe, expect, it } from "@effect/vitest";
import { ARTIFACT_PREVIEW_MARKUP_LIMIT, sanitizeArtifactPreviewMarkup } from "./artifact-preview";
describe("sanitizeArtifactPreviewMarkup", () => {
describe("the shape it is written against", () => {
it("keeps the structure and classes a smoke render produces", () => {
const result = sanitizeArtifactPreviewMarkup(
'
Revenue
' +
'
',
);
expect(result).toBe(
'
Revenue
' +
'
',
);
});
/**
* The app-layout pattern the `artifact-style` skill teaches — a bounded flex
* column with one scrolling region under a sticky header — is expressed
* ENTIRELY in class names. A sanitizer that dropped or rewrote them would
* leave the gallery card showing a layout the artifact does not have, so the
* classes that carry the structure are pinned here.
*/
it("keeps the app-layout classes a bounded artifact is built from", () => {
const result = sanitizeArtifactPreviewMarkup(
'
",
);
});
it("keeps React's HTML comment markers out without eating the content around them", () => {
// `renderToReadableStream` emits ``/`` around Suspense
// boundaries. They are comments, so they go; their content stays.
const result = sanitizeArtifactPreviewMarkup(
"
Loading
",
);
expect(result).toBe("
Loading
");
});
});
describe("scripts", () => {
it("removes a script element AND its body", () => {
const result = sanitizeArtifactPreviewMarkup(
'
beforeafter
',
);
expect(result).toBe("
beforeafter
");
expect(result).not.toContain("fetch");
expect(result).not.toContain("evil.test");
});
it("removes a style element AND its body", () => {
const result = sanitizeArtifactPreviewMarkup(
"
ok
",
);
expect(result).toBe("
ok
");
expect(result).not.toContain("evil.test");
});
it("does not let a nested tag close the skip early", () => {
const result = sanitizeArtifactPreviewMarkup(
"
after
",
);
expect(result).toBe("
after
");
expect(result).not.toContain("still inside");
expect(result).not.toContain("payload");
});
it("removes noscript, template and object with their contents", () => {
for (const tag of ["noscript", "template", "object"]) {
const result = sanitizeArtifactPreviewMarkup(`
");
expect(result).not.toContain("evil.test");
});
it("drops a style declaration that can fetch", () => {
const result = sanitizeArtifactPreviewMarkup(
'
x
',
);
expect(result).toBe('
x
');
expect(result).not.toContain("evil.test");
});
it("drops a url() smuggled into an allowed style property", () => {
const result = sanitizeArtifactPreviewMarkup(
'
x
',
);
expect(result).toBe('
x
');
expect(result).not.toContain("evil.test");
});
it("drops position and transform, which would escape the card", () => {
const result = sanitizeArtifactPreviewMarkup(
'
x
',
);
expect(result).toBe('
x
');
});
it("drops an svg url() paint that points anywhere but a local id", () => {
const result = sanitizeArtifactPreviewMarkup(
'',
);
expect(result).toContain(" {
const result = sanitizeArtifactPreviewMarkup(
'
ok
',
);
expect(result).toBe("
ok
");
});
it("neutralises a javascript: scheme wherever it lands", () => {
const result = sanitizeArtifactPreviewMarkup(
'
x
',
);
expect(result).not.toContain("javascript:");
});
});
describe("interactive elements", () => {
it("unwraps a button, keeping its label", () => {
const result = sanitizeArtifactPreviewMarkup("");
expect(result).toBe("
Refresh
");
});
it("drops form controls", () => {
const result = sanitizeArtifactPreviewMarkup(
'
ok
',
);
expect(result).toBe("
ok
");
expect(result).not.toContain("evil.test");
});
});
describe("ids", () => {
it("namespaces ids so a preview cannot collide with the host document", () => {
const result = sanitizeArtifactPreviewMarkup('
x
');
expect(result).toBe('
x
');
});
it("rewrites local paint references in step with the ids", () => {
const result = sanitizeArtifactPreviewMarkup(
'',
);
expect(result).toContain('id="artifact-preview-grad"');
expect(result).toContain('fill="url(#artifact-preview-grad)"');
});
});
describe("text and entities", () => {
it("keeps escaped markup escaped, and does not let it re-enter as an element", () => {
const result = sanitizeArtifactPreviewMarkup(
"
")).toBeNull();
});
it("keeps a purely geometric render that has no text at all", () => {
// A chart-only artifact is a real preview even with zero text nodes.
const result = sanitizeArtifactPreviewMarkup(
'',
);
expect(result).not.toBeNull();
expect(result).toContain("