unexpected
Version:
Extensible BDD assertion toolkit
219 lines (187 loc) • 8.31 kB
JavaScript
/* global global */
var basename = require('path').basename;
var debug = require('debug')('metalsmith-unexpected-markdown');
var dirname = require('path').dirname;
var extname = require('path').extname;
var resolve = require('path').resolve;
var marked = require('marked');
var fs = require('fs');
var extend = require('../lib/utils').extend;
var vm = require('vm');
var unexpected = require('../lib/').clone();
unexpected.installPlugin(require('magicpen-prism'));
var lightExpect = unexpected.clone()
.installPlugin(require('./magicpen-github-syntax-theme'));
var darkExpect = unexpected.clone()
.installPlugin(require('./magicpen-dark-syntax-theme'));
var styleRegex = /style=".*?"/;
module.exports = function plugin(options) {
options = options || {};
var keys = options.keys || [];
function parseFlags(flagsString) {
var flags = {};
flagsString.split(/,/).forEach(function (flagString) {
var m = /(\w+):(\w+)/.exec(flagString);
flags[m[1]] = m[2] === 'true';
});
return flags;
}
return function(files, metalsmith, done){
var exampleTests = {};
Object.keys(files).sort().forEach(function(file){
debug('checking file: %s', file);
if (!markdown(file)) return;
var data = files[file];
var dir = dirname(file);
var html = basename(file, extname(file)) + '.html';
if ('.' !== dir) html = dir + '/' + html;
var exampleExpect = (files[file].theme === 'dark' ? darkExpect : lightExpect).clone();
var lastError = '';
var oldGlobal = extend({}, global);
global.expect = exampleExpect;
var tests = exampleTests[file] = [];
options.renderer = new marked.Renderer();
options.renderer.code = function(code, lang, escaped) {
var m = /^(\w+)#(\w+:\w+(,\w+:\w+)*)/.exec(lang);
var flags = { evaluate: true };
if (m) {
lang = m[1];
extend(flags, parseFlags(m[2]));
}
if (lang === 'js') {
lang = 'javascript';
}
switch (lang) {
case 'javascript':
if (flags.evaluate) {
tests.push({ code: code });
try {
vm.runInThisContext(code);
lastError = '<div class="output"></div>';
} catch (e) {
var errorMessage = e._isUnexpected ?
e.output :
exampleExpect.output.clone().error(e.message);
lastError = errorMessage.toString('html')
.replace(styleRegex, 'class="output"');
}
}
break;
case 'output':
tests[tests.length - 1].output = code;
return lastError;
}
return exampleExpect.output.clone().code(code, 'javascript').toString('html')
.replace(styleRegex, 'class="code ' + this.options.langPrefix + 'javascript"');
};
debug('converting file: %s', file);
var str = marked(data.contents.toString(), options);
Object.keys(global).forEach(function (key) {
if (!(key in oldGlobal)) {
delete global[key];
}
});
extend(global, oldGlobal);
data.contents = new Buffer(str);
keys.forEach(function(key) {
data[key] = marked(data[key], options);
});
delete files[file];
files[html] = data;
});
var pen = unexpected.output.clone();
pen.indentationWidth = 4;
pen.addStyle('escapedString', function (content) {
this.text(JSON.stringify(content).replace(/^"|"$/g, ''));
});
pen.text('/*global describe, it, beforeEach*/').nl();
pen.text('// THIS FILE IS AUTOGENERATED! DO NOT CHANGE IT MANUALLY.').nl();
pen.text('// THIS FILE IS AUTOGENERATED! DO NOT CHANGE IT MANUALLY.').nl();
pen.text('// It is built based on the examples in the documentation folder').nl();
pen.text('// when the documentation site gets build by running "make site-build".').nl();
pen.text('var expect = require("../").clone();').nl(2);
pen.text('expect.addAssertion("to have message", function (expect, subject, value) {').nl();
pen.indentLines();
pen.i().block(function () {
this.text('var message;').nl();
this.text('if (subject._isUnexpected) {').nl();
this.text(' message = subject.output.toString();').nl();
this.text('} else if (subject && Object.prototype.toString.call(subject) === "[object Error]") {').nl();
this.text(' message = subject.message;').nl();
this.text('} else {').nl();
this.text(' message = String(subject);').nl();
this.text('}').nl();
this.text('this.errorMode = "bubble";').nl();
this.text('expect(message, "to equal", value);');
}).nl();
pen.outdentLines();
pen.text('});').nl(2);
pen.text('describe("documentation tests", function () {').nl();
pen.indentLines();
pen.i().text('beforeEach(function () {').nl();
pen.indentLines();
pen.i().text('expect = expect.clone();').nl();
pen.outdentLines();
pen.i().text('});').nl(2);
Object.keys(exampleTests).sort().forEach(function (file, index) {
var tests = exampleTests[file];
if (tests.length === 0) {
return;
}
if (index > 0) {
pen.nl();
}
pen.i().text('it("').text(file).text(' contains correct examples", function () {').nl();
pen.indentLines();
tests.forEach(function (test, index) {
if (index > 0) {
pen.nl();
}
if (test.output) {
pen.i().text('try {').nl();
pen.indentLines();
pen.i().block('text', test.code).nl();
pen.i().text('expect.fail(function (output) {').nl();
pen.indentLines();
pen.i().text('output.error("expected:").nl();').nl();
test.code.split(/\n/).forEach(function (line, index) {
pen.i().text('output.code("').escapedString(line).text('").nl();').nl();
});
pen.i().text('output.error("to throw");').nl();
pen.outdentLines();
pen.i().text('});').nl();
pen.outdentLines();
pen.i().text('} catch (e) {').nl();
pen.indentLines();
pen.i().text('expect(e, "to have message",').nl();
pen.indentLines();
var lines = test.output.split(/\n/);
lines.forEach(function (line, index) {
pen.i().text('"').escapedString(line);
if (index < lines.length - 1) {
pen.text('\\n" +').nl();
} else {
pen.text('"');
}
});
pen.nl();
pen.outdentLines();
pen.i().text(');').nl();
pen.outdentLines();
pen.i().text('}');
} else {
pen.i().block('text', test.code);
}
pen.nl();
});
pen.outdentLines();
pen.i().text('});').nl();
});
pen.outdentLines();
pen.text('});').nl();
fs.writeFile(resolve(__dirname, '..', 'test', 'documentation.spec.js'), pen.toString(), done);
};
};
function markdown(file){
return /\.md|\.markdown/.test(extname(file));
}