304 lines
13 KiB
TypeScript
304 lines
13 KiB
TypeScript
import { expect, test, suite, describe } from 'vitest'
|
|
|
|
import { HopDown, defaultTags } from '../dist';
|
|
import { TagCollection } from "../dist/tags";
|
|
|
|
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*', '<p><em>italic</em></p>');
|
|
assert('italic _', '_italic_', '<p><em>italic</em></p>', '*italic*');
|
|
assert('bold *', '**bold**', '<p><strong>bold</strong></p>');
|
|
assert('bold _', '__bold__', '<p><strong>bold</strong></p>', '**bold**');
|
|
assert('strikethrough', '~~strike~~', '<p><del>strike</del></p>');
|
|
assert('inline code', '`code`', '<p><code>code</code></p>');
|
|
assert('bold+italic', '***bi***', '<p><strong><em>bi</em></strong></p>');
|
|
assert('link', '[t](http://x)', '<p><a href="http://x" title="">t</a></p>');
|
|
assert('image', '', '<p><img src="src.png" alt="text" title="title"></p>');
|
|
assert('mixed', 'a **b** *c* `d`', '<p>a <strong>b</strong> <em>c</em> <code>d</code></p>');
|
|
assert('code then bold', '`a` **b**', '<p><code>a</code> <strong>b</strong></p>');
|
|
assert('nested, ummatched italic', '**i* b**', '<p><strong>i* b</strong></p>');
|
|
assert('nested, ummatched bold', '*b** i*', '<p><em>b** i</em></p>');
|
|
});
|
|
|
|
describe('block', () => {
|
|
assert('HR ***', '***', '<p><hr></p>');
|
|
assert('HR ---', '---', '<p><hr></p>', '***');
|
|
assert('HR ___', '___', '<p><hr></p>', '***');
|
|
//assert('HR ******', '******', '<p><hr></p>', '***');
|
|
assert('H1', '# h', "<p><h1 id='h'>h</h1></p>");
|
|
assert('H2', '## h', "<p><h2 id='h'>h</h2></p>");
|
|
assert('H3', '### h', "<p><h3 id='h'>h</h3></p>");
|
|
assert('H4', '#### h', "<p><h4 id='h'>h</h4></p>");
|
|
assert('H5', '##### h', "<p><h5 id='h'>h</h5></p>");
|
|
assert('H6', '###### h', "<p><h6 id='h'>h</h6></p>");
|
|
assert('H1 with nested', '# h **bold**', "<p><h1 id='h_bold'>h <strong>bold</strong></h1></p>");
|
|
assert('Blockquote+break', '> foo\n> \n> bar', "<p><blockquote>foo<br><p>bar</p></blockquote></p>");
|
|
assert('Blockquote', '> one\n> two', "<p><blockquote>one<br>two</blockquote></p>");
|
|
assert('Blockquote+Bold`', '> **foo**\n> bar', "<p><blockquote><strong>foo</strong><br>bar</blockquote></p>");
|
|
assert('fenced code', '```\none\ntwo\n```', '<p><pre>\none\ntwo\n</pre></p>');
|
|
assert('fenced code+lang', '```ts\nconst foo = "";\n```', '<p><pre class="language-ts">\nconst foo = "";\n</pre></p>');
|
|
assert('UL *', '* foo\n* bar', '<p><ul><li>foo</li><li>bar</li></ul></p>');
|
|
assert('UL -', '- foo\n- **bar**', '<p><ul><li>foo</li><li><strong>bar</strong></li></ul></p>', '* foo\n* **bar**');
|
|
assert('OL 1. 2.', '1. foo\n2. bar', '<p><ol><li>foo</li><li>bar</li></ol></p>');
|
|
assert('OL 1. 1.', '1. foo\n1. bar', '<p><ol><li>foo</li><li>bar</li></ol></p>', '1. foo\n2. bar');
|
|
assert('UL Nested', '* foo\n * bar', '<p><ul><li>foo<ul><li>bar</li></ul></li></ul></p>');
|
|
assert('OL in UL', '* foo\n 1. bar\n 2. baz', '<p><ul><li>foo<ol><li>bar</li><li>baz</li></ol></li></ul></p>');
|
|
});
|
|
|
|
describe('links and anchors', () => {
|
|
assert('link', '[foo](http://x)', '<p><a href="http://x" title="">foo</a></p>');
|
|
assert('link with title', '[foo](http://x "bar")', '<p><a href="http://x" title="bar">foo</a></p>');
|
|
assert('anchor', '[foo](#anchor)', '<p><a href="#anchor" title="">foo</a></p>');
|
|
assert(
|
|
'autolink',
|
|
'foo http://x bar',
|
|
'<p>foo <a href="http://x" title="">http://x</a> bar</p>',
|
|
'foo [http://x](http://x) bar',
|
|
);
|
|
assert(
|
|
'anchor wrapped with bold',
|
|
'**[bold](http://x)**',
|
|
'<p><strong><a href="http://x" title="">bold</a></strong></p>',
|
|
);
|
|
});
|
|
|
|
describe('tables', () => {
|
|
|
|
// basic table
|
|
var md = (
|
|
'| Header 1 | Header 2 |\n' +
|
|
'| --- | --- |\n' +
|
|
'| Cell 1 | Cell 2 |'
|
|
);
|
|
|
|
var html = (
|
|
'<p><table><thead>' +
|
|
'<tr><th>Header 1</th><th>Header 2</th></tr>' +
|
|
'</thead><tbody>' +
|
|
'<tr><td>Cell 1</td><td>Cell 2</td></tr>' +
|
|
'</tbody></table></p>'
|
|
);
|
|
assert('Table', md, html);
|
|
|
|
// table with inline formatting
|
|
var md = (
|
|
'| Header 1 | Header 2 |\n' +
|
|
'| --- | --- |\n' +
|
|
'| *italic* | **bold** |\n' +
|
|
'| `code` | |'
|
|
);
|
|
var html = (
|
|
'<p><table><thead>' +
|
|
'<tr><th>Header 1</th><th>Header 2</th></tr>' +
|
|
'</thead><tbody>' +
|
|
'<tr><td><em>italic</em></td><td><strong>bold</strong></td></tr>' +
|
|
'<tr><td><code>code</code></td><td></td></tr>' +
|
|
'</tbody></table></p>'
|
|
);
|
|
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 = (
|
|
'<p><table><thead><tr>' +
|
|
'<th class="align-center">Header 1</th>' +
|
|
'<th>Header 2</th>' +
|
|
'<th class="align-right">Header 3</th>' +
|
|
'</tr></thead><tbody><tr>' +
|
|
'<td class="align-center">Cell 1</td>' +
|
|
'<td>Cell 2</td>' +
|
|
'<td class="align-right">Cell 3</td>' +
|
|
'</tr></tbody></table></p>'
|
|
);
|
|
assert('Table alignment', md, html, expected);
|
|
|
|
});
|
|
|
|
describe('Edge Cases', () => {
|
|
assert('HTML is escaped',
|
|
'some <span>html</span>',
|
|
'<p>some <span>html</span></p>',
|
|
'some <span>html</span>'
|
|
);
|
|
assert('Bare brackets', '< foo', '<p>< foo</p>', '< foo');
|
|
assert(
|
|
'HTML Entities',
|
|
'< > & { { &unknown; foo',
|
|
'<p>< > & { { &unknown; foo</p>',
|
|
'< > & { { &unknown; foo'
|
|
);
|
|
assert('Escape sequence \\*', '\\*not italic\\*', '<p>\\*not italic\\*</p>');
|
|
assert('Escape sequence \\`', '\\`not code\\`', '<p>\\`not code\\`</p>');
|
|
|
|
assert('trailing space', '*i* ', '<p><em>i</em> </p>');
|
|
assert('no closing delim', '*', '<p>*</p>');
|
|
});
|
|
|
|
});
|
|
|
|
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', '<div><span class="delim em">*</span>i<span class="delim em">*</span></div><div> fnord</div>');
|
|
});
|
|
|
|
describe('block', () => {
|
|
assertBlock('hr ***', '***', '<hr>');
|
|
assertBlock('hr ---', '---', '<hr>');
|
|
assertBlock('hr ___', '___', '<hr>');
|
|
|
|
assertBlock('h1', '# one', '<h1 id="One">one</h1>');
|
|
assertBlock('h2', '## two', '<h2 id="Two">two</h2>');
|
|
assertBlock('h3', '### three', '<h3 id="Three">three</h3>');
|
|
assertBlock('h4', '#### four', '<h4 id="Four">four</h4>');
|
|
assertBlock('h5', '##### five', '<h5 id="Five">five</h5>');
|
|
assertBlock('h6', '###### six', '<h6 id="Six">six</h6>');
|
|
|
|
assertBlock('h1 with inline markup', '# *italic* one\nfoo', '<h1 id="Italic_One">*italic* one</h1><div>\nfoo</div>');
|
|
|
|
assertBlock(
|
|
'Fenced Code',
|
|
'```\nfoo\n```',
|
|
(
|
|
'<pre>' +
|
|
'<span class="delim pre">```</span>\n' +
|
|
'foo\n' +
|
|
'<span class="delim pre">```</span>' +
|
|
'</pre>'
|
|
)
|
|
);
|
|
assertBlock(
|
|
'Blockquote',
|
|
'> foo\n>\n> ***bar*** buz\nbaz',
|
|
(
|
|
'<blockquote>' +
|
|
'<p><span class="delim blockquote">></span>foo</p>' +
|
|
'<p><span class="delim blockquote">></span></p>' +
|
|
'<p><span class="delim blockquote">></span>' +
|
|
'<span class="delim em strong">***</span>' +
|
|
'bar' +
|
|
'<span class="delim em strong">***</span> buz</p>' +
|
|
'</blockquote><div>\nbaz</div>'
|
|
)
|
|
);
|
|
|
|
assertBlock(
|
|
'Unordered List',
|
|
'- foo\n - bar\n- **baz**\n',
|
|
(
|
|
'<ul>' +
|
|
'<li>foo<ul><li>bar</li></ul></li>' +
|
|
'<li><span class="delim strong">**</span>baz' +
|
|
'<span class="delim strong">**</span></li>' +
|
|
'</ul>'
|
|
)
|
|
);
|
|
|
|
assertBlock(
|
|
'Ordered List with Nesting',
|
|
'1. foo\n * ***bar***\n1. baz',
|
|
(
|
|
'<ol>' +
|
|
'<li>foo<ul><li>' +
|
|
'<span class="delim em strong">***</span>' +
|
|
'bar' +
|
|
'<span class="delim em strong">***</span>' +
|
|
'</li></ul></li>' +
|
|
'<li>baz</li>' +
|
|
'</ol>'
|
|
)
|
|
);
|
|
|
|
describe('tables', () => {
|
|
var md = (
|
|
'| Header 1 | Header 2 |\n' +
|
|
'| --- | ---: |\n' +
|
|
'| **Cell 1** | Cell 2 |'
|
|
);
|
|
|
|
var html = (
|
|
'<table>' +
|
|
'<thead>' +
|
|
'<tr>' +
|
|
'<th>Header 1</th>' +
|
|
'<th class="align-right">Header 2</th>' +
|
|
'</tr>' +
|
|
'</thead>' +
|
|
'<tbody>' +
|
|
'<tr>' +
|
|
'<td>' +
|
|
'<span class="delim strong">**</span>' +
|
|
'Cell 1' +
|
|
'<span class="delim strong">**</span>' +
|
|
'</td>' +
|
|
'<td class="align-right">Cell 2</td>' +
|
|
'</tr>' +
|
|
'</tbody>' +
|
|
'</table>'
|
|
);
|
|
assertBlock('Table', md, html);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe('link', () => {
|
|
assertInline('link', '[label](url)', '<a href="url" title="">label</a>');
|
|
assertInline('link', '[label](url "title")', '<a href="url" title="title">label</a>');
|
|
});
|
|
|
|
describe('image', () => {
|
|
assertInline('image', '', '<img src="url" alt="alt" title="">');
|
|
assertInline('image', '', '<img src="url" alt="alt" title="title">');
|
|
});
|
|
|
|
});
|