urequire-ab-specrunner
Version:
Automagically generates & runs mocha/chai/phantomjs tests from a uRequire `lib` & `specs` build.
410 lines (392 loc) • 18.2 kB
JavaScript
// Generated by CoffeeScript 1.12.4
var When, _, _B, body, comment, div, doctype, execP, fixPathsForNode, fsp, h1, h2, h3, h4, head, html, isWatching, l, li, link, meta, minUrequireVersion, p, pkg, raw, ref, render, renderable, script, spawn, specRunner, style, table, td, teacup, th, tidyP, title, tr, ul, upath,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
minUrequireVersion = "0.7.0-beta.27";
_B = require('uberscore');
_ = require('lodash');
l = new _B.Logger('urequire-ab-specrunner');
_.mixin((require('underscore.string')).exports());
upath = require('upath');
fsp = require('fs-promise');
When = require('./whenFull');
tidyP = When.node.lift(require('htmltidy').tidy);
spawn = require('child_process').spawn;
execP = When.node.lift(require("child_process").exec);
ref = teacup = require('teacup'), renderable = ref.renderable, render = ref.render, html = ref.html, doctype = ref.doctype, head = ref.head, link = ref.link, meta = ref.meta, script = ref.script, p = ref.p, body = ref.body, title = ref.title, h1 = ref.h1, h2 = ref.h2, h3 = ref.h3, h4 = ref.h4, div = ref.div, comment = ref.comment, ul = ref.ul, li = ref.li, raw = ref.raw, table = ref.table, tr = ref.tr, td = ref.td, th = ref.th, style = ref.style;
pkg = JSON.parse(fsp.readFileSync(__dirname + '/../../package.json'));
isWatching = false;
module.exports = specRunner = function(err, specBB, options) {
var _title, bb, chai_path, generateAndSaveHTML, generateHTML, getGlobalsPrint, grunt, i, isAMD, isHTML, isHTMLsaved, len, libBB, libRjsConf, libToSpecPath, mocha_path, ref1, requirejs_path, rjsConf, runMochaShell, specPathHTML, specRunners, specRunnersExecuted, specToLibPath, templates, watchesToBlend, writeHTMLSpec;
if (this.options) {
options = this.options;
} else {
if (!_B.isHash(options)) {
options = {};
}
}
if (!(libBB = specBB.urequire.findBBExecutedBefore(specBB))) {
err = "The library bundleBuilder is missing:\nYou need to build the 'lib' you want to run the specs against, just before the specs.\nI.e execute something like `$ grunt lib spec`, where `spec` has `afterBuild: require('urequire-ab-specrunner')`";
throw new Error('`urequire-ab-specrunner`:' + err);
}
if (require('semver').lt(libBB.urequire.VERSION, minUrequireVersion)) {
throw new Error("Incompatible `urequire` version '" + libBB.urequire.VERSION + "'. You need `urequire` version >= '" + minUrequireVersion + "' for `urequire-ab-specrunner` version '" + pkg.version + "'");
}
grunt = libBB.urequire.grunt;
_B.Logger.addDebugPathLevel('urequire-ab-specrunner', options.debugLevel || 0);
_title = "lib.target:'" + libBB.build.target + "', spec.target:'" + specBB.build.target + "'";
if (grunt && (!isWatching) && (options.watch || _.any([libBB, specBB], function(bb) {
return bb.build.watch.enabled === true;
}))) {
isWatching = true;
watchesToBlend = [
{
before: "urequire:" + libBB.build.target
}, options.watch, specBB.build.watch, libBB.build.watch
];
watchesToBlend.debugLevel = options.debugLevel;
require('urequire-ab-grunt-contrib-watch')(err, specBB, watchesToBlend);
}
if (!(libBB.build.hasChanged || specBB.build.hasChanged)) {
if (isWatching) {
l.ok("No changes for `" + _title + "` while `watch`-ing - not executing.");
return When();
}
}
ref1 = [libBB, specBB];
for (i = 0, len = ref1.length; i < len; i++) {
bb = ref1[i];
if (bb.build.hasErrors) {
if (options.runOnErrors === true) {
l.warn("Executing for `" + _title + "` despite of errors in bundle `" + bb.build.target + "` - cause `runOnErrors: true`");
} else {
l.er("Not executing for `" + _title + "` cause of errors in bundle `" + bb.build.target + "`");
return When();
}
}
}
l.ok("Executing for " + _title + ".");
isAMD = !((libBB.build.template.name === 'combined') && (specBB.build.template.name === 'combined'));
isHTML = !((libBB.build.template.name === 'nodejs') || (specBB.build.template.name === 'nodejs'));
if (isHTML) {
libRjsConf = libBB.build.calcRequireJsConfig('.', null, true);
rjsConf = fixPathsForNode(specBB.build.calcRequireJsConfig(null, libRjsConf, ['mocha', 'chai', 'requirejs'], [libBB.bundle["package"].name]));
l.deb(60, "All rjsConf paths, relative to `specBB.build.dstPath = " + specBB.build.dstPath + " `:\n", rjsConf.paths);
requirejs_path = rjsConf.paths.requirejs[0];
mocha_path = rjsConf.paths.mocha[0];
chai_path = rjsConf.paths.chai[0];
if (isAMD) {
rjsConf = fixPathsForNode(specBB.build.calcRequireJsConfig(libBB.build.dstPath, libRjsConf, true, [libBB.bundle["package"].name]));
}
delete rjsConf.paths.mocha;
delete rjsConf.paths.requirejs;
l.deb(40, "Final needed only `require.config.paths` for " + (isAMD ? "AMD" : "plain <script>") + ":\n", rjsConf.paths);
}
specToLibPath = upath.relative(specBB.build.dstPath, libBB.build.dstPath);
libToSpecPath = upath.relative(libBB.build.dstPath, specBB.build.dstPath);
getGlobalsPrint = function() {
var allGLobals, g;
allGLobals = _.unique(_.filter((_B.arrayize(options.globalsPrint) || []).concat(_.flatten((function() {
var j, len1, ref2, ref3, ref4, results;
ref2 = [libBB, specBB];
results = [];
for (j = 0, len1 = ref2.length; j < len1; j++) {
bb = ref2[j];
results.push(_.map((ref3 = bb.build) != null ? (ref4 = ref3.rjs) != null ? ref4.shim : void 0 : void 0, 'exports').concat(_.reduce(bb.bundle.dependencies.rootExports, (function(allRootExports, re) {
return allRootExports.concat(re);
}), [])).concat(_.reduce(bb.bundle.local_nonNode_depsVars, (function(localVars, lv) {
return localVars.concat(lv);
}), [])));
}
return results;
})()))));
if (!_.isEmpty(allGLobals)) {
l.deb(50, "Discovered ", g = ".globals([" + (_.map(allGLobals, function(exp) {
return "'" + exp + "'";
}).join(', ')) + "])");
return g;
} else {
return '';
}
};
generateHTML = function() {
var HTML, addRow, globalsPrint;
addRow = function(name, lib, spec) {
return tr(function() {
td('.tg-70v4', name);
td('.tg-031e', lib || 'undefined');
return td('.tg-031e', spec || 'undefined');
});
};
globalsPrint = getGlobalsPrint();
HTML = teacup.render(function() {
doctype(5);
return html(function() {
var dep, j, k, len1, mod, ref2;
head(function() {
meta({
charset: 'utf-8'
});
return title('urequire-ab-specrunner:' + _title);
});
body(function() {
h3("Auto generated spec runner by `urequire-ab-specrunner` v'" + pkg.version);
style('text/css', '.tg {border-collapse:collapse;border-spacing:0;border-color:#aabcfe;}\n .tg td{font-family:Arial, sans-serif;font-size:14px;padding:3px 20px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:#aabcfe;color:#669;background-color:#e8edff;}\n .tg th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:3px 20px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:#aabcfe;color:#039;background-color:#b9c9fe;}\n .tg .tg-70v4{background-color:#cbcefb;color:#000000}');
table('.tg', function() {
tr(function() {
td('.tg-70v4', 'Loader');
return td('.tg-031e', isAMD ? "AMD (RequireJS `" + (requirejs_path + '.js') + "`)" : '<script/>');
});
tr(function() {
td('.tg-70v4', '.globals');
return td('.tg-031e', globalsPrint);
});
tr(function() {
td('.tg-70v4', 'loaded deps');
return td('.tg-031e', _.keys(rjsConf.paths).join(', '));
});
return tr(function() {
td('.tg-70v4', 'watch');
return td('.tg-031e', libBB.build.watch.enabled ? "enabled (note: HTML not regenerated)" : 'disabled');
});
});
table('.tg', function() {
tr(function() {
th('.tg-031e');
th('.tg-031e', 'lib');
return th('.tg-031e', 'spec');
});
addRow('build.dstPath', libBB.build.dstPath, specBB.build.dstPath);
addRow('build.dstMainFilename', libBB.build.dstMainFilename, specBB.bundle.main || specBB.build.template.name === 'combined' ? specBB.build.dstMainFilename : 'none (all spec files loaded)');
addRow('build.target', libBB.build.target, specBB.build.target);
addRow('bundle.name', libBB.bundle.name, specBB.bundle.name);
return addRow('build.template', libBB.build.template.name, specBB.build.template.name);
});
return div('#mocha');
});
link({
rel: 'stylesheet',
href: mocha_path + '.css'
});
script({
src: mocha_path + '.js'
});
script({
src: chai_path + '.js'
});
if (options.injectCode) {
script(options.injectCode);
}
if (options.injectRaw) {
raw(options.injectRaw);
}
script("mocha.setup(" + (_.isString(options.mochaSetup) ? options.mochaSetup : _B.isHash(options.mochaSetup) ? JSON.stringify(options.mochaSetup, null, 2) : "'bdd'") + ");");
if (isAMD) {
rjsConf.baseUrl = specToLibPath;
rjsConf.paths[libBB.bundle["package"].name] = upath.trimExt(libBB.build.dstMainFilename);
rjsConf.paths.libToSpecPath = libToSpecPath;
l.deb(30, "Final AMD `require.config`:\n", rjsConf);
script({
src: requirejs_path + '.js'
});
script("require.config(" + (JSON.stringify(rjsConf, null, 2)) + ");");
} else {
comment("Loading all deps as <script>, sorted with shim order");
ref2 = rjsConf.shimSortedDeps;
for (j = 0, len1 = ref2.length; j < len1; j++) {
dep = ref2[j];
if (dep !== 'chai') {
script({
src: rjsConf.paths[dep] + '.js'
});
}
}
comment("Loading library");
script({
src: upath.join(specToLibPath, libBB.build.dstMainFilename)
});
comment("Loading specs");
script({
src: specBB.build.dstMainFilename
});
}
comment("Invoke `mocha.run()` as " + (isAMD ? 'AMD' : 'plain script') + ", taking care of phantomjs");
return script((isAMD ? "require(['" + ((specBB.bundle.main || (specBB.build.template.name === 'combined') ? [specBB.build.dstMainFilename] : (function() {
var ref3, results;
ref3 = specBB.bundle.modules;
results = [];
for (k in ref3) {
mod = ref3[k];
results.push(mod.dstFilename);
}
return results;
})()).map(function(f) {
return upath.join('libToSpecPath/', upath.trimExt(f));
}).join("', '")) + "'], function() {" : 'if (!window.PHANTOMJS) {') + ("\n\n if (window.mochaPhantomJS) {\n mochaPhantomJS.run()" + globalsPrint + ";\n } else {\n mocha.run()" + globalsPrint + ";\n }\n}") + (isAMD ? ");" : ''));
});
});
if (options.tidy) {
return tidyP(HTML);
} else {
return When(HTML);
}
};
specPathHTML = specBB.build.dstPath + "/urequire-ab-specrunner-" + (isAMD ? 'AMD' : 'script') + "-" + libBB.build.target + "_" + specBB.build.target + ".html";
writeHTMLSpec = function(htmlText) {
l.deb(80, "Saving spec HTML as `" + specPathHTML + "`");
return fsp.outputFile(specPathHTML, htmlText);
};
isHTMLsaved = false;
generateAndSaveHTML = function() {
if (isHTMLsaved) {
return When();
} else {
l.deb(50, "Generating spec HTML & saving as `" + specPathHTML + "`");
return When.pipeline([generateHTML, writeHTMLSpec]).then(function() {
l.deb(80, "Saved spec HTML to `" + specPathHTML + "`");
return isHTMLsaved = true;
});
}
};
runMochaShell = function(cmd, filename) {
var mochaParams;
mochaParams = _.filter(((options.mochaOptions || '') + ' ' + filename).split(/\s/));
l.deb(30, "Running shell `" + cmd + " " + (mochaParams.join(' ')) + "`");
if (!options.exec) {
if (process.platform === "win32") {
cmd = cmd.replace(/\//g, '\\') + '.cmd';
}
l.ok("spawn-ing `" + cmd + " " + (mochaParams.join(' ')) + "`");
return When.promise(function(resolve, reject) {
var cp;
cp = spawn(cmd, mochaParams);
cp.stdout.pipe(process.stdout);
cp.stderr.pipe(process.stderr);
return cp.on('close', function(code) {
if (code === 0) {
return resolve();
} else {
return reject(new Error("`urequire-ab-specrunner` error: `" + cmd + "` returned error code " + code));
}
});
});
} else {
l.ok("exec-ing `" + cmd + " " + (mochaParams.join(' ')) + "`");
cmd = cmd + " " + (mochaParams.join(' '));
return execP(cmd).then(function(res) {
return l.log(res[0]);
}, function(err) {
return l.err(err);
});
}
};
specRunners = {
'grunt-mocha': {
reject: ['nodejs'],
run: When.lift(function() {
if (grunt) {
return generateAndSaveHTML().then(function() {
var mocha, task;
mocha = {};
task = libBB.build.target + "_" + specBB.build.target;
mocha[task] = {
src: [specPathHTML]
};
if (!isAMD) {
mocha[task].options = {
run: true
};
}
grunt.config.merge({
'mocha': mocha
});
return grunt.task.run("mocha:" + task);
});
} else {
throw new Error("`urequire-ab-specrunner` error: Can't run specRunner `grunt-mocha` - not running through `grunt` & `grunt-urequire` >= v0.7.0");
}
})
},
'mocha-phantomjs': {
reject: ['nodejs'],
run: function() {
return generateAndSaveHTML().then(function() {
return runMochaShell('mocha-phantomjs', specPathHTML);
});
}
},
'mocha-cli': {
reject: ['AMD'],
run: function() {
var pkgJsonPath;
pkgJsonPath = specBB.build.dstPath + "/node_modules/" + libBB.bundle["package"].name + "/package.json";
l.deb(50, "Saving a fake module of lib into spec's dstPath in `" + pkgJsonPath + "`");
return fsp.outputFile(pkgJsonPath, JSON.stringify({
"name": libBB.bundle["package"].name,
"description": "A fake module for `" + libBB.bundle["package"].name + "` generated by `urequire-ab-specrunner` used by its specs.",
"main": upath.join(libBB.bundle["package"].name.split('/').map(function() {
return '../';
}).join(''), '../', specToLibPath, libBB.build.dstMainFilename)
}, null, 2), 'utf8').then(function() {
return runMochaShell('./node_modules/.bin/mocha', specBB.bundle.main || (specBB.build.template.name === 'combined') ? specBB.build.dstMainFilepath : specBB.build.dstPath + " --recursive");
});
}
}
};
templates = ['UMD', 'UMDplain', 'AMD', 'nodejs', 'combined'];
specRunnersExecuted = 0;
return When.each(_B.arrayize(options.specRunners || ['mocha-cli', 'mocha-phantomjs']), function(name) {
var j, len1, ref2, ref3, sr;
if (!(sr = specRunners[name])) {
throw new Error("`urequire-ab-specrunner` error: unknown mocha specRunner '" + name + "' - exiting.");
} else {
ref2 = [libBB, specBB];
for (j = 0, len1 = ref2.length; j < len1; j++) {
bb = ref2[j];
if (ref3 = bb.build.template.name, indexOf.call(sr.reject || [], ref3) >= 0) {
if (indexOf.call(_B.arrayize(options.specRunners), name) >= 0) {
throw new Error("`urequire-ab-specrunner` error: incompatible runtime - can't run requested specRunner `" + name + "` with bundle `" + bb.build.target + "` build with template `" + bb.build.template.name + "`\n * You can use any template among " + (l.prettify(_.reject(templates, function(t) {
return indexOf.call(sr.reject, t) >= 0;
}))) + "\n * Use the default `specRunners` or any different one among " + (l.prettify(_.keys(_.pick(specRunners, function(spr) {
var ref4;
return ref4 = !bb.build.template.name, indexOf.call(spr.reject || [], ref4) >= 0;
})))));
}
l.debug(80, "Ignoring incompatible runtime specRunner `" + name + "` for bundle `" + bb.build.target + "` build with template `" + bb.build.template.name + "`");
return;
}
}
l.debug(80, "Invoking specRunner `" + name + "` for lib `" + libBB.build.target + "` against spec `" + specBB.build.target + "`");
specRunnersExecuted++;
return sr.run();
}
}).then(function() {
if (specRunnersExecuted === 0) {
return l.warn("No compatible specRunners were found to execute - check your templates compatibility (eg 'nodejs' + 'AMD' work go together).");
} else {
return l.ok("Finished executing " + specRunnersExecuted + " compatible specRunners.");
}
});
};
fixPathsForNode = function(rjsCfg) {
if (rjsCfg.paths.mocha) {
rjsCfg.paths.mocha = _.uniq(_.map(rjsCfg.paths.mocha, function(p) {
return upath.dirname(p) + '/mocha';
}));
}
if (rjsCfg.paths.chai) {
rjsCfg.paths.chai = _.uniq(_.map(rjsCfg.paths.chai, function(p) {
return upath.dirname(p) + '/chai';
}));
}
if (rjsCfg.paths.requirejs) {
rjsCfg.paths.requirejs = _.uniq(_.map(rjsCfg.paths.requirejs, function(p) {
return p.replace('bin/r', 'require');
}));
}
return rjsCfg;
};
specRunner.options = function(opts) {
return function(err, bb) {
return specRunner(err, bb, opts);
};
};