Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 1 addition & 203 deletions __tests__/compilers/html-block.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,4 @@
import type { Element } from 'hast';

import { mdast, mdx, mdxish } from '../../index';

function findHTMLBlock(element: Element): Element | undefined {
if (element.tagName === 'HTMLBlock' || element.tagName === 'html-block') {
return element;
}
return element.children
.filter((child): child is Element => child.type === 'element')
.reduce<Element | undefined>((found, child) => found || findHTMLBlock(child), undefined);
}
import { mdast, mdx } from '../../index';

describe('html-block compiler', () => {
it('compiles html blocks within containers', () => {
Expand Down Expand Up @@ -51,194 +40,3 @@ const foo = () => {
expect(mdx(mdast(markdown)).trim()).toBe(expected.trim());
});
});

describe('mdxish html-block compiler', () => {
it('compiles html blocks within containers', () => {
const markdown = `
> 🚧 It compiles!
>
> <HTMLBlock>{\`
> <strong style="color: olive">Hello, World!</strong>
> \`}</HTMLBlock>
`;

const hast = mdxish(markdown.trim());
const callout = hast.children[0] as Element;

expect(callout.type).toBe('element');
expect(callout.tagName).toBe('Callout');

// Find HTMLBlock within the callout
const htmlBlock = findHTMLBlock(callout);
expect(htmlBlock).toBeDefined();
expect(htmlBlock?.tagName).toBe('html-block');
});

it('compiles html blocks preserving newlines', () => {
const markdown = `
<HTMLBlock>{\`
<pre><code>
const foo = () => {
const bar = {
baz: 'blammo'
}

return bar
}
</code></pre>
\`}</HTMLBlock>
`;

const hast = mdxish(markdown.trim());
const paragraph = hast.children[0] as Element;

expect(paragraph.type).toBe('element');
const htmlBlock = findHTMLBlock(paragraph);
expect(htmlBlock).toBeDefined();
expect(htmlBlock?.tagName).toBe('html-block');
});

it('adds newlines for readability', () => {
const markdown = '<HTMLBlock>{`<p><strong">Hello</strong>, World!</p>`}</HTMLBlock>';

const hast = mdxish(markdown);
const paragraph = hast.children[0] as Element;

expect(paragraph.type).toBe('element');
const htmlBlock = findHTMLBlock(paragraph);
expect(htmlBlock).toBeDefined();
expect(htmlBlock?.tagName).toBe('html-block');
});

it('unescapes backticks in HTML content', () => {
const markdown = '<HTMLBlock>{`<code>\\`example\\`</code>`}</HTMLBlock>';

const hast = mdxish(markdown);
const paragraph = hast.children[0] as Element;

expect(paragraph.type).toBe('element');
const htmlBlock = findHTMLBlock(paragraph);
expect(htmlBlock).toBeDefined();
expect(htmlBlock?.tagName).toBe('html-block');

// Verify that escaped backticks \` are unescaped to ` in the HTML
const htmlProp = htmlBlock?.properties?.html as string;
expect(htmlProp).toBeDefined();
expect(htmlProp).toContain('<code>`example`</code>');
expect(htmlProp).not.toContain('\\`');
});

it('passes safeMode property correctly', () => {
// Test with both JSX expression and string syntax
const markdown = '<HTMLBlock safeMode={true}>{`<script>alert("XSS")</script><p>Content</p>`}</HTMLBlock>';

const hast = mdxish(markdown);
const paragraph = hast.children[0] as Element;

expect(paragraph.type).toBe('element');
const htmlBlock = findHTMLBlock(paragraph);
expect(htmlBlock).toBeDefined();

const allProps = htmlBlock?.properties;
expect(allProps).toBeDefined();

const safeMode = allProps?.safeMode;
expect(safeMode).toBe('true');

// Verify that html property is still present (for safeMode to render as escaped text)
const htmlProp = allProps?.html as string;
expect(htmlProp).toBeDefined();
expect(htmlProp).toContain('<script>alert("XSS")</script>');
expect(htmlProp).toContain('<p>Content</p>');
});

it('should handle template literal with variables', () => {
// eslint-disable-next-line quotes
const markdown = `<HTMLBlock>{\`<code>const x = \${variable}</code>\`}</HTMLBlock>`;

const hast = mdxish(markdown);
const paragraph = hast.children[0] as Element;

expect(paragraph.type).toBe('element');
const htmlBlock = findHTMLBlock(paragraph);
expect(htmlBlock).toBeDefined();
// eslint-disable-next-line no-template-curly-in-string
expect(htmlBlock?.properties?.html).toBe('<code>const x = ${variable}</code>');
});

it('should handle nested template literals', () => {
// Use a regular string to avoid nested template literal syntax error
// The content should be: <pre>```javascript\nconst x = 1;\n```</pre>
const markdown = '<HTMLBlock>{`<pre>\\`\\`\\`javascript\\nconst x = 1;\\n\\`\\`\\`</pre>`}</HTMLBlock>';

const hast = mdxish(markdown);
const paragraph = hast.children[0] as Element;

expect(paragraph.type).toBe('element');
const htmlBlock = findHTMLBlock(paragraph);
expect(htmlBlock).toBeDefined();

// Verify that the HTML content is preserved correctly with newlines
const htmlProp = htmlBlock?.properties?.html as string;
expect(htmlProp).toBeDefined();

// The expected content should have triple backticks
expect(htmlProp).toBe('<pre>```javascript\nconst x = 1;\n```</pre>');
});

it('expands \\n only inside <pre>/<code>, not in plain text after tags', () => {
const markdown = [
'<HTMLBlock>{`',
'<pre>qerq3er \\n qerreqqe</pre>',
'<code>qerq3er \\n qerreqqe</code>',
'hello \\n world',
'`}</HTMLBlock>',
].join('\n');

const hast = mdxish(markdown);
const paragraph = hast.children[0] as Element;

expect(paragraph.type).toBe('element');
const htmlBlock = findHTMLBlock(paragraph);
expect(htmlBlock).toBeDefined();

const htmlProp = htmlBlock?.properties?.html as string;
expect(htmlProp).toBeDefined();

// Literal `\n` expands to real newlines only inside <pre> / <code>.
expect(htmlProp).toBe(
'<pre>qerq3er \n qerreqqe</pre>\n<code>qerq3er \n qerreqqe</code>\n\nhello \\n world',
);
// Must not turn the plain-text `hello \n world` into a line break between words.
expect(htmlProp).toContain('hello \\n world');
expect(htmlProp).not.toMatch(/hello \n world/); // space + LF + space (wrong)
});

it('preserves \\n escape sequences inside <script> string literals', () => {
const markdown = [
'<HTMLBlock runScripts={true}>{`',
'<div id="repro">before</div>',
'<script>alert(1);</script>',
'<script>',
' var x = "hello\\nworld";',
' document.getElementById("repro").textContent = "script ran: " + x;',
'</script>',
'`}</HTMLBlock>',
].join('\n');

const hast = mdxish(markdown);
const paragraph = hast.children[0] as Element;

expect(paragraph.type).toBe('element');
const htmlBlock = findHTMLBlock(paragraph);
expect(htmlBlock).toBeDefined();

const htmlProp = htmlBlock?.properties?.html as string;
expect(htmlProp).toBeDefined();

// The `\n` inside the JS string literal must survive as the two-byte escape
// sequence so eval() sees a well-formed JS string. A real LF here would break it.
expect(htmlProp).toContain('var x = "hello\\nworld";');
expect(htmlProp).not.toContain('var x = "hello\nworld";');
});
});
Loading