524 lines
11 KiB
JavaScript
524 lines
11 KiB
JavaScript
|
'use strict';
|
|||
|
/**
|
|||
|
* @module Base
|
|||
|
*/
|
|||
|
/**
|
|||
|
* Module dependencies.
|
|||
|
*/
|
|||
|
|
|||
|
var tty = require('tty');
|
|||
|
var diff = require('diff');
|
|||
|
var milliseconds = require('ms');
|
|||
|
var utils = require('../utils');
|
|||
|
var supportsColor = process.browser ? null : require('supports-color');
|
|||
|
var constants = require('../runner').constants;
|
|||
|
var EVENT_TEST_PASS = constants.EVENT_TEST_PASS;
|
|||
|
var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL;
|
|||
|
|
|||
|
/**
|
|||
|
* Expose `Base`.
|
|||
|
*/
|
|||
|
|
|||
|
exports = module.exports = Base;
|
|||
|
|
|||
|
/**
|
|||
|
* Check if both stdio streams are associated with a tty.
|
|||
|
*/
|
|||
|
|
|||
|
var isatty = process.stdout.isTTY && process.stderr.isTTY;
|
|||
|
|
|||
|
/**
|
|||
|
* Save log references to avoid tests interfering (see GH-3604).
|
|||
|
*/
|
|||
|
var consoleLog = console.log;
|
|||
|
|
|||
|
/**
|
|||
|
* Enable coloring by default, except in the browser interface.
|
|||
|
*/
|
|||
|
|
|||
|
exports.useColors =
|
|||
|
!process.browser &&
|
|||
|
(supportsColor.stdout || process.env.MOCHA_COLORS !== undefined);
|
|||
|
|
|||
|
/**
|
|||
|
* 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': 32,
|
|||
|
'diff removed': 31
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Default symbol map.
|
|||
|
*/
|
|||
|
|
|||
|
exports.symbols = {
|
|||
|
ok: '✓',
|
|||
|
err: '✖',
|
|||
|
dot: '․',
|
|||
|
comma: ',',
|
|||
|
bang: '!'
|
|||
|
};
|
|||
|
|
|||
|
// With node.js on Windows: use symbols available in terminal default fonts
|
|||
|
if (process.platform === 'win32') {
|
|||
|
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.
|
|||
|
*
|
|||
|
* @private
|
|||
|
* @param {string} type
|
|||
|
* @param {string} str
|
|||
|
* @return {string}
|
|||
|
*/
|
|||
|
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: 75
|
|||
|
};
|
|||
|
|
|||
|
if (isatty) {
|
|||
|
exports.window.width = process.stdout.getWindowSize
|
|||
|
? process.stdout.getWindowSize(1)[0]
|
|||
|
: tty.getWindowSize()[1];
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 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');
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
var showDiff = (exports.showDiff = function(err) {
|
|||
|
return (
|
|||
|
err &&
|
|||
|
err.showDiff !== false &&
|
|||
|
sameType(err.actual, err.expected) &&
|
|||
|
err.expected !== undefined
|
|||
|
);
|
|||
|
});
|
|||
|
|
|||
|
function stringifyDiffObjs(err) {
|
|||
|
if (!utils.isString(err.actual) || !utils.isString(err.expected)) {
|
|||
|
err.actual = utils.stringify(err.actual);
|
|||
|
err.expected = utils.stringify(err.expected);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns a diff between 2 strings with coloured ANSI output.
|
|||
|
*
|
|||
|
* @description
|
|||
|
* The diff will be either inline or unified dependent on the value
|
|||
|
* of `Base.inlineDiff`.
|
|||
|
*
|
|||
|
* @param {string} actual
|
|||
|
* @param {string} expected
|
|||
|
* @return {string} Diff
|
|||
|
*/
|
|||
|
var generateDiff = (exports.generateDiff = function(actual, expected) {
|
|||
|
try {
|
|||
|
return exports.inlineDiffs
|
|||
|
? inlineDiff(actual, expected)
|
|||
|
: unifiedDiff(actual, expected);
|
|||
|
} catch (err) {
|
|||
|
var msg =
|
|||
|
'\n ' +
|
|||
|
color('diff added', '+ expected') +
|
|||
|
' ' +
|
|||
|
color('diff removed', '- actual: failed to generate Mocha diff') +
|
|||
|
'\n';
|
|||
|
return msg;
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
/**
|
|||
|
* Outputs the given `failures` as a list.
|
|||
|
*
|
|||
|
* @public
|
|||
|
* @memberof Mocha.reporters.Base
|
|||
|
* @variation 1
|
|||
|
* @param {Object[]} failures - Each is Test instance with corresponding
|
|||
|
* Error property
|
|||
|
*/
|
|||
|
exports.list = function(failures) {
|
|||
|
var multipleErr, multipleTest;
|
|||
|
Base.consoleLog();
|
|||
|
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 msg;
|
|||
|
var err;
|
|||
|
if (test.err && test.err.multiple) {
|
|||
|
if (multipleTest !== test) {
|
|||
|
multipleTest = test;
|
|||
|
multipleErr = [test.err].concat(test.err.multiple);
|
|||
|
}
|
|||
|
err = multipleErr.shift();
|
|||
|
} else {
|
|||
|
err = test.err;
|
|||
|
}
|
|||
|
var message;
|
|||
|
if (err.message && typeof err.message.toString === 'function') {
|
|||
|
message = err.message + '';
|
|||
|
} else if (typeof err.inspect === 'function') {
|
|||
|
message = err.inspect() + '';
|
|||
|
} else {
|
|||
|
message = '';
|
|||
|
}
|
|||
|
var stack = err.stack || message;
|
|||
|
var index = message ? stack.indexOf(message) : -1;
|
|||
|
|
|||
|
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 (!exports.hideDiff && showDiff(err)) {
|
|||
|
stringifyDiffObjs(err);
|
|||
|
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);
|
|||
|
|
|||
|
msg += generateDiff(err.actual, err.expected);
|
|||
|
}
|
|||
|
|
|||
|
// indent stack trace
|
|||
|
stack = stack.replace(/^/gm, ' ');
|
|||
|
|
|||
|
// indented test title
|
|||
|
var testTitle = '';
|
|||
|
test.titlePath().forEach(function(str, index) {
|
|||
|
if (index !== 0) {
|
|||
|
testTitle += '\n ';
|
|||
|
}
|
|||
|
for (var i = 0; i < index; i++) {
|
|||
|
testTitle += ' ';
|
|||
|
}
|
|||
|
testTitle += str;
|
|||
|
});
|
|||
|
|
|||
|
Base.consoleLog(fmt, i + 1, testTitle, msg, stack);
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Constructs a new `Base` reporter instance.
|
|||
|
*
|
|||
|
* @description
|
|||
|
* All other reporters generally inherit from this reporter.
|
|||
|
*
|
|||
|
* @public
|
|||
|
* @class
|
|||
|
* @memberof Mocha.reporters
|
|||
|
* @param {Runner} runner - Instance triggers reporter actions.
|
|||
|
* @param {Object} [options] - runner options
|
|||
|
*/
|
|||
|
function Base(runner, options) {
|
|||
|
var failures = (this.failures = []);
|
|||
|
|
|||
|
if (!runner) {
|
|||
|
throw new TypeError('Missing runner argument');
|
|||
|
}
|
|||
|
this.options = options || {};
|
|||
|
this.runner = runner;
|
|||
|
this.stats = runner.stats; // assigned so Reporters keep a closer reference
|
|||
|
|
|||
|
runner.on(EVENT_TEST_PASS, function(test) {
|
|||
|
if (test.duration > test.slow()) {
|
|||
|
test.speed = 'slow';
|
|||
|
} else if (test.duration > test.slow() / 2) {
|
|||
|
test.speed = 'medium';
|
|||
|
} else {
|
|||
|
test.speed = 'fast';
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
runner.on(EVENT_TEST_FAIL, function(test, err) {
|
|||
|
if (showDiff(err)) {
|
|||
|
stringifyDiffObjs(err);
|
|||
|
}
|
|||
|
// more than one error per test
|
|||
|
if (test.err && err instanceof Error) {
|
|||
|
test.err.multiple = (test.err.multiple || []).concat(err);
|
|||
|
} else {
|
|||
|
test.err = err;
|
|||
|
}
|
|||
|
failures.push(test);
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Outputs common epilogue used by many of the bundled reporters.
|
|||
|
*
|
|||
|
* @public
|
|||
|
* @memberof Mocha.reporters
|
|||
|
*/
|
|||
|
Base.prototype.epilogue = function() {
|
|||
|
var stats = this.stats;
|
|||
|
var fmt;
|
|||
|
|
|||
|
Base.consoleLog();
|
|||
|
|
|||
|
// passes
|
|||
|
fmt =
|
|||
|
color('bright pass', ' ') +
|
|||
|
color('green', ' %d passing') +
|
|||
|
color('light', ' (%s)');
|
|||
|
|
|||
|
Base.consoleLog(fmt, stats.passes || 0, milliseconds(stats.duration));
|
|||
|
|
|||
|
// pending
|
|||
|
if (stats.pending) {
|
|||
|
fmt = color('pending', ' ') + color('pending', ' %d pending');
|
|||
|
|
|||
|
Base.consoleLog(fmt, stats.pending);
|
|||
|
}
|
|||
|
|
|||
|
// failures
|
|||
|
if (stats.failures) {
|
|||
|
fmt = color('fail', ' %d failing');
|
|||
|
|
|||
|
Base.consoleLog(fmt, stats.failures);
|
|||
|
|
|||
|
Base.list(this.failures);
|
|||
|
Base.consoleLog();
|
|||
|
}
|
|||
|
|
|||
|
Base.consoleLog();
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Pads the given `str` to `len`.
|
|||
|
*
|
|||
|
* @private
|
|||
|
* @param {string} str
|
|||
|
* @param {string} len
|
|||
|
* @return {string}
|
|||
|
*/
|
|||
|
function pad(str, len) {
|
|||
|
str = String(str);
|
|||
|
return Array(len - str.length + 1).join(' ') + str;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns inline diff between 2 strings with coloured ANSI output.
|
|||
|
*
|
|||
|
* @private
|
|||
|
* @param {String} actual
|
|||
|
* @param {String} expected
|
|||
|
* @return {string} Diff
|
|||
|
*/
|
|||
|
function inlineDiff(actual, expected) {
|
|||
|
var msg = errorDiff(actual, expected);
|
|||
|
|
|||
|
// 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 unified diff between two strings with coloured ANSI output.
|
|||
|
*
|
|||
|
* @private
|
|||
|
* @param {String} actual
|
|||
|
* @param {String} expected
|
|||
|
* @return {string} The diff.
|
|||
|
*/
|
|||
|
function unifiedDiff(actual, expected) {
|
|||
|
var indent = ' ';
|
|||
|
function cleanUp(line) {
|
|||
|
if (line[0] === '+') {
|
|||
|
return indent + colorLines('diff added', line);
|
|||
|
}
|
|||
|
if (line[0] === '-') {
|
|||
|
return indent + colorLines('diff removed', line);
|
|||
|
}
|
|||
|
if (line.match(/@@/)) {
|
|||
|
return '--';
|
|||
|
}
|
|||
|
if (line.match(/\\ No newline/)) {
|
|||
|
return null;
|
|||
|
}
|
|||
|
return indent + line;
|
|||
|
}
|
|||
|
function notBlank(line) {
|
|||
|
return typeof line !== 'undefined' && line !== null;
|
|||
|
}
|
|||
|
var msg = diff.createPatch('string', actual, expected);
|
|||
|
var lines = msg.split('\n').splice(5);
|
|||
|
return (
|
|||
|
'\n ' +
|
|||
|
colorLines('diff added', '+ expected') +
|
|||
|
' ' +
|
|||
|
colorLines('diff removed', '- actual') +
|
|||
|
'\n\n' +
|
|||
|
lines
|
|||
|
.map(cleanUp)
|
|||
|
.filter(notBlank)
|
|||
|
.join('\n')
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns character diff for `err`.
|
|||
|
*
|
|||
|
* @private
|
|||
|
* @param {String} actual
|
|||
|
* @param {String} expected
|
|||
|
* @return {string} the diff
|
|||
|
*/
|
|||
|
function errorDiff(actual, expected) {
|
|||
|
return diff
|
|||
|
.diffWordsWithSpace(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('');
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Colors lines for `str`, using the color `name`.
|
|||
|
*
|
|||
|
* @private
|
|||
|
* @param {string} name
|
|||
|
* @param {string} str
|
|||
|
* @return {string}
|
|||
|
*/
|
|||
|
function colorLines(name, str) {
|
|||
|
return str
|
|||
|
.split('\n')
|
|||
|
.map(function(str) {
|
|||
|
return color(name, str);
|
|||
|
})
|
|||
|
.join('\n');
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Object#toString reference.
|
|||
|
*/
|
|||
|
var objToString = Object.prototype.toString;
|
|||
|
|
|||
|
/**
|
|||
|
* Checks that a / b have the same type.
|
|||
|
*
|
|||
|
* @private
|
|||
|
* @param {Object} a
|
|||
|
* @param {Object} b
|
|||
|
* @return {boolean}
|
|||
|
*/
|
|||
|
function sameType(a, b) {
|
|||
|
return objToString.call(a) === objToString.call(b);
|
|||
|
}
|
|||
|
|
|||
|
Base.consoleLog = consoleLog;
|
|||
|
|
|||
|
Base.abstract = true;
|