Markdown parser, done right. 100% CommonMark support, extensions, syntax plugins & high speed https://markdown-it.github.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

496 lines
14 KiB

'use strict';
var assert = require('chai').assert;
var markdownit = require('../');
describe('API', function () {
it('constructor', function () {
assert.throws(function () {
markdownit('bad preset');
});
// options should override preset
var md = markdownit('commonmark', { html: false });
assert.strictEqual(md.render('<!-- -->'), '<p>&lt;!-- --&gt;</p>\n');
});
it('configure coverage', function () {
var md = markdownit();
// conditions coverage
md.configure({});
assert.strictEqual(md.render('123'), '<p>123</p>\n');
assert.throws(function () {
md.configure();
});
});
it('plugin', function () {
var succeeded = false;
function plugin(slf, opts) { if (opts === 'bar') { succeeded = true; } }
var md = markdownit();
md.use(plugin, 'foo');
assert.strictEqual(succeeded, false);
md.use(plugin, 'bar');
assert.strictEqual(succeeded, true);
});
it('highlight', function () {
var md = markdownit({
highlight: function (str) {
return '<pre><code>==' + str + '==</code></pre>';
}
});
assert.strictEqual(md.render('```\nhl\n```'), '<pre><code>==hl\n==</code></pre>\n');
});
it('highlight escape by default', function () {
var md = markdownit({
highlight: function () {
return '';
}
});
assert.strictEqual(md.render('```\n&\n```'), '<pre><code>&amp;\n</code></pre>\n');
});
it('highlight arguments', function () {
var md = markdownit({
highlight: function (str, lang, attrs) {
assert.strictEqual(lang, 'a');
assert.strictEqual(attrs, 'b c d');
return '<pre><code>==' + str + '==</code></pre>';
}
});
assert.strictEqual(md.render('``` a b c d \nhl\n```'), '<pre><code>==hl\n==</code></pre>\n');
});
it('force hardbreaks', function () {
var md = markdownit({ breaks: true });
assert.strictEqual(md.render('a\nb'), '<p>a<br>\nb</p>\n');
md.set({ xhtmlOut: true });
assert.strictEqual(md.render('a\nb'), '<p>a<br />\nb</p>\n');
});
it('xhtmlOut enabled', function () {
var md = markdownit({ xhtmlOut: true });
assert.strictEqual(md.render('---'), '<hr />\n');
assert.strictEqual(md.render('![]()'), '<p><img src="" alt="" /></p>\n');
assert.strictEqual(md.render('a \\\nb'), '<p>a <br />\nb</p>\n');
});
it('xhtmlOut disabled', function () {
var md = markdownit();
assert.strictEqual(md.render('---'), '<hr>\n');
assert.strictEqual(md.render('![]()'), '<p><img src="" alt=""></p>\n');
assert.strictEqual(md.render('a \\\nb'), '<p>a <br>\nb</p>\n');
});
it('bulk enable/disable rules in different chains', function () {
var md = markdownit();
var was = {
core: md.core.ruler.getRules('').length,
block: md.block.ruler.getRules('').length,
inline: md.inline.ruler.getRules('').length
};
// Disable 2 rule in each chain & compare result
md.disable([ 'block', 'inline', 'code', 'fence', 'emphasis', 'entity' ]);
var now = {
core: md.core.ruler.getRules('').length + 2,
block: md.block.ruler.getRules('').length + 2,
inline: md.inline.ruler.getRules('').length + 2
};
assert.deepEqual(was, now);
// Enable the same rules back
md.enable([ 'block', 'inline', 'code', 'fence', 'emphasis', 'entity' ]);
var back = {
core: md.core.ruler.getRules('').length,
block: md.block.ruler.getRules('').length,
inline: md.inline.ruler.getRules('').length
};
assert.deepEqual(was, back);
});
it('bulk enable/disable with errors control', function () {
var md = markdownit();
assert.throws(function () {
md.enable([ 'link', 'code', 'invalid' ]);
});
assert.throws(function () {
md.disable([ 'link', 'code', 'invalid' ]);
});
assert.doesNotThrow(function () {
md.enable([ 'link', 'code' ]);
});
assert.doesNotThrow(function () {
md.disable([ 'link', 'code' ]);
});
});
it('bulk enable/disable should understand strings', function () {
var md = markdownit();
md.disable('emphasis');
assert(md.renderInline('_foo_'), '_foo_');
md.enable('emphasis');
assert(md.renderInline('_foo_'), '<em>foo</em>');
});
it('input type check', function () {
var md = markdownit();
assert.throws(
function () { md.render(null); },
/Input data should be a String/
);
});
});
describe('Plugins', function () {
it('should not loop infinitely if all rules are disabled', function () {
var md = markdownit();
md.inline.ruler.enableOnly([]);
md.inline.ruler2.enableOnly([]);
md.block.ruler.enableOnly([]);
assert.throws(() => md.render(' - *foo*\n - `bar`'), /none of the block rules matched/);
});
it('should not loop infinitely if inline rule doesn\'t increment pos', function () {
var md = markdownit();
md.inline.ruler.after('text', 'custom', function (state/*, silent*/) {
if (state.src.charCodeAt(state.pos) !== 0x40/* @ */) return false;
return true;
});
assert.throws(() => md.render('foo@bar'), /inline rule didn't increment state.pos/);
assert.throws(() => md.render('[foo@bar]()'), /inline rule didn't increment state.pos/);
});
it('should not loop infinitely if block rule doesn\'t increment pos', function () {
var md = markdownit();
md.block.ruler.before('paragraph', 'custom', function (state, startLine/*, endLine, silent*/) {
var pos = state.bMarks[startLine] + state.tShift[startLine];
if (state.src.charCodeAt(pos) !== 0x40/* @ */) return false;
return true;
}, { alt: [ 'paragraph' ] });
assert.throws(() => md.render('foo\n@bar\nbaz'), /block rule didn't increment state.line/);
assert.throws(() => md.render('foo\n\n@bar\n\nbaz'), /block rule didn't increment state.line/);
});
});
describe('Misc', function () {
it('Should replace NULL characters', function () {
var md = markdownit();
assert.strictEqual(md.render('foo\u0000bar'), '<p>foo\uFFFDbar</p>\n');
});
it('Should correctly parse strings without tailing \\n', function () {
var md = markdownit();
assert.strictEqual(md.render('123'), '<p>123</p>\n');
assert.strictEqual(md.render('123\n'), '<p>123</p>\n');
assert.strictEqual(md.render(' codeblock'), '<pre><code>codeblock\n</code></pre>\n');
assert.strictEqual(md.render(' codeblock\n'), '<pre><code>codeblock\n</code></pre>\n');
});
it('Should quickly exit on empty string', function () {
var md = markdownit();
assert.strictEqual(md.render(''), '');
});
it('Should parse inlines only', function () {
var md = markdownit();
assert.strictEqual(md.renderInline('a *b* c'), 'a <em>b</em> c');
});
it('Renderer should have pluggable inline and block rules', function () {
var md = markdownit();
md.renderer.rules.em_open = function () { return '<it>'; };
md.renderer.rules.em_close = function () { return '</it>'; };
md.renderer.rules.paragraph_open = function () { return '<par>'; };
md.renderer.rules.paragraph_close = function () { return '</par>'; };
assert.strictEqual(md.render('*b*'), '<par><it>b</it></par>');
});
it('Zero preset should disable everything', function () {
var md = markdownit('zero');
assert.strictEqual(md.render('___foo___'), '<p>___foo___</p>\n');
assert.strictEqual(md.renderInline('___foo___'), '___foo___');
md.enable('emphasis');
assert.strictEqual(md.render('___foo___'), '<p><em><strong>foo</strong></em></p>\n');
assert.strictEqual(md.renderInline('___foo___'), '<em><strong>foo</strong></em>');
});
it('Should correctly check block termination rules when those are disabled (#13)', function () {
var md = markdownit('zero');
assert.strictEqual(md.render('foo\nbar'), '<p>foo\nbar</p>\n');
});
it('Should render link target attr', function () {
var md = markdownit()
.use(require('markdown-it-for-inline'), 'target', 'link_open', function (tokens, idx) {
tokens[idx].attrs.push([ 'target', '_blank' ]);
});
assert.strictEqual(md.render('[foo](bar)'), '<p><a href="bar" target="_blank">foo</a></p>\n');
});
it('Should normalize CR to LF', function () {
var md = markdownit();
assert.strictEqual(
md.render('# test\r\r - hello\r - world\r'),
md.render('# test\n\n - hello\n - world\n')
);
});
it('Should normalize CR+LF to LF', function () {
var md = markdownit();
assert.strictEqual(
md.render('# test\r\n\r\n - hello\r\n - world\r\n'),
md.render('# test\n\n - hello\n - world\n')
);
});
it('Should escape surrogate pairs (coverage)', function () {
var md = markdownit();
assert.strictEqual(md.render('\\\uD835\uDC9C'), '<p>\\\uD835\uDC9C</p>\n');
assert.strictEqual(md.render('\\\uD835x'), '<p>\\\uD835x</p>\n');
assert.strictEqual(md.render('\\\uD835'), '<p>\\\uD835</p>\n');
});
});
describe('Url normalization', function () {
it('Should be overridable', function () {
var md = markdownit({ linkify: true });
md.normalizeLink = function (url) {
assert(url.match(/example\.com/), 'wrong url passed');
return 'LINK';
};
md.normalizeLinkText = function (url) {
assert(url.match(/example\.com/), 'wrong url passed');
return 'TEXT';
};
assert.strictEqual(md.render('foo@example.com'), '<p><a href="LINK">TEXT</a></p>\n');
assert.strictEqual(md.render('http://example.com'), '<p><a href="LINK">TEXT</a></p>\n');
assert.strictEqual(md.render('<foo@example.com>'), '<p><a href="LINK">TEXT</a></p>\n');
assert.strictEqual(md.render('<http://example.com>'), '<p><a href="LINK">TEXT</a></p>\n');
assert.strictEqual(md.render('[test](http://example.com)'), '<p><a href="LINK">test</a></p>\n');
assert.strictEqual(md.render('![test](http://example.com)'), '<p><img src="LINK" alt="test"></p>\n');
});
});
describe('Links validation', function () {
it('Override validator, disable everything', function () {
var md = markdownit({ linkify: true });
md.validateLink = function () { return false; };
assert.strictEqual(md.render('foo@example.com'), '<p>foo@example.com</p>\n');
assert.strictEqual(md.render('http://example.com'), '<p>http://example.com</p>\n');
assert.strictEqual(md.render('<foo@example.com>'), '<p>&lt;foo@example.com&gt;</p>\n');
assert.strictEqual(md.render('<http://example.com>'), '<p>&lt;http://example.com&gt;</p>\n');
assert.strictEqual(md.render('[test](http://example.com)'), '<p>[test](http://example.com)</p>\n');
assert.strictEqual(md.render('![test](http://example.com)'), '<p>![test](http://example.com)</p>\n');
});
});
describe('maxNesting', function () {
it('Block parser should not nest above limit', function () {
var md = markdownit({ maxNesting: 2 });
assert.strictEqual(
md.render('>foo\n>>bar\n>>>baz'),
'<blockquote>\n<p>foo</p>\n<blockquote></blockquote>\n</blockquote>\n'
);
});
it('Inline parser should not nest above limit', function () {
var md = markdownit({ maxNesting: 1 });
assert.strictEqual(
md.render('[`foo`]()'),
'<p><a href="">`foo`</a></p>\n'
);
});
it('Inline nesting coverage', function () {
var md = markdownit({ maxNesting: 2 });
assert.strictEqual(
md.render('[[[[[[[[[[[[[[[[[[foo]()'),
'<p>[[[[[[[[[[[[[[[[[[foo]()</p>\n'
);
});
});
describe('smartquotes', function () {
var md = markdownit({
typographer: true,
// all strings have different length to make sure
// we didn't accidentally count the wrong one
quotes: [ '[[[', ']]', '(((((', '))))' ]
});
it('Should support multi-character quotes', function () {
assert.strictEqual(
md.render('"foo" \'bar\''),
'<p>[[[foo]] (((((bar))))</p>\n'
);
});
it('Should support nested multi-character quotes', function () {
assert.strictEqual(
md.render('"foo \'bar\' baz"'),
'<p>[[[foo (((((bar)))) baz]]</p>\n'
);
});
it('Should support multi-character quotes in different tags', function () {
assert.strictEqual(
md.render('"a *b \'c *d* e\' f* g"'),
'<p>[[[a <em>b (((((c <em>d</em> e)))) f</em> g]]</p>\n'
);
});
});
describe('Ordered list info', function () {
var md = markdownit();
function type_filter(tokens, type) {
return tokens.filter(function (t) { return t.type === type; });
}
it('Should mark ordered list item tokens with info', function () {
var tokens = md.parse('1. Foo\n2. Bar\n20. Fuzz');
assert.strictEqual(type_filter(tokens, 'ordered_list_open').length, 1);
tokens = type_filter(tokens, 'list_item_open');
assert.strictEqual(tokens.length, 3);
assert.strictEqual(tokens[0].info, '1');
assert.strictEqual(tokens[0].markup, '.');
assert.strictEqual(tokens[1].info, '2');
assert.strictEqual(tokens[1].markup, '.');
assert.strictEqual(tokens[2].info, '20');
assert.strictEqual(tokens[2].markup, '.');
tokens = md.parse(' 1. Foo\n2. Bar\n 20. Fuzz\n 199. Flp');
assert.strictEqual(type_filter(tokens, 'ordered_list_open').length, 1);
tokens = type_filter(tokens, 'list_item_open');
assert.strictEqual(tokens.length, 4);
assert.strictEqual(tokens[0].info, '1');
assert.strictEqual(tokens[0].markup, '.');
assert.strictEqual(tokens[1].info, '2');
assert.strictEqual(tokens[1].markup, '.');
assert.strictEqual(tokens[2].info, '20');
assert.strictEqual(tokens[2].markup, '.');
assert.strictEqual(tokens[3].info, '199');
assert.strictEqual(tokens[3].markup, '.');
});
});
describe('Token attributes', function () {
it('.attrJoin', function () {
var md = markdownit();
var tokens = md.parse('```'),
t = tokens[0];
t.attrJoin('class', 'foo');
t.attrJoin('class', 'bar');
assert.strictEqual(
md.renderer.render(tokens, md.options),
'<pre><code class="foo bar"></code></pre>\n'
);
});
it('.attrSet', function () {
var md = markdownit();
var tokens = md.parse('```'),
t = tokens[0];
t.attrSet('class', 'foo');
assert.strictEqual(
md.renderer.render(tokens, md.options),
'<pre><code class="foo"></code></pre>\n'
);
t.attrSet('class', 'bar');
assert.strictEqual(
md.renderer.render(tokens, md.options),
'<pre><code class="bar"></code></pre>\n'
);
});
it('.attrGet', function () {
var md = markdownit();
var tokens = md.parse('```'),
t = tokens[0];
assert.strictEqual(t.attrGet('myattr'), null);
t.attrSet('myattr', 'myvalue');
assert.strictEqual(t.attrGet('myattr'), 'myvalue');
});
});