UNPKG

markdown-styles

Version:

Markdown to HTML generator and multiple CSS themes for Markdown

398 lines (362 loc) 13.2 kB
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(); }); }); }); });