// v0.20 rendering base: renderMarkdown unit tests. // // Covers the 10 markdown subset items + mermaid/html passthrough, plus // boundary cases: empty input, pure whitespace, unclosed fences, XSS // escaping, CJK characters. import { describe, it, expect } from 'vitest' import { renderMarkdown } from '../../src/rendering/render-md.js' describe('renderMarkdown', () => { // 1. ATX headings it('renders ATX headings h1..h6', () => { const md = '# H1\n## H2\n### H3\n#### H4\n##### H5\n###### H6' const html = renderMarkdown(md) expect(html).toContain('
hello world
') }) it('joins consecutive non-blank lines into one paragraph', () => { const html = renderMarkdown('line one\nline two') expect(html).toBe('line one\nline two
') }) it('splits paragraphs on blank lines', () => { const html = renderMarkdown('first\n\nsecond') expect(html).toContain('first
') expect(html).toContain('second
') }) // 3. bold / italic / inline code it('renders **bold** and __bold__ as ', () => { expect(renderMarkdown('a **b** c')).toContain('b') expect(renderMarkdown('a __b__ c')).toContain('b') }) it('renders *italic* and _italic_ as ', () => { expect(renderMarkdown('a *b* c')).toContain('b') expect(renderMarkdown('a _b_ c')).toContain('b') }) it('renders `inline code` as', () => {
expect(renderMarkdown('use `map` here')).toContain('map')
})
it('leaves unmatched markers as literal text', () => {
expect(renderMarkdown('a * b * c')).toBe('a * b * c
')
})
// 4. lists
it('renders unordered list with - * +', () => {
expect(renderMarkdown('- a\n- b')).toContain('- a
- b
')
expect(renderMarkdown('* a\n* b')).toContain('- a
- b
')
expect(renderMarkdown('+ a\n+ b')).toContain('- a
- b
')
})
it('renders ordered list', () => {
expect(renderMarkdown('1. a\n2. b')).toContain('- a
- b
')
})
it('separates unordered and ordered lists', () => {
const html = renderMarkdown('- a\n1. b')
expect(html).toContain('- a
')
expect(html).toContain('- b
')
})
// 5. pipe table
it('renders a pipe table with header', () => {
const md = '| Name | Age |\n| --- | --- |\n| Ada | 36 |'
const html = renderMarkdown(md)
expect(html).toContain('')
expect(html).toContain('Name ')
expect(html).toContain('Age ')
expect(html).toContain('Ada ')
expect(html).toContain('36 ')
})
it('handles escaped pipes in table cells', () => {
const md = '| a\\|b | c |\n| --- | --- |\n| d | e |'
const html = renderMarkdown(md)
expect(html).toContain('a|b ')
})
// 6. fenced code block
it('renders fenced code block with language class', () => {
const html = renderMarkdown('```ts\nconst x = 1\n```')
expect(html).toContain('')
expect(html).toContain('const x = 1')
})
it('renders fenced code block without language', () => {
const html = renderMarkdown('```\nplain\n```')
expect(html).toContain('')
expect(html).toContain('plain')
})
// 7. blockquote
it('renders a blockquote', () => {
expect(renderMarkdown('> wisdom here')).toContain('wisdom here
')
})
it('renders multi-line blockquote', () => {
const html = renderMarkdown('> line1\n> line2')
expect(html).toContain('line1\nline2
')
})
// 8. link
it('renders [text](url) as anchor', () => {
const html = renderMarkdown('[click](https://example.com)')
expect(html).toContain('click')
})
it('escapes url with ampersand', () => {
const html = renderMarkdown('[q](https://ex.com/?a=1&b=2)')
expect(html).toContain('href="https://ex.com/?a=1&b=2"')
})
// 9. thematic break
it('renders --- as
', () => {
expect(renderMarkdown('a\n\n---\n\nb')).toContain('
')
})
it('renders *** and ___ as
', () => {
expect(renderMarkdown('***')).toBe('
')
expect(renderMarkdown('___')).toBe('
')
})
// 10. mermaid passthrough
it('renders mermaid fence as with escaped content', () => {
const md = '```mermaid\ngraph TD\nA-->B\n```'
const html = renderMarkdown(md)
expect(html).toBe('graph TD\nA-->B
')
})
// 11. html passthrough
it('renders html fence as with escaped content', () => {
const md = '```html\nraw\n```'
const html = renderMarkdown(md)
expect(html).toBe('<div>raw</div>
')
})
// ---- boundary cases ----
it('returns empty string for empty input', () => {
expect(renderMarkdown('')).toBe('')
})
it('returns empty string for pure whitespace input', () => {
expect(renderMarkdown(' \n \n')).toBe('')
})
it('auto-closes unclosed code fence at EOF', () => {
const html = renderMarkdown('```ts\nconst x = 1')
expect(html).toContain('')
expect(html).toContain('const x = 1')
expect(html).toContain('
')
})
it('auto-closes unclosed mermaid fence at EOF', () => {
const html = renderMarkdown('```mermaid\ngraph TD\nA-->B')
expect(html).toContain('')
expect(html).toContain('graph TD')
})
it('does not crash on an unclosed table (separator missing)', () => {
// A line with pipes but no following separator is just a paragraph.
const html = renderMarkdown('| a | b |')
expect(html).toContain('')
})
it('renders a standalone blockquote without trailing content', () => {
const html = renderMarkdown('> quote')
expect(html).toContain('
quote
')
})
it('escapes HTML special characters in text to prevent XSS', () => {
const html = renderMarkdown('')
expect(html).toContain('<script>alert(1)</script>')
expect(html).not.toContain('