├── .travis.yml ├── README.md ├── browser ├── index.html ├── mocha.css ├── mocha.js └── tests.js ├── checktime.js ├── config.json ├── gulpfile.js ├── lib ├── getPalette.js └── hex2rgb.js ├── outline.markdown ├── package.json ├── server.js ├── test ├── fixtures │ └── config-palette-non-array.json ├── getPalette.test.js └── hex2rgb.test.js ├── times.txt └── views └── index.jade /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.11" 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Tuts+ Course: JavaScript Unit Testing With Mocha, Chai and Sinon 2 | #### Instructor: Jason Rhodes 3 | 4 | In this course, you'll gain a basic understanding of the fundamentals of unit testing your JavaScript code using some popular testing tools. By the end of this course you should feel empowered and excited to start writing tests for all of your JavaScript code, whether it's for the server or the client. 5 | 6 | Source files for the Tuts+ course: [JavaScript Unit Testing With Mocha, Chai and Sinon](https://courses.tutsplus.com/) 7 | 8 | **Available on Tuts+ 28 July, 2014** 9 | 10 | #### USAGE NOTES 11 | 12 | If you'd like to see the code as it is when the course begins, switch to [the "start" branch](https://github.com/tutsplus/javascript-unit-testing-mocha-chai-sinon/tree/start) 13 | 14 | [![Build Status](https://travis-ci.org/jasonrhodes/courses-mocha.svg?branch=master)](https://travis-ci.org/jasonrhodes/courses-mocha) 15 | 16 | [![Build Status](https://ci.testling.com/jasonrhodes/courses-mocha.png?style=flat)](http://ci.testling.com/jasonrhodes/courses-mocha) 17 | -------------------------------------------------------------------------------- /browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /browser/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | margin:0; 5 | } 6 | 7 | #mocha { 8 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | margin: 60px 50px; 10 | } 11 | 12 | #mocha ul, 13 | #mocha li { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | #mocha ul { 19 | list-style: none; 20 | } 21 | 22 | #mocha h1, 23 | #mocha h2 { 24 | margin: 0; 25 | } 26 | 27 | #mocha h1 { 28 | margin-top: 15px; 29 | font-size: 1em; 30 | font-weight: 200; 31 | } 32 | 33 | #mocha h1 a { 34 | text-decoration: none; 35 | color: inherit; 36 | } 37 | 38 | #mocha h1 a:hover { 39 | text-decoration: underline; 40 | } 41 | 42 | #mocha .suite .suite h1 { 43 | margin-top: 0; 44 | font-size: .8em; 45 | } 46 | 47 | #mocha .hidden { 48 | display: none; 49 | } 50 | 51 | #mocha h2 { 52 | font-size: 12px; 53 | font-weight: normal; 54 | cursor: pointer; 55 | } 56 | 57 | #mocha .suite { 58 | margin-left: 15px; 59 | } 60 | 61 | #mocha .test { 62 | margin-left: 15px; 63 | overflow: hidden; 64 | } 65 | 66 | #mocha .test.pending:hover h2::after { 67 | content: '(pending)'; 68 | font-family: arial, sans-serif; 69 | } 70 | 71 | #mocha .test.pass.medium .duration { 72 | background: #c09853; 73 | } 74 | 75 | #mocha .test.pass.slow .duration { 76 | background: #b94a48; 77 | } 78 | 79 | #mocha .test.pass::before { 80 | content: '✓'; 81 | font-size: 12px; 82 | display: block; 83 | float: left; 84 | margin-right: 5px; 85 | color: #00d6b2; 86 | } 87 | 88 | #mocha .test.pass .duration { 89 | font-size: 9px; 90 | margin-left: 5px; 91 | padding: 2px 5px; 92 | color: #fff; 93 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 94 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 95 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 96 | -webkit-border-radius: 5px; 97 | -moz-border-radius: 5px; 98 | -ms-border-radius: 5px; 99 | -o-border-radius: 5px; 100 | border-radius: 5px; 101 | } 102 | 103 | #mocha .test.pass.fast .duration { 104 | display: none; 105 | } 106 | 107 | #mocha .test.pending { 108 | color: #0b97c4; 109 | } 110 | 111 | #mocha .test.pending::before { 112 | content: '◦'; 113 | color: #0b97c4; 114 | } 115 | 116 | #mocha .test.fail { 117 | color: #c00; 118 | } 119 | 120 | #mocha .test.fail pre { 121 | color: black; 122 | } 123 | 124 | #mocha .test.fail::before { 125 | content: '✖'; 126 | font-size: 12px; 127 | display: block; 128 | float: left; 129 | margin-right: 5px; 130 | color: #c00; 131 | } 132 | 133 | #mocha .test pre.error { 134 | color: #c00; 135 | max-height: 300px; 136 | overflow: auto; 137 | } 138 | 139 | /** 140 | * (1): approximate for browsers not supporting calc 141 | * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border) 142 | * ^^ seriously 143 | */ 144 | #mocha .test pre { 145 | display: block; 146 | float: left; 147 | clear: left; 148 | font: 12px/1.5 monaco, monospace; 149 | margin: 5px; 150 | padding: 15px; 151 | border: 1px solid #eee; 152 | max-width: 85%; /*(1)*/ 153 | max-width: calc(100% - 42px); /*(2)*/ 154 | word-wrap: break-word; 155 | border-bottom-color: #ddd; 156 | -webkit-border-radius: 3px; 157 | -webkit-box-shadow: 0 1px 3px #eee; 158 | -moz-border-radius: 3px; 159 | -moz-box-shadow: 0 1px 3px #eee; 160 | border-radius: 3px; 161 | } 162 | 163 | #mocha .test h2 { 164 | position: relative; 165 | } 166 | 167 | #mocha .test a.replay { 168 | position: absolute; 169 | top: 3px; 170 | right: 0; 171 | text-decoration: none; 172 | vertical-align: middle; 173 | display: block; 174 | width: 15px; 175 | height: 15px; 176 | line-height: 15px; 177 | text-align: center; 178 | background: #eee; 179 | font-size: 15px; 180 | -moz-border-radius: 15px; 181 | border-radius: 15px; 182 | -webkit-transition: opacity 200ms; 183 | -moz-transition: opacity 200ms; 184 | transition: opacity 200ms; 185 | opacity: 0.3; 186 | color: #888; 187 | } 188 | 189 | #mocha .test:hover a.replay { 190 | opacity: 1; 191 | } 192 | 193 | #mocha-report.pass .test.fail { 194 | display: none; 195 | } 196 | 197 | #mocha-report.fail .test.pass { 198 | display: none; 199 | } 200 | 201 | #mocha-report.pending .test.pass, 202 | #mocha-report.pending .test.fail { 203 | display: none; 204 | } 205 | #mocha-report.pending .test.pass.pending { 206 | display: block; 207 | } 208 | 209 | #mocha-error { 210 | color: #c00; 211 | font-size: 1.5em; 212 | font-weight: 100; 213 | letter-spacing: 1px; 214 | } 215 | 216 | #mocha-stats { 217 | position: fixed; 218 | top: 15px; 219 | right: 10px; 220 | font-size: 12px; 221 | margin: 0; 222 | color: #888; 223 | z-index: 1; 224 | } 225 | 226 | #mocha-stats .progress { 227 | float: right; 228 | padding-top: 0; 229 | } 230 | 231 | #mocha-stats em { 232 | color: black; 233 | } 234 | 235 | #mocha-stats a { 236 | text-decoration: none; 237 | color: inherit; 238 | } 239 | 240 | #mocha-stats a:hover { 241 | border-bottom: 1px solid #eee; 242 | } 243 | 244 | #mocha-stats li { 245 | display: inline-block; 246 | margin: 0 5px; 247 | list-style: none; 248 | padding-top: 11px; 249 | } 250 | 251 | #mocha-stats canvas { 252 | width: 40px; 253 | height: 40px; 254 | } 255 | 256 | #mocha code .comment { color: #ddd; } 257 | #mocha code .init { color: #2f6fad; } 258 | #mocha code .string { color: #5890ad; } 259 | #mocha code .keyword { color: #8a6343; } 260 | #mocha code .number { color: #2f6fad; } 261 | 262 | @media screen and (max-device-width: 480px) { 263 | #mocha { 264 | margin: 60px 0px; 265 | } 266 | 267 | #mocha #stats { 268 | position: absolute; 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /browser/mocha.js: -------------------------------------------------------------------------------- 1 | ;(function(){ 2 | 3 | // CommonJS require() 4 | 5 | function require(p){ 6 | var path = require.resolve(p) 7 | , mod = require.modules[path]; 8 | if (!mod) throw new Error('failed to require "' + p + '"'); 9 | if (!mod.exports) { 10 | mod.exports = {}; 11 | mod.call(mod.exports, mod, mod.exports, require.relative(path)); 12 | } 13 | return mod.exports; 14 | } 15 | 16 | require.modules = {}; 17 | 18 | require.resolve = function (path){ 19 | var orig = path 20 | , reg = path + '.js' 21 | , index = path + '/index.js'; 22 | return require.modules[reg] && reg 23 | || require.modules[index] && index 24 | || orig; 25 | }; 26 | 27 | require.register = function (path, fn){ 28 | require.modules[path] = fn; 29 | }; 30 | 31 | require.relative = function (parent) { 32 | return function(p){ 33 | if ('.' != p.charAt(0)) return require(p); 34 | 35 | var path = parent.split('/') 36 | , segs = p.split('/'); 37 | path.pop(); 38 | 39 | for (var i = 0; i < segs.length; i++) { 40 | var seg = segs[i]; 41 | if ('..' == seg) path.pop(); 42 | else if ('.' != seg) path.push(seg); 43 | } 44 | 45 | return require(path.join('/')); 46 | }; 47 | }; 48 | 49 | 50 | require.register("browser/debug.js", function(module, exports, require){ 51 | 52 | module.exports = function(type){ 53 | return function(){ 54 | } 55 | }; 56 | 57 | }); // module: browser/debug.js 58 | 59 | require.register("browser/diff.js", function(module, exports, require){ 60 | /* See LICENSE file for terms of use */ 61 | 62 | /* 63 | * Text diff implementation. 64 | * 65 | * This library supports the following APIS: 66 | * JsDiff.diffChars: Character by character diff 67 | * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace 68 | * JsDiff.diffLines: Line based diff 69 | * 70 | * JsDiff.diffCss: Diff targeted at CSS content 71 | * 72 | * These methods are based on the implementation proposed in 73 | * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). 74 | * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 75 | */ 76 | var JsDiff = (function() { 77 | /*jshint maxparams: 5*/ 78 | function clonePath(path) { 79 | return { newPos: path.newPos, components: path.components.slice(0) }; 80 | } 81 | function removeEmpty(array) { 82 | var ret = []; 83 | for (var i = 0; i < array.length; i++) { 84 | if (array[i]) { 85 | ret.push(array[i]); 86 | } 87 | } 88 | return ret; 89 | } 90 | function escapeHTML(s) { 91 | var n = s; 92 | n = n.replace(/&/g, '&'); 93 | n = n.replace(//g, '>'); 95 | n = n.replace(/"/g, '"'); 96 | 97 | return n; 98 | } 99 | 100 | var Diff = function(ignoreWhitespace) { 101 | this.ignoreWhitespace = ignoreWhitespace; 102 | }; 103 | Diff.prototype = { 104 | diff: function(oldString, newString) { 105 | // Handle the identity case (this is due to unrolling editLength == 0 106 | if (newString === oldString) { 107 | return [{ value: newString }]; 108 | } 109 | if (!newString) { 110 | return [{ value: oldString, removed: true }]; 111 | } 112 | if (!oldString) { 113 | return [{ value: newString, added: true }]; 114 | } 115 | 116 | newString = this.tokenize(newString); 117 | oldString = this.tokenize(oldString); 118 | 119 | var newLen = newString.length, oldLen = oldString.length; 120 | var maxEditLength = newLen + oldLen; 121 | var bestPath = [{ newPos: -1, components: [] }]; 122 | 123 | // Seed editLength = 0 124 | var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); 125 | if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) { 126 | return bestPath[0].components; 127 | } 128 | 129 | for (var editLength = 1; editLength <= maxEditLength; editLength++) { 130 | for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) { 131 | var basePath; 132 | var addPath = bestPath[diagonalPath-1], 133 | removePath = bestPath[diagonalPath+1]; 134 | oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; 135 | if (addPath) { 136 | // No one else is going to attempt to use this value, clear it 137 | bestPath[diagonalPath-1] = undefined; 138 | } 139 | 140 | var canAdd = addPath && addPath.newPos+1 < newLen; 141 | var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; 142 | if (!canAdd && !canRemove) { 143 | bestPath[diagonalPath] = undefined; 144 | continue; 145 | } 146 | 147 | // Select the diagonal that we want to branch from. We select the prior 148 | // path whose position in the new string is the farthest from the origin 149 | // and does not pass the bounds of the diff graph 150 | if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { 151 | basePath = clonePath(removePath); 152 | this.pushComponent(basePath.components, oldString[oldPos], undefined, true); 153 | } else { 154 | basePath = clonePath(addPath); 155 | basePath.newPos++; 156 | this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined); 157 | } 158 | 159 | var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath); 160 | 161 | if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) { 162 | return basePath.components; 163 | } else { 164 | bestPath[diagonalPath] = basePath; 165 | } 166 | } 167 | } 168 | }, 169 | 170 | pushComponent: function(components, value, added, removed) { 171 | var last = components[components.length-1]; 172 | if (last && last.added === added && last.removed === removed) { 173 | // We need to clone here as the component clone operation is just 174 | // as shallow array clone 175 | components[components.length-1] = 176 | {value: this.join(last.value, value), added: added, removed: removed }; 177 | } else { 178 | components.push({value: value, added: added, removed: removed }); 179 | } 180 | }, 181 | extractCommon: function(basePath, newString, oldString, diagonalPath) { 182 | var newLen = newString.length, 183 | oldLen = oldString.length, 184 | newPos = basePath.newPos, 185 | oldPos = newPos - diagonalPath; 186 | while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) { 187 | newPos++; 188 | oldPos++; 189 | 190 | this.pushComponent(basePath.components, newString[newPos], undefined, undefined); 191 | } 192 | basePath.newPos = newPos; 193 | return oldPos; 194 | }, 195 | 196 | equals: function(left, right) { 197 | var reWhitespace = /\S/; 198 | if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) { 199 | return true; 200 | } else { 201 | return left === right; 202 | } 203 | }, 204 | join: function(left, right) { 205 | return left + right; 206 | }, 207 | tokenize: function(value) { 208 | return value; 209 | } 210 | }; 211 | 212 | var CharDiff = new Diff(); 213 | 214 | var WordDiff = new Diff(true); 215 | var WordWithSpaceDiff = new Diff(); 216 | WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) { 217 | return removeEmpty(value.split(/(\s+|\b)/)); 218 | }; 219 | 220 | var CssDiff = new Diff(true); 221 | CssDiff.tokenize = function(value) { 222 | return removeEmpty(value.split(/([{}:;,]|\s+)/)); 223 | }; 224 | 225 | var LineDiff = new Diff(); 226 | LineDiff.tokenize = function(value) { 227 | return value.split(/^/m); 228 | }; 229 | 230 | return { 231 | Diff: Diff, 232 | 233 | diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); }, 234 | diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); }, 235 | diffWordsWithSpace: function(oldStr, newStr) { return WordWithSpaceDiff.diff(oldStr, newStr); }, 236 | diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); }, 237 | 238 | diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); }, 239 | 240 | createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { 241 | var ret = []; 242 | 243 | ret.push('Index: ' + fileName); 244 | ret.push('==================================================================='); 245 | ret.push('--- ' + fileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader)); 246 | ret.push('+++ ' + fileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); 247 | 248 | var diff = LineDiff.diff(oldStr, newStr); 249 | if (!diff[diff.length-1].value) { 250 | diff.pop(); // Remove trailing newline add 251 | } 252 | diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier 253 | 254 | function contextLines(lines) { 255 | return lines.map(function(entry) { return ' ' + entry; }); 256 | } 257 | function eofNL(curRange, i, current) { 258 | var last = diff[diff.length-2], 259 | isLast = i === diff.length-2, 260 | isLastOfType = i === diff.length-3 && (current.added !== last.added || current.removed !== last.removed); 261 | 262 | // Figure out if this is the last line for the given file and missing NL 263 | if (!/\n$/.test(current.value) && (isLast || isLastOfType)) { 264 | curRange.push('\\ No newline at end of file'); 265 | } 266 | } 267 | 268 | var oldRangeStart = 0, newRangeStart = 0, curRange = [], 269 | oldLine = 1, newLine = 1; 270 | for (var i = 0; i < diff.length; i++) { 271 | var current = diff[i], 272 | lines = current.lines || current.value.replace(/\n$/, '').split('\n'); 273 | current.lines = lines; 274 | 275 | if (current.added || current.removed) { 276 | if (!oldRangeStart) { 277 | var prev = diff[i-1]; 278 | oldRangeStart = oldLine; 279 | newRangeStart = newLine; 280 | 281 | if (prev) { 282 | curRange = contextLines(prev.lines.slice(-4)); 283 | oldRangeStart -= curRange.length; 284 | newRangeStart -= curRange.length; 285 | } 286 | } 287 | curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?'+':'-') + entry; })); 288 | eofNL(curRange, i, current); 289 | 290 | if (current.added) { 291 | newLine += lines.length; 292 | } else { 293 | oldLine += lines.length; 294 | } 295 | } else { 296 | if (oldRangeStart) { 297 | // Close out any changes that have been output (or join overlapping) 298 | if (lines.length <= 8 && i < diff.length-2) { 299 | // Overlapping 300 | curRange.push.apply(curRange, contextLines(lines)); 301 | } else { 302 | // end the range and output 303 | var contextSize = Math.min(lines.length, 4); 304 | ret.push( 305 | '@@ -' + oldRangeStart + ',' + (oldLine-oldRangeStart+contextSize) 306 | + ' +' + newRangeStart + ',' + (newLine-newRangeStart+contextSize) 307 | + ' @@'); 308 | ret.push.apply(ret, curRange); 309 | ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); 310 | if (lines.length <= 4) { 311 | eofNL(ret, i, current); 312 | } 313 | 314 | oldRangeStart = 0; newRangeStart = 0; curRange = []; 315 | } 316 | } 317 | oldLine += lines.length; 318 | newLine += lines.length; 319 | } 320 | } 321 | 322 | return ret.join('\n') + '\n'; 323 | }, 324 | 325 | applyPatch: function(oldStr, uniDiff) { 326 | var diffstr = uniDiff.split('\n'); 327 | var diff = []; 328 | var remEOFNL = false, 329 | addEOFNL = false; 330 | 331 | for (var i = (diffstr[0][0]==='I'?4:0); i < diffstr.length; i++) { 332 | if(diffstr[i][0] === '@') { 333 | var meh = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/); 334 | diff.unshift({ 335 | start:meh[3], 336 | oldlength:meh[2], 337 | oldlines:[], 338 | newlength:meh[4], 339 | newlines:[] 340 | }); 341 | } else if(diffstr[i][0] === '+') { 342 | diff[0].newlines.push(diffstr[i].substr(1)); 343 | } else if(diffstr[i][0] === '-') { 344 | diff[0].oldlines.push(diffstr[i].substr(1)); 345 | } else if(diffstr[i][0] === ' ') { 346 | diff[0].newlines.push(diffstr[i].substr(1)); 347 | diff[0].oldlines.push(diffstr[i].substr(1)); 348 | } else if(diffstr[i][0] === '\\') { 349 | if (diffstr[i-1][0] === '+') { 350 | remEOFNL = true; 351 | } else if(diffstr[i-1][0] === '-') { 352 | addEOFNL = true; 353 | } 354 | } 355 | } 356 | 357 | var str = oldStr.split('\n'); 358 | for (var i = diff.length - 1; i >= 0; i--) { 359 | var d = diff[i]; 360 | for (var j = 0; j < d.oldlength; j++) { 361 | if(str[d.start-1+j] !== d.oldlines[j]) { 362 | return false; 363 | } 364 | } 365 | Array.prototype.splice.apply(str,[d.start-1,+d.oldlength].concat(d.newlines)); 366 | } 367 | 368 | if (remEOFNL) { 369 | while (!str[str.length-1]) { 370 | str.pop(); 371 | } 372 | } else if (addEOFNL) { 373 | str.push(''); 374 | } 375 | return str.join('\n'); 376 | }, 377 | 378 | convertChangesToXML: function(changes){ 379 | var ret = []; 380 | for ( var i = 0; i < changes.length; i++) { 381 | var change = changes[i]; 382 | if (change.added) { 383 | ret.push(''); 384 | } else if (change.removed) { 385 | ret.push(''); 386 | } 387 | 388 | ret.push(escapeHTML(change.value)); 389 | 390 | if (change.added) { 391 | ret.push(''); 392 | } else if (change.removed) { 393 | ret.push(''); 394 | } 395 | } 396 | return ret.join(''); 397 | }, 398 | 399 | // See: http://code.google.com/p/google-diff-match-patch/wiki/API 400 | convertChangesToDMP: function(changes){ 401 | var ret = [], change; 402 | for ( var i = 0; i < changes.length; i++) { 403 | change = changes[i]; 404 | ret.push([(change.added ? 1 : change.removed ? -1 : 0), change.value]); 405 | } 406 | return ret; 407 | } 408 | }; 409 | })(); 410 | 411 | if (typeof module !== 'undefined') { 412 | module.exports = JsDiff; 413 | } 414 | 415 | }); // module: browser/diff.js 416 | 417 | require.register("browser/events.js", function(module, exports, require){ 418 | 419 | /** 420 | * Module exports. 421 | */ 422 | 423 | exports.EventEmitter = EventEmitter; 424 | 425 | /** 426 | * Check if `obj` is an array. 427 | */ 428 | 429 | function isArray(obj) { 430 | return '[object Array]' == {}.toString.call(obj); 431 | } 432 | 433 | /** 434 | * Event emitter constructor. 435 | * 436 | * @api public 437 | */ 438 | 439 | function EventEmitter(){}; 440 | 441 | /** 442 | * Adds a listener. 443 | * 444 | * @api public 445 | */ 446 | 447 | EventEmitter.prototype.on = function (name, fn) { 448 | if (!this.$events) { 449 | this.$events = {}; 450 | } 451 | 452 | if (!this.$events[name]) { 453 | this.$events[name] = fn; 454 | } else if (isArray(this.$events[name])) { 455 | this.$events[name].push(fn); 456 | } else { 457 | this.$events[name] = [this.$events[name], fn]; 458 | } 459 | 460 | return this; 461 | }; 462 | 463 | EventEmitter.prototype.addListener = EventEmitter.prototype.on; 464 | 465 | /** 466 | * Adds a volatile listener. 467 | * 468 | * @api public 469 | */ 470 | 471 | EventEmitter.prototype.once = function (name, fn) { 472 | var self = this; 473 | 474 | function on () { 475 | self.removeListener(name, on); 476 | fn.apply(this, arguments); 477 | }; 478 | 479 | on.listener = fn; 480 | this.on(name, on); 481 | 482 | return this; 483 | }; 484 | 485 | /** 486 | * Removes a listener. 487 | * 488 | * @api public 489 | */ 490 | 491 | EventEmitter.prototype.removeListener = function (name, fn) { 492 | if (this.$events && this.$events[name]) { 493 | var list = this.$events[name]; 494 | 495 | if (isArray(list)) { 496 | var pos = -1; 497 | 498 | for (var i = 0, l = list.length; i < l; i++) { 499 | if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { 500 | pos = i; 501 | break; 502 | } 503 | } 504 | 505 | if (pos < 0) { 506 | return this; 507 | } 508 | 509 | list.splice(pos, 1); 510 | 511 | if (!list.length) { 512 | delete this.$events[name]; 513 | } 514 | } else if (list === fn || (list.listener && list.listener === fn)) { 515 | delete this.$events[name]; 516 | } 517 | } 518 | 519 | return this; 520 | }; 521 | 522 | /** 523 | * Removes all listeners for an event. 524 | * 525 | * @api public 526 | */ 527 | 528 | EventEmitter.prototype.removeAllListeners = function (name) { 529 | if (name === undefined) { 530 | this.$events = {}; 531 | return this; 532 | } 533 | 534 | if (this.$events && this.$events[name]) { 535 | this.$events[name] = null; 536 | } 537 | 538 | return this; 539 | }; 540 | 541 | /** 542 | * Gets all listeners for a certain event. 543 | * 544 | * @api public 545 | */ 546 | 547 | EventEmitter.prototype.listeners = function (name) { 548 | if (!this.$events) { 549 | this.$events = {}; 550 | } 551 | 552 | if (!this.$events[name]) { 553 | this.$events[name] = []; 554 | } 555 | 556 | if (!isArray(this.$events[name])) { 557 | this.$events[name] = [this.$events[name]]; 558 | } 559 | 560 | return this.$events[name]; 561 | }; 562 | 563 | /** 564 | * Emits an event. 565 | * 566 | * @api public 567 | */ 568 | 569 | EventEmitter.prototype.emit = function (name) { 570 | if (!this.$events) { 571 | return false; 572 | } 573 | 574 | var handler = this.$events[name]; 575 | 576 | if (!handler) { 577 | return false; 578 | } 579 | 580 | var args = [].slice.call(arguments, 1); 581 | 582 | if ('function' == typeof handler) { 583 | handler.apply(this, args); 584 | } else if (isArray(handler)) { 585 | var listeners = handler.slice(); 586 | 587 | for (var i = 0, l = listeners.length; i < l; i++) { 588 | listeners[i].apply(this, args); 589 | } 590 | } else { 591 | return false; 592 | } 593 | 594 | return true; 595 | }; 596 | }); // module: browser/events.js 597 | 598 | require.register("browser/fs.js", function(module, exports, require){ 599 | 600 | }); // module: browser/fs.js 601 | 602 | require.register("browser/path.js", function(module, exports, require){ 603 | 604 | }); // module: browser/path.js 605 | 606 | require.register("browser/progress.js", function(module, exports, require){ 607 | /** 608 | * Expose `Progress`. 609 | */ 610 | 611 | module.exports = Progress; 612 | 613 | /** 614 | * Initialize a new `Progress` indicator. 615 | */ 616 | 617 | function Progress() { 618 | this.percent = 0; 619 | this.size(0); 620 | this.fontSize(11); 621 | this.font('helvetica, arial, sans-serif'); 622 | } 623 | 624 | /** 625 | * Set progress size to `n`. 626 | * 627 | * @param {Number} n 628 | * @return {Progress} for chaining 629 | * @api public 630 | */ 631 | 632 | Progress.prototype.size = function(n){ 633 | this._size = n; 634 | return this; 635 | }; 636 | 637 | /** 638 | * Set text to `str`. 639 | * 640 | * @param {String} str 641 | * @return {Progress} for chaining 642 | * @api public 643 | */ 644 | 645 | Progress.prototype.text = function(str){ 646 | this._text = str; 647 | return this; 648 | }; 649 | 650 | /** 651 | * Set font size to `n`. 652 | * 653 | * @param {Number} n 654 | * @return {Progress} for chaining 655 | * @api public 656 | */ 657 | 658 | Progress.prototype.fontSize = function(n){ 659 | this._fontSize = n; 660 | return this; 661 | }; 662 | 663 | /** 664 | * Set font `family`. 665 | * 666 | * @param {String} family 667 | * @return {Progress} for chaining 668 | */ 669 | 670 | Progress.prototype.font = function(family){ 671 | this._font = family; 672 | return this; 673 | }; 674 | 675 | /** 676 | * Update percentage to `n`. 677 | * 678 | * @param {Number} n 679 | * @return {Progress} for chaining 680 | */ 681 | 682 | Progress.prototype.update = function(n){ 683 | this.percent = n; 684 | return this; 685 | }; 686 | 687 | /** 688 | * Draw on `ctx`. 689 | * 690 | * @param {CanvasRenderingContext2d} ctx 691 | * @return {Progress} for chaining 692 | */ 693 | 694 | Progress.prototype.draw = function(ctx){ 695 | try { 696 | var percent = Math.min(this.percent, 100) 697 | , size = this._size 698 | , half = size / 2 699 | , x = half 700 | , y = half 701 | , rad = half - 1 702 | , fontSize = this._fontSize; 703 | 704 | ctx.font = fontSize + 'px ' + this._font; 705 | 706 | var angle = Math.PI * 2 * (percent / 100); 707 | ctx.clearRect(0, 0, size, size); 708 | 709 | // outer circle 710 | ctx.strokeStyle = '#9f9f9f'; 711 | ctx.beginPath(); 712 | ctx.arc(x, y, rad, 0, angle, false); 713 | ctx.stroke(); 714 | 715 | // inner circle 716 | ctx.strokeStyle = '#eee'; 717 | ctx.beginPath(); 718 | ctx.arc(x, y, rad - 1, 0, angle, true); 719 | ctx.stroke(); 720 | 721 | // text 722 | var text = this._text || (percent | 0) + '%' 723 | , w = ctx.measureText(text).width; 724 | 725 | ctx.fillText( 726 | text 727 | , x - w / 2 + 1 728 | , y + fontSize / 2 - 1); 729 | } catch (ex) {} //don't fail if we can't render progress 730 | return this; 731 | }; 732 | 733 | }); // module: browser/progress.js 734 | 735 | require.register("browser/tty.js", function(module, exports, require){ 736 | 737 | exports.isatty = function(){ 738 | return true; 739 | }; 740 | 741 | exports.getWindowSize = function(){ 742 | if ('innerHeight' in global) { 743 | return [global.innerHeight, global.innerWidth]; 744 | } else { 745 | // In a Web Worker, the DOM Window is not available. 746 | return [640, 480]; 747 | } 748 | }; 749 | 750 | }); // module: browser/tty.js 751 | 752 | require.register("context.js", function(module, exports, require){ 753 | 754 | /** 755 | * Expose `Context`. 756 | */ 757 | 758 | module.exports = Context; 759 | 760 | /** 761 | * Initialize a new `Context`. 762 | * 763 | * @api private 764 | */ 765 | 766 | function Context(){} 767 | 768 | /** 769 | * Set or get the context `Runnable` to `runnable`. 770 | * 771 | * @param {Runnable} runnable 772 | * @return {Context} 773 | * @api private 774 | */ 775 | 776 | Context.prototype.runnable = function(runnable){ 777 | if (0 == arguments.length) return this._runnable; 778 | this.test = this._runnable = runnable; 779 | return this; 780 | }; 781 | 782 | /** 783 | * Set test timeout `ms`. 784 | * 785 | * @param {Number} ms 786 | * @return {Context} self 787 | * @api private 788 | */ 789 | 790 | Context.prototype.timeout = function(ms){ 791 | this.runnable().timeout(ms); 792 | return this; 793 | }; 794 | 795 | /** 796 | * Set test slowness threshold `ms`. 797 | * 798 | * @param {Number} ms 799 | * @return {Context} self 800 | * @api private 801 | */ 802 | 803 | Context.prototype.slow = function(ms){ 804 | this.runnable().slow(ms); 805 | return this; 806 | }; 807 | 808 | /** 809 | * Inspect the context void of `._runnable`. 810 | * 811 | * @return {String} 812 | * @api private 813 | */ 814 | 815 | Context.prototype.inspect = function(){ 816 | return JSON.stringify(this, function(key, val){ 817 | if ('_runnable' == key) return; 818 | if ('test' == key) return; 819 | return val; 820 | }, 2); 821 | }; 822 | 823 | }); // module: context.js 824 | 825 | require.register("hook.js", function(module, exports, require){ 826 | 827 | /** 828 | * Module dependencies. 829 | */ 830 | 831 | var Runnable = require('./runnable'); 832 | 833 | /** 834 | * Expose `Hook`. 835 | */ 836 | 837 | module.exports = Hook; 838 | 839 | /** 840 | * Initialize a new `Hook` with the given `title` and callback `fn`. 841 | * 842 | * @param {String} title 843 | * @param {Function} fn 844 | * @api private 845 | */ 846 | 847 | function Hook(title, fn) { 848 | Runnable.call(this, title, fn); 849 | this.type = 'hook'; 850 | } 851 | 852 | /** 853 | * Inherit from `Runnable.prototype`. 854 | */ 855 | 856 | function F(){}; 857 | F.prototype = Runnable.prototype; 858 | Hook.prototype = new F; 859 | Hook.prototype.constructor = Hook; 860 | 861 | 862 | /** 863 | * Get or set the test `err`. 864 | * 865 | * @param {Error} err 866 | * @return {Error} 867 | * @api public 868 | */ 869 | 870 | Hook.prototype.error = function(err){ 871 | if (0 == arguments.length) { 872 | var err = this._error; 873 | this._error = null; 874 | return err; 875 | } 876 | 877 | this._error = err; 878 | }; 879 | 880 | }); // module: hook.js 881 | 882 | require.register("interfaces/bdd.js", function(module, exports, require){ 883 | 884 | /** 885 | * Module dependencies. 886 | */ 887 | 888 | var Suite = require('../suite') 889 | , Test = require('../test') 890 | , utils = require('../utils'); 891 | 892 | /** 893 | * BDD-style interface: 894 | * 895 | * describe('Array', function(){ 896 | * describe('#indexOf()', function(){ 897 | * it('should return -1 when not present', function(){ 898 | * 899 | * }); 900 | * 901 | * it('should return the index when present', function(){ 902 | * 903 | * }); 904 | * }); 905 | * }); 906 | * 907 | */ 908 | 909 | module.exports = function(suite){ 910 | var suites = [suite]; 911 | 912 | suite.on('pre-require', function(context, file, mocha){ 913 | 914 | /** 915 | * Execute before running tests. 916 | */ 917 | 918 | context.before = function(name, fn){ 919 | suites[0].beforeAll(name, fn); 920 | }; 921 | 922 | /** 923 | * Execute after running tests. 924 | */ 925 | 926 | context.after = function(name, fn){ 927 | suites[0].afterAll(name, fn); 928 | }; 929 | 930 | /** 931 | * Execute before each test case. 932 | */ 933 | 934 | context.beforeEach = function(name, fn){ 935 | suites[0].beforeEach(name, fn); 936 | }; 937 | 938 | /** 939 | * Execute after each test case. 940 | */ 941 | 942 | context.afterEach = function(name, fn){ 943 | suites[0].afterEach(name, fn); 944 | }; 945 | 946 | /** 947 | * Describe a "suite" with the given `title` 948 | * and callback `fn` containing nested suites 949 | * and/or tests. 950 | */ 951 | 952 | context.describe = context.context = function(title, fn){ 953 | var suite = Suite.create(suites[0], title); 954 | suites.unshift(suite); 955 | fn.call(suite); 956 | suites.shift(); 957 | return suite; 958 | }; 959 | 960 | /** 961 | * Pending describe. 962 | */ 963 | 964 | context.xdescribe = 965 | context.xcontext = 966 | context.describe.skip = function(title, fn){ 967 | var suite = Suite.create(suites[0], title); 968 | suite.pending = true; 969 | suites.unshift(suite); 970 | fn.call(suite); 971 | suites.shift(); 972 | }; 973 | 974 | /** 975 | * Exclusive suite. 976 | */ 977 | 978 | context.describe.only = function(title, fn){ 979 | var suite = context.describe(title, fn); 980 | mocha.grep(suite.fullTitle()); 981 | return suite; 982 | }; 983 | 984 | /** 985 | * Describe a specification or test-case 986 | * with the given `title` and callback `fn` 987 | * acting as a thunk. 988 | */ 989 | 990 | context.it = context.specify = function(title, fn){ 991 | var suite = suites[0]; 992 | if (suite.pending) var fn = null; 993 | var test = new Test(title, fn); 994 | suite.addTest(test); 995 | return test; 996 | }; 997 | 998 | /** 999 | * Exclusive test-case. 1000 | */ 1001 | 1002 | context.it.only = function(title, fn){ 1003 | var test = context.it(title, fn); 1004 | var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; 1005 | mocha.grep(new RegExp(reString)); 1006 | return test; 1007 | }; 1008 | 1009 | /** 1010 | * Pending test case. 1011 | */ 1012 | 1013 | context.xit = 1014 | context.xspecify = 1015 | context.it.skip = function(title){ 1016 | context.it(title); 1017 | }; 1018 | }); 1019 | }; 1020 | 1021 | }); // module: interfaces/bdd.js 1022 | 1023 | require.register("interfaces/exports.js", function(module, exports, require){ 1024 | 1025 | /** 1026 | * Module dependencies. 1027 | */ 1028 | 1029 | var Suite = require('../suite') 1030 | , Test = require('../test'); 1031 | 1032 | /** 1033 | * TDD-style interface: 1034 | * 1035 | * exports.Array = { 1036 | * '#indexOf()': { 1037 | * 'should return -1 when the value is not present': function(){ 1038 | * 1039 | * }, 1040 | * 1041 | * 'should return the correct index when the value is present': function(){ 1042 | * 1043 | * } 1044 | * } 1045 | * }; 1046 | * 1047 | */ 1048 | 1049 | module.exports = function(suite){ 1050 | var suites = [suite]; 1051 | 1052 | suite.on('require', visit); 1053 | 1054 | function visit(obj) { 1055 | var suite; 1056 | for (var key in obj) { 1057 | if ('function' == typeof obj[key]) { 1058 | var fn = obj[key]; 1059 | switch (key) { 1060 | case 'before': 1061 | suites[0].beforeAll(fn); 1062 | break; 1063 | case 'after': 1064 | suites[0].afterAll(fn); 1065 | break; 1066 | case 'beforeEach': 1067 | suites[0].beforeEach(fn); 1068 | break; 1069 | case 'afterEach': 1070 | suites[0].afterEach(fn); 1071 | break; 1072 | default: 1073 | suites[0].addTest(new Test(key, fn)); 1074 | } 1075 | } else { 1076 | var suite = Suite.create(suites[0], key); 1077 | suites.unshift(suite); 1078 | visit(obj[key]); 1079 | suites.shift(); 1080 | } 1081 | } 1082 | } 1083 | }; 1084 | 1085 | }); // module: interfaces/exports.js 1086 | 1087 | require.register("interfaces/index.js", function(module, exports, require){ 1088 | 1089 | exports.bdd = require('./bdd'); 1090 | exports.tdd = require('./tdd'); 1091 | exports.qunit = require('./qunit'); 1092 | exports.exports = require('./exports'); 1093 | 1094 | }); // module: interfaces/index.js 1095 | 1096 | require.register("interfaces/qunit.js", function(module, exports, require){ 1097 | 1098 | /** 1099 | * Module dependencies. 1100 | */ 1101 | 1102 | var Suite = require('../suite') 1103 | , Test = require('../test') 1104 | , utils = require('../utils'); 1105 | 1106 | /** 1107 | * QUnit-style interface: 1108 | * 1109 | * suite('Array'); 1110 | * 1111 | * test('#length', function(){ 1112 | * var arr = [1,2,3]; 1113 | * ok(arr.length == 3); 1114 | * }); 1115 | * 1116 | * test('#indexOf()', function(){ 1117 | * var arr = [1,2,3]; 1118 | * ok(arr.indexOf(1) == 0); 1119 | * ok(arr.indexOf(2) == 1); 1120 | * ok(arr.indexOf(3) == 2); 1121 | * }); 1122 | * 1123 | * suite('String'); 1124 | * 1125 | * test('#length', function(){ 1126 | * ok('foo'.length == 3); 1127 | * }); 1128 | * 1129 | */ 1130 | 1131 | module.exports = function(suite){ 1132 | var suites = [suite]; 1133 | 1134 | suite.on('pre-require', function(context, file, mocha){ 1135 | 1136 | /** 1137 | * Execute before running tests. 1138 | */ 1139 | 1140 | context.before = function(name, fn){ 1141 | suites[0].beforeAll(name, fn); 1142 | }; 1143 | 1144 | /** 1145 | * Execute after running tests. 1146 | */ 1147 | 1148 | context.after = function(name, fn){ 1149 | suites[0].afterAll(name, fn); 1150 | }; 1151 | 1152 | /** 1153 | * Execute before each test case. 1154 | */ 1155 | 1156 | context.beforeEach = function(name, fn){ 1157 | suites[0].beforeEach(name, fn); 1158 | }; 1159 | 1160 | /** 1161 | * Execute after each test case. 1162 | */ 1163 | 1164 | context.afterEach = function(name, fn){ 1165 | suites[0].afterEach(name, fn); 1166 | }; 1167 | 1168 | /** 1169 | * Describe a "suite" with the given `title`. 1170 | */ 1171 | 1172 | context.suite = function(title){ 1173 | if (suites.length > 1) suites.shift(); 1174 | var suite = Suite.create(suites[0], title); 1175 | suites.unshift(suite); 1176 | return suite; 1177 | }; 1178 | 1179 | /** 1180 | * Exclusive test-case. 1181 | */ 1182 | 1183 | context.suite.only = function(title, fn){ 1184 | var suite = context.suite(title, fn); 1185 | mocha.grep(suite.fullTitle()); 1186 | }; 1187 | 1188 | /** 1189 | * Describe a specification or test-case 1190 | * with the given `title` and callback `fn` 1191 | * acting as a thunk. 1192 | */ 1193 | 1194 | context.test = function(title, fn){ 1195 | var test = new Test(title, fn); 1196 | suites[0].addTest(test); 1197 | return test; 1198 | }; 1199 | 1200 | /** 1201 | * Exclusive test-case. 1202 | */ 1203 | 1204 | context.test.only = function(title, fn){ 1205 | var test = context.test(title, fn); 1206 | var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; 1207 | mocha.grep(new RegExp(reString)); 1208 | }; 1209 | 1210 | /** 1211 | * Pending test case. 1212 | */ 1213 | 1214 | context.test.skip = function(title){ 1215 | context.test(title); 1216 | }; 1217 | }); 1218 | }; 1219 | 1220 | }); // module: interfaces/qunit.js 1221 | 1222 | require.register("interfaces/tdd.js", function(module, exports, require){ 1223 | 1224 | /** 1225 | * Module dependencies. 1226 | */ 1227 | 1228 | var Suite = require('../suite') 1229 | , Test = require('../test') 1230 | , utils = require('../utils');; 1231 | 1232 | /** 1233 | * TDD-style interface: 1234 | * 1235 | * suite('Array', function(){ 1236 | * suite('#indexOf()', function(){ 1237 | * suiteSetup(function(){ 1238 | * 1239 | * }); 1240 | * 1241 | * test('should return -1 when not present', function(){ 1242 | * 1243 | * }); 1244 | * 1245 | * test('should return the index when present', function(){ 1246 | * 1247 | * }); 1248 | * 1249 | * suiteTeardown(function(){ 1250 | * 1251 | * }); 1252 | * }); 1253 | * }); 1254 | * 1255 | */ 1256 | 1257 | module.exports = function(suite){ 1258 | var suites = [suite]; 1259 | 1260 | suite.on('pre-require', function(context, file, mocha){ 1261 | 1262 | /** 1263 | * Execute before each test case. 1264 | */ 1265 | 1266 | context.setup = function(name, fn){ 1267 | suites[0].beforeEach(name, fn); 1268 | }; 1269 | 1270 | /** 1271 | * Execute after each test case. 1272 | */ 1273 | 1274 | context.teardown = function(name, fn){ 1275 | suites[0].afterEach(name, fn); 1276 | }; 1277 | 1278 | /** 1279 | * Execute before the suite. 1280 | */ 1281 | 1282 | context.suiteSetup = function(name, fn){ 1283 | suites[0].beforeAll(name, fn); 1284 | }; 1285 | 1286 | /** 1287 | * Execute after the suite. 1288 | */ 1289 | 1290 | context.suiteTeardown = function(name, fn){ 1291 | suites[0].afterAll(name, fn); 1292 | }; 1293 | 1294 | /** 1295 | * Describe a "suite" with the given `title` 1296 | * and callback `fn` containing nested suites 1297 | * and/or tests. 1298 | */ 1299 | 1300 | context.suite = function(title, fn){ 1301 | var suite = Suite.create(suites[0], title); 1302 | suites.unshift(suite); 1303 | fn.call(suite); 1304 | suites.shift(); 1305 | return suite; 1306 | }; 1307 | 1308 | /** 1309 | * Pending suite. 1310 | */ 1311 | context.suite.skip = function(title, fn) { 1312 | var suite = Suite.create(suites[0], title); 1313 | suite.pending = true; 1314 | suites.unshift(suite); 1315 | fn.call(suite); 1316 | suites.shift(); 1317 | }; 1318 | 1319 | /** 1320 | * Exclusive test-case. 1321 | */ 1322 | 1323 | context.suite.only = function(title, fn){ 1324 | var suite = context.suite(title, fn); 1325 | mocha.grep(suite.fullTitle()); 1326 | }; 1327 | 1328 | /** 1329 | * Describe a specification or test-case 1330 | * with the given `title` and callback `fn` 1331 | * acting as a thunk. 1332 | */ 1333 | 1334 | context.test = function(title, fn){ 1335 | var suite = suites[0]; 1336 | if (suite.pending) var fn = null; 1337 | var test = new Test(title, fn); 1338 | suite.addTest(test); 1339 | return test; 1340 | }; 1341 | 1342 | /** 1343 | * Exclusive test-case. 1344 | */ 1345 | 1346 | context.test.only = function(title, fn){ 1347 | var test = context.test(title, fn); 1348 | var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; 1349 | mocha.grep(new RegExp(reString)); 1350 | }; 1351 | 1352 | /** 1353 | * Pending test case. 1354 | */ 1355 | 1356 | context.test.skip = function(title){ 1357 | context.test(title); 1358 | }; 1359 | }); 1360 | }; 1361 | 1362 | }); // module: interfaces/tdd.js 1363 | 1364 | require.register("mocha.js", function(module, exports, require){ 1365 | /*! 1366 | * mocha 1367 | * Copyright(c) 2011 TJ Holowaychuk 1368 | * MIT Licensed 1369 | */ 1370 | 1371 | /** 1372 | * Module dependencies. 1373 | */ 1374 | 1375 | var path = require('browser/path') 1376 | , utils = require('./utils'); 1377 | 1378 | /** 1379 | * Expose `Mocha`. 1380 | */ 1381 | 1382 | exports = module.exports = Mocha; 1383 | 1384 | /** 1385 | * Expose internals. 1386 | */ 1387 | 1388 | exports.utils = utils; 1389 | exports.interfaces = require('./interfaces'); 1390 | exports.reporters = require('./reporters'); 1391 | exports.Runnable = require('./runnable'); 1392 | exports.Context = require('./context'); 1393 | exports.Runner = require('./runner'); 1394 | exports.Suite = require('./suite'); 1395 | exports.Hook = require('./hook'); 1396 | exports.Test = require('./test'); 1397 | 1398 | /** 1399 | * Return image `name` path. 1400 | * 1401 | * @param {String} name 1402 | * @return {String} 1403 | * @api private 1404 | */ 1405 | 1406 | function image(name) { 1407 | return __dirname + '/../images/' + name + '.png'; 1408 | } 1409 | 1410 | /** 1411 | * Setup mocha with `options`. 1412 | * 1413 | * Options: 1414 | * 1415 | * - `ui` name "bdd", "tdd", "exports" etc 1416 | * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` 1417 | * - `globals` array of accepted globals 1418 | * - `timeout` timeout in milliseconds 1419 | * - `bail` bail on the first test failure 1420 | * - `slow` milliseconds to wait before considering a test slow 1421 | * - `ignoreLeaks` ignore global leaks 1422 | * - `grep` string or regexp to filter tests with 1423 | * 1424 | * @param {Object} options 1425 | * @api public 1426 | */ 1427 | 1428 | function Mocha(options) { 1429 | options = options || {}; 1430 | this.files = []; 1431 | this.options = options; 1432 | this.grep(options.grep); 1433 | this.suite = new exports.Suite('', new exports.Context); 1434 | this.ui(options.ui); 1435 | this.bail(options.bail); 1436 | this.reporter(options.reporter); 1437 | if (null != options.timeout) this.timeout(options.timeout); 1438 | this.useColors(options.useColors) 1439 | if (options.slow) this.slow(options.slow); 1440 | 1441 | this.suite.on('pre-require', function (context) { 1442 | exports.afterEach = context.afterEach || context.teardown; 1443 | exports.after = context.after || context.suiteTeardown; 1444 | exports.beforeEach = context.beforeEach || context.setup; 1445 | exports.before = context.before || context.suiteSetup; 1446 | exports.describe = context.describe || context.suite; 1447 | exports.it = context.it || context.test; 1448 | exports.setup = context.setup || context.beforeEach; 1449 | exports.suiteSetup = context.suiteSetup || context.before; 1450 | exports.suiteTeardown = context.suiteTeardown || context.after; 1451 | exports.suite = context.suite || context.describe; 1452 | exports.teardown = context.teardown || context.afterEach; 1453 | exports.test = context.test || context.it; 1454 | }); 1455 | } 1456 | 1457 | /** 1458 | * Enable or disable bailing on the first failure. 1459 | * 1460 | * @param {Boolean} [bail] 1461 | * @api public 1462 | */ 1463 | 1464 | Mocha.prototype.bail = function(bail){ 1465 | if (0 == arguments.length) bail = true; 1466 | this.suite.bail(bail); 1467 | return this; 1468 | }; 1469 | 1470 | /** 1471 | * Add test `file`. 1472 | * 1473 | * @param {String} file 1474 | * @api public 1475 | */ 1476 | 1477 | Mocha.prototype.addFile = function(file){ 1478 | this.files.push(file); 1479 | return this; 1480 | }; 1481 | 1482 | /** 1483 | * Set reporter to `reporter`, defaults to "dot". 1484 | * 1485 | * @param {String|Function} reporter name or constructor 1486 | * @api public 1487 | */ 1488 | 1489 | Mocha.prototype.reporter = function(reporter){ 1490 | if ('function' == typeof reporter) { 1491 | this._reporter = reporter; 1492 | } else { 1493 | reporter = reporter || 'dot'; 1494 | var _reporter; 1495 | try { _reporter = require('./reporters/' + reporter); } catch (err) {}; 1496 | if (!_reporter) try { _reporter = require(reporter); } catch (err) {}; 1497 | if (!_reporter && reporter === 'teamcity') 1498 | console.warn('The Teamcity reporter was moved to a package named ' + 1499 | 'mocha-teamcity-reporter ' + 1500 | '(https://npmjs.org/package/mocha-teamcity-reporter).'); 1501 | if (!_reporter) throw new Error('invalid reporter "' + reporter + '"'); 1502 | this._reporter = _reporter; 1503 | } 1504 | return this; 1505 | }; 1506 | 1507 | /** 1508 | * Set test UI `name`, defaults to "bdd". 1509 | * 1510 | * @param {String} bdd 1511 | * @api public 1512 | */ 1513 | 1514 | Mocha.prototype.ui = function(name){ 1515 | name = name || 'bdd'; 1516 | this._ui = exports.interfaces[name]; 1517 | if (!this._ui) try { this._ui = require(name); } catch (err) {}; 1518 | if (!this._ui) throw new Error('invalid interface "' + name + '"'); 1519 | this._ui = this._ui(this.suite); 1520 | return this; 1521 | }; 1522 | 1523 | /** 1524 | * Load registered files. 1525 | * 1526 | * @api private 1527 | */ 1528 | 1529 | Mocha.prototype.loadFiles = function(fn){ 1530 | var self = this; 1531 | var suite = this.suite; 1532 | var pending = this.files.length; 1533 | this.files.forEach(function(file){ 1534 | file = path.resolve(file); 1535 | suite.emit('pre-require', global, file, self); 1536 | suite.emit('require', require(file), file, self); 1537 | suite.emit('post-require', global, file, self); 1538 | --pending || (fn && fn()); 1539 | }); 1540 | }; 1541 | 1542 | /** 1543 | * Enable growl support. 1544 | * 1545 | * @api private 1546 | */ 1547 | 1548 | Mocha.prototype._growl = function(runner, reporter) { 1549 | var notify = require('growl'); 1550 | 1551 | runner.on('end', function(){ 1552 | var stats = reporter.stats; 1553 | if (stats.failures) { 1554 | var msg = stats.failures + ' of ' + runner.total + ' tests failed'; 1555 | notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); 1556 | } else { 1557 | notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { 1558 | name: 'mocha' 1559 | , title: 'Passed' 1560 | , image: image('ok') 1561 | }); 1562 | } 1563 | }); 1564 | }; 1565 | 1566 | /** 1567 | * Add regexp to grep, if `re` is a string it is escaped. 1568 | * 1569 | * @param {RegExp|String} re 1570 | * @return {Mocha} 1571 | * @api public 1572 | */ 1573 | 1574 | Mocha.prototype.grep = function(re){ 1575 | this.options.grep = 'string' == typeof re 1576 | ? new RegExp(utils.escapeRegexp(re)) 1577 | : re; 1578 | return this; 1579 | }; 1580 | 1581 | /** 1582 | * Invert `.grep()` matches. 1583 | * 1584 | * @return {Mocha} 1585 | * @api public 1586 | */ 1587 | 1588 | Mocha.prototype.invert = function(){ 1589 | this.options.invert = true; 1590 | return this; 1591 | }; 1592 | 1593 | /** 1594 | * Ignore global leaks. 1595 | * 1596 | * @param {Boolean} ignore 1597 | * @return {Mocha} 1598 | * @api public 1599 | */ 1600 | 1601 | Mocha.prototype.ignoreLeaks = function(ignore){ 1602 | this.options.ignoreLeaks = !!ignore; 1603 | return this; 1604 | }; 1605 | 1606 | /** 1607 | * Enable global leak checking. 1608 | * 1609 | * @return {Mocha} 1610 | * @api public 1611 | */ 1612 | 1613 | Mocha.prototype.checkLeaks = function(){ 1614 | this.options.ignoreLeaks = false; 1615 | return this; 1616 | }; 1617 | 1618 | /** 1619 | * Enable growl support. 1620 | * 1621 | * @return {Mocha} 1622 | * @api public 1623 | */ 1624 | 1625 | Mocha.prototype.growl = function(){ 1626 | this.options.growl = true; 1627 | return this; 1628 | }; 1629 | 1630 | /** 1631 | * Ignore `globals` array or string. 1632 | * 1633 | * @param {Array|String} globals 1634 | * @return {Mocha} 1635 | * @api public 1636 | */ 1637 | 1638 | Mocha.prototype.globals = function(globals){ 1639 | this.options.globals = (this.options.globals || []).concat(globals); 1640 | return this; 1641 | }; 1642 | 1643 | /** 1644 | * Emit color output. 1645 | * 1646 | * @param {Boolean} colors 1647 | * @return {Mocha} 1648 | * @api public 1649 | */ 1650 | 1651 | Mocha.prototype.useColors = function(colors){ 1652 | this.options.useColors = arguments.length && colors != undefined 1653 | ? colors 1654 | : true; 1655 | return this; 1656 | }; 1657 | 1658 | /** 1659 | * Use inline diffs rather than +/-. 1660 | * 1661 | * @param {Boolean} inlineDiffs 1662 | * @return {Mocha} 1663 | * @api public 1664 | */ 1665 | 1666 | Mocha.prototype.useInlineDiffs = function(inlineDiffs) { 1667 | this.options.useInlineDiffs = arguments.length && inlineDiffs != undefined 1668 | ? inlineDiffs 1669 | : false; 1670 | return this; 1671 | }; 1672 | 1673 | /** 1674 | * Set the timeout in milliseconds. 1675 | * 1676 | * @param {Number} timeout 1677 | * @return {Mocha} 1678 | * @api public 1679 | */ 1680 | 1681 | Mocha.prototype.timeout = function(timeout){ 1682 | this.suite.timeout(timeout); 1683 | return this; 1684 | }; 1685 | 1686 | /** 1687 | * Set slowness threshold in milliseconds. 1688 | * 1689 | * @param {Number} slow 1690 | * @return {Mocha} 1691 | * @api public 1692 | */ 1693 | 1694 | Mocha.prototype.slow = function(slow){ 1695 | this.suite.slow(slow); 1696 | return this; 1697 | }; 1698 | 1699 | /** 1700 | * Makes all tests async (accepting a callback) 1701 | * 1702 | * @return {Mocha} 1703 | * @api public 1704 | */ 1705 | 1706 | Mocha.prototype.asyncOnly = function(){ 1707 | this.options.asyncOnly = true; 1708 | return this; 1709 | }; 1710 | 1711 | /** 1712 | * Run tests and invoke `fn()` when complete. 1713 | * 1714 | * @param {Function} fn 1715 | * @return {Runner} 1716 | * @api public 1717 | */ 1718 | 1719 | Mocha.prototype.run = function(fn){ 1720 | if (this.files.length) this.loadFiles(); 1721 | var suite = this.suite; 1722 | var options = this.options; 1723 | options.files = this.files; 1724 | var runner = new exports.Runner(suite); 1725 | var reporter = new this._reporter(runner, options); 1726 | runner.ignoreLeaks = false !== options.ignoreLeaks; 1727 | runner.asyncOnly = options.asyncOnly; 1728 | if (options.grep) runner.grep(options.grep, options.invert); 1729 | if (options.globals) runner.globals(options.globals); 1730 | if (options.growl) this._growl(runner, reporter); 1731 | exports.reporters.Base.useColors = options.useColors; 1732 | exports.reporters.Base.inlineDiffs = options.useInlineDiffs; 1733 | return runner.run(fn); 1734 | }; 1735 | 1736 | }); // module: mocha.js 1737 | 1738 | require.register("ms.js", function(module, exports, require){ 1739 | /** 1740 | * Helpers. 1741 | */ 1742 | 1743 | var s = 1000; 1744 | var m = s * 60; 1745 | var h = m * 60; 1746 | var d = h * 24; 1747 | var y = d * 365.25; 1748 | 1749 | /** 1750 | * Parse or format the given `val`. 1751 | * 1752 | * Options: 1753 | * 1754 | * - `long` verbose formatting [false] 1755 | * 1756 | * @param {String|Number} val 1757 | * @param {Object} options 1758 | * @return {String|Number} 1759 | * @api public 1760 | */ 1761 | 1762 | module.exports = function(val, options){ 1763 | options = options || {}; 1764 | if ('string' == typeof val) return parse(val); 1765 | return options.long ? longFormat(val) : shortFormat(val); 1766 | }; 1767 | 1768 | /** 1769 | * Parse the given `str` and return milliseconds. 1770 | * 1771 | * @param {String} str 1772 | * @return {Number} 1773 | * @api private 1774 | */ 1775 | 1776 | function parse(str) { 1777 | var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); 1778 | if (!match) return; 1779 | var n = parseFloat(match[1]); 1780 | var type = (match[2] || 'ms').toLowerCase(); 1781 | switch (type) { 1782 | case 'years': 1783 | case 'year': 1784 | case 'y': 1785 | return n * y; 1786 | case 'days': 1787 | case 'day': 1788 | case 'd': 1789 | return n * d; 1790 | case 'hours': 1791 | case 'hour': 1792 | case 'h': 1793 | return n * h; 1794 | case 'minutes': 1795 | case 'minute': 1796 | case 'm': 1797 | return n * m; 1798 | case 'seconds': 1799 | case 'second': 1800 | case 's': 1801 | return n * s; 1802 | case 'ms': 1803 | return n; 1804 | } 1805 | } 1806 | 1807 | /** 1808 | * Short format for `ms`. 1809 | * 1810 | * @param {Number} ms 1811 | * @return {String} 1812 | * @api private 1813 | */ 1814 | 1815 | function shortFormat(ms) { 1816 | if (ms >= d) return Math.round(ms / d) + 'd'; 1817 | if (ms >= h) return Math.round(ms / h) + 'h'; 1818 | if (ms >= m) return Math.round(ms / m) + 'm'; 1819 | if (ms >= s) return Math.round(ms / s) + 's'; 1820 | return ms + 'ms'; 1821 | } 1822 | 1823 | /** 1824 | * Long format for `ms`. 1825 | * 1826 | * @param {Number} ms 1827 | * @return {String} 1828 | * @api private 1829 | */ 1830 | 1831 | function longFormat(ms) { 1832 | return plural(ms, d, 'day') 1833 | || plural(ms, h, 'hour') 1834 | || plural(ms, m, 'minute') 1835 | || plural(ms, s, 'second') 1836 | || ms + ' ms'; 1837 | } 1838 | 1839 | /** 1840 | * Pluralization helper. 1841 | */ 1842 | 1843 | function plural(ms, n, name) { 1844 | if (ms < n) return; 1845 | if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name; 1846 | return Math.ceil(ms / n) + ' ' + name + 's'; 1847 | } 1848 | 1849 | }); // module: ms.js 1850 | 1851 | require.register("reporters/base.js", function(module, exports, require){ 1852 | 1853 | /** 1854 | * Module dependencies. 1855 | */ 1856 | 1857 | var tty = require('browser/tty') 1858 | , diff = require('browser/diff') 1859 | , ms = require('../ms') 1860 | , utils = require('../utils'); 1861 | 1862 | /** 1863 | * Save timer references to avoid Sinon interfering (see GH-237). 1864 | */ 1865 | 1866 | var Date = global.Date 1867 | , setTimeout = global.setTimeout 1868 | , setInterval = global.setInterval 1869 | , clearTimeout = global.clearTimeout 1870 | , clearInterval = global.clearInterval; 1871 | 1872 | /** 1873 | * Check if both stdio streams are associated with a tty. 1874 | */ 1875 | 1876 | var isatty = tty.isatty(1) && tty.isatty(2); 1877 | 1878 | /** 1879 | * Expose `Base`. 1880 | */ 1881 | 1882 | exports = module.exports = Base; 1883 | 1884 | /** 1885 | * Enable coloring by default. 1886 | */ 1887 | 1888 | exports.useColors = isatty || (process.env.MOCHA_COLORS !== undefined); 1889 | 1890 | /** 1891 | * Inline diffs instead of +/- 1892 | */ 1893 | 1894 | exports.inlineDiffs = false; 1895 | 1896 | /** 1897 | * Default color map. 1898 | */ 1899 | 1900 | exports.colors = { 1901 | 'pass': 90 1902 | , 'fail': 31 1903 | , 'bright pass': 92 1904 | , 'bright fail': 91 1905 | , 'bright yellow': 93 1906 | , 'pending': 36 1907 | , 'suite': 0 1908 | , 'error title': 0 1909 | , 'error message': 31 1910 | , 'error stack': 90 1911 | , 'checkmark': 32 1912 | , 'fast': 90 1913 | , 'medium': 33 1914 | , 'slow': 31 1915 | , 'green': 32 1916 | , 'light': 90 1917 | , 'diff gutter': 90 1918 | , 'diff added': 42 1919 | , 'diff removed': 41 1920 | }; 1921 | 1922 | /** 1923 | * Default symbol map. 1924 | */ 1925 | 1926 | exports.symbols = { 1927 | ok: '✓', 1928 | err: '✖', 1929 | dot: '․' 1930 | }; 1931 | 1932 | // With node.js on Windows: use symbols available in terminal default fonts 1933 | if ('win32' == process.platform) { 1934 | exports.symbols.ok = '\u221A'; 1935 | exports.symbols.err = '\u00D7'; 1936 | exports.symbols.dot = '.'; 1937 | } 1938 | 1939 | /** 1940 | * Color `str` with the given `type`, 1941 | * allowing colors to be disabled, 1942 | * as well as user-defined color 1943 | * schemes. 1944 | * 1945 | * @param {String} type 1946 | * @param {String} str 1947 | * @return {String} 1948 | * @api private 1949 | */ 1950 | 1951 | var color = exports.color = function(type, str) { 1952 | if (!exports.useColors) return str; 1953 | return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; 1954 | }; 1955 | 1956 | /** 1957 | * Expose term window size, with some 1958 | * defaults for when stderr is not a tty. 1959 | */ 1960 | 1961 | exports.window = { 1962 | width: isatty 1963 | ? process.stdout.getWindowSize 1964 | ? process.stdout.getWindowSize(1)[0] 1965 | : tty.getWindowSize()[1] 1966 | : 75 1967 | }; 1968 | 1969 | /** 1970 | * Expose some basic cursor interactions 1971 | * that are common among reporters. 1972 | */ 1973 | 1974 | exports.cursor = { 1975 | hide: function(){ 1976 | isatty && process.stdout.write('\u001b[?25l'); 1977 | }, 1978 | 1979 | show: function(){ 1980 | isatty && process.stdout.write('\u001b[?25h'); 1981 | }, 1982 | 1983 | deleteLine: function(){ 1984 | isatty && process.stdout.write('\u001b[2K'); 1985 | }, 1986 | 1987 | beginningOfLine: function(){ 1988 | isatty && process.stdout.write('\u001b[0G'); 1989 | }, 1990 | 1991 | CR: function(){ 1992 | if (isatty) { 1993 | exports.cursor.deleteLine(); 1994 | exports.cursor.beginningOfLine(); 1995 | } else { 1996 | process.stdout.write('\r'); 1997 | } 1998 | } 1999 | }; 2000 | 2001 | /** 2002 | * Outut the given `failures` as a list. 2003 | * 2004 | * @param {Array} failures 2005 | * @api public 2006 | */ 2007 | 2008 | exports.list = function(failures){ 2009 | console.error(); 2010 | failures.forEach(function(test, i){ 2011 | // format 2012 | var fmt = color('error title', ' %s) %s:\n') 2013 | + color('error message', ' %s') 2014 | + color('error stack', '\n%s\n'); 2015 | 2016 | // msg 2017 | var err = test.err 2018 | , message = err.message || '' 2019 | , stack = err.stack || message 2020 | , index = stack.indexOf(message) + message.length 2021 | , msg = stack.slice(0, index) 2022 | , actual = err.actual 2023 | , expected = err.expected 2024 | , escape = true; 2025 | 2026 | // uncaught 2027 | if (err.uncaught) { 2028 | msg = 'Uncaught ' + msg; 2029 | } 2030 | 2031 | // explicitly show diff 2032 | if (err.showDiff && sameType(actual, expected)) { 2033 | escape = false; 2034 | err.actual = actual = stringify(canonicalize(actual)); 2035 | err.expected = expected = stringify(canonicalize(expected)); 2036 | } 2037 | 2038 | // actual / expected diff 2039 | if ('string' == typeof actual && 'string' == typeof expected) { 2040 | fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n'); 2041 | var match = message.match(/^([^:]+): expected/); 2042 | msg = '\n ' + color('error message', match ? match[1] : msg); 2043 | 2044 | if (exports.inlineDiffs) { 2045 | msg += inlineDiff(err, escape); 2046 | } else { 2047 | msg += unifiedDiff(err, escape); 2048 | } 2049 | } 2050 | 2051 | // indent stack trace without msg 2052 | stack = stack.slice(index ? index + 1 : index) 2053 | .replace(/^/gm, ' '); 2054 | 2055 | console.error(fmt, (i + 1), test.fullTitle(), msg, stack); 2056 | }); 2057 | }; 2058 | 2059 | /** 2060 | * Initialize a new `Base` reporter. 2061 | * 2062 | * All other reporters generally 2063 | * inherit from this reporter, providing 2064 | * stats such as test duration, number 2065 | * of tests passed / failed etc. 2066 | * 2067 | * @param {Runner} runner 2068 | * @api public 2069 | */ 2070 | 2071 | function Base(runner) { 2072 | var self = this 2073 | , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } 2074 | , failures = this.failures = []; 2075 | 2076 | if (!runner) return; 2077 | this.runner = runner; 2078 | 2079 | runner.stats = stats; 2080 | 2081 | runner.on('start', function(){ 2082 | stats.start = new Date; 2083 | }); 2084 | 2085 | runner.on('suite', function(suite){ 2086 | stats.suites = stats.suites || 0; 2087 | suite.root || stats.suites++; 2088 | }); 2089 | 2090 | runner.on('test end', function(test){ 2091 | stats.tests = stats.tests || 0; 2092 | stats.tests++; 2093 | }); 2094 | 2095 | runner.on('pass', function(test){ 2096 | stats.passes = stats.passes || 0; 2097 | 2098 | var medium = test.slow() / 2; 2099 | test.speed = test.duration > test.slow() 2100 | ? 'slow' 2101 | : test.duration > medium 2102 | ? 'medium' 2103 | : 'fast'; 2104 | 2105 | stats.passes++; 2106 | }); 2107 | 2108 | runner.on('fail', function(test, err){ 2109 | stats.failures = stats.failures || 0; 2110 | stats.failures++; 2111 | test.err = err; 2112 | failures.push(test); 2113 | }); 2114 | 2115 | runner.on('end', function(){ 2116 | stats.end = new Date; 2117 | stats.duration = new Date - stats.start; 2118 | }); 2119 | 2120 | runner.on('pending', function(){ 2121 | stats.pending++; 2122 | }); 2123 | } 2124 | 2125 | /** 2126 | * Output common epilogue used by many of 2127 | * the bundled reporters. 2128 | * 2129 | * @api public 2130 | */ 2131 | 2132 | Base.prototype.epilogue = function(){ 2133 | var stats = this.stats; 2134 | var tests; 2135 | var fmt; 2136 | 2137 | console.log(); 2138 | 2139 | // passes 2140 | fmt = color('bright pass', ' ') 2141 | + color('green', ' %d passing') 2142 | + color('light', ' (%s)'); 2143 | 2144 | console.log(fmt, 2145 | stats.passes || 0, 2146 | ms(stats.duration)); 2147 | 2148 | // pending 2149 | if (stats.pending) { 2150 | fmt = color('pending', ' ') 2151 | + color('pending', ' %d pending'); 2152 | 2153 | console.log(fmt, stats.pending); 2154 | } 2155 | 2156 | // failures 2157 | if (stats.failures) { 2158 | fmt = color('fail', ' %d failing'); 2159 | 2160 | console.error(fmt, 2161 | stats.failures); 2162 | 2163 | Base.list(this.failures); 2164 | console.error(); 2165 | } 2166 | 2167 | console.log(); 2168 | }; 2169 | 2170 | /** 2171 | * Pad the given `str` to `len`. 2172 | * 2173 | * @param {String} str 2174 | * @param {String} len 2175 | * @return {String} 2176 | * @api private 2177 | */ 2178 | 2179 | function pad(str, len) { 2180 | str = String(str); 2181 | return Array(len - str.length + 1).join(' ') + str; 2182 | } 2183 | 2184 | 2185 | /** 2186 | * Returns an inline diff between 2 strings with coloured ANSI output 2187 | * 2188 | * @param {Error} Error with actual/expected 2189 | * @return {String} Diff 2190 | * @api private 2191 | */ 2192 | 2193 | function inlineDiff(err, escape) { 2194 | var msg = errorDiff(err, 'WordsWithSpace', escape); 2195 | 2196 | // linenos 2197 | var lines = msg.split('\n'); 2198 | if (lines.length > 4) { 2199 | var width = String(lines.length).length; 2200 | msg = lines.map(function(str, i){ 2201 | return pad(++i, width) + ' |' + ' ' + str; 2202 | }).join('\n'); 2203 | } 2204 | 2205 | // legend 2206 | msg = '\n' 2207 | + color('diff removed', 'actual') 2208 | + ' ' 2209 | + color('diff added', 'expected') 2210 | + '\n\n' 2211 | + msg 2212 | + '\n'; 2213 | 2214 | // indent 2215 | msg = msg.replace(/^/gm, ' '); 2216 | return msg; 2217 | } 2218 | 2219 | /** 2220 | * Returns a unified diff between 2 strings 2221 | * 2222 | * @param {Error} Error with actual/expected 2223 | * @return {String} Diff 2224 | * @api private 2225 | */ 2226 | 2227 | function unifiedDiff(err, escape) { 2228 | var indent = ' '; 2229 | function cleanUp(line) { 2230 | if (escape) { 2231 | line = escapeInvisibles(line); 2232 | } 2233 | if (line[0] === '+') return indent + colorLines('diff added', line); 2234 | if (line[0] === '-') return indent + colorLines('diff removed', line); 2235 | if (line.match(/\@\@/)) return null; 2236 | if (line.match(/\\ No newline/)) return null; 2237 | else return indent + line; 2238 | } 2239 | function notBlank(line) { 2240 | return line != null; 2241 | } 2242 | msg = diff.createPatch('string', err.actual, err.expected); 2243 | var lines = msg.split('\n').splice(4); 2244 | return '\n ' 2245 | + colorLines('diff added', '+ expected') + ' ' 2246 | + colorLines('diff removed', '- actual') 2247 | + '\n\n' 2248 | + lines.map(cleanUp).filter(notBlank).join('\n'); 2249 | } 2250 | 2251 | /** 2252 | * Return a character diff for `err`. 2253 | * 2254 | * @param {Error} err 2255 | * @return {String} 2256 | * @api private 2257 | */ 2258 | 2259 | function errorDiff(err, type, escape) { 2260 | var actual = escape ? escapeInvisibles(err.actual) : err.actual; 2261 | var expected = escape ? escapeInvisibles(err.expected) : err.expected; 2262 | return diff['diff' + type](actual, expected).map(function(str){ 2263 | if (str.added) return colorLines('diff added', str.value); 2264 | if (str.removed) return colorLines('diff removed', str.value); 2265 | return str.value; 2266 | }).join(''); 2267 | } 2268 | 2269 | /** 2270 | * Returns a string with all invisible characters in plain text 2271 | * 2272 | * @param {String} line 2273 | * @return {String} 2274 | * @api private 2275 | */ 2276 | function escapeInvisibles(line) { 2277 | return line.replace(/\t/g, '') 2278 | .replace(/\r/g, '') 2279 | .replace(/\n/g, '\n'); 2280 | } 2281 | 2282 | /** 2283 | * Color lines for `str`, using the color `name`. 2284 | * 2285 | * @param {String} name 2286 | * @param {String} str 2287 | * @return {String} 2288 | * @api private 2289 | */ 2290 | 2291 | function colorLines(name, str) { 2292 | return str.split('\n').map(function(str){ 2293 | return color(name, str); 2294 | }).join('\n'); 2295 | } 2296 | 2297 | /** 2298 | * Stringify `obj`. 2299 | * 2300 | * @param {Object} obj 2301 | * @return {String} 2302 | * @api private 2303 | */ 2304 | 2305 | function stringify(obj) { 2306 | if (obj instanceof RegExp) return obj.toString(); 2307 | return JSON.stringify(obj, null, 2); 2308 | } 2309 | 2310 | /** 2311 | * Return a new object that has the keys in sorted order. 2312 | * @param {Object} obj 2313 | * @return {Object} 2314 | * @api private 2315 | */ 2316 | 2317 | function canonicalize(obj, stack) { 2318 | stack = stack || []; 2319 | 2320 | if (utils.indexOf(stack, obj) !== -1) return obj; 2321 | 2322 | var canonicalizedObj; 2323 | 2324 | if ('[object Array]' == {}.toString.call(obj)) { 2325 | stack.push(obj); 2326 | canonicalizedObj = utils.map(obj, function(item) { 2327 | return canonicalize(item, stack); 2328 | }); 2329 | stack.pop(); 2330 | } else if (typeof obj === 'object' && obj !== null) { 2331 | stack.push(obj); 2332 | canonicalizedObj = {}; 2333 | utils.forEach(utils.keys(obj).sort(), function(key) { 2334 | canonicalizedObj[key] = canonicalize(obj[key], stack); 2335 | }); 2336 | stack.pop(); 2337 | } else { 2338 | canonicalizedObj = obj; 2339 | } 2340 | 2341 | return canonicalizedObj; 2342 | } 2343 | 2344 | /** 2345 | * Check that a / b have the same type. 2346 | * 2347 | * @param {Object} a 2348 | * @param {Object} b 2349 | * @return {Boolean} 2350 | * @api private 2351 | */ 2352 | 2353 | function sameType(a, b) { 2354 | a = Object.prototype.toString.call(a); 2355 | b = Object.prototype.toString.call(b); 2356 | return a == b; 2357 | } 2358 | 2359 | 2360 | }); // module: reporters/base.js 2361 | 2362 | require.register("reporters/doc.js", function(module, exports, require){ 2363 | 2364 | /** 2365 | * Module dependencies. 2366 | */ 2367 | 2368 | var Base = require('./base') 2369 | , utils = require('../utils'); 2370 | 2371 | /** 2372 | * Expose `Doc`. 2373 | */ 2374 | 2375 | exports = module.exports = Doc; 2376 | 2377 | /** 2378 | * Initialize a new `Doc` reporter. 2379 | * 2380 | * @param {Runner} runner 2381 | * @api public 2382 | */ 2383 | 2384 | function Doc(runner) { 2385 | Base.call(this, runner); 2386 | 2387 | var self = this 2388 | , stats = this.stats 2389 | , total = runner.total 2390 | , indents = 2; 2391 | 2392 | function indent() { 2393 | return Array(indents).join(' '); 2394 | } 2395 | 2396 | runner.on('suite', function(suite){ 2397 | if (suite.root) return; 2398 | ++indents; 2399 | console.log('%s
', indent()); 2400 | ++indents; 2401 | console.log('%s

%s

', indent(), utils.escape(suite.title)); 2402 | console.log('%s
', indent()); 2403 | }); 2404 | 2405 | runner.on('suite end', function(suite){ 2406 | if (suite.root) return; 2407 | console.log('%s
', indent()); 2408 | --indents; 2409 | console.log('%s
', indent()); 2410 | --indents; 2411 | }); 2412 | 2413 | runner.on('pass', function(test){ 2414 | console.log('%s
%s
', indent(), utils.escape(test.title)); 2415 | var code = utils.escape(utils.clean(test.fn.toString())); 2416 | console.log('%s
%s
', indent(), code); 2417 | }); 2418 | } 2419 | 2420 | }); // module: reporters/doc.js 2421 | 2422 | require.register("reporters/dot.js", function(module, exports, require){ 2423 | 2424 | /** 2425 | * Module dependencies. 2426 | */ 2427 | 2428 | var Base = require('./base') 2429 | , color = Base.color; 2430 | 2431 | /** 2432 | * Expose `Dot`. 2433 | */ 2434 | 2435 | exports = module.exports = Dot; 2436 | 2437 | /** 2438 | * Initialize a new `Dot` matrix test reporter. 2439 | * 2440 | * @param {Runner} runner 2441 | * @api public 2442 | */ 2443 | 2444 | function Dot(runner) { 2445 | Base.call(this, runner); 2446 | 2447 | var self = this 2448 | , stats = this.stats 2449 | , width = Base.window.width * .75 | 0 2450 | , n = 0; 2451 | 2452 | runner.on('start', function(){ 2453 | process.stdout.write('\n '); 2454 | }); 2455 | 2456 | runner.on('pending', function(test){ 2457 | process.stdout.write(color('pending', Base.symbols.dot)); 2458 | }); 2459 | 2460 | runner.on('pass', function(test){ 2461 | if (++n % width == 0) process.stdout.write('\n '); 2462 | if ('slow' == test.speed) { 2463 | process.stdout.write(color('bright yellow', Base.symbols.dot)); 2464 | } else { 2465 | process.stdout.write(color(test.speed, Base.symbols.dot)); 2466 | } 2467 | }); 2468 | 2469 | runner.on('fail', function(test, err){ 2470 | if (++n % width == 0) process.stdout.write('\n '); 2471 | process.stdout.write(color('fail', Base.symbols.dot)); 2472 | }); 2473 | 2474 | runner.on('end', function(){ 2475 | console.log(); 2476 | self.epilogue(); 2477 | }); 2478 | } 2479 | 2480 | /** 2481 | * Inherit from `Base.prototype`. 2482 | */ 2483 | 2484 | function F(){}; 2485 | F.prototype = Base.prototype; 2486 | Dot.prototype = new F; 2487 | Dot.prototype.constructor = Dot; 2488 | 2489 | }); // module: reporters/dot.js 2490 | 2491 | require.register("reporters/html-cov.js", function(module, exports, require){ 2492 | 2493 | /** 2494 | * Module dependencies. 2495 | */ 2496 | 2497 | var JSONCov = require('./json-cov') 2498 | , fs = require('browser/fs'); 2499 | 2500 | /** 2501 | * Expose `HTMLCov`. 2502 | */ 2503 | 2504 | exports = module.exports = HTMLCov; 2505 | 2506 | /** 2507 | * Initialize a new `JsCoverage` reporter. 2508 | * 2509 | * @param {Runner} runner 2510 | * @api public 2511 | */ 2512 | 2513 | function HTMLCov(runner) { 2514 | var jade = require('jade') 2515 | , file = __dirname + '/templates/coverage.jade' 2516 | , str = fs.readFileSync(file, 'utf8') 2517 | , fn = jade.compile(str, { filename: file }) 2518 | , self = this; 2519 | 2520 | JSONCov.call(this, runner, false); 2521 | 2522 | runner.on('end', function(){ 2523 | process.stdout.write(fn({ 2524 | cov: self.cov 2525 | , coverageClass: coverageClass 2526 | })); 2527 | }); 2528 | } 2529 | 2530 | /** 2531 | * Return coverage class for `n`. 2532 | * 2533 | * @return {String} 2534 | * @api private 2535 | */ 2536 | 2537 | function coverageClass(n) { 2538 | if (n >= 75) return 'high'; 2539 | if (n >= 50) return 'medium'; 2540 | if (n >= 25) return 'low'; 2541 | return 'terrible'; 2542 | } 2543 | }); // module: reporters/html-cov.js 2544 | 2545 | require.register("reporters/html.js", function(module, exports, require){ 2546 | 2547 | /** 2548 | * Module dependencies. 2549 | */ 2550 | 2551 | var Base = require('./base') 2552 | , utils = require('../utils') 2553 | , Progress = require('../browser/progress') 2554 | , escape = utils.escape; 2555 | 2556 | /** 2557 | * Save timer references to avoid Sinon interfering (see GH-237). 2558 | */ 2559 | 2560 | var Date = global.Date 2561 | , setTimeout = global.setTimeout 2562 | , setInterval = global.setInterval 2563 | , clearTimeout = global.clearTimeout 2564 | , clearInterval = global.clearInterval; 2565 | 2566 | /** 2567 | * Expose `HTML`. 2568 | */ 2569 | 2570 | exports = module.exports = HTML; 2571 | 2572 | /** 2573 | * Stats template. 2574 | */ 2575 | 2576 | var statsTemplate = '
    ' 2577 | + '
  • ' 2578 | + '
  • passes: 0
  • ' 2579 | + '
  • failures: 0
  • ' 2580 | + '
  • duration: 0s
  • ' 2581 | + '
'; 2582 | 2583 | /** 2584 | * Initialize a new `HTML` reporter. 2585 | * 2586 | * @param {Runner} runner 2587 | * @api public 2588 | */ 2589 | 2590 | function HTML(runner) { 2591 | Base.call(this, runner); 2592 | 2593 | var self = this 2594 | , stats = this.stats 2595 | , total = runner.total 2596 | , stat = fragment(statsTemplate) 2597 | , items = stat.getElementsByTagName('li') 2598 | , passes = items[1].getElementsByTagName('em')[0] 2599 | , passesLink = items[1].getElementsByTagName('a')[0] 2600 | , failures = items[2].getElementsByTagName('em')[0] 2601 | , failuresLink = items[2].getElementsByTagName('a')[0] 2602 | , duration = items[3].getElementsByTagName('em')[0] 2603 | , canvas = stat.getElementsByTagName('canvas')[0] 2604 | , report = fragment('
    ') 2605 | , stack = [report] 2606 | , progress 2607 | , ctx 2608 | , root = document.getElementById('mocha'); 2609 | 2610 | if (canvas.getContext) { 2611 | var ratio = window.devicePixelRatio || 1; 2612 | canvas.style.width = canvas.width; 2613 | canvas.style.height = canvas.height; 2614 | canvas.width *= ratio; 2615 | canvas.height *= ratio; 2616 | ctx = canvas.getContext('2d'); 2617 | ctx.scale(ratio, ratio); 2618 | progress = new Progress; 2619 | } 2620 | 2621 | if (!root) return error('#mocha div missing, add it to your document'); 2622 | 2623 | // pass toggle 2624 | on(passesLink, 'click', function(){ 2625 | unhide(); 2626 | var name = /pass/.test(report.className) ? '' : ' pass'; 2627 | report.className = report.className.replace(/fail|pass/g, '') + name; 2628 | if (report.className.trim()) hideSuitesWithout('test pass'); 2629 | }); 2630 | 2631 | // failure toggle 2632 | on(failuresLink, 'click', function(){ 2633 | unhide(); 2634 | var name = /fail/.test(report.className) ? '' : ' fail'; 2635 | report.className = report.className.replace(/fail|pass/g, '') + name; 2636 | if (report.className.trim()) hideSuitesWithout('test fail'); 2637 | }); 2638 | 2639 | root.appendChild(stat); 2640 | root.appendChild(report); 2641 | 2642 | if (progress) progress.size(40); 2643 | 2644 | runner.on('suite', function(suite){ 2645 | if (suite.root) return; 2646 | 2647 | // suite 2648 | var url = self.suiteURL(suite); 2649 | var el = fragment('
  • %s

  • ', url, escape(suite.title)); 2650 | 2651 | // container 2652 | stack[0].appendChild(el); 2653 | stack.unshift(document.createElement('ul')); 2654 | el.appendChild(stack[0]); 2655 | }); 2656 | 2657 | runner.on('suite end', function(suite){ 2658 | if (suite.root) return; 2659 | stack.shift(); 2660 | }); 2661 | 2662 | runner.on('fail', function(test, err){ 2663 | if ('hook' == test.type) runner.emit('test end', test); 2664 | }); 2665 | 2666 | runner.on('test end', function(test){ 2667 | // TODO: add to stats 2668 | var percent = stats.tests / this.total * 100 | 0; 2669 | if (progress) progress.update(percent).draw(ctx); 2670 | 2671 | // update stats 2672 | var ms = new Date - stats.start; 2673 | text(passes, stats.passes); 2674 | text(failures, stats.failures); 2675 | text(duration, (ms / 1000).toFixed(2)); 2676 | 2677 | // test 2678 | if ('passed' == test.state) { 2679 | var url = self.testURL(test); 2680 | var el = fragment('
  • %e%ems

  • ', test.speed, test.title, test.duration, url); 2681 | } else if (test.pending) { 2682 | var el = fragment('
  • %e

  • ', test.title); 2683 | } else { 2684 | var el = fragment('
  • %e

  • ', test.title, encodeURIComponent(test.fullTitle())); 2685 | var str = test.err.stack || test.err.toString(); 2686 | 2687 | // FF / Opera do not add the message 2688 | if (!~str.indexOf(test.err.message)) { 2689 | str = test.err.message + '\n' + str; 2690 | } 2691 | 2692 | // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we 2693 | // check for the result of the stringifying. 2694 | if ('[object Error]' == str) str = test.err.message; 2695 | 2696 | // Safari doesn't give you a stack. Let's at least provide a source line. 2697 | if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { 2698 | str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; 2699 | } 2700 | 2701 | el.appendChild(fragment('
    %e
    ', str)); 2702 | } 2703 | 2704 | // toggle code 2705 | // TODO: defer 2706 | if (!test.pending) { 2707 | var h2 = el.getElementsByTagName('h2')[0]; 2708 | 2709 | on(h2, 'click', function(){ 2710 | pre.style.display = 'none' == pre.style.display 2711 | ? 'block' 2712 | : 'none'; 2713 | }); 2714 | 2715 | var pre = fragment('
    %e
    ', utils.clean(test.fn.toString())); 2716 | el.appendChild(pre); 2717 | pre.style.display = 'none'; 2718 | } 2719 | 2720 | // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack. 2721 | if (stack[0]) stack[0].appendChild(el); 2722 | }); 2723 | } 2724 | 2725 | /** 2726 | * Provide suite URL 2727 | * 2728 | * @param {Object} [suite] 2729 | */ 2730 | 2731 | HTML.prototype.suiteURL = function(suite){ 2732 | return '?grep=' + encodeURIComponent(suite.fullTitle()); 2733 | }; 2734 | 2735 | /** 2736 | * Provide test URL 2737 | * 2738 | * @param {Object} [test] 2739 | */ 2740 | 2741 | HTML.prototype.testURL = function(test){ 2742 | return '?grep=' + encodeURIComponent(test.fullTitle()); 2743 | }; 2744 | 2745 | /** 2746 | * Display error `msg`. 2747 | */ 2748 | 2749 | function error(msg) { 2750 | document.body.appendChild(fragment('
    %s
    ', msg)); 2751 | } 2752 | 2753 | /** 2754 | * Return a DOM fragment from `html`. 2755 | */ 2756 | 2757 | function fragment(html) { 2758 | var args = arguments 2759 | , div = document.createElement('div') 2760 | , i = 1; 2761 | 2762 | div.innerHTML = html.replace(/%([se])/g, function(_, type){ 2763 | switch (type) { 2764 | case 's': return String(args[i++]); 2765 | case 'e': return escape(args[i++]); 2766 | } 2767 | }); 2768 | 2769 | return div.firstChild; 2770 | } 2771 | 2772 | /** 2773 | * Check for suites that do not have elements 2774 | * with `classname`, and hide them. 2775 | */ 2776 | 2777 | function hideSuitesWithout(classname) { 2778 | var suites = document.getElementsByClassName('suite'); 2779 | for (var i = 0; i < suites.length; i++) { 2780 | var els = suites[i].getElementsByClassName(classname); 2781 | if (0 == els.length) suites[i].className += ' hidden'; 2782 | } 2783 | } 2784 | 2785 | /** 2786 | * Unhide .hidden suites. 2787 | */ 2788 | 2789 | function unhide() { 2790 | var els = document.getElementsByClassName('suite hidden'); 2791 | for (var i = 0; i < els.length; ++i) { 2792 | els[i].className = els[i].className.replace('suite hidden', 'suite'); 2793 | } 2794 | } 2795 | 2796 | /** 2797 | * Set `el` text to `str`. 2798 | */ 2799 | 2800 | function text(el, str) { 2801 | if (el.textContent) { 2802 | el.textContent = str; 2803 | } else { 2804 | el.innerText = str; 2805 | } 2806 | } 2807 | 2808 | /** 2809 | * Listen on `event` with callback `fn`. 2810 | */ 2811 | 2812 | function on(el, event, fn) { 2813 | if (el.addEventListener) { 2814 | el.addEventListener(event, fn, false); 2815 | } else { 2816 | el.attachEvent('on' + event, fn); 2817 | } 2818 | } 2819 | 2820 | }); // module: reporters/html.js 2821 | 2822 | require.register("reporters/index.js", function(module, exports, require){ 2823 | 2824 | exports.Base = require('./base'); 2825 | exports.Dot = require('./dot'); 2826 | exports.Doc = require('./doc'); 2827 | exports.TAP = require('./tap'); 2828 | exports.JSON = require('./json'); 2829 | exports.HTML = require('./html'); 2830 | exports.List = require('./list'); 2831 | exports.Min = require('./min'); 2832 | exports.Spec = require('./spec'); 2833 | exports.Nyan = require('./nyan'); 2834 | exports.XUnit = require('./xunit'); 2835 | exports.Markdown = require('./markdown'); 2836 | exports.Progress = require('./progress'); 2837 | exports.Landing = require('./landing'); 2838 | exports.JSONCov = require('./json-cov'); 2839 | exports.HTMLCov = require('./html-cov'); 2840 | exports.JSONStream = require('./json-stream'); 2841 | 2842 | }); // module: reporters/index.js 2843 | 2844 | require.register("reporters/json-cov.js", function(module, exports, require){ 2845 | 2846 | /** 2847 | * Module dependencies. 2848 | */ 2849 | 2850 | var Base = require('./base'); 2851 | 2852 | /** 2853 | * Expose `JSONCov`. 2854 | */ 2855 | 2856 | exports = module.exports = JSONCov; 2857 | 2858 | /** 2859 | * Initialize a new `JsCoverage` reporter. 2860 | * 2861 | * @param {Runner} runner 2862 | * @param {Boolean} output 2863 | * @api public 2864 | */ 2865 | 2866 | function JSONCov(runner, output) { 2867 | var self = this 2868 | , output = 1 == arguments.length ? true : output; 2869 | 2870 | Base.call(this, runner); 2871 | 2872 | var tests = [] 2873 | , failures = [] 2874 | , passes = []; 2875 | 2876 | runner.on('test end', function(test){ 2877 | tests.push(test); 2878 | }); 2879 | 2880 | runner.on('pass', function(test){ 2881 | passes.push(test); 2882 | }); 2883 | 2884 | runner.on('fail', function(test){ 2885 | failures.push(test); 2886 | }); 2887 | 2888 | runner.on('end', function(){ 2889 | var cov = global._$jscoverage || {}; 2890 | var result = self.cov = map(cov); 2891 | result.stats = self.stats; 2892 | result.tests = tests.map(clean); 2893 | result.failures = failures.map(clean); 2894 | result.passes = passes.map(clean); 2895 | if (!output) return; 2896 | process.stdout.write(JSON.stringify(result, null, 2 )); 2897 | }); 2898 | } 2899 | 2900 | /** 2901 | * Map jscoverage data to a JSON structure 2902 | * suitable for reporting. 2903 | * 2904 | * @param {Object} cov 2905 | * @return {Object} 2906 | * @api private 2907 | */ 2908 | 2909 | function map(cov) { 2910 | var ret = { 2911 | instrumentation: 'node-jscoverage' 2912 | , sloc: 0 2913 | , hits: 0 2914 | , misses: 0 2915 | , coverage: 0 2916 | , files: [] 2917 | }; 2918 | 2919 | for (var filename in cov) { 2920 | var data = coverage(filename, cov[filename]); 2921 | ret.files.push(data); 2922 | ret.hits += data.hits; 2923 | ret.misses += data.misses; 2924 | ret.sloc += data.sloc; 2925 | } 2926 | 2927 | ret.files.sort(function(a, b) { 2928 | return a.filename.localeCompare(b.filename); 2929 | }); 2930 | 2931 | if (ret.sloc > 0) { 2932 | ret.coverage = (ret.hits / ret.sloc) * 100; 2933 | } 2934 | 2935 | return ret; 2936 | }; 2937 | 2938 | /** 2939 | * Map jscoverage data for a single source file 2940 | * to a JSON structure suitable for reporting. 2941 | * 2942 | * @param {String} filename name of the source file 2943 | * @param {Object} data jscoverage coverage data 2944 | * @return {Object} 2945 | * @api private 2946 | */ 2947 | 2948 | function coverage(filename, data) { 2949 | var ret = { 2950 | filename: filename, 2951 | coverage: 0, 2952 | hits: 0, 2953 | misses: 0, 2954 | sloc: 0, 2955 | source: {} 2956 | }; 2957 | 2958 | data.source.forEach(function(line, num){ 2959 | num++; 2960 | 2961 | if (data[num] === 0) { 2962 | ret.misses++; 2963 | ret.sloc++; 2964 | } else if (data[num] !== undefined) { 2965 | ret.hits++; 2966 | ret.sloc++; 2967 | } 2968 | 2969 | ret.source[num] = { 2970 | source: line 2971 | , coverage: data[num] === undefined 2972 | ? '' 2973 | : data[num] 2974 | }; 2975 | }); 2976 | 2977 | ret.coverage = ret.hits / ret.sloc * 100; 2978 | 2979 | return ret; 2980 | } 2981 | 2982 | /** 2983 | * Return a plain-object representation of `test` 2984 | * free of cyclic properties etc. 2985 | * 2986 | * @param {Object} test 2987 | * @return {Object} 2988 | * @api private 2989 | */ 2990 | 2991 | function clean(test) { 2992 | return { 2993 | title: test.title 2994 | , fullTitle: test.fullTitle() 2995 | , duration: test.duration 2996 | } 2997 | } 2998 | 2999 | }); // module: reporters/json-cov.js 3000 | 3001 | require.register("reporters/json-stream.js", function(module, exports, require){ 3002 | 3003 | /** 3004 | * Module dependencies. 3005 | */ 3006 | 3007 | var Base = require('./base') 3008 | , color = Base.color; 3009 | 3010 | /** 3011 | * Expose `List`. 3012 | */ 3013 | 3014 | exports = module.exports = List; 3015 | 3016 | /** 3017 | * Initialize a new `List` test reporter. 3018 | * 3019 | * @param {Runner} runner 3020 | * @api public 3021 | */ 3022 | 3023 | function List(runner) { 3024 | Base.call(this, runner); 3025 | 3026 | var self = this 3027 | , stats = this.stats 3028 | , total = runner.total; 3029 | 3030 | runner.on('start', function(){ 3031 | console.log(JSON.stringify(['start', { total: total }])); 3032 | }); 3033 | 3034 | runner.on('pass', function(test){ 3035 | console.log(JSON.stringify(['pass', clean(test)])); 3036 | }); 3037 | 3038 | runner.on('fail', function(test, err){ 3039 | console.log(JSON.stringify(['fail', clean(test)])); 3040 | }); 3041 | 3042 | runner.on('end', function(){ 3043 | process.stdout.write(JSON.stringify(['end', self.stats])); 3044 | }); 3045 | } 3046 | 3047 | /** 3048 | * Return a plain-object representation of `test` 3049 | * free of cyclic properties etc. 3050 | * 3051 | * @param {Object} test 3052 | * @return {Object} 3053 | * @api private 3054 | */ 3055 | 3056 | function clean(test) { 3057 | return { 3058 | title: test.title 3059 | , fullTitle: test.fullTitle() 3060 | , duration: test.duration 3061 | } 3062 | } 3063 | }); // module: reporters/json-stream.js 3064 | 3065 | require.register("reporters/json.js", function(module, exports, require){ 3066 | 3067 | /** 3068 | * Module dependencies. 3069 | */ 3070 | 3071 | var Base = require('./base') 3072 | , cursor = Base.cursor 3073 | , color = Base.color; 3074 | 3075 | /** 3076 | * Expose `JSON`. 3077 | */ 3078 | 3079 | exports = module.exports = JSONReporter; 3080 | 3081 | /** 3082 | * Initialize a new `JSON` reporter. 3083 | * 3084 | * @param {Runner} runner 3085 | * @api public 3086 | */ 3087 | 3088 | function JSONReporter(runner) { 3089 | var self = this; 3090 | Base.call(this, runner); 3091 | 3092 | var tests = [] 3093 | , failures = [] 3094 | , passes = []; 3095 | 3096 | runner.on('test end', function(test){ 3097 | tests.push(test); 3098 | }); 3099 | 3100 | runner.on('pass', function(test){ 3101 | passes.push(test); 3102 | }); 3103 | 3104 | runner.on('fail', function(test){ 3105 | failures.push(test); 3106 | }); 3107 | 3108 | runner.on('end', function(){ 3109 | var obj = { 3110 | stats: self.stats 3111 | , tests: tests.map(clean) 3112 | , failures: failures.map(clean) 3113 | , passes: passes.map(clean) 3114 | }; 3115 | 3116 | process.stdout.write(JSON.stringify(obj, null, 2)); 3117 | }); 3118 | } 3119 | 3120 | /** 3121 | * Return a plain-object representation of `test` 3122 | * free of cyclic properties etc. 3123 | * 3124 | * @param {Object} test 3125 | * @return {Object} 3126 | * @api private 3127 | */ 3128 | 3129 | function clean(test) { 3130 | return { 3131 | title: test.title 3132 | , fullTitle: test.fullTitle() 3133 | , duration: test.duration 3134 | } 3135 | } 3136 | }); // module: reporters/json.js 3137 | 3138 | require.register("reporters/landing.js", function(module, exports, require){ 3139 | 3140 | /** 3141 | * Module dependencies. 3142 | */ 3143 | 3144 | var Base = require('./base') 3145 | , cursor = Base.cursor 3146 | , color = Base.color; 3147 | 3148 | /** 3149 | * Expose `Landing`. 3150 | */ 3151 | 3152 | exports = module.exports = Landing; 3153 | 3154 | /** 3155 | * Airplane color. 3156 | */ 3157 | 3158 | Base.colors.plane = 0; 3159 | 3160 | /** 3161 | * Airplane crash color. 3162 | */ 3163 | 3164 | Base.colors['plane crash'] = 31; 3165 | 3166 | /** 3167 | * Runway color. 3168 | */ 3169 | 3170 | Base.colors.runway = 90; 3171 | 3172 | /** 3173 | * Initialize a new `Landing` reporter. 3174 | * 3175 | * @param {Runner} runner 3176 | * @api public 3177 | */ 3178 | 3179 | function Landing(runner) { 3180 | Base.call(this, runner); 3181 | 3182 | var self = this 3183 | , stats = this.stats 3184 | , width = Base.window.width * .75 | 0 3185 | , total = runner.total 3186 | , stream = process.stdout 3187 | , plane = color('plane', '✈') 3188 | , crashed = -1 3189 | , n = 0; 3190 | 3191 | function runway() { 3192 | var buf = Array(width).join('-'); 3193 | return ' ' + color('runway', buf); 3194 | } 3195 | 3196 | runner.on('start', function(){ 3197 | stream.write('\n '); 3198 | cursor.hide(); 3199 | }); 3200 | 3201 | runner.on('test end', function(test){ 3202 | // check if the plane crashed 3203 | var col = -1 == crashed 3204 | ? width * ++n / total | 0 3205 | : crashed; 3206 | 3207 | // show the crash 3208 | if ('failed' == test.state) { 3209 | plane = color('plane crash', '✈'); 3210 | crashed = col; 3211 | } 3212 | 3213 | // render landing strip 3214 | stream.write('\u001b[4F\n\n'); 3215 | stream.write(runway()); 3216 | stream.write('\n '); 3217 | stream.write(color('runway', Array(col).join('⋅'))); 3218 | stream.write(plane) 3219 | stream.write(color('runway', Array(width - col).join('⋅') + '\n')); 3220 | stream.write(runway()); 3221 | stream.write('\u001b[0m'); 3222 | }); 3223 | 3224 | runner.on('end', function(){ 3225 | cursor.show(); 3226 | console.log(); 3227 | self.epilogue(); 3228 | }); 3229 | } 3230 | 3231 | /** 3232 | * Inherit from `Base.prototype`. 3233 | */ 3234 | 3235 | function F(){}; 3236 | F.prototype = Base.prototype; 3237 | Landing.prototype = new F; 3238 | Landing.prototype.constructor = Landing; 3239 | 3240 | }); // module: reporters/landing.js 3241 | 3242 | require.register("reporters/list.js", function(module, exports, require){ 3243 | 3244 | /** 3245 | * Module dependencies. 3246 | */ 3247 | 3248 | var Base = require('./base') 3249 | , cursor = Base.cursor 3250 | , color = Base.color; 3251 | 3252 | /** 3253 | * Expose `List`. 3254 | */ 3255 | 3256 | exports = module.exports = List; 3257 | 3258 | /** 3259 | * Initialize a new `List` test reporter. 3260 | * 3261 | * @param {Runner} runner 3262 | * @api public 3263 | */ 3264 | 3265 | function List(runner) { 3266 | Base.call(this, runner); 3267 | 3268 | var self = this 3269 | , stats = this.stats 3270 | , n = 0; 3271 | 3272 | runner.on('start', function(){ 3273 | console.log(); 3274 | }); 3275 | 3276 | runner.on('test', function(test){ 3277 | process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); 3278 | }); 3279 | 3280 | runner.on('pending', function(test){ 3281 | var fmt = color('checkmark', ' -') 3282 | + color('pending', ' %s'); 3283 | console.log(fmt, test.fullTitle()); 3284 | }); 3285 | 3286 | runner.on('pass', function(test){ 3287 | var fmt = color('checkmark', ' '+Base.symbols.dot) 3288 | + color('pass', ' %s: ') 3289 | + color(test.speed, '%dms'); 3290 | cursor.CR(); 3291 | console.log(fmt, test.fullTitle(), test.duration); 3292 | }); 3293 | 3294 | runner.on('fail', function(test, err){ 3295 | cursor.CR(); 3296 | console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); 3297 | }); 3298 | 3299 | runner.on('end', self.epilogue.bind(self)); 3300 | } 3301 | 3302 | /** 3303 | * Inherit from `Base.prototype`. 3304 | */ 3305 | 3306 | function F(){}; 3307 | F.prototype = Base.prototype; 3308 | List.prototype = new F; 3309 | List.prototype.constructor = List; 3310 | 3311 | 3312 | }); // module: reporters/list.js 3313 | 3314 | require.register("reporters/markdown.js", function(module, exports, require){ 3315 | /** 3316 | * Module dependencies. 3317 | */ 3318 | 3319 | var Base = require('./base') 3320 | , utils = require('../utils'); 3321 | 3322 | /** 3323 | * Expose `Markdown`. 3324 | */ 3325 | 3326 | exports = module.exports = Markdown; 3327 | 3328 | /** 3329 | * Initialize a new `Markdown` reporter. 3330 | * 3331 | * @param {Runner} runner 3332 | * @api public 3333 | */ 3334 | 3335 | function Markdown(runner) { 3336 | Base.call(this, runner); 3337 | 3338 | var self = this 3339 | , stats = this.stats 3340 | , level = 0 3341 | , buf = ''; 3342 | 3343 | function title(str) { 3344 | return Array(level).join('#') + ' ' + str; 3345 | } 3346 | 3347 | function indent() { 3348 | return Array(level).join(' '); 3349 | } 3350 | 3351 | function mapTOC(suite, obj) { 3352 | var ret = obj; 3353 | obj = obj[suite.title] = obj[suite.title] || { suite: suite }; 3354 | suite.suites.forEach(function(suite){ 3355 | mapTOC(suite, obj); 3356 | }); 3357 | return ret; 3358 | } 3359 | 3360 | function stringifyTOC(obj, level) { 3361 | ++level; 3362 | var buf = ''; 3363 | var link; 3364 | for (var key in obj) { 3365 | if ('suite' == key) continue; 3366 | if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; 3367 | if (key) buf += Array(level).join(' ') + link; 3368 | buf += stringifyTOC(obj[key], level); 3369 | } 3370 | --level; 3371 | return buf; 3372 | } 3373 | 3374 | function generateTOC(suite) { 3375 | var obj = mapTOC(suite, {}); 3376 | return stringifyTOC(obj, 0); 3377 | } 3378 | 3379 | generateTOC(runner.suite); 3380 | 3381 | runner.on('suite', function(suite){ 3382 | ++level; 3383 | var slug = utils.slug(suite.fullTitle()); 3384 | buf += '' + '\n'; 3385 | buf += title(suite.title) + '\n'; 3386 | }); 3387 | 3388 | runner.on('suite end', function(suite){ 3389 | --level; 3390 | }); 3391 | 3392 | runner.on('pass', function(test){ 3393 | var code = utils.clean(test.fn.toString()); 3394 | buf += test.title + '.\n'; 3395 | buf += '\n```js\n'; 3396 | buf += code + '\n'; 3397 | buf += '```\n\n'; 3398 | }); 3399 | 3400 | runner.on('end', function(){ 3401 | process.stdout.write('# TOC\n'); 3402 | process.stdout.write(generateTOC(runner.suite)); 3403 | process.stdout.write(buf); 3404 | }); 3405 | } 3406 | }); // module: reporters/markdown.js 3407 | 3408 | require.register("reporters/min.js", function(module, exports, require){ 3409 | 3410 | /** 3411 | * Module dependencies. 3412 | */ 3413 | 3414 | var Base = require('./base'); 3415 | 3416 | /** 3417 | * Expose `Min`. 3418 | */ 3419 | 3420 | exports = module.exports = Min; 3421 | 3422 | /** 3423 | * Initialize a new `Min` minimal test reporter (best used with --watch). 3424 | * 3425 | * @param {Runner} runner 3426 | * @api public 3427 | */ 3428 | 3429 | function Min(runner) { 3430 | Base.call(this, runner); 3431 | 3432 | runner.on('start', function(){ 3433 | // clear screen 3434 | process.stdout.write('\u001b[2J'); 3435 | // set cursor position 3436 | process.stdout.write('\u001b[1;3H'); 3437 | }); 3438 | 3439 | runner.on('end', this.epilogue.bind(this)); 3440 | } 3441 | 3442 | /** 3443 | * Inherit from `Base.prototype`. 3444 | */ 3445 | 3446 | function F(){}; 3447 | F.prototype = Base.prototype; 3448 | Min.prototype = new F; 3449 | Min.prototype.constructor = Min; 3450 | 3451 | 3452 | }); // module: reporters/min.js 3453 | 3454 | require.register("reporters/nyan.js", function(module, exports, require){ 3455 | /** 3456 | * Module dependencies. 3457 | */ 3458 | 3459 | var Base = require('./base') 3460 | , color = Base.color; 3461 | 3462 | /** 3463 | * Expose `Dot`. 3464 | */ 3465 | 3466 | exports = module.exports = NyanCat; 3467 | 3468 | /** 3469 | * Initialize a new `Dot` matrix test reporter. 3470 | * 3471 | * @param {Runner} runner 3472 | * @api public 3473 | */ 3474 | 3475 | function NyanCat(runner) { 3476 | Base.call(this, runner); 3477 | var self = this 3478 | , stats = this.stats 3479 | , width = Base.window.width * .75 | 0 3480 | , rainbowColors = this.rainbowColors = self.generateColors() 3481 | , colorIndex = this.colorIndex = 0 3482 | , numerOfLines = this.numberOfLines = 4 3483 | , trajectories = this.trajectories = [[], [], [], []] 3484 | , nyanCatWidth = this.nyanCatWidth = 11 3485 | , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) 3486 | , scoreboardWidth = this.scoreboardWidth = 5 3487 | , tick = this.tick = 0 3488 | , n = 0; 3489 | 3490 | runner.on('start', function(){ 3491 | Base.cursor.hide(); 3492 | self.draw(); 3493 | }); 3494 | 3495 | runner.on('pending', function(test){ 3496 | self.draw(); 3497 | }); 3498 | 3499 | runner.on('pass', function(test){ 3500 | self.draw(); 3501 | }); 3502 | 3503 | runner.on('fail', function(test, err){ 3504 | self.draw(); 3505 | }); 3506 | 3507 | runner.on('end', function(){ 3508 | Base.cursor.show(); 3509 | for (var i = 0; i < self.numberOfLines; i++) write('\n'); 3510 | self.epilogue(); 3511 | }); 3512 | } 3513 | 3514 | /** 3515 | * Draw the nyan cat 3516 | * 3517 | * @api private 3518 | */ 3519 | 3520 | NyanCat.prototype.draw = function(){ 3521 | this.appendRainbow(); 3522 | this.drawScoreboard(); 3523 | this.drawRainbow(); 3524 | this.drawNyanCat(); 3525 | this.tick = !this.tick; 3526 | }; 3527 | 3528 | /** 3529 | * Draw the "scoreboard" showing the number 3530 | * of passes, failures and pending tests. 3531 | * 3532 | * @api private 3533 | */ 3534 | 3535 | NyanCat.prototype.drawScoreboard = function(){ 3536 | var stats = this.stats; 3537 | var colors = Base.colors; 3538 | 3539 | function draw(color, n) { 3540 | write(' '); 3541 | write('\u001b[' + color + 'm' + n + '\u001b[0m'); 3542 | write('\n'); 3543 | } 3544 | 3545 | draw(colors.green, stats.passes); 3546 | draw(colors.fail, stats.failures); 3547 | draw(colors.pending, stats.pending); 3548 | write('\n'); 3549 | 3550 | this.cursorUp(this.numberOfLines); 3551 | }; 3552 | 3553 | /** 3554 | * Append the rainbow. 3555 | * 3556 | * @api private 3557 | */ 3558 | 3559 | NyanCat.prototype.appendRainbow = function(){ 3560 | var segment = this.tick ? '_' : '-'; 3561 | var rainbowified = this.rainbowify(segment); 3562 | 3563 | for (var index = 0; index < this.numberOfLines; index++) { 3564 | var trajectory = this.trajectories[index]; 3565 | if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); 3566 | trajectory.push(rainbowified); 3567 | } 3568 | }; 3569 | 3570 | /** 3571 | * Draw the rainbow. 3572 | * 3573 | * @api private 3574 | */ 3575 | 3576 | NyanCat.prototype.drawRainbow = function(){ 3577 | var self = this; 3578 | 3579 | this.trajectories.forEach(function(line, index) { 3580 | write('\u001b[' + self.scoreboardWidth + 'C'); 3581 | write(line.join('')); 3582 | write('\n'); 3583 | }); 3584 | 3585 | this.cursorUp(this.numberOfLines); 3586 | }; 3587 | 3588 | /** 3589 | * Draw the nyan cat 3590 | * 3591 | * @api private 3592 | */ 3593 | 3594 | NyanCat.prototype.drawNyanCat = function() { 3595 | var self = this; 3596 | var startWidth = this.scoreboardWidth + this.trajectories[0].length; 3597 | var color = '\u001b[' + startWidth + 'C'; 3598 | var padding = ''; 3599 | 3600 | write(color); 3601 | write('_,------,'); 3602 | write('\n'); 3603 | 3604 | write(color); 3605 | padding = self.tick ? ' ' : ' '; 3606 | write('_|' + padding + '/\\_/\\ '); 3607 | write('\n'); 3608 | 3609 | write(color); 3610 | padding = self.tick ? '_' : '__'; 3611 | var tail = self.tick ? '~' : '^'; 3612 | var face; 3613 | write(tail + '|' + padding + this.face() + ' '); 3614 | write('\n'); 3615 | 3616 | write(color); 3617 | padding = self.tick ? ' ' : ' '; 3618 | write(padding + '"" "" '); 3619 | write('\n'); 3620 | 3621 | this.cursorUp(this.numberOfLines); 3622 | }; 3623 | 3624 | /** 3625 | * Draw nyan cat face. 3626 | * 3627 | * @return {String} 3628 | * @api private 3629 | */ 3630 | 3631 | NyanCat.prototype.face = function() { 3632 | var stats = this.stats; 3633 | if (stats.failures) { 3634 | return '( x .x)'; 3635 | } else if (stats.pending) { 3636 | return '( o .o)'; 3637 | } else if(stats.passes) { 3638 | return '( ^ .^)'; 3639 | } else { 3640 | return '( - .-)'; 3641 | } 3642 | } 3643 | 3644 | /** 3645 | * Move cursor up `n`. 3646 | * 3647 | * @param {Number} n 3648 | * @api private 3649 | */ 3650 | 3651 | NyanCat.prototype.cursorUp = function(n) { 3652 | write('\u001b[' + n + 'A'); 3653 | }; 3654 | 3655 | /** 3656 | * Move cursor down `n`. 3657 | * 3658 | * @param {Number} n 3659 | * @api private 3660 | */ 3661 | 3662 | NyanCat.prototype.cursorDown = function(n) { 3663 | write('\u001b[' + n + 'B'); 3664 | }; 3665 | 3666 | /** 3667 | * Generate rainbow colors. 3668 | * 3669 | * @return {Array} 3670 | * @api private 3671 | */ 3672 | 3673 | NyanCat.prototype.generateColors = function(){ 3674 | var colors = []; 3675 | 3676 | for (var i = 0; i < (6 * 7); i++) { 3677 | var pi3 = Math.floor(Math.PI / 3); 3678 | var n = (i * (1.0 / 6)); 3679 | var r = Math.floor(3 * Math.sin(n) + 3); 3680 | var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); 3681 | var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); 3682 | colors.push(36 * r + 6 * g + b + 16); 3683 | } 3684 | 3685 | return colors; 3686 | }; 3687 | 3688 | /** 3689 | * Apply rainbow to the given `str`. 3690 | * 3691 | * @param {String} str 3692 | * @return {String} 3693 | * @api private 3694 | */ 3695 | 3696 | NyanCat.prototype.rainbowify = function(str){ 3697 | var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; 3698 | this.colorIndex += 1; 3699 | return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; 3700 | }; 3701 | 3702 | /** 3703 | * Stdout helper. 3704 | */ 3705 | 3706 | function write(string) { 3707 | process.stdout.write(string); 3708 | } 3709 | 3710 | /** 3711 | * Inherit from `Base.prototype`. 3712 | */ 3713 | 3714 | function F(){}; 3715 | F.prototype = Base.prototype; 3716 | NyanCat.prototype = new F; 3717 | NyanCat.prototype.constructor = NyanCat; 3718 | 3719 | 3720 | }); // module: reporters/nyan.js 3721 | 3722 | require.register("reporters/progress.js", function(module, exports, require){ 3723 | 3724 | /** 3725 | * Module dependencies. 3726 | */ 3727 | 3728 | var Base = require('./base') 3729 | , cursor = Base.cursor 3730 | , color = Base.color; 3731 | 3732 | /** 3733 | * Expose `Progress`. 3734 | */ 3735 | 3736 | exports = module.exports = Progress; 3737 | 3738 | /** 3739 | * General progress bar color. 3740 | */ 3741 | 3742 | Base.colors.progress = 90; 3743 | 3744 | /** 3745 | * Initialize a new `Progress` bar test reporter. 3746 | * 3747 | * @param {Runner} runner 3748 | * @param {Object} options 3749 | * @api public 3750 | */ 3751 | 3752 | function Progress(runner, options) { 3753 | Base.call(this, runner); 3754 | 3755 | var self = this 3756 | , options = options || {} 3757 | , stats = this.stats 3758 | , width = Base.window.width * .50 | 0 3759 | , total = runner.total 3760 | , complete = 0 3761 | , max = Math.max; 3762 | 3763 | // default chars 3764 | options.open = options.open || '['; 3765 | options.complete = options.complete || '▬'; 3766 | options.incomplete = options.incomplete || Base.symbols.dot; 3767 | options.close = options.close || ']'; 3768 | options.verbose = false; 3769 | 3770 | // tests started 3771 | runner.on('start', function(){ 3772 | console.log(); 3773 | cursor.hide(); 3774 | }); 3775 | 3776 | // tests complete 3777 | runner.on('test end', function(){ 3778 | complete++; 3779 | var incomplete = total - complete 3780 | , percent = complete / total 3781 | , n = width * percent | 0 3782 | , i = width - n; 3783 | 3784 | cursor.CR(); 3785 | process.stdout.write('\u001b[J'); 3786 | process.stdout.write(color('progress', ' ' + options.open)); 3787 | process.stdout.write(Array(n).join(options.complete)); 3788 | process.stdout.write(Array(i).join(options.incomplete)); 3789 | process.stdout.write(color('progress', options.close)); 3790 | if (options.verbose) { 3791 | process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); 3792 | } 3793 | }); 3794 | 3795 | // tests are complete, output some stats 3796 | // and the failures if any 3797 | runner.on('end', function(){ 3798 | cursor.show(); 3799 | console.log(); 3800 | self.epilogue(); 3801 | }); 3802 | } 3803 | 3804 | /** 3805 | * Inherit from `Base.prototype`. 3806 | */ 3807 | 3808 | function F(){}; 3809 | F.prototype = Base.prototype; 3810 | Progress.prototype = new F; 3811 | Progress.prototype.constructor = Progress; 3812 | 3813 | 3814 | }); // module: reporters/progress.js 3815 | 3816 | require.register("reporters/spec.js", function(module, exports, require){ 3817 | 3818 | /** 3819 | * Module dependencies. 3820 | */ 3821 | 3822 | var Base = require('./base') 3823 | , cursor = Base.cursor 3824 | , color = Base.color; 3825 | 3826 | /** 3827 | * Expose `Spec`. 3828 | */ 3829 | 3830 | exports = module.exports = Spec; 3831 | 3832 | /** 3833 | * Initialize a new `Spec` test reporter. 3834 | * 3835 | * @param {Runner} runner 3836 | * @api public 3837 | */ 3838 | 3839 | function Spec(runner) { 3840 | Base.call(this, runner); 3841 | 3842 | var self = this 3843 | , stats = this.stats 3844 | , indents = 0 3845 | , n = 0; 3846 | 3847 | function indent() { 3848 | return Array(indents).join(' ') 3849 | } 3850 | 3851 | runner.on('start', function(){ 3852 | console.log(); 3853 | }); 3854 | 3855 | runner.on('suite', function(suite){ 3856 | ++indents; 3857 | console.log(color('suite', '%s%s'), indent(), suite.title); 3858 | }); 3859 | 3860 | runner.on('suite end', function(suite){ 3861 | --indents; 3862 | if (1 == indents) console.log(); 3863 | }); 3864 | 3865 | runner.on('pending', function(test){ 3866 | var fmt = indent() + color('pending', ' - %s'); 3867 | console.log(fmt, test.title); 3868 | }); 3869 | 3870 | runner.on('pass', function(test){ 3871 | if ('fast' == test.speed) { 3872 | var fmt = indent() 3873 | + color('checkmark', ' ' + Base.symbols.ok) 3874 | + color('pass', ' %s '); 3875 | cursor.CR(); 3876 | console.log(fmt, test.title); 3877 | } else { 3878 | var fmt = indent() 3879 | + color('checkmark', ' ' + Base.symbols.ok) 3880 | + color('pass', ' %s ') 3881 | + color(test.speed, '(%dms)'); 3882 | cursor.CR(); 3883 | console.log(fmt, test.title, test.duration); 3884 | } 3885 | }); 3886 | 3887 | runner.on('fail', function(test, err){ 3888 | cursor.CR(); 3889 | console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); 3890 | }); 3891 | 3892 | runner.on('end', self.epilogue.bind(self)); 3893 | } 3894 | 3895 | /** 3896 | * Inherit from `Base.prototype`. 3897 | */ 3898 | 3899 | function F(){}; 3900 | F.prototype = Base.prototype; 3901 | Spec.prototype = new F; 3902 | Spec.prototype.constructor = Spec; 3903 | 3904 | 3905 | }); // module: reporters/spec.js 3906 | 3907 | require.register("reporters/tap.js", function(module, exports, require){ 3908 | 3909 | /** 3910 | * Module dependencies. 3911 | */ 3912 | 3913 | var Base = require('./base') 3914 | , cursor = Base.cursor 3915 | , color = Base.color; 3916 | 3917 | /** 3918 | * Expose `TAP`. 3919 | */ 3920 | 3921 | exports = module.exports = TAP; 3922 | 3923 | /** 3924 | * Initialize a new `TAP` reporter. 3925 | * 3926 | * @param {Runner} runner 3927 | * @api public 3928 | */ 3929 | 3930 | function TAP(runner) { 3931 | Base.call(this, runner); 3932 | 3933 | var self = this 3934 | , stats = this.stats 3935 | , n = 1 3936 | , passes = 0 3937 | , failures = 0; 3938 | 3939 | runner.on('start', function(){ 3940 | var total = runner.grepTotal(runner.suite); 3941 | console.log('%d..%d', 1, total); 3942 | }); 3943 | 3944 | runner.on('test end', function(){ 3945 | ++n; 3946 | }); 3947 | 3948 | runner.on('pending', function(test){ 3949 | console.log('ok %d %s # SKIP -', n, title(test)); 3950 | }); 3951 | 3952 | runner.on('pass', function(test){ 3953 | passes++; 3954 | console.log('ok %d %s', n, title(test)); 3955 | }); 3956 | 3957 | runner.on('fail', function(test, err){ 3958 | failures++; 3959 | console.log('not ok %d %s', n, title(test)); 3960 | if (err.stack) console.log(err.stack.replace(/^/gm, ' ')); 3961 | }); 3962 | 3963 | runner.on('end', function(){ 3964 | console.log('# tests ' + (passes + failures)); 3965 | console.log('# pass ' + passes); 3966 | console.log('# fail ' + failures); 3967 | }); 3968 | } 3969 | 3970 | /** 3971 | * Return a TAP-safe title of `test` 3972 | * 3973 | * @param {Object} test 3974 | * @return {String} 3975 | * @api private 3976 | */ 3977 | 3978 | function title(test) { 3979 | return test.fullTitle().replace(/#/g, ''); 3980 | } 3981 | 3982 | }); // module: reporters/tap.js 3983 | 3984 | require.register("reporters/xunit.js", function(module, exports, require){ 3985 | 3986 | /** 3987 | * Module dependencies. 3988 | */ 3989 | 3990 | var Base = require('./base') 3991 | , utils = require('../utils') 3992 | , escape = utils.escape; 3993 | 3994 | /** 3995 | * Save timer references to avoid Sinon interfering (see GH-237). 3996 | */ 3997 | 3998 | var Date = global.Date 3999 | , setTimeout = global.setTimeout 4000 | , setInterval = global.setInterval 4001 | , clearTimeout = global.clearTimeout 4002 | , clearInterval = global.clearInterval; 4003 | 4004 | /** 4005 | * Expose `XUnit`. 4006 | */ 4007 | 4008 | exports = module.exports = XUnit; 4009 | 4010 | /** 4011 | * Initialize a new `XUnit` reporter. 4012 | * 4013 | * @param {Runner} runner 4014 | * @api public 4015 | */ 4016 | 4017 | function XUnit(runner) { 4018 | Base.call(this, runner); 4019 | var stats = this.stats 4020 | , tests = [] 4021 | , self = this; 4022 | 4023 | runner.on('pending', function(test){ 4024 | tests.push(test); 4025 | }); 4026 | 4027 | runner.on('pass', function(test){ 4028 | tests.push(test); 4029 | }); 4030 | 4031 | runner.on('fail', function(test){ 4032 | tests.push(test); 4033 | }); 4034 | 4035 | runner.on('end', function(){ 4036 | console.log(tag('testsuite', { 4037 | name: 'Mocha Tests' 4038 | , tests: stats.tests 4039 | , failures: stats.failures 4040 | , errors: stats.failures 4041 | , skipped: stats.tests - stats.failures - stats.passes 4042 | , timestamp: (new Date).toUTCString() 4043 | , time: (stats.duration / 1000) || 0 4044 | }, false)); 4045 | 4046 | tests.forEach(test); 4047 | console.log(''); 4048 | }); 4049 | } 4050 | 4051 | /** 4052 | * Inherit from `Base.prototype`. 4053 | */ 4054 | 4055 | function F(){}; 4056 | F.prototype = Base.prototype; 4057 | XUnit.prototype = new F; 4058 | XUnit.prototype.constructor = XUnit; 4059 | 4060 | 4061 | /** 4062 | * Output tag for the given `test.` 4063 | */ 4064 | 4065 | function test(test) { 4066 | var attrs = { 4067 | classname: test.parent.fullTitle() 4068 | , name: test.title 4069 | , time: (test.duration / 1000) || 0 4070 | }; 4071 | 4072 | if ('failed' == test.state) { 4073 | var err = test.err; 4074 | attrs.message = escape(err.message); 4075 | console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); 4076 | } else if (test.pending) { 4077 | console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); 4078 | } else { 4079 | console.log(tag('testcase', attrs, true) ); 4080 | } 4081 | } 4082 | 4083 | /** 4084 | * HTML tag helper. 4085 | */ 4086 | 4087 | function tag(name, attrs, close, content) { 4088 | var end = close ? '/>' : '>' 4089 | , pairs = [] 4090 | , tag; 4091 | 4092 | for (var key in attrs) { 4093 | pairs.push(key + '="' + escape(attrs[key]) + '"'); 4094 | } 4095 | 4096 | tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; 4097 | if (content) tag += content + ''; 4107 | } 4108 | 4109 | }); // module: reporters/xunit.js 4110 | 4111 | require.register("runnable.js", function(module, exports, require){ 4112 | 4113 | /** 4114 | * Module dependencies. 4115 | */ 4116 | 4117 | var EventEmitter = require('browser/events').EventEmitter 4118 | , debug = require('browser/debug')('mocha:runnable') 4119 | , milliseconds = require('./ms'); 4120 | 4121 | /** 4122 | * Save timer references to avoid Sinon interfering (see GH-237). 4123 | */ 4124 | 4125 | var Date = global.Date 4126 | , setTimeout = global.setTimeout 4127 | , setInterval = global.setInterval 4128 | , clearTimeout = global.clearTimeout 4129 | , clearInterval = global.clearInterval; 4130 | 4131 | /** 4132 | * Object#toString(). 4133 | */ 4134 | 4135 | var toString = Object.prototype.toString; 4136 | 4137 | /** 4138 | * Expose `Runnable`. 4139 | */ 4140 | 4141 | module.exports = Runnable; 4142 | 4143 | /** 4144 | * Initialize a new `Runnable` with the given `title` and callback `fn`. 4145 | * 4146 | * @param {String} title 4147 | * @param {Function} fn 4148 | * @api private 4149 | */ 4150 | 4151 | function Runnable(title, fn) { 4152 | this.title = title; 4153 | this.fn = fn; 4154 | this.async = fn && fn.length; 4155 | this.sync = ! this.async; 4156 | this._timeout = 2000; 4157 | this._slow = 75; 4158 | this.timedOut = false; 4159 | } 4160 | 4161 | /** 4162 | * Inherit from `EventEmitter.prototype`. 4163 | */ 4164 | 4165 | function F(){}; 4166 | F.prototype = EventEmitter.prototype; 4167 | Runnable.prototype = new F; 4168 | Runnable.prototype.constructor = Runnable; 4169 | 4170 | 4171 | /** 4172 | * Set & get timeout `ms`. 4173 | * 4174 | * @param {Number|String} ms 4175 | * @return {Runnable|Number} ms or self 4176 | * @api private 4177 | */ 4178 | 4179 | Runnable.prototype.timeout = function(ms){ 4180 | if (0 == arguments.length) return this._timeout; 4181 | if ('string' == typeof ms) ms = milliseconds(ms); 4182 | debug('timeout %d', ms); 4183 | this._timeout = ms; 4184 | if (this.timer) this.resetTimeout(); 4185 | return this; 4186 | }; 4187 | 4188 | /** 4189 | * Set & get slow `ms`. 4190 | * 4191 | * @param {Number|String} ms 4192 | * @return {Runnable|Number} ms or self 4193 | * @api private 4194 | */ 4195 | 4196 | Runnable.prototype.slow = function(ms){ 4197 | if (0 === arguments.length) return this._slow; 4198 | if ('string' == typeof ms) ms = milliseconds(ms); 4199 | debug('timeout %d', ms); 4200 | this._slow = ms; 4201 | return this; 4202 | }; 4203 | 4204 | /** 4205 | * Return the full title generated by recursively 4206 | * concatenating the parent's full title. 4207 | * 4208 | * @return {String} 4209 | * @api public 4210 | */ 4211 | 4212 | Runnable.prototype.fullTitle = function(){ 4213 | return this.parent.fullTitle() + ' ' + this.title; 4214 | }; 4215 | 4216 | /** 4217 | * Clear the timeout. 4218 | * 4219 | * @api private 4220 | */ 4221 | 4222 | Runnable.prototype.clearTimeout = function(){ 4223 | clearTimeout(this.timer); 4224 | }; 4225 | 4226 | /** 4227 | * Inspect the runnable void of private properties. 4228 | * 4229 | * @return {String} 4230 | * @api private 4231 | */ 4232 | 4233 | Runnable.prototype.inspect = function(){ 4234 | return JSON.stringify(this, function(key, val){ 4235 | if ('_' == key[0]) return; 4236 | if ('parent' == key) return '#'; 4237 | if ('ctx' == key) return '#'; 4238 | return val; 4239 | }, 2); 4240 | }; 4241 | 4242 | /** 4243 | * Reset the timeout. 4244 | * 4245 | * @api private 4246 | */ 4247 | 4248 | Runnable.prototype.resetTimeout = function(){ 4249 | var self = this; 4250 | var ms = this.timeout() || 1e9; 4251 | 4252 | this.clearTimeout(); 4253 | this.timer = setTimeout(function(){ 4254 | self.callback(new Error('timeout of ' + ms + 'ms exceeded')); 4255 | self.timedOut = true; 4256 | }, ms); 4257 | }; 4258 | 4259 | /** 4260 | * Whitelist these globals for this test run 4261 | * 4262 | * @api private 4263 | */ 4264 | Runnable.prototype.globals = function(arr){ 4265 | var self = this; 4266 | this._allowedGlobals = arr; 4267 | }; 4268 | 4269 | /** 4270 | * Run the test and invoke `fn(err)`. 4271 | * 4272 | * @param {Function} fn 4273 | * @api private 4274 | */ 4275 | 4276 | Runnable.prototype.run = function(fn){ 4277 | var self = this 4278 | , ms = this.timeout() 4279 | , start = new Date 4280 | , ctx = this.ctx 4281 | , finished 4282 | , emitted; 4283 | 4284 | if (ctx) ctx.runnable(this); 4285 | 4286 | // called multiple times 4287 | function multiple(err) { 4288 | if (emitted) return; 4289 | emitted = true; 4290 | self.emit('error', err || new Error('done() called multiple times')); 4291 | } 4292 | 4293 | // finished 4294 | function done(err) { 4295 | if (self.timedOut) return; 4296 | if (finished) return multiple(err); 4297 | self.clearTimeout(); 4298 | self.duration = new Date - start; 4299 | finished = true; 4300 | fn(err); 4301 | } 4302 | 4303 | // for .resetTimeout() 4304 | this.callback = done; 4305 | 4306 | // explicit async with `done` argument 4307 | if (this.async) { 4308 | this.resetTimeout(); 4309 | 4310 | try { 4311 | this.fn.call(ctx, function(err){ 4312 | if (err instanceof Error || toString.call(err) === "[object Error]") return done(err); 4313 | if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); 4314 | done(); 4315 | }); 4316 | } catch (err) { 4317 | done(err); 4318 | } 4319 | return; 4320 | } 4321 | 4322 | if (this.asyncOnly) { 4323 | return done(new Error('--async-only option in use without declaring `done()`')); 4324 | } 4325 | 4326 | // sync or promise-returning 4327 | try { 4328 | if (this.pending) { 4329 | done(); 4330 | } else { 4331 | callFn(this.fn); 4332 | } 4333 | } catch (err) { 4334 | done(err); 4335 | } 4336 | 4337 | function callFn(fn) { 4338 | var result = fn.call(ctx); 4339 | if (result && typeof result.then === 'function') { 4340 | self.resetTimeout(); 4341 | result.then(function(){ done() }, done); 4342 | } else { 4343 | done(); 4344 | } 4345 | } 4346 | }; 4347 | 4348 | }); // module: runnable.js 4349 | 4350 | require.register("runner.js", function(module, exports, require){ 4351 | /** 4352 | * Module dependencies. 4353 | */ 4354 | 4355 | var EventEmitter = require('browser/events').EventEmitter 4356 | , debug = require('browser/debug')('mocha:runner') 4357 | , Test = require('./test') 4358 | , utils = require('./utils') 4359 | , filter = utils.filter 4360 | , keys = utils.keys; 4361 | 4362 | /** 4363 | * Non-enumerable globals. 4364 | */ 4365 | 4366 | var globals = [ 4367 | 'setTimeout', 4368 | 'clearTimeout', 4369 | 'setInterval', 4370 | 'clearInterval', 4371 | 'XMLHttpRequest', 4372 | 'Date' 4373 | ]; 4374 | 4375 | /** 4376 | * Expose `Runner`. 4377 | */ 4378 | 4379 | module.exports = Runner; 4380 | 4381 | /** 4382 | * Initialize a `Runner` for the given `suite`. 4383 | * 4384 | * Events: 4385 | * 4386 | * - `start` execution started 4387 | * - `end` execution complete 4388 | * - `suite` (suite) test suite execution started 4389 | * - `suite end` (suite) all tests (and sub-suites) have finished 4390 | * - `test` (test) test execution started 4391 | * - `test end` (test) test completed 4392 | * - `hook` (hook) hook execution started 4393 | * - `hook end` (hook) hook complete 4394 | * - `pass` (test) test passed 4395 | * - `fail` (test, err) test failed 4396 | * - `pending` (test) test pending 4397 | * 4398 | * @api public 4399 | */ 4400 | 4401 | function Runner(suite) { 4402 | var self = this; 4403 | this._globals = []; 4404 | this._abort = false; 4405 | this.suite = suite; 4406 | this.total = suite.total(); 4407 | this.failures = 0; 4408 | this.on('test end', function(test){ self.checkGlobals(test); }); 4409 | this.on('hook end', function(hook){ self.checkGlobals(hook); }); 4410 | this.grep(/.*/); 4411 | this.globals(this.globalProps().concat(extraGlobals())); 4412 | } 4413 | 4414 | /** 4415 | * Wrapper for setImmediate, process.nextTick, or browser polyfill. 4416 | * 4417 | * @param {Function} fn 4418 | * @api private 4419 | */ 4420 | 4421 | Runner.immediately = global.setImmediate || process.nextTick; 4422 | 4423 | /** 4424 | * Inherit from `EventEmitter.prototype`. 4425 | */ 4426 | 4427 | function F(){}; 4428 | F.prototype = EventEmitter.prototype; 4429 | Runner.prototype = new F; 4430 | Runner.prototype.constructor = Runner; 4431 | 4432 | 4433 | /** 4434 | * Run tests with full titles matching `re`. Updates runner.total 4435 | * with number of tests matched. 4436 | * 4437 | * @param {RegExp} re 4438 | * @param {Boolean} invert 4439 | * @return {Runner} for chaining 4440 | * @api public 4441 | */ 4442 | 4443 | Runner.prototype.grep = function(re, invert){ 4444 | debug('grep %s', re); 4445 | this._grep = re; 4446 | this._invert = invert; 4447 | this.total = this.grepTotal(this.suite); 4448 | return this; 4449 | }; 4450 | 4451 | /** 4452 | * Returns the number of tests matching the grep search for the 4453 | * given suite. 4454 | * 4455 | * @param {Suite} suite 4456 | * @return {Number} 4457 | * @api public 4458 | */ 4459 | 4460 | Runner.prototype.grepTotal = function(suite) { 4461 | var self = this; 4462 | var total = 0; 4463 | 4464 | suite.eachTest(function(test){ 4465 | var match = self._grep.test(test.fullTitle()); 4466 | if (self._invert) match = !match; 4467 | if (match) total++; 4468 | }); 4469 | 4470 | return total; 4471 | }; 4472 | 4473 | /** 4474 | * Return a list of global properties. 4475 | * 4476 | * @return {Array} 4477 | * @api private 4478 | */ 4479 | 4480 | Runner.prototype.globalProps = function() { 4481 | var props = utils.keys(global); 4482 | 4483 | // non-enumerables 4484 | for (var i = 0; i < globals.length; ++i) { 4485 | if (~utils.indexOf(props, globals[i])) continue; 4486 | props.push(globals[i]); 4487 | } 4488 | 4489 | return props; 4490 | }; 4491 | 4492 | /** 4493 | * Allow the given `arr` of globals. 4494 | * 4495 | * @param {Array} arr 4496 | * @return {Runner} for chaining 4497 | * @api public 4498 | */ 4499 | 4500 | Runner.prototype.globals = function(arr){ 4501 | if (0 == arguments.length) return this._globals; 4502 | debug('globals %j', arr); 4503 | this._globals = this._globals.concat(arr); 4504 | return this; 4505 | }; 4506 | 4507 | /** 4508 | * Check for global variable leaks. 4509 | * 4510 | * @api private 4511 | */ 4512 | 4513 | Runner.prototype.checkGlobals = function(test){ 4514 | if (this.ignoreLeaks) return; 4515 | var ok = this._globals; 4516 | 4517 | var globals = this.globalProps(); 4518 | var isNode = process.kill; 4519 | var leaks; 4520 | 4521 | if (test) { 4522 | ok = ok.concat(test._allowedGlobals || []); 4523 | } 4524 | 4525 | if(this.prevGlobalsLength == globals.length) return; 4526 | this.prevGlobalsLength = globals.length; 4527 | 4528 | leaks = filterLeaks(ok, globals); 4529 | this._globals = this._globals.concat(leaks); 4530 | 4531 | if (leaks.length > 1) { 4532 | this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); 4533 | } else if (leaks.length) { 4534 | this.fail(test, new Error('global leak detected: ' + leaks[0])); 4535 | } 4536 | }; 4537 | 4538 | /** 4539 | * Fail the given `test`. 4540 | * 4541 | * @param {Test} test 4542 | * @param {Error} err 4543 | * @api private 4544 | */ 4545 | 4546 | Runner.prototype.fail = function(test, err){ 4547 | ++this.failures; 4548 | test.state = 'failed'; 4549 | 4550 | if ('string' == typeof err) { 4551 | err = new Error('the string "' + err + '" was thrown, throw an Error :)'); 4552 | } 4553 | 4554 | this.emit('fail', test, err); 4555 | }; 4556 | 4557 | /** 4558 | * Fail the given `hook` with `err`. 4559 | * 4560 | * Hook failures work in the following pattern: 4561 | * - If bail, then exit 4562 | * - Failed `before` hook skips all tests in a suite and subsuites, 4563 | * but jumps to corresponding `after` hook 4564 | * - Failed `before each` hook skips remaining tests in a 4565 | * suite and jumps to corresponding `after each` hook, 4566 | * which is run only once 4567 | * - Failed `after` hook does not alter 4568 | * execution order 4569 | * - Failed `after each` hook skips remaining tests in a 4570 | * suite and subsuites, but executes other `after each` 4571 | * hooks 4572 | * 4573 | * @param {Hook} hook 4574 | * @param {Error} err 4575 | * @api private 4576 | */ 4577 | 4578 | Runner.prototype.failHook = function(hook, err){ 4579 | this.fail(hook, err); 4580 | if (this.suite.bail()) { 4581 | this.emit('end'); 4582 | } 4583 | }; 4584 | 4585 | /** 4586 | * Run hook `name` callbacks and then invoke `fn()`. 4587 | * 4588 | * @param {String} name 4589 | * @param {Function} function 4590 | * @api private 4591 | */ 4592 | 4593 | Runner.prototype.hook = function(name, fn){ 4594 | var suite = this.suite 4595 | , hooks = suite['_' + name] 4596 | , self = this 4597 | , timer; 4598 | 4599 | function next(i) { 4600 | var hook = hooks[i]; 4601 | if (!hook) return fn(); 4602 | if (self.failures && suite.bail()) return fn(); 4603 | self.currentRunnable = hook; 4604 | 4605 | hook.ctx.currentTest = self.test; 4606 | 4607 | self.emit('hook', hook); 4608 | 4609 | hook.on('error', function(err){ 4610 | self.failHook(hook, err); 4611 | }); 4612 | 4613 | hook.run(function(err){ 4614 | hook.removeAllListeners('error'); 4615 | var testError = hook.error(); 4616 | if (testError) self.fail(self.test, testError); 4617 | if (err) { 4618 | self.failHook(hook, err); 4619 | 4620 | // stop executing hooks, notify callee of hook err 4621 | return fn(err); 4622 | } 4623 | self.emit('hook end', hook); 4624 | delete hook.ctx.currentTest; 4625 | next(++i); 4626 | }); 4627 | } 4628 | 4629 | Runner.immediately(function(){ 4630 | next(0); 4631 | }); 4632 | }; 4633 | 4634 | /** 4635 | * Run hook `name` for the given array of `suites` 4636 | * in order, and callback `fn(err, errSuite)`. 4637 | * 4638 | * @param {String} name 4639 | * @param {Array} suites 4640 | * @param {Function} fn 4641 | * @api private 4642 | */ 4643 | 4644 | Runner.prototype.hooks = function(name, suites, fn){ 4645 | var self = this 4646 | , orig = this.suite; 4647 | 4648 | function next(suite) { 4649 | self.suite = suite; 4650 | 4651 | if (!suite) { 4652 | self.suite = orig; 4653 | return fn(); 4654 | } 4655 | 4656 | self.hook(name, function(err){ 4657 | if (err) { 4658 | var errSuite = self.suite; 4659 | self.suite = orig; 4660 | return fn(err, errSuite); 4661 | } 4662 | 4663 | next(suites.pop()); 4664 | }); 4665 | } 4666 | 4667 | next(suites.pop()); 4668 | }; 4669 | 4670 | /** 4671 | * Run hooks from the top level down. 4672 | * 4673 | * @param {String} name 4674 | * @param {Function} fn 4675 | * @api private 4676 | */ 4677 | 4678 | Runner.prototype.hookUp = function(name, fn){ 4679 | var suites = [this.suite].concat(this.parents()).reverse(); 4680 | this.hooks(name, suites, fn); 4681 | }; 4682 | 4683 | /** 4684 | * Run hooks from the bottom up. 4685 | * 4686 | * @param {String} name 4687 | * @param {Function} fn 4688 | * @api private 4689 | */ 4690 | 4691 | Runner.prototype.hookDown = function(name, fn){ 4692 | var suites = [this.suite].concat(this.parents()); 4693 | this.hooks(name, suites, fn); 4694 | }; 4695 | 4696 | /** 4697 | * Return an array of parent Suites from 4698 | * closest to furthest. 4699 | * 4700 | * @return {Array} 4701 | * @api private 4702 | */ 4703 | 4704 | Runner.prototype.parents = function(){ 4705 | var suite = this.suite 4706 | , suites = []; 4707 | while (suite = suite.parent) suites.push(suite); 4708 | return suites; 4709 | }; 4710 | 4711 | /** 4712 | * Run the current test and callback `fn(err)`. 4713 | * 4714 | * @param {Function} fn 4715 | * @api private 4716 | */ 4717 | 4718 | Runner.prototype.runTest = function(fn){ 4719 | var test = this.test 4720 | , self = this; 4721 | 4722 | if (this.asyncOnly) test.asyncOnly = true; 4723 | 4724 | try { 4725 | test.on('error', function(err){ 4726 | self.fail(test, err); 4727 | }); 4728 | test.run(fn); 4729 | } catch (err) { 4730 | fn(err); 4731 | } 4732 | }; 4733 | 4734 | /** 4735 | * Run tests in the given `suite` and invoke 4736 | * the callback `fn()` when complete. 4737 | * 4738 | * @param {Suite} suite 4739 | * @param {Function} fn 4740 | * @api private 4741 | */ 4742 | 4743 | Runner.prototype.runTests = function(suite, fn){ 4744 | var self = this 4745 | , tests = suite.tests.slice() 4746 | , test; 4747 | 4748 | 4749 | function hookErr(err, errSuite, after) { 4750 | // before/after Each hook for errSuite failed: 4751 | var orig = self.suite; 4752 | 4753 | // for failed 'after each' hook start from errSuite parent, 4754 | // otherwise start from errSuite itself 4755 | self.suite = after ? errSuite.parent : errSuite; 4756 | 4757 | if (self.suite) { 4758 | // call hookUp afterEach 4759 | self.hookUp('afterEach', function(err2, errSuite2) { 4760 | self.suite = orig; 4761 | // some hooks may fail even now 4762 | if (err2) return hookErr(err2, errSuite2, true); 4763 | // report error suite 4764 | fn(errSuite); 4765 | }); 4766 | } else { 4767 | // there is no need calling other 'after each' hooks 4768 | self.suite = orig; 4769 | fn(errSuite); 4770 | } 4771 | } 4772 | 4773 | function next(err, errSuite) { 4774 | // if we bail after first err 4775 | if (self.failures && suite._bail) return fn(); 4776 | 4777 | if (self._abort) return fn(); 4778 | 4779 | if (err) return hookErr(err, errSuite, true); 4780 | 4781 | // next test 4782 | test = tests.shift(); 4783 | 4784 | // all done 4785 | if (!test) return fn(); 4786 | 4787 | // grep 4788 | var match = self._grep.test(test.fullTitle()); 4789 | if (self._invert) match = !match; 4790 | if (!match) return next(); 4791 | 4792 | // pending 4793 | if (test.pending) { 4794 | self.emit('pending', test); 4795 | self.emit('test end', test); 4796 | return next(); 4797 | } 4798 | 4799 | // execute test and hook(s) 4800 | self.emit('test', self.test = test); 4801 | self.hookDown('beforeEach', function(err, errSuite){ 4802 | 4803 | if (err) return hookErr(err, errSuite, false); 4804 | 4805 | self.currentRunnable = self.test; 4806 | self.runTest(function(err){ 4807 | test = self.test; 4808 | 4809 | if (err) { 4810 | self.fail(test, err); 4811 | self.emit('test end', test); 4812 | return self.hookUp('afterEach', next); 4813 | } 4814 | 4815 | test.state = 'passed'; 4816 | self.emit('pass', test); 4817 | self.emit('test end', test); 4818 | self.hookUp('afterEach', next); 4819 | }); 4820 | }); 4821 | } 4822 | 4823 | this.next = next; 4824 | next(); 4825 | }; 4826 | 4827 | /** 4828 | * Run the given `suite` and invoke the 4829 | * callback `fn()` when complete. 4830 | * 4831 | * @param {Suite} suite 4832 | * @param {Function} fn 4833 | * @api private 4834 | */ 4835 | 4836 | Runner.prototype.runSuite = function(suite, fn){ 4837 | var total = this.grepTotal(suite) 4838 | , self = this 4839 | , i = 0; 4840 | 4841 | debug('run suite %s', suite.fullTitle()); 4842 | 4843 | if (!total) return fn(); 4844 | 4845 | this.emit('suite', this.suite = suite); 4846 | 4847 | function next(errSuite) { 4848 | if (errSuite) { 4849 | // current suite failed on a hook from errSuite 4850 | if (errSuite == suite) { 4851 | // if errSuite is current suite 4852 | // continue to the next sibling suite 4853 | return done(); 4854 | } else { 4855 | // errSuite is among the parents of current suite 4856 | // stop execution of errSuite and all sub-suites 4857 | return done(errSuite); 4858 | } 4859 | } 4860 | 4861 | if (self._abort) return done(); 4862 | 4863 | var curr = suite.suites[i++]; 4864 | if (!curr) return done(); 4865 | self.runSuite(curr, next); 4866 | } 4867 | 4868 | function done(errSuite) { 4869 | self.suite = suite; 4870 | self.hook('afterAll', function(){ 4871 | self.emit('suite end', suite); 4872 | fn(errSuite); 4873 | }); 4874 | } 4875 | 4876 | this.hook('beforeAll', function(err){ 4877 | if (err) return done(); 4878 | self.runTests(suite, next); 4879 | }); 4880 | }; 4881 | 4882 | /** 4883 | * Handle uncaught exceptions. 4884 | * 4885 | * @param {Error} err 4886 | * @api private 4887 | */ 4888 | 4889 | Runner.prototype.uncaught = function(err){ 4890 | debug('uncaught exception %s', err.message); 4891 | var runnable = this.currentRunnable; 4892 | if (!runnable || 'failed' == runnable.state) return; 4893 | runnable.clearTimeout(); 4894 | err.uncaught = true; 4895 | this.fail(runnable, err); 4896 | 4897 | // recover from test 4898 | if ('test' == runnable.type) { 4899 | this.emit('test end', runnable); 4900 | this.hookUp('afterEach', this.next); 4901 | return; 4902 | } 4903 | 4904 | // bail on hooks 4905 | this.emit('end'); 4906 | }; 4907 | 4908 | /** 4909 | * Run the root suite and invoke `fn(failures)` 4910 | * on completion. 4911 | * 4912 | * @param {Function} fn 4913 | * @return {Runner} for chaining 4914 | * @api public 4915 | */ 4916 | 4917 | Runner.prototype.run = function(fn){ 4918 | var self = this 4919 | , fn = fn || function(){}; 4920 | 4921 | function uncaught(err){ 4922 | self.uncaught(err); 4923 | } 4924 | 4925 | debug('start'); 4926 | 4927 | // callback 4928 | this.on('end', function(){ 4929 | debug('end'); 4930 | process.removeListener('uncaughtException', uncaught); 4931 | fn(self.failures); 4932 | }); 4933 | 4934 | // run suites 4935 | this.emit('start'); 4936 | this.runSuite(this.suite, function(){ 4937 | debug('finished running'); 4938 | self.emit('end'); 4939 | }); 4940 | 4941 | // uncaught exception 4942 | process.on('uncaughtException', uncaught); 4943 | 4944 | return this; 4945 | }; 4946 | 4947 | /** 4948 | * Cleanly abort execution 4949 | * 4950 | * @return {Runner} for chaining 4951 | * @api public 4952 | */ 4953 | Runner.prototype.abort = function(){ 4954 | debug('aborting'); 4955 | this._abort = true; 4956 | } 4957 | 4958 | /** 4959 | * Filter leaks with the given globals flagged as `ok`. 4960 | * 4961 | * @param {Array} ok 4962 | * @param {Array} globals 4963 | * @return {Array} 4964 | * @api private 4965 | */ 4966 | 4967 | function filterLeaks(ok, globals) { 4968 | return filter(globals, function(key){ 4969 | // Firefox and Chrome exposes iframes as index inside the window object 4970 | if (/^d+/.test(key)) return false; 4971 | 4972 | // in firefox 4973 | // if runner runs in an iframe, this iframe's window.getInterface method not init at first 4974 | // it is assigned in some seconds 4975 | if (global.navigator && /^getInterface/.test(key)) return false; 4976 | 4977 | // an iframe could be approached by window[iframeIndex] 4978 | // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak 4979 | if (global.navigator && /^\d+/.test(key)) return false; 4980 | 4981 | // Opera and IE expose global variables for HTML element IDs (issue #243) 4982 | if (/^mocha-/.test(key)) return false; 4983 | 4984 | var matched = filter(ok, function(ok){ 4985 | if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); 4986 | return key == ok; 4987 | }); 4988 | return matched.length == 0 && (!global.navigator || 'onerror' !== key); 4989 | }); 4990 | } 4991 | 4992 | /** 4993 | * Array of globals dependent on the environment. 4994 | * 4995 | * @return {Array} 4996 | * @api private 4997 | */ 4998 | 4999 | function extraGlobals() { 5000 | if (typeof(process) === 'object' && 5001 | typeof(process.version) === 'string') { 5002 | 5003 | var nodeVersion = process.version.split('.').reduce(function(a, v) { 5004 | return a << 8 | v; 5005 | }); 5006 | 5007 | // 'errno' was renamed to process._errno in v0.9.11. 5008 | 5009 | if (nodeVersion < 0x00090B) { 5010 | return ['errno']; 5011 | } 5012 | } 5013 | 5014 | return []; 5015 | } 5016 | 5017 | }); // module: runner.js 5018 | 5019 | require.register("suite.js", function(module, exports, require){ 5020 | 5021 | /** 5022 | * Module dependencies. 5023 | */ 5024 | 5025 | var EventEmitter = require('browser/events').EventEmitter 5026 | , debug = require('browser/debug')('mocha:suite') 5027 | , milliseconds = require('./ms') 5028 | , utils = require('./utils') 5029 | , Hook = require('./hook'); 5030 | 5031 | /** 5032 | * Expose `Suite`. 5033 | */ 5034 | 5035 | exports = module.exports = Suite; 5036 | 5037 | /** 5038 | * Create a new `Suite` with the given `title` 5039 | * and parent `Suite`. When a suite with the 5040 | * same title is already present, that suite 5041 | * is returned to provide nicer reporter 5042 | * and more flexible meta-testing. 5043 | * 5044 | * @param {Suite} parent 5045 | * @param {String} title 5046 | * @return {Suite} 5047 | * @api public 5048 | */ 5049 | 5050 | exports.create = function(parent, title){ 5051 | var suite = new Suite(title, parent.ctx); 5052 | suite.parent = parent; 5053 | if (parent.pending) suite.pending = true; 5054 | title = suite.fullTitle(); 5055 | parent.addSuite(suite); 5056 | return suite; 5057 | }; 5058 | 5059 | /** 5060 | * Initialize a new `Suite` with the given 5061 | * `title` and `ctx`. 5062 | * 5063 | * @param {String} title 5064 | * @param {Context} ctx 5065 | * @api private 5066 | */ 5067 | 5068 | function Suite(title, ctx) { 5069 | this.title = title; 5070 | this.ctx = ctx; 5071 | this.suites = []; 5072 | this.tests = []; 5073 | this.pending = false; 5074 | this._beforeEach = []; 5075 | this._beforeAll = []; 5076 | this._afterEach = []; 5077 | this._afterAll = []; 5078 | this.root = !title; 5079 | this._timeout = 2000; 5080 | this._slow = 75; 5081 | this._bail = false; 5082 | } 5083 | 5084 | /** 5085 | * Inherit from `EventEmitter.prototype`. 5086 | */ 5087 | 5088 | function F(){}; 5089 | F.prototype = EventEmitter.prototype; 5090 | Suite.prototype = new F; 5091 | Suite.prototype.constructor = Suite; 5092 | 5093 | 5094 | /** 5095 | * Return a clone of this `Suite`. 5096 | * 5097 | * @return {Suite} 5098 | * @api private 5099 | */ 5100 | 5101 | Suite.prototype.clone = function(){ 5102 | var suite = new Suite(this.title); 5103 | debug('clone'); 5104 | suite.ctx = this.ctx; 5105 | suite.timeout(this.timeout()); 5106 | suite.slow(this.slow()); 5107 | suite.bail(this.bail()); 5108 | return suite; 5109 | }; 5110 | 5111 | /** 5112 | * Set timeout `ms` or short-hand such as "2s". 5113 | * 5114 | * @param {Number|String} ms 5115 | * @return {Suite|Number} for chaining 5116 | * @api private 5117 | */ 5118 | 5119 | Suite.prototype.timeout = function(ms){ 5120 | if (0 == arguments.length) return this._timeout; 5121 | if ('string' == typeof ms) ms = milliseconds(ms); 5122 | debug('timeout %d', ms); 5123 | this._timeout = parseInt(ms, 10); 5124 | return this; 5125 | }; 5126 | 5127 | /** 5128 | * Set slow `ms` or short-hand such as "2s". 5129 | * 5130 | * @param {Number|String} ms 5131 | * @return {Suite|Number} for chaining 5132 | * @api private 5133 | */ 5134 | 5135 | Suite.prototype.slow = function(ms){ 5136 | if (0 === arguments.length) return this._slow; 5137 | if ('string' == typeof ms) ms = milliseconds(ms); 5138 | debug('slow %d', ms); 5139 | this._slow = ms; 5140 | return this; 5141 | }; 5142 | 5143 | /** 5144 | * Sets whether to bail after first error. 5145 | * 5146 | * @parma {Boolean} bail 5147 | * @return {Suite|Number} for chaining 5148 | * @api private 5149 | */ 5150 | 5151 | Suite.prototype.bail = function(bail){ 5152 | if (0 == arguments.length) return this._bail; 5153 | debug('bail %s', bail); 5154 | this._bail = bail; 5155 | return this; 5156 | }; 5157 | 5158 | /** 5159 | * Run `fn(test[, done])` before running tests. 5160 | * 5161 | * @param {Function} fn 5162 | * @return {Suite} for chaining 5163 | * @api private 5164 | */ 5165 | 5166 | Suite.prototype.beforeAll = function(title, fn){ 5167 | if (this.pending) return this; 5168 | if ('function' === typeof title) { 5169 | fn = title; 5170 | title = fn.name; 5171 | } 5172 | title = '"before all" hook' + (title ? ': ' + title : ''); 5173 | 5174 | var hook = new Hook(title, fn); 5175 | hook.parent = this; 5176 | hook.timeout(this.timeout()); 5177 | hook.slow(this.slow()); 5178 | hook.ctx = this.ctx; 5179 | this._beforeAll.push(hook); 5180 | this.emit('beforeAll', hook); 5181 | return this; 5182 | }; 5183 | 5184 | /** 5185 | * Run `fn(test[, done])` after running tests. 5186 | * 5187 | * @param {Function} fn 5188 | * @return {Suite} for chaining 5189 | * @api private 5190 | */ 5191 | 5192 | Suite.prototype.afterAll = function(title, fn){ 5193 | if (this.pending) return this; 5194 | if ('function' === typeof title) { 5195 | fn = title; 5196 | title = fn.name; 5197 | } 5198 | title = '"after all" hook' + (title ? ': ' + title : ''); 5199 | 5200 | var hook = new Hook(title, fn); 5201 | hook.parent = this; 5202 | hook.timeout(this.timeout()); 5203 | hook.slow(this.slow()); 5204 | hook.ctx = this.ctx; 5205 | this._afterAll.push(hook); 5206 | this.emit('afterAll', hook); 5207 | return this; 5208 | }; 5209 | 5210 | /** 5211 | * Run `fn(test[, done])` before each test case. 5212 | * 5213 | * @param {Function} fn 5214 | * @return {Suite} for chaining 5215 | * @api private 5216 | */ 5217 | 5218 | Suite.prototype.beforeEach = function(title, fn){ 5219 | if (this.pending) return this; 5220 | if ('function' === typeof title) { 5221 | fn = title; 5222 | title = fn.name; 5223 | } 5224 | title = '"before each" hook' + (title ? ': ' + title : ''); 5225 | 5226 | var hook = new Hook(title, fn); 5227 | hook.parent = this; 5228 | hook.timeout(this.timeout()); 5229 | hook.slow(this.slow()); 5230 | hook.ctx = this.ctx; 5231 | this._beforeEach.push(hook); 5232 | this.emit('beforeEach', hook); 5233 | return this; 5234 | }; 5235 | 5236 | /** 5237 | * Run `fn(test[, done])` after each test case. 5238 | * 5239 | * @param {Function} fn 5240 | * @return {Suite} for chaining 5241 | * @api private 5242 | */ 5243 | 5244 | Suite.prototype.afterEach = function(title, fn){ 5245 | if (this.pending) return this; 5246 | if ('function' === typeof title) { 5247 | fn = title; 5248 | title = fn.name; 5249 | } 5250 | title = '"after each" hook' + (title ? ': ' + title : ''); 5251 | 5252 | var hook = new Hook(title, fn); 5253 | hook.parent = this; 5254 | hook.timeout(this.timeout()); 5255 | hook.slow(this.slow()); 5256 | hook.ctx = this.ctx; 5257 | this._afterEach.push(hook); 5258 | this.emit('afterEach', hook); 5259 | return this; 5260 | }; 5261 | 5262 | /** 5263 | * Add a test `suite`. 5264 | * 5265 | * @param {Suite} suite 5266 | * @return {Suite} for chaining 5267 | * @api private 5268 | */ 5269 | 5270 | Suite.prototype.addSuite = function(suite){ 5271 | suite.parent = this; 5272 | suite.timeout(this.timeout()); 5273 | suite.slow(this.slow()); 5274 | suite.bail(this.bail()); 5275 | this.suites.push(suite); 5276 | this.emit('suite', suite); 5277 | return this; 5278 | }; 5279 | 5280 | /** 5281 | * Add a `test` to this suite. 5282 | * 5283 | * @param {Test} test 5284 | * @return {Suite} for chaining 5285 | * @api private 5286 | */ 5287 | 5288 | Suite.prototype.addTest = function(test){ 5289 | test.parent = this; 5290 | test.timeout(this.timeout()); 5291 | test.slow(this.slow()); 5292 | test.ctx = this.ctx; 5293 | this.tests.push(test); 5294 | this.emit('test', test); 5295 | return this; 5296 | }; 5297 | 5298 | /** 5299 | * Return the full title generated by recursively 5300 | * concatenating the parent's full title. 5301 | * 5302 | * @return {String} 5303 | * @api public 5304 | */ 5305 | 5306 | Suite.prototype.fullTitle = function(){ 5307 | if (this.parent) { 5308 | var full = this.parent.fullTitle(); 5309 | if (full) return full + ' ' + this.title; 5310 | } 5311 | return this.title; 5312 | }; 5313 | 5314 | /** 5315 | * Return the total number of tests. 5316 | * 5317 | * @return {Number} 5318 | * @api public 5319 | */ 5320 | 5321 | Suite.prototype.total = function(){ 5322 | return utils.reduce(this.suites, function(sum, suite){ 5323 | return sum + suite.total(); 5324 | }, 0) + this.tests.length; 5325 | }; 5326 | 5327 | /** 5328 | * Iterates through each suite recursively to find 5329 | * all tests. Applies a function in the format 5330 | * `fn(test)`. 5331 | * 5332 | * @param {Function} fn 5333 | * @return {Suite} 5334 | * @api private 5335 | */ 5336 | 5337 | Suite.prototype.eachTest = function(fn){ 5338 | utils.forEach(this.tests, fn); 5339 | utils.forEach(this.suites, function(suite){ 5340 | suite.eachTest(fn); 5341 | }); 5342 | return this; 5343 | }; 5344 | 5345 | }); // module: suite.js 5346 | 5347 | require.register("test.js", function(module, exports, require){ 5348 | 5349 | /** 5350 | * Module dependencies. 5351 | */ 5352 | 5353 | var Runnable = require('./runnable'); 5354 | 5355 | /** 5356 | * Expose `Test`. 5357 | */ 5358 | 5359 | module.exports = Test; 5360 | 5361 | /** 5362 | * Initialize a new `Test` with the given `title` and callback `fn`. 5363 | * 5364 | * @param {String} title 5365 | * @param {Function} fn 5366 | * @api private 5367 | */ 5368 | 5369 | function Test(title, fn) { 5370 | Runnable.call(this, title, fn); 5371 | this.pending = !fn; 5372 | this.type = 'test'; 5373 | } 5374 | 5375 | /** 5376 | * Inherit from `Runnable.prototype`. 5377 | */ 5378 | 5379 | function F(){}; 5380 | F.prototype = Runnable.prototype; 5381 | Test.prototype = new F; 5382 | Test.prototype.constructor = Test; 5383 | 5384 | 5385 | }); // module: test.js 5386 | 5387 | require.register("utils.js", function(module, exports, require){ 5388 | /** 5389 | * Module dependencies. 5390 | */ 5391 | 5392 | var fs = require('browser/fs') 5393 | , path = require('browser/path') 5394 | , join = path.join 5395 | , debug = require('browser/debug')('mocha:watch'); 5396 | 5397 | /** 5398 | * Ignored directories. 5399 | */ 5400 | 5401 | var ignore = ['node_modules', '.git']; 5402 | 5403 | /** 5404 | * Escape special characters in the given string of html. 5405 | * 5406 | * @param {String} html 5407 | * @return {String} 5408 | * @api private 5409 | */ 5410 | 5411 | exports.escape = function(html){ 5412 | return String(html) 5413 | .replace(/&/g, '&') 5414 | .replace(/"/g, '"') 5415 | .replace(//g, '>'); 5417 | }; 5418 | 5419 | /** 5420 | * Array#forEach (<=IE8) 5421 | * 5422 | * @param {Array} array 5423 | * @param {Function} fn 5424 | * @param {Object} scope 5425 | * @api private 5426 | */ 5427 | 5428 | exports.forEach = function(arr, fn, scope){ 5429 | for (var i = 0, l = arr.length; i < l; i++) 5430 | fn.call(scope, arr[i], i); 5431 | }; 5432 | 5433 | /** 5434 | * Array#map (<=IE8) 5435 | * 5436 | * @param {Array} array 5437 | * @param {Function} fn 5438 | * @param {Object} scope 5439 | * @api private 5440 | */ 5441 | 5442 | exports.map = function(arr, fn, scope){ 5443 | var result = []; 5444 | for (var i = 0, l = arr.length; i < l; i++) 5445 | result.push(fn.call(scope, arr[i], i)); 5446 | return result; 5447 | }; 5448 | 5449 | /** 5450 | * Array#indexOf (<=IE8) 5451 | * 5452 | * @parma {Array} arr 5453 | * @param {Object} obj to find index of 5454 | * @param {Number} start 5455 | * @api private 5456 | */ 5457 | 5458 | exports.indexOf = function(arr, obj, start){ 5459 | for (var i = start || 0, l = arr.length; i < l; i++) { 5460 | if (arr[i] === obj) 5461 | return i; 5462 | } 5463 | return -1; 5464 | }; 5465 | 5466 | /** 5467 | * Array#reduce (<=IE8) 5468 | * 5469 | * @param {Array} array 5470 | * @param {Function} fn 5471 | * @param {Object} initial value 5472 | * @api private 5473 | */ 5474 | 5475 | exports.reduce = function(arr, fn, val){ 5476 | var rval = val; 5477 | 5478 | for (var i = 0, l = arr.length; i < l; i++) { 5479 | rval = fn(rval, arr[i], i, arr); 5480 | } 5481 | 5482 | return rval; 5483 | }; 5484 | 5485 | /** 5486 | * Array#filter (<=IE8) 5487 | * 5488 | * @param {Array} array 5489 | * @param {Function} fn 5490 | * @api private 5491 | */ 5492 | 5493 | exports.filter = function(arr, fn){ 5494 | var ret = []; 5495 | 5496 | for (var i = 0, l = arr.length; i < l; i++) { 5497 | var val = arr[i]; 5498 | if (fn(val, i, arr)) ret.push(val); 5499 | } 5500 | 5501 | return ret; 5502 | }; 5503 | 5504 | /** 5505 | * Object.keys (<=IE8) 5506 | * 5507 | * @param {Object} obj 5508 | * @return {Array} keys 5509 | * @api private 5510 | */ 5511 | 5512 | exports.keys = Object.keys || function(obj) { 5513 | var keys = [] 5514 | , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 5515 | 5516 | for (var key in obj) { 5517 | if (has.call(obj, key)) { 5518 | keys.push(key); 5519 | } 5520 | } 5521 | 5522 | return keys; 5523 | }; 5524 | 5525 | /** 5526 | * Watch the given `files` for changes 5527 | * and invoke `fn(file)` on modification. 5528 | * 5529 | * @param {Array} files 5530 | * @param {Function} fn 5531 | * @api private 5532 | */ 5533 | 5534 | exports.watch = function(files, fn){ 5535 | var options = { interval: 100 }; 5536 | files.forEach(function(file){ 5537 | debug('file %s', file); 5538 | fs.watchFile(file, options, function(curr, prev){ 5539 | if (prev.mtime < curr.mtime) fn(file); 5540 | }); 5541 | }); 5542 | }; 5543 | 5544 | /** 5545 | * Ignored files. 5546 | */ 5547 | 5548 | function ignored(path){ 5549 | return !~ignore.indexOf(path); 5550 | } 5551 | 5552 | /** 5553 | * Lookup files in the given `dir`. 5554 | * 5555 | * @return {Array} 5556 | * @api private 5557 | */ 5558 | 5559 | exports.files = function(dir, ret){ 5560 | ret = ret || []; 5561 | 5562 | fs.readdirSync(dir) 5563 | .filter(ignored) 5564 | .forEach(function(path){ 5565 | path = join(dir, path); 5566 | if (fs.statSync(path).isDirectory()) { 5567 | exports.files(path, ret); 5568 | } else if (path.match(/\.(js|coffee|litcoffee|coffee.md)$/)) { 5569 | ret.push(path); 5570 | } 5571 | }); 5572 | 5573 | return ret; 5574 | }; 5575 | 5576 | /** 5577 | * Compute a slug from the given `str`. 5578 | * 5579 | * @param {String} str 5580 | * @return {String} 5581 | * @api private 5582 | */ 5583 | 5584 | exports.slug = function(str){ 5585 | return str 5586 | .toLowerCase() 5587 | .replace(/ +/g, '-') 5588 | .replace(/[^-\w]/g, ''); 5589 | }; 5590 | 5591 | /** 5592 | * Strip the function definition from `str`, 5593 | * and re-indent for pre whitespace. 5594 | */ 5595 | 5596 | exports.clean = function(str) { 5597 | str = str 5598 | .replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, '') 5599 | .replace(/^function *\(.*\) *{/, '') 5600 | .replace(/\s+\}$/, ''); 5601 | 5602 | var spaces = str.match(/^\n?( *)/)[1].length 5603 | , tabs = str.match(/^\n?(\t*)/)[1].length 5604 | , re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm'); 5605 | 5606 | str = str.replace(re, ''); 5607 | 5608 | return exports.trim(str); 5609 | }; 5610 | 5611 | /** 5612 | * Escape regular expression characters in `str`. 5613 | * 5614 | * @param {String} str 5615 | * @return {String} 5616 | * @api private 5617 | */ 5618 | 5619 | exports.escapeRegexp = function(str){ 5620 | return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); 5621 | }; 5622 | 5623 | /** 5624 | * Trim the given `str`. 5625 | * 5626 | * @param {String} str 5627 | * @return {String} 5628 | * @api private 5629 | */ 5630 | 5631 | exports.trim = function(str){ 5632 | return str.replace(/^\s+|\s+$/g, ''); 5633 | }; 5634 | 5635 | /** 5636 | * Parse the given `qs`. 5637 | * 5638 | * @param {String} qs 5639 | * @return {Object} 5640 | * @api private 5641 | */ 5642 | 5643 | exports.parseQuery = function(qs){ 5644 | return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ 5645 | var i = pair.indexOf('=') 5646 | , key = pair.slice(0, i) 5647 | , val = pair.slice(++i); 5648 | 5649 | obj[key] = decodeURIComponent(val); 5650 | return obj; 5651 | }, {}); 5652 | }; 5653 | 5654 | /** 5655 | * Highlight the given string of `js`. 5656 | * 5657 | * @param {String} js 5658 | * @return {String} 5659 | * @api private 5660 | */ 5661 | 5662 | function highlight(js) { 5663 | return js 5664 | .replace(//g, '>') 5666 | .replace(/\/\/(.*)/gm, '//$1') 5667 | .replace(/('.*?')/gm, '$1') 5668 | .replace(/(\d+\.\d+)/gm, '$1') 5669 | .replace(/(\d+)/gm, '$1') 5670 | .replace(/\bnew *(\w+)/gm, 'new $1') 5671 | .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') 5672 | } 5673 | 5674 | /** 5675 | * Highlight the contents of tag `name`. 5676 | * 5677 | * @param {String} name 5678 | * @api private 5679 | */ 5680 | 5681 | exports.highlightTags = function(name) { 5682 | var code = document.getElementsByTagName(name); 5683 | for (var i = 0, len = code.length; i < len; ++i) { 5684 | code[i].innerHTML = highlight(code[i].innerHTML); 5685 | } 5686 | }; 5687 | 5688 | }); // module: utils.js 5689 | // The global object is "self" in Web Workers. 5690 | global = (function() { return this; })(); 5691 | 5692 | /** 5693 | * Save timer references to avoid Sinon interfering (see GH-237). 5694 | */ 5695 | 5696 | var Date = global.Date; 5697 | var setTimeout = global.setTimeout; 5698 | var setInterval = global.setInterval; 5699 | var clearTimeout = global.clearTimeout; 5700 | var clearInterval = global.clearInterval; 5701 | 5702 | /** 5703 | * Node shims. 5704 | * 5705 | * These are meant only to allow 5706 | * mocha.js to run untouched, not 5707 | * to allow running node code in 5708 | * the browser. 5709 | */ 5710 | 5711 | var process = {}; 5712 | process.exit = function(status){}; 5713 | process.stdout = {}; 5714 | 5715 | var uncaughtExceptionHandlers = []; 5716 | 5717 | /** 5718 | * Remove uncaughtException listener. 5719 | */ 5720 | 5721 | process.removeListener = function(e, fn){ 5722 | if ('uncaughtException' == e) { 5723 | global.onerror = function() {}; 5724 | var i = Mocha.utils.indexOf(uncaughtExceptionHandlers, fn); 5725 | if (i != -1) { uncaughtExceptionHandlers.splice(i, 1); } 5726 | } 5727 | }; 5728 | 5729 | /** 5730 | * Implements uncaughtException listener. 5731 | */ 5732 | 5733 | process.on = function(e, fn){ 5734 | if ('uncaughtException' == e) { 5735 | global.onerror = function(err, url, line){ 5736 | fn(new Error(err + ' (' + url + ':' + line + ')')); 5737 | return true; 5738 | }; 5739 | uncaughtExceptionHandlers.push(fn); 5740 | } 5741 | }; 5742 | 5743 | /** 5744 | * Expose mocha. 5745 | */ 5746 | 5747 | var Mocha = global.Mocha = require('mocha'), 5748 | mocha = global.mocha = new Mocha({ reporter: 'html' }); 5749 | 5750 | // The BDD UI is registered by default, but no UI will be functional in the 5751 | // browser without an explicit call to the overridden `mocha.ui` (see below). 5752 | // Ensure that this default UI does not expose its methods to the global scope. 5753 | mocha.suite.removeAllListeners('pre-require'); 5754 | 5755 | var immediateQueue = [] 5756 | , immediateTimeout; 5757 | 5758 | function timeslice() { 5759 | var immediateStart = new Date().getTime(); 5760 | while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) { 5761 | immediateQueue.shift()(); 5762 | } 5763 | if (immediateQueue.length) { 5764 | immediateTimeout = setTimeout(timeslice, 0); 5765 | } else { 5766 | immediateTimeout = null; 5767 | } 5768 | } 5769 | 5770 | /** 5771 | * High-performance override of Runner.immediately. 5772 | */ 5773 | 5774 | Mocha.Runner.immediately = function(callback) { 5775 | immediateQueue.push(callback); 5776 | if (!immediateTimeout) { 5777 | immediateTimeout = setTimeout(timeslice, 0); 5778 | } 5779 | }; 5780 | 5781 | /** 5782 | * Function to allow assertion libraries to throw errors directly into mocha. 5783 | * This is useful when running tests in a browser because window.onerror will 5784 | * only receive the 'message' attribute of the Error. 5785 | */ 5786 | mocha.throwError = function(err) { 5787 | Mocha.utils.forEach(uncaughtExceptionHandlers, function (fn) { 5788 | fn(err); 5789 | }); 5790 | throw err; 5791 | }; 5792 | 5793 | /** 5794 | * Override ui to ensure that the ui functions are initialized. 5795 | * Normally this would happen in Mocha.prototype.loadFiles. 5796 | */ 5797 | 5798 | mocha.ui = function(ui){ 5799 | Mocha.prototype.ui.call(this, ui); 5800 | this.suite.emit('pre-require', global, null, this); 5801 | return this; 5802 | }; 5803 | 5804 | /** 5805 | * Setup mocha with the given setting options. 5806 | */ 5807 | 5808 | mocha.setup = function(opts){ 5809 | if ('string' == typeof opts) opts = { ui: opts }; 5810 | for (var opt in opts) this[opt](opts[opt]); 5811 | return this; 5812 | }; 5813 | 5814 | /** 5815 | * Run mocha, returning the Runner. 5816 | */ 5817 | 5818 | mocha.run = function(fn){ 5819 | var options = mocha.options; 5820 | mocha.globals('location'); 5821 | 5822 | var query = Mocha.utils.parseQuery(global.location.search || ''); 5823 | if (query.grep) mocha.grep(query.grep); 5824 | if (query.invert) mocha.invert(); 5825 | 5826 | return Mocha.prototype.run.call(mocha, function(){ 5827 | // The DOM Document is not available in Web Workers. 5828 | if (global.document) { 5829 | Mocha.utils.highlightTags('code'); 5830 | } 5831 | if (fn) fn(); 5832 | }); 5833 | }; 5834 | 5835 | /** 5836 | * Expose the process shim. 5837 | */ 5838 | 5839 | Mocha.process = process; 5840 | })(); -------------------------------------------------------------------------------- /checktime.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | 3 | fs.readFile(process.cwd() + "/times.txt", function (err, buffer) { 4 | 5 | var lines = buffer.toString().split("\n").slice(0,-1); 6 | var m = 0; 7 | var s = 0; 8 | 9 | lines.forEach(function (line) { 10 | 11 | var time = line.split("|").pop().split(":"); 12 | m += Number(time[0]); 13 | s += Number(time[1]); 14 | 15 | console.log(time); 16 | 17 | }); 18 | 19 | console.log(m, s); 20 | 21 | var minutes = m + Math.floor(s / 60); 22 | var seconds = s % 60; 23 | 24 | var average = (((m * 60) + s) / lines.length); 25 | average = Math.floor(average / 60) + ":" + average % 60; 26 | 27 | console.log("Total course time: %s:%s", minutes, seconds); 28 | console.log("Average length: %s", average); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "something": "doesn't matter", 3 | "palette":["#f933f9","#cc0033", "#a98488"] 4 | } 5 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var mocha = require("gulp-mocha"); 3 | 4 | gulp.task("default", function () { 5 | 6 | gulp.watch("./*.js", ["test"]); 7 | 8 | }); 9 | 10 | gulp.task("test", function () { 11 | 12 | gulp.src("./test/**/*.js") 13 | .pipe(mocha({ reporter: "spec" })); 14 | 15 | }); -------------------------------------------------------------------------------- /lib/getPalette.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | 3 | function getConfig(path) { 4 | return JSON.parse(fs.readFileSync(path).toString()); 5 | } 6 | 7 | module.exports = function (configPath) { 8 | 9 | configPath = configPath || process.cwd() + "/config.json"; 10 | var palette = getConfig(configPath).palette; 11 | 12 | if (!Array.isArray(palette)) { 13 | throw new Error("Palette is not an array"); 14 | } 15 | 16 | return palette; 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /lib/hex2rgb.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | convert: function (hex, callback) { 4 | 5 | setTimeout(function () { 6 | 7 | if (/^#/.test(hex)) { 8 | hex = hex.slice(1); 9 | } 10 | 11 | var invalid = this.isInvalid(hex); 12 | if (invalid) return callback(new Error(invalid.reason)); 13 | 14 | var values = this.arrayify(hex); 15 | callback(null, this.parse(values)); 16 | 17 | }.bind(this), 50); 18 | 19 | }, 20 | 21 | arrayify: function (hex) { 22 | 23 | var values = hex.split(""); 24 | 25 | if (values.length === 3) { 26 | values = [values[0], values[0], values[1], values[1], values[2], values[2]]; 27 | } 28 | 29 | return values; 30 | 31 | }, 32 | 33 | isInvalid: function (hex) { 34 | 35 | if (hex.length !== 3 && hex.length !== 6) { 36 | return { reason: "Invalid hexadecimal string" }; 37 | } 38 | 39 | }, 40 | 41 | parse: function (values) { 42 | 43 | var r = parseInt([values[0], values[1]].join(""), 16); 44 | var g = parseInt([values[4], values[5]].join(""), 16); 45 | var b = parseInt([values[2], values[3]].join(""), 16); 46 | 47 | return [r, g, b]; 48 | 49 | } 50 | 51 | }; 52 | 53 | -------------------------------------------------------------------------------- /outline.markdown: -------------------------------------------------------------------------------- 1 | Course Title: JavaScript Unit Testing with Mocha, Chai, and Sinon 2 | 3 | # Course Description 4 | 5 | In this course, you'll gain a basic understanding of the fundamentals of unit testing your JavaScript code using some popular testing tools. By the end of this course you should feel empowered and excited to start writing tests for all of your JavaScript code, whether it's for the server or the client. 6 | 7 | **Code Repo:** https://github.com/jasonrhodes/courses-mocha 8 | 9 | 10 | Getting Started 11 | =============== 12 | 13 | ## 01 Introduction 14 | 15 | ### Time: 00:04:12 16 | 17 | To get started, we'll walk through the project that we'll be using to demonstrate unit testing for the rest of the course. 18 | 19 | **Related Links:** 20 | 21 | * [Install Node.js](http://nodejs.org) 22 | * [Cygwin](http://www.cygwin.com/), a linux-style command prompt for Windows 23 | * [Express](http://expressjs.com/), the Node.js web server framework we use briefly 24 | * [TestlingCI](https://ci.testling.com/) _Note: there's a later video in this series that goes more into detail about this service._ 25 | 26 | 27 | ## 02 Writing Your First Test 28 | 29 | ### Time: 00:13:55 30 | 31 | Here we write our first mocha test using most of mocha's defaults. We'll also take a quick look at mocha interfaces and reporters so that we can control the input and output of our tests a little more. 32 | 33 | **Related Links:** 34 | 35 | * [nodemon](http://nodemon.io/) 36 | * [Mocha docs](http://visionmedia.github.io/mocha/) 37 | * [Node core assert module](http://nodejs.org/api/assert.html) 38 | 39 | 40 | ## 03 Building a Group of Tests (A "Test Suite") 41 | 42 | ### Time: 00:10:54 43 | 44 | As we add more tests, mocha gives us easy ways to organize them into groups using the 'describe' function. We'll also take a look at asserting that errors are thrown and the concept of "deep equals". 45 | 46 | **Related Links:** 47 | 48 | * [JavaScript "deep equals" explained](http://stackoverflow.com/a/1144249/171021) 49 | 50 | 51 | ## 04 Mocha on the Command Line 52 | 53 | ### Time: 00:05:59 54 | 55 | Mocha tests are usually run from the command line, so it's a good idea to stop here and go through a few command line options that mocha provides. You can also use npm to run your custom set of flags. 56 | 57 | **Related Links:** 58 | 59 | * [Running scripts using npm ](https://www.npmjs.org/doc/misc/npm-scripts.html) 60 | 61 | 62 | Fundamental Concepts 63 | ==================== 64 | 65 | ## 05 Dependency Injection 66 | 67 | ### Time: 00:04:54 68 | 69 | By passing in dependencies to functions, you can control the context of your tests. This video is a very basic introduction to this concept and how it applies to unit testing. 70 | 71 | 72 | ## 06 Asynchronous Testing 73 | 74 | ### Time: 00:05:41 75 | 76 | JavaScript encourages a lot of async programming concepts, so we need to make sure we can test async functions. In this video, I'll explain how mocha makes async testing incredibly easy. 77 | 78 | 79 | ## 07 Before and After Hooks 80 | 81 | ### Time: 00:12:52 82 | 83 | In this video, we'll look at how the before, after, beforeEach, and afterEach hooks allow you to repeat the same setup or cleanup actions before and after running a group of tests. 84 | 85 | _Note: The method of overwriting a file in this video is admittedly terrible, but demonstrates hooks well. We'll talk about why it's terrible and how to solve the problem better in later videos._ 86 | 87 | **Related Links:** 88 | 89 | * [The fs module](http://nodejs.org/api/fs.html) 90 | 91 | 92 | ## 08 Segmenting with Skip and Only 93 | 94 | ### Time: 00:03:16 95 | 96 | There are times when you want to skip over a set of tests, or only run a single test for a while. Skip and only are ways to segment your tests from within the test files without using comments or other hacks. 97 | 98 | 99 | ## 09 Fixtures 100 | 101 | ### Time: 00:05:49 102 | 103 | Here we'll look at how to avoid writing over files and instead use stored sample files for testing, called fixtures. 104 | 105 | 106 | Using Chai Assertions 107 | ===================== 108 | 109 | ## 10 Chai Assert 110 | 111 | ### Time: 00:02:20 112 | 113 | In this video I'll introduce the Chai assertion library and look at Chai's version of assert. 114 | 115 | **Related Links:** 116 | 117 | * [Chai assert docs](http://chaijs.com/api/assert/) 118 | 119 | 120 | ## 11 Chai Should 121 | 122 | ### Time: 00:06:41 123 | 124 | The Chai library has a few different assertion styles, and in this video we'll look at how to use the "should" style. 125 | 126 | **Related Links:** 127 | 128 | * [Chai bdd docs](http://chaijs.com/api/bdd/) 129 | 130 | 131 | ## 12 Chai Expect 132 | 133 | ### Time: 00:03:00 134 | 135 | In this last video about the Chai library, we'll look at how to use the "expect" assertion style. 136 | 137 | **Related Links:** 138 | 139 | * [Chai bdd docs](http://chaijs.com/api/bdd/) 140 | 141 | 142 | Putting it All Together 143 | ======================= 144 | 145 | ## 13 Running Mocha Tests in the Browser 146 | 147 | ### Time: 00:05:00 148 | 149 | If you're running tests on your client-side JavaScript, you'll want to be able to test them in browsers. In this video, we'll look at how to run your mocha tests in the browser manually. 150 | 151 | **Related Links:** 152 | 153 | * [Browserify](http://browserify.org/) 154 | 155 | 156 | ## 14 Automated Browser Testing with Testling 157 | 158 | ### Time: 00:09:18 159 | 160 | In this video, we'll look at how Testling will help you automate your client-side JavaScript tests, with command line tools and a git hook that will run your tests in multiple browsers every time you push to your GitHub repo. 161 | 162 | **Related Links:** 163 | 164 | * [Testling CI](https://ci.testling.com/) 165 | * [Saucelabs Automated Testing](https://saucelabs.com/) 166 | * [Karma - Spectacular Test Runner](http://karma-runner.github.io/0.12/index.html) 167 | 168 | 169 | ## 15 Continuous Integration with Travis CI 170 | 171 | ### Time: 00:05:02 172 | 173 | Now that we have tests set up for our project, we can take advantage of a continuous integration setup that will run our tests on a separate server when we push to our Git repo. In this video, I'll show you how to set your project up on Travis CI in less than 5 minutes. 174 | 175 | **Related Links:** 176 | 177 | * [Travis CI](https://travis-ci.org/) 178 | 179 | 180 | # More Control with SinonJS 181 | 182 | ## 16 Intro to Sinon 183 | 184 | ### Time: 00:03:30 185 | 186 | In this video we'll introduce Sinon and talk about how to add it to our project. In the next few videos, we'll run through some of the most powerful Sinon tools and show how they can help us write even better unit tests. 187 | 188 | **Related Links:** 189 | 190 | * [SinonJS](http://sinonjs.org/) 191 | 192 | 193 | ## 17 Sinon Spies 194 | 195 | ### Time: 00:06:00 196 | 197 | Spies are the simplest tool that Sinon offers, and in this video we'll look at how they give us a lot more control over the specifics of what we can test, including whether a method was ever run, how many times, etc. 198 | 199 | **Related Links:** 200 | 201 | * [SinonJS spies docs](http://sinonjs.org/docs/#spies) 202 | 203 | 204 | ## 18 Sinon Stubs 205 | 206 | ### Time: 00:04:35 207 | 208 | In this video, we'll build on the idea of test spies by adding return values to them, creating something called a "stub". 209 | 210 | **Related Links:** 211 | 212 | * [SinonJS stubs docs](http://sinonjs.org/docs/#stubs) 213 | 214 | 215 | ## 19 Sinon Mocks 216 | 217 | ### Time: 00:06:39 218 | 219 | In our last Sinon video, we'll look at mocks, which build on spies and stubs by adding in specific, pre-programmed expectations, giving you the most control over your method testing. 220 | 221 | **Related Links:** 222 | 223 | * [SinonJS mocks docs](http://sinonjs.org/docs/#mocks) 224 | * [SinonJS fake timers](http://sinonjs.org/docs/#clock) 225 | * [SinonJS fake server for faking XHR](http://sinonjs.org/docs/#server) 226 | 227 | 228 | # Conclusion 229 | 230 | ## 20 Go Write Some Tests 231 | 232 | ### Time: 00:01:32 233 | 234 | Hopefully you now have a basic understanding of unit testing in JavaScript and that you're encouraged to start writing tests. Happy testing! 235 | 236 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mochacourse", 3 | "version": "1.0.0", 4 | "description": "Example code and repo for my Nettuts.com course: JS Unit Testing with Mocha, Chai, and Sinon ", 5 | "main": "server.js", 6 | "dependencies": { 7 | "chai": "~1.9.1" 8 | }, 9 | "devDependencies": { 10 | "mocha": "~1.18.2", 11 | "chai": "^1.9.1", 12 | "sinon": "^1.9.1" 13 | }, 14 | "testling": { 15 | "harness": "mocha-bdd", 16 | "files": "test/*.js", 17 | "browsers": [ 18 | "ie/7..latest", 19 | "chrome/latest", 20 | "firefox/latest", 21 | "iphone/latest", 22 | "ipad/latest", 23 | "safari/latest" 24 | ] 25 | }, 26 | "scripts": { 27 | "test": "./node_modules/.bin/mocha -R list -b" 28 | }, 29 | "author": "Jason Rhodes", 30 | "license": "ISC" 31 | } 32 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var express = require("express"); 2 | var app = express(); 3 | var getPalette = require("./lib/getPalette"); 4 | 5 | app.set('view engine', 'jade'); 6 | 7 | app.get("/", function (req, res) { 8 | res.render("index", { palette: getPalette() }); 9 | }); 10 | 11 | app.listen(8000); 12 | -------------------------------------------------------------------------------- /test/fixtures/config-palette-non-array.json: -------------------------------------------------------------------------------- 1 | {"palette":24} 2 | -------------------------------------------------------------------------------- /test/getPalette.test.js: -------------------------------------------------------------------------------- 1 | var getPalette = require("../lib/getPalette"); 2 | var expect = require("chai").expect; 3 | 4 | describe("getPalette", function () { 5 | 6 | it("should throw an error if the result is not an array", function (done) { 7 | 8 | var notArray = function () { 9 | getPalette(process.cwd() + "/test/fixtures/config-palette-non-array.json"); 10 | }; 11 | 12 | expect(notArray).to.throw(/is not an array/); 13 | done(); 14 | 15 | }); 16 | 17 | it("should return an array with 3 items by default", function () { 18 | 19 | var palette = getPalette(); 20 | expect(palette).to.be.an("array").with.length(3); 21 | 22 | }); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /test/hex2rgb.test.js: -------------------------------------------------------------------------------- 1 | var hex2rgb = require("../lib/hex2rgb"); 2 | var expect = require("chai").expect; 3 | var sinon = require("sinon"); 4 | 5 | describe.only("hex2rgb", function () { 6 | 7 | describe("convert method", function () { 8 | 9 | it("should call parse once", function (done) { 10 | 11 | sinon.spy(hex2rgb, "parse"); 12 | 13 | hex2rgb.convert("#ffffff", function (err, result) { 14 | 15 | expect(hex2rgb.parse.calledOnce).to.be.true; 16 | expect(hex2rgb.parse.args[0][0]).to.have.length(6); 17 | 18 | hex2rgb.parse.restore(); 19 | done(); 20 | 21 | }); 22 | 23 | }); 24 | 25 | it("should always return the result of parse", function (done) { 26 | 27 | sinon.stub(hex2rgb, "parse").returns([0,900,100]); 28 | 29 | hex2rgb.convert("#abc", function (error, result) { 30 | 31 | expect(result).to.deep.equal([0,900,100]); 32 | 33 | hex2rgb.parse.restore(); 34 | done(); 35 | 36 | }); 37 | 38 | }); 39 | 40 | it("should always pass a 6 item array to parse", function (done) { 41 | 42 | var mock = sinon.mock(hex2rgb); 43 | mock.expects("parse").twice().withExactArgs("000000".split('')); 44 | 45 | hex2rgb.convert("#000000", function (error, result) { 46 | 47 | hex2rgb.convert("#000", function (error, result) { 48 | 49 | mock.verify(); 50 | done(); 51 | 52 | }); 53 | 54 | }); 55 | 56 | }); 57 | 58 | it("should return an error if the value is not a hex code", function (done) { 59 | 60 | hex2rgb.convert("blue", function (error, result) { 61 | expect(error).to.exist; 62 | done(); 63 | }); 64 | 65 | }); 66 | 67 | it("should return a correctly converted rgb value", function (done) { 68 | 69 | hex2rgb.convert("#fff", function (error, result) { 70 | 71 | expect(error).to.not.exist; 72 | expect(result).to.deep.equal([255, 255, 255]); 73 | 74 | done(); 75 | }); 76 | 77 | }); 78 | 79 | }); 80 | 81 | }); 82 | -------------------------------------------------------------------------------- /times.txt: -------------------------------------------------------------------------------- 1 | 01-getting-started|4:12 2 | 02-first-test|14:6 3 | 03-build-a-test-suite|11:4 4 | 04-mocha-cli|5:59 5 | 05-di|4:54 6 | 06-async-tests|5:57 7 | 07-hooks|12:58 8 | 08-segmenting|3:24 9 | 09-chai-assert|2:20 10 | 10-chai-should|6:41 11 | 11-chai-expect|3:0 12 | 12-fixtures|5:49 13 | 13-browser|5:3 14 | 14-testling|9:18 15 | 15-travisci|5:2 16 | 16-sinon-intro|3:30 17 | 17-sinon-spy|6:0 18 | 18-sinon-stub|4:35 19 | 19-sinon-mock|6:39 20 | 20-goodbye|1:32 21 | -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang=en) 3 | head 4 | title=title 5 | 6 | body 7 | h1="Color Palette" 8 | each color in palette 9 | div.color(style="background-color: " + color + "; height: 100px; margin-bottom: 10px;") 10 | p=color 11 | --------------------------------------------------------------------------------