marked/test/unit/marked-spec.js

454 lines
11 KiB
JavaScript
Raw Normal View History

2019-11-06 12:15:12 -06:00
const marked = require('../../src/marked.js');
2018-03-03 21:33:02 -06:00
2019-03-11 11:12:46 -05:00
describe('Test heading ID functionality', () => {
it('should add id attribute by default', () => {
2019-04-25 13:07:58 -05:00
const renderer = new marked.Renderer();
const slugger = new marked.Slugger();
const header = renderer.heading('test', 1, 'test', slugger);
2018-12-21 15:23:33 -08:00
expect(header).toBe('<h1 id="test">test</h1>\n');
2018-12-20 16:07:10 -05:00
});
2019-03-11 11:12:46 -05:00
it('should NOT add id attribute when options set false', () => {
2019-04-25 13:07:58 -05:00
const renderer = new marked.Renderer({ headerIds: false });
const header = renderer.heading('test', 1, 'test');
2018-12-21 15:23:33 -08:00
expect(header).toBe('<h1>test</h1>\n');
2018-12-20 16:07:10 -05:00
});
2018-12-21 15:23:33 -08:00
});
2018-12-20 16:07:10 -05:00
2019-03-11 11:12:46 -05:00
describe('Test slugger functionality', () => {
it('should use lowercase slug', () => {
2019-04-25 13:07:58 -05:00
const slugger = new marked.Slugger();
2018-12-21 15:23:33 -08:00
expect(slugger.slug('Test')).toBe('test');
2018-12-20 16:07:10 -05:00
});
2019-03-11 11:12:46 -05:00
it('should be unique to avoid collisions 1280', () => {
2019-04-25 13:07:58 -05:00
const slugger = new marked.Slugger();
2018-12-21 15:23:33 -08:00
expect(slugger.slug('test')).toBe('test');
expect(slugger.slug('test')).toBe('test-1');
expect(slugger.slug('test')).toBe('test-2');
2018-04-05 00:00:08 -05:00
});
2019-03-11 11:12:46 -05:00
it('should be unique when slug ends with number', () => {
2019-04-25 13:07:58 -05:00
const slugger = new marked.Slugger();
2018-12-21 15:51:05 -08:00
expect(slugger.slug('test 1')).toBe('test-1');
expect(slugger.slug('test')).toBe('test');
expect(slugger.slug('test')).toBe('test-2');
});
2019-03-11 11:12:46 -05:00
it('should be unique when slug ends with hyphen number', () => {
2019-04-25 13:07:58 -05:00
const slugger = new marked.Slugger();
2018-12-21 15:23:33 -08:00
expect(slugger.slug('foo')).toBe('foo');
expect(slugger.slug('foo')).toBe('foo-1');
expect(slugger.slug('foo 1')).toBe('foo-1-1');
expect(slugger.slug('foo-1')).toBe('foo-1-2');
expect(slugger.slug('foo')).toBe('foo-2');
});
2019-03-11 11:12:46 -05:00
it('should allow non-latin chars', () => {
2019-04-25 13:07:58 -05:00
const slugger = new marked.Slugger();
2018-12-21 15:23:33 -08:00
expect(slugger.slug('привет')).toBe('привет');
});
2019-03-11 11:12:46 -05:00
it('should remove ampersands 857', () => {
2019-04-25 13:07:58 -05:00
const slugger = new marked.Slugger();
2018-12-21 15:23:33 -08:00
expect(slugger.slug('This & That Section')).toBe('this--that-section');
});
2019-03-11 11:12:46 -05:00
it('should remove periods', () => {
2019-04-25 13:07:58 -05:00
const slugger = new marked.Slugger();
2018-12-21 15:23:33 -08:00
expect(slugger.slug('file.txt')).toBe('filetxt');
2018-04-05 00:00:08 -05:00
});
2020-04-02 00:22:40 -05:00
it('should remove html tags', () => {
const slugger = new marked.Slugger();
expect(slugger.slug('<em>html</em>')).toBe('html');
});
});
2018-05-02 09:13:58 -05:00
2019-03-11 11:12:46 -05:00
describe('Test paragraph token type', () => {
it('should use the "paragraph" type on top level', () => {
2018-05-02 09:13:58 -05:00
const md = 'A Paragraph.\n\n> A blockquote\n\n- list item\n';
const tokens = marked.lexer(md);
expect(tokens[0].type).toBe('paragraph');
2020-04-02 00:22:40 -05:00
expect(tokens[2].tokens[0].type).toBe('paragraph');
expect(tokens[3].items[0].tokens[0].type).toBe('text');
2018-05-02 09:13:58 -05:00
});
});
2019-12-05 11:45:07 -06:00
describe('changeDefaults', () => {
it('should change global defaults', () => {
const { defaults, changeDefaults } = require('../../src/defaults');
expect(defaults.test).toBeUndefined();
changeDefaults({ test: true });
expect(require('../../src/defaults').defaults.test).toBe(true);
});
});
2020-02-12 00:24:43 -06:00
describe('inlineLexer', () => {
it('should send html to renderer.html', () => {
const renderer = new marked.Renderer();
spyOn(renderer, 'html').and.callThrough();
const md = 'HTML Image: <img alt="MY IMAGE" src="example.png" />';
marked(md, { renderer });
expect(renderer.html).toHaveBeenCalledWith('<img alt="MY IMAGE" src="example.png" />');
});
});
2020-04-19 00:04:06 -05:00
describe('use extension', () => {
it('should use renderer', () => {
const extension = {
renderer: {
paragraph(text) {
return 'extension';
}
}
};
spyOn(extension.renderer, 'paragraph').and.callThrough();
marked.use(extension);
const html = marked('text');
expect(extension.renderer.paragraph).toHaveBeenCalledWith('text');
expect(html).toBe('extension');
});
it('should use tokenizer', () => {
const extension = {
tokenizer: {
paragraph(text) {
return {
type: 'paragraph',
raw: text,
text: 'extension'
};
}
}
};
spyOn(extension.tokenizer, 'paragraph').and.callThrough();
marked.use(extension);
const html = marked('text');
expect(extension.tokenizer.paragraph).toHaveBeenCalledWith('text');
expect(html).toBe('<p>extension</p>\n');
});
2020-05-12 16:46:39 -05:00
it('should use walkTokens', () => {
let walked = 0;
const extension = {
walkTokens(token) {
walked++;
}
};
marked.use(extension);
marked('text');
expect(walked).toBe(2);
});
2020-04-19 00:04:06 -05:00
it('should use options from extension', () => {
const extension = {
headerIds: false
};
marked.use(extension);
const html = marked('# heading');
expect(html).toBe('<h1>heading</h1>\n');
});
2020-05-12 16:46:39 -05:00
it('should call all walkTokens in reverse order', () => {
let walkedOnce = 0;
let walkedTwice = 0;
const extension1 = {
walkTokens(token) {
if (token.walkedOnce) {
walkedTwice++;
}
}
};
const extension2 = {
walkTokens(token) {
walkedOnce++;
token.walkedOnce = true;
}
};
marked.use(extension1);
marked.use(extension2);
marked('text');
expect(walkedOnce).toBe(2);
expect(walkedTwice).toBe(2);
});
2020-04-19 00:04:06 -05:00
it('should use last extension function and not override others', () => {
const extension1 = {
renderer: {
paragraph(text) {
return 'extension1 paragraph\n';
},
html(html) {
return 'extension1 html\n';
}
}
};
const extension2 = {
renderer: {
paragraph(text) {
return 'extension2 paragraph\n';
}
}
};
marked.use(extension1);
marked.use(extension2);
const html = marked(`
paragraph
<html />
# heading
`);
expect(html).toBe('extension2 paragraph\nextension1 html\n<h1 id="heading">heading</h1>\n');
});
it('should use previous extension when returning false', () => {
const extension1 = {
renderer: {
paragraph(text) {
if (text !== 'original') {
return 'extension1 paragraph\n';
}
return false;
}
}
};
const extension2 = {
renderer: {
paragraph(text) {
if (text !== 'extension1' && text !== 'original') {
return 'extension2 paragraph\n';
}
return false;
}
}
};
marked.use(extension1);
marked.use(extension2);
const html = marked(`
paragraph
extension1
original
`);
expect(html).toBe('extension2 paragraph\nextension1 paragraph\n<p>original</p>\n');
});
2020-04-19 00:04:06 -05:00
it('should get options with this.options', () => {
const extension = {
renderer: {
heading: () => {
return this.options ? 'arrow options\n' : 'arrow no options\n';
},
html: function() {
return this.options ? 'function options\n' : 'function no options\n';
},
paragraph() {
return this.options ? 'shorthand options\n' : 'shorthand no options\n';
}
}
};
marked.use(extension);
const html = marked(`
# heading
<html />
paragraph
`);
expect(html).toBe('arrow no options\nfunction options\nshorthand options\n');
});
});
2020-05-04 14:06:24 -05:00
describe('async highlight', () => {
let highlight, markdown;
beforeEach(() => {
highlight = jasmine.createSpy('highlight', (text, lang, callback) => {
setImmediate(() => {
callback(null, `async ${text || ''}`);
});
});
markdown = `
\`\`\`lang1
text 1
\`\`\`
> \`\`\`lang2
> text 2
> \`\`\`
- \`\`\`lang3
text 3
\`\`\`
`;
});
it('should highlight codeblocks async', (done) => {
highlight.and.callThrough();
marked(markdown, { highlight }, (err, html) => {
if (err) {
fail(err);
}
expect(html).toBe(`<pre><code class="language-lang1">async text 1</code></pre>
<blockquote>
<pre><code class="language-lang2">async text 2</code></pre>
</blockquote>
<ul>
<li><pre><code class="language-lang3">async text 3</code></pre>
</li>
</ul>
`);
done();
});
});
it('should call callback for each error in highlight', (done) => {
2020-05-21 10:23:36 -05:00
highlight.and.callFake((text, lang, callback) => {
2020-05-04 14:06:24 -05:00
callback(new Error('highlight error'));
});
let numErrors = 0;
marked(markdown, { highlight }, (err, html) => {
expect(err).toBeTruthy();
expect(html).toBeUndefined();
if (err) {
numErrors++;
}
if (numErrors === 3) {
done();
}
});
});
2020-05-21 10:23:36 -05:00
it('should highlight codeblocks when not async', (done) => {
highlight.and.callFake((text, lang, callback) => {
callback(null, `async ${text || ''}`);
});
marked(markdown, { highlight }, (err, html) => {
if (err) {
fail(err);
}
expect(html).toBe(`<pre><code class="language-lang1">async text 1</code></pre>
<blockquote>
<pre><code class="language-lang2">async text 2</code></pre>
</blockquote>
<ul>
<li><pre><code class="language-lang3">async text 3</code></pre>
</li>
</ul>
`);
done();
});
});
2020-05-04 14:06:24 -05:00
});
2020-05-12 16:21:55 -05:00
describe('walkTokens', () => {
it('should walk over every token', () => {
2020-05-04 14:06:24 -05:00
const markdown = `
paragraph
---
# heading
\`\`\`
code
\`\`\`
| a | b |
|---|---|
| 1 | 2 |
| 3 | 4 |
> blockquote
- list
<div>html</div>
[link](https://example.com)
![image](https://example.com/image.jpg)
**strong**
*em*
\`codespan\`
~~del~~
br
br
`;
const tokens = marked.lexer(markdown, { ...marked.getDefaults(), breaks: true });
const tokensSeen = [];
2020-05-12 16:21:55 -05:00
marked.walkTokens(tokens, (token) => {
2020-05-04 14:06:24 -05:00
tokensSeen.push([token.type, (token.raw || '').replace(/\n/g, '')]);
});
expect(tokensSeen).toEqual([
['paragraph', 'paragraph'],
['text', 'paragraph'],
['space', ''],
['hr', '---'],
['heading', '# heading'],
['text', 'heading'],
['code', '```code```'],
['table', '| a | b ||---|---|| 1 | 2 || 3 | 4 |'],
['text', 'a'],
['text', 'b'],
['text', '1'],
['text', '2'],
['text', '3'],
['text', '4'],
['blockquote', '> blockquote'],
['paragraph', 'blockquote'],
['text', 'blockquote'],
['list', '- list'],
['list_item', '- list'],
['text', 'list'],
['text', 'list'],
['space', ''],
['html', '<div>html</div>'],
['paragraph', '[link](https://example.com)'],
['link', '[link](https://example.com)'],
['text', 'link'],
['space', ''],
['paragraph', '![image](https://example.com/image.jpg)'],
['image', '![image](https://example.com/image.jpg)'],
['space', ''],
['paragraph', '**strong**'],
['strong', '**strong**'],
['text', 'strong'],
['space', ''],
['paragraph', '*em*'],
['em', '*em*'],
['text', 'em'],
['space', ''],
['paragraph', '`codespan`'],
['codespan', '`codespan`'],
['space', ''],
['paragraph', '~~del~~'],
['del', '~~del~~'],
['text', 'del'],
['space', ''],
['paragraph', 'brbr'],
['text', 'br'],
['br', ''],
['text', 'br']
]);
});
});