markdown-styles
Version:
Markdown to HTML generator and multiple CSS themes for Markdown
398 lines (362 loc) • 13.2 kB
JavaScript
var assert = require('assert'),
fs = require('fs'),
pi = require('pipe-iterators'),
xtend = require('xtend'),
applyTemplate = require('../lib/apply-template'),
setOutputPath = require('../lib/set-output-path'),
md = require('markdown-stream-utils'),
fixture = require('file-fixture'),
mds = require('../');
describe('integration tests', function() {
describe('metadata tests', function() {
var layoutDir = fixture.dir({
'page.html': '"{{title}}" by {{author}}\n{{> content}}'
});
it('reads and renders metadata stored in a file', function(done) {
var dir = fixture.dir({
'foo.md': [
'title: Hello world',
'author: Anonymous',
'----',
'# Test',
'abcdef'
].join('\n')
});
var out = fixture.dirname();
mds.render({
input: dir,
output: out,
layout: layoutDir
}, function() {
assert.strictEqual(fs.readFileSync(out + '/foo.html', 'utf8'), [
'"Hello world" by Anonymous',
'<h1 id="test">Test</h1>',
'<p>abcdef</p>\n'
].join('\n'));
done();
});
});
it('reads and renders metadata stored in a file with Chinese characters and the same heading used multiple times', function(done) {
var dir = fixture.dir({
'foo.md': [
'title: 你好 世界',
'author: Anonymous',
'----',
'# 世界因我而不同',
'## 世界因我而不同',
'### 世界因我而不同',
'# 一个世界,一个梦想',
'## 一个世界,一个梦想',
'### 世界因我而不同'
].join('\n')
});
var out = fixture.dirname();
var layoutDir = fixture.dir({
'page.html': '"{{title}}" by {{author}}\n{{> toc}}{{> content}}'
});
mds.render({
input: dir,
output: out,
layout: layoutDir
}, function() {
assert.strictEqual(fs.readFileSync(out + '/foo.html', 'utf8'), [
'"你好 世界" by Anonymous',
'<ul class="nav nav-list">',
' <li class="sidebar-header-1"><a href="#世界因我而不同">世界因我而不同</a></li>',
' <li class="sidebar-header-2"><a href="#世界因我而不同-1">世界因我而不同</a></li>',
' <li class="sidebar-header-3"><a href="#世界因我而不同-2">世界因我而不同</a></li>',
' <li class="sidebar-header-1"><a href="#一个世界,一个梦想">一个世界,一个梦想</a></li>',
' <li class="sidebar-header-2"><a href="#一个世界,一个梦想-1">一个世界,一个梦想</a></li>',
' <li class="sidebar-header-3"><a href="#世界因我而不同-3">世界因我而不同</a></li>',
'</ul>',
'<h1 id="世界因我而不同">世界因我而不同</h1>',
'<h2 id="世界因我而不同-1">世界因我而不同</h2>',
'<h3 id="世界因我而不同-2">世界因我而不同</h3>',
'<h1 id="一个世界,一个梦想">一个世界,一个梦想</h1>',
'<h2 id="一个世界,一个梦想-1">一个世界,一个梦想</h2>',
'<h3 id="世界因我而不同-3">世界因我而不同</h3>\n'
].join('\n'));
done();
});
});
it('will implicitly pick up the first heading as the title if no meta title is set', function(done) {
var dir = fixture.dir({
'foo.md': [
'# Some title',
'abcdef'
].join('\n')
});
var out = fixture.dirname();
mds.render({
input: dir,
output: out,
layout: layoutDir
}, function() {
assert.strictEqual(fs.readFileSync(out + '/foo.html', 'utf8'), [
'"Some title" by ',
'<h1 id="some-title">Some title</h1>',
'<p>abcdef</p>\n'
].join('\n'));
done();
});
});
it('If there is no first heading, the file name is used (without the file extension)');
it('reads and scopes the meta.json based on the path relative to target directory', function(done) {
var dir = fixture.dir({
'meta.json': JSON.stringify({
'*': { cascade: 'value-from-*' },
foo: { cascade: 'value-from-key-foo' },
'foo/*': { cascade: 'value-from-key-foo-*' },
'abc/bar': { cascade: 'value-from-key-abc/bar' },
'abc/bar/baz/*': { cascade: 'value-from-key-abc-bar-baz-*' }
}),
'foo.md': 'file: foo.md\nbase: keep\n---\nfoo.md', // components: *, foo
'foo/bar.md': 'file: foo/bar.md\n---\nfoo/bar.md', // components: *, foo, bar
'abc/bar.md': 'file: abc/bar.md\n---\nabc/bar.md', // components: *, abc, bar
'abc/bar/baz.md': 'file: abc/bar/baz.md\n---\nabc/bar/baz.md', // components: *, abc, bar, baz
'abc/bar/baz/foo.md': 'file: abc/bar/baz/foo.md\n---\nabc/bar/baz.md' // components: *, abc, bar, baz, foo
});
var layoutDir = fixture.dir({
'page.html': '"{{file}}","{{cascade}}","{{base}}"\n{{> content}}'
});
var out = fixture.dirname();
mds.render({
input: dir,
output: out,
layout: layoutDir
}, function() {
assert.strictEqual(fs.readFileSync(out + '/foo.html', 'utf8'), [
'"foo.md","value-from-key-foo","keep"',
'<p>foo.md</p>\n'
].join('\n'));
assert.strictEqual(fs.readFileSync(out + '/foo/bar.html', 'utf8'), [
'"foo/bar.md","value-from-key-foo-*",""',
'<p>foo/bar.md</p>\n'
].join('\n'));
assert.strictEqual(fs.readFileSync(out + '/abc/bar.html', 'utf8'), [
'"abc/bar.md","value-from-key-abc/bar",""',
'<p>abc/bar.md</p>\n'
].join('\n'));
assert.strictEqual(fs.readFileSync(out + '/abc/bar/baz.html', 'utf8'), [
'"abc/bar/baz.md","value-from-*",""',
'<p>abc/bar/baz.md</p>\n'
].join('\n'));
assert.strictEqual(fs.readFileSync(out + '/abc/bar/baz/foo.html', 'utf8'), [
'"abc/bar/baz/foo.md","value-from-key-abc-bar-baz-*",""',
'<p>abc/bar/baz.md</p>\n'
].join('\n'));
done();
});
});
it('when the same header is repeated, ' +
'the header links are unique (within a particular file, but not across the render)', function(done) {
var dir = fixture.dir({
'foo.md': [
'# some heading',
'hello',
'# some heading',
].join('\n'),
'bar.md': [
'# some heading',
'hello',
'# some heading',
].join('\n')
});
var out = fixture.dirname();
var layoutDir = fixture.dir({
'page.html': '{{> content}}'
});
mds.render({
input: dir,
output: out,
layout: layoutDir
}, function() {
assert.strictEqual(fs.readFileSync(out + '/foo.html', 'utf8'), [
'<h1 id="some-heading">some heading</h1>',
'<p>hello</p>',
'<h1 id="some-heading-1">some heading</h1>',
''
].join('\n'));
assert.strictEqual(fs.readFileSync(out + '/bar.html', 'utf8'), [
'<h1 id="some-heading">some heading</h1>',
'<p>hello</p>',
'<h1 id="some-heading-1">some heading</h1>',
''
].join('\n'));
done();
});
});
});
describe('theme tests', function() {
function render(item, opts, onDone) {
pi.fromArray([
xtend({ path: '/fake/input/index.md' }, item)
])
.pipe(pi.head([
md.parseHeader(),
md.parseMd(),
md.annotateMdHeadings(),
md.highlight(),
mds.convertMd(),
setOutputPath({
input: '/fake/input',
output: '/fake/output',
'asset-path': '/fake/output/assets/'
}),
applyTemplate(opts),
pi.toArray(function(results) {
onDone(results[0].contents);
})
]));
}
it('renders {{> content}}', function(done) {
render({ contents: 'Hello world' }, { template: 'a{{> content}}b' }, function(html) {
assert.strictEqual(html, 'a<p>Hello world</p>\nb');
done();
});
});
it('renders {{title}}', function(done) {
render({ title: 'foo', contents: 'a' }, { template: 'a{{title}}b' }, function(html) {
assert.strictEqual(html, 'afoob');
done();
});
});
it('renders {{> toc}}', function(done) {
render({ contents: [
'a',
'# foo',
'b',
'## bar',
'c'
].join('\n')
}, { template: 'a{{> toc}}b' }, function(html) {
assert.strictEqual(html, [
'a<ul class="nav nav-list">',
' <li class="sidebar-header-1"><a href="#foo">foo</a></li>',
' <li class="sidebar-header-2"><a href="#bar">bar</a></li>',
'</ul>',
'b'
].join('\n'));
done();
});
});
it('renders {{asset "css/style.css"}}', function(done) {
render({ contents: 'a' }, { template: 'src="{{asset "css/style.css"}}"' }, function(html) {
assert.strictEqual(html, 'src="assets/css/style.css"');
done();
});
});
it('renders partials via {{> partialName}}', function(done) {
render({ contents: 'a' }, {
template: 'a{{> sample-partial}}b',
partials: __dirname + '/fixtures/partials'
}, function(html) {
assert.strictEqual(html, 'aSample partial\nb');
done();
});
});
it('can override base partial such as {{> toc}}', function(done) {
render({ contents: [
'a',
'# foo',
'b',
'## bar',
'c'
].join('\n') }, {
template: 'a{{> toc}}b',
partials: __dirname + '/fixtures/partials'
}, function(html) {
assert.strictEqual(html, [
'aTOC:',
' - Hello foo!',
' - Hello bar!',
'b'
].join('\n'));
done();
});
});
it('loads helpers from the helpers directory', function(done) {
render({ contents: 'a' }, {
template: 'a{{sample-helper "world"}}b',
helpers: __dirname + '/fixtures/helpers'
}, function(html) {
assert.strictEqual(html, 'aHello world!b');
done();
});
});
it('renders code with syntax highlighting', function(done) {
render({ contents: [
'a',
'# foo',
'',
'```js',
'var foo = bar;',
'```'
].join('\n') }, {
template: 'a{{> content}}b'
}, function(html) {
assert.strictEqual(html, [
'a<p>a</p>',
'<h1 id="foo"><a class="header-link" href="#foo"></a>foo</h1>',
'<pre class="hljs"><code>' +
'<span class="hljs-keyword">var</span> foo = bar;</code></pre>b'
].join('\n'));
done();
});
});
it('when the same header text is repeated, it produces ids with a number appended to them', function(done) {
render({ contents: [
'# some heading',
'hello',
'# some heading',
'world',
'# some heading',
].join('\n') }, {
template: '{{> content}}'
}, function(html) {
assert.strictEqual(html, [
'<h1 id="some-heading"><a class="header-link" href="#some-heading"></a>some heading</h1>',
'<p>hello</p>',
'<h1 id="some-heading-1"><a class="header-link" href="#some-heading-1"></a>some heading</h1>',
'<p>world</p>',
'<h1 id="some-heading-2"><a class="header-link" href="#some-heading-2"></a>some heading</h1>',
''
].join('\n'));
done();
});
});
it('replaces .md in local links but not in remote links ', function(done) {
render({
contents: [
'title: Hello world',
'author: Anonymous',
'----',
'# Test',
'[foo](./foo.md)',
'[bar](bar.md)',
'[baz](/baz.md)',
'[multi](./multi.md.md)',
'[http](http://foo.md)',
'[https](https://foo.md)',
'[ftp](ftp://foo.md)',
'[javascript](javascript://foo.md)',
'[Connections](#connections)', // Issue #45
].join('\n')
}, { template: '{{> content}}' }, function(html) {
assert.strictEqual(html, [
'<h1 id="test"><a class="header-link" href="#test"></a>Test</h1>',
'<p><a href="./foo.html">foo</a>',
'<a href="./bar.html">bar</a>',
'<a href="/baz.html">baz</a>',
'<a href="./multi.md.html">multi</a>',
'<a href="http://foo.md">http</a>',
'<a href="https://foo.md">https</a>',
'<a href="ftp://foo.md">ftp</a>',
'<a href="javascript://foo.md">javascript</a>',
'<a href="#connections">Connections</a>' + '</p>\n',
].join('\n'));
done();
});
});
});
});