465 lines
9.5 KiB
JavaScript
465 lines
9.5 KiB
JavaScript
|
/**
|
|||
|
* Module dependencies.
|
|||
|
*/
|
|||
|
|
|||
|
var tty = require('tty')
|
|||
|
, diff = require('diff')
|
|||
|
, ms = require('../ms')
|
|||
|
, utils = require('../utils')
|
|||
|
, supportsColor = process.env ? require('supports-color') : null;
|
|||
|
|
|||
|
/**
|
|||
|
* 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, except in the browser interface.
|
|||
|
*/
|
|||
|
|
|||
|
exports.useColors = process.env
|
|||
|
? (supportsColor || (process.env.MOCHA_COLORS !== undefined))
|
|||
|
: false;
|
|||
|
|
|||
|
/**
|
|||
|
* Inline diffs instead of +/-
|
|||
|
*/
|
|||
|
|
|||
|
exports.inlineDiffs = false;
|
|||
|
|
|||
|
/**
|
|||
|
* 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 String(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(){
|
|||
|
isatty && process.stdout.write('\u001b[?25l');
|
|||
|
},
|
|||
|
|
|||
|
show: function(){
|
|||
|
isatty && process.stdout.write('\u001b[?25h');
|
|||
|
},
|
|||
|
|
|||
|
deleteLine: function(){
|
|||
|
isatty && process.stdout.write('\u001b[2K');
|
|||
|
},
|
|||
|
|
|||
|
beginningOfLine: function(){
|
|||
|
isatty && process.stdout.write('\u001b[0G');
|
|||
|
},
|
|||
|
|
|||
|
CR: function(){
|
|||
|
if (isatty) {
|
|||
|
exports.cursor.deleteLine();
|
|||
|
exports.cursor.beginningOfLine();
|
|||
|
} else {
|
|||
|
process.stdout.write('\r');
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Outut the given `failures` as a list.
|
|||
|
*
|
|||
|
* @param {Array} failures
|
|||
|
* @api public
|
|||
|
*/
|
|||
|
|
|||
|
exports.list = function(failures){
|
|||
|
console.log();
|
|||
|
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)
|
|||
|
, actual = err.actual
|
|||
|
, expected = err.expected
|
|||
|
, escape = true;
|
|||
|
if (index === -1) {
|
|||
|
msg = message;
|
|||
|
} else {
|
|||
|
index += message.length;
|
|||
|
msg = stack.slice(0, index);
|
|||
|
// remove msg from stack
|
|||
|
stack = stack.slice(index + 1);
|
|||
|
}
|
|||
|
|
|||
|
// uncaught
|
|||
|
if (err.uncaught) {
|
|||
|
msg = 'Uncaught ' + msg;
|
|||
|
}
|
|||
|
// explicitly show diff
|
|||
|
if (err.showDiff !== false && sameType(actual, expected)
|
|||
|
&& expected !== undefined) {
|
|||
|
|
|||
|
escape = false;
|
|||
|
err.actual = actual = utils.stringify(actual);
|
|||
|
err.expected = expected = utils.stringify(expected);
|
|||
|
|
|||
|
fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n');
|
|||
|
var match = message.match(/^([^:]+): expected/);
|
|||
|
msg = '\n ' + color('error message', match ? match[1] : msg);
|
|||
|
|
|||
|
if (exports.inlineDiffs) {
|
|||
|
msg += inlineDiff(err, escape);
|
|||
|
} else {
|
|||
|
msg += unifiedDiff(err, escape);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// indent stack trace
|
|||
|
stack = stack.replace(/^/gm, ' ');
|
|||
|
|
|||
|
console.log(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.log(fmt, stats.failures);
|
|||
|
|
|||
|
Base.list(this.failures);
|
|||
|
console.log();
|
|||
|
}
|
|||
|
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* Returns an inline diff between 2 strings with coloured ANSI output
|
|||
|
*
|
|||
|
* @param {Error} Error with actual/expected
|
|||
|
* @return {String} Diff
|
|||
|
* @api private
|
|||
|
*/
|
|||
|
|
|||
|
function inlineDiff(err, escape) {
|
|||
|
var msg = errorDiff(err, 'WordsWithSpace', 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, ' ');
|
|||
|
return msg;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns a unified diff between 2 strings
|
|||
|
*
|
|||
|
* @param {Error} Error with actual/expected
|
|||
|
* @return {String} Diff
|
|||
|
* @api private
|
|||
|
*/
|
|||
|
|
|||
|
function unifiedDiff(err, escape) {
|
|||
|
var indent = ' ';
|
|||
|
function cleanUp(line) {
|
|||
|
if (escape) {
|
|||
|
line = escapeInvisibles(line);
|
|||
|
}
|
|||
|
if (line[0] === '+') return indent + colorLines('diff added', line);
|
|||
|
if (line[0] === '-') return indent + colorLines('diff removed', line);
|
|||
|
if (line.match(/\@\@/)) return null;
|
|||
|
if (line.match(/\\ No newline/)) return null;
|
|||
|
else return indent + line;
|
|||
|
}
|
|||
|
function notBlank(line) {
|
|||
|
return line != null;
|
|||
|
}
|
|||
|
var msg = diff.createPatch('string', err.actual, err.expected);
|
|||
|
var lines = msg.split('\n').splice(4);
|
|||
|
return '\n '
|
|||
|
+ colorLines('diff added', '+ expected') + ' '
|
|||
|
+ colorLines('diff removed', '- actual')
|
|||
|
+ '\n\n'
|
|||
|
+ lines.map(cleanUp).filter(notBlank).join('\n');
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Return a character diff for `err`.
|
|||
|
*
|
|||
|
* @param {Error} err
|
|||
|
* @return {String}
|
|||
|
* @api private
|
|||
|
*/
|
|||
|
|
|||
|
function errorDiff(err, type, escape) {
|
|||
|
var actual = escape ? escapeInvisibles(err.actual) : err.actual;
|
|||
|
var expected = escape ? escapeInvisibles(err.expected) : err.expected;
|
|||
|
return diff['diff' + type](actual, expected).map(function(str){
|
|||
|
if (str.added) return colorLines('diff added', str.value);
|
|||
|
if (str.removed) return colorLines('diff removed', str.value);
|
|||
|
return str.value;
|
|||
|
}).join('');
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns a string with all invisible characters in plain text
|
|||
|
*
|
|||
|
* @param {String} line
|
|||
|
* @return {String}
|
|||
|
* @api private
|
|||
|
*/
|
|||
|
function escapeInvisibles(line) {
|
|||
|
return line.replace(/\t/g, '<tab>')
|
|||
|
.replace(/\r/g, '<CR>')
|
|||
|
.replace(/\n/g, '<LF>\n');
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 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');
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 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;
|
|||
|
}
|