Switch parsers to a model

This commit is contained in:
Samy Pesse
2016-04-30 22:06:16 +02:00
parent 36b49c66c6
commit c1d53ec11f
47 changed files with 168 additions and 2576 deletions
+1 -2
View File
@@ -1,4 +1,3 @@
var _ = require('lodash');
var path = require('path');
var chokidar = require('chokidar');
@@ -20,7 +19,7 @@ function watch(dir) {
];
// Watch all parsable files
_.each(parsers.extensions, function(ext) {
parsers.extensions.forEach(function(ext) {
toWatch.push('**/*'+ext);
});
+1 -1
View File
@@ -36,7 +36,7 @@ File.prototype.exists = function() {
File.prototype.getType = function() {
var parser = this.getParser();
if (parser) {
return parser.name;
return parser.getName();
} else {
return undefined;
}
+1 -1
View File
@@ -48,7 +48,7 @@ Glossary.prototype.toText = function(parser) {
});
}
return parser.glossary.toText(entries.toJS());
return parser.renderGlossary(entries.toJS());
};
+113
View File
@@ -0,0 +1,113 @@
var Immutable = require('immutable');
var Promise = require('../utils/promise');
var Parser = Immutable.Record({
name: String(),
// List of extensions that can be processed using this parser
extensions: Immutable.List(),
// Parsing functions
readme: Function(),
langs: Function(),
summary: Function(),
glossary: Function(),
page: Function(),
inline: Function()
});
Parser.prototype.getName = function() {
return this.get('name');
};
Parser.prototype.getExtensions = function() {
return this.get('extensions');
};
// PARSE
Parser.prototype.parseReadme = function(content) {
var readme = this.get('readme');
return Promise(readme(content));
};
Parser.prototype.parseSummary = function(content) {
var summary = this.get('summary');
return Promise(summary(content));
};
Parser.prototype.parseGlossary = function(content) {
var glossary = this.get('glossary');
return Promise(glossary(content));
};
Parser.prototype.preparePage = function(content) {
var page = this.get('page');
return Promise(page.prepare(content));
};
Parser.prototype.parsePage = function(content) {
var page = this.get('page');
return Promise(page(content));
};
Parser.prototype.parseLanguages = function(content) {
var langs = this.get('langs');
return Promise(langs(content));
};
Parser.prototype.parseInline = function(content) {
var inline = this.get('inline');
return Promise(inline(content));
};
// TO TEXT
Parser.prototype.renderLanguages = function(content) {
var langs = this.get('langs');
return Promise(langs.toText(content));
};
Parser.prototype.renderSummary = function(content) {
var summary = this.get('summary');
return Promise(summary.toText(content));
};
Parser.prototype.renderGlossary = function(content) {
var glossary = this.get('glossary');
return Promise(glossary.toText(content));
};
/**
Test if this parser matches an extension
@param {String} ext
@return {Boolean}
*/
Parser.prototype.matchExtension = function(ext) {
var exts = this.getExtensions();
return exts.includes(ext.toLowerCase());
};
/**
Create a new parser using a module (gitbook-markdown, etc)
@param {String} name
@param {Array<String>} extensions
@param {Object} module
@return {Parser}
*/
Parser.create = function(name, extensions, module) {
return new Parser({
name: name,
extensions: Immutable.List(extensions),
readme: module.readme,
langs: module.langs,
summary: module.summary,
glossary: module.glossary,
page: module.page,
inline: module.inline
});
};
module.exports = Parser;
+1 -1
View File
@@ -146,7 +146,7 @@ Summary.prototype.toText = function(parser) {
});
}
return parser.summary.toText({
return parser.renderSummary({
parts: parts.toJS()
});
};
+2 -2
View File
@@ -39,7 +39,7 @@ function generatePage(output, page) {
// Escape code blocks with raw tags
.then(function(currentPage) {
return parser.page.prepare(currentPage.getContent());
return parser.preparePage(currentPage.getContent());
})
// Render templating syntax
@@ -48,7 +48,7 @@ function generatePage(output, page) {
})
// Render page using parser (markdown -> HTML)
.then(parser.page).get('content')
.then(parser.parsePage.bind(parser)).get('content')
// Post processing for templating syntax
.then(function(content) {
+14 -1
View File
@@ -24,9 +24,22 @@ function parseFile(fs, file, type) {
return fs.readAsString(filepath)
.then(function(content) {
if (type === 'readme') {
return parser.parseReadme(content);
} else if (type === 'glossary') {
return parser.parseGlossary(content);
} else if (type === 'summary') {
return parser.parseSummary(content);
} else if (type === 'langs') {
return parser.parseLanguages(content);
} else {
throw new Error('Parsing invalid type "' + type + '"');
}
})
.then(function(result) {
return [
file,
parser[type](content)
result
];
});
}
+35 -44
View File
@@ -1,69 +1,60 @@
var _ = require('lodash');
var path = require('path');
var Immutable = require('immutable');
var markdownParser = require('gitbook-markdown');
var asciidocParser = require('gitbook-asciidoc');
var Promise = require('./utils/promise');
var Parser = require('./models/parser');
// This list is ordered by priority of parsers to use
var PARSERS = [
createParser(markdownParser, {
name: 'markdown',
extensions: ['.md', '.markdown', '.mdown']
}),
createParser(asciidocParser, {
name: 'asciidoc',
extensions: ['.adoc', '.asciidoc']
})
];
var parsers = Immutable.List([
Parser.create('markdown', ['.md', '.markdown', '.mdown'], markdownParser),
Parser.create('asciidoc', ['.adoc', '.asciidoc'], asciidocParser)
]);
/**
Return a specific parser by its name
// Prepare and compose a parser
function createParser(parser, base) {
var nparser = base;
nparser.glossary = Promise.wrapfn(parser.glossary);
nparser.glossary.toText = Promise.wrapfn(parser.glossary.toText);
nparser.summary = Promise.wrapfn(parser.summary);
nparser.summary.toText = Promise.wrapfn(parser.summary.toText);
nparser.langs = Promise.wrapfn(parser.langs);
nparser.langs.toText = Promise.wrapfn(parser.langs.toText);
nparser.readme = Promise.wrapfn(parser.readme);
nparser.page = Promise.wrapfn(parser.page);
nparser.page.prepare = Promise.wrapfn(parser.page.prepare || _.identity);
nparser.inline = Promise.wrapfn(parser.inline);
return nparser;
}
// Return a specific parser
@param {String} name
@return {Parser|undefined}
*/
function getParser(name) {
return _.find(PARSERS, {
name: name
return parsers.find(function(parser) {
return parser.getName() === name;
});
}
// Return a specific parser according to an extension
/**
Return a specific parser according to an extension
@param {String} ext
@return {Parser|undefined}
*/
function getParserByExt(ext) {
return _.find(PARSERS, function(input) {
return input.name == ext || _.contains(input.extensions, ext);
return parsers.find(function(parser) {
return parser.matchExtension(ext);
});
}
// Return parser for a file
/**
Return parser for a file
@param {String} ext
@return {Parser|undefined}
*/
function getParserForFile(filename) {
return getParser(path.extname(filename));
}
// List all parsable extensions
var extensions = parsers
.map(function(parser) {
return parser.getExtensions();
})
.flatten();
module.exports = {
all: PARSERS,
extensions: _.flatten(_.pluck(PARSERS, 'extensions')),
extensions: extensions,
get: getParser,
getByExt: getParserByExt,
getForFile: getParserForFile
-36
View File
@@ -1,36 +0,0 @@
// Module API
require('./exports');
// Utilities
require('./location');
require('./paths');
// Parsing
require('./locate');
require('./config');
require('./readme');
require('./summary');
require('./glossary');
require('./langs');
require('./parse');
require('./git');
require('./plugins');
require('./template');
require('./conrefs');
// Page and HTML generation
require('./page');
// Output
require('./assets-inliner');
require('./output-json');
require('./output-website');
require('./output-ebook');
require('./structure');
require('./ignore');
// Misc
require('./init');
-75
View File
@@ -1,75 +0,0 @@
var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var cheerio = require('cheerio');
var should = require('should');
// Assertions to test if an Output has generated a file
should.Assertion.add('file', function(file, description) {
var rootFolder;
if (_.isFunction(this.obj.root)) {
rootFolder = this.obj.root();
} else {
rootFolder = this.obj;
}
this.params = {
actual: rootFolder,
operator: 'have file ' + file,
message: description
};
if (_.isFunction(this.obj.resolve)) {
file = this.obj.resolve(file);
} else {
file = path.resolve(rootFolder, file);
}
this.assert(fs.existsSync(file));
});
should.Assertion.add('html', function(rules, description) {
this.params = { actual: 'HTML string', operator: 'valid html', message: description };
var $ = cheerio.load(this.obj);
_.each(rules, function(validations, query) {
validations = _.defaults(validations || {}, {
// Select a specific element in the list of matched elements
index: null,
// Check that there is the correct count of elements
count: 1,
// Check attribute values
attributes: {},
// Trim inner text
trim: false,
// Check inner text
text: undefined
});
var $el = $(query);
// Select correct element
if (_.isNumber(validations.index)) $el = $($el.get(validations.index));
// Test number of elements
$el.length.should.be.equal(validations.count);
// Test text
if (validations.text !== undefined) {
var text = $el.text();
if (validations.trim) text = text.trim();
text.should.be.equal(validations.text);
}
// Test attributes
_.each(validations.attributes, function(value, name) {
var attr = $el.attr(name);
should(attr).be.ok();
attr.should.be.equal(value);
});
});
});
-86
View File
@@ -1,86 +0,0 @@
var cheerio = require('cheerio');
var path = require('path');
var mock = require('./mock');
var AssetsInliner = require('../lib/output/assets-inliner')();
describe('Assets Inliner Output', function() {
var output;
before(function() {
var SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100" version="1.1"><rect width="200" height="100" stroke="black" stroke-width="6" fill="green"/></svg>';
return mock.outputDefaultBook(AssetsInliner, {
'README.md': '',
// SVGs
'svg_file.md': '![image](test.svg)',
'svg_inline.md': 'This is a svg: '+SVG,
'test.svg': '<?xml version="1.0" encoding="UTF-8"?>' + SVG,
// Relative
'folder/test.md': '![image](../test.svg)',
// Remote images
'remote_png.md': '![image](https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png)',
'remote_svg.md': '![image](https://upload.wikimedia.org/wikipedia/commons/0/02/SVG_logo.svg)',
'SUMMARY.md': '* [svg inline](svg_inline.md)\n' +
'* [svg file](svg_file.md)\n' +
'* [remote png file](remote_png.md)\n' +
'* [remote svg file](remote_svg.md)\n' +
'* [relative image](folder/test.md)\n' +
'\n\n'
})
.then(function(_output) {
output = _output;
});
});
function testImageInPage(filename) {
var page = output.book.getPage(filename);
var $ = cheerio.load(page.content);
// Is there an image?
var $img = $('img');
$img.length.should.equal(1);
// Does the file exists
var src = $img.attr('src');
// Resolve the filename
src = page.resolveLocal(src);
output.should.have.file(src);
path.extname(src).should.equal('.png');
return src;
}
describe('SVG', function() {
it('should correctly convert SVG files to PNG', function() {
testImageInPage('svg_file.md');
});
it('should correctly convert inline SVG to PNG', function() {
testImageInPage('svg_inline.md');
});
});
describe('Remote Assets', function() {
it('should correctly download a PNG file', function() {
testImageInPage('remote_png.md');
});
it('should correctly download then convert a remote SVG to PNG', function() {
testImageInPage('remote_svg.md');
});
});
describe('Relative Images', function() {
it('should correctly resolve image', function() {
testImageInPage('folder/test.md');
});
});
});
-137
View File
@@ -1,137 +0,0 @@
var should = require('should');
var mock = require('./mock');
var validator = require('../lib/config/validator');
describe('Configuration', function() {
describe('Validation', function() {
it('should merge default', function() {
validator.validate({}).should.have.property('gitbook').which.equal('*');
});
it('should throw error for invalid configuration', function() {
should.throws(function() {
validator.validate({
direction: 'invalid'
});
});
});
it('should not throw error for non existing configuration', function() {
validator.validate({
style: {
'pdf': 'test.css'
}
});
});
it('should validate plugins as an array', function() {
validator.validate({
plugins: ['hello']
});
});
it('should validate plugins as a string', function() {
validator.validate({
plugins: 'hello,world'
});
});
});
describe('No configuration', function() {
var book;
before(function() {
return mock.setupDefaultBook()
.then(function(_book) {
book = _book;
return book.prepareConfig();
});
});
it('should signal that configuration is not defined', function() {
book.config.exists().should.not.be.ok();
});
});
describe('JSON file', function() {
var book;
before(function() {
return mock.setupDefaultBook({
'book.json': { title: 'Hello World' }
})
.then(function(_book) {
book = _book;
return book.prepareConfig();
});
});
it('should correctly extend configuration', function() {
book.config.get('title', '').should.equal('Hello World');
});
});
describe('JS file', function() {
var book;
before(function() {
return mock.setupDefaultBook({
'book.js': 'module.exports = { title: "Hello World" };'
})
.then(function(_book) {
book = _book;
return book.prepareConfig();
});
});
it('should correctly extend configuration', function() {
book.config.get('title', '').should.equal('Hello World');
});
});
describe('Multilingual', function() {
var book;
before(function() {
return mock.setupDefaultBook({
'book.json': {
title: 'Hello World',
pluginsConfig: {
'test': {
'hello': true
}
}
},
'LANGS.md': '# Languages\n\n'
+ '* [en](./en)\n'
+ '* [fr](./fr)\n\n',
'en/README.md': '# Hello',
'fr/README.md': '# Bonjour',
'en/book.json': { description: 'In english' },
'fr/book.json': { description: 'En francais' }
})
.then(function(_book) {
book = _book;
return book.parse();
});
});
it('should correctly extend configuration', function() {
book.config.get('title', '').should.equal('Hello World');
book.config.get('description', '').should.equal('');
var en = book.books[0];
en.config.get('title', '').should.equal('Hello World');
en.config.get('description', '').should.equal('In english');
en.config.get('pluginsConfig.test.hello').should.equal(true);
var fr = book.books[1];
fr.config.get('title', '').should.equal('Hello World');
fr.config.get('description', '').should.equal('En francais');
fr.config.get('pluginsConfig.test.hello').should.equal(true);
});
});
});
-49
View File
@@ -1,49 +0,0 @@
var mock = require('./mock');
var ConrefsLoader = require('../lib/output/conrefs')();
describe('Conrefs Loader', function() {
var output;
before(function() {
return mock.outputDefaultBook(ConrefsLoader, {
'test.md': 'World'
})
.then(function(_output) {
output = _output;
});
});
it('should include a local file', function() {
return output.template.renderString('Hello {% include "./test.md" %}')
.should.be.fulfilledWith('Hello World');
});
it('should include a git url', function() {
return output.template.renderString('Hello {% include "./test.md" %}')
.should.be.fulfilledWith('Hello World');
});
it('should reject file out of scope', function() {
return output.template.renderString('Hello {% include "../test.md" %}')
.should.be.rejected();
});
describe('Git Urls', function() {
it('should include a file from a git repo', function() {
return output.template.renderString('{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md" %}')
.should.be.fulfilledWith('Hello from git');
});
it('should handle deep inclusion (1)', function() {
return output.template.renderString('{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test2.md" %}')
.should.be.fulfilledWith('First Hello. Hello from git');
});
it('should handle deep inclusion (2)', function() {
return output.template.renderString('{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test3.md" %}')
.should.be.fulfilledWith('First Hello. Hello from git');
});
});
});
-12
View File
@@ -1,12 +0,0 @@
var should = require('should');
var gitbook = require('../');
describe('Exports', function() {
it('should export the Book class', function() {
should(gitbook.Book).be.a.Function();
});
it('should export the list of commands', function() {
should(gitbook.commands).be.an.Array();
});
});
-61
View File
@@ -1,61 +0,0 @@
var should = require('should');
var path = require('path');
var os = require('os');
var Git = require('../lib/utils/git');
describe('Git', function() {
describe('URL parsing', function() {
it('should correctly validate git urls', function() {
// HTTPS
Git.isUrl('git+https://github.com/Hello/world.git').should.be.ok();
// SSH
Git.isUrl('git+git@github.com:GitbookIO/gitbook.git/directory/README.md#e1594cde2c32e4ff48f6c4eff3d3d461743d74e1').should.be.ok;
// Non valid
Git.isUrl('https://github.com/Hello/world.git').should.not.be.ok();
Git.isUrl('README.md').should.not.be.ok();
});
it('should parse HTTPS urls', function() {
var parts = Git.parseUrl('git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md');
should.exist(parts);
parts.host.should.be.equal('https://gist.github.com/69ea4542e4c8967d2fa7.git');
should(parts.ref).be.equal(null);
parts.filepath.should.be.equal('test.md');
});
it('should parse HTTPS urls with a reference', function() {
var parts = Git.parseUrl('git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md#1.0.0');
should.exist(parts);
parts.host.should.be.equal('https://gist.github.com/69ea4542e4c8967d2fa7.git');
parts.ref.should.be.equal('1.0.0');
parts.filepath.should.be.equal('test.md');
});
it('should parse SSH urls', function() {
var parts = Git.parseUrl('git+git@github.com:GitbookIO/gitbook.git/directory/README.md#e1594cde2c32e4ff48f6c4eff3d3d461743d74e1');
should.exist(parts);
parts.host.should.be.equal('git@github.com:GitbookIO/gitbook.git');
parts.ref.should.be.equal('e1594cde2c32e4ff48f6c4eff3d3d461743d74e1');
parts.filepath.should.be.equal('directory/README.md');
});
});
describe('Cloning and resolving', function() {
it('should clone an HTTPS url', function() {
var git = new Git(path.join(os.tmpdir(), 'test-git-'+Date.now()));
return git.resolve('git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md')
.then(function(filename) {
path.extname(filename).should.equal('.md');
});
});
});
});
-71
View File
@@ -1,71 +0,0 @@
var should = require('should');
var mock = require('./mock');
describe('Glossary', function() {
it('should parse empty glossary', function() {
return mock.setupDefaultBook({
'GLOSSARY.md': ''
})
.then(function(book) {
return book.prepareConfig()
.then(function() {
return book.glossary.load();
})
.then(function() {
book.glossary.isEmpty().should.be.true();
});
});
});
describe('Non-empty glossary', function() {
var book;
before(function() {
return mock.setupDefaultBook({
'GLOSSARY.md': '# Glossary\n\n## Hello World\n\nThis is an entry'
})
.then(function(_book) {
book = _book;
return book.prepareConfig();
})
.then(function() {
return book.glossary.load();
});
});
it('should not be empty', function() {
book.glossary.isEmpty().should.be.false();
});
describe('glossary.get', function() {
it('should return an existing entry', function() {
var entry = book.glossary.get('hello_world');
should.exist(entry);
entry.name.should.equal('Hello World');
entry.description.should.equal('This is an entry');
entry.id.should.equal('hello_world');
});
it('should undefined return non existing entry', function() {
var entry = book.glossary.get('cool');
should.not.exist(entry);
});
});
describe('glossary.find', function() {
it('should return an existing entry', function() {
var entry = book.glossary.find('HeLLo World');
should.exist(entry);
entry.id.should.equal('hello_world');
});
it('should return undefined for non existing entry', function() {
var entry = book.glossary.find('Hello');
should.not.exist(entry);
});
});
});
});
-33
View File
@@ -1,33 +0,0 @@
var mock = require('./mock');
var WebsiteOutput = require('../lib/output/website');
describe('Ignore', function() {
var output;
before(function() {
return mock.outputDefaultBook(WebsiteOutput, {
'.ignore': 'test-1.js',
'.gitignore': 'test-2.js\ntest-3.js',
'.bookignore': '!test-3.js',
'test-1.js': '1',
'test-2.js': '2',
'test-3.js': '3'
})
.then(function(_output) {
output = _output;
});
});
it('should load rules from .ignore', function() {
output.should.not.have.file('test-1.js');
});
it('should load rules from .gitignore', function() {
output.should.not.have.file('test-2.js');
});
it('should load rules from .bookignore', function() {
output.should.have.file('test-3.js');
});
});
-57
View File
@@ -1,57 +0,0 @@
var Book = require('../lib/book');
var mock = require('./mock');
describe('Init', function() {
it('should create file according to summary', function() {
return mock.setupFS({
'SUMMARY.md': '# Summary\n\n'
+ '* [Hello](hello.md)\n'
+ '* [Hello 2](hello 2.md)\n'
})
.then(function(rootFolder) {
return Book.init(mock.fs, rootFolder, {
log: function() {}
})
.then(function() {
rootFolder.should.have.file('SUMMARY.md');
rootFolder.should.have.file('README.md');
rootFolder.should.have.file('hello.md');
rootFolder.should.have.file('hello 2.md');
});
});
});
it('should create file subfolder', function() {
return mock.setupFS({
'SUMMARY.md': '# Summary\n\n'
+ '* [Hello](test/hello.md)\n'
+ '* [Hello 2](test/test2/world.md)\n'
})
.then(function(rootFolder) {
return Book.init(mock.fs, rootFolder, {
log: function() {}
})
.then(function() {
rootFolder.should.have.file('README.md');
rootFolder.should.have.file('SUMMARY.md');
rootFolder.should.have.file('test/hello.md');
rootFolder.should.have.file('test/test2/world.md');
});
});
});
it('should create SUMMARY if non-existant', function() {
return mock.setupFS({})
.then(function(rootFolder) {
return Book.init(mock.fs, rootFolder, {
log: function() {}
})
.then(function() {
rootFolder.should.have.file('SUMMARY.md');
rootFolder.should.have.file('README.md');
});
});
});
});
-46
View File
@@ -1,46 +0,0 @@
var mock = require('./mock');
describe('Langs', function() {
it('should parse empty langs', function() {
return mock.setupDefaultBook({
'LANGS.md': ''
})
.then(function(book) {
return book.prepareConfig()
.then(function() {
return book.langs.load();
})
.then(function() {
book.langs.count().should.equal(0);
});
});
});
describe('Non-empty languages list', function() {
var book;
before(function() {
return mock.setupDefaultBook({
'LANGS.md': '# Languages\n\n'
+ '* [en](./en)\n'
+ '* [fr](./fr)\n\n'
})
.then(function(_book) {
book = _book;
return book.langs.load();
});
});
it('should correctly count languages', function() {
book.langs.count().should.equal(2);
});
it('should correctly define book as multilingual', function() {
book.isMultilingual().should.equal(true);
});
});
});
-34
View File
@@ -1,34 +0,0 @@
var path = require('path');
var should = require('should');
var mock = require('./mock');
describe('Locate', function() {
it('should use root folder if no .gitbook', function() {
return mock.setupBook({
'README.md': '# Hello'
})
.then(function(book) {
return book.prepareConfig()
.then(function() {
should(book.originalRoot).not.be.ok();
});
});
});
it('should use resolve using book.js root property', function() {
return mock.setupBook({
'README.md': '# Hello',
'docs/README.md': '# Hello Book',
'book.json': { root: './docs' }
})
.then(function(book) {
return book.prepareConfig()
.then(function() {
should(book.originalRoot).be.ok();
book.root.should.equal(path.resolve(book.originalRoot, 'docs'));
});
});
});
});
-55
View File
@@ -1,55 +0,0 @@
var location = require('../lib/utils/location');
describe('Location', function() {
it('should correctly test external location', function() {
location.isExternal('http://google.fr').should.be.exactly(true);
location.isExternal('https://google.fr').should.be.exactly(true);
location.isExternal('test.md').should.be.exactly(false);
location.isExternal('folder/test.md').should.be.exactly(false);
location.isExternal('/folder/test.md').should.be.exactly(false);
});
it('should correctly detect anchor location', function() {
location.isAnchor('#test').should.be.exactly(true);
location.isAnchor(' #test').should.be.exactly(true);
location.isAnchor('https://google.fr#test').should.be.exactly(false);
location.isAnchor('test.md#test').should.be.exactly(false);
});
describe('.relative', function() {
it('should resolve to a relative path (same folder)', function() {
location.relative('links/', 'links/test.md').should.equal('test.md');
});
it('should resolve to a relative path (parent folder)', function() {
location.relative('links/', 'test.md').should.equal('../test.md');
});
it('should resolve to a relative path (child folder)', function() {
location.relative('links/', 'links/hello/test.md').should.equal('hello/test.md');
});
});
describe('.toAbsolute', function() {
it('should correctly transform as absolute', function() {
location.toAbsolute('http://google.fr').should.be.equal('http://google.fr');
location.toAbsolute('test.md', './', './').should.be.equal('test.md');
location.toAbsolute('folder/test.md', './', './').should.be.equal('folder/test.md');
});
it('should correctly handle windows path', function() {
location.toAbsolute('folder\\test.md', './', './').should.be.equal('folder/test.md');
});
it('should correctly handle absolute path', function() {
location.toAbsolute('/test.md', './', './').should.be.equal('test.md');
location.toAbsolute('/test.md', 'test', 'test').should.be.equal('../test.md');
location.toAbsolute('/sub/test.md', 'test', 'test').should.be.equal('../sub/test.md');
location.toAbsolute('/test.png', 'folder', '').should.be.equal('test.png');
});
it('should correctly handle absolute path (windows)', function() {
location.toAbsolute('\\test.png', 'folder', '').should.be.equal('test.png');
});
});
});
-137
View File
@@ -1,137 +0,0 @@
/* eslint-disable no-console */
var Q = require('q');
var _ = require('lodash');
var tmp = require('tmp');
var path = require('path');
var Book = require('../').Book;
var NodeFS = require('../lib/fs/node');
var fs = require('../lib/utils/fs');
require('./assertions');
// Create filesystem instance for testing
var nodeFS = new NodeFS();
function setupFS(files) {
return Q.nfcall(tmp.dir.bind(tmp)).get(0)
.then(function(rootFolder) {
return _.chain(_.pairs(files))
.sortBy(0)
.reduce(function(prev, pair) {
return prev.then(function() {
var filename = path.resolve(rootFolder, pair[0]);
var buf = pair[1];
if (_.isObject(buf)) buf = JSON.stringify(buf);
if (_.isString(buf)) buf = new Buffer(buf, 'utf-8');
return fs.mkdirp(path.dirname(filename))
.then(function() {
return fs.writeFile(filename, buf);
});
});
}, Q())
.value()
.then(function() {
return rootFolder;
});
});
}
// Setup a mock book for testing using a map of files
function setupBook(files, opts) {
opts = opts || {};
opts.log = function() { };
return setupFS(files)
.then(function(folder) {
opts.fs = nodeFS;
opts.root = folder;
return new Book(opts);
});
}
// Setup a book with default README/SUMMARY
function setupDefaultBook(files, summary, opts) {
var summaryContent = '# Summary \n\n' +
_.map(summary, function(article) {
return '* [' + article.title +'](' + article.path + ')';
})
.join('\n');
return setupBook(_.defaults(files || {}, {
'README.md': 'Hello',
'SUMMARY.md': summaryContent
}), opts);
}
// Prepare output for a book
function setupOutput(Output, files, opts) {
return setupBook(files, opts)
.then(function(book) {
// Parse the book
return book.parse()
// Start generation
.then(function() {
return new Output(book);
});
});
}
// Prepare output for a book
function setupDefaultOutput(Output, files, summary, opts) {
return setupDefaultBook(files, summary, opts)
.then(function(book) {
// Parse the book
return book.parse()
// Start generation
.then(function() {
return new Output(book);
});
});
}
// Output a book with a specific generator
function outputDefaultBook(Output, files, summary, opts) {
return setupDefaultOutput(Output, files, summary, opts)
.then(function(output) {
return output.generate()
.thenResolve(output);
});
}
// Output a book with a specific generator
function outputBook(Output, files, opts) {
return setupOutput(Output, files, opts)
.then(function(output) {
return output.generate()
.thenResolve(output);
});
}
// Log an error
function logError(err) {
console.log(err.stack || err);
}
module.exports = {
fs: nodeFS,
setupFS: setupFS,
setupBook: setupBook,
setupDefaultBook: setupDefaultBook,
setupOutput: setupOutput,
setupDefaultOutput: setupDefaultOutput,
outputBook: outputBook,
outputDefaultBook: outputDefaultBook,
logError: logError
};
-10
View File
@@ -1,10 +0,0 @@
{
"name": "gitbook-plugin-dep1",
"version": "1.0.0",
"engines": {
"gitbook": "*"
},
"dependencies": {
"gitbook-plugin-dep2": "*"
}
}
-7
View File
@@ -1,7 +0,0 @@
{
"name": "gitbook-plugin-dep2",
"version": "1.0.0",
"engines": {
"gitbook": "*"
}
}
-21
View File
@@ -1,21 +0,0 @@
var should = require('should');
module.exports = {
blocks: {
// Simple test
hello: function(blk) {
return 'Hello ' + blk.body + '!';
},
// Test using the conetxt "this"
testContext: function(s) {
this.should.have.property('config');
this.should.have.property('log');
this.should.have.property('options');
this.should.have.property('resolve').which.is.a.function;
this.should.have.property('book').which.equal(this);
return 'Hello ' + s + '!';
}
}
};
-7
View File
@@ -1,7 +0,0 @@
{
"name": "gitbook-plugin-test-blocks",
"version": "1.0.0",
"engines": {
"gitbook": "*"
}
}
-1
View File
@@ -1 +0,0 @@
module.exports = {};
-21
View File
@@ -1,21 +0,0 @@
{
"name": "gitbook-plugin-test-config",
"version": "1.0.0",
"engines": {
"gitbook": "*"
},
"gitbook": {
"properties": {
"myProperty": {
"type": "string",
"required": true,
"title": "This is a required property"
},
"myDefaultProperty": {
"type": "string",
"default": "hello",
"title": "This is a required property"
}
}
}
}
-8
View File
@@ -1,8 +0,0 @@
module.exports = {
hooks: {
page: function(page) {
page.sections[0].content = 'Hello (sections) ' + page.content;
return page;
}
}
};
-7
View File
@@ -1,7 +0,0 @@
{
"name": "gitbook-plugin-test-deprecated",
"version": "1.0.0",
"engines": {
"gitbook": "*"
}
}
-21
View File
@@ -1,21 +0,0 @@
var should = require('should');
module.exports = {
filters: {
// Simple test
hello: function(s) {
return 'Hello ' + s + '!';
},
// Test using the conetxt "this"
testContext: function(s) {
this.should.have.property('config');
this.should.have.property('log');
this.should.have.property('options');
this.should.have.property('resolve').which.is.a.function;
this.should.have.property('book').which.equal(this);
return 'Hello ' + s + '!';
}
}
};
-7
View File
@@ -1,7 +0,0 @@
{
"name": "gitbook-plugin-test-filters",
"version": "1.0.0",
"engines": {
"gitbook": "*"
}
}
-18
View File
@@ -1,18 +0,0 @@
module.exports = {
hooks: {
'init': function() {
global._hooks = [];
global._hooks.push('init');
},
'finish': function() {
global._hooks.push('finish');
},
'finish:before': function() {
global._hooks.push('finish:before');
},
page: function(page) {
page.content = 'Hello ' + page.content;
return page;
}
}
};
-7
View File
@@ -1,7 +0,0 @@
{
"name": "gitbook-plugin-test-hooks",
"version": "1.0.0",
"engines": {
"gitbook": "*"
}
}
-12
View File
@@ -1,12 +0,0 @@
module.exports = {
book: {
assets: './assets',
js: [
'myfile.js',
'https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js'
],
css: [
'myfile.css'
]
}
};
-7
View File
@@ -1,7 +0,0 @@
{
"name": "gitbook-plugin-test-resources",
"version": "1.0.0",
"engines": {
"gitbook": "*"
}
}
-33
View File
@@ -1,33 +0,0 @@
var mock = require('./mock');
var EbookOutput = require('../lib/output/ebook');
describe('Ebook Output', function() {
describe('Sample Book', function() {
var output;
before(function() {
return mock.outputDefaultBook(EbookOutput)
.then(function(_output) {
output = _output;
});
});
it('should correctly generate an index.html', function() {
output.should.have.file('index.html');
});
it('should correctly generate a SUMMARY.html', function() {
output.should.have.file('index.html');
});
it('should correctly copy assets', function() {
output.should.have.file('gitbook/ebook.css');
});
it('should correctly copy plugins', function() {
output.should.have.file('gitbook/gitbook-plugin-highlight/ebook.css');
});
});
});
-65
View File
@@ -1,65 +0,0 @@
var mock = require('./mock');
var JSONOutput = require('../lib/output/json');
describe('JSON Output', function() {
describe('Sample book', function() {
var output;
before(function() {
return mock.outputDefaultBook(JSONOutput)
.then(function(_output) {
output = _output;
});
});
it('should correctly generate a README.json', function() {
output.should.have.file('README.json');
});
});
describe('Multilingual Book', function() {
var output;
before(function() {
return mock.outputBook(JSONOutput, {
'LANGS.md': '# Languages\n\n'
+ '* [en](./en)\n'
+ '* [fr](./fr)\n\n',
'en/README.md': '# Hello',
'fr/README.md': '# Bonjour'
})
.then(function(_output) {
output = _output;
});
});
it('should correctly generate a README.json for each language', function() {
output.should.have.file('en/README.json');
output.should.have.file('fr/README.json');
});
it('should correctly add languages list to all json', function() {
var jsonFR = require(output.resolve('fr/README.json'));
var jsonEN = require(output.resolve('en/README.json'));
jsonFR.should.have.property('languages')
.with.property('list').with.lengthOf(2);
jsonEN.should.have.property('languages')
.with.property('list').with.lengthOf(2);
});
it('should correctly generate a README.json for the whole book', function() {
output.should.have.file('README.json');
var json = require(output.resolve('README.json'));
json.book.language.should.equal('en');
json.should.have.property('languages')
.with.property('list').with.lengthOf(2);
});
});
});
-129
View File
@@ -1,129 +0,0 @@
var fs = require('fs');
var mock = require('./mock');
var WebsiteOutput = require('../lib/output/website');
describe('Website Output', function() {
describe('Sample Book', function() {
var output;
before(function() {
return mock.outputDefaultBook(WebsiteOutput)
.then(function(_output) {
output = _output;
});
});
it('should correctly generate an index.html', function() {
output.should.have.file('index.html');
});
it('should correctly copy assets', function() {
output.should.have.file('gitbook/gitbook.js');
output.should.have.file('gitbook/theme.js');
output.should.have.file('gitbook/images/favicon.ico');
});
it('should correctly copy plugins', function() {
output.should.have.file('gitbook/gitbook-plugin-highlight/website.css');
});
});
describe('Book with chapters', function() {
var output;
before(function() {
return mock.outputDefaultBook(WebsiteOutput, {
'hello/README.md': '# Hello',
'hello/test.md': '# Test'
}, [
{
title: 'Hello',
path: 'hello/README.md'
},
{
title: 'Test',
path: 'hello/test.md'
}
])
.then(function(_output) {
output = _output;
});
});
it('should correctly generate an index.html', function() {
output.should.have.file('index.html');
});
it('should correctly generate files in folder', function() {
output.should.have.file('hello/index.html');
output.should.have.file('hello/test.html');
});
});
describe('Multilingual Book', function() {
var output;
before(function() {
return mock.outputBook(WebsiteOutput, {
'LANGS.md': '# Languages\n\n'
+ '* [en](./en)\n'
+ '* [fr](./fr)\n\n',
'en/README.md': '# Hello',
'fr/README.md': '# Bonjour'
})
.then(function(_output) {
output = _output;
});
});
it('should correctly generate an index.html for each language', function() {
output.should.have.file('en/index.html');
output.should.have.file('fr/index.html');
});
it('should correctly copy assets', function() {
output.should.have.file('gitbook/gitbook.js');
output.should.have.file('gitbook/theme.js');
});
it('should not copy assets for each language', function() {
output.should.have.not.file('en/gitbook/gitbook.js');
output.should.have.not.file('fr/gitbook/gitbook.js');
output.should.have.not.file('en/gitbook/theme.js');
output.should.have.not.file('fr/gitbook/theme.js');
});
it('should correctly generate an index.html', function() {
output.should.have.file('index.html');
});
});
describe('Theming', function() {
var output;
before(function() {
return mock.outputDefaultBook(WebsiteOutput, {
'_layouts/website/page.html': '{% extends "website/page.html" %}{% block body %}{{ super() }}<div id="theming-added"></div>{% endblock %}'
})
.then(function(_output) {
output = _output;
});
});
it('should extend default theme', function() {
var readme = fs.readFileSync(output.resolve('index.html'), 'utf-8');
readme.should.be.html({
'#theming-added': {
count: 1
}
});
});
});
});
-431
View File
@@ -1,431 +0,0 @@
var mock = require('./mock');
var Output = require('../lib/output/base');
describe('Page', function() {
var book, output;
before(function() {
return mock.setupDefaultBook({
'README.md': ' # Hello World\n\nThis is a description',
'heading.md': '# Hello\n\n## World',
'description.md': '# This is a title\n\nThis is the short description.\n\nAnd the rest of the description.\n\n# Heading\n\nThis is not in the description',
'frontmatter/description.md': '---\ndescription: Hello World\n---\n\n# This is a title\n\nThis is not the description',
'frontmatter/var.md': '---\ntest: Hello World\n---\n\n{{ page.test }}',
'links.md': '[link](hello.md) [link 2](variables/page/next.md) [readme](README.md)',
'links/relative.md': '[link](../hello.md) [link 2](/variables/page/next.md) [readme](../README.md)',
'images.md': '![this is an image](test.png) ![this is an absolute image](/test2.png) ![this is a remote image](https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png)',
'images/relative.md': '![this is an image](test.png) ![this is an absolute image](/test2.png)',
'annotations/simple.md': 'A magicien say abracadabra!',
'annotations/code.md': 'A magicien say `abracadabra`!',
'annotations/class.md': 'A magicien say <div class="no-glossary"><b>abracadabra</b>, right?</div>!',
'codes/simple.md': '```hello world```',
'codes/lang.md': '```js\nhello world\n```',
'codes/lang.adoc': '```js\nhello world\n```',
'folder/paths.md': '',
'variables/file/mtime.md': '{{ file.mtime }}',
'variables/file/path.md': '{{ file.path }}',
'variables/page/title.md': '{{ page.title }}',
'variables/page/previous.md': '{{ page.previous.title }} {{ page.previous.path }}',
'variables/page/next.md': '{{ page.next.title }} {{ page.next.path }}',
'variables/page/dir/ltr.md': 'This is english: {{ page.dir }}',
'variables/page/dir/rtl.md': 'بسيطة {{ page.dir }}',
'variables/config/title.md': '{{ config.title}}',
'variables/gitbook/generator.md': '{{ gitbook.generator }}',
'GLOSSARY.md': '# Glossary\n\n\n## abracadabra\n\nthis is the description',
'blocks/markdown.md': 'Hello <span>{% markdown %}**World**{% endmarkdown %}</span>',
'blocks/asciidoc.md': 'Hello <span>{% asciidoc %}^super^script phrase{% endasciidoc %}</span>'
}, [
{
title: 'Test page.next',
path: 'variables/page/next.md'
},
{
title: 'Test Variables',
path: 'variables/page/title.md'
},
{
title: 'Test page.previous',
path: 'variables/page/previous.md'
}
])
.then(function(_book) {
book = _book;
output = new Output(book);
return book.parse();
});
});
describe('.resolveLocal', function() {
it('should correctly resolve path to file', function() {
var page = book.addPage('heading.md');
page.resolveLocal('test.png').should.equal('test.png');
page.resolveLocal('/test.png').should.equal('test.png');
page.resolveLocal('test/hello.png').should.equal('test/hello.png');
page.resolveLocal('/test/hello.png').should.equal('test/hello.png');
});
it('should correctly resolve path to file (2)', function() {
var page = book.addPage('folder/paths.md');
page.resolveLocal('test.png').should.equal('folder/test.png');
page.resolveLocal('/test.png').should.equal('test.png');
page.resolveLocal('test/hello.png').should.equal('folder/test/hello.png');
page.resolveLocal('/test/hello.png').should.equal('test/hello.png');
});
});
describe('.relative', function() {
it('should correctly resolve absolute path in the book', function() {
var page = book.addPage('heading.md');
page.relative('/test.png').should.equal('test.png');
page.relative('test.png').should.equal('test.png');
var page2 = book.addPage('folder/paths.md');
page2.relative('/test.png').should.equal('../test.png');
page2.relative('test.png').should.equal('../test.png');
});
});
describe('.resolve', function() {
var page;
before(function() {
page = book.addPage('links/relative.md');
});
it('should resolve to a relative path (same folder)', function() {
page.relative('links/test.md').should.equal('test.md');
});
it('should resolve to a relative path (parent folder)', function() {
page.relative('test.md').should.equal('../test.md');
page.relative('hello/test.md').should.equal('../hello/test.md');
});
it('should resolve to a relative path (child folder)', function() {
page.relative('links/hello/test.md').should.equal('hello/test.md');
});
});
describe('Headings', function() {
it('should add a default ID to headings', function() {
var page = book.addPage('heading.md');
return page.toHTML(output)
.then(function() {
page.content.should.be.html({
'h1#hello': {
count: 1
},
'h2#world': {
count: 1
}
});
});
});
});
describe('Description', function() {
it('should extract page description from content', function() {
var page = book.addPage('description.md');
return page.toHTML(output)
.then(function() {
page.attributes.description.should.equal('This is the short description. And the rest of the description.');
});
});
});
describe('Font-Matter', function() {
it('should extract page description from front matter', function() {
var page = book.addPage('frontmatter/description.md');
return page.toHTML(output)
.then(function() {
page.attributes.description.should.equal('Hello World');
});
});
it('should extend page attributes with custom properties', function() {
var page = book.addPage('frontmatter/var.md');
return page.toHTML(output)
.then(function() {
page.content.should.equal('<p>Hello World</p>\n');
});
});
});
describe('Code Blocks', function() {
var page;
before(function() {
output.template.addBlock('code', function(blk) {
return (blk.kwargs.language || '') + blk.body + 'test';
});
});
it('should apply "code" block', function() {
page = book.addPage('codes/simple.md');
return page.toHTML(output)
.should.be.fulfilledWith('<p><code>hello worldtest</code></p>\n');
});
it('should add language as kwargs', function() {
page = book.addPage('codes/lang.md');
return page.toHTML(output)
.should.be.fulfilledWith('<pre><code class="lang-js">jshello world\ntest</code></pre>\n');
});
it('should add language as kwargs (asciidoc)', function() {
page = book.addPage('codes/lang.adoc');
return page.toHTML(output)
.should.be.fulfilledWith('<div class="listingblock">\n<div class="content">\n<pre class="highlight"><code class="language-js" data-lang="js">jshello worldtest</code></pre>\n</div>\n</div>');
});
});
describe('Links', function() {
describe('From base directory', function() {
var page;
before(function() {
page = book.addPage('links.md');
return page.toHTML(output);
});
it('should replace links to page to .html', function() {
page.content.should.be.html({
'a[href="variables/page/next.html"]': {
count: 1
}
});
});
it('should use directory urls when file is a README', function() {
page.content.should.be.html({
'a[href="./"]': {
count: 1
}
});
});
it('should not replace links to file not in SUMMARY', function() {
page.content.should.be.html({
'a[href="hello.md"]': {
count: 1
}
});
});
});
describe('From sub-directory', function() {
var page;
before(function() {
page = book.addPage('links/relative.md');
return page.toHTML(output);
});
it('should replace links to page to .html', function() {
page.content.should.be.html({
'a[href="../variables/page/next.html"]': {
count: 1
}
});
});
it('should use directory urls when file is a README', function() {
page.content.should.be.html({
'a[href="../"]': {
count: 1
}
});
});
it('should not replace links to file not in SUMMARY', function() {
page.content.should.be.html({
'a[href="../hello.md"]': {
count: 1
}
});
});
});
});
describe('Images', function() {
describe('From base directory', function() {
var page;
before(function() {
page = book.addPage('images.md');
return page.toHTML(output);
});
it('should resolve relative images', function() {
page.content.should.be.html({
'img[src="test.png"]': {
count: 1
}
});
});
it('should resolve absolute images', function() {
page.content.should.be.html({
'img[src="test2.png"]': {
count: 1
}
});
});
it('should keep external images path', function() {
page.content.should.be.html({
'img[src="https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png"]': {
count: 1
}
});
});
});
describe('From sub-directory', function() {
var page;
before(function() {
page = book.addPage('images/relative.md');
return page.toHTML(output);
});
it('should resolve relative images', function() {
page.content.should.be.html({
'img[src="test.png"]': {
count: 1
}
});
});
it('should resolve absolute images', function() {
page.content.should.be.html({
'img[src="../test2.png"]': {
count: 1
}
});
});
});
});
describe('Templating Context', function() {
it('should set file.mtime', function() {
var page = book.addPage('variables/file/mtime.md');
return page.toHTML(output)
.then(function(content) {
// A date ends with "(CET)" or "(UTC)""
content.should.endWith(')</p>\n');
});
});
it('should set file.path', function() {
var page = book.addPage('variables/file/path.md');
return page.toHTML(output)
.should.be.fulfilledWith('<p>variables/file/path.md</p>\n');
});
it('should set page.title when page is in summary', function() {
var page = book.getPage('variables/page/title.md');
return page.toHTML(output)
.should.be.fulfilledWith('<p>Test Variables</p>\n');
});
it('should set page.previous when possible', function() {
var page = book.getPage('variables/page/previous.md');
return page.toHTML(output)
.should.be.fulfilledWith('<p>Test Variables variables/page/title.md</p>\n');
});
it('should set page.next when possible', function() {
var page = book.getPage('variables/page/next.md');
return page.toHTML(output)
.should.be.fulfilledWith('<p>Test Variables variables/page/title.md</p>\n');
});
it('should set config.title', function() {
var page = book.addPage('variables/config/title.md');
return page.toHTML(output)
.should.be.fulfilledWith('<p>Hello World</p>\n');
});
it('should set gitbook.generator', function() {
var page = book.addPage('variables/gitbook/generator.md');
return page.toHTML(output)
.should.be.fulfilledWith('<p>base</p>\n');
});
describe('page.dir', function() {
it('should detect ltr', function() {
var page = book.addPage('variables/page/dir/ltr.md');
return page.toHTML(output)
.should.be.fulfilledWith('<p>This is english: ltr</p>\n');
});
it('should detect rtl', function() {
var page = book.addPage('variables/page/dir/rtl.md');
return page.toHTML(output)
.should.be.fulfilledWith('<p>&#x628;&#x633;&#x64A;&#x637;&#x629; rtl</p>\n');
});
});
});
describe('Annotations / Glossary', function() {
it('should replace glossary terms', function() {
return book.addPage('annotations/simple.md').toHTML(output)
.should.finally.be.html({
'.glossary-term': {
count: 1,
text: 'abracadabra',
attributes: {
title: 'this is the description',
href: '../GLOSSARY.html#abracadabra'
}
}
});
});
it('should not replace terms in code blocks', function() {
return book.addPage('annotations/code.md').toHTML(output)
.should.finally.be.html({
'.glossary-term': {
count: 0
}
});
});
it('should not replace terms in ".no-glossary"', function() {
return book.addPage('annotations/class.md').toHTML(output)
.should.finally.be.html({
'.glossary-term': {
count: 0
}
});
});
});
describe('Default Blocks', function() {
it('should render block "markdown"', function() {
return book.addPage('blocks/markdown.md').toHTML(output)
.should.finally.equal('<p>Hello <span><strong>World</strong></span></p>\n');
});
it('should render block "asciidoc"', function() {
return book.addPage('blocks/asciidoc.md').toHTML(output)
.should.finally.equal('<p>Hello <span><sup>super</sup>script phrase</span></p>\n');
});
});
});
-59
View File
@@ -1,59 +0,0 @@
var mock = require('./mock');
describe('Parsing', function() {
it('should not fail without SUMMARY', function() {
return mock.setupBook({
'README.md': ''
})
.then(function(book) {
return book.parse().should.be.fulfilled();
});
});
it('should fail without README', function() {
return mock.setupBook({
'SUMMARY.md': ''
})
.then(function(book) {
return book.parse().should.be.rejected;
});
});
it('should add GLOSSARY as a page', function() {
return mock.setupDefaultBook({
'GLOSSARY.md': ''
})
.then(function(book) {
return book.parse()
.then(function() {
book.hasPage('GLOSSARY.md').should.equal(true);
});
});
});
describe('Multilingual book', function() {
var book;
before(function() {
return mock.setupBook({
'LANGS.md': '# Languages\n\n'
+ '* [English](./en)\n'
+ '* [French](./fr)\n\n',
'en/README.md': '# English',
'en/SUMMARY.md': '# Summary',
'fr/README.md': '# French',
'fr/SUMMARY.md': '# Summary'
})
.then(function(_book) {
book = _book;
return book.parse();
});
});
it('should list language books', function() {
book.isMultilingual().should.equal(true);
book.books.should.have.lengthOf(2);
});
});
});
-17
View File
@@ -1,17 +0,0 @@
var path = require('path');
var pathUtils = require('../lib/utils/path');
describe('Paths', function() {
describe('setExtension', function() {
it('should correctly change extension of filename', function() {
pathUtils.setExtension('test.md', '.html').should.be.equal('test.html');
pathUtils.setExtension('test.md', '.json').should.be.equal('test.json');
});
it('should correctly change extension of path', function() {
pathUtils.setExtension('hello/test.md', '.html').should.be.equal(path.normalize('hello/test.html'));
pathUtils.setExtension('hello/test.md', '.json').should.be.equal(path.normalize('hello/test.json'));
});
});
});
-271
View File
@@ -1,271 +0,0 @@
var _ = require('lodash');
var path = require('path');
var mock = require('./mock');
var registry = require('../lib/plugins/registry');
var Output = require('../lib/output/base');
var PluginsManager = require('../lib/plugins');
var BookPlugin = require('../lib/plugins/plugin');
var JSONOutput = require('../lib/output/json');
var PLUGINS_ROOT = path.resolve(__dirname, 'node_modules');
function TestPlugin(book, name) {
return new BookPlugin(book, name, path.resolve(PLUGINS_ROOT, 'gitbook-plugin-'+name));
}
describe('Plugins', function() {
var book;
before(function() {
return mock.setupBook({})
.then(function(_book) {
book = _book;
});
});
describe('Resolve Version', function() {
it('should resolve a plugin version', function() {
return registry.resolve('ga')
.should.be.fulfilled();
});
});
describe('Installation', function() {
it('should install a plugin from NPM without a specific version', function() {
return registry.install(book, 'ga')
.should.be.fulfilled();
});
it('should install a plugin from NPM with a specific version', function() {
return registry.install(book, 'ga', '1.0.0')
.should.be.fulfilled();
});
it('should correctly install all dependencies (if none)', function() {
return mock.setupBook({})
.then(function(book) {
var plugins = new PluginsManager(book);
return plugins.install()
.should.be.fulfilledWith(0);
});
});
it('should correctly install all dependencies (if any)', function() {
return mock.setupBook({
'book.json': {
plugins: ['ga']
}
})
.then(function(book) {
return book.prepareConfig()
.then(function() {
var plugins = new PluginsManager(book);
return plugins.install();
});
})
.should.be.fulfilledWith(1);
});
it('should correctly install dependencies from GitHub', function() {
return mock.setupBook({
'book.json': {
plugins: ['ga@git+https://github.com/GitbookIO/plugin-ga#master']
}
})
.then(function(book) {
return book.prepareConfig()
.then(function() {
var plugins = new PluginsManager(book);
return plugins.install();
});
})
.should.be.fulfilledWith(1);
});
// This test requires a SSH key, we only run it locally
if (!process.env.CI) {
it('should correctly install dependencies from GitHub via ssh', function() {
return mock.setupBook({
'book.json': {
plugins: ['ga@git@github.com:GitbookIO/plugin-ga.git#master']
}
})
.then(function(book) {
return book.prepareConfig()
.then(function() {
var plugins = new PluginsManager(book);
return plugins.install();
});
})
.should.be.fulfilledWith(1);
});
}
});
describe('Loading', function() {
it('should load default plugins', function() {
return mock.outputDefaultBook(Output)
.then(function(output) {
output.plugins.count().should.be.greaterThan(0);
});
});
});
describe('Configuration', function() {
it('should fail loading a plugin with an invalid configuration', function() {
var plugin = TestPlugin(book, 'test-config');
return plugin.load(PLUGINS_ROOT)
.should.be.rejectedWith('Error with book\'s configuration: pluginsConfig.test-config.myProperty is required');
});
it('should extend configuration with default properties', function() {
return mock.setupBook({
'book.json': {
pluginsConfig: {
'test-config': {
'myProperty': 'world'
}
}
}
})
.then(function(book2) {
return book2.prepareConfig()
.then(function() {
var plugin = TestPlugin(book2, 'test-config');
return plugin.load(PLUGINS_ROOT);
})
.then(function() {
book2.config.get('pluginsConfig.test-config.myDefaultProperty', '').should.equal('hello');
});
});
});
});
describe('Resources', function() {
var plugin;
before(function() {
plugin = TestPlugin(book, 'test-resources');
return plugin.load(PLUGINS_ROOT);
});
it('should list all resources for website', function() {
return plugin.getResources('website')
.then(function(resources) {
resources.assets.should.equal('./assets');
resources.js.should.have.lengthOf(2);
resources.js[0].path.should.equal('gitbook-plugin-test-resources/myfile.js');
resources.js[1].url.should.equal('https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js');
resources.css.should.have.lengthOf(1);
resources.css[0].path.should.equal('gitbook-plugin-test-resources/myfile.css');
});
});
});
describe('Filters', function() {
var plugin, filters;
before(function() {
plugin = TestPlugin(book, 'test-filters');
return plugin.load(PLUGINS_ROOT)
.then(function() {
filters = plugin.getFilters();
});
});
it('should list all filters', function() {
_.size(filters).should.equal(2);
});
it('should correctly execute a filter', function() {
filters.hello('World').should.equal('Hello World!');
});
it('should correctly set contexts for filter', function() {
filters.testContext('Hello');
});
});
describe('Blocks', function() {
var plugin, blocks;
before(function() {
plugin = TestPlugin(book, 'test-blocks');
return plugin.load(PLUGINS_ROOT)
.then(function() {
blocks = plugin.getBlocks();
});
});
it('should list all blocks', function() {
_.size(blocks).should.equal(2);
});
it('should correctly normalize block', function() {
blocks.hello.process({ body: 'World' }).should.equal('Hello World!');
});
it('should correctly set contexts for filter', function() {
blocks.testContext.process({ body: 'Hello' });
});
});
describe('Hooks', function() {
var plugin;
before(function() {
plugin = TestPlugin(book, 'test-hooks');
return plugin.load(PLUGINS_ROOT);
});
it('can call a hook', function() {
return plugin.hook('init')
.then(function() {
global._hooks.should.deepEqual(['init']);
});
});
describe('Hook "page"', function() {
var pluginDeprecated;
before(function() {
pluginDeprecated = TestPlugin(book, 'test-deprecated');
return pluginDeprecated.load(PLUGINS_ROOT);
});
it('should update content using "content" property', function() {
return mock.setupDefaultOutput(JSONOutput)
.then(function(output) {
output.plugins.load(plugin);
return output.generate()
.then(function() {
var json = require(output.resolve('README.json'));
json.page.content.should.equal('Hello <p>Hello</p>\n');
});
});
});
it('should update content using deprecated "sections" property', function() {
return mock.setupDefaultOutput(JSONOutput)
.then(function(output) {
output.plugins.load(pluginDeprecated);
return output.generate()
.then(function() {
var json = require(output.resolve('README.json'));
json.page.content.should.equal('Hello (sections) <p>Hello</p>\n');
});
});
});
});
});
});
-43
View File
@@ -1,43 +0,0 @@
var mock = require('./mock');
describe('Readme', function() {
it('should parse empty readme', function() {
return mock.setupDefaultBook({
'README.md': ''
})
.then(function(book) {
return book.prepareConfig()
.then(function() {
return book.readme.load();
});
});
});
it('should parse readme', function() {
return mock.setupDefaultBook({
'README.md': '# Hello World\nThis is my book'
})
.then(function(book) {
return book.readme.load()
.then(function() {
book.readme.title.should.equal('Hello World');
book.readme.description.should.equal('This is my book');
});
});
});
it('should parse AsciiDoc readme', function() {
return mock.setupBook({
'README.adoc': '# Hello World\n\nThis is my book\n'
})
.then(function(book) {
return book.readme.load()
.then(function() {
book.readme.title.should.equal('Hello World');
book.readme.description.should.equal('This is my book');
});
});
});
});
-51
View File
@@ -1,51 +0,0 @@
var fs = require('fs');
var mock = require('./mock');
var WebsiteOutput = require('../lib/output/website');
/*
Testing using configuration "structure" to use custom Readme/Summary/Glossary paths
*/
describe('Structure', function() {
var output;
before(function() {
return mock.outputBook(WebsiteOutput, {
'book.json': {
structure: {
readme: 'intro.md'
}
},
'SUMMARY.md': '* [Test](test.md)',
'intro.md': 'This is the intro',
'test.md': 'Go to [intro](intro.md)'
})
.then(function(_output) {
output = _output;
});
});
it('should generate index.html', function() {
output.should.have.file('index.html');
});
it('should generate test.html', function() {
output.should.have.file('test.html');
});
it('should correctly resolve link to Readme', function() {
var readme = fs.readFileSync(output.resolve('test.html'), 'utf-8');
readme.should.be.html({
'.page-inner a': {
count: 1,
text: 'intro',
attributes: {
href: './'
}
}
});
});
});
-328
View File
@@ -1,328 +0,0 @@
var should = require('should');
var mock = require('./mock');
function mockSummary(files, summary) {
return mock.setupDefaultBook(files, summary)
.then(function(book) {
return book.readme.load()
.then(function() {
return book.summary.load();
})
.thenResolve(book);
});
}
describe('Summary / Table of contents', function() {
describe('Empty summary list', function() {
var book;
before(function() {
return mockSummary({})
.then(function(_book) {
book = _book;
});
});
it('should add README as first entry', function() {
should(book.summary.getArticle('README.md')).be.ok();
});
it('should correctly count articles', function() {
book.summary.count().should.equal(1);
});
});
describe('Non-existant summary', function() {
var book;
before(function() {
return mock.setupBook({
'README.md': 'Hello'
})
.then(function(_book) {
book = _book;
return book.readme.load()
.then(function() {
return book.summary.load();
});
});
});
it('should add README as first entry', function() {
should(book.summary.getArticle('README.md')).be.ok();
});
it('should correctly count articles', function() {
book.summary.count().should.equal(1);
});
});
describe('Unicode summary', function() {
var book;
before(function() {
return mockSummary({
'SUMMARY.md': '# Summary\n\n'
+ '* [Hello](./hello world.md)\n'
+ '* [Spanish](./Descripción del problema.md)\n\n'
+ '* [Chinese](读了这本书.md)\n\n'
})
.then(function(_book) {
book = _book;
});
});
it('should accept article with spaces', function() {
should(book.summary.getArticle('hello world.md')).be.ok();
});
it('should accept article with chinese filename', function() {
should(book.summary.getArticle('读了这本书.md')).be.ok();
});
it('should accept article with accents', function() {
should(book.summary.getArticle('Descripción del problema.md')).be.ok();
});
});
describe('Non-empty summary list', function() {
var book;
before(function() {
return mockSummary({
'SUMMARY.md': '# Summary\n\n'
+ '* [Hello](./hello.md)\n'
+ '* [World](./world.md)\n\n'
})
.then(function(_book) {
book = _book;
});
});
it('should correctly count articles', function() {
book.summary.count().should.equal(3);
});
});
describe('Levels', function() {
var book;
before(function() {
return mockSummary({
'SUMMARY.md': '# Summary\n\n'
+ '* [Hello](./hello.md)\n'
+ ' * [Hello 2](./hello2.md)\n'
+ '* [World](./world.md)\n\n'
+ '## Part 2\n\n'
+ '* [Hello 3](./hello.md)\n'
+ ' * [Hello 4](./hello2.md)\n'
})
.then(function(_book) {
book = _book;
});
});
it('should correctly index levels', function() {
book.summary.getArticleByLevel('0').title.should.equal('Introduction');
book.summary.getArticleByLevel('1.1').title.should.equal('Hello');
book.summary.getArticleByLevel('1.1.1').title.should.equal('Hello 2');
book.summary.getArticleByLevel('1.2').title.should.equal('World');
book.summary.getArticleByLevel('2.1').title.should.equal('Hello 3');
book.summary.getArticleByLevel('2.1.1').title.should.equal('Hello 4');
});
it('should correctly calcul depth', function() {
book.summary.getArticleByLevel('0').depth().should.equal(1);
book.summary.getArticleByLevel('1.1').depth().should.equal(2);
book.summary.getArticleByLevel('1.1.1').depth().should.equal(3);
});
});
describe('External', function() {
var book;
before(function() {
return mockSummary({}, [
{
title: 'Google',
path: 'https://www.google.fr'
}
])
.then(function(_book) {
book = _book;
});
});
it('should correctly count articles', function() {
book.summary.count().should.equal(2);
});
it('should correctly signal it as external', function() {
var article = book.summary.getArticleByLevel('1');
should(article).be.ok();
should(article.path).not.be.ok();
article.title.should.equal('Google');
article.ref.should.equal('https://www.google.fr');
article.isExternal().should.be.ok;
});
});
describe('Next / Previous', function() {
var book;
before(function() {
return mockSummary({
'SUMMARY.md': '# Summary\n\n' +
'* [Hello](hello.md)\n' +
'* [Hello 2](hello2.md)\n' +
' * [Hello 3](hello3.md)\n' +
' * [Hello 4](hello4.md)\n' +
' * [Hello 5](hello5.md)\n' +
'* [Hello 6](hello6.md)\n\n\n' +
'### Part 2\n\n' +
'* [Hello 7](hello7.md)\n' +
' * [Hello 8](hello8.md)\n\n' +
'### Part 3\n\n' +
'* [Hello 9](hello9.md)\n' +
'* [Hello 10](hello10.md)\n\n'
})
.then(function(_book) {
book = _book;
});
});
it('should only return a next for the readme', function() {
var article = book.summary.getArticle('README.md');
var prev = article.prev();
var next = article.next();
should(prev).not.be.ok();
should(next).be.ok();
next.path.should.equal('hello.md');
});
it('should return next/prev for a first level page', function() {
var article = book.summary.getArticle('hello.md');
var prev = article.prev();
var next = article.next();
should(prev).be.ok();
should(next).be.ok();
prev.path.should.equal('README.md');
next.path.should.equal('hello2.md');
});
it('should return next/prev for a joint -> child', function() {
var article = book.summary.getArticle('hello2.md');
var prev = article.prev();
var next = article.next();
should(prev).be.ok();
should(next).be.ok();
prev.path.should.equal('hello.md');
next.path.should.equal('hello3.md');
});
it('should return next/prev for a joint <- child', function() {
var article = book.summary.getArticle('hello3.md');
var prev = article.prev();
var next = article.next();
should(prev).be.ok();
should(next).be.ok();
prev.path.should.equal('hello2.md');
next.path.should.equal('hello4.md');
});
it('should return next/prev for a children', function() {
var article = book.summary.getArticle('hello4.md');
var prev = article.prev();
var next = article.next();
should(prev).be.ok();
should(next).be.ok();
prev.path.should.equal('hello3.md');
next.path.should.equal('hello5.md');
});
it('should return next/prev for a joint -> parent', function() {
var article = book.summary.getArticle('hello5.md');
var prev = article.prev();
var next = article.next();
should(prev).be.ok();
should(next).be.ok();
prev.path.should.equal('hello4.md');
next.path.should.equal('hello6.md');
});
it('should return next/prev for a joint -> parts', function() {
var article = book.summary.getArticle('hello6.md');
var prev = article.prev();
var next = article.next();
should(prev).be.ok();
should(next).be.ok();
prev.path.should.equal('hello5.md');
next.path.should.equal('hello7.md');
});
it('should return next/prev for a joint <- parts', function() {
var article = book.summary.getArticle('hello7.md');
var prev = article.prev();
var next = article.next();
should(prev).be.ok();
should(next).be.ok();
prev.path.should.equal('hello6.md');
next.path.should.equal('hello8.md');
});
it('should return next and prev', function() {
var article = book.summary.getArticle('hello8.md');
var prev = article.prev();
var next = article.next();
should(prev).be.ok();
should(next).be.ok();
prev.path.should.equal('hello7.md');
next.path.should.equal('hello9.md');
});
it('should return only prev for last', function() {
var article = book.summary.getArticle('hello10.md');
var prev = article.prev();
var next = article.next();
should(prev).be.ok();
should(next).be.not.ok();
prev.path.should.equal('hello9.md');
});
});
});
-54
View File
@@ -1,54 +0,0 @@
var mock = require('./mock');
var Output = require('../lib/output/base');
describe('Template', function() {
var output;
before(function() {
return mock.outputDefaultBook(Output, {})
.then(function(_output) {
output = _output;
});
});
describe('.renderString', function() {
it('should render a simple string', function() {
return output.template.renderString('Hello World')
.should.be.fulfilledWith('Hello World');
});
it('should render with variable', function() {
return output.template.renderString('Hello {{ world }}', { world: 'World'})
.should.be.fulfilledWith('Hello World');
});
});
describe('Blocks', function() {
it('should correctly add a block', function() {
output.template.addBlock('sayhello', function(blk) {
return 'Hello ' + blk.body + '!';
});
return output.template.renderString('{% sayhello %}World{% endsayhello %}')
.should.be.fulfilledWith('Hello World!');
});
it('should correctly add a block with args', function() {
output.template.addBlock('sayhello_args', function(blk) {
return 'Hello ' + blk.args[0] + '!';
});
return output.template.renderString('{% sayhello_args "World" %}{% endsayhello_args %}')
.should.be.fulfilledWith('Hello World!');
});
it('should correctly add a block with kwargs', function() {
output.template.addBlock('sayhello_kwargs', function(blk) {
return 'Hello ' + blk.kwargs.name + '!';
});
return output.template.renderString('{% sayhello_kwargs name="World" %}{% endsayhello_kwargs %}')
.should.be.fulfilledWith('Hello World!');
});
});
});