Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Implementation of support for diff property in AssertionError #3251

Closed
wants to merge 9 commits into from
178 changes: 50 additions & 128 deletions lib/reporters/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
*/

var tty = require('tty');
var diff = require('diff');
var ms = require('../ms');
var utils = require('../utils');
var supportsColor = process.browser ? null : require('supports-color');
var utils = require('../utils');
var generate = require('structured-diff/lib/core');
var UnifiedDiff = require('./diff/unified-diff');
var InlineDiff = require('./diff/inline-diff');

/**
* Expose `Base`.
Expand Down Expand Up @@ -70,7 +72,10 @@ exports.colors = {
light: 90,
'diff gutter': 90,
'diff added': 32,
'diff removed': 31
'diff removed': 31,
'inline diff added': 36,
'inline diff removed': 35,
'inline diff changed': 35
};

/**
Expand All @@ -92,6 +97,20 @@ if (process.platform === 'win32') {
exports.symbols.dot = '.';
}

/**
* Creates regular expression from string that match this string.
*
* @api private
* @param {string} str String for which regular expression is created
* @return {RegExp} Regular expression that match specified string.
*/
function convertToRE (str) {
var escapedSpecialRegexpSymbols = str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
return new RegExp(escapedSpecialRegexpSymbols, 'g');
}

var close = '\u001b[0m';
var closeRE = convertToRE(close);
/**
* Color `str` with the given `type`,
* allowing colors to be disabled,
Expand All @@ -107,9 +126,13 @@ var color = exports.color = function (type, str) {
if (!exports.useColors) {
return String(str);
}
return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m';
var open = '\u001b[' + exports.colors[type] + 'm';
return open + str.replace(closeRE, open) + close;
};

var unifiedDiff = new UnifiedDiff(color, ' ');
var inlineDiff = new InlineDiff(color, ' ');

/**
* Expose term window size, with some defaults for when stderr is not a tty.
*/
Expand Down Expand Up @@ -155,15 +178,13 @@ exports.cursor = {
}
};

function showDiff (err) {
return err && err.showDiff !== false && sameType(err.actual, err.expected) && err.expected !== undefined;
function hasDiff (err) {
return typeof err.diff !== '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);
}
function showDiff (err) {
return hasDiff(err) ||
(err.showDiff !== false && typeof err.expected !== 'undefined' && sameType(err.actual, err.expected));
}

/**
Expand All @@ -176,10 +197,10 @@ function stringifyDiffObjs (err) {
* @param {string} expected
* @return {string} Diff
*/
var generateDiff = exports.generateDiff = function (actual, expected) {
return exports.inlineDiffs
? inlineDiff(actual, expected)
: unifiedDiff(actual, expected);
exports.generateDiff = function (actual, expected) {
var diff = generateDiff(expected, actual);

return exports.inlineDiffs ? inlineDiff.print(diff, 4) : unifiedDiff.print(diff, 4);
};

/**
Expand Down Expand Up @@ -226,12 +247,11 @@ exports.list = function (failures) {
}
// 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);
msg += makeDiff(err, 4);
}

// indent stack trace
Expand Down Expand Up @@ -307,9 +327,6 @@ function Base (runner) {
runner.on('fail', function (test, err) {
stats.failures = stats.failures || 0;
stats.failures++;
if (showDiff(err)) {
stringifyDiffObjs(err);
}
test.err = err;
failures.push(test);
});
Expand Down Expand Up @@ -367,121 +384,26 @@ Base.prototype.epilogue = function () {
};

/**
* Pad the given `str` to `len`.
*
* @api 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 an inline diff between 2 strings with coloured ANSI output.
* Creates diffs for given error by comparing `expected` and `actual` converted to strings.
*
* @api private
* @param {String} actual
* @param {String} expected
* @return {string} Diff
* @param {Error} err with actual/expected
* @param {number?} context Count of context lines befor and after changed lines in diff output.
* If not specified or negative, output all lines (infinity count of context lines)
* @return {string} The diff for output to the console.
*/
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');
}
function makeDiff (err, context) {
var diff = hasDiff(err) ? err.diff : generateDiff(err.expected, err.actual);

// legend
msg = '\n' +
color('diff removed', 'actual') +
' ' +
color('diff added', 'expected') +
'\n\n' +
msg +
'\n';

// indent
msg = msg.replace(/^/gm, ' ');
return msg;
return exports.inlineDiffs ? inlineDiff.print(diff, context) : unifiedDiff.print(diff, context);
}

/**
* Returns a unified diff between two strings with coloured ANSI output.
*
* @api 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;
function generateDiff (expected, actual) {
if (!utils.isString(expected) || !utils.isString(actual)) {
expected = utils.stringify(expected);
actual = utils.stringify(actual);
}
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');
}

/**
* Return a character diff for `err`.
*
* @api 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('');
}

/**
* Color lines for `str`, using the color `name`.
*
* @api 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');
return generate(expected, actual);
}

/**
Expand Down
91 changes: 91 additions & 0 deletions lib/reporters/diff/console-printer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use strict';

/**
* Pad the given `str` to `len`.
*
* @api private
* @param {string} str
* @param {string} len
* @return {string}
*/
function pad (str, len) {
str = String(str);
return Array(len - str.length + 1).join(' ') + str;
}
/**
* Allow printing colored lines to the console. Subclasses must implement
* `colorize(line)`method.
*
* @api public
* @param {function(string, string)} color Function that returns colorized version of input value
* for given style
* @param {string} indent Indent that will be appended to each line
*/
function ConsolePrinter (color, indent) {
this.color = color;
this.indent = indent;
}

ConsolePrinter.prototype.ins = function (str) {
return this.color('diff added', str);
};

ConsolePrinter.prototype.del = function (str) {
return this.color('diff removed', str);
};

ConsolePrinter.prototype.change = function (str) {
return this.color('inline diff changed', str);
};

ConsolePrinter.prototype.stringifyHunk = function (result, hunk, withLineNumbers, width) {
var lines = hunk.lines;
var k;

if (withLineNumbers) {
var i = hunk.oldStart;
var j = hunk.newStart;

for (k = 0; k < lines.length; ++k) {
var line = lines[k];
var del = '';
var ins = '';
if (line.kind !== '+') { del = i++; }
if (line.kind !== '-') { ins = j++; }

del = pad(del, width);
ins = pad(ins, width);

// colorize numbers: line removed/inserted/changed
if (line.kind === '-') { del = this.del(del); }
if (line.kind === '+') { ins = this.ins(ins); }
if (line.kind === '?') {
del = this.change(del);
ins = this.change(ins);
}

result.push(this.indent +
del + ' | ' +
ins + ' | ' +
this.colorize(line)
);
}
} else {
for (k = 0; k < lines.length; ++k) {
result.push(this.indent + this.colorize(lines[k]));
}
}
};

ConsolePrinter.prototype.stringify = function (hunks, withLineNumbers, width) {
var result = [];
for (var i = 0; i < hunks.length; ++i) {
if (i !== 0) {
result.push('--');
}
this.stringifyHunk(result, hunks[i], withLineNumbers, width);
}
return result;
};

module.exports = ConsolePrinter;
Loading