/* Copyright 2026 Marimo. All rights reserved. */
import { describe, expect, test } from "vitest";
import { sanitizeHtml } from "../sanitize";
describe("sanitizeHtml", () => {
test("renders basic HTML", () => {
const html = "
Unclosed div";
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("removes data URIs with javascript", () => {
const html = '
Link';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
Link"`,
);
});
test("preserves safe data URIs", () => {
const html = '

';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"

"`,
);
});
test("removes srcdoc attribute from iframe", () => {
const html = '
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(`""`);
});
test("handles complex nested structure", () => {
const html = `
`;
expect(sanitizeHtml(html)).toMatchInlineSnapshot(`
"
"
`);
});
test("keeps marquee and blink tags (not considered dangerous by DOMPurify)", () => {
const html = "
";
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("preserves table structures", () => {
const html = "
";
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("removes xml-stylesheet processing instructions", () => {
const html =
'
Text
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(`"
Text
"`);
});
test("preserves use element in SVG", () => {
const html = '
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("preserves SVG defs and use pattern", () => {
const html = [
'
",
].join("");
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("strips javascript: href from SVG use element", () => {
const html = '
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("strips javascript: xlink:href from SVG use element", () => {
const html = '
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("preserves external href on SVG use element", () => {
const html =
'
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("preserves external xlink:href on SVG use element", () => {
const html =
'
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("removes javascript in SVG href", () => {
const html =
'
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("preserves img with valid src", () => {
const html = '

';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"

"`,
);
});
test("handles multiple scripts interleaved", () => {
const html =
"
Text1
Text2
";
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
Text1
Text2
"`,
);
});
test("removes frameset and frame tags", () => {
const html = '
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(`""`);
});
test("handles vbscript: protocol", () => {
const html = '
Link';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
Link"`,
);
});
test("removes autofocus and onfocus from input", () => {
const html = '
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(`"
"`);
});
test("removes formaction attribute", () => {
const html = '
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("handles nested script-like content", () => {
const html = "
<script>alert(1)</script>
";
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
<script>alert(1)</script>
"`,
);
});
test("preserves valid inline styles", () => {
const html = '
Styled
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
Styled
"`,
);
});
test("keeps expression() in styles (legacy IE only)", () => {
const html = '
Text
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
Text
"`,
);
});
test("keeps moz-binding in styles (legacy Firefox only)", () => {
const html = '
Text
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
Text
"`,
);
});
test("preserves title and alt attributes", () => {
const html = '

';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"

"`,
);
});
test("handles multiple targets on links", () => {
const html = '
Link';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
Link"`,
);
});
test("removes on* attributes comprehensively", () => {
const html =
'
Text
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(`"
Text
"`);
});
test("removes SVG foreignObject", () => {
const html =
"
";
expect(sanitizeHtml(html)).toMatchInlineSnapshot(`"
"`);
});
test("removes xlink:href with javascript in SVG", () => {
const html = '
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("preserves role attributes", () => {
const html = '
Clickable
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
Clickable
"`,
);
});
test("handles HTML comments", () => {
const html = "
Text
";
expect(sanitizeHtml(html)).toMatchInlineSnapshot(`"
Text
"`);
});
test("removes conditional comments", () => {
const html = "
Text
";
expect(sanitizeHtml(html)).toMatchInlineSnapshot(`"
Text
"`);
});
test("preserves pre and code elements", () => {
const html = "
const x = 1;
";
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
const x = 1;
"`,
);
});
test("handles mixed content with scripts", () => {
const html =
"
";
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("preserves video and audio elements", () => {
const html = '
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("handles source elements in video", () => {
const html =
'
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("removes import statement in style", () => {
const html = '';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`""`,
);
});
test("handles HTML5 semantic elements", () => {
const html =
"
";
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("preserves canvas element", () => {
const html = '
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("handles details and summary elements", () => {
const html =
"
Click me
Hidden content
";
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
Click me
Hidden content
"`,
);
});
test("preserves iconify-icon custom element", () => {
const html = '
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("preserves iconify-icon with all attributes", () => {
const html =
'
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("preserves self-closing iconify-icon", () => {
const html = '
';
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
`"
"`,
);
});
test("still removes other non-marimo/non-iconify custom elements", () => {
const html = "
Content";
expect(sanitizeHtml(html)).toMatchInlineSnapshot(`"Content"`);
});
});