Skip to content

Commit 63f65f2

Browse files
sandros94farnabaz
andauthored
fix(stringify): join raw-HTML children without blank lines (#304)
Co-authored-by: Farnabaz <farnabaz@gmail.com>
1 parent daf377e commit 63f65f2

2 files changed

Lines changed: 45 additions & 2 deletions

File tree

packages/comark/src/internal/stringify/handlers/html.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ export async function html(node: ElementNode, state: State, parent?: ElementNode
7171
childrenContent.push(await state.one(child, state, node))
7272
}
7373

74+
// A blank line inside a raw-HTML element would terminate it on reparse
75+
const childSeparator = state.context.html ? state.context.blockSeparator : oneLiner ? '' : '\n'
76+
7477
let content = ''
7578
let isPrevBlock = true
7679
for (let i = 0; i < children.length; i++) {
@@ -81,13 +84,13 @@ export async function html(node: ElementNode, state: State, parent?: ElementNode
8184
(blockTags.has(String(child?.[0])) || (!inlineTags.has(String(child?.[0])) && !hasTextSibling))
8285

8386
if (i > 0 && !isPrevBlock && isBlock) {
84-
content += state.context.blockSeparator
87+
content += childSeparator
8588
}
8689
content += childContent
8790
isPrevBlock = isBlock
8891

8992
if (isBlock && i < children.length - 1) {
90-
content += state.context.blockSeparator
93+
content += childSeparator
9194
}
9295
}
9396

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { parseMarkdown } from '../src/parse'
3+
import { renderMarkdown } from '../src/render'
4+
5+
/**
6+
* A raw-HTML element round-trips when re-parsing the rendered markdown yields
7+
* the same tree. A blank line inside the element would terminate the HTML
8+
* block (or the paragraph, for inline HTML) and split the element apart.
9+
*/
10+
async function roundTrip(md: string) {
11+
const t1 = await parseMarkdown(md)
12+
const rendered = await renderMarkdown(t1)
13+
const t2 = await parseMarkdown(rendered)
14+
return { t1, t2, rendered }
15+
}
16+
17+
describe('raw HTML round-trip (parse → renderMarkdown → parse)', () => {
18+
const cases: Record<string, string> = {
19+
'picture with source and img': '<picture>\n<source srcset="a.avif">\n<img src="a.jpg">\n</picture>',
20+
'figure with img and figcaption': '<figure>\n<img src="a.jpg">\n<figcaption>cap</figcaption>\n</figure>',
21+
'inline picture inside a paragraph': 'text <picture><source srcset="a.avif"><img src="a.jpg"></picture> tail',
22+
'details with summary and p': '<details>\n<summary>sum</summary>\n<p>body</p>\n</details>',
23+
'dl with dt and dd': '<dl>\n<dt>term</dt>\n<dd>def</dd>\n</dl>',
24+
'select with two options': '<select>\n<option>a</option>\n<option>b</option>\n</select>',
25+
'audio with two sources': '<audio controls>\n<source src="a.mp3">\n<source src="a.ogg">\n</audio>',
26+
'object with two params':
27+
'<object data="x.swf">\n<param name="a" value="1">\n<param name="b" value="2">\n</object>',
28+
'nested divs': '<div>\n<div>one</div>\n<div>two</div>\n</div>',
29+
'single-child video': '<video controls>\n<source src="a.mp4" type="video/mp4">\n</video>',
30+
'mixed text and element children': '<div>\nsome text\n<figcaption>cap</figcaption>\n</div>',
31+
}
32+
33+
for (const [name, md] of Object.entries(cases)) {
34+
it(name, async () => {
35+
const { t1, t2, rendered } = await roundTrip(md)
36+
expect(rendered.trimEnd()).not.toMatch(/\n[ \t]*\n/)
37+
expect(t2.nodes).toEqual(t1.nodes)
38+
})
39+
}
40+
})

0 commit comments

Comments
 (0)