// 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('

H1

') expect(html).toContain('

H2

') expect(html).toContain('

H3

') expect(html).toContain('

H4

') expect(html).toContain('
H5
') expect(html).toContain('
H6
') }) // 2. paragraphs + line breaks it('renders a single paragraph', () => { expect(renderMarkdown('hello world')).toBe('

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('
  1. a
  2. b
') }) it('separates unordered and ordered lists', () => { const html = renderMarkdown('- a\n1. b') expect(html).toContain('
  • a
') expect(html).toContain('
  1. 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('') expect(html).toContain('') expect(html).toContain('') expect(html).toContain('') }) it('handles escaped pipes in table cells', () => { const md = '| a\\|b | c |\n| --- | --- |\n| d | e |' const html = renderMarkdown(md) expect(html).toContain('') }) // 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\n
raw
\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('
NameAgeAda36a|b