Use custom object formatters for any part of a diff, not just leaf nodes
This commit is contained in:
committed by
Steve Gravrock
parent
25816a6e77
commit
873d1c2945
@@ -505,7 +505,7 @@ describe('PrettyPrinter', function() {
|
||||
pp = jasmineUnderTest.makePrettyPrinter(customObjectFormatters),
|
||||
obj = { foo: 'bar' };
|
||||
|
||||
expect(pp(obj, customObjectFormatters)).toEqual('2nd: bar');
|
||||
expect(pp(obj)).toEqual('2nd: bar');
|
||||
});
|
||||
|
||||
it('should fall back to built in logic if all custom object formatters return undefined', function() {
|
||||
@@ -517,7 +517,39 @@ describe('PrettyPrinter', function() {
|
||||
pp = jasmineUnderTest.makePrettyPrinter(customObjectFormatters),
|
||||
obj = { foo: 'bar' };
|
||||
|
||||
expect(pp(obj, customObjectFormatters)).toEqual("Object({ foo: 'bar' })");
|
||||
expect(pp(obj)).toEqual("Object({ foo: 'bar' })");
|
||||
});
|
||||
});
|
||||
|
||||
describe('#customFormat_', function() {
|
||||
it('should use the first custom object formatter that does not return undefined', function() {
|
||||
var customObjectFormatters = [
|
||||
function(obj) {
|
||||
return undefined;
|
||||
},
|
||||
function(obj) {
|
||||
return '2nd: ' + obj.foo;
|
||||
},
|
||||
function(obj) {
|
||||
return '3rd: ' + obj.foo;
|
||||
}
|
||||
],
|
||||
pp = jasmineUnderTest.makePrettyPrinter(customObjectFormatters),
|
||||
obj = { foo: 'bar' };
|
||||
|
||||
expect(pp.customFormat_(obj)).toEqual('2nd: bar');
|
||||
});
|
||||
|
||||
it('should return undefined if all custom object formatters return undefined', function() {
|
||||
var customObjectFormatters = [
|
||||
function(obj) {
|
||||
return undefined;
|
||||
}
|
||||
],
|
||||
pp = jasmineUnderTest.makePrettyPrinter(customObjectFormatters),
|
||||
obj = { foo: 'bar' };
|
||||
|
||||
expect(pp.customFormat_(obj)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,73 +1,136 @@
|
||||
describe("DiffBuilder", function() {
|
||||
it("records the actual and expected objects", function() {
|
||||
describe("DiffBuilder", function () {
|
||||
it("records the actual and expected objects", function () {
|
||||
var diffBuilder = jasmineUnderTest.DiffBuilder();
|
||||
diffBuilder.record({x: 'actual'}, {x: 'expected'});
|
||||
diffBuilder.setRoots({x: 'actual'}, {x: 'expected'});
|
||||
diffBuilder.recordMismatch();
|
||||
|
||||
expect(diffBuilder.getMessage()).toEqual("Expected Object({ x: 'actual' }) to equal Object({ x: 'expected' }).");
|
||||
});
|
||||
|
||||
it("prints the path at which the difference was found", function() {
|
||||
it("prints the path at which the difference was found", function () {
|
||||
var diffBuilder = jasmineUnderTest.DiffBuilder();
|
||||
diffBuilder.setRoots({foo: {x: 'actual'}}, {foo: {x: 'expected'}});
|
||||
|
||||
diffBuilder.withPath('foo', function() {
|
||||
diffBuilder.record({x: 'actual'}, {x: 'expected'});
|
||||
diffBuilder.withPath('foo', function () {
|
||||
diffBuilder.recordMismatch();
|
||||
});
|
||||
|
||||
expect(diffBuilder.getMessage()).toEqual("Expected $.foo = Object({ x: 'actual' }) to equal Object({ x: 'expected' }).");
|
||||
});
|
||||
|
||||
it("prints multiple messages, separated by newlines", function() {
|
||||
it("prints multiple messages, separated by newlines", function () {
|
||||
var diffBuilder = jasmineUnderTest.DiffBuilder();
|
||||
diffBuilder.setRoots({foo: 1, bar: 3}, {foo: 2, bar: 4});
|
||||
|
||||
diffBuilder.withPath('foo', function() {
|
||||
diffBuilder.record(1, 2);
|
||||
diffBuilder.withPath('foo', function () {
|
||||
diffBuilder.recordMismatch();
|
||||
});
|
||||
diffBuilder.withPath('bar', function () {
|
||||
diffBuilder.recordMismatch();
|
||||
});
|
||||
|
||||
var message =
|
||||
"Expected $.foo = 1 to equal 2.\n" +
|
||||
"Expected 3 to equal 4.";
|
||||
"Expected $.bar = 3 to equal 4.";
|
||||
|
||||
diffBuilder.record(3, 4);
|
||||
expect(diffBuilder.getMessage()).toEqual(message);
|
||||
});
|
||||
|
||||
it("allows customization of the message", function() {
|
||||
it("allows customization of the message", function () {
|
||||
var diffBuilder = jasmineUnderTest.DiffBuilder();
|
||||
diffBuilder.setRoots({x: 'bar'}, {x: 'foo'});
|
||||
|
||||
function darthVaderFormatter(actual, expected, path) {
|
||||
return "I find your lack of " + expected + " disturbing. (was " + actual + ", at " + path + ")"
|
||||
}
|
||||
|
||||
diffBuilder.withPath('x', function() {
|
||||
diffBuilder.record('bar', 'foo', darthVaderFormatter);
|
||||
diffBuilder.withPath('x', function () {
|
||||
diffBuilder.recordMismatch(darthVaderFormatter);
|
||||
});
|
||||
|
||||
expect(diffBuilder.getMessage()).toEqual("I find your lack of foo disturbing. (was bar, at $.x)");
|
||||
});
|
||||
|
||||
it("uses the injected pretty-printer", function() {
|
||||
var prettyPrinter = function(val) {
|
||||
it("uses the injected pretty-printer", function () {
|
||||
var prettyPrinter = function (val) {
|
||||
return '|' + val + '|';
|
||||
},
|
||||
diffBuilder = jasmineUnderTest.DiffBuilder({prettyPrinter: prettyPrinter});
|
||||
prettyPrinter.customFormat_ = function () {
|
||||
};
|
||||
|
||||
diffBuilder.withPath('foo', function() {
|
||||
diffBuilder.record('actual', 'expected');
|
||||
diffBuilder.setRoots({foo: 'actual'}, {foo: 'expected'});
|
||||
diffBuilder.withPath('foo', function () {
|
||||
diffBuilder.recordMismatch();
|
||||
});
|
||||
|
||||
expect(diffBuilder.getMessage()).toEqual("Expected $.foo = |actual| to equal |expected|.");
|
||||
});
|
||||
|
||||
|
||||
it("passes the injected pretty-printer to the diff formatter", function() {
|
||||
it("passes the injected pretty-printer to the diff formatter", function () {
|
||||
var diffFormatter = jasmine.createSpy('diffFormatter'),
|
||||
prettyPrinter = function() {},
|
||||
prettyPrinter = function () {
|
||||
},
|
||||
diffBuilder = jasmineUnderTest.DiffBuilder({prettyPrinter: prettyPrinter});
|
||||
prettyPrinter.customFormat_ = function () {
|
||||
};
|
||||
|
||||
diffBuilder.withPath('x', function() {
|
||||
diffBuilder.record('bar', 'foo', diffFormatter);
|
||||
diffBuilder.setRoots({x: 'bar'}, {x: 'foo'});
|
||||
diffBuilder.withPath('x', function () {
|
||||
diffBuilder.recordMismatch(diffFormatter);
|
||||
});
|
||||
|
||||
diffBuilder.getMessage();
|
||||
|
||||
expect(diffFormatter).toHaveBeenCalledWith('bar', 'foo', jasmine.anything(), prettyPrinter);
|
||||
});
|
||||
|
||||
it("uses custom object formatters on leaf nodes", function() {
|
||||
var formatter = function(x) {
|
||||
if (typeof x === 'number') {
|
||||
return '[number:' + x + ']';
|
||||
}
|
||||
};
|
||||
prettyPrinter = jasmineUnderTest.makePrettyPrinter([formatter]);
|
||||
var diffBuilder = new jasmineUnderTest.DiffBuilder({prettyPrinter: prettyPrinter});
|
||||
|
||||
diffBuilder.setRoots(5, 4);
|
||||
diffBuilder.recordMismatch();
|
||||
|
||||
expect(diffBuilder.getMessage()).toEqual('Expected [number:5] to equal [number:4].');
|
||||
});
|
||||
|
||||
|
||||
it("uses custom object formatters on non leaf nodes", function () {
|
||||
var formatter = function (x) {
|
||||
if (x.hasOwnProperty('a')) {
|
||||
return '[thing with a=' + x.a + ', b=' + JSON.stringify(x.b) + ']';
|
||||
}
|
||||
};
|
||||
prettyPrinter = jasmineUnderTest.makePrettyPrinter([formatter]);
|
||||
var diffBuilder = new jasmineUnderTest.DiffBuilder({prettyPrinter: prettyPrinter});
|
||||
var expectedMsg = 'Expected $[0].foo = [thing with a=1, b={"x":42}] to equal [thing with a=1, b={"x":43}].\n' +
|
||||
"Expected $[0].bar = 'yes' to equal 'no'.";
|
||||
|
||||
diffBuilder.setRoots(
|
||||
[{foo: {a: 1, b: {x: 42}}, bar: 'yes'}],
|
||||
[{foo: {a: 1, b: {x: 43}}, bar: 'no'}]
|
||||
);
|
||||
|
||||
diffBuilder.withPath(0, function () {
|
||||
diffBuilder.withPath('foo', function () {
|
||||
diffBuilder.withPath('b', function () {
|
||||
diffBuilder.withPath('x', function () {
|
||||
diffBuilder.recordMismatch();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
diffBuilder.withPath('bar', function () {
|
||||
diffBuilder.recordMismatch();
|
||||
});
|
||||
});
|
||||
|
||||
expect(diffBuilder.getMessage()).toEqual(expectedMsg);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
describe('MismatchTree', function () {
|
||||
describe('#add', function () {
|
||||
describe('When the path is empty', function () {
|
||||
it('flags the root node as mismatched', function () {
|
||||
var tree = new jasmineUnderTest.MismatchTree();
|
||||
tree.add(new jasmineUnderTest.ObjectPath([]));
|
||||
expect(tree.isMismatch).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the path is not empty', function () {
|
||||
it('flags the node as mismatched', function () {
|
||||
var tree = new jasmineUnderTest.MismatchTree();
|
||||
|
||||
tree.add(new jasmineUnderTest.ObjectPath(['a', 'b']));
|
||||
|
||||
expect(tree.child('a').child('b').isMismatch).toBe(true);
|
||||
});
|
||||
|
||||
it('does not flag ancestors as mismatched', function () {
|
||||
var tree = new jasmineUnderTest.MismatchTree();
|
||||
|
||||
tree.add(new jasmineUnderTest.ObjectPath(['a', 'b']));
|
||||
|
||||
expect(tree.isMismatch).toBe(false);
|
||||
expect(tree.child('a').isMismatch).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('stores the formatter on only the target node', function () {
|
||||
var tree = new jasmineUnderTest.MismatchTree();
|
||||
|
||||
tree.add(new jasmineUnderTest.ObjectPath(['a', 'b']), formatter);
|
||||
|
||||
expect(tree.formatter).toBeFalsy();
|
||||
expect(tree.child('a').formatter).toBeFalsy();
|
||||
expect(tree.child('a').child('b').formatter).toBe(formatter);
|
||||
});
|
||||
|
||||
it('stores the path to the node', function () {
|
||||
var tree = new jasmineUnderTest.MismatchTree();
|
||||
|
||||
tree.add(new jasmineUnderTest.ObjectPath(['a', 'b']), formatter);
|
||||
|
||||
expect(tree.child('a').child('b').path.components).toEqual(['a', 'b']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#traverse', function () {
|
||||
it('calls the callback for all nodes that are or contain mismatches', function () {
|
||||
var tree = new jasmineUnderTest.MismatchTree();
|
||||
tree.add(new jasmineUnderTest.ObjectPath(['a', 'b']), formatter);
|
||||
tree.add(new jasmineUnderTest.ObjectPath(['c']));
|
||||
var visit = jasmine.createSpy('visit').and.returnValue(true);
|
||||
|
||||
tree.traverse(visit);
|
||||
|
||||
expect(visit).toHaveBeenCalledWith(
|
||||
new jasmineUnderTest.ObjectPath([]), false, undefined
|
||||
);
|
||||
expect(visit).toHaveBeenCalledWith(
|
||||
new jasmineUnderTest.ObjectPath(['a']), false, undefined
|
||||
);
|
||||
expect(visit).toHaveBeenCalledWith(
|
||||
new jasmineUnderTest.ObjectPath(['a', 'b']), true, formatter
|
||||
);
|
||||
expect(visit).toHaveBeenCalledWith(
|
||||
new jasmineUnderTest.ObjectPath(['c']), true, undefined
|
||||
);
|
||||
});
|
||||
|
||||
it('does not call the callback if there are no mismatches', function () {
|
||||
var tree = new jasmineUnderTest.MismatchTree();
|
||||
var visit = jasmine.createSpy('visit');
|
||||
|
||||
tree.traverse(visit);
|
||||
|
||||
expect(visit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('visits parents before children', function () {
|
||||
var tree = new jasmineUnderTest.MismatchTree();
|
||||
tree.add(new jasmineUnderTest.ObjectPath(['a', 'b']));
|
||||
var visited = [];
|
||||
|
||||
tree.traverse(function (path) {
|
||||
visited.push(path);
|
||||
return true;
|
||||
});
|
||||
|
||||
expect(visited).toEqual([
|
||||
new jasmineUnderTest.ObjectPath([]),
|
||||
new jasmineUnderTest.ObjectPath(['a']),
|
||||
new jasmineUnderTest.ObjectPath(['a', 'b'])
|
||||
]);
|
||||
});
|
||||
|
||||
it('visits children in the order they were recorded', function() {
|
||||
var tree = new jasmineUnderTest.MismatchTree();
|
||||
tree.add(new jasmineUnderTest.ObjectPath(['length']));
|
||||
tree.add(new jasmineUnderTest.ObjectPath([1]));
|
||||
var visited = [];
|
||||
|
||||
tree.traverse(function (path) {
|
||||
visited.push(path);
|
||||
return true;
|
||||
});
|
||||
|
||||
expect(visited).toEqual([
|
||||
new jasmineUnderTest.ObjectPath([]),
|
||||
new jasmineUnderTest.ObjectPath(['length']),
|
||||
new jasmineUnderTest.ObjectPath([1])
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not visit children if the callback returns falsy', function() {
|
||||
var tree = new jasmineUnderTest.MismatchTree();
|
||||
tree.add(new jasmineUnderTest.ObjectPath(['a', 'b']));
|
||||
var visited = [];
|
||||
|
||||
tree.traverse(function (path) {
|
||||
visited.push(path);
|
||||
return path.depth() === 0;
|
||||
});
|
||||
|
||||
expect(visited).toEqual([
|
||||
new jasmineUnderTest.ObjectPath([]),
|
||||
new jasmineUnderTest.ObjectPath(['a'])
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
function formatter() {
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,13 +1,8 @@
|
||||
describe('NullDiffBuilder', function() {
|
||||
it('responds to withPath() by calling the passed function', function() {
|
||||
describe('NullDiffBuilder', function () {
|
||||
it('responds to withPath() by calling the passed function', function () {
|
||||
var spy = jasmine.createSpy('callback');
|
||||
jasmineUnderTest.NullDiffBuilder().withPath('does not matter', spy);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('responds to record()', function() {
|
||||
expect(function() {
|
||||
jasmineUnderTest.NullDiffBuilder().record('does not matter');
|
||||
}).not.toThrow();
|
||||
})
|
||||
});
|
||||
|
||||
@@ -39,5 +39,13 @@ describe('ObjectPath', function() {
|
||||
|
||||
expect(path.toString()).toEqual('$.foo');
|
||||
expect(root.toString()).toEqual('');
|
||||
})
|
||||
});
|
||||
|
||||
describe('#dereference', function() {
|
||||
it('returns the value corresponding to the path', function () {
|
||||
var path = new ObjectPath().add('foo').add(1).add('bar');
|
||||
var obj = {foo: ['', {bar: 42}]};
|
||||
expect(path.dereference(obj)).toEqual(42);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -730,26 +730,26 @@ describe("matchersUtil", function() {
|
||||
var diffBuilder = new jasmineUnderTest.DiffBuilder(),
|
||||
matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
|
||||
spyOn(diffBuilder, 'record');
|
||||
spyOn(diffBuilder, 'recordMismatch');
|
||||
spyOn(diffBuilder, 'withPath').and.callThrough();
|
||||
|
||||
matchersUtil.equals([1], [2], [], diffBuilder);
|
||||
expect(diffBuilder.withPath).toHaveBeenCalledWith('length', jasmine.any(Function));
|
||||
expect(diffBuilder.withPath).toHaveBeenCalledWith(0, jasmine.any(Function));
|
||||
expect(diffBuilder.record).toHaveBeenCalledWith(1, 2);
|
||||
expect(diffBuilder.recordMismatch).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('uses a diffBuilder if one is provided as the third argument', function() {
|
||||
var diffBuilder = new jasmineUnderTest.DiffBuilder(),
|
||||
matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
|
||||
spyOn(diffBuilder, 'record');
|
||||
spyOn(diffBuilder, 'recordMismatch');
|
||||
spyOn(diffBuilder, 'withPath').and.callThrough();
|
||||
|
||||
matchersUtil.equals([1], [2], diffBuilder);
|
||||
expect(diffBuilder.withPath).toHaveBeenCalledWith('length', jasmine.any(Function));
|
||||
expect(diffBuilder.withPath).toHaveBeenCalledWith(0, jasmine.any(Function));
|
||||
expect(diffBuilder.record).toHaveBeenCalledWith(1, 2);
|
||||
expect(diffBuilder.recordMismatch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -97,8 +97,12 @@ describe("toEqual", function() {
|
||||
expect(compareEquals(actual, expected).message).toEqual(message);
|
||||
});
|
||||
|
||||
it("uses custom object formatters to pretty-print properties", function() {
|
||||
function formatter(x) { return '|' + x + '|'; }
|
||||
it("uses custom object formatters to pretty-print simple properties", function() {
|
||||
function formatter(x) {
|
||||
if (typeof x === 'number') {
|
||||
return '|' + x + '|';
|
||||
}
|
||||
}
|
||||
|
||||
var actual = {x: {y: 1, z: 2, f: 4}},
|
||||
expected = {x: {y: 1, z: 2, g: 3}},
|
||||
@@ -114,8 +118,12 @@ describe("toEqual", function() {
|
||||
expect(matcher.compare(actual, expected).message).toEqual(message);
|
||||
});
|
||||
|
||||
it("uses custom object formatters to build diffs", function() {
|
||||
function formatter(x) { return '|' + x + '|'; }
|
||||
it("uses custom object formatters to show simple values in diffs", function() {
|
||||
function formatter(x) {
|
||||
if (typeof x === 'number') {
|
||||
return '|' + x + '|';
|
||||
}
|
||||
}
|
||||
|
||||
var actual = [{foo: 4}],
|
||||
expected = [{foo: 5}],
|
||||
@@ -127,6 +135,30 @@ describe("toEqual", function() {
|
||||
expect(matcher.compare(actual, expected).message).toEqual(message);
|
||||
});
|
||||
|
||||
it("uses custom object formatters to show more complex objects diffs", function() {
|
||||
function formatter(x) {
|
||||
if (x.hasOwnProperty('a')) {
|
||||
return '[thing with a=' + x.a + ', b=' + x.b + ']';
|
||||
}
|
||||
}
|
||||
|
||||
var actual = [{
|
||||
foo: {a: 1, b: 2},
|
||||
bar: 'should not be pretty printed'
|
||||
}],
|
||||
expected = [{
|
||||
foo: {a: 5, b: 2},
|
||||
bar: "shouldn't be pretty printed"
|
||||
}],
|
||||
prettyPrinter = jasmineUnderTest.makePrettyPrinter([formatter]),
|
||||
util = new jasmineUnderTest.MatchersUtil({pp: prettyPrinter}),
|
||||
matcher = jasmineUnderTest.matchers.toEqual(util),
|
||||
message = "Expected $[0].foo = [thing with a=1, b=2] to equal [thing with a=5, b=2].\n" +
|
||||
"Expected $[0].bar = 'should not be pretty printed' to equal 'shouldn't be pretty printed'.";
|
||||
|
||||
expect(matcher.compare(actual, expected).message).toEqual(message);
|
||||
});
|
||||
|
||||
it("reports extra and missing properties of the root-level object", function() {
|
||||
var actual = {x: 1},
|
||||
expected = {a: 1},
|
||||
@@ -303,10 +335,14 @@ describe("toEqual", function() {
|
||||
expect(compareEquals(actual, expected).message).toEqual(message);
|
||||
});
|
||||
|
||||
it("uses custom object formatters to report objects with different constructors", function () {
|
||||
it("uses custom object formatters for the value but not the type when reporting objects with different constructors", function () {
|
||||
function Foo() {}
|
||||
function Bar() {}
|
||||
function formatter(x) { return '|' + x + '|'; }
|
||||
function formatter(x) {
|
||||
if (x instanceof Foo || x instanceof Bar) {
|
||||
return '|' + x + '|';
|
||||
}
|
||||
}
|
||||
|
||||
var actual = {x: new Foo()},
|
||||
expected = {x: new Bar()},
|
||||
@@ -329,6 +365,11 @@ describe("toEqual", function() {
|
||||
expect(compareEquals(actual, expected).message).toEqual(message);
|
||||
});
|
||||
|
||||
it("reports value mismatches at the root level", function() {
|
||||
expect(compareEquals(1, 2).message).toEqual("Expected 1 to equal 2.");
|
||||
});
|
||||
|
||||
|
||||
it("reports mismatches between objects with their own constructor property", function () {
|
||||
function Foo() {}
|
||||
function Bar() {}
|
||||
@@ -839,7 +880,11 @@ describe("toEqual", function() {
|
||||
});
|
||||
|
||||
it("uses custom object formatters when the actual array is longer", function() {
|
||||
function formatter(x) { return '|' + x + '|'; }
|
||||
function formatter(x) {
|
||||
if (typeof x === 'number') {
|
||||
return '|' + x + '|';
|
||||
}
|
||||
}
|
||||
|
||||
var actual = [1, 1, 2, 3, 5],
|
||||
expected = [1, 1, 2, 3],
|
||||
|
||||
Reference in New Issue
Block a user