diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index a3f303b6..b442632e 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -2519,6 +2519,27 @@ getJasmineRequireObj().ObjectContaining = function(j$) { return true; }; + ObjectContaining.prototype.valuesForDiff_ = function(other) { + if (!j$.isObject_(other)) { + return { + self: this.jasmineToString(), + other: other + }; + } + + var filteredOther = {}; + Object.keys(this.sample).forEach(function (k) { + // eq short-circuits comparison of objects that have different key sets, + // so include all keys even if undefined. + filteredOther[k] = other[k]; + }); + + return { + self: this.sample, + other: filteredOther + }; + }; + ObjectContaining.prototype.jasmineToString = function() { return ''; }; @@ -4192,7 +4213,16 @@ getJasmineRequireObj().matchersUtil = function(j$) { return obj && j$.isA_('Function', obj.asymmetricMatch); } - function asymmetricMatch(a, b, customTesters, diffBuilder) { + function asymmetricDiff(a, b, aStack, bStack, customTesters, diffBuilder) { + if (j$.isFunction_(b.valuesForDiff_)) { + var values = b.valuesForDiff_(a); + eq(values.other, values.self, aStack, bStack, customTesters, diffBuilder); + } else { + diffBuilder.record(a, b); + } + } + + function asymmetricMatch(a, b, aStack, bStack, customTesters, diffBuilder) { var asymmetricA = isAsymmetric(a), asymmetricB = isAsymmetric(b), result; @@ -4204,6 +4234,8 @@ getJasmineRequireObj().matchersUtil = function(j$) { if (asymmetricA) { result = a.asymmetricMatch(b, customTesters); if (!result) { + // TODO: Do we want to build an asymmetric diff when the actual was an + // asymmeteric equality tester? Might be confusing. diffBuilder.record(a, b); } return result; @@ -4212,7 +4244,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { if (asymmetricB) { result = b.asymmetricMatch(a, customTesters); if (!result) { - diffBuilder.record(a, b); + asymmetricDiff(a, b, aStack, bStack, customTesters, diffBuilder); } return result; } @@ -4230,7 +4262,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { function eq(a, b, aStack, bStack, customTesters, diffBuilder) { var result = true, i; - var asymmetricResult = asymmetricMatch(a, b, customTesters, diffBuilder); + var asymmetricResult = asymmetricMatch(a, b, aStack, bStack, customTesters, diffBuilder); if (!j$.util.isUndefined(asymmetricResult)) { return asymmetricResult; } diff --git a/spec/core/asymmetric_equality/ObjectContainingSpec.js b/spec/core/asymmetric_equality/ObjectContainingSpec.js index e2741a9d..b17fd925 100644 --- a/spec/core/asymmetric_equality/ObjectContainingSpec.js +++ b/spec/core/asymmetric_equality/ObjectContainingSpec.js @@ -96,4 +96,65 @@ describe("ObjectContaining", function() { expect(containing.asymmetricMatch({foo: "fooBar"}, [tester])).toBe(true); }); + + describe("valuesForDiff_", function() { + describe("when other is not an object", function() { + it("sets self to jasmineToString()", function () { + var containing = new jasmineUnderTest.ObjectContaining({}), + result = containing.valuesForDiff_('a'); + + expect(result).toEqual({ + self: '', + other: 'a' + }); + }); + }); + + describe("when other is an object", function() { + it("includes keys that are present in both other and sample", function() { + var sample = {a: 1, b: 2}, + other = {a: 3, b: 4}, + containing = new jasmineUnderTest.ObjectContaining(sample), + result = containing.valuesForDiff_(other); + + expect(result.self).not.toBeInstanceOf(jasmineUnderTest.ObjectContaining); + expect(result).toEqual({ + self: sample, + other: other + }); + }); + + it("includes keys that are present in only sample", function() { + var sample = {a: 1, b: 2}, + other = {a: 3}, + containing = new jasmineUnderTest.ObjectContaining(sample), + result = containing.valuesForDiff_(other); + + expect(result.self).not.toBeInstanceOf(jasmineUnderTest.ObjectContaining); + expect(containing.valuesForDiff_(other)).toEqual({ + self: sample, + other: { + a: 3, + b: undefined + } + }); + }); + + it("omits keys that are present only in other", function() { + var sample = {a: 1, b: 2}, + other = {a: 3, b: 4, c: 5}, + containing = new jasmineUnderTest.ObjectContaining(sample), + result = containing.valuesForDiff_(other); + + expect(result.self).not.toBeInstanceOf(jasmineUnderTest.ObjectContaining); + expect(result).toEqual({ + self: sample, + other: { + a: 3, + b: 4 + } + }); + }); + }); + }); }); diff --git a/spec/core/matchers/matchersUtilSpec.js b/spec/core/matchers/matchersUtilSpec.js index 9dd42ce5..b2146e94 100644 --- a/spec/core/matchers/matchersUtilSpec.js +++ b/spec/core/matchers/matchersUtilSpec.js @@ -593,6 +593,42 @@ describe("matchersUtil", function() { expect(['foo']).toEqual(['foo']); }); }); + + describe("Building diffs for asymmetric equality testers", function() { + it("diffs the values returned by valuesForDiff_", function() { + var tester = { + asymmetricMatch: function() { return false; }, + valuesForDiff_: function() { + return { + self: 'asymmetric tester value', + other: 'other value' + } + } + }, + diffBuilder = jasmine.createSpyObj('diffBuilder', ['record', 'withPath']); + diffBuilder.withPath.and.callFake(function(p, block) { block() }); + debugger; + jasmineUnderTest.matchersUtil.equals({x: 42}, {x: tester}, [], diffBuilder); + + expect(diffBuilder.withPath).toHaveBeenCalledWith('x', jasmine.any(Function)); + expect(diffBuilder.record). toHaveBeenCalledWith( + 'other value', 'asymmetric tester value' + ); + }); + + it("records both objects when the tester does not implement valuesForDiff", function() { + var tester = { + asymmetricMatch: function() { return false; }, + }, + diffBuilder = jasmine.createSpyObj('diffBuilder', ['record', 'withPath']); + diffBuilder.withPath.and.callFake(function(p, block) { block() }); + debugger; + jasmineUnderTest.matchersUtil.equals({x: 42}, {x: tester}, [], diffBuilder); + + expect(diffBuilder.withPath).toHaveBeenCalledWith('x', jasmine.any(Function)); + expect(diffBuilder.record). toHaveBeenCalledWith(42, tester); + }); + }); }); describe("contains", function() { diff --git a/spec/core/matchers/toEqualSpec.js b/spec/core/matchers/toEqualSpec.js index 345eb724..ca23679f 100644 --- a/spec/core/matchers/toEqualSpec.js +++ b/spec/core/matchers/toEqualSpec.js @@ -350,6 +350,20 @@ describe("toEqual", function() { expect(compareEquals(actual, expected).message).toEqual(message); }); + it("reports mismatches involving objectContaining", function() { + var actual = {x: {a: 1, b: 4, c: 3, extra: 'ignored'}}; + var expected = {x: jasmineUnderTest.objectContaining({a: 1, b: 2, c: 3})}; + expect(compareEquals(actual, expected).message).toEqual('Expected $.x.b = 4 to equal 2.') + }); + + it("reports mismatches between a non-object and objectContaining", function() { + var actual = {x: 1}; + var expected = {x: jasmineUnderTest.objectContaining({a: 1})}; + expect(compareEquals(actual, expected).message).toEqual( + "Expected $.x = 1 to equal ''." + ); + }); + // == Sets == it("reports mismatches between Sets", function() { diff --git a/src/core/asymmetric_equality/ObjectContaining.js b/src/core/asymmetric_equality/ObjectContaining.js index 62e9bf17..9830fe90 100644 --- a/src/core/asymmetric_equality/ObjectContaining.js +++ b/src/core/asymmetric_equality/ObjectContaining.js @@ -41,6 +41,27 @@ getJasmineRequireObj().ObjectContaining = function(j$) { return true; }; + ObjectContaining.prototype.valuesForDiff_ = function(other) { + if (!j$.isObject_(other)) { + return { + self: this.jasmineToString(), + other: other + }; + } + + var filteredOther = {}; + Object.keys(this.sample).forEach(function (k) { + // eq short-circuits comparison of objects that have different key sets, + // so include all keys even if undefined. + filteredOther[k] = other[k]; + }); + + return { + self: this.sample, + other: filteredOther + }; + }; + ObjectContaining.prototype.jasmineToString = function() { return ''; }; diff --git a/src/core/matchers/matchersUtil.js b/src/core/matchers/matchersUtil.js index cfc0c749..522759a5 100644 --- a/src/core/matchers/matchersUtil.js +++ b/src/core/matchers/matchersUtil.js @@ -55,7 +55,16 @@ getJasmineRequireObj().matchersUtil = function(j$) { return obj && j$.isA_('Function', obj.asymmetricMatch); } - function asymmetricMatch(a, b, customTesters, diffBuilder) { + function asymmetricDiff(a, b, aStack, bStack, customTesters, diffBuilder) { + if (j$.isFunction_(b.valuesForDiff_)) { + var values = b.valuesForDiff_(a); + eq(values.other, values.self, aStack, bStack, customTesters, diffBuilder); + } else { + diffBuilder.record(a, b); + } + } + + function asymmetricMatch(a, b, aStack, bStack, customTesters, diffBuilder) { var asymmetricA = isAsymmetric(a), asymmetricB = isAsymmetric(b), result; @@ -67,6 +76,8 @@ getJasmineRequireObj().matchersUtil = function(j$) { if (asymmetricA) { result = a.asymmetricMatch(b, customTesters); if (!result) { + // TODO: Do we want to build an asymmetric diff when the actual was an + // asymmeteric equality tester? Might be confusing. diffBuilder.record(a, b); } return result; @@ -75,7 +86,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { if (asymmetricB) { result = b.asymmetricMatch(a, customTesters); if (!result) { - diffBuilder.record(a, b); + asymmetricDiff(a, b, aStack, bStack, customTesters, diffBuilder); } return result; } @@ -93,7 +104,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { function eq(a, b, aStack, bStack, customTesters, diffBuilder) { var result = true, i; - var asymmetricResult = asymmetricMatch(a, b, customTesters, diffBuilder); + var asymmetricResult = asymmetricMatch(a, b, aStack, bStack, customTesters, diffBuilder); if (!j$.util.isUndefined(asymmetricResult)) { return asymmetricResult; }