123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- /**
- * Module dependencies.
- */
- var tty = require('tty')
- , diff = require('diff')
- , ms = require('../ms');
- /**
- * Save timer references to avoid Sinon interfering (see GH-237).
- */
- var Date = global.Date
- , setTimeout = global.setTimeout
- , setInterval = global.setInterval
- , clearTimeout = global.clearTimeout
- , clearInterval = global.clearInterval;
- /**
- * Check if both stdio streams are associated with a tty.
- */
- var isatty = tty.isatty(1) && tty.isatty(2);
- /**
- * Expose `Base`.
- */
- exports = module.exports = Base;
- /**
- * Enable coloring by default.
- */
- exports.useColors = isatty;
- /**
- * Default color map.
- */
- exports.colors = {
- 'pass': 90
- , 'fail': 31
- , 'bright pass': 92
- , 'bright fail': 91
- , 'bright yellow': 93
- , 'pending': 36
- , 'suite': 0
- , 'error title': 0
- , 'error message': 31
- , 'error stack': 90
- , 'checkmark': 32
- , 'fast': 90
- , 'medium': 33
- , 'slow': 31
- , 'green': 32
- , 'light': 90
- , 'diff gutter': 90
- , 'diff added': 42
- , 'diff removed': 41
- };
- /**
- * Default symbol map.
- */
- exports.symbols = {
- ok: '✓',
- err: '✖',
- dot: '․'
- };
- // With node.js on Windows: use symbols available in terminal default fonts
- if ('win32' == process.platform) {
- exports.symbols.ok = '\u221A';
- exports.symbols.err = '\u00D7';
- exports.symbols.dot = '.';
- }
- /**
- * Color `str` with the given `type`,
- * allowing colors to be disabled,
- * as well as user-defined color
- * schemes.
- *
- * @param {String} type
- * @param {String} str
- * @return {String}
- * @api private
- */
- var color = exports.color = function(type, str) {
- if (!exports.useColors) return str;
- return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m';
- };
- /**
- * Expose term window size, with some
- * defaults for when stderr is not a tty.
- */
- exports.window = {
- width: isatty
- ? process.stdout.getWindowSize
- ? process.stdout.getWindowSize(1)[0]
- : tty.getWindowSize()[1]
- : 75
- };
- /**
- * Expose some basic cursor interactions
- * that are common among reporters.
- */
- exports.cursor = {
- hide: function(){
- process.stdout.write('\u001b[?25l');
- },
- show: function(){
- process.stdout.write('\u001b[?25h');
- },
- deleteLine: function(){
- process.stdout.write('\u001b[2K');
- },
- beginningOfLine: function(){
- process.stdout.write('\u001b[0G');
- },
- CR: function(){
- exports.cursor.deleteLine();
- exports.cursor.beginningOfLine();
- }
- };
- /**
- * Outut the given `failures` as a list.
- *
- * @param {Array} failures
- * @api public
- */
- exports.list = function(failures){
- console.error();
- failures.forEach(function(test, i){
- // format
- var fmt = color('error title', ' %s) %s:\n')
- + color('error message', ' %s')
- + color('error stack', '\n%s\n');
- // msg
- var err = test.err
- , message = err.message || ''
- , stack = err.stack || message
- , index = stack.indexOf(message) + message.length
- , msg = stack.slice(0, index)
- , actual = err.actual
- , expected = err.expected
- , escape = true;
- // uncaught
- if (err.uncaught) {
- msg = 'Uncaught ' + msg;
- }
- // explicitly show diff
- if (err.showDiff && sameType(actual, expected)) {
- escape = false;
- err.actual = actual = stringify(actual);
- err.expected = expected = stringify(expected);
- }
- // actual / expected diff
- if ('string' == typeof actual && 'string' == typeof expected) {
- msg = errorDiff(err, 'Words', escape);
- // linenos
- var lines = msg.split('\n');
- if (lines.length > 4) {
- var width = String(lines.length).length;
- msg = lines.map(function(str, i){
- return pad(++i, width) + ' |' + ' ' + str;
- }).join('\n');
- }
- // legend
- msg = '\n'
- + color('diff removed', 'actual')
- + ' '
- + color('diff added', 'expected')
- + '\n\n'
- + msg
- + '\n';
- // indent
- msg = msg.replace(/^/gm, ' ');
- fmt = color('error title', ' %s) %s:\n%s')
- + color('error stack', '\n%s\n');
- }
- // indent stack trace without msg
- stack = stack.slice(index ? index + 1 : index)
- .replace(/^/gm, ' ');
- console.error(fmt, (i + 1), test.fullTitle(), msg, stack);
- });
- };
- /**
- * Initialize a new `Base` reporter.
- *
- * All other reporters generally
- * inherit from this reporter, providing
- * stats such as test duration, number
- * of tests passed / failed etc.
- *
- * @param {Runner} runner
- * @api public
- */
- function Base(runner) {
- var self = this
- , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 }
- , failures = this.failures = [];
- if (!runner) return;
- this.runner = runner;
- runner.stats = stats;
- runner.on('start', function(){
- stats.start = new Date;
- });
- runner.on('suite', function(suite){
- stats.suites = stats.suites || 0;
- suite.root || stats.suites++;
- });
- runner.on('test end', function(test){
- stats.tests = stats.tests || 0;
- stats.tests++;
- });
- runner.on('pass', function(test){
- stats.passes = stats.passes || 0;
- var medium = test.slow() / 2;
- test.speed = test.duration > test.slow()
- ? 'slow'
- : test.duration > medium
- ? 'medium'
- : 'fast';
- stats.passes++;
- });
- runner.on('fail', function(test, err){
- stats.failures = stats.failures || 0;
- stats.failures++;
- test.err = err;
- failures.push(test);
- });
- runner.on('end', function(){
- stats.end = new Date;
- stats.duration = new Date - stats.start;
- });
- runner.on('pending', function(){
- stats.pending++;
- });
- }
- /**
- * Output common epilogue used by many of
- * the bundled reporters.
- *
- * @api public
- */
- Base.prototype.epilogue = function(){
- var stats = this.stats;
- var tests;
- var fmt;
- console.log();
- // passes
- fmt = color('bright pass', ' ')
- + color('green', ' %d passing')
- + color('light', ' (%s)');
- console.log(fmt,
- stats.passes || 0,
- ms(stats.duration));
- // pending
- if (stats.pending) {
- fmt = color('pending', ' ')
- + color('pending', ' %d pending');
- console.log(fmt, stats.pending);
- }
- // failures
- if (stats.failures) {
- fmt = color('fail', ' %d failing');
- console.error(fmt,
- stats.failures);
- Base.list(this.failures);
- console.error();
- }
- console.log();
- };
- /**
- * Pad the given `str` to `len`.
- *
- * @param {String} str
- * @param {String} len
- * @return {String}
- * @api private
- */
- function pad(str, len) {
- str = String(str);
- return Array(len - str.length + 1).join(' ') + str;
- }
- /**
- * Return a character diff for `err`.
- *
- * @param {Error} err
- * @return {String}
- * @api private
- */
- function errorDiff(err, type, escape) {
- return diff['diff' + type](err.actual, err.expected).map(function(str){
- if (escape) {
- str.value = str.value
- .replace(/\t/g, '<tab>')
- .replace(/\r/g, '<CR>')
- .replace(/\n/g, '<LF>\n');
- }
- if (str.added) return colorLines('diff added', str.value);
- if (str.removed) return colorLines('diff removed', str.value);
- return str.value;
- }).join('');
- }
- /**
- * Color lines for `str`, using the color `name`.
- *
- * @param {String} name
- * @param {String} str
- * @return {String}
- * @api private
- */
- function colorLines(name, str) {
- return str.split('\n').map(function(str){
- return color(name, str);
- }).join('\n');
- }
- /**
- * Stringify `obj`.
- *
- * @param {Mixed} obj
- * @return {String}
- * @api private
- */
- function stringify(obj) {
- if (obj instanceof RegExp) return obj.toString();
- return JSON.stringify(obj, null, 2);
- }
- /**
- * Check that a / b have the same type.
- *
- * @param {Object} a
- * @param {Object} b
- * @return {Boolean}
- * @api private
- */
- function sameType(a, b) {
- a = Object.prototype.toString.call(a);
- b = Object.prototype.toString.call(b);
- return a == b;
- }
|