├── .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 | [](https://travis-ci.org/jasonrhodes/courses-mocha)
15 |
16 | [](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: 0 s '
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(' ', 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 + '' + name + end;
4098 | return tag;
4099 | }
4100 |
4101 | /**
4102 | * Return cdata escaped CDATA `str`.
4103 | */
4104 |
4105 | function cdata(str) {
4106 | return '';
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, '')
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 |
--------------------------------------------------------------------------------