| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589 |
- 'use strict';
- /**
- * @typedef {import('../runner.js')} Runner
- * @typedef {import('../test.js')} Test
- * @typedef {import('../types.d.ts').FullErrorStack} FullErrorStack
- */
- /**
- * @module Base
- */
- /**
- * Module dependencies.
- */
- var diff = require('diff');
- var milliseconds = require('ms');
- var utils = require('../utils');
- var supportsColor = require('supports-color');
- var symbols = require('log-symbols');
- var constants = require('../runner').constants;
- var EVENT_TEST_PASS = constants.EVENT_TEST_PASS;
- var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL;
- const isBrowser = utils.isBrowser();
- function getBrowserWindowSize() {
- if ('innerHeight' in global) {
- return [global.innerHeight, global.innerWidth];
- }
- // In a Web Worker, the DOM Window is not available.
- return [640, 480];
- }
- /**
- * Expose `Base`.
- */
- exports = module.exports = Base;
- /**
- * Check if both stdio streams are associated with a tty.
- */
- var isatty = isBrowser || (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 =
- !isBrowser &&
- (supportsColor.stdout || process.env.MOCHA_COLORS !== undefined);
- /**
- * Inline diffs instead of +/-
- */
- exports.inlineDiffs = false;
- /**
- * Truncate diffs longer than this value to avoid slow performance
- */
- exports.maxDiffSize = 8192;
- /**
- * 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,
- 'diff added inline': '30;42',
- 'diff removed inline': '30;41'
- };
- /**
- * Default symbol map.
- */
- exports.symbols = {
- ok: symbols.success,
- err: symbols.error,
- dot: '.',
- comma: ',',
- bang: '!'
- };
- /**
- * 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) {
- if (isBrowser) {
- exports.window.width = getBrowserWindowSize()[1];
- } else {
- exports.window.width = process.stdout.getWindowSize(1)[0];
- }
- }
- /**
- * 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 {
- var maxLen = exports.maxDiffSize;
- var skipped = 0;
- if (maxLen > 0) {
- skipped = Math.max(actual.length - maxLen, expected.length - maxLen);
- actual = actual.slice(0, maxLen);
- expected = expected.slice(0, maxLen);
- }
- let result = exports.inlineDiffs
- ? inlineDiff(actual, expected)
- : unifiedDiff(actual, expected);
- if (skipped > 0) {
- result = `${result}\n [mocha] output truncated to ${maxLen} characters, see "maxDiffSize" reporter-option\n`;
- }
- return result;
- } catch (err) {
- var msg =
- '\n ' +
- color('diff added', '+ expected') +
- ' ' +
- color('diff removed', '- actual: failed to generate Mocha diff') +
- '\n';
- return msg;
- }
- });
- /**
- * Traverses err.cause and returns all stack traces
- *
- * @private
- * @param {Error} err
- * @param {Set<Error>} [seen]
- * @return {FullErrorStack}
- */
- var getFullErrorStack = function (err, seen) {
- if (seen && seen.has(err)) {
- return { message: '', msg: '<circular>', stack: '' };
- }
- var message;
- if (typeof err.inspect === 'function') {
- message = err.inspect() + '';
- } else if (err.message && typeof err.message.toString === 'function') {
- message = err.message + '';
- } else {
- message = '';
- }
- var msg;
- 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);
- if (err.cause) {
- seen = seen || new Set();
- seen.add(err);
- const causeStack = getFullErrorStack(err.cause, seen)
- stack += '\n Caused by: ' + causeStack.msg + (causeStack.stack ? '\n' + causeStack.stack : '');
- }
- }
- return {
- message,
- msg,
- stack
- };
- };
- /**
- * 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 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, msg, stack } = getFullErrorStack(err);
- // 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
- var maxDiffSizeOpt =
- this.options.reporterOption && this.options.reporterOption.maxDiffSize;
- if (maxDiffSizeOpt !== undefined && !isNaN(Number(maxDiffSizeOpt))) {
- exports.maxDiffSize = Number(maxDiffSizeOpt);
- }
- 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 inline', 'actual') +
- ' ' +
- color('diff added inline', '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 inline', str.value);
- }
- if (str.removed) {
- return colorLines('diff removed inline', 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;
|