diff --git a/lib/assertThat.js b/lib/assertThat.js index 058a10e..9d00985 100644 --- a/lib/assertThat.js +++ b/lib/assertThat.js @@ -28,13 +28,9 @@ function assertThat(reason, actual, matcher) { matcher.describeMismatch(actual, description); var errorProperties = {}; - if (_.isFunction(matcher.getExpectedForDiff) && - _.isFunction(matcher.formatActualForDiff)) { - errorProperties = { - showDiff: true, - expected: matcher.getExpectedForDiff(), - actual: matcher.formatActualForDiff(actual) - }; + if (_.isFunction(matcher.getDiffItems)) { + errorProperties = matcher.getDiffItems(actual); + errorProperties.showDiff = true; } throw new AssertionError(description.get(), errorProperties, assertThat); diff --git a/lib/matchers/Is.js b/lib/matchers/Is.js index 4abd5ae..46b57e1 100644 --- a/lib/matchers/Is.js +++ b/lib/matchers/Is.js @@ -18,8 +18,7 @@ var Is = acceptingMatcher(function Is(innerMatcher) { describeMismatch: function (value, description) { return innerMatcher.describeMismatch(value, description); }, - getExpectedForDiff: innerMatcher.getExpectedForDiff, - formatActualForDiff: innerMatcher.formatActualForDiff + getDiffItems: innerMatcher.getDiffItems }); }); diff --git a/lib/matchers/IsEqual.js b/lib/matchers/IsEqual.js index cf08416..4f9ce29 100644 --- a/lib/matchers/IsEqual.js +++ b/lib/matchers/IsEqual.js @@ -12,8 +12,12 @@ function IsEqual(expectedValue) { describeTo: function (description) { description.appendValue(expectedValue); }, - getExpectedForDiff: function () { return expectedValue; }, - formatActualForDiff: function (actual) { return actual; } + getDiffItems: function (actual) { + return { + expected: expectedValue, + actual: actual + }; + } }); } diff --git a/lib/matchers/IsObjectWithProperties.js b/lib/matchers/IsObjectWithProperties.js index 7431bb0..765f0cc 100644 --- a/lib/matchers/IsObjectWithProperties.js +++ b/lib/matchers/IsObjectWithProperties.js @@ -5,6 +5,7 @@ var _ = require('lodash') , asMatcher = require('./IsEqual').asMatcher , defined = require('./IsDefined').defined ; +var Description = require('../Description'); var promiseAgnostic = require('./promiseAgnostic'); function IsObjectWithProperties(properties) { @@ -56,6 +57,33 @@ function IsObjectWithProperties(properties) { .append(' '); return propertyMatchers[key].describeMismatch(actual[key], description); }); + }, + + getDiffItems: function (actual) { + var relevantMatchers = _.pick(propertyMatchers, function (matcher, key) { + return !matcher.matches(actual[key]); + }); + + var guessDiffItems = function (matcher, actual) { + var description = new Description(); + matcher.describeTo(description); + return { + expected: description.get(), + actual: actual + }; + }; + + var diffItemsIndividually = _.mapValues(relevantMatchers, function (matcher, key) { + if (_.isFunction(matcher.getDiffItems)) { + return matcher.getDiffItems(actual[key]); + } + return guessDiffItems(matcher, actual[key]); + }); + + return { + expected: _.mapValues(diffItemsIndividually, 'expected'), + actual: _.mapValues(diffItemsIndividually, 'actual') + }; } }); } diff --git a/lib/matchers/SubstringMatcher.js b/lib/matchers/SubstringMatcher.js index 446477f..e2f3b0f 100644 --- a/lib/matchers/SubstringMatcher.js +++ b/lib/matchers/SubstringMatcher.js @@ -26,8 +26,12 @@ function SubstringMatcher(substring, relation, matchesString) { .append('was ') .appendValue(actual); }, - getExpectedForDiff: function () { return substring; }, - formatActualForDiff: function (actual) { return actual; } + getDiffItems: function (actual) { + return { + expected: substring, + actual: actual + }; + } }); } diff --git a/lib/promiseThat.js b/lib/promiseThat.js index a65a279..d2f20aa 100644 --- a/lib/promiseThat.js +++ b/lib/promiseThat.js @@ -20,14 +20,14 @@ function promiseThat(reason, actual, matcher) { .appendDescriptionOf(matcher) .append('\n but: '); return q(matcher.describeMismatch(actual, description)).then(function () { - if (!_.isFunction(matcher.getExpectedForDiff) || - !_.isFunction(matcher.formatActualForDiff)) { + if (!_.isFunction(matcher.getDiffItems)) { return {}; } + var diffItems = matcher.getDiffItems(actual); return q.all([ - matcher.getExpectedForDiff(), - matcher.formatActualForDiff(actual) + diffItems.expected, + diffItems.actual ]).spread(function (expected, actual) { return { showDiff: true, diff --git a/test/assertThatSpec.js b/test/assertThatSpec.js index 492dd18..fc57851 100644 --- a/test/assertThatSpec.js +++ b/test/assertThatSpec.js @@ -63,11 +63,11 @@ describe('assertThat', function () { var thrown; var testMatcher = new TestMatcher(function () { return false; }); - testMatcher.getExpectedForDiff = function () { - return 'expected for diff'; - }; - testMatcher.formatActualForDiff = function (actual) { - return 'actual for diff: ' + actual; + testMatcher.getDiffItems = function (actual) { + return { + expected: 'expected for diff', + actual: 'actual for diff: ' + actual + }; }; try { diff --git a/test/matchers/IsObjectWithPropertiesSpec.js b/test/matchers/IsObjectWithPropertiesSpec.js index 22504ec..46f970b 100644 --- a/test/matchers/IsObjectWithPropertiesSpec.js +++ b/test/matchers/IsObjectWithPropertiesSpec.js @@ -5,6 +5,7 @@ var IsObjectWithProperties = require('../../lib/matchers/IsObjectWithProperties' , __ = require('../../lib/hamjest') ; var deferMatcher = require('../deferMatcher'); +var TestMatcher = require('../TestMatcher'); describe('IsObjectWithProperties', function () { @@ -80,6 +81,61 @@ describe('IsObjectWithProperties', function () { }); }); + describe('diff', function() { + it('should exist', function () { + __.assertThat(sut.getDiffItems({}), __.hasProperties({ + expected: __.defined(), + actual: __.defined() + })); + }); + + it('should contain mismatched properties', function () { + var diffObjects = sut.getDiffItems(new Person('Jim', 2)); + __.assertThat(diffObjects, __.everyItem(__.hasProperty('name'))); + }); + + it('should omit matched properties', function () { + var diffObjects = sut.getDiffItems({name: 'Joe', children: 0}); + __.assertThat(diffObjects, __.everyItem(__.not(__.hasProperty('name')))); + }); + + it('should omit extra properties', function () { + var diffObjects = sut.getDiffItems({foo: 'bar'}); + __.assertThat(diffObjects, __.everyItem(__.not(__.hasProperty('foo')))); + }); + + it('should use diff from diff-capable nested matcher', function () { + var testMatcher = new TestMatcher(function () { return false; }); + testMatcher.getDiffItems = function (actual) { + return { + expected: 'expected from nested', + actual: 'actual from nested: ' + actual + }; + }; + var sutWithNestedMatcher = hasProperties({a: testMatcher}); + var diffObjects = sutWithNestedMatcher.getDiffItems({a: 1}); + __.assertThat(diffObjects, __.hasProperties({ + expected: {a: 'expected from nested'}, + actual: {a: 'actual from nested: 1'} + })); + }); + + it('should use actual and mismatch description from non-diff-capable nested matcher', function () { + var testMatcher = new TestMatcher(function () { return false; }); + + var description = new Description(); + testMatcher.describeTo(description); + + var sutWithNestedMatcher = hasProperties({a: testMatcher}); + var diffObjects = sutWithNestedMatcher.getDiffItems({a: 1}); + + __.assertThat(diffObjects, __.hasProperties({ + expected: {a: description.get()}, + actual: {a: 1} + })); + }); + }); + describe('with a promising matcher', function () { beforeEach(function () { sut = hasProperties({ diff --git a/test/matchers/SubstringMatcherSpec.js b/test/matchers/SubstringMatcherSpec.js index fe3a9d7..28ed8b1 100644 --- a/test/matchers/SubstringMatcherSpec.js +++ b/test/matchers/SubstringMatcherSpec.js @@ -6,7 +6,6 @@ var AssertionError = require('assertion-error') , __ = require('../../lib/hamjest') , assertTrue = require('../asserts').assertTrue , assertFalse = require('../asserts').assertFalse - , assertEquals = require('../asserts').assertEquals ; describe('SubstringMatcher', function () { @@ -41,12 +40,11 @@ describe('SubstringMatcher', function () { assertFalse(sut.matches(5)); }); - it('should provide expected for diff', function () { - assertEquals('a value', sut.getExpectedForDiff()); - }); - - it('should format actual for diff', function() { - assertEquals('foo', sut.formatActualForDiff('foo')); + it('should provide diff items', function () { + __.assertThat(sut.getDiffItems('foo'), __.equalTo({ + expected: 'a value', + actual: 'foo' + })); }); describe('description', function () { diff --git a/test/promiseThatSpec.js b/test/promiseThatSpec.js index 85b2ad5..b62f924 100644 --- a/test/promiseThatSpec.js +++ b/test/promiseThatSpec.js @@ -160,11 +160,11 @@ describe('promiseThat', function () { it('should pass diff representations to AssertionError', function (done) { var testMatcher = new TestMatcher(function () { return false; }); - testMatcher.getExpectedForDiff = function () { - return 'expected for diff'; - }; - testMatcher.formatActualForDiff = function (actual) { - return q('actual for diff: ' + actual); + testMatcher.getDiffItems = function (actual) { + return { + expected: 'expected for diff', + actual: q('actual for diff: ' + actual) + }; }; promiseThat('actual value', testMatcher).done(