import { expect, test, suite, describe } from 'vitest' import { HopDown } from '../dist'; const hopdown = new HopDown(); suite('Markdown to HTML Round-Trips', () => { function assert(name: string, input: string, output: string, expected: string | undefined = undefined) { return test(name, () => { const html = hopdown.toHTML(input); expect(html.toLowerCase()).toBe(output.toLowerCase()); expect(hopdown.toMarkdown(html)).toBe(expected !== undefined ? expected : input); }); } describe('inline', () => { assert('italic *', '*italic*', '

italic

'); assert('italic _', '_italic_', '

italic

', '*italic*'); assert('bold *', '**bold**', '

bold

'); assert('bold _', '__bold__', '

bold

', '**bold**'); assert('strikethrough', '~~strike~~', '

strike

'); assert('inline code', '`code`', '

code

'); assert('bold+italic', '***bi***', '

bi

'); assert('link', '[t](http://x)', '

t

'); assert('image', '![text](src.png "title")', '

text

'); assert('mixed', 'a **b** *c* `d`', '

a b c d

'); assert('code then bold', '`a` **b**', '

a b

'); assert('nested, ummatched italic', '**i* b**', '

i* b

'); assert('nested, ummatched bold', '*b** i*', '

b** i

'); }); describe('block', () => { assert('HR ***', '***', '


'); assert('HR ---', '---', '


', '***'); assert('HR ___', '___', '


', '***'); //assert('HR ******', '******', '


', '***'); assert('H1', '# h', "

h

"); assert('H2', '## h', "

h

"); assert('H3', '### h', "

h

"); assert('H4', '#### h', "

h

"); assert('H5', '##### h', "

h

"); assert('H6', '###### h', "

h

"); assert('H1 with nested', '# h **bold**', "

h bold

"); assert('Blockquote+break', '> foo\n> \n> bar', "

foo

bar

"); assert('Blockquote', '> one\n> two', "

one
two

"); assert('Blockquote+Bold`', '> **foo**\n> bar', "

foo
bar

"); assert('fenced code', '```\none\ntwo\n```', '

\none\ntwo\n

'); assert('fenced code+lang', '```ts\nconst foo = "";\n```', '

\nconst foo = "";\n

'); assert('UL *', '* foo\n* bar', '

'); assert('UL -', '- foo\n- **bar**', '

', '* foo\n* **bar**'); assert('OL 1. 2.', '1. foo\n2. bar', '

  1. foo
  2. bar

'); assert('OL 1. 1.', '1. foo\n1. bar', '

  1. foo
  2. bar

', '1. foo\n2. bar'); assert('UL Nested', '* foo\n * bar', '

'); assert('OL in UL', '* foo\n 1. bar\n 2. baz', '

'); }); describe('links and anchors', () => { assert('link', '[foo](http://x)', '

foo

'); assert('link with title', '[foo](http://x "bar")', '

foo

'); assert('anchor', '[foo](#anchor)', '

foo

'); assert( 'autolink', 'foo http://x bar', '

foo http://x bar

', 'foo [http://x](http://x) bar', ); assert( 'anchor wrapped with bold', '**[bold](http://x)**', '

bold

', ); }); describe('tables', () => { // basic table var md = ( '| Header 1 | Header 2 |\n' + '| --- | --- |\n' + '| Cell 1 | Cell 2 |' ); var html = ( '

' + '' + '' + '' + '
Header 1Header 2
Cell 1Cell 2

' ); assert('Table', md, html); // table with inline formatting var md = ( '| Header 1 | Header 2 |\n' + '| --- | --- |\n' + '| *italic* | **bold** |\n' + '| `code` | |' ); var html = ( '

' + '' + '' + '' + '' + '
Header 1Header 2
italicbold
code

' ); assert('Table + inline', md, html); // normalized markdown and cell alignment var expected = ( '| Header 1 | Header 2 | Header 3 |\n' + '| :---: | --- | ---: |\n' + '| Cell 1 | Cell 2 | Cell 3 |' ); var md = ( '| Header 1 | Header 2 |Header 3 |\n' + '| :---: | :--- | ----------: |\n' + '| Cell 1 | Cell 2 |Cell 3 |' ) var html = ( '

' + '' + '' + '' + '' + '' + '' + '' + '
Header 1Header 2Header 3
Cell 1Cell 2Cell 3

' ); assert('Table alignment', md, html, expected); }); describe('Edge Cases', () => { assert('HTML is escaped', 'some html', '

some <span>html</span>

', 'some html' ); assert('Bare brackets', '< foo', '

< foo

', '< foo'); assert( 'HTML Entities', '< > & { { &unknown; foo', '

< > & { { &unknown; foo

', '< > & { { &unknown; foo' ); assert('Escape sequence \\*', '\\*not italic\\*', '

\\*not italic\\*

'); assert('Escape sequence \\`', '\\`not code\\`', '

\\`not code\\`

'); assert('trailing space', '*i* ', '

i

'); }); }); suite('Markdown to Editor Nodes Round-Trips', () => { function span(name: string, delim: string) { const s = document.createElement('span'); s.className = `delim ${name.toLowerCase()}`; s.textContent = delim; return s; } function assertInline(name: string, markdown: string, expected: string) { return test(name, () => { const node = hopdown.toEditorNode(markdown); var html = ''; for (const child of Array.from(node.children)) { html += child.outerHTML; } expect(html).toStrictEqual(expected); }); } function assertBlock(name: string, input: string, output: string) { return test(name, () => { const div = document.createElement('div'); div.appendChild(hopdown.toEditorNode(input)); expect(div.innerHTML).toStrictEqual(output); }); } describe('inline', () => { assertInline('trailing text', '*i* fnord', '
*i*
fnord
'); }); describe('block', () => { assertBlock('hr ***', '***', '
'); assertBlock('hr ---', '---', '
'); assertBlock('hr ___', '___', '
'); assertBlock('h1', '# one', '

one

'); assertBlock('h2', '## two', '

two

'); assertBlock('h3', '### three', '

three

'); assertBlock('h4', '#### four', '

four

'); assertBlock('h5', '##### five', '
five
'); assertBlock('h6', '###### six', '
six
'); assertBlock('h1 with inline markup', '# *italic* one\nfoo', '

*italic* one

\nfoo
'); assertBlock( 'Fenced Code', '```\nfoo\n```', ( '
' +
                '```\n' +
                'foo\n' +
                '```' +
                '
' ) ); assertBlock( 'Blockquote', '> foo\n>\n> ***bar*** buz\nbaz', ( '
' + '

>foo

' + '

>

' + '

>' + '***' + 'bar' + '*** buz

' + '
\nbaz
' ) ); assertBlock( 'Unordered List', '- foo\n - bar\n- **baz**\n', ( '' ) ); assertBlock( 'Ordered List with Nesting', '1. foo\n * ***bar***\n1. baz', ( '
    ' + '
  1. foo
  2. ' + '
  3. baz
  4. ' + '
' ) ); describe('tables', () => { var md = ( '| Header 1 | Header 2 |\n' + '| --- | ---: |\n' + '| **Cell 1** | Cell 2 |' ); var html = ( '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
Header 1Header 2
' + '**' + 'Cell 1' + '**' + 'Cell 2
' ); assertBlock('Table', md, html); }); }); describe('link', () => { assertInline('link', '[label](url)', 'label'); assertInline('link', '[label](url "title")', 'label'); }); describe('image', () => { assertInline('image', '![alt](url)', 'alt'); assertInline('image', '![alt](url "title")', 'alt'); }); });