source: Dev/trunk/node_modules/mocha/lib/reporters/base.js

Last change on this file was 484, checked in by hendrikvanantwerpen, 11 years ago

Commit node_modules, to make checkouts and builds more deterministic.

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