Use custom object formatters for any part of a diff, not just leaf nodes

This commit is contained in:
Steve Gravrock
2020-02-01 18:49:06 -08:00
committed by Steve Gravrock
parent 25816a6e77
commit 873d1c2945
15 changed files with 669 additions and 141 deletions

View File

@@ -102,15 +102,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
};
SinglePrettyPrintRun.prototype.applyCustomFormatters_ = function(value) {
var i, result;
for (i = 0; i < this.customObjectFormatters_.length; i++) {
result = this.customObjectFormatters_[i](value);
if (result !== undefined) {
return result;
}
}
return customFormat(value, this.customObjectFormatters_);
};
SinglePrettyPrintRun.prototype.iterateObject = function(obj, fn) {
@@ -383,16 +375,31 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
return extraKeys;
}
function customFormat(value, customObjectFormatters) {
var i, result;
for (i = 0; i < customObjectFormatters.length; i++) {
result = customObjectFormatters[i](value);
if (result !== undefined) {
return result;
}
}
}
return function(customObjectFormatters) {
customObjectFormatters = customObjectFormatters || [];
var pp = function(value) {
var prettyPrinter = new SinglePrettyPrintRun(
customObjectFormatters || [],
pp
);
var prettyPrinter = new SinglePrettyPrintRun(customObjectFormatters, pp);
prettyPrinter.format(value);
return prettyPrinter.stringParts.join('');
};
pp.customFormat_ = function(value) {
return customFormat(value, customObjectFormatters);
};
return pp;
};
};

View File

@@ -1,17 +1,51 @@
getJasmineRequireObj().DiffBuilder = function(j$) {
getJasmineRequireObj().DiffBuilder = function (j$) {
return function DiffBuilder(config) {
var path = new j$.ObjectPath(),
mismatches = [],
prettyPrinter = (config || {}).prettyPrinter || j$.makePrettyPrinter();
var prettyPrinter = (config || {}).prettyPrinter || j$.makePrettyPrinter(),
mismatches = new j$.MismatchTree(),
path = new j$.ObjectPath(),
actualRoot = undefined,
expectedRoot = undefined;
return {
record: function (actual, expected, formatter) {
formatter = formatter || defaultFormatter;
mismatches.push(formatter(actual, expected, path, prettyPrinter));
setRoots: function (actual, expected) {
actualRoot = actual;
expectedRoot = expected;
},
recordMismatch: function (formatter) {
mismatches.add(path, formatter);
},
getMessage: function () {
return mismatches.join('\n');
var messages = [];
mismatches.traverse(function (path, isLeaf, formatter) {
var actualCustom, expectedCustom, useCustom,
actual = path.dereference(actualRoot),
expected = path.dereference(expectedRoot);
if (formatter) {
messages.push(formatter(actual, expected, path, prettyPrinter));
return true;
}
actualCustom = prettyPrinter.customFormat_(actual);
expectedCustom = prettyPrinter.customFormat_(expected);
useCustom = !(j$.util.isUndefined(actualCustom) && j$.util.isUndefined(expectedCustom));
if (useCustom) {
messages.push(wrapPrettyPrinted(actualCustom, expectedCustom, path));
return false; // don't recurse further
}
if (isLeaf) {
messages.push(defaultFormatter(actual, expected, path, prettyPrinter));
}
return true;
});
return messages.join('\n');
},
withPath: function (pathComponent, block) {
@@ -22,12 +56,16 @@ getJasmineRequireObj().DiffBuilder = function(j$) {
}
};
function defaultFormatter (actual, expected, path, prettyPrinter) {
function defaultFormatter(actual, expected, path, prettyPrinter) {
return wrapPrettyPrinted(prettyPrinter(actual), prettyPrinter(expected), path);
}
function wrapPrettyPrinted(actual, expected, path) {
return 'Expected ' +
path + (path.depth() ? ' = ' : '') +
prettyPrinter(actual) +
actual +
' to equal ' +
prettyPrinter(expected) +
expected +
'.';
}
};

View File

@@ -0,0 +1,62 @@
getJasmineRequireObj().MismatchTree = function (j$) {
/*
To be able to apply custom object formatters at all possible levels of an
object graph, DiffBuilder needs to be able to know not just where the
mismatch occurred but also all ancestors of the mismatched value in both
the expected and actual object graphs. MismatchTree maintains that context
and provides it via the traverse method.
*/
function MismatchTree(path) {
this.path = path || new j$.ObjectPath([]);
this.formatter = undefined;
this.children = [];
this.isMismatch = false;
}
MismatchTree.prototype.add = function (path, formatter) {
var key, child;
if (path.depth() === 0) {
this.formatter = formatter;
this.isMismatch = true;
} else {
key = path.components[0];
path = path.shift();
child = this.child(key);
if (!child) {
child = new MismatchTree(this.path.add(key));
this.children.push(child);
}
child.add(path, formatter);
}
};
MismatchTree.prototype.traverse = function (visit) {
var i, hasChildren = this.children.length > 0;
if (this.isMismatch || hasChildren) {
if (visit(this.path, !hasChildren, this.formatter)) {
for (i = 0; i < this.children.length; i++) {
this.children[i].traverse(visit);
}
}
}
};
MismatchTree.prototype.child = function(key) {
var i, pathEls;
for (i = 0; i < this.children.length; i++) {
pathEls = this.children[i].path.components;
if (pathEls[pathEls.length - 1] === key) {
return this.children[i];
}
}
};
return MismatchTree;
};

View File

@@ -4,7 +4,8 @@ getJasmineRequireObj().NullDiffBuilder = function(j$) {
withPath: function(_, block) {
block();
},
record: function() {}
setRoots: function() {},
recordMismatch: function() {}
};
};
};

View File

@@ -11,10 +11,24 @@ getJasmineRequireObj().ObjectPath = function(j$) {
}
};
ObjectPath.prototype.dereference = function(obj) {
var i;
for (i = 0; i < this.components.length; i++) {
obj = obj[this.components[i]];
}
return obj;
};
ObjectPath.prototype.add = function(component) {
return new ObjectPath(this.components.concat([component]));
};
ObjectPath.prototype.shift = function() {
return new ObjectPath(this.components.slice(1));
};
ObjectPath.prototype.depth = function() {
return this.components.length;
};

View File

@@ -69,7 +69,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
if (asymmetricA) {
result = a.asymmetricMatch(b, shim);
if (!result) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
}
return result;
}
@@ -77,7 +77,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
if (asymmetricB) {
result = b.asymmetricMatch(a, shim);
if (!result) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
}
return result;
}
@@ -95,6 +95,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
customTesters = customTesters || this.customTesters_;
diffBuilder = diffBuilder || j$.NullDiffBuilder();
diffBuilder.setRoots(a, b);
return this.eq_(a, b, [], [], customTesters, diffBuilder);
};
@@ -113,7 +114,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
var customTesterResult = customTesters[i](a, b);
if (!j$.util.isUndefined(customTesterResult)) {
if (!customTesterResult) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
}
return customTesterResult;
}
@@ -122,7 +123,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
if (a instanceof Error && b instanceof Error) {
result = a.message == b.message;
if (!result) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
}
return result;
}
@@ -132,7 +133,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
if (a === b) {
result = a !== 0 || 1 / a == 1 / b;
if (!result) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
}
return result;
}
@@ -140,13 +141,13 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
if (a === null || b === null) {
result = a === b;
if (!result) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
}
return result;
}
var className = Object.prototype.toString.call(a);
if (className != Object.prototype.toString.call(b)) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
return false;
}
switch (className) {
@@ -156,7 +157,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
// equivalent to `new String("5")`.
result = a == String(b);
if (!result) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
}
return result;
case '[object Number]':
@@ -164,7 +165,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
// other numeric values.
result = a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b);
if (!result) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
}
return result;
case '[object Date]':
@@ -174,7 +175,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
// of `NaN` are not equivalent.
result = +a == +b;
if (!result) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
}
return result;
// RegExps are compared by their source patterns and flags.
@@ -185,7 +186,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
a.ignoreCase == b.ignoreCase;
}
if (typeof a != 'object' || typeof b != 'object') {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
return false;
}
@@ -195,12 +196,12 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
// At first try to use DOM3 method isEqualNode
result = a.isEqualNode(b);
if (!result) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
}
return result;
}
if (aIsDomNode || bIsDomNode) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
return false;
}
@@ -230,7 +231,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
diffBuilder.withPath('length', function() {
if (aLength !== bLength) {
diffBuilder.record(aLength, bLength);
diffBuilder.recordMismatch();
result = false;
}
});
@@ -238,7 +239,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
for (i = 0; i < aLength || i < bLength; i++) {
diffBuilder.withPath(i, function() {
if (i >= bLength) {
diffBuilder.record(a[i], void 0, actualArrayIsLongerFormatter.bind(null, self.pp));
diffBuilder.recordMismatch(actualArrayIsLongerFormatter.bind(null, self.pp));
result = false;
} else {
result = self.eq_(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0, aStack, bStack, customTesters, diffBuilder) && result;
@@ -250,7 +251,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
}
} else if (j$.isMap(a) && j$.isMap(b)) {
if (a.size != b.size) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
return false;
}
@@ -292,12 +293,12 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
}
if (!result) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
return false;
}
} else if (j$.isSet(a) && j$.isSet(b)) {
if (a.size != b.size) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
return false;
}
@@ -342,7 +343,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
}
if (!result) {
diffBuilder.record(a, b);
diffBuilder.recordMismatch();
return false;
}
} else {
@@ -355,7 +356,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
a instanceof aCtor && b instanceof bCtor &&
!(aCtor instanceof aCtor && bCtor instanceof bCtor)) {
diffBuilder.record(a, b, constructorsAreDifferentFormatter.bind(null, this.pp));
diffBuilder.recordMismatch(constructorsAreDifferentFormatter.bind(null, this.pp));
return false;
}
}
@@ -366,7 +367,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
// Ensure that both objects contain the same number of properties before comparing deep equality.
if (keys(b, className == '[object Array]').length !== size) {
diffBuilder.record(a, b, objectKeysAreDifferentFormatter.bind(null, this.pp));
diffBuilder.recordMismatch(objectKeysAreDifferentFormatter.bind(null, this.pp));
return false;
}
@@ -374,7 +375,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
key = aKeys[i];
// Deep compare each member
if (!j$.util.has(b, key)) {
diffBuilder.record(a, b, objectKeysAreDifferentFormatter.bind(null, this.pp));
diffBuilder.recordMismatch(objectKeysAreDifferentFormatter.bind(null, this.pp));
result = false;
continue;
}
@@ -480,7 +481,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
}
function isDiffBuilder(obj) {
return obj && typeof obj.record === 'function';
return obj && typeof obj.recordMismatch === 'function';
}
return MatchersUtil;

View File

@@ -84,6 +84,7 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
j$.DiffBuilder = jRequire.DiffBuilder(j$);
j$.NullDiffBuilder = jRequire.NullDiffBuilder(j$);
j$.ObjectPath = jRequire.ObjectPath(j$);
j$.MismatchTree = jRequire.MismatchTree(j$);
j$.GlobalErrors = jRequire.GlobalErrors(j$);
j$.Truthy = jRequire.Truthy(j$);