diff --git a/test/mocha/.bower.json b/test/mocha/.bower.json index bb375ca4b..c52303518 100644 --- a/test/mocha/.bower.json +++ b/test/mocha/.bower.json @@ -1,6 +1,6 @@ { "name": "mocha", - "version": "1.14.0", + "version": "1.17.0", "main": "mocha.js", "ignore": [ "bin", @@ -18,11 +18,11 @@ "package.json" ], "homepage": "https://github.com/visionmedia/mocha", - "_release": "1.14.0", + "_release": "1.17.0", "_resolution": { "type": "version", - "tag": "1.14.0", - "commit": "10c65f379c4501269c83a719a04bd2fb0013f853" + "tag": "1.17.0", + "commit": "ffaa38d49d10d4a5efd8e8b67db2960c4731cdc3" }, "_source": "git://github.com/visionmedia/mocha.git", "_target": ">=1.13.0", diff --git a/test/mocha/History.md b/test/mocha/History.md index a259b7a87..d8cadebd0 100644 --- a/test/mocha/History.md +++ b/test/mocha/History.md @@ -1,3 +1,58 @@ +1.17.0 / 2014-01-09 +================== + + * add: able to require globals (describe, it, etc.) through mocha (#1077) + * fix: abort previous run on --watch change (#1100) + * fix: reset context for each --watch triggered run (#1099) + * fix: error when cli can't resolve path or pattern (#799) + * fix: canonicalize objects before stringifying and diffing them (#1079) + * fix: make CR call behave like carriage return for non tty (#1087) + + +1.16.2 / 2013-12-23 +================== + + * fix: couple issues with ie 8 (#1082, #1081) + * fix: issue running the xunit reporter in browsers (#1068) + * fix: issue with firefox < 3.5 (#725) + + +1.16.1 / 2013-12-19 +================== + + * fix: recompiled for missed changes from the last release + + +1.16.0 / 2013-12-19 +================== + + * add: Runnable.globals(arr) for per test global whitelist (#1046) + * add: mocha.throwError(err) for assertion libs to call (#985) + * remove: --watch's spinner (#806) + * fix: duplicate test output for multi-line specs in spec reporter (#1006) + * fix: gracefully exit on SIGINT (#1063) + * fix expose the specified ui only in the browser (#984) + * fix: ensure process exit code is preserved when using --no-exit (#1059) + * fix: return true from window.onerror handler (#868) + * fix: xunit reporter to use process.stdout.write (#1068) + * fix: utils.clean(str) indentation (#761) + * fix: xunit reporter returning test duration a NaN (#1039) + +1.15.1 / 2013-12-03 +================== + + * fix: recompiled for missed changes from the last release + +1.15.0 / 2013-12-02 +================== + + * add: `--no-exit` to prevent `process.exit()` (#1018) + * fix: using inline diffs (#1044) + * fix: show pending test details in xunit reporter (#1051) + * fix: faster global leak detection (#1024) + * fix: yui compression (#1035) + * fix: wrapping long lines in test results (#1030, #1031) + * fix: handle errors in hooks (#1043) 1.14.0 / 2013-11-02 ================== diff --git a/test/mocha/Readme.md b/test/mocha/Readme.md index ae0e8cceb..9340cacfd 100644 --- a/test/mocha/Readme.md +++ b/test/mocha/Readme.md @@ -9,112 +9,160 @@ ``` project : mocha - repo age : 1 year, 7 months - active : 272 days - commits : 1116 - files : 123 + repo age : 2 years, 4 months ago + commits : 1314 + active : 372 days + files : 141 authors : - 504 TJ Holowaychuk 45.2% - 389 Tj Holowaychuk 34.9% - 31 Guillermo Rauch 2.8% - 13 Attila Domokos 1.2% - 9 John Firebaugh 0.8% - 8 Jo Liss 0.7% - 7 Nathan Rajlich 0.6% - 6 James Carr 0.5% - 6 Brendan Nee 0.5% - 5 Aaron Heckmann 0.4% - 4 hokaccha 0.4% - 4 Xavier Antoviaque 0.4% - 4 Joshua Krall 0.4% - 3 Wil Moore III 0.3% - 3 Jesse Dailey 0.3% - 3 Nathan Bowser 0.3% - 3 Tyson Tate 0.3% - 3 Cory Thomas 0.3% - 3 Ryunosuke SATO 0.3% - 3 Paul Miller 0.3% - 3 Ben Lindsey 0.3% - 2 Forbes Lindesay 0.2% - 2 Konstantin Käfer 0.2% - 2 Brian Beck 0.2% - 2 Merrick Christensen 0.2% - 2 Michael Riley 0.2% - 2 David Henderson 0.2% - 2 Nathan Alderson 0.2% - 2 Paul Armstrong 0.2% - 2 Pete Hawkins 0.2% - 2 Quang Van 0.2% - 2 Raynos 0.2% - 2 Jonas Westerlund 0.2% - 2 Domenic Denicola 0.2% - 2 Shawn Krisman 0.2% - 2 Simon Gaeremynck 0.2% - 2 FARKAS Máté 0.2% - 2 Timo Tijhof 0.2% - 2 Justin DuJardin 0.2% - 2 Juzer Ali 0.2% - 2 Ian Storm Taylor 0.2% - 2 Arian Stolwijk 0.2% - 2 domenic 0.2% - 1 Richard Dingwall 0.1% - 1 Russ Bradberry 0.1% - 1 Sasha Koss 0.1% - 1 Seiya Konno 0.1% - 1 Standa Opichal 0.1% - 1 Steve Mason 0.1% - 1 Will Langstroth 0.1% - 1 Yanis Wang 0.1% - 1 Yuest Wang 0.1% - 1 abrkn 0.1% - 1 airportyh 0.1% - 1 fengmk2 0.1% - 1 tgautier@yahoo.com 0.1% - 1 traleig1 0.1% - 1 vlad 0.1% - 1 yuitest 0.1% - 1 Adam Crabtree 0.1% - 1 Andreas Brekken 0.1% - 1 Atsuya Takagi 0.1% - 1 Austin Birch 0.1% - 1 Bjørge Næss 0.1% - 1 Brian Moore 0.1% - 1 Bryan Donovan 0.1% - 1 Casey Foster 0.1% - 1 Corey Butler 0.1% - 1 Dave McKenna 0.1% - 1 Fedor Indutny 0.1% - 1 Florian Margaine 0.1% - 1 Frederico Silva 0.1% - 1 Fredrik Lindin 0.1% - 1 Gareth Murphy 0.1% - 1 Gavin Mogan 0.1% - 1 Greg Perkins 0.1% - 1 Harry Brundage 0.1% - 1 Herman Junge 0.1% - 1 Ian Young 0.1% - 1 Ivan 0.1% - 1 Jaakko Salonen 0.1% - 1 Jakub Nešetřil 0.1% - 1 James Bowes 0.1% - 1 James Lal 0.1% - 1 Jason Barry 0.1% - 1 Javier Aranda 0.1% - 1 Jeff Kunkle 0.1% - 1 Jonathan Creamer 0.1% - 1 Jussi Virtanen 0.1% - 1 Katie Gengler 0.1% - 1 Kazuhito Hokamura 0.1% - 1 Koen Punt 0.1% - 1 Laszlo Bacsi 0.1% - 1 László Bácsi 0.1% - 1 Maciej Małecki 0.1% - 1 Matt Robenolt 0.1% - 1 Matt Smith 0.1% - 1 Matthew Shanley 0.1% - 1 Michael Schoonmaker 0.1% - 1 Phil Sung 0.1% - 1 R56 0.1% + 582 TJ Holowaychuk 44.3% + 389 Tj Holowaychuk 29.6% + 46 Travis Jeffery 3.5% + 31 Guillermo Rauch 2.4% + 13 Attila Domokos 1.0% + 10 John Firebaugh 0.8% + 8 Jo Liss 0.6% + 7 Nathan Rajlich 0.5% + 6 Mike Pennisi 0.5% + 6 James Carr 0.5% + 6 Brendan Nee 0.5% + 5 Aaron Heckmann 0.4% + 5 Ryunosuke SATO 0.4% + 4 hokaccha 0.3% + 4 Joshua Krall 0.3% + 4 Xavier Antoviaque 0.3% + 3 Jesse Dailey 0.2% + 3 Forbes Lindesay 0.2% + 3 Sindre Sorhus 0.2% + 3 Cory Thomas 0.2% + 3 Fredrik Enestad 0.2% + 3 Ben Lindsey 0.2% + 3 Tyson Tate 0.2% + 3 Mathieu Desvé 0.2% + 3 Valentin Agachi 0.2% + 3 Wil Moore III 0.2% + 3 Merrick Christensen 0.2% + 3 eiji.ienaga 0.2% + 3 fool2fish 0.2% + 3 Nathan Bowser 0.2% + 3 Paul Miller 0.2% + 2 Juzer Ali 0.2% + 2 Pete Hawkins 0.2% + 2 Jonas Westerlund 0.2% + 2 Arian Stolwijk 0.2% + 2 Quang Van 0.2% + 2 Glen Mailer 0.2% + 2 Justin DuJardin 0.2% + 2 FARKAS Máté 0.2% + 2 Raynos 0.2% + 2 Michael Riley 0.2% + 2 Michael Schoonmaker 0.2% + 2 Domenic Denicola 0.2% + 2 Simon Gaeremynck 0.2% + 2 Konstantin Käfer 0.2% + 2 domenic 0.2% + 2 Paul Armstrong 0.2% + 2 fcrisci 0.2% + 2 Alexander Early 0.2% + 2 Shawn Krisman 0.2% + 2 Brian Beck 0.2% + 2 Nathan Alderson 0.2% + 2 David Henderson 0.2% + 2 Timo Tijhof 0.2% + 2 Ian Storm Taylor 0.2% + 2 travis jeffery 0.2% + 1 Matt Smith 0.1% + 1 Matthew Shanley 0.1% + 1 Nathan Black 0.1% + 1 Phil Sung 0.1% + 1 R56 0.1% + 1 Refael Ackermann 0.1% + 1 Richard Dingwall 0.1% + 1 Romain Prieto 0.1% + 1 Roman Neuhauser 0.1% + 1 Roman Shtylman 0.1% + 1 Russ Bradberry 0.1% + 1 Russell Munson 0.1% + 1 Rustem Mustafin 0.1% + 1 Salehen Shovon Rahman 0.1% + 1 Sasha Koss 0.1% + 1 Seiya Konno 0.1% + 1 Simon Goumaz 0.1% + 1 Standa Opichal 0.1% + 1 Stephen Mathieson 0.1% + 1 Steve Mason 0.1% + 1 Tapiwa Kelvin 0.1% + 1 Teddy Zeenny 0.1% + 1 Tim Ehat 0.1% + 1 Vadim Nikitin 0.1% + 1 Victor Costan 0.1% + 1 Will Langstroth 0.1% + 1 Yanis Wang 0.1% + 1 Yuest Wang 0.1% + 1 abrkn 0.1% + 1 airportyh 0.1% + 1 badunk 0.1% + 1 fengmk2 0.1% + 1 grasGendarme 0.1% + 1 lodr 0.1% + 1 tgautier@yahoo.com 0.1% + 1 traleig1 0.1% + 1 vlad 0.1% + 1 yuitest 0.1% + 1 Adam Crabtree 0.1% + 1 Andreas Brekken 0.1% + 1 Andreas Lind Petersen 0.1% + 1 Andrew Nesbitt 0.1% + 1 Andrey Popp 0.1% + 1 Arnaud Brousseau 0.1% + 1 Atsuya Takagi 0.1% + 1 Austin Birch 0.1% + 1 Bjørge Næss 0.1% + 1 Brian Lalor 0.1% + 1 Brian M. Carlson 0.1% + 1 Brian Moore 0.1% + 1 Bryan Donovan 0.1% + 1 Casey Foster 0.1% + 1 ChrisWren 0.1% + 1 Corey Butler 0.1% + 1 Daniel Stockman 0.1% + 1 Dave McKenna 0.1% + 1 Di Wu 0.1% + 1 Dmitry Shirokov 0.1% + 1 Fedor Indutny 0.1% + 1 Florian Margaine 0.1% + 1 Frederico Silva 0.1% + 1 Fredrik Lindin 0.1% + 1 Gareth Murphy 0.1% + 1 Gavin Mogan 0.1% + 1 Glen Huang 0.1% + 1 Greg Perkins 0.1% + 1 Harry Brundage 0.1% + 1 Herman Junge 0.1% + 1 Ian Young 0.1% + 1 Ivan 0.1% + 1 JP Bochi 0.1% + 1 Jaakko Salonen 0.1% + 1 Jakub Nešetřil 0.1% + 1 James Bowes 0.1% + 1 James Lal 0.1% + 1 Jason Barry 0.1% + 1 Javier Aranda 0.1% + 1 Jeff Kunkle 0.1% + 1 Jeremy Martin 0.1% + 1 Jimmy Cuadra 0.1% + 1 Jonathan Creamer 0.1% + 1 Jussi Virtanen 0.1% + 1 Katie Gengler 0.1% + 1 Kazuhito Hokamura 0.1% + 1 Kirill Korolyov 0.1% + 1 Koen Punt 0.1% + 1 Laszlo Bacsi 0.1% + 1 Liam Newman 0.1% + 1 László Bácsi 0.1% + 1 Maciej Małecki 0.1% + 1 Mal Graty 0.1% + 1 Marc Kuo 0.1% + 1 Matt Robenolt 0.1% ``` ## Links diff --git a/test/mocha/bower.json b/test/mocha/bower.json index 59b3db3db..5a452f583 100644 --- a/test/mocha/bower.json +++ b/test/mocha/bower.json @@ -1,6 +1,6 @@ { "name": "mocha", - "version": "1.12.0", + "version": "1.17.0", "main": "mocha.js", "ignore": [ "bin", @@ -17,4 +17,4 @@ "Makefile", "package.json" ] -} \ No newline at end of file +} diff --git a/test/mocha/mocha.css b/test/mocha/mocha.css index 274b97762..42b9798fa 100644 --- a/test/mocha/mocha.css +++ b/test/mocha/mocha.css @@ -9,7 +9,8 @@ body { margin: 60px 50px; } -#mocha ul, #mocha li { +#mocha ul, +#mocha li { margin: 0; padding: 0; } @@ -18,7 +19,8 @@ body { list-style: none; } -#mocha h1, #mocha h2 { +#mocha h1, +#mocha h2 { margin: 0; } @@ -67,11 +69,11 @@ body { } #mocha .test.pass.medium .duration { - background: #C09853; + background: #c09853; } #mocha .test.pass.slow .duration { - background: #B94A48; + background: #b94a48; } #mocha .test.pass::before { @@ -87,7 +89,7 @@ body { font-size: 9px; margin-left: 5px; padding: 2px 5px; - color: white; + color: #fff; -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); box-shadow: inset 0 1px 1px rgba(0,0,0,.2); @@ -134,6 +136,11 @@ body { overflow: auto; } +/** + * (1): approximate for browsers not supporting calc + * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border) + * ^^ seriously + */ #mocha .test pre { display: block; float: left; @@ -142,6 +149,9 @@ body { margin: 5px; padding: 15px; border: 1px solid #eee; + max-width: 85%; /*(1)*/ + max-width: calc(100% - 42px); /*(2)*/ + word-wrap: break-word; border-bottom-color: #ddd; -webkit-border-radius: 3px; -webkit-box-shadow: 0 1px 3px #eee; @@ -243,14 +253,14 @@ body { height: 40px; } -#mocha code .comment { color: #ddd } -#mocha code .init { color: #2F6FAD } -#mocha code .string { color: #5890AD } -#mocha code .keyword { color: #8A6343 } -#mocha code .number { color: #2F6FAD } +#mocha code .comment { color: #ddd; } +#mocha code .init { color: #2f6fad; } +#mocha code .string { color: #5890ad; } +#mocha code .keyword { color: #8a6343; } +#mocha code .number { color: #2f6fad; } @media screen and (max-device-width: 480px) { - #mocha { + #mocha { margin: 60px 0px; } diff --git a/test/mocha/mocha.js b/test/mocha/mocha.js index 9b7c1fa56..e4f8e6b20 100644 --- a/test/mocha/mocha.js +++ b/test/mocha/mocha.js @@ -604,7 +604,6 @@ require.register("browser/path.js", function(module, exports, require){ }); // module: browser/path.js require.register("browser/progress.js", function(module, exports, require){ - /** * Expose `Progress`. */ @@ -693,40 +692,41 @@ Progress.prototype.update = function(n){ */ 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; - - ctx.font = fontSize + 'px ' + this._font; - - 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(); - - // 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; - - ctx.fillText( - text - , x - w / 2 + 1 - , y + fontSize / 2 - 1); - + 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; + + 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(); + + // 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; + + ctx.fillText( + text + , x - w / 2 + 1 + , y + fontSize / 2 - 1); + } catch (ex) {} //don't fail if we can't render progress return this; }; @@ -1437,6 +1437,21 @@ function Mocha(options) { if (null != options.timeout) this.timeout(options.timeout); this.useColors(options.useColors) 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; + }); } /** @@ -1640,6 +1655,21 @@ Mocha.prototype.useColors = function(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. * @@ -1698,6 +1728,7 @@ Mocha.prototype.run = function(fn){ if (options.globals) runner.globals(options.globals); if (options.growl) this._growl(runner, reporter); exports.reporters.Base.useColors = options.useColors; + exports.reporters.Base.inlineDiffs = options.useInlineDiffs; return runner.run(fn); }; @@ -1730,9 +1761,7 @@ var y = d * 365.25; module.exports = function(val, options){ options = options || {}; if ('string' == typeof val) return parse(val); - return options.long - ? long(val) - : short(val); + return options.long ? longFormat(val) : shortFormat(val); }; /** @@ -1782,7 +1811,7 @@ function parse(str) { * @api private */ -function short(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'; @@ -1798,7 +1827,7 @@ function short(ms) { * @api private */ -function long(ms) { +function longFormat(ms) { return plural(ms, d, 'day') || plural(ms, h, 'hour') || plural(ms, m, 'minute') @@ -1826,7 +1855,8 @@ require.register("reporters/base.js", function(module, exports, require){ var tty = require('browser/tty') , diff = require('browser/diff') - , ms = require('../ms'); + , ms = require('../ms') + , utils = require('../utils'); /** * Save timer references to avoid Sinon interfering (see GH-237). @@ -1962,7 +1992,7 @@ exports.cursor = { exports.cursor.deleteLine(); exports.cursor.beginningOfLine(); } else { - process.stdout.write('\n'); + process.stdout.write('\r'); } } }; @@ -2000,8 +2030,8 @@ exports.list = function(failures){ // explicitly show diff if (err.showDiff && sameType(actual, expected)) { escape = false; - err.actual = actual = stringify(actual); - err.expected = expected = stringify(expected); + err.actual = actual = stringify(canonicalize(actual)); + err.expected = expected = stringify(canonicalize(expected)); } // actual / expected diff @@ -2266,7 +2296,7 @@ function colorLines(name, str) { /** * Stringify `obj`. * - * @param {Mixed} obj + * @param {Object} obj * @return {String} * @api private */ @@ -2276,6 +2306,40 @@ function stringify(obj) { return JSON.stringify(obj, null, 2); } +/** + * Return a new object that has the keys in sorted order. + * @param {Object} obj + * @return {Object} + * @api private + */ + + function canonicalize(obj, stack) { + stack = stack || []; + + if (utils.indexOf(stack, obj) !== -1) return obj; + + var canonicalizedObj; + + if ('[object Array]' == {}.toString.call(obj)) { + stack.push(obj); + canonicalizedObj = utils.map(obj, function(item) { + return canonicalize(item, stack); + }); + stack.pop(); + } else if (typeof obj === 'object' && obj !== null) { + stack.push(obj); + canonicalizedObj = {}; + utils.forEach(utils.keys(obj).sort(), function(key) { + canonicalizedObj[key] = canonicalize(obj[key], stack); + }); + stack.pop(); + } else { + canonicalizedObj = obj; + } + + return canonicalizedObj; + } + /** * Check that a / b have the same type. * @@ -2292,7 +2356,6 @@ function sameType(a, b) { } - }); // module: reporters/base.js require.register("reporters/doc.js", function(module, exports, require){ @@ -3799,10 +3862,6 @@ function Spec(runner) { 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); @@ -3961,6 +4020,10 @@ function XUnit(runner) { , tests = [] , self = this; + runner.on('pending', function(test){ + tests.push(test); + }); + runner.on('pass', function(test){ tests.push(test); }); @@ -4003,7 +4066,7 @@ function test(test) { var attrs = { classname: test.parent.fullTitle() , name: test.title - , time: test.duration / 1000 + , time: (test.duration / 1000) || 0 }; if ('failed' == test.state) { @@ -4193,6 +4256,16 @@ Runnable.prototype.resetTimeout = function(){ }, 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)`. * @@ -4324,6 +4397,7 @@ module.exports = Runner; function Runner(suite) { var self = this; this._globals = []; + this._abort = false; this.suite = suite; this.total = suite.total(); this.failures = 0; @@ -4422,9 +4496,7 @@ Runner.prototype.globalProps = function() { 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; }; @@ -4437,10 +4509,15 @@ Runner.prototype.globals = function(arr){ Runner.prototype.checkGlobals = function(test){ if (this.ignoreLeaks) return; var ok = this._globals; + var globals = this.globalProps(); var isNode = process.kill; var leaks; + if (test) { + ok = ok.concat(test._allowedGlobals || []); + } + // check length - 2 ('errno' and 'location' globals) if (isNode && 1 == ok.length - globals.length) return; else if (2 == ok.length - globals.length) return; @@ -4480,10 +4557,18 @@ Runner.prototype.fail = function(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 @@ -4492,7 +4577,9 @@ Runner.prototype.fail = function(test, err){ Runner.prototype.failHook = function(hook, err){ this.fail(hook, err); - this.emit('end'); + if (this.suite.bail()) { + this.emit('end'); + } }; /** @@ -4527,7 +4614,12 @@ Runner.prototype.hook = function(name, fn){ hook.removeAllListeners('error'); var testError = hook.error(); if (testError) self.fail(self.test, testError); - if (err) return self.failHook(hook, err); + if (err) { + 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); @@ -4541,7 +4633,7 @@ Runner.prototype.hook = function(name, fn){ /** * 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 @@ -4563,8 +4655,9 @@ Runner.prototype.hooks = function(name, suites, 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()); @@ -4652,10 +4745,39 @@ Runner.prototype.runTests = function(suite, fn){ , 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(); @@ -4676,7 +4798,10 @@ Runner.prototype.runTests = function(suite, fn){ // execute test and hook(s) self.emit('test', self.test = test); - self.hookDown('beforeEach', function(){ + self.hookDown('beforeEach', function(err, errSuite){ + + if (err) return hookErr(err, errSuite, false); + self.currentRunnable = self.test; self.runTest(function(err){ test = self.test; @@ -4719,21 +4844,37 @@ Runner.prototype.runSuite = function(suite, 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); }); }; @@ -4803,6 +4944,17 @@ Runner.prototype.run = function(fn){ 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`. * @@ -5229,6 +5381,22 @@ exports.forEach = function(arr, fn, scope){ 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)); + return result; +}; + /** * Array#indexOf (<=IE8) * @@ -5378,11 +5546,13 @@ exports.slug = function(str){ exports.clean = function(str) { str = str + .replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, '') .replace(/^function *\(.*\) *{/, '') .replace(/\s+\}$/, ''); - var whitespace = str.match(/^\n?(\s*)/)[1] - , re = new RegExp('^' + whitespace, 'gm'); + var spaces = str.match(/^\n?( *)/)[1].length + , tabs = str.match(/^\n?(\t*)/)[1].length + , re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm'); str = str.replace(re, ''); @@ -5493,13 +5663,17 @@ var process = {}; process.exit = function(status){}; process.stdout = {}; +var uncaughtExceptionHandlers = []; + /** * Remove uncaughtException listener. */ -process.removeListener = function(e){ +process.removeListener = function(e, fn){ if ('uncaughtException' == e) { global.onerror = function() {}; + var i = Mocha.utils.indexOf(uncaughtExceptionHandlers, fn); + if (i != -1) { uncaughtExceptionHandlers.splice(i, 1); } } }; @@ -5511,7 +5685,9 @@ process.on = function(e, fn){ if ('uncaughtException' == e) { global.onerror = function(err, url, line){ fn(new Error(err + ' (' + url + ':' + line + ')')); + return true; }; + uncaughtExceptionHandlers.push(fn); } }; @@ -5522,6 +5698,11 @@ process.on = function(e, fn){ var Mocha = global.Mocha = require('mocha'), mocha = global.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'); + var immediateQueue = [] , immediateTimeout; @@ -5548,6 +5729,18 @@ Mocha.Runner.immediately = function(callback) { } }; +/** + * 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; +}; + /** * Override ui to ensure that the ui functions are initialized. * Normally this would happen in Mocha.prototype.loadFiles.