/* Copyright 2026 Marimo. All rights reserved. */ import { describe, expect, test } from "vitest"; import { sanitizeHtml } from "../sanitize"; describe("sanitizeHtml", () => { test("renders basic HTML", () => { const html = "

Hello World

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

Hello World

"`); }); test("renders nested HTML", () => { const html = "

Paragraph

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

Paragraph

Span
"`, ); }); test("removes script tags", () => { const html = "
Hello
"; expect(sanitizeHtml(html)).toMatchInlineSnapshot(`"
Hello
"`); }); test("removes inline script in onclick", () => { const html = ""; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `""`, ); }); test("removes javascript: protocol in href", () => { const html = "Link"; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"Link"`, ); }); test("removes onerror attribute", () => { const html = ''; expect(sanitizeHtml(html)).toMatchInlineSnapshot(`""`); }); test("keeps form tags but removes action attribute", () => { const html = '
'; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"
"`, ); }); test("removes iframe tags", () => { const html = ''; expect(sanitizeHtml(html)).toMatchInlineSnapshot(`""`); }); test("removes embed tags", () => { const html = ''; expect(sanitizeHtml(html)).toMatchInlineSnapshot(`""`); }); test("removes object tags", () => { const html = ''; expect(sanitizeHtml(html)).toMatchInlineSnapshot(`""`); }); test("preserves safe anchor with target=_blank", () => { const html = 'Link'; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"Link"`, ); }); test("adds target=_self to anchor without target", () => { const html = 'Link'; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"Link"`, ); }); test("preserves target=_self on anchor", () => { const html = 'Link'; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"Link"`, ); }); test("preserves target=_parent on anchor", () => { const html = 'Link'; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"Link"`, ); }); test("preserves SVG elements", () => { const html = ''; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `""`, ); }); test("removes script from SVG", () => { const html = ''; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `""`, ); }); test("preserves MathML", () => { const html = "x=2"; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"x=2"`, ); }); test("preserves custom marimo elements", () => { const html = ''; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `""`, ); }); test("preserves marimo elements with valid naming", () => { const html = ""; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `""`, ); }); test("removes invalid custom elements (not marimo-*)", () => { const html = "Content"; expect(sanitizeHtml(html)).toMatchInlineSnapshot(`"Content"`); }); test("preserves marimo elements with simple data attribute", () => { const html = 'Test'; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"Test"`, ); }); test("preserves marimo-mermaid with data-diagram attribute", () => { const html = ""; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `""`, ); }); test("keeps style tags with FORCE_BODY", () => { const html = "

Text

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

Text

"`, ); }); test("removes link tags", () => { const html = '

Text

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

Text

"`); }); test("removes meta tags", () => { const html = ''; expect(sanitizeHtml(html)).toMatchInlineSnapshot(`""`); }); test("removes base tags", () => { const html = '

Text

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

Text

"`); }); test("preserves safe HTML entities", () => { const html = "

<div> & "quotes"

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

<div> & "quotes"

"`, ); }); test("preserves data attributes", () => { const html = '
Content
'; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"
Content
"`, ); }); test("preserves aria attributes", () => { const html = ''; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `""`, ); }); test("preserves class and id attributes", () => { const html = '
Content
'; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"
Content
"`, ); }); test("removes dangerous event handlers", () => { const html = '
Text
'; expect(sanitizeHtml(html)).toMatchInlineSnapshot(`"
Text
"`); }); test("handles empty string", () => { const html = ""; expect(sanitizeHtml(html)).toMatchInlineSnapshot(`""`); }); test("handles text without tags", () => { const html = "Just plain text"; expect(sanitizeHtml(html)).toMatchInlineSnapshot(`"Just plain text"`); }); test("handles malformed HTML", () => { const html = "

Unclosed div"; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"

Unclosed div

"`, ); }); 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 = `

Title

Content

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

Title

Content

" `); }); test("keeps marquee and blink tags (not considered dangerous by DOMPurify)", () => { const html = "Scrolling textBlinking"; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"Scrolling textBlinking"`, ); }); test("preserves table structures", () => { const html = "
Cell 1Cell 2
"; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"
Cell 1Cell 2
"`, ); }); 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 = 'Click'; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"Click"`, ); }); test("preserves img with valid src", () => { const html = 'Image'; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"Image"`, ); }); 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 = 'Picture'; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"Picture"`, ); }); 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 = "

Text

"; expect(sanitizeHtml(html)).toMatchInlineSnapshot(`""`); }); test("removes xlink:href with javascript in SVG", () => { const html = 'Click'; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"Click"`, ); }); 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 = "

Safe

More safe

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

Safe

More safe

"`, ); }); 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 = "
Title
Footer
"; expect(sanitizeHtml(html)).toMatchInlineSnapshot( `"
Title
Footer
"`, ); }); 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"`); }); });