Hello";
expect(parseHtml({ html })).toMatchInlineSnapshot(`
Hello
`);
});
test("script", () => {
const html = "
Hello
";
expect(parseHtml({ html })).toMatchInlineSnapshot(`
[
Hello
,
,
]
`);
});
test("iframe", () => {
const html = '';
expect(parseHtml({ html })).toMatchInlineSnapshot(`
",
}
}
/>
`);
});
test("img has key derived from src so React remounts on src change", () => {
const html = '';
const result = parseHtml({ html }) as React.ReactElement;
expect(result.key).toBe("https://cdn.example.com/a.png-0");
});
test("multiple imgs each get distinct keys", () => {
const html =
'
';
const result = parseHtml({ html }) as React.ReactElement<{
children: React.ReactElement[];
}>;
const children = result.props.children;
expect(children[0].key).toBe("https://cdn.example.com/a.png-0");
expect(children[1].key).toBe("https://cdn.example.com/b.png-1");
});
test("img without src is left alone", () => {
const html = "";
const result = parseHtml({ html }) as React.ReactElement;
expect(result.key).toBeNull();
});
test("img with data: URI is not keyed (inline, no network fetch)", () => {
const longPayload = "A".repeat(10_000);
const html = ``;
const result = parseHtml({ html }) as React.ReactElement;
// No remount-on-src needed for inline images, so we leave the key
// unset rather than bloat it with the base64 payload.
expect(result.key).toBeNull();
});
test("img with uppercase DATA: URI is also skipped (scheme is case-insensitive)", () => {
const html = ``;
const result = parseHtml({ html }) as React.ReactElement;
expect(result.key).toBeNull();
});
test("img wrapped by data-tooltip is still keyed by src", () => {
const html =
'';
const result = parseHtml({ html }) as React.ReactElement;
// Outer Tooltip carries the src-based key so it remounts on src change,
// forcing the inner to remount as well.
expect(result.key).toBe("https://cdn.example.com/a.png-0");
});
test("img wrapped by data-marimo-doc is still keyed by src", () => {
const html =
'';
const result = parseHtml({ html }) as React.ReactElement;
expect(result.key).toBe("https://cdn.example.com/b.png-0");
});
test("checked task-list checkbox is keyed by index and checked state", () => {
const html = '
item
';
const result = parseHtml({ html }) as React.ReactElement<{
children: React.ReactElement[];
}>;
const checkbox = result.props.children[0];
expect(checkbox.key).toBe("checkbox-0-true");
});
test("unchecked task-list checkbox is keyed by index and checked state", () => {
const html = '
item
';
const result = parseHtml({ html }) as React.ReactElement<{
children: React.ReactElement[];
}>;
const checkbox = result.props.children[0];
expect(checkbox.key).toBe("checkbox-0-false");
});
test("task list: item reused at same position gets a new key when checked state flips", () => {
// Simulates editing markdown so an unchecked item now sits where a
// checked item used to be (e.g. an earlier checked item was deleted).
const before = parseHtml({
html: '
a
',
}) as React.ReactElement<{ children: React.ReactElement }>;
const after = parseHtml({
html: '
b
',
}) as React.ReactElement<{ children: React.ReactElement }>;
const beforeLi = before.props.children as React.ReactElement<{
children: React.ReactElement[];
}>;
const afterLi = after.props.children as React.ReactElement<{
children: React.ReactElement[];
}>;
const beforeKey = beforeLi.props.children[0].key;
const afterKey = afterLi.props.children[0].key;
expect(beforeKey).not.toBe(afterKey);
});
test("non-checkbox input is left alone", () => {
const html = '';
const result = parseHtml({ html }) as React.ReactElement;
expect(result.key).toBeNull();
});
test("codehilite with copy button", () => {
const html =
'
console.log("Hello");
';
const result = parseHtml({ html });
// Check that the result is wrapped in a CopyableCode component
expect(result).toMatchInlineSnapshot(`
console.log("Hello");
`);
});
test("custom tags - valid", () => {
let html = "";
expect(parseHtml({ html })).toMatchInlineSnapshot("");
html = "";
expect(parseHtml({ html })).toMatchInlineSnapshot("");
html = "";
expect(parseHtml({ html })).toMatchInlineSnapshot("");
});
test("custom tags - invalid", () => {
let html = " lorem";
expect(parseHtml({ html })).toMatchInlineSnapshot(`
[
,
" lorem",
]
`);
html = "
";
expect(parseHtml({ html })).toMatchInlineSnapshot(`
`);
html = "
<1>
";
expect(parseHtml({ html })).toMatchInlineSnapshot(`
<1>
`);
html = "
<1/>
";
expect(parseHtml({ html })).toMatchInlineSnapshot(`
<1/>
`);
html = "
";
expect(parseHtml({ html })).toMatchInlineSnapshot(`
`);
});
test("removes body tags but preserves children", () => {
const html = "
Hello
World
";
expect(parseHtml({ html })).toMatchInlineSnapshot(`
Hello
World
`);
});
test("removes nested body tags", () => {
const html = "
Content
";
expect(parseHtml({ html })).toMatchInlineSnapshot(`
Content
`);
});
test("removes html tags but preserves children", () => {
const html =
"Test
Content
";
expect(parseHtml({ html })).toMatchInlineSnapshot(`
Test
Content
`);
});
test("removes nested html tags", () => {
const html = "
Content
";
expect(parseHtml({ html })).toMatchInlineSnapshot(`
Content
`);
});
test("remove nested body in html", () => {
const html = "Content";
expect(parseHtml({ html })).toMatchInlineSnapshot(`
Content
`);
});
});
describe("replaceSrcScripts trust gate", () => {
let previousHasRunAnyCell: ExtractAtomValue;
let previousConfig: ExtractAtomValue;
let previousMode: ExtractAtomValue;
const windowWithExport = window as Window & {
__MARIMO_EXPORT_CONTEXT__?: unknown;
};
function clearTrustSignals() {
const cleared = parseUserConfig({});
store.set(hasRunAnyCellAtom, false);
store.set(userConfigAtom, {
...cleared,
runtime: { ...cleared.runtime, auto_instantiate: false },
});
store.set(initialModeAtom, "edit");
delete windowWithExport.__MARIMO_EXPORT_CONTEXT__;
}
beforeEach(() => {
previousHasRunAnyCell = store.get(hasRunAnyCellAtom);
previousConfig = store.get(userConfigAtom);
previousMode = store.get(initialModeAtom);
clearTrustSignals();
for (const s of document.head.querySelectorAll(
'script[src^="https://cdn.example.com/"]',
)) {
s.remove();
}
});
afterEach(() => {
store.set(hasRunAnyCellAtom, previousHasRunAnyCell);
store.set(userConfigAtom, previousConfig);
store.set(initialModeAtom, previousMode);
delete windowWithExport.__MARIMO_EXPORT_CONTEXT__;
for (const s of document.head.querySelectorAll(
'script[src^="https://cdn.example.com/"]',
)) {
s.remove();
}
});
test("drops ',
});
expect(
document.head.querySelector(
'script[src="https://cdn.example.com/unrun.js"]',
),
).toBeNull();
});
test("loads ',
});
expect(
document.head.querySelector(
'script[src="https://cdn.example.com/ok.js"]',
),
).not.toBeNull();
});
test("loads ',
});
expect(
document.head.querySelector(
'script[src="https://cdn.example.com/export.js"]',
),
).not.toBeNull();
});
});
describe("wrapTooltipTargets", () => {
test("data-tooltip wraps element in Tooltip component", () => {
const html = 'Hover me';
expect(parseHtml({ html })).toMatchInlineSnapshot(`
Hover me
`);
});
test("element without data-tooltip is not wrapped", () => {
const html = "No tooltip";
expect(parseHtml({ html })).toMatchInlineSnapshot(`
No tooltip
`);
});
test("data-tooltip on nested element wraps only that element", () => {
const html = '
Outer inner text
';
expect(parseHtml({ html })).toMatchInlineSnapshot(`
Outer
inner
text
`);
});
test("data-tooltip on marimo custom elements is not wrapped", () => {
const html =
'click';
expect(parseHtml({ html })).toMatchInlineSnapshot(`
click
`);
});
test("data-tooltip on non-marimo custom elements is still wrapped", () => {
const html = 'content';
expect(parseHtml({ html })).toMatchInlineSnapshot(`
content
`);
});
test("data-tooltip multiline via
entity renders ", () => {
const html =
'hover';
const result = parseHtml({ html });
expect(result).toMatchInlineSnapshot(`
Line A
Line B
Line C
}
>
hover
`);
});
test("data-tooltip multiline via literal \\n renders ", () => {
const html = 'hover';
expect(parseHtml({ html })).toMatchInlineSnapshot(`
Line A
Line B
}
>
hover
`);
});
test("data-tooltip single line still works as before", () => {
const html = 'hover';
expect(parseHtml({ html })).toMatchInlineSnapshot(`
hover
`);
});
test.each([
["CRLF", "A\r\nB", 'A B'],
[
"encoded CRLF",
"A
B",
'A B',
],
["leading newline", "\nA", "A"],
["trailing newline", "A\n", "A"],
[
"boundary newlines",
"\n\nA\nB\n\n",
'A B',
],
[
"boundary CRLF",
"
A
B
",
'A B',
],
["only newlines", "\n\n", ""],
["only CRLF", "
", ""],
[
"internal blank line",
"A\n\nB",
'A
B',
],
[
"spaces and tabs",
"\n A\t\n\tB \n",
' A\t \tB ',
],
[
"HTML text",
"<b>A</b>\nB",
'<b>A</b> B',
],
])("data-tooltip handles %s", (_, content, expected) => {
const result = parseHtml({
html: `Hover me`,
});
if (!isValidElement<{ content: ReactNode }>(result)) {
throw new Error("Expected a Tooltip element");
}
expect(renderToStaticMarkup(result.props.content)).toBe(expected);
});
test("data-tooltip with newline renders as line breaks", () => {
const html = 'Hover me';
expect(parseHtml({ html })).toMatchInlineSnapshot(`
Line one
Line two
}
>
Hover me
`);
});
test("data-tooltip with multiple newlines renders all as line breaks", () => {
const html = 'Hover me';
expect(parseHtml({ html })).toMatchInlineSnapshot(`
A
B
C
}
>
Hover me
`);
});
});
describe("parseHtml with < nad >", () => {
const html =
'thread panicked at "assertion failed: `(left == right)`"';
test("", () => {
expect(parseHtml({ html })).toMatchInlineSnapshot(`
[
"thread ",
panicked at "assertion failed: \`(left == right)\`"
,
]
`);
});
test(" sanitized", () => {
const sanitized = html.replaceAll("<", "<").replaceAll(">", ">");
expect(parseHtml({ html: sanitized })).toMatchInlineSnapshot(
`"thread panicked at "assertion failed: \`(left == right)\`""`,
);
});
});