UNPKG

11.1 kBJavaScriptView Raw
1'use strict';
2/**
3 * @module Base
4 */
5/**
6 * Module dependencies.
7 */
8
9var tty = require('tty');
10var diff = require('diff');
11var milliseconds = require('ms');
12var utils = require('../utils');
13var supportsColor = process.browser ? null : require('supports-color');
14var constants = require('../runner').constants;
15var EVENT_TEST_PASS = constants.EVENT_TEST_PASS;
16var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL;
17
18/**
19 * Expose `Base`.
20 */
21
22exports = module.exports = Base;
23
24/**
25 * Check if both stdio streams are associated with a tty.
26 */
27
28var isatty = process.stdout.isTTY && process.stderr.isTTY;
29
30/**
31 * Save log references to avoid tests interfering (see GH-3604).
32 */
33var consoleLog = console.log;
34
35/**
36 * Enable coloring by default, except in the browser interface.
37 */
38
39exports.useColors =
40 !process.browser &&
41 (supportsColor.stdout || process.env.MOCHA_COLORS !== undefined);
42
43/**
44 * Inline diffs instead of +/-
45 */
46
47exports.inlineDiffs = false;
48
49/**
50 * Default color map.
51 */
52
53exports.colors = {
54 pass: 90,
55 fail: 31,
56 'bright pass': 92,
57 'bright fail': 91,
58 'bright yellow': 93,
59 pending: 36,
60 suite: 0,
61 'error title': 0,
62 'error message': 31,
63 'error stack': 90,
64 checkmark: 32,
65 fast: 90,
66 medium: 33,
67 slow: 31,
68 green: 32,
69 light: 90,
70 'diff gutter': 90,
71 'diff added': 32,
72 'diff removed': 31
73};
74
75/**
76 * Default symbol map.
77 */
78
79exports.symbols = {
80 ok: '✓',
81 err: '✖',
82 dot: '․',
83 comma: ',',
84 bang: '!'
85};
86
87// With node.js on Windows: use symbols available in terminal default fonts
88if (process.platform === 'win32') {
89 exports.symbols.ok = '\u221A';
90 exports.symbols.err = '\u00D7';
91 exports.symbols.dot = '.';
92}
93
94/**
95 * Color `str` with the given `type`,
96 * allowing colors to be disabled,
97 * as well as user-defined color
98 * schemes.
99 *
100 * @private
101 * @param {string} type
102 * @param {string} str
103 * @return {string}
104 */
105var color = (exports.color = function(type, str) {
106 if (!exports.useColors) {
107 return String(str);
108 }
109 return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m';
110});
111
112/**
113 * Expose term window size, with some defaults for when stderr is not a tty.
114 */
115
116exports.window = {
117 width: 75
118};
119
120if (isatty) {
121 exports.window.width = process.stdout.getWindowSize
122 ? process.stdout.getWindowSize(1)[0]
123 : tty.getWindowSize()[1];
124}
125
126/**
127 * Expose some basic cursor interactions that are common among reporters.
128 */
129
130exports.cursor = {
131 hide: function() {
132 isatty && process.stdout.write('\u001b[?25l');
133 },
134
135 show: function() {
136 isatty && process.stdout.write('\u001b[?25h');
137 },
138
139 deleteLine: function() {
140 isatty && process.stdout.write('\u001b[2K');
141 },
142
143 beginningOfLine: function() {
144 isatty && process.stdout.write('\u001b[0G');
145 },
146
147 CR: function() {
148 if (isatty) {
149 exports.cursor.deleteLine();
150 exports.cursor.beginningOfLine();
151 } else {
152 process.stdout.write('\r');
153 }
154 }
155};
156
157var showDiff = (exports.showDiff = function(err) {
158 return (
159 err &&
160 err.showDiff !== false &&
161 sameType(err.actual, err.expected) &&
162 err.expected !== undefined
163 );
164});
165
166function stringifyDiffObjs(err) {
167 if (!utils.isString(err.actual) || !utils.isString(err.expected)) {
168 err.actual = utils.stringify(err.actual);
169 err.expected = utils.stringify(err.expected);
170 }
171}
172
173/**
174 * Returns a diff between 2 strings with coloured ANSI output.
175 *
176 * @description
177 * The diff will be either inline or unified dependent on the value
178 * of `Base.inlineDiff`.
179 *
180 * @param {string} actual
181 * @param {string} expected
182 * @return {string} Diff
183 */
184var generateDiff = (exports.generateDiff = function(actual, expected) {
185 try {
186 return exports.inlineDiffs
187 ? inlineDiff(actual, expected)
188 : unifiedDiff(actual, expected);
189 } catch (err) {
190 var msg =
191 '\n ' +
192 color('diff added', '+ expected') +
193 ' ' +
194 color('diff removed', '- actual: failed to generate Mocha diff') +
195 '\n';
196 return msg;
197 }
198});
199
200/**
201 * Outputs the given `failures` as a list.
202 *
203 * @public
204 * @memberof Mocha.reporters.Base
205 * @variation 1
206 * @param {Object[]} failures - Each is Test instance with corresponding
207 * Error property
208 */
209exports.list = function(failures) {
210 var multipleErr, multipleTest;
211 Base.consoleLog();
212 failures.forEach(function(test, i) {
213 // format
214 var fmt =
215 color('error title', ' %s) %s:\n') +
216 color('error message', ' %s') +
217 color('error stack', '\n%s\n');
218
219 // msg
220 var msg;
221 var err;
222 if (test.err && test.err.multiple) {
223 if (multipleTest !== test) {
224 multipleTest = test;
225 multipleErr = [test.err].concat(test.err.multiple);
226 }
227 err = multipleErr.shift();
228 } else {
229 err = test.err;
230 }
231 var message;
232 if (err.message && typeof err.message.toString === 'function') {
233 message = err.message + '';
234 } else if (typeof err.inspect === 'function') {
235 message = err.inspect() + '';
236 } else {
237 message = '';
238 }
239 var stack = err.stack || message;
240 var index = message ? stack.indexOf(message) : -1;
241
242 if (index === -1) {
243 msg = message;
244 } else {
245 index += message.length;
246 msg = stack.slice(0, index);
247 // remove msg from stack
248 stack = stack.slice(index + 1);
249 }
250
251 // uncaught
252 if (err.uncaught) {
253 msg = 'Uncaught ' + msg;
254 }
255 // explicitly show diff
256 if (!exports.hideDiff && showDiff(err)) {
257 stringifyDiffObjs(err);
258 fmt =
259 color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n');
260 var match = message.match(/^([^:]+): expected/);
261 msg = '\n ' + color('error message', match ? match[1] : msg);
262
263 msg += generateDiff(err.actual, err.expected);
264 }
265
266 // indent stack trace
267 stack = stack.replace(/^/gm, ' ');
268
269 // indented test title
270 var testTitle = '';
271 test.titlePath().forEach(function(str, index) {
272 if (index !== 0) {
273 testTitle += '\n ';
274 }
275 for (var i = 0; i < index; i++) {
276 testTitle += ' ';
277 }
278 testTitle += str;
279 });
280
281 Base.consoleLog(fmt, i + 1, testTitle, msg, stack);
282 });
283};
284
285/**
286 * Constructs a new `Base` reporter instance.
287 *
288 * @description
289 * All other reporters generally inherit from this reporter.
290 *
291 * @public
292 * @class
293 * @memberof Mocha.reporters
294 * @param {Runner} runner - Instance triggers reporter actions.
295 * @param {Object} [options] - runner options
296 */
297function Base(runner, options) {
298 var failures = (this.failures = []);
299
300 if (!runner) {
301 throw new TypeError('Missing runner argument');
302 }
303 this.options = options || {};
304 this.runner = runner;
305 this.stats = runner.stats; // assigned so Reporters keep a closer reference
306
307 runner.on(EVENT_TEST_PASS, function(test) {
308 if (test.duration > test.slow()) {
309 test.speed = 'slow';
310 } else if (test.duration > test.slow() / 2) {
311 test.speed = 'medium';
312 } else {
313 test.speed = 'fast';
314 }
315 });
316
317 runner.on(EVENT_TEST_FAIL, function(test, err) {
318 if (showDiff(err)) {
319 stringifyDiffObjs(err);
320 }
321 // more than one error per test
322 if (test.err && err instanceof Error) {
323 test.err.multiple = (test.err.multiple || []).concat(err);
324 } else {
325 test.err = err;
326 }
327 failures.push(test);
328 });
329}
330
331/**
332 * Outputs common epilogue used by many of the bundled reporters.
333 *
334 * @public
335 * @memberof Mocha.reporters
336 */
337Base.prototype.epilogue = function() {
338 var stats = this.stats;
339 var fmt;
340
341 Base.consoleLog();
342
343 // passes
344 fmt =
345 color('bright pass', ' ') +
346 color('green', ' %d passing') +
347 color('light', ' (%s)');
348
349 Base.consoleLog(fmt, stats.passes || 0, milliseconds(stats.duration));
350
351 // pending
352 if (stats.pending) {
353 fmt = color('pending', ' ') + color('pending', ' %d pending');
354
355 Base.consoleLog(fmt, stats.pending);
356 }
357
358 // failures
359 if (stats.failures) {
360 fmt = color('fail', ' %d failing');
361
362 Base.consoleLog(fmt, stats.failures);
363
364 Base.list(this.failures);
365 Base.consoleLog();
366 }
367
368 Base.consoleLog();
369};
370
371/**
372 * Pads the given `str` to `len`.
373 *
374 * @private
375 * @param {string} str
376 * @param {string} len
377 * @return {string}
378 */
379function pad(str, len) {
380 str = String(str);
381 return Array(len - str.length + 1).join(' ') + str;
382}
383
384/**
385 * Returns inline diff between 2 strings with coloured ANSI output.
386 *
387 * @private
388 * @param {String} actual
389 * @param {String} expected
390 * @return {string} Diff
391 */
392function inlineDiff(actual, expected) {
393 var msg = errorDiff(actual, expected);
394
395 // linenos
396 var lines = msg.split('\n');
397 if (lines.length > 4) {
398 var width = String(lines.length).length;
399 msg = lines
400 .map(function(str, i) {
401 return pad(++i, width) + ' |' + ' ' + str;
402 })
403 .join('\n');
404 }
405
406 // legend
407 msg =
408 '\n' +
409 color('diff removed', 'actual') +
410 ' ' +
411 color('diff added', 'expected') +
412 '\n\n' +
413 msg +
414 '\n';
415
416 // indent
417 msg = msg.replace(/^/gm, ' ');
418 return msg;
419}
420
421/**
422 * Returns unified diff between two strings with coloured ANSI output.
423 *
424 * @private
425 * @param {String} actual
426 * @param {String} expected
427 * @return {string} The diff.
428 */
429function unifiedDiff(actual, expected) {
430 var indent = ' ';
431 function cleanUp(line) {
432 if (line[0] === '+') {
433 return indent + colorLines('diff added', line);
434 }
435 if (line[0] === '-') {
436 return indent + colorLines('diff removed', line);
437 }
438 if (line.match(/@@/)) {
439 return '--';
440 }
441 if (line.match(/\\ No newline/)) {
442 return null;
443 }
444 return indent + line;
445 }
446 function notBlank(line) {
447 return typeof line !== 'undefined' && line !== null;
448 }
449 var msg = diff.createPatch('string', actual, expected);
450 var lines = msg.split('\n').splice(5);
451 return (
452 '\n ' +
453 colorLines('diff added', '+ expected') +
454 ' ' +
455 colorLines('diff removed', '- actual') +
456 '\n\n' +
457 lines
458 .map(cleanUp)
459 .filter(notBlank)
460 .join('\n')
461 );
462}
463
464/**
465 * Returns character diff for `err`.
466 *
467 * @private
468 * @param {String} actual
469 * @param {String} expected
470 * @return {string} the diff
471 */
472function errorDiff(actual, expected) {
473 return diff
474 .diffWordsWithSpace(actual, expected)
475 .map(function(str) {
476 if (str.added) {
477 return colorLines('diff added', str.value);
478 }
479 if (str.removed) {
480 return colorLines('diff removed', str.value);
481 }
482 return str.value;
483 })
484 .join('');
485}
486
487/**
488 * Colors lines for `str`, using the color `name`.
489 *
490 * @private
491 * @param {string} name
492 * @param {string} str
493 * @return {string}
494 */
495function colorLines(name, str) {
496 return str
497 .split('\n')
498 .map(function(str) {
499 return color(name, str);
500 })
501 .join('\n');
502}
503
504/**
505 * Object#toString reference.
506 */
507var objToString = Object.prototype.toString;
508
509/**
510 * Checks that a / b have the same type.
511 *
512 * @private
513 * @param {Object} a
514 * @param {Object} b
515 * @return {boolean}
516 */
517function sameType(a, b) {
518 return objToString.call(a) === objToString.call(b);
519}
520
521Base.consoleLog = consoleLog;
522
523Base.abstract = true;