app/assets/javascripts/netzke/testing/mocha/mocha.js in netzke-testing-0.12.3 vs app/assets/javascripts/netzke/testing/mocha/mocha.js in netzke-testing-1.0.0.0.pre

- old
+ new

@@ -1,8 +1,7 @@ ;(function(){ - // CommonJS require() function require(p){ var path = require.resolve(p) , mod = require.modules[path]; @@ -47,20 +46,19 @@ }; }; require.register("browser/debug.js", function(module, exports, require){ - module.exports = function(type){ return function(){ - } }; + }); // module: browser/debug.js require.register("browser/diff.js", function(module, exports, require){ -/* See license.txt for terms of usage */ +/* See LICENSE file for terms of use */ /* * Text diff implementation. * * This library supports the following APIS: @@ -73,10 +71,11 @@ * These methods are based on the implementation proposed in * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 */ var JsDiff = (function() { + /*jshint maxparams: 5*/ function clonePath(path) { return { newPos: path.newPos, components: path.components.slice(0) }; } function removeEmpty(array) { var ret = []; @@ -87,26 +86,25 @@ } return ret; } function escapeHTML(s) { var n = s; - n = n.replace(/&/g, "&amp;"); - n = n.replace(/</g, "&lt;"); - n = n.replace(/>/g, "&gt;"); - n = n.replace(/"/g, "&quot;"); + n = n.replace(/&/g, '&amp;'); + n = n.replace(/</g, '&lt;'); + n = n.replace(/>/g, '&gt;'); + n = n.replace(/"/g, '&quot;'); return n; } - - var fbDiff = function(ignoreWhitespace) { + var Diff = function(ignoreWhitespace) { this.ignoreWhitespace = ignoreWhitespace; }; - fbDiff.prototype = { + Diff.prototype = { diff: function(oldString, newString) { // Handle the identity case (this is due to unrolling editLength == 0 - if (newString == oldString) { + if (newString === oldString) { return [{ value: newString }]; } if (!newString) { return [{ value: oldString, removed: true }]; } @@ -197,66 +195,85 @@ equals: function(left, right) { var reWhitespace = /\S/; if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) { return true; } else { - return left == right; + return left === right; } }, join: function(left, right) { return left + right; }, tokenize: function(value) { return value; } }; - var CharDiff = new fbDiff(); + var CharDiff = new Diff(); - var WordDiff = new fbDiff(true); - WordDiff.tokenize = function(value) { + var WordDiff = new Diff(true); + var WordWithSpaceDiff = new Diff(); + WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) { return removeEmpty(value.split(/(\s+|\b)/)); }; - var CssDiff = new fbDiff(true); + var CssDiff = new Diff(true); CssDiff.tokenize = function(value) { return removeEmpty(value.split(/([{}:;,]|\s+)/)); }; - var LineDiff = new fbDiff(); + var LineDiff = new Diff(); LineDiff.tokenize = function(value) { - return value.split(/^/m); + var retLines = [], + lines = value.split(/^/m); + + for(var i = 0; i < lines.length; i++) { + var line = lines[i], + lastLine = lines[i - 1]; + + // Merge lines that may contain windows new lines + if (line == '\n' && lastLine && lastLine[lastLine.length - 1] === '\r') { + retLines[retLines.length - 1] += '\n'; + } else if (line) { + retLines.push(line); + } + } + + return retLines; }; return { + Diff: Diff, + diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); }, diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); }, + diffWordsWithSpace: function(oldStr, newStr) { return WordWithSpaceDiff.diff(oldStr, newStr); }, diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); }, diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); }, createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { var ret = []; - ret.push("Index: " + fileName); - ret.push("==================================================================="); - ret.push("--- " + fileName + (typeof oldHeader === "undefined" ? "" : "\t" + oldHeader)); - ret.push("+++ " + fileName + (typeof newHeader === "undefined" ? "" : "\t" + newHeader)); + ret.push('Index: ' + fileName); + ret.push('==================================================================='); + ret.push('--- ' + fileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader)); + ret.push('+++ ' + fileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); var diff = LineDiff.diff(oldStr, newStr); if (!diff[diff.length-1].value) { diff.pop(); // Remove trailing newline add } - diff.push({value: "", lines: []}); // Append an empty value to make cleanup easier + diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier function contextLines(lines) { return lines.map(function(entry) { return ' ' + entry; }); } function eofNL(curRange, i, current) { var last = diff[diff.length-2], isLast = i === diff.length-2, - isLastOfType = i === diff.length-3 && (current.added === !last.added || current.removed === !last.removed); + isLastOfType = i === diff.length-3 && (current.added !== last.added || current.removed !== last.removed); // Figure out if this is the last line for the given file and missing NL if (!/\n$/.test(current.value) && (isLast || isLastOfType)) { curRange.push('\\ No newline at end of file'); } @@ -264,11 +281,11 @@ var oldRangeStart = 0, newRangeStart = 0, curRange = [], oldLine = 1, newLine = 1; for (var i = 0; i < diff.length; i++) { var current = diff[i], - lines = current.lines || current.value.replace(/\n$/, "").split("\n"); + lines = current.lines || current.value.replace(/\n$/, '').split('\n'); current.lines = lines; if (current.added || current.removed) { if (!oldRangeStart) { var prev = diff[i-1]; @@ -279,11 +296,11 @@ curRange = contextLines(prev.lines.slice(-4)); oldRangeStart -= curRange.length; newRangeStart -= curRange.length; } } - curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?"+":"-") + entry; })); + curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?'+':'-') + entry; })); eofNL(curRange, i, current); if (current.added) { newLine += lines.length; } else { @@ -297,13 +314,13 @@ curRange.push.apply(curRange, contextLines(lines)); } else { // end the range and output var contextSize = Math.min(lines.length, 4); ret.push( - "@@ -" + oldRangeStart + "," + (oldLine-oldRangeStart+contextSize) - + " +" + newRangeStart + "," + (newLine-newRangeStart+contextSize) - + " @@"); + '@@ -' + oldRangeStart + ',' + (oldLine-oldRangeStart+contextSize) + + ' +' + newRangeStart + ',' + (newLine-newRangeStart+contextSize) + + ' @@'); ret.push.apply(ret, curRange); ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); if (lines.length <= 4) { eofNL(ret, i, current); } @@ -317,41 +334,118 @@ } return ret.join('\n') + '\n'; }, + applyPatch: function(oldStr, uniDiff) { + var diffstr = uniDiff.split('\n'); + var diff = []; + var remEOFNL = false, + addEOFNL = false; + + for (var i = (diffstr[0][0]==='I'?4:0); i < diffstr.length; i++) { + if(diffstr[i][0] === '@') { + var meh = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/); + diff.unshift({ + start:meh[3], + oldlength:meh[2], + oldlines:[], + newlength:meh[4], + newlines:[] + }); + } else if(diffstr[i][0] === '+') { + diff[0].newlines.push(diffstr[i].substr(1)); + } else if(diffstr[i][0] === '-') { + diff[0].oldlines.push(diffstr[i].substr(1)); + } else if(diffstr[i][0] === ' ') { + diff[0].newlines.push(diffstr[i].substr(1)); + diff[0].oldlines.push(diffstr[i].substr(1)); + } else if(diffstr[i][0] === '\\') { + if (diffstr[i-1][0] === '+') { + remEOFNL = true; + } else if(diffstr[i-1][0] === '-') { + addEOFNL = true; + } + } + } + + var str = oldStr.split('\n'); + for (var i = diff.length - 1; i >= 0; i--) { + var d = diff[i]; + for (var j = 0; j < d.oldlength; j++) { + if(str[d.start-1+j] !== d.oldlines[j]) { + return false; + } + } + Array.prototype.splice.apply(str,[d.start-1,+d.oldlength].concat(d.newlines)); + } + + if (remEOFNL) { + while (!str[str.length-1]) { + str.pop(); + } + } else if (addEOFNL) { + str.push(''); + } + return str.join('\n'); + }, + convertChangesToXML: function(changes){ var ret = []; for ( var i = 0; i < changes.length; i++) { var change = changes[i]; if (change.added) { - ret.push("<ins>"); + ret.push('<ins>'); } else if (change.removed) { - ret.push("<del>"); + ret.push('<del>'); } ret.push(escapeHTML(change.value)); if (change.added) { - ret.push("</ins>"); + ret.push('</ins>'); } else if (change.removed) { - ret.push("</del>"); + ret.push('</del>'); } } - return ret.join(""); + return ret.join(''); + }, + + // See: http://code.google.com/p/google-diff-match-patch/wiki/API + convertChangesToDMP: function(changes){ + var ret = [], change; + for ( var i = 0; i < changes.length; i++) { + change = changes[i]; + ret.push([(change.added ? 1 : change.removed ? -1 : 0), change.value]); + } + return ret; } }; })(); -if (typeof module !== "undefined") { +if (typeof module !== 'undefined') { module.exports = JsDiff; } }); // module: browser/diff.js -require.register("browser/events.js", function(module, exports, require){ +require.register("browser/escape-string-regexp.js", function(module, exports, require){ +'use strict'; +var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g; + +module.exports = function (str) { + if (typeof str !== 'string') { + throw new TypeError('Expected a string'); + } + + return str.replace(matchOperatorsRe, '\\$&'); +}; + +}); // module: browser/escape-string-regexp.js + +require.register("browser/events.js", function(module, exports, require){ /** * Module exports. */ exports.EventEmitter = EventEmitter; @@ -525,22 +619,26 @@ return false; } return true; }; + }); // module: browser/events.js require.register("browser/fs.js", function(module, exports, require){ }); // module: browser/fs.js +require.register("browser/glob.js", function(module, exports, require){ + +}); // module: browser/glob.js + require.register("browser/path.js", function(module, exports, require){ }); // module: browser/path.js require.register("browser/progress.js", function(module, exports, require){ - /** * Expose `Progress`. */ module.exports = Progress; @@ -625,62 +723,67 @@ * @param {CanvasRenderingContext2d} ctx * @return {Progress} for chaining */ Progress.prototype.draw = function(ctx){ - var percent = Math.min(this.percent, 100) - , size = this._size - , half = size / 2 - , x = half - , y = half - , rad = half - 1 - , fontSize = this._fontSize; + try { + var percent = Math.min(this.percent, 100) + , size = this._size + , half = size / 2 + , x = half + , y = half + , rad = half - 1 + , fontSize = this._fontSize; - ctx.font = fontSize + 'px ' + this._font; + ctx.font = fontSize + 'px ' + this._font; - var angle = Math.PI * 2 * (percent / 100); - ctx.clearRect(0, 0, size, size); + var angle = Math.PI * 2 * (percent / 100); + ctx.clearRect(0, 0, size, size); - // outer circle - ctx.strokeStyle = '#9f9f9f'; - ctx.beginPath(); - ctx.arc(x, y, rad, 0, angle, false); - ctx.stroke(); + // outer circle + ctx.strokeStyle = '#9f9f9f'; + ctx.beginPath(); + ctx.arc(x, y, rad, 0, angle, false); + ctx.stroke(); - // inner circle - ctx.strokeStyle = '#eee'; - ctx.beginPath(); - ctx.arc(x, y, rad - 1, 0, angle, true); - ctx.stroke(); + // inner circle + ctx.strokeStyle = '#eee'; + ctx.beginPath(); + ctx.arc(x, y, rad - 1, 0, angle, true); + ctx.stroke(); - // text - var text = this._text || (percent | 0) + '%' - , w = ctx.measureText(text).width; + // text + var text = this._text || (percent | 0) + '%' + , w = ctx.measureText(text).width; - ctx.fillText( - text - , x - w / 2 + 1 - , y + fontSize / 2 - 1); - + ctx.fillText( + text + , x - w / 2 + 1 + , y + fontSize / 2 - 1); + } catch (ex) {} //don't fail if we can't render progress return this; }; }); // module: browser/progress.js require.register("browser/tty.js", function(module, exports, require){ - exports.isatty = function(){ return true; }; exports.getWindowSize = function(){ - return [window.innerHeight, window.innerWidth]; + if ('innerHeight' in global) { + return [global.innerHeight, global.innerWidth]; + } else { + // In a Web Worker, the DOM Window is not available. + return [640, 480]; + } }; + }); // module: browser/tty.js require.register("context.js", function(module, exports, require){ - /** * Expose `Context`. */ module.exports = Context; @@ -714,15 +817,30 @@ * @return {Context} self * @api private */ Context.prototype.timeout = function(ms){ + if (arguments.length === 0) return this.runnable().timeout(); this.runnable().timeout(ms); return this; }; /** + * Set test timeout `enabled`. + * + * @param {Boolean} enabled + * @return {Context} self + * @api private + */ + +Context.prototype.enableTimeouts = function (enabled) { + this.runnable().enableTimeouts(enabled); + return this; +}; + + +/** * Set test slowness threshold `ms`. * * @param {Number} ms * @return {Context} self * @api private @@ -732,10 +850,22 @@ this.runnable().slow(ms); return this; }; /** + * Mark a test as skipped. + * + * @return {Context} self + * @api private + */ + +Context.prototype.skip = function(){ + this.runnable().skip(); + return this; +}; + +/** * Inspect the context void of `._runnable`. * * @return {String} * @api private */ @@ -749,11 +879,10 @@ }; }); // module: context.js require.register("hook.js", function(module, exports, require){ - /** * Module dependencies. */ var Runnable = require('./runnable'); @@ -803,21 +932,21 @@ } this._error = err; }; - }); // module: hook.js require.register("interfaces/bdd.js", function(module, exports, require){ - /** * Module dependencies. */ var Suite = require('../suite') - , Test = require('../test'); + , Test = require('../test') + , utils = require('../utils') + , escapeRe = require('browser/escape-string-regexp'); /** * BDD-style interface: * * describe('Array', function(){ @@ -837,50 +966,26 @@ module.exports = function(suite){ var suites = [suite]; suite.on('pre-require', function(context, file, mocha){ - /** - * Execute before running tests. - */ + var common = require('./common')(suites, context); - context.before = function(fn){ - suites[0].beforeAll(fn); - }; - + context.before = common.before; + context.after = common.after; + context.beforeEach = common.beforeEach; + context.afterEach = common.afterEach; + context.run = mocha.options.delay && common.runWithSuite(suite); /** - * Execute after running tests. - */ - - context.after = function(fn){ - suites[0].afterAll(fn); - }; - - /** - * Execute before each test case. - */ - - context.beforeEach = function(fn){ - suites[0].beforeEach(fn); - }; - - /** - * Execute after each test case. - */ - - context.afterEach = function(fn){ - suites[0].afterEach(fn); - }; - - /** * Describe a "suite" with the given `title` * and callback `fn` containing nested suites * and/or tests. */ context.describe = context.context = function(title, fn){ var suite = Suite.create(suites[0], title); + suite.file = file; suites.unshift(suite); fn.call(suite); suites.shift(); return suite; }; @@ -904,33 +1009,37 @@ */ context.describe.only = function(title, fn){ var suite = context.describe(title, fn); mocha.grep(suite.fullTitle()); + return suite; }; /** * Describe a specification or test-case * with the given `title` and callback `fn` * acting as a thunk. */ context.it = context.specify = function(title, fn){ var suite = suites[0]; - if (suite.pending) var fn = null; + if (suite.pending) fn = null; var test = new Test(title, fn); + test.file = file; suite.addTest(test); return test; }; /** * Exclusive test-case. */ context.it.only = function(title, fn){ var test = context.it(title, fn); - mocha.grep(test.fullTitle()); + var reString = '^' + escapeRe(test.fullTitle()) + '$'; + mocha.grep(new RegExp(reString)); + return test; }; /** * Pending test case. */ @@ -938,17 +1047,79 @@ context.xit = context.xspecify = context.it.skip = function(title){ context.it(title); }; + }); }; }); // module: interfaces/bdd.js -require.register("interfaces/exports.js", function(module, exports, require){ +require.register("interfaces/common.js", function(module, exports, require){ +/** + * Functions common to more than one interface + * @module lib/interfaces/common + */ +'use strict'; + +module.exports = function (suites, context) { + + return { + /** + * This is only present if flag --delay is passed into Mocha. It triggers + * root suite execution. Returns a function which runs the root suite. + */ + runWithSuite: function runWithSuite(suite) { + return function run() { + suite.run(); + }; + }, + + /** + * Execute before running tests. + */ + before: function (name, fn) { + suites[0].beforeAll(name, fn); + }, + + /** + * Execute after running tests. + */ + after: function (name, fn) { + suites[0].afterAll(name, fn); + }, + + /** + * Execute before each test case. + */ + beforeEach: function (name, fn) { + suites[0].beforeEach(name, fn); + }, + + /** + * Execute after each test case. + */ + afterEach: function (name, fn) { + suites[0].afterEach(name, fn); + }, + + test: { + /** + * Pending test case. + */ + skip: function (title) { + context.test(title); + } + } + } +}; + +}); // module: interfaces/common.js + +require.register("interfaces/exports.js", function(module, exports, require){ /** * Module dependencies. */ var Suite = require('../suite') @@ -974,11 +1145,11 @@ module.exports = function(suite){ var suites = [suite]; suite.on('require', visit); - function visit(obj) { + function visit(obj, file) { var suite; for (var key in obj) { if ('function' == typeof obj[key]) { var fn = obj[key]; switch (key) { @@ -993,40 +1164,43 @@ break; case 'afterEach': suites[0].afterEach(fn); break; default: - suites[0].addTest(new Test(key, fn)); + var test = new Test(key, fn); + test.file = file; + suites[0].addTest(test); } } else { - var suite = Suite.create(suites[0], key); + suite = Suite.create(suites[0], key); suites.unshift(suite); visit(obj[key]); suites.shift(); } } } }; + }); // module: interfaces/exports.js require.register("interfaces/index.js", function(module, exports, require){ - exports.bdd = require('./bdd'); exports.tdd = require('./tdd'); exports.qunit = require('./qunit'); exports.exports = require('./exports'); }); // module: interfaces/index.js require.register("interfaces/qunit.js", function(module, exports, require){ - /** * Module dependencies. */ var Suite = require('../suite') - , Test = require('../test'); + , Test = require('../test') + , escapeRe = require('browser/escape-string-regexp') + , utils = require('../utils'); /** * QUnit-style interface: * * suite('Array'); @@ -1052,76 +1226,79 @@ */ module.exports = function(suite){ var suites = [suite]; - suite.on('pre-require', function(context){ + suite.on('pre-require', function(context, file, mocha){ - /** - * Execute before running tests. - */ + var common = require('./common')(suites, context); - context.before = function(fn){ - suites[0].beforeAll(fn); - }; - + context.before = common.before; + context.after = common.after; + context.beforeEach = common.beforeEach; + context.afterEach = common.afterEach; + context.run = mocha.options.delay && common.runWithSuite(suite); /** - * Execute after running tests. + * Describe a "suite" with the given `title`. */ - context.after = function(fn){ - suites[0].afterAll(fn); + context.suite = function(title){ + if (suites.length > 1) suites.shift(); + var suite = Suite.create(suites[0], title); + suite.file = file; + suites.unshift(suite); + return suite; }; /** - * Execute before each test case. + * Exclusive test-case. */ - context.beforeEach = function(fn){ - suites[0].beforeEach(fn); + context.suite.only = function(title, fn){ + var suite = context.suite(title, fn); + mocha.grep(suite.fullTitle()); }; /** - * Execute after each test case. + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. */ - context.afterEach = function(fn){ - suites[0].afterEach(fn); + context.test = function(title, fn){ + var test = new Test(title, fn); + test.file = file; + suites[0].addTest(test); + return test; }; /** - * Describe a "suite" with the given `title`. + * Exclusive test-case. */ - context.suite = function(title){ - if (suites.length > 1) suites.shift(); - var suite = Suite.create(suites[0], title); - suites.unshift(suite); + context.test.only = function(title, fn){ + var test = context.test(title, fn); + var reString = '^' + escapeRe(test.fullTitle()) + '$'; + mocha.grep(new RegExp(reString)); }; - /** - * Describe a specification or test-case - * with the given `title` and callback `fn` - * acting as a thunk. - */ + context.test.skip = common.test.skip; - context.test = function(title, fn){ - suites[0].addTest(new Test(title, fn)); - }; }); }; }); // module: interfaces/qunit.js require.register("interfaces/tdd.js", function(module, exports, require){ - /** * Module dependencies. */ var Suite = require('../suite') - , Test = require('../test'); + , Test = require('../test') + , escapeRe = require('browser/escape-string-regexp') + , utils = require('../utils'); /** * TDD-style interface: * * suite('Array', function(){ @@ -1149,57 +1326,44 @@ module.exports = function(suite){ var suites = [suite]; suite.on('pre-require', function(context, file, mocha){ - /** - * Execute before each test case. - */ + var common = require('./common')(suites, context); - context.setup = function(fn){ - suites[0].beforeEach(fn); - }; - + context.setup = common.beforeEach; + context.teardown = common.afterEach; + context.suiteSetup = common.before; + context.suiteTeardown = common.after; + context.run = mocha.options.delay && common.runWithSuite(suite); /** - * Execute after each test case. - */ - - context.teardown = function(fn){ - suites[0].afterEach(fn); - }; - - /** - * Execute before the suite. - */ - - context.suiteSetup = function(fn){ - suites[0].beforeAll(fn); - }; - - /** - * Execute after the suite. - */ - - context.suiteTeardown = function(fn){ - suites[0].afterAll(fn); - }; - - /** * Describe a "suite" with the given `title` * and callback `fn` containing nested suites * and/or tests. */ context.suite = function(title, fn){ var suite = Suite.create(suites[0], title); + suite.file = file; suites.unshift(suite); fn.call(suite); suites.shift(); return suite; }; /** + * Pending suite. + */ + context.suite.skip = function(title, fn) { + var suite = Suite.create(suites[0], title); + suite.pending = true; + suites.unshift(suite); + fn.call(suite); + suites.shift(); + }; + + /** * Exclusive test-case. */ context.suite.only = function(title, fn){ var suite = context.suite(title, fn); @@ -1211,31 +1375,29 @@ * with the given `title` and callback `fn` * acting as a thunk. */ context.test = function(title, fn){ + var suite = suites[0]; + if (suite.pending) fn = null; var test = new Test(title, fn); - suites[0].addTest(test); + test.file = file; + suite.addTest(test); return test; }; /** * Exclusive test-case. */ context.test.only = function(title, fn){ var test = context.test(title, fn); - mocha.grep(test.fullTitle()); + var reString = '^' + escapeRe(test.fullTitle()) + '$'; + mocha.grep(new RegExp(reString)); }; - /** - * Pending test case. - */ - - context.test.skip = function(title){ - context.test(title); - }; + context.test.skip = common.test.skip; }); }; }); // module: interfaces/tdd.js @@ -1249,19 +1411,30 @@ /** * Module dependencies. */ var path = require('browser/path') + , escapeRe = require('browser/escape-string-regexp') , utils = require('./utils'); /** * Expose `Mocha`. */ exports = module.exports = Mocha; /** + * To require local UIs and reporters when running in node. + */ + +if (typeof process !== 'undefined' && typeof process.cwd === 'function') { + var join = path.join + , cwd = process.cwd(); + module.paths.push(cwd, join(cwd, 'node_modules')); +} + +/** * Expose internals. */ exports.utils = utils; exports.interfaces = require('./interfaces'); @@ -1289,33 +1462,53 @@ * Setup mocha with `options`. * * Options: * * - `ui` name "bdd", "tdd", "exports" etc - * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` + * - `reporter` reporter instance, defaults to `mocha.reporters.spec` * - `globals` array of accepted globals * - `timeout` timeout in milliseconds * - `bail` bail on the first test failure * - `slow` milliseconds to wait before considering a test slow * - `ignoreLeaks` ignore global leaks + * - `fullTrace` display the full stack-trace on failing * - `grep` string or regexp to filter tests with * * @param {Object} options * @api public */ function Mocha(options) { options = options || {}; this.files = []; this.options = options; - this.grep(options.grep); + if (options.grep) this.grep(new RegExp(options.grep)); + if (options.fgrep) this.grep(options.fgrep); this.suite = new exports.Suite('', new exports.Context); this.ui(options.ui); this.bail(options.bail); - this.reporter(options.reporter); - if (options.timeout) this.timeout(options.timeout); + this.reporter(options.reporter, options.reporterOptions); + if (null != options.timeout) this.timeout(options.timeout); + this.useColors(options.useColors); + if (options.enableTimeouts !== null) this.enableTimeouts(options.enableTimeouts); if (options.slow) this.slow(options.slow); + + this.suite.on('pre-require', function (context) { + exports.afterEach = context.afterEach || context.teardown; + exports.after = context.after || context.suiteTeardown; + exports.beforeEach = context.beforeEach || context.setup; + exports.before = context.before || context.suiteSetup; + exports.describe = context.describe || context.suite; + exports.it = context.it || context.test; + exports.setup = context.setup || context.beforeEach; + exports.suiteSetup = context.suiteSetup || context.before; + exports.suiteTeardown = context.suiteTeardown || context.after; + exports.suite = context.suite || context.describe; + exports.teardown = context.teardown || context.afterEach; + exports.test = context.test || context.it; + exports.run = context.run; + }); } /** * Enable or disable bailing on the first failure. * @@ -1340,28 +1533,36 @@ this.files.push(file); return this; }; /** - * Set reporter to `reporter`, defaults to "dot". + * Set reporter to `reporter`, defaults to "spec". * * @param {String|Function} reporter name or constructor + * @param {Object} reporterOptions optional options * @api public */ - -Mocha.prototype.reporter = function(reporter){ +Mocha.prototype.reporter = function(reporter, reporterOptions){ if ('function' == typeof reporter) { this._reporter = reporter; } else { - reporter = reporter || 'dot'; - try { - this._reporter = require('./reporters/' + reporter); - } catch (err) { - this._reporter = require(reporter); + reporter = reporter || 'spec'; + var _reporter; + try { _reporter = require('./reporters/' + reporter); } catch (err) {} + if (!_reporter) try { _reporter = require(reporter); } catch (err) { + err.message.indexOf('Cannot find module') !== -1 + ? console.warn('"' + reporter + '" reporter not found') + : console.warn('"' + reporter + '" reporter blew up with error:\n' + err.stack); } - if (!this._reporter) throw new Error('invalid reporter "' + reporter + '"'); + if (!_reporter && reporter === 'teamcity') + console.warn('The Teamcity reporter was moved to a package named ' + + 'mocha-teamcity-reporter ' + + '(https://npmjs.org/package/mocha-teamcity-reporter).'); + if (!_reporter) throw new Error('invalid reporter "' + reporter + '"'); + this._reporter = _reporter; } + this.options.reporterOptions = reporterOptions; return this; }; /** * Set test UI `name`, defaults to "bdd". @@ -1371,10 +1572,11 @@ */ Mocha.prototype.ui = function(name){ name = name || 'bdd'; this._ui = exports.interfaces[name]; + if (!this._ui) try { this._ui = require(name); } catch (err) {} if (!this._ui) throw new Error('invalid interface "' + name + '"'); this._ui = this._ui(this.suite); return this; }; @@ -1429,11 +1631,11 @@ * @api public */ Mocha.prototype.grep = function(re){ this.options.grep = 'string' == typeof re - ? new RegExp(utils.escapeRegexp(re)) + ? new RegExp(escapeRe(re)) : re; return this; }; /** @@ -1449,16 +1651,17 @@ }; /** * Ignore global leaks. * + * @param {Boolean} ignore * @return {Mocha} * @api public */ -Mocha.prototype.ignoreLeaks = function(){ - this.options.ignoreLeaks = true; +Mocha.prototype.ignoreLeaks = function(ignore){ + this.options.ignoreLeaks = !!ignore; return this; }; /** * Enable global leak checking. @@ -1471,10 +1674,22 @@ this.options.ignoreLeaks = false; return this; }; /** + * Display long stack-trace on failing + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.fullTrace = function() { + this.options.fullStackTrace = true; + return this; +}; + +/** * Enable growl support. * * @return {Mocha} * @api public */ @@ -1496,10 +1711,40 @@ this.options.globals = (this.options.globals || []).concat(globals); return this; }; /** + * Emit color output. + * + * @param {Boolean} colors + * @return {Mocha} + * @api public + */ + +Mocha.prototype.useColors = function(colors){ + if (colors !== undefined) { + this.options.useColors = colors; + } + return this; +}; + +/** + * Use inline diffs rather than +/-. + * + * @param {Boolean} inlineDiffs + * @return {Mocha} + * @api public + */ + +Mocha.prototype.useInlineDiffs = function(inlineDiffs) { + this.options.useInlineDiffs = arguments.length && inlineDiffs != undefined + ? inlineDiffs + : false; + return this; +}; + +/** * Set the timeout in milliseconds. * * @param {Number} timeout * @return {Mocha} * @api public @@ -1522,10 +1767,25 @@ this.suite.slow(slow); return this; }; /** + * Enable timeouts. + * + * @param {Boolean} enabled + * @return {Mocha} + * @api public + */ + +Mocha.prototype.enableTimeouts = function(enabled) { + this.suite.enableTimeouts(arguments.length && enabled !== undefined + ? enabled + : true); + return this +}; + +/** * Makes all tests async (accepting a callback) * * @return {Mocha} * @api public */ @@ -1534,126 +1794,208 @@ this.options.asyncOnly = true; return this; }; /** + * Disable syntax highlighting (in browser). + * @returns {Mocha} + * @api public + */ +Mocha.prototype.noHighlighting = function() { + this.options.noHighlighting = true; + return this; +}; + +/** + * Delay root suite execution. + * @returns {Mocha} + * @api public + */ +Mocha.prototype.delay = function delay() { + this.options.delay = true; + return this; +}; + +/** * Run tests and invoke `fn()` when complete. * * @param {Function} fn * @return {Runner} * @api public */ - Mocha.prototype.run = function(fn){ if (this.files.length) this.loadFiles(); var suite = this.suite; var options = this.options; - var runner = new exports.Runner(suite); - var reporter = new this._reporter(runner); - runner.ignoreLeaks = options.ignoreLeaks; + options.files = this.files; + var runner = new exports.Runner(suite, options.delay); + var reporter = new this._reporter(runner, options); + runner.ignoreLeaks = false !== options.ignoreLeaks; + runner.fullStackTrace = options.fullStackTrace; runner.asyncOnly = options.asyncOnly; if (options.grep) runner.grep(options.grep, options.invert); if (options.globals) runner.globals(options.globals); if (options.growl) this._growl(runner, reporter); - return runner.run(fn); + if (options.useColors !== undefined) { + exports.reporters.Base.useColors = options.useColors; + } + exports.reporters.Base.inlineDiffs = options.useInlineDiffs; + + function done(failures) { + if (reporter.done) { + reporter.done(failures, fn); + } else fn && fn(failures); + } + + return runner.run(done); }; }); // module: mocha.js require.register("ms.js", function(module, exports, require){ - /** * Helpers. */ var s = 1000; var m = s * 60; var h = m * 60; var d = h * 24; +var y = d * 365.25; /** * Parse or format the given `val`. * + * Options: + * + * - `long` verbose formatting [false] + * * @param {String|Number} val + * @param {Object} options * @return {String|Number} * @api public */ -module.exports = function(val){ +module.exports = function(val, options){ + options = options || {}; if ('string' == typeof val) return parse(val); - return format(val); -} + return options['long'] ? longFormat(val) : shortFormat(val); +}; /** * Parse the given `str` and return milliseconds. * * @param {String} str * @return {Number} * @api private */ function parse(str) { - var m = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); - if (!m) return; - var n = parseFloat(m[1]); - var type = (m[2] || 'ms').toLowerCase(); + var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); + if (!match) return; + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); switch (type) { case 'years': case 'year': case 'y': - return n * 31557600000; + return n * y; case 'days': case 'day': case 'd': - return n * 86400000; + return n * d; case 'hours': case 'hour': case 'h': - return n * 3600000; + return n * h; case 'minutes': case 'minute': case 'm': - return n * 60000; + return n * m; case 'seconds': case 'second': case 's': - return n * 1000; + return n * s; case 'ms': return n; } } /** - * Format the given `ms`. + * Short format for `ms`. * * @param {Number} ms * @return {String} - * @api public + * @api private */ -function format(ms) { - if (ms == d) return Math.round(ms / d) + ' day'; - if (ms > d) return Math.round(ms / d) + ' days'; - if (ms == h) return Math.round(ms / h) + ' hour'; - if (ms > h) return Math.round(ms / h) + ' hours'; - if (ms == m) return Math.round(ms / m) + ' minute'; - if (ms > m) return Math.round(ms / m) + ' minutes'; - if (ms == s) return Math.round(ms / s) + ' second'; - if (ms > s) return Math.round(ms / s) + ' seconds'; - return ms + ' ms'; +function shortFormat(ms) { + if (ms >= d) return Math.round(ms / d) + 'd'; + if (ms >= h) return Math.round(ms / h) + 'h'; + if (ms >= m) return Math.round(ms / m) + 'm'; + if (ms >= s) return Math.round(ms / s) + 's'; + return ms + 'ms'; } + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function longFormat(ms) { + return plural(ms, d, 'day') + || plural(ms, h, 'hour') + || plural(ms, m, 'minute') + || plural(ms, s, 'second') + || ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, n, name) { + if (ms < n) return; + if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name; + return Math.ceil(ms / n) + ' ' + name + 's'; +} + }); // module: ms.js -require.register("reporters/base.js", function(module, exports, require){ +require.register("pending.js", function(module, exports, require){ /** + * Expose `Pending`. + */ + +module.exports = Pending; + +/** + * Initialize a new `Pending` error with the given message. + * + * @param {String} message + */ + +function Pending(message) { + this.message = message; +} + +}); // module: pending.js + +require.register("reporters/base.js", function(module, exports, require){ +/** * Module dependencies. */ var tty = require('browser/tty') , diff = require('browser/diff') - , ms = require('../ms'); + , ms = require('../ms') + , utils = require('../utils') + , supportsColor = process.env ? require('supports-color') : null; /** * Save timer references to avoid Sinon interfering (see GH-237). */ @@ -1674,16 +2016,24 @@ */ exports = module.exports = Base; /** - * Enable coloring by default. + * Enable coloring by default, except in the browser interface. */ -exports.useColors = isatty; +exports.useColors = process.env + ? (supportsColor || (process.env.MOCHA_COLORS !== undefined)) + : false; /** + * Inline diffs instead of +/- + */ + +exports.inlineDiffs = false; + +/** * Default color map. */ exports.colors = { 'pass': 90 @@ -1735,11 +2085,11 @@ * @return {String} * @api private */ var color = exports.color = function(type, str) { - if (!exports.useColors) return str; + if (!exports.useColors) return String(str); return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; }; /** * Expose term window size, with some @@ -1759,28 +2109,32 @@ * that are common among reporters. */ exports.cursor = { hide: function(){ - process.stdout.write('\u001b[?25l'); + isatty && process.stdout.write('\u001b[?25l'); }, show: function(){ - process.stdout.write('\u001b[?25h'); + isatty && process.stdout.write('\u001b[?25h'); }, deleteLine: function(){ - process.stdout.write('\u001b[2K'); + isatty && process.stdout.write('\u001b[2K'); }, beginningOfLine: function(){ - process.stdout.write('\u001b[0G'); + isatty && process.stdout.write('\u001b[0G'); }, CR: function(){ - exports.cursor.deleteLine(); - exports.cursor.beginningOfLine(); + if (isatty) { + exports.cursor.deleteLine(); + exports.cursor.beginningOfLine(); + } else { + process.stdout.write('\r'); + } } }; /** * Outut the given `failures` as a list. @@ -1788,71 +2142,64 @@ * @param {Array} failures * @api public */ exports.list = function(failures){ - console.error(); + console.log(); failures.forEach(function(test, i){ + console.log("test ", test); // format var fmt = color('error title', ' %s) %s:\n') + color('error message', ' %s') + color('error stack', '\n%s\n'); // msg var err = test.err , message = err.message || '' , stack = err.stack || message - , index = stack.indexOf(message) + message.length - , msg = stack.slice(0, index) + , index = stack.indexOf(message) , actual = err.actual , expected = err.expected , escape = true; + if (index === -1) { + msg = message; + } else { + index += message.length; + msg = stack.slice(0, index); + // remove msg from stack + stack = stack.slice(index + 1); + } - // explicitly show diff - if (err.showDiff) { - escape = false; - err.actual = actual = JSON.stringify(actual, null, 2); - err.expected = expected = JSON.stringify(expected, null, 2); + // uncaught + if (err.uncaught) { + msg = 'Uncaught ' + msg; } + // explicitly show diff + if (err.showDiff !== false && sameType(actual, expected) + && expected !== undefined) { - // actual / expected diff - if ('string' == typeof actual && 'string' == typeof expected) { - var len = Math.max(actual.length, expected.length); - - if (len < 20) msg = errorDiff(err, 'Chars', escape); - else msg = errorDiff(err, 'Words', escape); - - // linenos - var lines = msg.split('\n'); - if (lines.length > 4) { - var width = String(lines.length).length; - msg = lines.map(function(str, i){ - return pad(++i, width) + ' |' + ' ' + str; - }).join('\n'); + if ('string' !== typeof actual) { + escape = false; + err.actual = actual = utils.stringify(actual); + err.expected = expected = utils.stringify(expected); } - // legend - msg = '\n' - + color('diff removed', 'actual') - + ' ' - + color('diff added', 'expected') - + '\n\n' - + msg - + '\n'; + fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n'); + var match = message.match(/^([^:]+): expected/); + msg = '\n ' + color('error message', match ? match[1] : msg); - // indent - msg = msg.replace(/^/gm, ' '); - - fmt = color('error title', ' %s) %s:\n%s') - + color('error stack', '\n%s\n'); + if (exports.inlineDiffs) { + msg += inlineDiff(err, escape); + } else { + msg += unifiedDiff(err, escape); + } } - // indent stack trace without msg - stack = stack.slice(index ? index + 1 : index) - .replace(/^/gm, ' '); + // indent stack trace + stack = stack.replace(/^/gm, ' '); - console.error(fmt, (i + 1), test.fullTitle(), msg, stack); + console.log(fmt, (i + 1), test.fullTitle(), msg, stack); }); }; /** * Initialize a new `Base` reporter. @@ -1926,54 +2273,43 @@ * * @api public */ Base.prototype.epilogue = function(){ - var stats = this.stats - , fmt - , tests; + var stats = this.stats; + var tests; + var fmt; console.log(); - function pluralize(n) { - return 1 == n ? 'test' : 'tests'; - } - - // failure - if (stats.failures) { - fmt = color('bright fail', ' ' + exports.symbols.err) - + color('fail', ' %d of %d %s failed') - + color('light', ':') - - console.error(fmt, - stats.failures, - this.runner.total, - pluralize(this.runner.total)); - - Base.list(this.failures); - console.error(); - return; - } - - // pass + // passes fmt = color('bright pass', ' ') - + color('green', ' %d %s complete') + + color('green', ' %d passing') + color('light', ' (%s)'); console.log(fmt, - stats.tests || 0, - pluralize(stats.tests), + stats.passes || 0, ms(stats.duration)); // pending if (stats.pending) { fmt = color('pending', ' ') - + color('pending', ' %d %s pending'); + + color('pending', ' %d pending'); - console.log(fmt, stats.pending, pluralize(stats.pending)); + console.log(fmt, stats.pending); } + // failures + if (stats.failures) { + fmt = color('fail', ' %d failing'); + + console.log(fmt, stats.failures); + + Base.list(this.failures); + console.log(); + } + console.log(); }; /** * Pad the given `str` to `len`. @@ -1987,33 +2323,109 @@ function pad(str, len) { str = String(str); return Array(len - str.length + 1).join(' ') + str; } + /** + * Returns an inline diff between 2 strings with coloured ANSI output + * + * @param {Error} Error with actual/expected + * @return {String} Diff + * @api private + */ + +function inlineDiff(err, escape) { + var msg = errorDiff(err, 'WordsWithSpace', escape); + + // linenos + var lines = msg.split('\n'); + if (lines.length > 4) { + var width = String(lines.length).length; + msg = lines.map(function(str, i){ + return pad(++i, width) + ' |' + ' ' + str; + }).join('\n'); + } + + // legend + msg = '\n' + + color('diff removed', 'actual') + + ' ' + + color('diff added', 'expected') + + '\n\n' + + msg + + '\n'; + + // indent + msg = msg.replace(/^/gm, ' '); + return msg; +} + +/** + * Returns a unified diff between 2 strings + * + * @param {Error} Error with actual/expected + * @return {String} Diff + * @api private + */ + +function unifiedDiff(err, escape) { + var indent = ' '; + function cleanUp(line) { + if (escape) { + line = escapeInvisibles(line); + } + if (line[0] === '+') return indent + colorLines('diff added', line); + if (line[0] === '-') return indent + colorLines('diff removed', line); + if (line.match(/\@\@/)) return null; + if (line.match(/\\ No newline/)) return null; + else return indent + line; + } + function notBlank(line) { + return line != null; + } + var msg = diff.createPatch('string', err.actual, err.expected); + var lines = msg.split('\n').splice(4); + return '\n ' + + colorLines('diff added', '+ expected') + ' ' + + colorLines('diff removed', '- actual') + + '\n\n' + + lines.map(cleanUp).filter(notBlank).join('\n'); +} + +/** * Return a character diff for `err`. * * @param {Error} err * @return {String} * @api private */ function errorDiff(err, type, escape) { - return diff['diff' + type](err.actual, err.expected).map(function(str){ - if (escape) { - str.value = str.value - .replace(/\t/g, '<tab>') - .replace(/\r/g, '<CR>') - .replace(/\n/g, '<LF>\n'); - } + var actual = escape ? escapeInvisibles(err.actual) : err.actual; + var expected = escape ? escapeInvisibles(err.expected) : err.expected; + return diff['diff' + type](actual, expected).map(function(str){ if (str.added) return colorLines('diff added', str.value); if (str.removed) return colorLines('diff removed', str.value); return str.value; }).join(''); } /** + * Returns a string with all invisible characters in plain text + * + * @param {String} line + * @return {String} + * @api private + */ +function escapeInvisibles(line) { + return line.replace(/\t/g, '<tab>') + .replace(/\r/g, '<CR>') + .replace(/\n/g, '<LF>\n'); +} + +/** * Color lines for `str`, using the color `name`. * * @param {String} name * @param {String} str * @return {String} @@ -2024,14 +2436,28 @@ return str.split('\n').map(function(str){ return color(name, str); }).join('\n'); } +/** + * Check that a / b have the same type. + * + * @param {Object} a + * @param {Object} b + * @return {Boolean} + * @api private + */ + +function sameType(a, b) { + a = Object.prototype.toString.call(a); + b = Object.prototype.toString.call(b); + return a == b; +} + }); // module: reporters/base.js require.register("reporters/doc.js", function(module, exports, require){ - /** * Module dependencies. */ var Base = require('./base') @@ -2082,16 +2508,22 @@ runner.on('pass', function(test){ console.log('%s <dt>%s</dt>', indent(), utils.escape(test.title)); var code = utils.escape(utils.clean(test.fn.toString())); console.log('%s <dd><pre><code>%s</code></pre></dd>', indent(), code); }); + + runner.on('fail', function(test, err){ + console.log('%s <dt class="error">%s</dt>', indent(), utils.escape(test.title)); + var code = utils.escape(utils.clean(test.fn.toString())); + console.log('%s <dd class="error"><pre><code>%s</code></pre></dd>', indent(), code); + console.log('%s <dd class="error">%s</dd>', indent(), utils.escape(err)); + }); } }); // module: reporters/doc.js require.register("reporters/dot.js", function(module, exports, require){ - /** * Module dependencies. */ var Base = require('./base') @@ -2114,17 +2546,18 @@ Base.call(this, runner); var self = this , stats = this.stats , width = Base.window.width * .75 | 0 - , n = 0; + , n = -1; runner.on('start', function(){ - process.stdout.write('\n '); + process.stdout.write('\n'); }); runner.on('pending', function(test){ + if (++n % width == 0) process.stdout.write('\n '); process.stdout.write(color('pending', Base.symbols.dot)); }); runner.on('pass', function(test){ if (++n % width == 0) process.stdout.write('\n '); @@ -2153,14 +2586,14 @@ function F(){}; F.prototype = Base.prototype; Dot.prototype = new F; Dot.prototype.constructor = Dot; + }); // module: reporters/dot.js require.register("reporters/html-cov.js", function(module, exports, require){ - /** * Module dependencies. */ var JSONCov = require('./json-cov') @@ -2207,14 +2640,14 @@ if (n >= 75) return 'high'; if (n >= 50) return 'medium'; if (n >= 25) return 'low'; return 'terrible'; } + }); // module: reporters/html-cov.js require.register("reporters/html.js", function(module, exports, require){ - /** * Module dependencies. */ var Base = require('./base') @@ -2231,11 +2664,11 @@ , setInterval = global.setInterval , clearTimeout = global.clearTimeout , clearInterval = global.clearInterval; /** - * Expose `Doc`. + * Expose `HTML`. */ exports = module.exports = HTML; /** @@ -2248,17 +2681,17 @@ + '<li class="failures"><a href="#">failures:</a> <em>0</em></li>' + '<li class="duration">duration: <em>0</em>s</li>' + '</ul>'; /** - * Initialize a new `Doc` reporter. + * Initialize a new `HTML` reporter. * * @param {Runner} runner * @api public */ -function HTML(runner, root) { +function HTML(runner) { Base.call(this, runner); var self = this , stats = this.stats , total = runner.total @@ -2272,13 +2705,12 @@ , canvas = stat.getElementsByTagName('canvas')[0] , report = fragment('<ul id="mocha-report"></ul>') , stack = [report] , progress , ctx + , root = document.getElementById('mocha'); - root = root || document.getElementById('mocha'); - if (canvas.getContext) { var ratio = window.devicePixelRatio || 1; canvas.style.width = canvas.width; canvas.style.height = canvas.height; canvas.width *= ratio; @@ -2313,11 +2745,11 @@ runner.on('suite', function(suite){ if (suite.root) return; // suite - var url = '?grep=' + encodeURIComponent(suite.fullTitle()); + var url = self.suiteURL(suite); var el = fragment('<li class="suite"><h1><a href="%s">%s</a></h1></li>', url, escape(suite.title)); // container stack[0].appendChild(el); stack.unshift(document.createElement('ul')); @@ -2332,12 +2764,10 @@ runner.on('fail', function(test, err){ if ('hook' == test.type) runner.emit('test end', test); }); runner.on('test end', function(test){ - window.scrollTo(0, document.body.scrollHeight); - // TODO: add to stats var percent = stats.tests / this.total * 100 | 0; if (progress) progress.update(percent).draw(ctx); // update stats @@ -2346,15 +2776,16 @@ text(failures, stats.failures); text(duration, (ms / 1000).toFixed(2)); // test if ('passed' == test.state) { - var el = fragment('<li class="test pass %e"><h2>%e<span class="duration">%ems</span> <a href="?grep=%e" class="replay">‣</a></h2></li>', test.speed, test.title, test.duration, encodeURIComponent(test.fullTitle())); + var url = self.testURL(test); + var el = fragment('<li class="test pass %e"><h2>%e<span class="duration">%ems</span> <a href="%s" class="replay">‣</a></h2></li>', test.speed, test.title, test.duration, url); } else if (test.pending) { var el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title); } else { - var el = fragment('<li class="test fail"><h2>%e <a href="?grep=%e" class="replay">‣</a></h2></li>', test.title, encodeURIComponent(test.fullTitle())); + var el = fragment('<li class="test fail"><h2>%e <a href="%e" class="replay">‣</a></h2></li>', test.title, self.testURL(test)); var str = test.err.stack || test.err.toString(); // FF / Opera do not add the message if (!~str.indexOf(test.err.message)) { str = test.err.message + '\n' + str; @@ -2392,10 +2823,45 @@ if (stack[0]) stack[0].appendChild(el); }); } /** + * Makes a URL, preserving querystring ("search") parameters. + * @param {string} s + * @returns {string} your new URL + */ +var makeUrl = function makeUrl(s) { + var search = window.location.search; + + // Remove previous grep query parameter if present + if (search) { + search = search.replace(/[?&]grep=[^&\s]*/g, '').replace(/^&/, '?'); + } + + return window.location.pathname + (search ? search + '&' : '?' ) + 'grep=' + encodeURIComponent(s); +}; + +/** + * Provide suite URL + * + * @param {Object} [suite] + */ +HTML.prototype.suiteURL = function(suite){ + return makeUrl(suite.fullTitle()); +}; + +/** + * Provide test URL + * + * @param {Object} [test] + */ + +HTML.prototype.testURL = function(test){ + return makeUrl(test.fullTitle()); +}; + +/** * Display error `msg`. */ function error(msg) { document.body.appendChild(fragment('<div id="mocha-error">%s</div>', msg)); @@ -2469,11 +2935,10 @@ } }); // module: reporters/html.js require.register("reporters/index.js", function(module, exports, require){ - exports.Base = require('./base'); exports.Dot = require('./dot'); exports.Doc = require('./doc'); exports.TAP = require('./tap'); exports.JSON = require('./json'); @@ -2487,16 +2952,14 @@ exports.Progress = require('./progress'); exports.Landing = require('./landing'); exports.JSONCov = require('./json-cov'); exports.HTMLCov = require('./html-cov'); exports.JSONStream = require('./json-stream'); -exports.Teamcity = require('./teamcity'); }); // module: reporters/index.js require.register("reporters/json-cov.js", function(module, exports, require){ - /** * Module dependencies. */ var Base = require('./base'); @@ -2583,11 +3046,11 @@ if (ret.sloc > 0) { ret.coverage = (ret.hits / ret.sloc) * 100; } return ret; -}; +} /** * Map jscoverage data for a single source file * to a JSON structure suitable for reporting. * @@ -2649,11 +3112,10 @@ } }); // module: reporters/json-cov.js require.register("reporters/json-stream.js", function(module, exports, require){ - /** * Module dependencies. */ var Base = require('./base') @@ -2686,11 +3148,13 @@ runner.on('pass', function(test){ console.log(JSON.stringify(['pass', clean(test)])); }); runner.on('fail', function(test, err){ - console.log(JSON.stringify(['fail', clean(test)])); + test = clean(test); + test.err = err.message; + console.log(JSON.stringify(['fail', test])); }); runner.on('end', function(){ process.stdout.write(JSON.stringify(['end', self.stats])); }); @@ -2710,14 +3174,14 @@ title: test.title , fullTitle: test.fullTitle() , duration: test.duration } } + }); // module: reporters/json-stream.js require.register("reporters/json.js", function(module, exports, require){ - /** * Module dependencies. */ var Base = require('./base') @@ -2740,10 +3204,11 @@ function JSONReporter(runner) { var self = this; Base.call(this, runner); var tests = [] + , pending = [] , failures = [] , passes = []; runner.on('test end', function(test){ tests.push(test); @@ -2755,18 +3220,25 @@ runner.on('fail', function(test){ failures.push(test); }); + runner.on('pending', function(test){ + pending.push(test); + }); + runner.on('end', function(){ var obj = { - stats: self.stats - , tests: tests.map(clean) - , failures: failures.map(clean) - , passes: passes.map(clean) + stats: self.stats, + tests: tests.map(clean), + pending: pending.map(clean), + failures: failures.map(clean), + passes: passes.map(clean) }; + runner.testResults = obj; + process.stdout.write(JSON.stringify(obj, null, 2)); }); } /** @@ -2778,19 +3250,34 @@ * @api private */ function clean(test) { return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration + title: test.title, + fullTitle: test.fullTitle(), + duration: test.duration, + err: errorJSON(test.err || {}) } } + +/** + * Transform `error` into a JSON object. + * @param {Error} err + * @return {Object} + */ + +function errorJSON(err) { + var res = {}; + Object.getOwnPropertyNames(err).forEach(function(key) { + res[key] = err[key]; + }, err); + return res; +} + }); // module: reporters/json.js require.register("reporters/landing.js", function(module, exports, require){ - /** * Module dependencies. */ var Base = require('./base') @@ -2844,11 +3331,11 @@ var buf = Array(width).join('-'); return ' ' + color('runway', buf); } runner.on('start', function(){ - stream.write('\n '); + stream.write('\n\n\n '); cursor.hide(); }); runner.on('test end', function(test){ // check if the plane crashed @@ -2861,11 +3348,11 @@ plane = color('plane crash', '✈'); crashed = col; } // render landing strip - stream.write('\u001b[4F\n\n'); + stream.write('\u001b['+(width+1)+'D\u001b[2A'); stream.write(runway()); stream.write('\n '); stream.write(color('runway', Array(col).join('⋅'))); stream.write(plane) stream.write(color('runway', Array(width - col).join('⋅') + '\n')); @@ -2887,14 +3374,14 @@ function F(){}; F.prototype = Base.prototype; Landing.prototype = new F; Landing.prototype.constructor = Landing; + }); // module: reporters/landing.js require.register("reporters/list.js", function(module, exports, require){ - /** * Module dependencies. */ var Base = require('./base') @@ -2970,10 +3457,16 @@ var Base = require('./base') , utils = require('../utils'); /** + * Constants + */ + +var SUITE_PREFIX = '$'; + +/** * Expose `Markdown`. */ exports = module.exports = Markdown; @@ -2999,12 +3492,13 @@ function indent() { return Array(level).join(' '); } function mapTOC(suite, obj) { - var ret = obj; - obj = obj[suite.title] = obj[suite.title] || { suite: suite }; + var ret = obj, + key = SUITE_PREFIX + suite.title; + obj = obj[key] = obj[key] || { suite: suite }; suite.suites.forEach(function(suite){ mapTOC(suite, obj); }); return ret; } @@ -3013,15 +3507,17 @@ ++level; var buf = ''; var link; for (var key in obj) { if ('suite' == key) continue; - if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; - if (key) buf += Array(level).join(' ') + link; + if (key !== SUITE_PREFIX) { + link = ' - [' + key.substring(1) + ']'; + link += '(#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; + buf += Array(level).join(' ') + link; + } buf += stringifyTOC(obj[key], level); } - --level; return buf; } function generateTOC(suite) { var obj = mapTOC(suite, {}); @@ -3053,14 +3549,14 @@ process.stdout.write('# TOC\n'); process.stdout.write(generateTOC(runner.suite)); process.stdout.write(buf); }); } + }); // module: reporters/markdown.js require.register("reporters/min.js", function(module, exports, require){ - /** * Module dependencies. */ var Base = require('./base'); @@ -3098,20 +3594,19 @@ function F(){}; F.prototype = Base.prototype; Min.prototype = new F; Min.prototype.constructor = Min; + }); // module: reporters/min.js require.register("reporters/nyan.js", function(module, exports, require){ - /** * Module dependencies. */ -var Base = require('./base') - , color = Base.color; +var Base = require('./base'); /** * Expose `Dot`. */ @@ -3124,11 +3619,10 @@ * @api public */ function NyanCat(runner) { Base.call(this, runner); - var self = this , stats = this.stats , width = Base.window.width * .75 | 0 , rainbowColors = this.rainbowColors = self.generateColors() , colorIndex = this.colorIndex = 0 @@ -3140,44 +3634,43 @@ , tick = this.tick = 0 , n = 0; runner.on('start', function(){ Base.cursor.hide(); - self.draw('start'); + self.draw(); }); runner.on('pending', function(test){ - self.draw('pending'); + self.draw(); }); runner.on('pass', function(test){ - self.draw('pass'); + self.draw(); }); runner.on('fail', function(test, err){ - self.draw('fail'); + self.draw(); }); runner.on('end', function(){ Base.cursor.show(); for (var i = 0; i < self.numberOfLines; i++) write('\n'); self.epilogue(); }); } /** - * Draw the nyan cat with runner `status`. + * Draw the nyan cat * - * @param {String} status * @api private */ -NyanCat.prototype.draw = function(status){ +NyanCat.prototype.draw = function(){ this.appendRainbow(); this.drawScoreboard(); this.drawRainbow(); - this.drawNyanCat(status); + this.drawNyanCat(); this.tick = !this.tick; }; /** * Draw the "scoreboard" showing the number @@ -3186,21 +3679,20 @@ * @api private */ NyanCat.prototype.drawScoreboard = function(){ var stats = this.stats; - var colors = Base.colors; - function draw(color, n) { + function draw(type, n) { write(' '); - write('\u001b[' + color + 'm' + n + '\u001b[0m'); + write(Base.color(type, n)); write('\n'); } - draw(colors.green, stats.passes); - draw(colors.fail, stats.failures); - draw(colors.pending, stats.pending); + draw('green', stats.passes); + draw('fail', stats.failures); + draw('pending', stats.pending); write('\n'); this.cursorUp(this.numberOfLines); }; @@ -3238,62 +3730,66 @@ this.cursorUp(this.numberOfLines); }; /** - * Draw the nyan cat with `status`. + * Draw the nyan cat * - * @param {String} status * @api private */ -NyanCat.prototype.drawNyanCat = function(status) { +NyanCat.prototype.drawNyanCat = function() { var self = this; var startWidth = this.scoreboardWidth + this.trajectories[0].length; + var dist = '\u001b[' + startWidth + 'C'; + var padding = ''; - [0, 1, 2, 3].forEach(function(index) { - write('\u001b[' + startWidth + 'C'); + write(dist); + write('_,------,'); + write('\n'); - switch (index) { - case 0: - write('_,------,'); - write('\n'); - break; - case 1: - var padding = self.tick ? ' ' : ' '; - write('_|' + padding + '/\\_/\\ '); - write('\n'); - break; - case 2: - var padding = self.tick ? '_' : '__'; - var tail = self.tick ? '~' : '^'; - var face; - switch (status) { - case 'pass': - face = '( ^ .^)'; - break; - case 'fail': - face = '( o .o)'; - break; - default: - face = '( - .-)'; - } - write(tail + '|' + padding + face + ' '); - write('\n'); - break; - case 3: - var padding = self.tick ? ' ' : ' '; - write(padding + '"" "" '); - write('\n'); - break; - } - }); + write(dist); + padding = self.tick ? ' ' : ' '; + write('_|' + padding + '/\\_/\\ '); + write('\n'); + write(dist); + padding = self.tick ? '_' : '__'; + var tail = self.tick ? '~' : '^'; + var face; + write(tail + '|' + padding + this.face() + ' '); + write('\n'); + + write(dist); + padding = self.tick ? ' ' : ' '; + write(padding + '"" "" '); + write('\n'); + this.cursorUp(this.numberOfLines); }; /** + * Draw nyan cat face. + * + * @return {String} + * @api private + */ + +NyanCat.prototype.face = function() { + var stats = this.stats; + if (stats.failures) { + return '( x .x)'; + } else if (stats.pending) { + return '( o .o)'; + } else if(stats.passes) { + return '( ^ .^)'; + } else { + return '( - .-)'; + } +}; + +/** * Move cursor up `n`. * * @param {Number} n * @api private */ @@ -3342,10 +3838,12 @@ * @return {String} * @api private */ NyanCat.prototype.rainbowify = function(str){ + if (!Base.useColors) + return str; var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; this.colorIndex += 1; return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; }; @@ -3368,11 +3866,10 @@ }); // module: reporters/nyan.js require.register("reporters/progress.js", function(module, exports, require){ - /** * Module dependencies. */ var Base = require('./base') @@ -3406,11 +3903,12 @@ , options = options || {} , stats = this.stats , width = Base.window.width * .50 | 0 , total = runner.total , complete = 0 - , max = Math.max; + , max = Math.max + , lastN = -1; // default chars options.open = options.open || '['; options.complete = options.complete || '▬'; options.incomplete = options.incomplete || Base.symbols.dot; @@ -3429,10 +3927,16 @@ var incomplete = total - complete , percent = complete / total , n = width * percent | 0 , i = width - n; + if (lastN === n && !options.verbose) { + // Don't re-render the line if it hasn't changed + return; + } + lastN = n; + cursor.CR(); process.stdout.write('\u001b[J'); process.stdout.write(color('progress', ' ' + options.open)); process.stdout.write(Array(n).join(options.complete)); process.stdout.write(Array(i).join(options.incomplete)); @@ -3462,11 +3966,10 @@ }); // module: reporters/progress.js require.register("reporters/spec.js", function(module, exports, require){ - /** * Module dependencies. */ var Base = require('./base') @@ -3510,31 +4013,27 @@ runner.on('suite end', function(suite){ --indents; if (1 == indents) console.log(); }); - runner.on('test', function(test){ - process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); - }); - runner.on('pending', function(test){ var fmt = indent() + color('pending', ' - %s'); console.log(fmt, test.title); }); runner.on('pass', function(test){ if ('fast' == test.speed) { var fmt = indent() + color('checkmark', ' ' + Base.symbols.ok) - + color('pass', ' %s '); + + color('pass', ' %s'); cursor.CR(); console.log(fmt, test.title); } else { var fmt = indent() + color('checkmark', ' ' + Base.symbols.ok) - + color('pass', ' %s ') - + color(test.speed, '(%dms)'); + + color('pass', ' %s') + + color(test.speed, ' (%dms)'); cursor.CR(); console.log(fmt, test.title, test.duration); } }); @@ -3557,11 +4056,10 @@ }); // module: reporters/spec.js require.register("reporters/tap.js", function(module, exports, require){ - /** * Module dependencies. */ var Base = require('./base') @@ -3633,87 +4131,18 @@ return test.fullTitle().replace(/#/g, ''); } }); // module: reporters/tap.js -require.register("reporters/teamcity.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base'); - -/** - * Expose `Teamcity`. - */ - -exports = module.exports = Teamcity; - -/** - * Initialize a new `Teamcity` reporter. - * - * @param {Runner} runner - * @api public - */ - -function Teamcity(runner) { - Base.call(this, runner); - var stats = this.stats; - - runner.on('start', function() { - console.log("##teamcity[testSuiteStarted name='mocha.suite']"); - }); - - runner.on('test', function(test) { - console.log("##teamcity[testStarted name='" + escape(test.fullTitle()) + "']"); - }); - - runner.on('fail', function(test, err) { - console.log("##teamcity[testFailed name='" + escape(test.fullTitle()) + "' message='" + escape(err.message) + "']"); - }); - - runner.on('pending', function(test) { - console.log("##teamcity[testIgnored name='" + escape(test.fullTitle()) + "' message='pending']"); - }); - - runner.on('test end', function(test) { - console.log("##teamcity[testFinished name='" + escape(test.fullTitle()) + "' duration='" + test.duration + "']"); - }); - - runner.on('end', function() { - console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='" + stats.duration + "']"); - }); -} - -/** - * Escape the given `str`. - */ - -function escape(str) { - return str - .replace(/\|/g, "||") - .replace(/\n/g, "|n") - .replace(/\r/g, "|r") - .replace(/\[/g, "|[") - .replace(/\]/g, "|]") - .replace(/\u0085/g, "|x") - .replace(/\u2028/g, "|l") - .replace(/\u2029/g, "|p") - .replace(/'/g, "|'"); -} - -}); // module: reporters/teamcity.js - require.register("reporters/xunit.js", function(module, exports, require){ - /** * Module dependencies. */ var Base = require('./base') , utils = require('../utils') + , fs = require('browser/fs') , escape = utils.escape; /** * Save timer references to avoid Sinon interfering (see GH-237). */ @@ -3735,71 +4164,105 @@ * * @param {Runner} runner * @api public */ -function XUnit(runner) { +function XUnit(runner, options) { Base.call(this, runner); var stats = this.stats , tests = [] , self = this; + if (options.reporterOptions && options.reporterOptions.output) { + if (! fs.createWriteStream) { + throw new Error('file output not supported in browser'); + } + self.fileStream = fs.createWriteStream(options.reporterOptions.output); + } + + runner.on('pending', function(test){ + tests.push(test); + }); + runner.on('pass', function(test){ tests.push(test); }); runner.on('fail', function(test){ tests.push(test); }); runner.on('end', function(){ - console.log(tag('testsuite', { + self.write(tag('testsuite', { name: 'Mocha Tests' , tests: stats.tests , failures: stats.failures , errors: stats.failures - , skip: stats.tests - stats.failures - stats.passes + , skipped: stats.tests - stats.failures - stats.passes , timestamp: (new Date).toUTCString() - , time: stats.duration / 1000 + , time: (stats.duration / 1000) || 0 }, false)); - tests.forEach(test); - console.log('</testsuite>'); + tests.forEach(function(t) { self.test(t); }); + self.write('</testsuite>'); }); } /** + * Override done to close the stream (if it's a file). + */ +XUnit.prototype.done = function(failures, fn) { + if (this.fileStream) { + this.fileStream.end(function() { + fn(failures); + }); + } else { + fn(failures); + } +}; + +/** * Inherit from `Base.prototype`. */ function F(){}; F.prototype = Base.prototype; XUnit.prototype = new F; XUnit.prototype.constructor = XUnit; /** + * Write out the given line + */ +XUnit.prototype.write = function(line) { + if (this.fileStream) { + this.fileStream.write(line + '\n'); + } else { + console.log(line); + } +}; + +/** * Output tag for the given `test.` */ -function test(test) { +XUnit.prototype.test = function(test, ostream) { var attrs = { classname: test.parent.fullTitle() , name: test.title - , time: test.duration / 1000 + , time: (test.duration / 1000) || 0 }; if ('failed' == test.state) { var err = test.err; - attrs.message = escape(err.message); - console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); + this.write(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + "\n" + err.stack)))); } else if (test.pending) { - console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); + this.write(tag('testcase', attrs, false, tag('skipped', {}, true))); } else { - console.log(tag('testcase', attrs, true) ); + this.write(tag('testcase', attrs, true) ); } -} +}; /** * HTML tag helper. */ @@ -3826,18 +4289,19 @@ } }); // module: reporters/xunit.js require.register("runnable.js", function(module, exports, require){ - /** * Module dependencies. */ var EventEmitter = require('browser/events').EventEmitter , debug = require('browser/debug')('mocha:runnable') - , milliseconds = require('./ms'); + , Pending = require('./pending') + , milliseconds = require('./ms') + , utils = require('./utils'); /** * Save timer references to avoid Sinon interfering (see GH-237). */ @@ -3872,11 +4336,13 @@ this.fn = fn; this.async = fn && fn.length; this.sync = ! this.async; this._timeout = 2000; this._slow = 75; + this._enableTimeouts = true; this.timedOut = false; + this._trace = new Error('done() called multiple times') } /** * Inherit from `EventEmitter.prototype`. */ @@ -3895,10 +4361,11 @@ * @api private */ Runnable.prototype.timeout = function(ms){ if (0 == arguments.length) return this._timeout; + if (ms === 0) this._enableTimeouts = false; if ('string' == typeof ms) ms = milliseconds(ms); debug('timeout %d', ms); this._timeout = ms; if (this.timer) this.resetTimeout(); return this; @@ -3919,10 +4386,35 @@ this._slow = ms; return this; }; /** + * Set and & get timeout `enabled`. + * + * @param {Boolean} enabled + * @return {Runnable|Boolean} enabled or self + * @api private + */ + +Runnable.prototype.enableTimeouts = function(enabled){ + if (arguments.length === 0) return this._enableTimeouts; + debug('enableTimeouts %s', enabled); + this._enableTimeouts = enabled; + return this; +}; + +/** + * Halt and mark as pending. + * + * @api private + */ + +Runnable.prototype.skip = function(){ + throw new Pending(); +}; + +/** * Return the full title generated by recursively * concatenating the parent's full title. * * @return {String} * @api public @@ -3963,112 +4455,146 @@ * * @api private */ Runnable.prototype.resetTimeout = function(){ - var self = this - , ms = this.timeout(); + var self = this; + var ms = this.timeout() || 1e9; + if (!this._enableTimeouts) return; this.clearTimeout(); - if (ms) { - this.timer = setTimeout(function(){ - self.callback(new Error('timeout of ' + ms + 'ms exceeded')); - self.timedOut = true; - }, ms); - } + this.timer = setTimeout(function(){ + if (!self._enableTimeouts) return; + self.callback(new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.')); + self.timedOut = true; + }, ms); }; /** + * Whitelist these globals for this test run + * + * @api private + */ +Runnable.prototype.globals = function(arr){ + var self = this; + this._allowedGlobals = arr; +}; + +/** * Run the test and invoke `fn(err)`. * * @param {Function} fn * @api private */ Runnable.prototype.run = function(fn){ var self = this - , ms = this.timeout() , start = new Date , ctx = this.ctx , finished , emitted; - if (ctx) ctx.runnable(this); + // Some times the ctx exists but it is not runnable + if (ctx && ctx.runnable) ctx.runnable(this); - // timeout - if (this.async) { - if (ms) { - this.timer = setTimeout(function(){ - done(new Error('timeout of ' + ms + 'ms exceeded')); - self.timedOut = true; - }, ms); - } - } - // called multiple times function multiple(err) { if (emitted) return; emitted = true; - self.emit('error', err || new Error('done() called multiple times')); + self.emit('error', err || new Error('done() called multiple times; stacktrace may be inaccurate')); } // finished function done(err) { + var ms = self.timeout(); if (self.timedOut) return; - if (finished) return multiple(err); + if (finished) return multiple(err || self._trace); + + // Discard the resolution if this test has already failed asynchronously + if (self.state) return; + self.clearTimeout(); self.duration = new Date - start; finished = true; + if (!err && self.duration > ms && self._enableTimeouts) err = new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.'); fn(err); } // for .resetTimeout() this.callback = done; - // async + // explicit async with `done` argument if (this.async) { + this.resetTimeout(); + try { this.fn.call(ctx, function(err){ if (err instanceof Error || toString.call(err) === "[object Error]") return done(err); - if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); + if (null != err) { + if (Object.prototype.toString.call(err) === '[object Object]') { + return done(new Error('done() invoked with non-Error: ' + JSON.stringify(err))); + } else { + return done(new Error('done() invoked with non-Error: ' + err)); + } + } done(); }); } catch (err) { - done(err); + done(utils.getError(err)); } return; } if (this.asyncOnly) { return done(new Error('--async-only option in use without declaring `done()`')); } - // sync + // sync or promise-returning try { - if (!this.pending) this.fn.call(ctx); - this.duration = new Date - start; - fn(); + if (this.pending) { + done(); + } else { + callFn(this.fn); + } } catch (err) { - fn(err); + done(utils.getError(err)); } + + function callFn(fn) { + var result = fn.call(ctx); + if (result && typeof result.then === 'function') { + self.resetTimeout(); + result + .then(function() { + done() + }, + function(reason) { + done(reason || new Error('Promise rejected with no or falsy reason')) + }); + } else { + done(); + } + } }; }); // module: runnable.js require.register("runner.js", function(module, exports, require){ - /** * Module dependencies. */ var EventEmitter = require('browser/events').EventEmitter , debug = require('browser/debug')('mocha:runner') + , Pending = require('./pending') , Test = require('./test') , utils = require('./utils') , filter = utils.filter , keys = utils.keys - , noop = function(){}; + , type = utils.type + , stringify = utils.stringify + , stackFilter = utils.stackTraceFilter(); /** * Non-enumerable globals. */ @@ -4076,11 +4602,13 @@ 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'XMLHttpRequest', - 'Date' + 'Date', + 'setImmediate', + 'clearImmediate' ]; /** * Expose `Runner`. */ @@ -4100,27 +4628,42 @@ * - `test end` (test) test completed * - `hook` (hook) hook execution started * - `hook end` (hook) hook complete * - `pass` (test) test passed * - `fail` (test, err) test failed + * - `pending` (test) test pending * + * @param {Suite} suite Root suite + * @param {boolean} [delay] Whether or not to delay execution of root suite + * until ready. * @api public */ -function Runner(suite) { +function Runner(suite, delay) { var self = this; this._globals = []; + this._abort = false; + this._delay = delay; this.suite = suite; this.total = suite.total(); this.failures = 0; this.on('test end', function(test){ self.checkGlobals(test); }); this.on('hook end', function(hook){ self.checkGlobals(hook); }); this.grep(/.*/); - this.globals(this.globalProps().concat(['errno'])); + this.globals(this.globalProps().concat(extraGlobals())); } /** + * Wrapper for setImmediate, process.nextTick, or browser polyfill. + * + * @param {Function} fn + * @api private + */ + +Runner.immediately = global.setImmediate || process.nextTick; + +/** * Inherit from `EventEmitter.prototype`. */ function F(){}; F.prototype = EventEmitter.prototype; @@ -4196,13 +4739,11 @@ */ Runner.prototype.globals = function(arr){ if (0 == arguments.length) return this._globals; debug('globals %j', arr); - utils.forEach(arr, function(arr){ - this._globals.push(arr); - }, this); + this._globals = this._globals.concat(arr); return this; }; /** * Check for global variable leaks. @@ -4211,18 +4752,21 @@ */ Runner.prototype.checkGlobals = function(test){ if (this.ignoreLeaks) return; var ok = this._globals; + var globals = this.globalProps(); - var isNode = process.kill; var leaks; - // check length - 2 ('errno' and 'location' globals) - if (isNode && 1 == ok.length - globals.length) return - else if (2 == ok.length - globals.length) return; + if (test) { + ok = ok.concat(test._allowedGlobals || []); + } + if(this.prevGlobalsLength == globals.length) return; + this.prevGlobalsLength = globals.length; + leaks = filterLeaks(ok, globals); this._globals = this._globals.concat(leaks); if (leaks.length > 1) { this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); @@ -4237,37 +4781,51 @@ * @param {Test} test * @param {Error} err * @api private */ -Runner.prototype.fail = function(test, err){ +Runner.prototype.fail = function(test, err) { ++this.failures; test.state = 'failed'; - if ('string' == typeof err) { - err = new Error('the string "' + err + '" was thrown, throw an Error :)'); + if (!(err instanceof Error)) { + err = new Error('the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)'); } + err.stack = (this.fullStackTrace || !err.stack) + ? err.stack + : stackFilter(err.stack); + this.emit('fail', test, err); }; /** * Fail the given `hook` with `err`. * - * Hook failures (currently) hard-end due - * to that fact that a failing hook will - * surely cause subsequent tests to fail, - * causing jumbled reporting. + * Hook failures work in the following pattern: + * - If bail, then exit + * - Failed `before` hook skips all tests in a suite and subsuites, + * but jumps to corresponding `after` hook + * - Failed `before each` hook skips remaining tests in a + * suite and jumps to corresponding `after each` hook, + * which is run only once + * - Failed `after` hook does not alter + * execution order + * - Failed `after each` hook skips remaining tests in a + * suite and subsuites, but executes other `after each` + * hooks * * @param {Hook} hook * @param {Error} err * @api private */ Runner.prototype.failHook = function(hook, err){ this.fail(hook, err); - this.emit('end'); + if (this.suite.bail()) { + this.emit('end'); + } }; /** * Run hook `name` callbacks and then invoke `fn()`. * @@ -4285,34 +4843,46 @@ function next(i) { var hook = hooks[i]; if (!hook) return fn(); self.currentRunnable = hook; + hook.ctx.currentTest = self.test; + self.emit('hook', hook); hook.on('error', function(err){ self.failHook(hook, err); }); hook.run(function(err){ hook.removeAllListeners('error'); var testError = hook.error(); if (testError) self.fail(self.test, testError); - if (err) return self.failHook(hook, err); + if (err) { + if (err instanceof Pending) { + suite.pending = true; + } else { + self.failHook(hook, err); + + // stop executing hooks, notify callee of hook err + return fn(err); + } + } self.emit('hook end', hook); + delete hook.ctx.currentTest; next(++i); }); } - process.nextTick(function(){ + Runner.immediately(function(){ next(0); }); }; /** * Run hook `name` for the given array of `suites` - * in order, and callback `fn(err)`. + * in order, and callback `fn(err, errSuite)`. * * @param {String} name * @param {Array} suites * @param {Function} fn * @api private @@ -4330,12 +4900,13 @@ return fn(); } self.hook(name, function(err){ if (err) { + var errSuite = self.suite; self.suite = orig; - return fn(err); + return fn(err, errSuite); } next(suites.pop()); }); } @@ -4419,14 +4990,43 @@ Runner.prototype.runTests = function(suite, fn){ var self = this , tests = suite.tests.slice() , test; - function next(err) { + + function hookErr(err, errSuite, after) { + // before/after Each hook for errSuite failed: + var orig = self.suite; + + // for failed 'after each' hook start from errSuite parent, + // otherwise start from errSuite itself + self.suite = after ? errSuite.parent : errSuite; + + if (self.suite) { + // call hookUp afterEach + self.hookUp('afterEach', function(err2, errSuite2) { + self.suite = orig; + // some hooks may fail even now + if (err2) return hookErr(err2, errSuite2, true); + // report error suite + fn(errSuite); + }); + } else { + // there is no need calling other 'after each' hooks + self.suite = orig; + fn(errSuite); + } + } + + function next(err, errSuite) { // if we bail after first err if (self.failures && suite._bail) return fn(); + if (self._abort) return fn(); + + if (err) return hookErr(err, errSuite, true); + // next test test = tests.shift(); // all done if (!test) return fn(); @@ -4443,18 +5043,35 @@ return next(); } // execute test and hook(s) self.emit('test', self.test = test); - self.hookDown('beforeEach', function(){ + self.hookDown('beforeEach', function(err, errSuite){ + + if (suite.pending) { + self.emit('pending', test); + self.emit('test end', test); + return next(); + } + if (err) return hookErr(err, errSuite, false); + self.currentRunnable = self.test; self.runTest(function(err){ test = self.test; if (err) { - self.fail(test, err); + if (err instanceof Pending) { + self.emit('pending', test); + } else { + self.fail(test, err); + } self.emit('test end', test); + + if (err instanceof Pending) { + return next(); + } + return self.hookUp('afterEach', next); } test.state = 'passed'; self.emit('pass', test); @@ -4486,25 +5103,41 @@ if (!total) return fn(); this.emit('suite', this.suite = suite); - function next() { + function next(errSuite) { + if (errSuite) { + // current suite failed on a hook from errSuite + if (errSuite == suite) { + // if errSuite is current suite + // continue to the next sibling suite + return done(); + } else { + // errSuite is among the parents of current suite + // stop execution of errSuite and all sub-suites + return done(errSuite); + } + } + + if (self._abort) return done(); + var curr = suite.suites[i++]; if (!curr) return done(); self.runSuite(curr, next); } - function done() { + function done(errSuite) { self.suite = suite; self.hook('afterAll', function(){ self.emit('suite end', suite); - fn(); + fn(errSuite); }); } - this.hook('beforeAll', function(){ + this.hook('beforeAll', function(err){ + if (err) return done(); self.runTests(suite, next); }); }; /** @@ -4513,15 +5146,27 @@ * @param {Error} err * @api private */ Runner.prototype.uncaught = function(err){ - debug('uncaught exception %s', err.message); + if (err) { + debug('uncaught exception %s', err !== function () { + return this; + }.call(err) ? err : ( err.message || err )); + } else { + debug('uncaught undefined exception'); + err = utils.undefinedError(); + } + err.uncaught = true; + var runnable = this.currentRunnable; - if (!runnable || 'failed' == runnable.state) return; + if (!runnable) return; + runnable.clearTimeout(); - err.uncaught = true; + + // Ignore errors if complete + if (runnable.state) return; this.fail(runnable, err); // recover from test if ('test' == runnable.type) { this.emit('test end', runnable); @@ -4541,64 +5186,125 @@ * @return {Runner} for chaining * @api public */ Runner.prototype.run = function(fn){ - var self = this - , fn = fn || function(){}; + var self = this, + rootSuite = this.suite; + fn = fn || function(){}; + + function uncaught(err){ + self.uncaught(err); + } + + function start() { + self.emit('start'); + self.runSuite(rootSuite, function(){ + debug('finished running'); + self.emit('end'); + }); + } + debug('start'); // callback this.on('end', function(){ debug('end'); - process.removeListener('uncaughtException', function(err){ - self.uncaught(err); - }); + process.removeListener('uncaughtException', uncaught); fn(self.failures); }); - // run suites - this.emit('start'); - this.runSuite(this.suite, function(){ - debug('finished running'); - self.emit('end'); - }); - // uncaught exception - process.on('uncaughtException', function(err){ - self.uncaught(err); - }); + process.on('uncaughtException', uncaught); + if (this._delay) { + // for reporters, I guess. + // might be nice to debounce some dots while we wait. + this.emit('waiting', rootSuite); + rootSuite.once('run', start); + } + else { + start(); + } + return this; }; /** + * Cleanly abort execution + * + * @return {Runner} for chaining + * @api public + */ +Runner.prototype.abort = function(){ + debug('aborting'); + this._abort = true; +}; + +/** * Filter leaks with the given globals flagged as `ok`. * * @param {Array} ok * @param {Array} globals * @return {Array} * @api private */ function filterLeaks(ok, globals) { return filter(globals, function(key){ + // Firefox and Chrome exposes iframes as index inside the window object + if (/^d+/.test(key)) return false; + + // in firefox + // if runner runs in an iframe, this iframe's window.getInterface method not init at first + // it is assigned in some seconds + if (global.navigator && /^getInterface/.test(key)) return false; + + // an iframe could be approached by window[iframeIndex] + // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak + if (global.navigator && /^\d+/.test(key)) return false; + + // Opera and IE expose global variables for HTML element IDs (issue #243) + if (/^mocha-/.test(key)) return false; + var matched = filter(ok, function(ok){ if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); - // Opera and IE expose global variables for HTML element IDs (issue #243) - if (/^mocha-/.test(key)) return true; return key == ok; }); return matched.length == 0 && (!global.navigator || 'onerror' !== key); }); } +/** + * Array of globals dependent on the environment. + * + * @return {Array} + * @api private + */ + +function extraGlobals() { + if (typeof(process) === 'object' && + typeof(process.version) === 'string') { + + var nodeVersion = process.version.split('.').reduce(function(a, v) { + return a << 8 | v; + }); + + // 'errno' was renamed to process._errno in v0.9.11. + + if (nodeVersion < 0x00090B) { + return ['errno']; + } + } + + return []; +} + }); // module: runner.js require.register("suite.js", function(module, exports, require){ - /** * Module dependencies. */ var EventEmitter = require('browser/events').EventEmitter @@ -4642,24 +5348,28 @@ * @param {String} title * @param {Context} ctx * @api private */ -function Suite(title, ctx) { +function Suite(title, parentContext) { this.title = title; - this.ctx = ctx; + var context = function() {}; + context.prototype = parentContext; + this.ctx = new context(); this.suites = []; this.tests = []; this.pending = false; this._beforeEach = []; this._beforeAll = []; this._afterEach = []; this._afterAll = []; this.root = !title; this._timeout = 2000; + this._enableTimeouts = true; this._slow = 75; this._bail = false; + this.delayed = false; } /** * Inherit from `EventEmitter.prototype`. */ @@ -4680,10 +5390,11 @@ Suite.prototype.clone = function(){ var suite = new Suite(this.title); debug('clone'); suite.ctx = this.ctx; suite.timeout(this.timeout()); + suite.enableTimeouts(this.enableTimeouts()); suite.slow(this.slow()); suite.bail(this.bail()); return suite; }; @@ -4695,17 +5406,33 @@ * @api private */ Suite.prototype.timeout = function(ms){ if (0 == arguments.length) return this._timeout; + if (ms.toString() === '0') this._enableTimeouts = false; if ('string' == typeof ms) ms = milliseconds(ms); debug('timeout %d', ms); this._timeout = parseInt(ms, 10); return this; }; /** + * Set timeout `enabled`. + * + * @param {Boolean} enabled + * @return {Suite|Boolean} self or enabled + * @api private + */ + +Suite.prototype.enableTimeouts = function(enabled){ + if (arguments.length === 0) return this._enableTimeouts; + debug('enableTimeouts %s', enabled); + this._enableTimeouts = enabled; + return this; +}; + +/** * Set slow `ms` or short-hand such as "2s". * * @param {Number|String} ms * @return {Suite|Number} for chaining * @api private @@ -4720,11 +5447,11 @@ }; /** * Sets whether to bail after first error. * - * @parma {Boolean} bail + * @param {Boolean} bail * @return {Suite|Number} for chaining * @api private */ Suite.prototype.bail = function(bail){ @@ -4740,15 +5467,22 @@ * @param {Function} fn * @return {Suite} for chaining * @api private */ -Suite.prototype.beforeAll = function(fn){ +Suite.prototype.beforeAll = function(title, fn){ if (this.pending) return this; - var hook = new Hook('"before all" hook', fn); + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"before all" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._beforeAll.push(hook); this.emit('beforeAll', hook); return this; @@ -4760,15 +5494,22 @@ * @param {Function} fn * @return {Suite} for chaining * @api private */ -Suite.prototype.afterAll = function(fn){ +Suite.prototype.afterAll = function(title, fn){ if (this.pending) return this; - var hook = new Hook('"after all" hook', fn); + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"after all" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._afterAll.push(hook); this.emit('afterAll', hook); return this; @@ -4780,15 +5521,22 @@ * @param {Function} fn * @return {Suite} for chaining * @api private */ -Suite.prototype.beforeEach = function(fn){ +Suite.prototype.beforeEach = function(title, fn){ if (this.pending) return this; - var hook = new Hook('"before each" hook', fn); + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"before each" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._beforeEach.push(hook); this.emit('beforeEach', hook); return this; @@ -4800,15 +5548,22 @@ * @param {Function} fn * @return {Suite} for chaining * @api private */ -Suite.prototype.afterEach = function(fn){ +Suite.prototype.afterEach = function(title, fn){ if (this.pending) return this; - var hook = new Hook('"after each" hook', fn); + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"after each" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._afterEach.push(hook); this.emit('afterEach', hook); return this; @@ -4823,10 +5578,11 @@ */ Suite.prototype.addSuite = function(suite){ suite.parent = this; suite.timeout(this.timeout()); + suite.enableTimeouts(this.enableTimeouts()); suite.slow(this.slow()); suite.bail(this.bail()); this.suites.push(suite); this.emit('suite', suite); return this; @@ -4841,10 +5597,11 @@ */ Suite.prototype.addTest = function(test){ test.parent = this; test.timeout(this.timeout()); + test.enableTimeouts(this.enableTimeouts()); test.slow(this.slow()); test.ctx = this.ctx; this.tests.push(test); this.emit('test', test); return this; @@ -4895,14 +5652,22 @@ suite.eachTest(fn); }); return this; }; +/** + * This will run the root suite if we happen to be running in delayed mode. + */ +Suite.prototype.run = function run() { + if (this.root) { + this.emit('run'); + } +}; + }); // module: suite.js require.register("test.js", function(module, exports, require){ - /** * Module dependencies. */ var Runnable = require('./runnable'); @@ -4938,17 +5703,19 @@ }); // module: test.js require.register("utils.js", function(module, exports, require){ - /** * Module dependencies. */ var fs = require('browser/fs') , path = require('browser/path') + , basename = path.basename + , exists = fs.existsSync || path.existsSync + , glob = require('browser/glob') , join = path.join , debug = require('browser/debug')('mocha:watch'); /** * Ignored directories. @@ -4985,10 +5752,26 @@ for (var i = 0, l = arr.length; i < l; i++) fn.call(scope, arr[i], i); }; /** + * Array#map (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.map = function(arr, fn, scope){ + var result = []; + for (var i = 0, l = arr.length; i < l; i++) + result.push(fn.call(scope, arr[i], i, arr)); + return result; +}; + +/** * Array#indexOf (<=IE8) * * @parma {Array} arr * @param {Object} obj to find index of * @param {Number} start @@ -5049,11 +5832,11 @@ * @api private */ exports.keys = Object.keys || function(obj) { var keys = [] - , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 + , has = Object.prototype.hasOwnProperty; // for `window` on <=IE8 for (var key in obj) { if (has.call(obj, key)) { keys.push(key); } @@ -5080,10 +5863,32 @@ }); }); }; /** + * Array.isArray (<=IE8) + * + * @param {Object} obj + * @return {Boolean} + * @api private + */ +var isArray = Array.isArray || function (obj) { + return '[object Array]' == {}.toString.call(obj); +}; + +/** + * @description + * Buffer.prototype.toJSON polyfill + * @type {Function} + */ +if(typeof Buffer !== 'undefined' && Buffer.prototype) { + Buffer.prototype.toJSON = Buffer.prototype.toJSON || function () { + return Array.prototype.slice.call(this, 0); + }; +} + +/** * Ignored files. */ function ignored(path){ return !~ignore.indexOf(path); @@ -5094,23 +5899,26 @@ * * @return {Array} * @api private */ -exports.files = function(dir, ret){ +exports.files = function(dir, ext, ret){ ret = ret || []; + ext = ext || ['js']; + var re = new RegExp('\\.(' + ext.join('|') + ')$'); + fs.readdirSync(dir) - .filter(ignored) - .forEach(function(path){ - path = join(dir, path); - if (fs.statSync(path).isDirectory()) { - exports.files(path, ret); - } else if (path.match(/\.(js|coffee)$/)) { - ret.push(path); - } - }); + .filter(ignored) + .forEach(function(path){ + path = join(dir, path); + if (fs.statSync(path).isDirectory()) { + exports.files(path, ext, ret); + } else if (path.match(re)) { + ret.push(path); + } + }); return ret; }; /** @@ -5133,34 +5941,24 @@ * and re-indent for pre whitespace. */ exports.clean = function(str) { str = str - .replace(/^function *\(.*\) *{/, '') + .replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, '') + .replace(/^function *\(.*\)\s*{|\(.*\) *=> *{?/, '') .replace(/\s+\}$/, ''); var spaces = str.match(/^\n?( *)/)[1].length - , re = new RegExp('^ {' + spaces + '}', 'gm'); + , tabs = str.match(/^\n?(\t*)/)[1].length + , re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm'); str = str.replace(re, ''); return exports.trim(str); }; /** - * Escape regular expression characters in `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -exports.escapeRegexp = function(str){ - return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); -}; - -/** * Trim the given `str`. * * @param {String} str * @return {String} * @api private @@ -5203,11 +6001,11 @@ .replace(/>/g, '&gt;') .replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>') .replace(/('.*?')/gm, '<span class="string">$1</span>') .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>') .replace(/(\d+)/gm, '<span class="number">$1</span>') - .replace(/\bnew *(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>') + .replace(/\bnew[ \t]+(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>') .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>') } /** * Highlight the contents of tag `name`. @@ -5215,126 +6013,553 @@ * @param {String} name * @api private */ exports.highlightTags = function(name) { - var code = document.getElementsByTagName(name); + var code = document.getElementById('mocha').getElementsByTagName(name); for (var i = 0, len = code.length; i < len; ++i) { code[i].innerHTML = highlight(code[i].innerHTML); } }; -}); // module: utils.js /** - * Node shims. + * If a value could have properties, and has none, this function is called, which returns + * a string representation of the empty value. * - * These are meant only to allow - * mocha.js to run untouched, not - * to allow running node code in - * the browser. + * Functions w/ no properties return `'[Function]'` + * Arrays w/ length === 0 return `'[]'` + * Objects w/ no properties return `'{}'` + * All else: return result of `value.toString()` + * + * @param {*} value Value to inspect + * @param {string} [type] The type of the value, if known. + * @returns {string} */ +var emptyRepresentation = function emptyRepresentation(value, type) { + type = type || exports.type(value); -process = {}; -process.exit = function(status){}; -process.stdout = {}; -global = window; + switch(type) { + case 'function': + return '[Function]'; + case 'object': + return '{}'; + case 'array': + return '[]'; + default: + return value.toString(); + } +}; /** - * next tick implementation. + * Takes some variable and asks `{}.toString()` what it thinks it is. + * @param {*} value Anything + * @example + * type({}) // 'object' + * type([]) // 'array' + * type(1) // 'number' + * type(false) // 'boolean' + * type(Infinity) // 'number' + * type(null) // 'null' + * type(new Date()) // 'date' + * type(/foo/) // 'regexp' + * type('type') // 'string' + * type(global) // 'global' + * @api private + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString + * @returns {string} */ +exports.type = function type(value) { + if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { + return 'buffer'; + } + return Object.prototype.toString.call(value) + .replace(/^\[.+\s(.+?)\]$/, '$1') + .toLowerCase(); +}; -process.nextTick = (function(){ - // postMessage behaves badly on IE8 - if (window.ActiveXObject || !window.postMessage) { - return function(fn){ fn() }; +/** + * @summary Stringify `value`. + * @description Different behavior depending on type of value. + * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively. + * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes. + * - If `value` is an *empty* object, function, or array, return result of function + * {@link emptyRepresentation}. + * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of + * JSON.stringify(). + * + * @see exports.type + * @param {*} value + * @return {string} + * @api private + */ + +exports.stringify = function(value) { + var type = exports.type(value); + + if (!~exports.indexOf(['object', 'array', 'function'], type)) { + if(type != 'buffer') { + return jsonStringify(value); + } + var json = value.toJSON(); + // Based on the toJSON result + return jsonStringify(json.data && json.type ? json.data : json, 2) + .replace(/,(\n|$)/g, '$1'); } - // based on setZeroTimeout by David Baron - // - http://dbaron.org/log/20100309-faster-timeouts - var timeouts = [] - , name = 'mocha-zero-timeout' + for (var prop in value) { + if (Object.prototype.hasOwnProperty.call(value, prop)) { + return jsonStringify(exports.canonicalize(value), 2).replace(/,(\n|$)/g, '$1'); + } + } - window.addEventListener('message', function(e){ - if (e.source == window && e.data == name) { - if (e.stopPropagation) e.stopPropagation(); - if (timeouts.length) timeouts.shift()(); + return emptyRepresentation(value, type); +}; + +/** + * @description + * like JSON.stringify but more sense. + * @param {Object} object + * @param {Number=} spaces + * @param {number=} depth + * @returns {*} + * @private + */ +function jsonStringify(object, spaces, depth) { + if(typeof spaces == 'undefined') return _stringify(object); // primitive types + + depth = depth || 1; + var space = spaces * depth + , str = isArray(object) ? '[' : '{' + , end = isArray(object) ? ']' : '}' + , length = object.length || exports.keys(object).length + , repeat = function(s, n) { return new Array(n).join(s); }; // `.repeat()` polyfill + + function _stringify(val) { + switch (exports.type(val)) { + case 'null': + case 'undefined': + val = '[' + val + ']'; + break; + case 'array': + case 'object': + val = jsonStringify(val, spaces, depth + 1); + break; + case 'boolean': + case 'regexp': + case 'number': + val = val === 0 && (1/val) === -Infinity // `-0` + ? '-0' + : val.toString(); + break; + case 'date': + val = '[Date: ' + val.toISOString() + ']'; + break; + case 'buffer': + var json = val.toJSON(); + // Based on the toJSON result + json = json.data && json.type ? json.data : json; + val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']'; + break; + default: + val = (val == '[Function]' || val == '[Circular]') + ? val + : '"' + val + '"'; //string } - }, true); + return val; + } - return function(fn){ - timeouts.push(fn); - window.postMessage(name, '*'); + for(var i in object) { + if(!object.hasOwnProperty(i)) continue; // not my business + --length; + str += '\n ' + repeat(' ', space) + + (isArray(object) ? '' : '"' + i + '": ') // key + + _stringify(object[i]) // value + + (length ? ',' : ''); // comma } -})(); + return str + (str.length != 1 // [], {} + ? '\n' + repeat(' ', --space) + end + : end); +} + /** + * Return if obj is a Buffer + * @param {Object} arg + * @return {Boolean} + * @api private + */ +exports.isBuffer = function (arg) { + return typeof Buffer !== 'undefined' && Buffer.isBuffer(arg); +}; + +/** + * @summary Return a new Thing that has the keys in sorted order. Recursive. + * @description If the Thing... + * - has already been seen, return string `'[Circular]'` + * - is `undefined`, return string `'[undefined]'` + * - is `null`, return value `null` + * - is some other primitive, return the value + * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method + * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again. + * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()` + * + * @param {*} value Thing to inspect. May or may not have properties. + * @param {Array} [stack=[]] Stack of seen values + * @return {(Object|Array|Function|string|undefined)} + * @see {@link exports.stringify} + * @api private + */ + +exports.canonicalize = function(value, stack) { + var canonicalizedObj, + type = exports.type(value), + prop, + withStack = function withStack(value, fn) { + stack.push(value); + fn(); + stack.pop(); + }; + + stack = stack || []; + + if (exports.indexOf(stack, value) !== -1) { + return '[Circular]'; + } + + switch(type) { + case 'undefined': + case 'buffer': + case 'null': + canonicalizedObj = value; + break; + case 'array': + withStack(value, function () { + canonicalizedObj = exports.map(value, function (item) { + return exports.canonicalize(item, stack); + }); + }); + break; + case 'function': + for (prop in value) { + canonicalizedObj = {}; + break; + } + if (!canonicalizedObj) { + canonicalizedObj = emptyRepresentation(value, type); + break; + } + /* falls through */ + case 'object': + canonicalizedObj = canonicalizedObj || {}; + withStack(value, function () { + exports.forEach(exports.keys(value).sort(), function (key) { + canonicalizedObj[key] = exports.canonicalize(value[key], stack); + }); + }); + break; + case 'date': + case 'number': + case 'regexp': + case 'boolean': + canonicalizedObj = value; + break; + default: + canonicalizedObj = value.toString(); + } + + return canonicalizedObj; +}; + +/** + * Lookup file names at the given `path`. + */ +exports.lookupFiles = function lookupFiles(path, extensions, recursive) { + var files = []; + var re = new RegExp('\\.(' + extensions.join('|') + ')$'); + + if (!exists(path)) { + if (exists(path + '.js')) { + path += '.js'; + } else { + files = glob.sync(path); + if (!files.length) throw new Error("cannot resolve path (or pattern) '" + path + "'"); + return files; + } + } + + try { + var stat = fs.statSync(path); + if (stat.isFile()) return path; + } + catch (ignored) { + return; + } + + fs.readdirSync(path).forEach(function(file) { + file = join(path, file); + try { + var stat = fs.statSync(file); + if (stat.isDirectory()) { + if (recursive) { + files = files.concat(lookupFiles(file, extensions, recursive)); + } + return; + } + } + catch (ignored) { + return; + } + if (!stat.isFile() || !re.test(file) || basename(file)[0] === '.') return; + files.push(file); + }); + + return files; +}; + +/** + * Generate an undefined error with a message warning the user. + * + * @return {Error} + */ + +exports.undefinedError = function() { + return new Error('Caught undefined error, did you throw without specifying what?'); +}; + +/** + * Generate an undefined error if `err` is not defined. + * + * @param {Error} err + * @return {Error} + */ + +exports.getError = function(err) { + return err || exports.undefinedError(); +}; + + +/** + * @summary + * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`) + * @description + * When invoking this function you get a filter function that get the Error.stack as an input, + * and return a prettify output. + * (i.e: strip Mocha, node_modules, bower and componentJS from stack trace). + * @returns {Function} + */ + +exports.stackTraceFilter = function() { + var slash = '/' + , is = typeof document === 'undefined' + ? { node: true } + : { browser: true } + , cwd = is.node + ? process.cwd() + slash + : location.href.replace(/\/[^\/]*$/, '/'); + + function isNodeModule (line) { + return (~line.indexOf('node_modules')); + } + + function isMochaInternal (line) { + return (~line.indexOf('node_modules' + slash + 'mocha')) || + (~line.indexOf('components' + slash + 'mochajs')) || + (~line.indexOf('components' + slash + 'mocha')); + } + + // node_modules, bower, componentJS + function isBrowserModule(line) { + return (~line.indexOf('node_modules')) || + (~line.indexOf('components')); + } + + function isNodeInternal (line) { + return (~line.indexOf('(timers.js:')) || + (~line.indexOf('(events.js:')) || + (~line.indexOf('(node.js:')) || + (~line.indexOf('(module.js:')) || + (~line.indexOf('GeneratorFunctionPrototype.next (native)')) || + false + } + + return function(stack) { + stack = stack.split('\n'); + + stack = exports.reduce(stack, function(list, line) { + if (is.node && (isNodeModule(line) || + isMochaInternal(line) || + isNodeInternal(line))) + return list; + + if (is.browser && (isBrowserModule(line))) + return list; + + // Clean up cwd(absolute) + list.push(line.replace(cwd, '')); + return list; + }, []); + + return stack.join('\n'); + } +}; +}); // module: utils.js +// The global object is "self" in Web Workers. +var global = (function() { return this; })(); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date; +var setTimeout = global.setTimeout; +var setInterval = global.setInterval; +var clearTimeout = global.clearTimeout; +var clearInterval = global.clearInterval; + +/** + * Node shims. + * + * These are meant only to allow + * mocha.js to run untouched, not + * to allow running node code in + * the browser. + */ + +var process = {}; +process.exit = function(status){}; +process.stdout = {}; + +var uncaughtExceptionHandlers = []; + +var originalOnerrorHandler = global.onerror; + +/** * Remove uncaughtException listener. + * Revert to original onerror handler if previously defined. */ -process.removeListener = function(e){ +process.removeListener = function(e, fn){ if ('uncaughtException' == e) { - window.onerror = null; + if (originalOnerrorHandler) { + global.onerror = originalOnerrorHandler; + } else { + global.onerror = function() {}; + } + var i = Mocha.utils.indexOf(uncaughtExceptionHandlers, fn); + if (i != -1) { uncaughtExceptionHandlers.splice(i, 1); } } }; /** * Implements uncaughtException listener. */ process.on = function(e, fn){ if ('uncaughtException' == e) { - window.onerror = function(err, url, line){ + global.onerror = function(err, url, line){ fn(new Error(err + ' (' + url + ':' + line + ')')); + return true; }; + uncaughtExceptionHandlers.push(fn); } }; -// boot -;(function(){ +/** + * Expose mocha. + */ - /** - * Expose mocha. - */ +var Mocha = global.Mocha = require('mocha'), + mocha = global.mocha = new Mocha({ reporter: 'html' }); - var Mocha = window.Mocha = require('mocha'), - mocha = window.mocha = new Mocha({ reporter: 'html' }); +// The BDD UI is registered by default, but no UI will be functional in the +// browser without an explicit call to the overridden `mocha.ui` (see below). +// Ensure that this default UI does not expose its methods to the global scope. +mocha.suite.removeAllListeners('pre-require'); - /** - * Override ui to ensure that the ui functions are initialized. - * Normally this would happen in Mocha.prototype.loadFiles. - */ +var immediateQueue = [] + , immediateTimeout; - mocha.ui = function(ui){ - Mocha.prototype.ui.call(this, ui); - this.suite.emit('pre-require', window, null, this); - return this; - }; +function timeslice() { + var immediateStart = new Date().getTime(); + while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) { + immediateQueue.shift()(); + } + if (immediateQueue.length) { + immediateTimeout = setTimeout(timeslice, 0); + } else { + immediateTimeout = null; + } +} - /** - * Setup mocha with the given setting options. - */ +/** + * High-performance override of Runner.immediately. + */ - mocha.setup = function(opts){ - if ('string' == typeof opts) opts = { ui: opts }; - for (var opt in opts) this[opt](opts[opt]); - return this; - }; +Mocha.Runner.immediately = function(callback) { + immediateQueue.push(callback); + if (!immediateTimeout) { + immediateTimeout = setTimeout(timeslice, 0); + } +}; - /** - * Run mocha, returning the Runner. - */ +/** + * Function to allow assertion libraries to throw errors directly into mocha. + * This is useful when running tests in a browser because window.onerror will + * only receive the 'message' attribute of the Error. + */ +mocha.throwError = function(err) { + Mocha.utils.forEach(uncaughtExceptionHandlers, function (fn) { + fn(err); + }); + throw err; +}; - mocha.run = function(fn){ - var options = mocha.options; - mocha.globals('location'); +/** + * Override ui to ensure that the ui functions are initialized. + * Normally this would happen in Mocha.prototype.loadFiles. + */ - var query = Mocha.utils.parseQuery(window.location.search || ''); - if (query.grep) mocha.grep(query.grep); - if (query.invert) mocha.invert(); +mocha.ui = function(ui){ + Mocha.prototype.ui.call(this, ui); + this.suite.emit('pre-require', global, null, this); + return this; +}; - return Mocha.prototype.run.call(mocha, function(){ +/** + * Setup mocha with the given setting options. + */ + +mocha.setup = function(opts){ + if ('string' == typeof opts) opts = { ui: opts }; + for (var opt in opts) this[opt](opts[opt]); + return this; +}; + +/** + * Run mocha, returning the Runner. + */ + +mocha.run = function(fn){ + var options = mocha.options; + mocha.globals('location'); + + var query = Mocha.utils.parseQuery(global.location.search || ''); + if (query.grep) mocha.grep(new RegExp(query.grep)); + if (query.fgrep) mocha.grep(query.fgrep); + if (query.invert) mocha.invert(); + + return Mocha.prototype.run.call(mocha, function(err){ + // The DOM Document is not available in Web Workers. + var document = global.document; + if (document && document.getElementById('mocha') && options.noHighlighting !== true) { Mocha.utils.highlightTags('code'); - if (fn) fn(); - }); - }; -})(); + } + if (fn) fn(err); + }); +}; + +/** + * Expose the process shim. + */ + +Mocha.process = process; })();