UNPKG

urequire-ab-specrunner

Version:

Automagically generates & runs mocha/chai/phantomjs tests from a uRequire `lib` & `specs` build.

410 lines (392 loc) 18.2 kB
// 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); }; };