import { assert } from 'chai' import markdownit from '../index.mjs' import forInline from 'markdown-it-for-inline' describe('API', function () { it('constructor', function () { assert.throws(function () { markdownit('bad preset') }) // options should override preset const md = markdownit('commonmark', { html: false }) assert.strictEqual(md.render(''), '

<!-- -->

\n') }) it('configure coverage', function () { const md = markdownit() // conditions coverage md.configure({}) assert.strictEqual(md.render('123'), '

123

\n') assert.throws(function () { md.configure() }) }) it('plugin', function () { let succeeded = false function plugin (slf, opts) { if (opts === 'bar') { succeeded = true } } const md = markdownit() md.use(plugin, 'foo') assert.strictEqual(succeeded, false) md.use(plugin, 'bar') assert.strictEqual(succeeded, true) }) it('highlight', function () { const md = markdownit({ highlight: function (str) { return '
==' + str + '==
' } }) assert.strictEqual(md.render('```\nhl\n```'), '
==hl\n==
\n') }) it('highlight escape by default', function () { const md = markdownit({ highlight: function () { return '' } }) assert.strictEqual(md.render('```\n&\n```'), '
&\n
\n') }) it('highlight arguments', function () { const md = markdownit({ highlight: function (str, lang, attrs) { assert.strictEqual(lang, 'a') assert.strictEqual(attrs, 'b c d') return '
==' + str + '==
' } }) assert.strictEqual(md.render('``` a b c d \nhl\n```'), '
==hl\n==
\n') }) it('force hardbreaks', function () { const md = markdownit({ breaks: true }) assert.strictEqual(md.render('a\nb'), '

a
\nb

\n') md.set({ xhtmlOut: true }) assert.strictEqual(md.render('a\nb'), '

a
\nb

\n') }) it('xhtmlOut enabled', function () { const md = markdownit({ xhtmlOut: true }) assert.strictEqual(md.render('---'), '
\n') assert.strictEqual(md.render('![]()'), '

\n') assert.strictEqual(md.render('a \\\nb'), '

a
\nb

\n') }) it('xhtmlOut disabled', function () { const md = markdownit() assert.strictEqual(md.render('---'), '
\n') assert.strictEqual(md.render('![]()'), '

\n') assert.strictEqual(md.render('a \\\nb'), '

a
\nb

\n') }) it('bulk enable/disable rules in different chains', function () { const md = markdownit() const 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']) const 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']) const 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 () { const 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 () { const md = markdownit() md.disable('emphasis') assert(md.renderInline('_foo_'), '_foo_') md.enable('emphasis') assert(md.renderInline('_foo_'), 'foo') }) it('input type check', function () { const 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 () { const 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 () { const 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 () { const md = markdownit() md.block.ruler.before('paragraph', 'custom', function (state, startLine/*, endLine, silent*/) { const 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 () { const md = markdownit() assert.strictEqual(md.render('foo\u0000bar'), '

foo\uFFFDbar

\n') }) it('Should correctly parse strings without tailing \\n', function () { const md = markdownit() assert.strictEqual(md.render('123'), '

123

\n') assert.strictEqual(md.render('123\n'), '

123

\n') assert.strictEqual(md.render(' codeblock'), '
codeblock\n
\n') assert.strictEqual(md.render(' codeblock\n'), '
codeblock\n
\n') }) it('Should quickly exit on empty string', function () { const md = markdownit() assert.strictEqual(md.render(''), '') }) it('Should parse inlines only', function () { const md = markdownit() assert.strictEqual(md.renderInline('a *b* c'), 'a b c') }) it('Renderer should have pluggable inline and block rules', function () { const md = markdownit() md.renderer.rules.em_open = function () { return '' } md.renderer.rules.em_close = function () { return '' } md.renderer.rules.paragraph_open = function () { return '' } md.renderer.rules.paragraph_close = function () { return '' } assert.strictEqual(md.render('*b*'), 'b') }) it('Zero preset should disable everything', function () { const md = markdownit('zero') assert.strictEqual(md.render('___foo___'), '

___foo___

\n') assert.strictEqual(md.renderInline('___foo___'), '___foo___') md.enable('emphasis') assert.strictEqual(md.render('___foo___'), '

foo

\n') assert.strictEqual(md.renderInline('___foo___'), 'foo') }) it('Should correctly check block termination rules when those are disabled (#13)', function () { const md = markdownit('zero') assert.strictEqual(md.render('foo\nbar'), '

foo\nbar

\n') }) it('Should render link target attr', function () { const md = markdownit() .use(forInline, 'target', 'link_open', function (tokens, idx) { tokens[idx].attrs.push(['target', '_blank']) }) assert.strictEqual(md.render('[foo](bar)'), '

foo

\n') }) it('Should normalize CR to LF', function () { const 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 () { const 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 () { const md = markdownit() assert.strictEqual(md.render('\\\uD835\uDC9C'), '

\\\uD835\uDC9C

\n') assert.strictEqual(md.render('\\\uD835x'), '

\\\uD835x

\n') assert.strictEqual(md.render('\\\uD835'), '

\\\uD835

\n') }) }) describe('Url normalization', function () { it('Should be overridable', function () { const 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'), '

TEXT

\n') assert.strictEqual(md.render('http://example.com'), '

TEXT

\n') assert.strictEqual(md.render(''), '

TEXT

\n') assert.strictEqual(md.render(''), '

TEXT

\n') assert.strictEqual(md.render('[test](http://example.com)'), '

test

\n') assert.strictEqual(md.render('![test](http://example.com)'), '

test

\n') }) }) describe('Links validation', function () { it('Override validator, disable everything', function () { const md = markdownit({ linkify: true }) md.validateLink = function () { return false } assert.strictEqual(md.render('foo@example.com'), '

foo@example.com

\n') assert.strictEqual(md.render('http://example.com'), '

http://example.com

\n') assert.strictEqual(md.render(''), '

<foo@example.com>

\n') assert.strictEqual(md.render(''), '

<http://example.com>

\n') assert.strictEqual(md.render('[test](http://example.com)'), '

[test](http://example.com)

\n') assert.strictEqual(md.render('![test](http://example.com)'), '

![test](http://example.com)

\n') }) }) describe('maxNesting', function () { it('Block parser should not nest above limit', function () { const md = markdownit({ maxNesting: 2 }) assert.strictEqual( md.render('>foo\n>>bar\n>>>baz'), '
\n

foo

\n
\n
\n' ) }) it('Inline parser should not nest above limit', function () { const md = markdownit({ maxNesting: 1 }) assert.strictEqual( md.render('[`foo`]()'), '

`foo`

\n' ) }) it('Inline nesting coverage', function () { const md = markdownit({ maxNesting: 2 }) assert.strictEqual( md.render('[[[[[[[[[[[[[[[[[[foo]()'), '

[[[[[[[[[[[[[[[[[[foo]()

\n' ) }) }) describe('smartquotes', function () { const 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\''), '

[[[foo]] (((((bar))))

\n' ) }) it('Should support nested multi-character quotes', function () { assert.strictEqual( md.render('"foo \'bar\' baz"'), '

[[[foo (((((bar)))) baz]]

\n' ) }) it('Should support multi-character quotes in different tags', function () { assert.strictEqual( md.render('"a *b \'c *d* e\' f* g"'), '

[[[a b (((((c d e)))) f g]]

\n' ) }) }) describe('Ordered list info', function () { const 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 () { let 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 () { const md = markdownit() const tokens = md.parse('```'), t = tokens[0] t.attrJoin('class', 'foo') t.attrJoin('class', 'bar') assert.strictEqual( md.renderer.render(tokens, md.options), '
\n' ) }) it('.attrSet', function () { const md = markdownit() const tokens = md.parse('```'), t = tokens[0] t.attrSet('class', 'foo') assert.strictEqual( md.renderer.render(tokens, md.options), '
\n' ) t.attrSet('class', 'bar') assert.strictEqual( md.renderer.render(tokens, md.options), '
\n' ) }) it('.attrGet', function () { const md = markdownit() const tokens = md.parse('```'), t = tokens[0] assert.strictEqual(t.attrGet('myattr'), null) t.attrSet('myattr', 'myvalue') assert.strictEqual(t.attrGet('myattr'), 'myvalue') }) })