From dec67bd535bfeeb9e4953f14c35ff56874aabcf9 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Sat, 21 Sep 2019 10:58:09 -0700 Subject: [PATCH 1/8] Don't require matchers and asymmetric equality testers to pass custom object formatters back to Jasmine This makes it easier to write high quality matchers and asymmetric equality testers, and is also a step toward supporting custom object formatters. Previously, Jasmine passed custom object formatters as the second argument to matcher factories and as and the second argument to asymmetric equality testers' `asymmetricMatch` method. Matchers and asymmetric equality testers were responsible for passing the custom object formatters to methods like `matchersUtil#equals`: function toEqual(util, customEqualityTesters) { return { compare: function(actual, expected) { // ... result.pass = util.equals(actual, expected, customEqualityTesters, diffBuilder); And: ArrayContaining.prototype.asymmetricMatch = function(other, customTesters) { // ... for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; if (!j$.matchersUtil.contains(other, item, customTesters)) { return false; } } With this change, that is no longer necessary. Matchers and asymmetric equality testers can ignore the existence of custom equality testers and still fully support them: function toEqual(util) { return { compare: function(actual, expected) { // ... result.pass = util.equals(actual, expected, diffBuilder); And: ArrayContaining.prototype.asymmetricMatch = function(other, matchersUtil) { // ... for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; if (!matchersUtil.contains(other, item)) { return false; } } The old interfaces are still supported, for now, but will be deprecated in a future commit and removed in the next major release after that. In addition to making matchers and custom equality testers simpler, this change sets the stage for adding support for custom object formatters. Those will be architecturally similar to custom equality testers, and by injecting a `MatchersUtil` instance everywhere we can add them without requiring user code to pass them around as used to be the case with custom object formatters. --- lib/jasmine-core/jasmine.js | 332 ++++++++++---- spec/core/AsyncExpectationSpec.js | 8 +- spec/core/EnvSpec.js | 58 +++ spec/core/ExpectationSpec.js | 2 +- ...ymmetricEqualityTesterArgCompatShimSpec.js | 101 ++++ .../ArrayContainingSpec.js | 15 +- .../ArrayWithExactContentsSpec.js | 14 +- .../asymmetric_equality/MapContainingSpec.js | 36 +- .../ObjectContainingSpec.js | 30 +- .../asymmetric_equality/SetContainingSpec.js | 22 +- .../integration/CustomAsyncMatchersSpec.js | 32 ++ spec/core/integration/CustomMatchersSpec.js | 46 +- spec/core/integration/EnvSpec.js | 31 +- spec/core/integration/MatchersSpec.js | 5 +- spec/core/matchers/async/toBeRejectedSpec.js | 9 +- .../async/toBeRejectedWithErrorSpec.js | 36 +- .../matchers/async/toBeRejectedWithSpec.js | 18 +- spec/core/matchers/async/toBeResolvedSpec.js | 9 +- .../core/matchers/async/toBeResolvedToSpec.js | 18 +- spec/core/matchers/matchersUtilSpec.js | 433 ++++++++++++------ spec/core/matchers/toBeSpec.js | 18 +- spec/core/matchers/toContainSpec.js | 15 +- spec/core/matchers/toEqualSpec.js | 23 +- .../core/matchers/toHaveBeenCalledWithSpec.js | 20 +- spec/helpers/integrationMatchers.js | 5 +- spec/npmPackage/npmPackageSpec.js | 2 +- src/core/Env.js | 14 +- src/core/Spy.js | 4 +- .../asymmetricEqualityTesterArgCompatShim.js | 105 +++++ .../asymmetric_equality/ArrayContaining.js | 4 +- .../ArrayWithExactContents.js | 4 +- src/core/asymmetric_equality/MapContaining.js | 6 +- .../asymmetric_equality/ObjectContaining.js | 4 +- src/core/asymmetric_equality/SetContaining.js | 6 +- src/core/matchers/async/toBeRejectedWith.js | 4 +- src/core/matchers/async/toBeResolvedTo.js | 4 +- src/core/matchers/matchersUtil.js | 147 +++--- src/core/matchers/toBeInstanceOf.js | 2 +- src/core/matchers/toContain.js | 6 +- src/core/matchers/toEqual.js | 6 +- src/core/matchers/toHaveBeenCalledWith.js | 6 +- src/core/matchers/toHaveClass.js | 2 +- src/core/requireCore.js | 7 +- 43 files changed, 1214 insertions(+), 455 deletions(-) create mode 100644 spec/core/asymmetricEqualityTesterArgCompatShimSpec.js create mode 100644 src/core/asymmetricEqualityTesterArgCompatShim.js diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 4875624e..555cb046 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -73,7 +73,12 @@ var getJasmineRequireObj = (function(jasmineGlobal) { j$.buildExpectationResult = jRequire.buildExpectationResult(); j$.noopTimer = jRequire.noopTimer(); j$.JsApiReporter = jRequire.JsApiReporter(j$); - j$.matchersUtil = jRequire.matchersUtil(j$); + j$.asymmetricEqualityTesterArgCompatShim = jRequire.asymmetricEqualityTesterArgCompatShim( + j$ + ); + j$.MatchersUtil = jRequire.MatchersUtil(j$); + j$.matchersUtil = new j$.MatchersUtil({ customTesters: [] }); + j$.ObjectContaining = jRequire.ObjectContaining(j$); j$.ArrayContaining = jRequire.ArrayContaining(j$); j$.ArrayWithExactContents = jRequire.ArrayWithExactContents(j$); @@ -1188,6 +1193,7 @@ getJasmineRequireObj().Env = function(j$) { } var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { customMatchers[matcherName] = matchersToAdd[matcherName]; } @@ -1201,6 +1207,7 @@ getJasmineRequireObj().Env = function(j$) { } var customAsyncMatchers = runnableResources[currentRunnable().id].customAsyncMatchers; + for (var matcherName in matchersToAdd) { customAsyncMatchers[matcherName] = matchersToAdd[matcherName]; } @@ -1220,9 +1227,12 @@ getJasmineRequireObj().Env = function(j$) { }; var expectationFactory = function(actual, spec) { + var customEqualityTesters = + runnableResources[spec.id].customEqualityTesters; + return j$.Expectation.factory({ - util: j$.matchersUtil, - customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + util: new j$.MatchersUtil({ customTesters: customEqualityTesters }), + customEqualityTesters: customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, actual: actual, addExpectationResult: addExpectationResult @@ -1234,8 +1244,11 @@ getJasmineRequireObj().Env = function(j$) { }; var asyncExpectationFactory = function(actual, spec) { + var customEqualityTesters = + runnableResources[spec.id].customEqualityTesters; + return j$.Expectation.asyncFactory({ - util: j$.matchersUtil, + util: new j$.MatchersUtil({ customTesters: customEqualityTesters }), customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customAsyncMatchers: runnableResources[spec.id].customAsyncMatchers, actual: actual, @@ -2269,7 +2282,7 @@ getJasmineRequireObj().ArrayContaining = function(j$) { this.sample = sample; } - ArrayContaining.prototype.asymmetricMatch = function(other, customTesters) { + ArrayContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isArray_(this.sample)) { throw new Error('You must provide an array to arrayContaining, not ' + j$.pp(this.sample) + '.'); } @@ -2283,7 +2296,7 @@ getJasmineRequireObj().ArrayContaining = function(j$) { for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; - if (!j$.matchersUtil.contains(other, item, customTesters)) { + if (!matchersUtil.contains(other, item)) { return false; } } @@ -2304,7 +2317,7 @@ getJasmineRequireObj().ArrayWithExactContents = function(j$) { this.sample = sample; } - ArrayWithExactContents.prototype.asymmetricMatch = function(other, customTesters) { + ArrayWithExactContents.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isArray_(this.sample)) { throw new Error('You must provide an array to arrayWithExactContents, not ' + j$.pp(this.sample) + '.'); } @@ -2315,7 +2328,7 @@ getJasmineRequireObj().ArrayWithExactContents = function(j$) { for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; - if (!j$.matchersUtil.contains(other, item, customTesters)) { + if (!matchersUtil.contains(other, item)) { return false; } } @@ -2380,7 +2393,7 @@ getJasmineRequireObj().MapContaining = function(j$) { this.sample = sample; } - MapContaining.prototype.asymmetricMatch = function(other, customTesters) { + MapContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isMap(other)) return false; var hasAllMatches = true; @@ -2390,8 +2403,8 @@ getJasmineRequireObj().MapContaining = function(j$) { var hasMatch = false; j$.util.forEachBreakable(other, function(oBreakLoop, oValue, oKey) { if ( - j$.matchersUtil.equals(oKey, key, customTesters) - && j$.matchersUtil.equals(oValue, value, customTesters) + matchersUtil.equals(oKey, key) + && matchersUtil.equals(oValue, value) ) { hasMatch = true; oBreakLoop(); @@ -2470,12 +2483,12 @@ getJasmineRequireObj().ObjectContaining = function(j$) { return hasProperty(getPrototype(obj), property); } - ObjectContaining.prototype.asymmetricMatch = function(other, customTesters) { + ObjectContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } for (var property in this.sample) { if (!hasProperty(other, property) || - !j$.matchersUtil.equals(this.sample[property], other[property], customTesters)) { + !matchersUtil.equals(this.sample[property], other[property])) { return false; } } @@ -2499,17 +2512,17 @@ getJasmineRequireObj().SetContaining = function(j$) { this.sample = sample; } - SetContaining.prototype.asymmetricMatch = function(other, customTesters) { + SetContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isSet(other)) return false; var hasAllMatches = true; j$.util.forEachBreakable(this.sample, function(breakLoop, item) { // for each item in `sample` there should be at least one matching item in `other` - // (not using `j$.matchersUtil.contains` because it compares set members by reference, + // (not using `matchersUtil.contains` because it compares set members by reference, // not by deep value equality) var hasMatch = false; j$.util.forEachBreakable(other, function(oBreakLoop, oItem) { - if (j$.matchersUtil.equals(oItem, item, customTesters)) { + if (matchersUtil.equals(oItem, item)) { hasMatch = true; oBreakLoop(); } @@ -2566,6 +2579,112 @@ getJasmineRequireObj().Truthy = function(j$) { return Truthy; }; +getJasmineRequireObj().asymmetricEqualityTesterArgCompatShim = function(j$) { + /* + Older versions of Jasmine passed an array of custom equality testers as the + second argument to each asymmetric equality tester's `asymmetricMatch` + method. Newer versions will pass a `MatchersUtil` instance. The + asymmetricEqualityTesterArgCompatShim allows for a graceful migration from + the old interface to the new by "being" both an array of custom equality + testers and a `MatchersUtil` at the same time. + + This code should be removed in the next major release. + */ + + var likelyArrayProps = [ + 'concat', + 'constructor', + 'copyWithin', + 'entries', + 'every', + 'fill', + 'filter', + 'find', + 'findIndex', + 'flat', + 'flatMap', + 'forEach', + 'includes', + 'indexOf', + 'join', + 'keys', + 'lastIndexOf', + 'length', + 'map', + 'pop', + 'push', + 'reduce', + 'reduceRight', + 'reverse', + 'shift', + 'slice', + 'some', + 'sort', + 'splice', + 'toLocaleString', + 'toSource', + 'toString', + 'unshift', + 'values' + ]; + + function asymmetricEqualityTesterArgCompatShim( + matchersUtil, + customEqualityTesters + ) { + var self = Object.create(matchersUtil), + props, + i, + k; + + copy(self, customEqualityTesters, 'length'); + + for (i = 0; i < customEqualityTesters.length; i++) { + copy(self, customEqualityTesters, i); + } + + var props = arrayProps(); + + for (i = 0; i < props.length; i++) { + k = props[i]; + if (k !== 'length') { + copy(self, Array.prototype, k); + } + } + + return self; + } + + function copy(dest, src, propName) { + Object.defineProperty(dest, propName, { + get: function() { + return src[propName]; + } + }); + } + + function arrayProps() { + var props, a, k; + + if (!Object.getOwnPropertyDescriptors) { + return likelyArrayProps.filter(function(k) { + return Array.prototype.hasOwnProperty(k); + }); + } + + props = Object.getOwnPropertyDescriptors(Array.prototype); + a = []; + + for (k in props) { + a.push(k); + } + + return a; + } + + return asymmetricEqualityTesterArgCompatShim; +}; + getJasmineRequireObj().CallTracker = function(j$) { /** * @namespace Spy#calls @@ -3822,7 +3941,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { * @example * return expectAsync(aPromise).toBeRejectedWith({prop: 'value'}); */ - return function toBeRejectedWith(util, customEqualityTesters) { + return function toBeRejectedWith(util) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { @@ -3843,7 +3962,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { }; }, function(actualValue) { - if (util.equals(actualValue, expectedValue, customEqualityTesters)) { + if (util.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' @@ -4000,7 +4119,7 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { * @example * return expectAsync(aPromise).toBeResolvedTo({prop: 'value'}); */ - return function toBeResolvedTo(util, customEqualityTesters) { + return function toBeResolvedTo(util) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { @@ -4015,7 +4134,7 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { return actualPromise.then( function(actualValue) { - if (util.equals(actualValue, expectedValue, customEqualityTesters)) { + if (util.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' @@ -4073,66 +4192,70 @@ getJasmineRequireObj().DiffBuilder = function(j$) { }; }; -getJasmineRequireObj().matchersUtil = function(j$) { +getJasmineRequireObj().MatchersUtil = function(j$) { // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? - return { - equals: equals, + function MatchersUtil(options) { + options = options || {}; + this.customTesters_ = options.customTesters || []; - contains: function(haystack, needle, customTesters) { - customTesters = customTesters || []; - - if (j$.isSet(haystack)) { - return haystack.has(needle); - } - - if ((Object.prototype.toString.apply(haystack) === '[object Array]') || - (!!haystack && !haystack.indexOf)) - { - for (var i = 0; i < haystack.length; i++) { - if (equals(haystack[i], needle, customTesters)) { - return true; - } - } - return false; - } - - return !!haystack && haystack.indexOf(needle) >= 0; - }, - - buildFailureMessage: function() { - var args = Array.prototype.slice.call(arguments, 0), - matcherName = args[0], - isNot = args[1], - actual = args[2], - expected = args.slice(3), - englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); - - var message = 'Expected ' + - j$.pp(actual) + - (isNot ? ' not ' : ' ') + - englishyPredicate; - - if (expected.length > 0) { - for (var i = 0; i < expected.length; i++) { - if (i > 0) { - message += ','; - } - message += ' ' + j$.pp(expected[i]); - } - } - - return message + '.'; + if (!j$.isArray_(this.customTesters_)) { + throw new Error("MatchersUtil requires custom equality testers"); } + } + + MatchersUtil.prototype.contains = function(haystack, needle, customTesters) { + if (j$.isSet(haystack)) { + return haystack.has(needle); + } + + if ((Object.prototype.toString.apply(haystack) === '[object Array]') || + (!!haystack && !haystack.indexOf)) + { + for (var i = 0; i < haystack.length; i++) { + if (this.equals(haystack[i], needle, customTesters)) { + return true; + } + } + return false; + } + + return !!haystack && haystack.indexOf(needle) >= 0; + }; + + MatchersUtil.prototype.buildFailureMessage = function() { + var args = Array.prototype.slice.call(arguments, 0), + matcherName = args[0], + isNot = args[1], + actual = args[2], + expected = args.slice(3), + englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + + var message = 'Expected ' + + j$.pp(actual) + + (isNot ? ' not ' : ' ') + + englishyPredicate; + + if (expected.length > 0) { + for (var i = 0; i < expected.length; i++) { + if (i > 0) { + message += ','; + } + message += ' ' + j$.pp(expected[i]); + } + } + + return message + '.'; }; function isAsymmetric(obj) { return obj && j$.isA_('Function', obj.asymmetricMatch); } - function asymmetricMatch(a, b, customTesters, diffBuilder) { + MatchersUtil.prototype.asymmetricMatch_ = function(a, b, customTesters, diffBuilder) { var asymmetricA = isAsymmetric(a), asymmetricB = isAsymmetric(b), + shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters), result; if (asymmetricA && asymmetricB) { @@ -4140,7 +4263,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { } if (asymmetricA) { - result = a.asymmetricMatch(b, customTesters); + result = a.asymmetricMatch(b, shim); if (!result) { diffBuilder.record(a, b); } @@ -4148,27 +4271,36 @@ getJasmineRequireObj().matchersUtil = function(j$) { } if (asymmetricB) { - result = b.asymmetricMatch(a, customTesters); + result = b.asymmetricMatch(a, shim); if (!result) { diffBuilder.record(a, b); } return result; } - } + }; - function equals(a, b, customTesters, diffBuilder) { - customTesters = customTesters || []; + MatchersUtil.prototype.equals = function(a, b, customTestersOrDiffBuilder, diffBuilderOrNothing) { + var customTesters, diffBuilder; + + if (isDiffBuilder(customTestersOrDiffBuilder)) { + diffBuilder = customTestersOrDiffBuilder; + } else { + customTesters = customTestersOrDiffBuilder; + diffBuilder = diffBuilderOrNothing; + } + + customTesters = customTesters || this.customTesters_; diffBuilder = diffBuilder || j$.NullDiffBuilder(); - return eq(a, b, [], [], customTesters, diffBuilder); - } + return this.eq_(a, b, [], [], customTesters, diffBuilder); + }; // Equality function lovingly adapted from isEqual in // [Underscore](http://underscorejs.org) - function eq(a, b, aStack, bStack, customTesters, diffBuilder) { - var result = true, i; + MatchersUtil.prototype.eq_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { + var result = true, self = this, i; - var asymmetricResult = asymmetricMatch(a, b, customTesters, diffBuilder); + var asymmetricResult = this.asymmetricMatch_(a, b, customTesters, diffBuilder); if (!j$.util.isUndefined(asymmetricResult)) { return asymmetricResult; } @@ -4305,7 +4437,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { diffBuilder.record(a[i], void 0, actualArrayIsLongerFormatter); result = false; } else { - result = eq(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0, aStack, bStack, customTesters, diffBuilder) && result; + result = self.eq_(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0, aStack, bStack, customTesters, diffBuilder) && result; } }); } @@ -4346,12 +4478,12 @@ getJasmineRequireObj().matchersUtil = function(j$) { // otherwise explicitly look up the mapKey in the other Map since we want keys with unique // obj identity (that are otherwise equal) to not match. if (isAsymmetric(mapKey) || isAsymmetric(cmpKey) && - eq(mapKey, cmpKey, aStack, bStack, customTesters, j$.NullDiffBuilder())) { + this.eq_(mapKey, cmpKey, aStack, bStack, customTesters, j$.NullDiffBuilder())) { mapValueB = b.get(cmpKey); } else { mapValueB = b.get(mapKey); } - result = eq(mapValueA, mapValueB, aStack, bStack, customTesters, j$.NullDiffBuilder()); + result = this.eq_(mapValueA, mapValueB, aStack, bStack, customTesters, j$.NullDiffBuilder()); } } @@ -4395,7 +4527,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { otherValue = otherValues[l]; prevStackSize = baseStack.length; // compare by value equality - found = eq(baseValue, otherValue, baseStack, otherStack, customTesters, j$.NullDiffBuilder()); + found = this.eq_(baseValue, otherValue, baseStack, otherStack, customTesters, j$.NullDiffBuilder()); if (!found && prevStackSize !== baseStack.length) { baseStack.splice(prevStackSize); otherStack.splice(prevStackSize); @@ -4444,7 +4576,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { } diffBuilder.withPath(key, function() { - if(!eq(a[key], b[key], aStack, bStack, customTesters, diffBuilder)) { + if(!self.eq_(a[key], b[key], aStack, bStack, customTesters, diffBuilder)) { result = false; } }); @@ -4459,7 +4591,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { bStack.pop(); return result; - } + }; function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : @@ -4542,6 +4674,12 @@ getJasmineRequireObj().matchersUtil = function(j$) { } return formatted; } + + function isDiffBuilder(obj) { + return obj && typeof obj.record === 'function'; + } + + return MatchersUtil; }; getJasmineRequireObj().nothing = function() { @@ -4839,7 +4977,7 @@ getJasmineRequireObj().toBeInstanceOf = function(j$) { * expect(3).toBeInstanceOf(Number); * expect(new Error()).toBeInstanceOf(Error); */ - function toBeInstanceOf(util, customEqualityTesters) { + function toBeInstanceOf() { return { compare: function(actual, expected) { var actualType = actual && actual.constructor ? j$.fnNameFor(actual.constructor) : j$.pp(actual), @@ -5109,14 +5247,12 @@ getJasmineRequireObj().toContain = function() { * expect(array).toContain(anElement); * expect(string).toContain(substring); */ - function toContain(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - + function toContain(util) { return { compare: function(actual, expected) { return { - pass: util.contains(actual, expected, customEqualityTesters) + pass: util.contains(actual, expected) }; } }; @@ -5135,9 +5271,7 @@ getJasmineRequireObj().toEqual = function(j$) { * @example * expect(bigObject).toEqual({"foo": ['bar', 'baz']}); */ - function toEqual(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - + function toEqual(util) { return { compare: function(actual, expected) { var result = { @@ -5145,7 +5279,7 @@ getJasmineRequireObj().toEqual = function(j$) { }, diffBuilder = j$.DiffBuilder(); - result.pass = util.equals(actual, expected, customEqualityTesters, diffBuilder); + result.pass = util.equals(actual, expected, diffBuilder); // TODO: only set error message if test fails result.message = diffBuilder.getMessage(); @@ -5315,7 +5449,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { * @example * expect(mySpy).toHaveBeenCalledWith('foo', 'bar', 2); */ - function toHaveBeenCalledWith(util, customEqualityTesters) { + function toHaveBeenCalledWith(util) { return { compare: function() { var args = Array.prototype.slice.call(arguments, 0), @@ -5336,7 +5470,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { return result; } - if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { + if (util.contains(actual.calls.allArgs(), expectedArgs)) { result.pass = true; result.message = function() { return 'Expected spy ' + actual.and.identity + ' not to have been called with:\n' + @@ -5351,7 +5485,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { var diffs = actual.calls.allArgs().map(function(argsForCall, callIx) { var diffBuilder = new j$.DiffBuilder(); - util.equals(argsForCall, expectedArgs, customEqualityTesters, diffBuilder); + util.equals(argsForCall, expectedArgs, diffBuilder); return 'Call ' + callIx + ':\n' + diffBuilder.getMessage().replace(/^/mg, ' '); }); @@ -5384,7 +5518,7 @@ getJasmineRequireObj().toHaveClass = function(j$) { * el.className = 'foo bar baz'; * expect(el).toHaveClass('bar'); */ - function toHaveClass(util, customEqualityTesters) { + function toHaveClass() { return { compare: function(actual, expected) { if (!isElement(actual)) { @@ -6899,6 +7033,8 @@ getJasmineRequireObj().Spy = function(j$) { }; })(); + var matchersUtil = new j$.MatchersUtil(); + /** * _Note:_ Do not construct this directly, use {@link spyOn}, {@link spyOnProperty}, {@link jasmine.createSpy}, or {@link jasmine.createSpyObj} * @constructor @@ -7094,7 +7230,7 @@ getJasmineRequireObj().Spy = function(j$) { var i; for (i = 0; i < this.strategies.length; i++) { - if (j$.matchersUtil.equals(args, this.strategies[i].args)) { + if (matchersUtil.equals(args, this.strategies[i].args)) { return this.strategies[i].strategy; } } diff --git a/spec/core/AsyncExpectationSpec.js b/spec/core/AsyncExpectationSpec.js index 9b2e3f26..23d145e1 100644 --- a/spec/core/AsyncExpectationSpec.js +++ b/spec/core/AsyncExpectationSpec.js @@ -25,7 +25,7 @@ describe('AsyncExpectation', function() { var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.resolve(), expectation = jasmineUnderTest.Expectation.asyncFactory({ - util: jasmineUnderTest.matchersUtil, + util: new jasmineUnderTest.MatchersUtil(), actual: actual, addExpectationResult: addExpectationResult }); @@ -47,7 +47,7 @@ describe('AsyncExpectation', function() { var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.reject(), expectation = jasmineUnderTest.Expectation.asyncFactory({ - util: jasmineUnderTest.matchersUtil, + util: new jasmineUnderTest.MatchersUtil(), actual: actual, addExpectationResult: addExpectationResult }); @@ -183,7 +183,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, - util: jasmineUnderTest.matchersUtil + util: new jasmineUnderTest.MatchersUtil() }); return expectation @@ -208,7 +208,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, - util: jasmineUnderTest.matchersUtil + util: new jasmineUnderTest.MatchersUtil() }); return expectation diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index aa87a997..dc7233c8 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -304,4 +304,62 @@ describe('Env', function() { expect(globalErrors.install).toHaveBeenCalled(); }); }); + + it('creates an expectationFactory that uses the current custom equality testers', function(done) { + function customEqualityTester() {} + var RealSpec = jasmineUnderTest.Spec, + specInstance, + expectationFactory; + spyOn(jasmineUnderTest, 'MatchersUtil'); + spyOn(jasmineUnderTest, 'Spec').and.callFake(function(options) { + expectationFactory = options.expectationFactory; + specInstance = new RealSpec(options); + return specInstance; + }); + + env.it('spec', function() { + env.addCustomEqualityTester(customEqualityTester); + expectationFactory('actual', specInstance); + }); + + env.addReporter({ + jasmineDone: function() { + expect(jasmineUnderTest.MatchersUtil).toHaveBeenCalledWith({ + customTesters: [customEqualityTester] + }); + done(); + } + }); + + env.execute(); + }); + + it('creates an asyncExpectationFactory that uses the current custom equality testers', function(done) { + function customEqualityTester() {} + var RealSpec = jasmineUnderTest.Spec, + specInstance, + asyncExpectationFactory; + spyOn(jasmineUnderTest, 'MatchersUtil'); + spyOn(jasmineUnderTest, 'Spec').and.callFake(function(options) { + asyncExpectationFactory = options.asyncExpectationFactory; + specInstance = new RealSpec(options); + return specInstance; + }); + + env.it('spec', function() { + env.addCustomEqualityTester(customEqualityTester); + asyncExpectationFactory('actual', specInstance); + }); + + env.addReporter({ + jasmineDone: function() { + expect(jasmineUnderTest.MatchersUtil).toHaveBeenCalledWith({ + customTesters: [customEqualityTester] + }); + done(); + } + }); + + env.execute(); + }); }); diff --git a/spec/core/ExpectationSpec.js b/spec/core/ExpectationSpec.js index 45f69eb5..361224ab 100644 --- a/spec/core/ExpectationSpec.js +++ b/spec/core/ExpectationSpec.js @@ -634,7 +634,7 @@ describe('Expectation', function() { addExpectationResult = jasmine.createSpy('addExpectationResult'), expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, - util: jasmineUnderTest.matchersUtil, + util: new jasmineUnderTest.MatchersUtil(), actual: 'an actual', addExpectationResult: addExpectationResult }); diff --git a/spec/core/asymmetricEqualityTesterArgCompatShimSpec.js b/spec/core/asymmetricEqualityTesterArgCompatShimSpec.js new file mode 100644 index 00000000..c7c4d9b6 --- /dev/null +++ b/spec/core/asymmetricEqualityTesterArgCompatShimSpec.js @@ -0,0 +1,101 @@ +describe('asymmetricEqualityTesterArgCompatShim', function() { + it('provides all the properties of the MatchersUtil', function() { + var matchersUtil = { + foo: function() {}, + bar: function() {} + }, + shim = jasmineUnderTest.asymmetricEqualityTesterArgCompatShim( + matchersUtil, + [] + ); + + expect(shim.foo).toBe(matchersUtil.foo); + expect(shim.bar).toBe(matchersUtil.bar); + }); + + it('provides all the properties of the customEqualityTesters', function() { + var customEqualityTesters = [function() {}, function() {}], + shim = jasmineUnderTest.asymmetricEqualityTesterArgCompatShim( + {}, + customEqualityTesters + ); + + expect(shim.length).toBe(2); + expect(shim[0]).toBe(customEqualityTesters[0]); + expect(shim[1]).toBe(customEqualityTesters[1]); + }); + + it('provides all the properties of Array.prototype', function() { + var shim = jasmineUnderTest.asymmetricEqualityTesterArgCompatShim({}, []); + + expect(shim.filter).toBe(Array.prototype.filter); + expect(shim.forEach).toBe(Array.prototype.forEach); + expect(shim.map).toBe(Array.prototype.map); + }); + + it('provides properties of Array.prototype', function() { + var keys = [ + 'concat', + 'constructor', + 'every', + 'filter', + 'forEach', + 'indexOf', + 'join', + 'lastIndexOf', + 'length', + 'map', + 'pop', + 'push', + 'reduce', + 'reduceRight', + 'reverse', + 'shift', + 'slice', + 'some', + 'sort', + 'splice', + 'toLocaleString', + 'toString', + 'unshift' + ], + optionalKeys = [ + 'copyWithin', + 'entries', + 'fill', + 'find', + 'findIndex', + 'flat', + 'flatMap', + 'includes', + 'keys', + 'toSource', + 'values' + ], + shim = jasmineUnderTest.asymmetricEqualityTesterArgCompatShim({}, []), + i, + k; + + // Properties that are present on all supported runtimes + for (i = 0; i < keys.length; i++) { + k = keys[i]; + expect(shim[k]) + .withContext(k) + .not.toBeUndefined(); + expect(shim[k]) + .withContext(k) + .toBe(Array.prototype[k]); + } + + // Properties that are present on only some supported runtimes + for (i = 0; i < optionalKeys.length; i++) { + k = optionalKeys[i]; + + if (shim[k] !== undefined) { + expect(shim[k]) + .withContext(k) + .toBe(Array.prototype[k]); + } + } + }); +}); diff --git a/spec/core/asymmetric_equality/ArrayContainingSpec.js b/spec/core/asymmetric_equality/ArrayContainingSpec.js index 30b5aaad..4cbfa66d 100644 --- a/spec/core/asymmetric_equality/ArrayContainingSpec.js +++ b/spec/core/asymmetric_equality/ArrayContainingSpec.js @@ -15,26 +15,30 @@ describe("ArrayContaining", function() { it("matches when the item is in the actual", function() { var containing = new jasmineUnderTest.ArrayContaining(["foo"]); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(["foo"])).toBe(true); + expect(containing.asymmetricMatch(["foo"], matchersUtil)).toBe(true); }); it("matches when additional items are in the actual", function() { var containing = new jasmineUnderTest.ArrayContaining(["foo"]); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(["foo", "bar"])).toBe(true); + expect(containing.asymmetricMatch(["foo", "bar"], matchersUtil)).toBe(true); }); it("does not match when the item is not in the actual", function() { var containing = new jasmineUnderTest.ArrayContaining(["foo"]); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(["bar"])).toBe(false); + expect(containing.asymmetricMatch(["bar"], matchersUtil)).toBe(false); }); it("does not match when the actual is not an array", function() { var containing = new jasmineUnderTest.ArrayContaining(["foo"]); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch("foo")).toBe(false); + expect(containing.asymmetricMatch("foo", matchersUtil)).toBe(false); }); it("jasmineToStrings itself", function() { @@ -52,7 +56,8 @@ describe("ArrayContaining", function() { } }; var containing = new jasmineUnderTest.ArrayContaining(["fooVal"]); + var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); - expect(containing.asymmetricMatch(["fooBar"], [tester])).toBe(true); + expect(containing.asymmetricMatch(["fooBar"], matchersUtil)).toBe(true); }); }); diff --git a/spec/core/asymmetric_equality/ArrayWithExactContentsSpec.js b/spec/core/asymmetric_equality/ArrayWithExactContentsSpec.js index 1cae0a0b..5f8795d6 100644 --- a/spec/core/asymmetric_equality/ArrayWithExactContentsSpec.js +++ b/spec/core/asymmetric_equality/ArrayWithExactContentsSpec.js @@ -1,8 +1,9 @@ describe("ArrayWithExactContents", function() { it("matches an array with the same items in a different order", function() { var matcher = new jasmineUnderTest.ArrayWithExactContents(['a', 2, /a/]); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(matcher.asymmetricMatch([2, 'a', /a/])).toBe(true); + expect(matcher.asymmetricMatch([2, 'a', /a/], matchersUtil)).toBe(true); }); it("does not work when not passed an array", function() { @@ -15,15 +16,17 @@ describe("ArrayWithExactContents", function() { it("does not match when an item is missing", function() { var matcher = new jasmineUnderTest.ArrayWithExactContents(['a', 2, /a/]); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(matcher.asymmetricMatch(['a', 2])).toBe(false); - expect(matcher.asymmetricMatch(['a', 2, undefined])).toBe(false); + expect(matcher.asymmetricMatch(['a', 2], matchersUtil)).toBe(false); + expect(matcher.asymmetricMatch(['a', 2, undefined], matchersUtil)).toBe(false); }); it("does not match when there is an extra item", function() { var matcher = new jasmineUnderTest.ArrayWithExactContents(['a']); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(matcher.asymmetricMatch(['a', 2])).toBe(false); + expect(matcher.asymmetricMatch(['a', 2], matchersUtil)).toBe(false); }); it("jasmineToStrings itself", function() { @@ -41,7 +44,8 @@ describe("ArrayWithExactContents", function() { } }; var matcher = new jasmineUnderTest.ArrayWithExactContents(["fooVal"]); + var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); - expect(matcher.asymmetricMatch(["fooBar"], [tester])).toBe(true); + expect(matcher.asymmetricMatch(["fooBar"], matchersUtil)).toBe(true); }); }); diff --git a/spec/core/asymmetric_equality/MapContainingSpec.js b/spec/core/asymmetric_equality/MapContainingSpec.js index a214dce8..f264e693 100644 --- a/spec/core/asymmetric_equality/MapContainingSpec.js +++ b/spec/core/asymmetric_equality/MapContainingSpec.js @@ -31,8 +31,9 @@ describe('MapContaining', function() { ['foo', [1, 2, 3]], ]); var containing = new jasmineUnderTest.MapContaining(containingMap); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualMap)).toBe(true); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(true); }); it('does not match when a key is not in actual', function() { @@ -46,8 +47,9 @@ describe('MapContaining', function() { ['foo', [1, 2, 3]], ]); var containing = new jasmineUnderTest.MapContaining(containingMap); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualMap)).toBe(false); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(false); }); it('does not match when a value is not in actual', function() { @@ -61,8 +63,9 @@ describe('MapContaining', function() { ['foo', [1, 2]], ]); var containing = new jasmineUnderTest.MapContaining(containingMap); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualMap)).toBe(false); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(false); }); it('matches when all the key/value pairs in sample have asymmetric matches in actual', function() { @@ -74,17 +77,18 @@ describe('MapContaining', function() { var containingMap = new MapI([ [ - jasmine.stringMatching(/^foo\d/), + jasmineUnderTest.stringMatching(/^foo\d/), 'bar' ], [ 'baz', - jasmine.arrayContaining([2, 3]) + jasmineUnderTest.arrayContaining([2, 3]) ], ]); var containing = new jasmineUnderTest.MapContaining(containingMap); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualMap)).toBe(true); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(true); }); it('does not match when a key in sample has no asymmetric matches in actual', function() { @@ -95,17 +99,18 @@ describe('MapContaining', function() { var containingMap = new MapI([ [ - jasmine.stringMatching(/^foo\d/), + jasmineUnderTest.stringMatching(/^foo\d/), 'bar' ], [ 'baz', - jasmine.arrayContaining([2, 3]) + jasmineUnderTest.arrayContaining([2, 3]) ], ]); var containing = new jasmineUnderTest.MapContaining(containingMap); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualMap)).toBe(false); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(false); }); it('does not match when a value in sample has no asymmetric matches in actual', function() { @@ -116,17 +121,18 @@ describe('MapContaining', function() { var containingMap = new MapI([ [ - jasmine.stringMatching(/^foo\d/), + jasmineUnderTest.stringMatching(/^foo\d/), 'bar' ], [ 'baz', - jasmine.arrayContaining([4, 5]) + jasmineUnderTest.arrayContaining([4, 5]) ], ]); var containing = new jasmineUnderTest.MapContaining(containingMap); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualMap)).toBe(false); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(false); }); it('matches recursively', function() { @@ -147,8 +153,9 @@ describe('MapContaining', function() { ], ]); var containing = new jasmineUnderTest.MapContaining(containingMap); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualMap)).toBe(true); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(true); }); it('uses custom equality testers', function() { @@ -158,8 +165,9 @@ describe('MapContaining', function() { } var actualMap = new MapI([['foo', -1]]); var containing = new jasmineUnderTest.MapContaining(new MapI([['foo', -2]])); + var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); - expect(containing.asymmetricMatch(actualMap, [tester])).toBe(true); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(true); }); it('does not match when actual is not a map', function() { diff --git a/spec/core/asymmetric_equality/ObjectContainingSpec.js b/spec/core/asymmetric_equality/ObjectContainingSpec.js index e2741a9d..13fa0a90 100644 --- a/spec/core/asymmetric_equality/ObjectContainingSpec.js +++ b/spec/core/asymmetric_equality/ObjectContainingSpec.js @@ -2,8 +2,9 @@ describe("ObjectContaining", function() { it("matches any actual to an empty object", function() { var containing = new jasmineUnderTest.ObjectContaining({}); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch("foo")).toBe(true); + expect(containing.asymmetricMatch("foo", matchersUtil)).toBe(true); }); it("does not match an empty object actual", function() { @@ -16,20 +17,23 @@ describe("ObjectContaining", function() { it("matches when the key/value pair is present in the actual", function() { var containing = new jasmineUnderTest.ObjectContaining({foo: "fooVal"}); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch({foo: "fooVal", bar: "barVal"})).toBe(true); + expect(containing.asymmetricMatch({foo: "fooVal", bar: "barVal"}, matchersUtil)).toBe(true); }); it("does not match when the key/value pair is not present in the actual", function() { var containing = new jasmineUnderTest.ObjectContaining({foo: "fooVal"}); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch({bar: "barVal", quux: "quuxVal"})).toBe(false); + expect(containing.asymmetricMatch({bar: "barVal", quux: "quuxVal"}, matchersUtil)).toBe(false); }); it("does not match when the key is present but the value is different in the actual", function() { var containing = new jasmineUnderTest.ObjectContaining({foo: "other"}); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch({foo: "fooVal", bar: "barVal"})).toBe(false); + expect(containing.asymmetricMatch({foo: "fooVal", bar: "barVal"}, matchersUtil)).toBe(false); }); it("jasmineToString's itself", function() { @@ -40,34 +44,39 @@ describe("ObjectContaining", function() { it("matches recursively", function() { var containing = new jasmineUnderTest.ObjectContaining({one: new jasmineUnderTest.ObjectContaining({two: {}})}); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch({one: {two: {}}})).toBe(true); + expect(containing.asymmetricMatch({one: {two: {}}}, matchersUtil)).toBe(true); }); it("matches when key is present with undefined value", function() { var containing = new jasmineUnderTest.ObjectContaining({ one: undefined }); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch({ one: undefined })).toBe(true); + expect(containing.asymmetricMatch({ one: undefined }, matchersUtil)).toBe(true); }); it("does not match when key with undefined value is not present", function() { var containing = new jasmineUnderTest.ObjectContaining({ one: undefined }); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch({})).toBe(false); + expect(containing.asymmetricMatch({}, matchersUtil)).toBe(false); }); it("matches defined properties", function(){ var containing = new jasmineUnderTest.ObjectContaining({ foo: "fooVal" }); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var definedPropertyObject = {}; Object.defineProperty(definedPropertyObject, "foo", { get: function() { return "fooVal" } }); - expect(containing.asymmetricMatch(definedPropertyObject)).toBe(true); + expect(containing.asymmetricMatch(definedPropertyObject, matchersUtil)).toBe(true); }); it("matches prototype properties", function(){ var containing = new jasmineUnderTest.ObjectContaining({ foo: "fooVal" }); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var prototypeObject = {foo: "fooVal"}; var obj; @@ -81,7 +90,7 @@ describe("ObjectContaining", function() { obj = new Foo(); } - expect(containing.asymmetricMatch(obj)).toBe(true); + expect(containing.asymmetricMatch(obj, matchersUtil)).toBe(true); }); it("uses custom equality testers", function() { @@ -93,7 +102,8 @@ describe("ObjectContaining", function() { } }; var containing = new jasmineUnderTest.ObjectContaining({foo: "fooVal"}); + var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); - expect(containing.asymmetricMatch({foo: "fooBar"}, [tester])).toBe(true); + expect(containing.asymmetricMatch({foo: "fooBar"}, matchersUtil)).toBe(true); }); }); diff --git a/spec/core/asymmetric_equality/SetContainingSpec.js b/spec/core/asymmetric_equality/SetContainingSpec.js index d1ae7a5a..f882621f 100644 --- a/spec/core/asymmetric_equality/SetContainingSpec.js +++ b/spec/core/asymmetric_equality/SetContainingSpec.js @@ -28,8 +28,9 @@ describe('SetContaining', function() { [1, 2, 3], {'foo': 'bar'} ]); var containing = new jasmineUnderTest.SetContaining(containingSet); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualSet)).toBe(true); + expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(true); }); it('does not match when a value is not in actual', function() { @@ -41,8 +42,9 @@ describe('SetContaining', function() { [1, 2], {'foo': 'bar'} ]); var containing = new jasmineUnderTest.SetContaining(containingSet); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualSet)).toBe(false); + expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(false); }); it('matches when all the values in sample have asymmetric matches in actual', function() { @@ -51,12 +53,13 @@ describe('SetContaining', function() { ]); var containingSet = new SetI([ - jasmine.stringMatching(/^foo\d/), - jasmine.arrayContaining([2, 3]), + jasmineUnderTest.stringMatching(/^foo\d/), + jasmineUnderTest.arrayContaining([2, 3]), ]); var containing = new jasmineUnderTest.SetContaining(containingSet); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualSet)).toBe(true); + expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(true); }); it('does not match when a value in sample has no asymmetric matches in actual', function() { @@ -69,8 +72,9 @@ describe('SetContaining', function() { jasmine.arrayContaining([2, 3]), ]); var containing = new jasmineUnderTest.SetContaining(containingSet); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualSet)).toBe(false); + expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(false); }); it('matches recursively', function() { @@ -82,8 +86,9 @@ describe('SetContaining', function() { new jasmineUnderTest.SetContaining(new SetI(['bar'])), 'foo' ]); var containing = new jasmineUnderTest.SetContaining(containingSet); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualSet)).toBe(true); + expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(true); }); it('uses custom equality testers', function() { @@ -93,8 +98,9 @@ describe('SetContaining', function() { } var actualSet = new SetI(['foo', -1]); var containing = new jasmineUnderTest.SetContaining(new SetI([-2, 'foo'])); + var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); - expect(containing.asymmetricMatch(actualSet, [tester])).toBe(true); + expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(true); }); it('does not match when actual is not a set', function() { diff --git a/spec/core/integration/CustomAsyncMatchersSpec.js b/spec/core/integration/CustomAsyncMatchersSpec.js index 51ddef34..de4806c3 100644 --- a/spec/core/integration/CustomAsyncMatchersSpec.js +++ b/spec/core/integration/CustomAsyncMatchersSpec.js @@ -111,4 +111,36 @@ describe('Custom Async Matchers (Integration)', function() { env.addReporter({ specDone: specExpectations, jasmineDone: done }); env.execute(); }); + + it("provides custom equality testers to the matcher factory via matchersUtil", function(done) { + jasmine.getEnv().requirePromises(); + + var matcherFactory = function (matchersUtil) { + return { + compare: function (actual, expected) { + return Promise.resolve({pass: matchersUtil.equals(actual[0], expected)}); + } + }; + }, + customEqualityFn = jasmine.createSpy("customEqualityFn").and.callFake(function (a, b) { + return a.toString() === b; + }); + + env.it("spec with expectation", function() { + env.addCustomEqualityTester(customEqualityFn); + env.addAsyncMatchers({ + toBeArrayWithFirstElement: matcherFactory + }); + + return env.expectAsync([1, 2]).toBeArrayWithFirstElement("1"); + }); + + var specExpectations = function(result) { + expect(customEqualityFn).toHaveBeenCalledWith(1, "1"); + expect(result.failedExpectations).toEqual([]); + }; + + env.addReporter({ specDone: specExpectations, jasmineDone: done }); + env.execute(); + }); }); diff --git a/spec/core/integration/CustomMatchersSpec.js b/spec/core/integration/CustomMatchersSpec.js index 4bb0c352..dfca7a78 100644 --- a/spec/core/integration/CustomMatchersSpec.js +++ b/spec/core/integration/CustomMatchersSpec.js @@ -1,19 +1,19 @@ -describe("Custom Matchers (Integration)", function() { +describe("Custom Matchers (Integration)", function () { var env; var fakeTimer; - beforeEach(function() { + beforeEach(function () { env = new jasmineUnderTest.Env(); env.configure({random: false}); }); - it("allows adding more matchers local to a spec", function(done) { - env.it('spec defining a custom matcher', function() { + it("allows adding more matchers local to a spec", function (done) { + env.it('spec defining a custom matcher', function () { env.addMatchers({ - matcherForSpec: function() { + matcherForSpec: function () { return { - compare: function(actual, expected) { - return { pass: false, message: "matcherForSpec: actual: " + actual + "; expected: " + expected }; + compare: function (actual, expected) { + return {pass: false, message: "matcherForSpec: actual: " + actual + "; expected: " + expected}; } } } @@ -83,6 +83,8 @@ describe("Custom Matchers (Integration)", function() { it("supports asymmetric equality testers that take a list of custom equality testers", function(done) { // TODO: remove this in the next major release. + spyOn(jasmineUnderTest, 'getEnv').and.returnValue(env); + env.it("spec using custom asymmetric equality tester", function() { var customEqualityFn = function(a, b) { if (a === 2 && b === "two") { @@ -229,4 +231,34 @@ describe("Custom Matchers (Integration)", function() { env.addReporter({ specDone: specExpectations, jasmineDone: done }); env.execute(); }); + + it("provides custom equality testers to the matcher factory via matchersUtil", function (done) { + var matcherFactory = function (matchersUtil) { + return { + compare: function (actual, expected) { + return {pass: matchersUtil.equals(actual[0], expected)}; + } + }; + }, + customEqualityFn = jasmine.createSpy("customEqualityFn").and.callFake(function (a, b) { + return a.toString() === b; + }); + + env.it("spec with expectation", function () { + env.addCustomEqualityTester(customEqualityFn); + env.addMatchers({ + toBeArrayWithFirstElement: matcherFactory + }); + + env.expect([1, 2]).toBeArrayWithFirstElement("1"); + }); + + var specExpectations = function (result) { + expect(customEqualityFn).toHaveBeenCalledWith(1, "1"); + expect(result.failedExpectations).toEqual([]); + }; + + env.addReporter({specDone: specExpectations, jasmineDone: done}); + env.execute(); + }); }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index e31f7d9d..2536fa07 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -2149,7 +2149,7 @@ describe("Env integration", function() { env.it('is a spec without any expectations', function() { // does nothing, just a mock spec without expectations }); - + }); it('should report "failed" status if "failSpecWithNoExpectations" is enabled', function(done) { @@ -2556,4 +2556,33 @@ describe("Env integration", function() { env.execute(); }); + + it("supports asymmetric equality testers that take a matchersUtil", function(done) { + var env = new jasmineUnderTest.Env(); + + env.it("spec using custom asymmetric equality tester", function() { + var customEqualityFn = function(a, b) { + if (a === 2 && b === "two") { + return true; + } + }; + var arrayWithFirstElement = function(sample) { + return { + asymmetricMatch: function (actual, matchersUtil) { + return matchersUtil.equals(sample, actual[0]); + } + }; + }; + + env.addCustomEqualityTester(customEqualityFn); + env.expect(["two"]).toEqual(arrayWithFirstElement(2)); + }); + + var specExpectations = function(result) { + expect(result.status).toEqual('passed'); + }; + + env.addReporter({ specDone: specExpectations, jasmineDone: done }); + env.execute(); + }); }); diff --git a/spec/core/integration/MatchersSpec.js b/spec/core/integration/MatchersSpec.js index 6f81ea04..b21208d5 100644 --- a/spec/core/integration/MatchersSpec.js +++ b/spec/core/integration/MatchersSpec.js @@ -449,7 +449,10 @@ describe('Matchers (Integration)', function() { describe('toThrow', function() { verifyPasses(function(env) { - env.expect(function() { throw new Error(); }).toThrow(); + env.addCustomEqualityTester(function(a, b) { + return a.toString() === b.toString(); + }); + env.expect(function() { throw '5'; }).toThrow(5); }); verifyFails(function(env) { diff --git a/spec/core/matchers/async/toBeRejectedSpec.js b/spec/core/matchers/async/toBeRejectedSpec.js index 442ddba2..61bd2a5a 100644 --- a/spec/core/matchers/async/toBeRejectedSpec.js +++ b/spec/core/matchers/async/toBeRejectedSpec.js @@ -2,7 +2,8 @@ describe('toBeRejected', function() { it('passes if the actual is rejected', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejected(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejected(matchersUtil), actual = Promise.reject('AsyncExpectationSpec rejection'); return matcher.compare(actual).then(function(result) { @@ -13,7 +14,8 @@ describe('toBeRejected', function() { it('fails if the actual is resolved', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejected(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejected(matchersUtil), actual = Promise.resolve(); return matcher.compare(actual).then(function(result) { @@ -22,7 +24,8 @@ describe('toBeRejected', function() { }); it('fails if actual is not a promise', function() { - var matcher = jasmineUnderTest.asyncMatchers.toBeRejected(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejected(matchersUtil), actual = 'not a promise'; function f() { diff --git a/spec/core/matchers/async/toBeRejectedWithErrorSpec.js b/spec/core/matchers/async/toBeRejectedWithErrorSpec.js index 3b207645..03a337b0 100644 --- a/spec/core/matchers/async/toBeRejectedWithErrorSpec.js +++ b/spec/core/matchers/async/toBeRejectedWithErrorSpec.js @@ -2,7 +2,8 @@ describe('#toBeRejectedWithError', function () { it('passes when Error type matches', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new TypeError('foo')); return matcher.compare(actual, TypeError).then(function (result) { @@ -16,7 +17,8 @@ describe('#toBeRejectedWithError', function () { it('passes when Error type and message matches', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new TypeError('foo')); return matcher.compare(actual, TypeError, 'foo').then(function (result) { @@ -30,7 +32,8 @@ describe('#toBeRejectedWithError', function () { it('passes when Error matches and is exactly Error', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error()); return matcher.compare(actual, Error).then(function (result) { @@ -45,7 +48,8 @@ describe('#toBeRejectedWithError', function () { it('passes when Error message matches a string', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error('foo')); return matcher.compare(actual, 'foo').then(function (result) { @@ -59,7 +63,8 @@ describe('#toBeRejectedWithError', function () { it('passes when Error message matches a RegExp', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error('foo')); return matcher.compare(actual, /foo/).then(function (result) { @@ -73,7 +78,8 @@ describe('#toBeRejectedWithError', function () { it('passes when Error message is empty', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error()); return matcher.compare(actual, '').then(function (result) { @@ -87,7 +93,8 @@ describe('#toBeRejectedWithError', function () { it('passes when no arguments', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error()); return matcher.compare(actual, void 0).then(function (result) { @@ -101,7 +108,8 @@ describe('#toBeRejectedWithError', function () { it('fails when resolved', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.resolve(new Error('foo')); return matcher.compare(actual, 'foo').then(function (result) { @@ -115,7 +123,8 @@ describe('#toBeRejectedWithError', function () { it('fails when rejected with non Error type', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject('foo'); return matcher.compare(actual, 'foo').then(function (result) { @@ -129,7 +138,8 @@ describe('#toBeRejectedWithError', function () { it('fails when Error type mismatches', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error('foo')); return matcher.compare(actual, TypeError, 'foo').then(function (result) { @@ -143,7 +153,8 @@ describe('#toBeRejectedWithError', function () { it('fails when Error message mismatches', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error('foo')); return matcher.compare(actual, 'bar').then(function (result) { @@ -155,7 +166,8 @@ describe('#toBeRejectedWithError', function () { }); it('fails if actual is not a promise', function() { - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = 'not a promise'; function f() { diff --git a/spec/core/matchers/async/toBeRejectedWithSpec.js b/spec/core/matchers/async/toBeRejectedWithSpec.js index ea927c20..c1d22d0b 100644 --- a/spec/core/matchers/async/toBeRejectedWithSpec.js +++ b/spec/core/matchers/async/toBeRejectedWithSpec.js @@ -2,7 +2,8 @@ describe('#toBeRejectedWith', function () { it('should return true if the promise is rejected with the expected value', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = Promise.reject({error: 'PEBCAK'}); return matcher.compare(actual, {error: 'PEBCAK'}).then(function (result) { @@ -13,7 +14,8 @@ describe('#toBeRejectedWith', function () { it('should fail if the promise resolves', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = Promise.resolve(); return matcher.compare(actual, '').then(function (result) { @@ -24,7 +26,8 @@ describe('#toBeRejectedWith', function () { it('should fail if the promise is rejected with a different value', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = Promise.reject('A Bad Apple'); return matcher.compare(actual, 'Some Cool Thing').then(function (result) { @@ -38,7 +41,8 @@ describe('#toBeRejectedWith', function () { it('should build its error correctly when negated', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = Promise.reject(true); return matcher.compare(actual, true).then(function (result) { @@ -53,7 +57,8 @@ describe('#toBeRejectedWith', function () { jasmine.getEnv().requirePromises(); var customEqualityTesters = [function() { return true; }], - matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(jasmineUnderTest.matchersUtil, customEqualityTesters), + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: customEqualityTesters}), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = Promise.reject('actual'); return matcher.compare(actual, 'expected').then(function(result) { @@ -62,7 +67,8 @@ describe('#toBeRejectedWith', function () { }); it('fails if actual is not a promise', function() { - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = 'not a promise'; function f() { diff --git a/spec/core/matchers/async/toBeResolvedSpec.js b/spec/core/matchers/async/toBeResolvedSpec.js index 28297e30..a6ab25ba 100644 --- a/spec/core/matchers/async/toBeResolvedSpec.js +++ b/spec/core/matchers/async/toBeResolvedSpec.js @@ -2,7 +2,8 @@ describe('toBeResolved', function() { it('passes if the actual is resolved', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeResolved(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolved(matchersUtil), actual = Promise.resolve(); return matcher.compare(actual).then(function(result) { @@ -13,7 +14,8 @@ describe('toBeResolved', function() { it('fails if the actual is rejected', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeResolved(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolved(matchersUtil), actual = Promise.reject('AsyncExpectationSpec rejection'); return matcher.compare(actual).then(function(result) { @@ -22,7 +24,8 @@ describe('toBeResolved', function() { }); it('fails if actual is not a promise', function() { - var matcher = jasmineUnderTest.asyncMatchers.toBeResolved(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolved(matchersUtil), actual = 'not a promise'; function f() { diff --git a/spec/core/matchers/async/toBeResolvedToSpec.js b/spec/core/matchers/async/toBeResolvedToSpec.js index dc330920..426f9f12 100644 --- a/spec/core/matchers/async/toBeResolvedToSpec.js +++ b/spec/core/matchers/async/toBeResolvedToSpec.js @@ -2,7 +2,8 @@ describe('#toBeResolvedTo', function() { it('passes if the promise is resolved to the expected value', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.resolve({foo: 42}); return matcher.compare(actual, {foo: 42}).then(function(result) { @@ -13,7 +14,8 @@ describe('#toBeResolvedTo', function() { it('fails if the promise is rejected', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.reject('AsyncExpectationSpec error'); return matcher.compare(actual, '').then(function(result) { @@ -27,7 +29,8 @@ describe('#toBeResolvedTo', function() { it('fails if the promise is resolved to a different value', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.resolve({foo: 17}); return matcher.compare(actual, {foo: 42}).then(function(result) { @@ -41,7 +44,8 @@ describe('#toBeResolvedTo', function() { it('builds its message correctly when negated', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.resolve(true); return matcher.compare(actual, true).then(function(result) { @@ -56,7 +60,8 @@ describe('#toBeResolvedTo', function() { jasmine.getEnv().requirePromises(); var customEqualityTesters = [function() { return true; }], - matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(jasmineUnderTest.matchersUtil, customEqualityTesters), + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: customEqualityTesters}), + matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.resolve('actual'); return matcher.compare(actual, 'expected').then(function(result) { @@ -65,7 +70,8 @@ describe('#toBeResolvedTo', function() { }); it('fails if actual is not a promise', function() { - var matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = 'not a promise'; function f() { diff --git a/spec/core/matchers/matchersUtilSpec.js b/spec/core/matchers/matchersUtilSpec.js index 570f8f20..096d7c86 100644 --- a/spec/core/matchers/matchersUtilSpec.js +++ b/spec/core/matchers/matchersUtilSpec.js @@ -1,178 +1,210 @@ describe("matchersUtil", function() { describe("equals", function() { it("passes for literals that are triple-equal", function() { - expect(jasmineUnderTest.matchersUtil.equals(null, null)).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(void 0, void 0)).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(null, null)).toBe(true); + expect(matchersUtil.equals(void 0, void 0)).toBe(true); }); it("fails for things that are not equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals({a: "foo"}, 1)).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals({a: "foo"}, 1)).toBe(false); }); it("passes for Strings that are equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals("foo", "foo")).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals("foo", "foo")).toBe(true); }); it("fails for Strings that are not equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals("foo", "bar")).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals("foo", "bar")).toBe(false); }); it("passes for Numbers that are equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(123, 123)).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(123, 123)).toBe(true); }); it("fails for Numbers that are not equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(123, 456)).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(123, 456)).toBe(false); }); it("passes for Dates that are equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(new Date("Jan 1, 1970"), new Date("Jan 1, 1970"))).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(new Date("Jan 1, 1970"), new Date("Jan 1, 1970"))).toBe(true); }); it("fails for Dates that are not equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(new Date("Jan 1, 1970"), new Date("Feb 3, 1991"))).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(new Date("Jan 1, 1970"), new Date("Feb 3, 1991"))).toBe(false); }); it("passes for Booleans that are equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(true, true)).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(true, true)).toBe(true); }); it("fails for Booleans that are not equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(true, false)).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(true, false)).toBe(false); }); it("passes for RegExps that are equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(/foo/, /foo/)).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(/foo/, /foo/)).toBe(true); }); it("fails for RegExps that are not equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(/foo/, /bar/)).toBe(false); - expect(jasmineUnderTest.matchersUtil.equals(new RegExp("foo", "i"), new RegExp("foo"))).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(/foo/, /bar/)).toBe(false); + expect(matchersUtil.equals(new RegExp("foo", "i"), new RegExp("foo"))).toBe(false); }); it("passes for Arrays that are equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals([1, 2], [1, 2])).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals([1, 2], [1, 2])).toBe(true); }); it("passes for Arrays that are equivalent, with elements added by changing length", function() { - var foo = []; + var foo = [], + matchersUtil = new jasmineUnderTest.MatchersUtil(); foo.length = 1; - expect(jasmineUnderTest.matchersUtil.equals(foo, [undefined])).toBe(true); + expect(matchersUtil.equals(foo, [undefined])).toBe(true); }); it("fails for Arrays that have different lengths", function() { - expect(jasmineUnderTest.matchersUtil.equals([1, 2], [1, 2, 3])).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals([1, 2], [1, 2, 3])).toBe(false); }); it("fails for Arrays that have different elements", function() { - expect(jasmineUnderTest.matchersUtil.equals([1, 2, 3], [1, 5, 3])).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals([1, 2, 3], [1, 5, 3])).toBe(false); }); it("fails for Arrays whose contents are equivalent, but have differing properties", function() { var one = [1,2,3], - two = [1,2,3]; + two = [1,2,3], + matchersUtil = new jasmineUnderTest.MatchersUtil(); one.foo = 'bar'; two.foo = 'baz'; - expect(jasmineUnderTest.matchersUtil.equals(one, two)).toBe(false); + expect(matchersUtil.equals(one, two)).toBe(false); }); it("passes for Arrays with equivalent contents and properties", function() { var one = [1,2,3], - two = [1,2,3]; + two = [1,2,3], + matchersUtil = new jasmineUnderTest.MatchersUtil(); one.foo = 'bar'; two.foo = 'bar'; - expect(jasmineUnderTest.matchersUtil.equals(one, two)).toBe(true); + expect(matchersUtil.equals(one, two)).toBe(true); }); it("passes for Errors that are the same type and have the same message", function() { - expect(jasmineUnderTest.matchersUtil.equals(new Error("foo"), new Error("foo"))).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(new Error("foo"), new Error("foo"))).toBe(true); }); it("fails for Errors that are the same type and have different messages", function() { - expect(jasmineUnderTest.matchersUtil.equals(new Error("foo"), new Error("bar"))).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(new Error("foo"), new Error("bar"))).toBe(false); }); it("fails for objects with different constructors", function() { + var matchersUtil = new jasmineUnderTest.MatchersUtil(); function One() {} function Two() {} - expect(jasmineUnderTest.matchersUtil.equals(new One(), new Two())).toBe(false); + expect(matchersUtil.equals(new One(), new Two())).toBe(false); }); it("passes for Objects that are equivalent (simple case)", function() { - expect(jasmineUnderTest.matchersUtil.equals({a: "foo"}, {a: "foo"})).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals({a: "foo"}, {a: "foo"})).toBe(true); }); it("fails for Objects that are not equivalent (simple case)", function() { - expect(jasmineUnderTest.matchersUtil.equals({a: "foo"}, {a: "bar"})).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals({a: "foo"}, {a: "bar"})).toBe(false); }); it("passes for Objects that are equivalent (deep case)", function() { - expect(jasmineUnderTest.matchersUtil.equals({a: "foo", b: { c: "bar"}}, {a: "foo", b: { c: "bar"}})).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals({a: "foo", b: { c: "bar"}}, {a: "foo", b: { c: "bar"}})).toBe(true); }); it("fails for Objects that are not equivalent (deep case)", function() { - expect(jasmineUnderTest.matchersUtil.equals({a: "foo", b: { c: "baz"}}, {a: "foo", b: { c: "bar"}})).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals({a: "foo", b: { c: "baz"}}, {a: "foo", b: { c: "bar"}})).toBe(false); }); it("passes for Objects that are equivalent (with cycles)", function() { var actual = { a: "foo" }, - expected = { a: "foo" }; + expected = { a: "foo" }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); actual.b = actual; expected.b = actual; - expect(jasmineUnderTest.matchersUtil.equals(actual, expected)).toBe(true); + expect(matchersUtil.equals(actual, expected)).toBe(true); }); it("fails for Objects that are not equivalent (with cycles)", function() { var actual = { a: "foo" }, - expected = { a: "bar" }; + expected = { a: "bar" }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); actual.b = actual; expected.b = actual; - expect(jasmineUnderTest.matchersUtil.equals(actual, expected)).toBe(false); + expect(matchersUtil.equals(actual, expected)).toBe(false); }); it("fails for Objects that have the same number of keys, but different keys/values", function () { var expected = { a: undefined }, - actual = { b: 1 }; + actual = { b: 1 }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(actual, expected)).toBe(false); + expect(matchersUtil.equals(actual, expected)).toBe(false); }); it("fails when comparing an empty object to an empty array (issue #114)", function() { var emptyObject = {}, - emptyArray = []; + emptyArray = [], + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(emptyObject, emptyArray)).toBe(false); - expect(jasmineUnderTest.matchersUtil.equals(emptyArray, emptyObject)).toBe(false); + + expect(matchersUtil.equals(emptyObject, emptyArray)).toBe(false); + expect(matchersUtil.equals(emptyArray, emptyObject)).toBe(false); }); it("passes for equivalent frozen objects (GitHub issue #266)", function() { var a = { foo: 1 }, - b = {foo: 1 }; + b = {foo: 1 }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); Object.freeze(a); Object.freeze(b); - expect(jasmineUnderTest.matchersUtil.equals(a,b)).toBe(true); + expect(matchersUtil.equals(a,b)).toBe(true); }); it("passes for equivalent Promises (GitHub issue #1314)", function() { if (typeof Promise === 'undefined') { return; } var p1 = new Promise(function () {}), - p2 = new Promise(function () {}); + p2 = new Promise(function () {}), + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(p1, p1)).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(p1, p2)).toBe(false); + expect(matchersUtil.equals(p1, p1)).toBe(true); + expect(matchersUtil.equals(p1, p2)).toBe(false); }); describe("when running in a browser", function() { @@ -185,6 +217,8 @@ describe("matchersUtil", function() { return; } var a = document.createElement("div"); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + a.setAttribute("test-attr", "attr-value"); a.appendChild(document.createTextNode('test')); @@ -192,17 +226,18 @@ describe("matchersUtil", function() { b.setAttribute("test-attr", "attr-value"); b.appendChild(document.createTextNode('test')); - expect(jasmineUnderTest.matchersUtil.equals(a,b)).toBe(true); + expect(matchersUtil.equals(a,b)).toBe(true); }); it("passes for equivalent objects from different frames", function() { if (isNotRunningInBrowser()) { return; } + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var iframe = document.createElement('iframe'); document.body.appendChild(iframe); iframe.contentWindow.eval('window.testObject = {}'); - expect(jasmineUnderTest.matchersUtil.equals({}, iframe.contentWindow.testObject)).toBe(true); + expect(matchersUtil.equals({}, iframe.contentWindow.testObject)).toBe(true); document.body.removeChild(iframe); }); @@ -210,6 +245,7 @@ describe("matchersUtil", function() { if (isNotRunningInBrowser()) { return; } + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var a = document.createElement("div"); a.setAttribute("test-attr", "attr-value"); a.appendChild(document.createTextNode('test')); @@ -218,16 +254,16 @@ describe("matchersUtil", function() { b.setAttribute("test-attr", "attr-value2"); b.appendChild(document.createTextNode('test')); - expect(jasmineUnderTest.matchersUtil.equals(a,b)).toBe(false); + expect(matchersUtil.equals(a,b)).toBe(false); b.setAttribute("test-attr", "attr-value"); - expect(jasmineUnderTest.matchersUtil.equals(a,b)).toBe(true); + expect(matchersUtil.equals(a,b)).toBe(true); b.appendChild(document.createTextNode('2')); - expect(jasmineUnderTest.matchersUtil.equals(a,b)).toBe(false); + expect(matchersUtil.equals(a,b)).toBe(false); a.appendChild(document.createTextNode('2')); - expect(jasmineUnderTest.matchersUtil.equals(a,b)).toBe(true); + expect(matchersUtil.equals(a,b)).toBe(true); }); }); @@ -240,43 +276,47 @@ describe("matchersUtil", function() { if (isNotRunningInNode()) { return; } + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var vm = require('vm'); var sandbox = { obj: null }; vm.runInNewContext('obj = {a: 1, b: 2}', sandbox); - expect(jasmineUnderTest.matchersUtil.equals(sandbox.obj, {a: 1, b: 2})).toBe(true); + expect(matchersUtil.equals(sandbox.obj, {a: 1, b: 2})).toBe(true); }); it("passes for equivalent arrays from different vm contexts", function() { if (isNotRunningInNode()) { return; } + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var vm = require('vm'); var sandbox = { arr: null }; vm.runInNewContext('arr = [1, 2]', sandbox); - expect(jasmineUnderTest.matchersUtil.equals(sandbox.arr, [1, 2])).toBe(true); + expect(matchersUtil.equals(sandbox.arr, [1, 2])).toBe(true); }); }); it("passes when Any is used", function() { var number = 3, - anyNumber = new jasmineUnderTest.Any(Number); + anyNumber = new jasmineUnderTest.Any(Number), + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(number, anyNumber)).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(anyNumber, number)).toBe(true); + expect(matchersUtil.equals(number, anyNumber)).toBe(true); + expect(matchersUtil.equals(anyNumber, number)).toBe(true); }); it("fails when Any is compared to something unexpected", function() { var number = 3, - anyString = new jasmineUnderTest.Any(String); + anyString = new jasmineUnderTest.Any(String), + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(number, anyString)).toBe(false); - expect(jasmineUnderTest.matchersUtil.equals(anyString, number)).toBe(false); + expect(matchersUtil.equals(number, anyString)).toBe(false); + expect(matchersUtil.equals(anyString, number)).toBe(false); }); it("passes when ObjectContaining is used", function() { @@ -284,135 +324,209 @@ describe("matchersUtil", function() { foo: 3, bar: 7 }, - containing = new jasmineUnderTest.ObjectContaining({foo: 3}); + containing = new jasmineUnderTest.ObjectContaining({foo: 3}), + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(obj, containing)).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(containing, obj)).toBe(true); + expect(matchersUtil.equals(obj, containing)).toBe(true); + expect(matchersUtil.equals(containing, obj)).toBe(true); }); it("passes when MapContaining is used", function() { jasmine.getEnv().requireFunctioningMaps(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var obj = new Map(); obj.set(1, 2); obj.set('foo', 'bar'); var containing = new jasmineUnderTest.MapContaining(new Map()); containing.sample.set('foo', 'bar'); - expect(jasmineUnderTest.matchersUtil.equals(obj, containing)).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(containing, obj)).toBe(true); + expect(matchersUtil.equals(obj, containing)).toBe(true); + expect(matchersUtil.equals(containing, obj)).toBe(true); }); it("passes when SetContaining is used", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var obj = new Set(); obj.add(1); obj.add('foo'); var containing = new jasmineUnderTest.SetContaining(new Set()); containing.sample.add(1); - expect(jasmineUnderTest.matchersUtil.equals(obj, containing)).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(containing, obj)).toBe(true); + expect(matchersUtil.equals(obj, containing)).toBe(true); + expect(matchersUtil.equals(containing, obj)).toBe(true); }); it("passes when an asymmetric equality tester returns true", function() { - var tester = { asymmetricMatch: function(other) { return true; } }; + var tester = { asymmetricMatch: function(other) { return true; } }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(false, tester)).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(tester, false)).toBe(true); + expect(matchersUtil.equals(false, tester)).toBe(true); + expect(matchersUtil.equals(tester, false)).toBe(true); }); it("fails when an asymmetric equality tester returns false", function() { - var tester = { asymmetricMatch: function(other) { return false; } }; + var tester = { asymmetricMatch: function(other) { return false; } }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(true, tester)).toBe(false); - expect(jasmineUnderTest.matchersUtil.equals(tester, true)).toBe(false); + expect(matchersUtil.equals(true, tester)).toBe(false); + expect(matchersUtil.equals(tester, true)).toBe(false); }); it("passes when ArrayContaining is used", function() { - var arr = ["foo", "bar"]; + var arr = ["foo", "bar"], + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(arr, new jasmineUnderTest.ArrayContaining(["bar"]))).toBe(true); + expect(matchersUtil.equals(arr, new jasmineUnderTest.ArrayContaining(["bar"]))).toBe(true); }); - it("passes when a custom equality matcher returns true", function() { - var tester = function(a, b) { return true; }; + it("passes when a custom equality matcher passed to equals returns true", function() { + // TODO: remove this in the next major release. + var tester = function(a, b) { return true; }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(1, 2, [tester])).toBe(true); + expect(matchersUtil.equals(1, 2, [tester])).toBe(true); + }); + + it("passes when a custom equality matcher passed to the constructor returns true", function() { + var tester = function(a, b) { return true; }, + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); + + expect(matchersUtil.equals(1, 2)).toBe(true); }); it("passes for two empty Objects", function () { - expect(jasmineUnderTest.matchersUtil.equals({}, {})).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals({}, {})).toBe(true); }); - describe("when a custom equality matcher is installed that returns 'undefined'", function () { + describe("when a custom equality matcher is passed to equals that returns 'undefined'", function () { + // TODO: remove this in the next major release. var tester = function(a, b) { return jasmine.undefined; }; it("passes for two empty Objects", function () { - expect(jasmineUnderTest.matchersUtil.equals({}, {}, [tester])).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals({}, {}, [tester])).toBe(true); }); }); - it("fails for equivalents when a custom equality matcher returns false", function() { - var tester = function(a, b) { return false; }; + describe("when a custom equality matcher is passed to the constructor that returns 'undefined'", function () { + var tester = function(a, b) { return jasmine.undefined; }; - expect(jasmineUnderTest.matchersUtil.equals(1, 1, [tester])).toBe(false); + it("passes for two empty Objects", function () { + var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); + expect(matchersUtil.equals({}, {})).toBe(true); + }); }); - it("passes for an asymmetric equality tester that returns true when a custom equality tester return false", function() { + it("fails for equivalents when a custom equality matcher passed to equals returns false", function() { + // TODO: remove this in the next major release. + var tester = function(a, b) { return false; }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); + + expect(matchersUtil.equals(1, 1, [tester])).toBe(false); + }); + + it("fails for equivalents when a custom equality matcher passed to the constructor returns false", function() { + var tester = function(a, b) { return false; }, + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); + + expect(matchersUtil.equals(1, 1)).toBe(false); + }); + + it("passes for an asymmetric equality tester that returns true when a custom equality tester passed to equals return false", function() { + // TODO: remove this in the next major release. var asymmetricTester = { asymmetricMatch: function(other) { return true; } }, - symmetricTester = function(a, b) { return false; }; + symmetricTester = function(a, b) { return false; }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(asymmetricTester, true, [symmetricTester])).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(true, asymmetricTester, [symmetricTester])).toBe(true); + expect(matchersUtil.equals(asymmetricTester, true, [symmetricTester])).toBe(true); + expect(matchersUtil.equals(true, asymmetricTester, [symmetricTester])).toBe(true); }); - it("passes custom equality matchers to asymmetric equality testers", function() { - var tester = function(a, b) {}; - var asymmetricTester = { asymmetricMatch: jasmine.createSpy('asymmetricMatch') }; - asymmetricTester.asymmetricMatch.and.returnValue(true); - var other = {}; + it("passes for an asymmetric equality tester that returns true when a custom equality tester passed to the constructor return false", function() { + var asymmetricTester = { asymmetricMatch: function(other) { return true; } }, + symmetricTester = function(a, b) { return false; }, + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [symmetricTester()]}); - expect(jasmineUnderTest.matchersUtil.equals(asymmetricTester, other, [tester])).toBe(true); - expect(asymmetricTester.asymmetricMatch).toHaveBeenCalledWith(other, [tester]); + expect(matchersUtil.equals(asymmetricTester, true)).toBe(true); + expect(matchersUtil.equals(true, asymmetricTester)).toBe(true); + }); + + describe("The compatibility shim passed to asymmetric equality testers", function() { + describe("When equals is called with custom equality testers", function() { + it("is both a matchersUtil and the custom equality testers passed to equals", function() { + var asymmetricTester = jasmine.createSpyObj('tester', ['asymmetricMatch']), + symmetricTester = function() { } , + matchersUtil = new jasmineUnderTest.MatchersUtil(), + shim; + + matchersUtil.equals(true, asymmetricTester, [symmetricTester]); + shim = asymmetricTester.asymmetricMatch.calls.argsFor(0)[1]; + expect(shim).toEqual(jasmine.any(jasmineUnderTest.MatchersUtil)); + expect(shim.length).toEqual(1); + expect(shim[0]).toEqual(symmetricTester); + }); + }); + + describe("When equals is called with custom equality testers", function() { + it("is both a matchersUtil and the custom equality testers passed to the constructor", function() { + var asymmetricTester = jasmine.createSpyObj('tester', ['asymmetricMatch']), + symmetricTester = function() { } , + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [symmetricTester]}), + shim; + + matchersUtil.equals(true, asymmetricTester); + shim = asymmetricTester.asymmetricMatch.calls.argsFor(0)[1]; + expect(shim).toEqual(jasmine.any(jasmineUnderTest.MatchersUtil)); + expect(shim.length).toEqual(1); + expect(shim[0]).toEqual(symmetricTester); + }); + }); }); it("passes when an Any is compared to an Any that checks for the same type", function() { var any1 = new jasmineUnderTest.Any(Function), - any2 = new jasmineUnderTest.Any(Function); + any2 = new jasmineUnderTest.Any(Function), + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(any1, any2)).toBe(true); + expect(matchersUtil.equals(any1, any2)).toBe(true); }); it("passes for null prototype objects with same properties", function () { var objA = Object.create(null), - objB = Object.create(null); + objB = Object.create(null), + matchersUtil = new jasmineUnderTest.MatchersUtil(); objA.name = 'test'; objB.name = 'test'; - expect(jasmineUnderTest.matchersUtil.equals(objA, objB)).toBe(true); + expect(matchersUtil.equals(objA, objB)).toBe(true); }); it("fails for null prototype objects with different properties", function () { var objA = Object.create(null), - objB = Object.create(null); + objB = Object.create(null), + matchersUtil = new jasmineUnderTest.MatchersUtil(); objA.name = 'test'; objB.test = 'name'; - expect(jasmineUnderTest.matchersUtil.equals(objA, objB)).toBe(false); + expect(matchersUtil.equals(objA, objB)).toBe(false); }); it("passes when comparing two empty sets", function() { jasmine.getEnv().requireFunctioningSets(); - expect(jasmineUnderTest.matchersUtil.equals(new Set(), new Set())).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(new Set(), new Set())).toBe(true); }); it("passes when comparing identical sets", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var setA = new Set(); setA.add(6); setA.add(5); @@ -420,12 +534,13 @@ describe("matchersUtil", function() { setB.add(6); setB.add(5); - expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(true); + expect(matchersUtil.equals(setA, setB)).toBe(true); }); it("passes when comparing identical sets with different insertion order and simple elements", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var setA = new Set(); setA.add(3); setA.add(6); @@ -433,12 +548,13 @@ describe("matchersUtil", function() { setB.add(6); setB.add(3); - expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(true); + expect(matchersUtil.equals(setA, setB)).toBe(true); }); it("passes when comparing identical sets with different insertion order and complex elements 1", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var setA1 = new Set(); setA1.add(['a',3]); setA1.add([6,1]); @@ -460,12 +576,13 @@ describe("matchersUtil", function() { setB.add(setB1); setB.add(setB2); - expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(true); + expect(matchersUtil.equals(setA, setB)).toBe(true); }); it("passes when comparing identical sets with different insertion order and complex elements 2", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var setA = new Set(); setA.add([[1,2], [3,4]]); setA.add([[5,6], [7,8]]); @@ -473,11 +590,12 @@ describe("matchersUtil", function() { setB.add([[5,6], [7,8]]); setB.add([[1,2], [3,4]]); - expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(true); + expect(matchersUtil.equals(setA, setB)).toBe(true); }); it("fails for sets with different elements", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var setA = new Set(); setA.add(6); setA.add(3); @@ -487,11 +605,12 @@ describe("matchersUtil", function() { setB.add(4); setB.add(5); - expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(false); + expect(matchersUtil.equals(setA, setB)).toBe(false); }); it("fails for sets of different size", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var setA = new Set(); setA.add(6); setA.add(3); @@ -500,36 +619,40 @@ describe("matchersUtil", function() { setB.add(4); setB.add(5); - expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(false); + expect(matchersUtil.equals(setA, setB)).toBe(false); }); it("passes when comparing two empty maps", function() { jasmine.getEnv().requireFunctioningMaps(); - expect(jasmineUnderTest.matchersUtil.equals(new Map(), new Map())).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(new Map(), new Map())).toBe(true); }); it("passes when comparing identical maps", function() { jasmine.getEnv().requireFunctioningMaps(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var mapA = new Map(); mapA.set(6, 5); var mapB = new Map(); mapB.set(6, 5); - expect(jasmineUnderTest.matchersUtil.equals(mapA, mapB)).toBe(true); + expect(matchersUtil.equals(mapA, mapB)).toBe(true); }); it("passes when comparing identical maps with different insertion order", function() { jasmine.getEnv().requireFunctioningMaps(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var mapA = new Map(); mapA.set("a", 3); mapA.set(6, 1); var mapB = new Map(); mapB.set(6, 1); mapB.set("a", 3); - expect(jasmineUnderTest.matchersUtil.equals(mapA, mapB)).toBe(true); + expect(matchersUtil.equals(mapA, mapB)).toBe(true); }); it("fails for maps with different elements", function() { jasmine.getEnv().requireFunctioningMaps(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var mapA = new Map(); mapA.set(6, 3); mapA.set(5, 1); @@ -537,17 +660,18 @@ describe("matchersUtil", function() { mapB.set(6, 4); mapB.set(5, 1); - expect(jasmineUnderTest.matchersUtil.equals(mapA, mapB)).toBe(false); + expect(matchersUtil.equals(mapA, mapB)).toBe(false); }); it("fails for maps of different size", function() { jasmine.getEnv().requireFunctioningMaps(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var mapA = new Map(); mapA.set(6, 3); var mapB = new Map(); mapB.set(6, 4); mapB.set(5, 1); - expect(jasmineUnderTest.matchersUtil.equals(mapA, mapB)).toBe(false); + expect(matchersUtil.equals(mapA, mapB)).toBe(false); }); describe("when running in an environment with array polyfills", function() { @@ -593,71 +717,119 @@ describe("matchersUtil", function() { expect(['foo']).toEqual(['foo']); }); }); + + it('uses a diffBuilder if one is provided as the fourth argument', function() { + // TODO: remove this in the next major release. + var diffBuilder = new jasmineUnderTest.DiffBuilder(), + matchersUtil = new jasmineUnderTest.MatchersUtil(); + + spyOn(diffBuilder, 'record'); + 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); + }); + + 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, '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); + }); }); describe("contains", function() { it("passes when expected is a substring of actual", function() { - expect(jasmineUnderTest.matchersUtil.contains("ABC", "BC")).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.contains("ABC", "BC")).toBe(true); }); it("fails when expected is a not substring of actual", function() { - expect(jasmineUnderTest.matchersUtil.contains("ABC", "X")).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.contains("ABC", "X")).toBe(false); }); it("passes when expected is an element in an actual array", function() { - expect(jasmineUnderTest.matchersUtil.contains(['foo', 'bar'], 'foo')).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.contains(['foo', 'bar'], 'foo')).toBe(true); }); it("fails when expected is not an element in an actual array", function() { - expect(jasmineUnderTest.matchersUtil.contains(['foo', 'bar'], 'baz')).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.contains(['foo', 'bar'], 'baz')).toBe(false); }); it("passes with mixed-element arrays", function() { - expect(jasmineUnderTest.matchersUtil.contains(["foo", {some: "bar"}], "foo")).toBe(true); - expect(jasmineUnderTest.matchersUtil.contains(["foo", {some: "bar"}], {some: "bar"})).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.contains(["foo", {some: "bar"}], "foo")).toBe(true); + expect(matchersUtil.contains(["foo", {some: "bar"}], {some: "bar"})).toBe(true); }); - it("uses custom equality testers if passed in and actual is an Array", function() { - var customTester = function(a, b) {return true;}; + it("uses custom equality testers if passed to contains and actual is an Array", function() { + // TODO: remove this in the next major release. + var customTester = function(a, b) {return true;}, + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.contains([1, 2], 3, [customTester])).toBe(true); + expect(matchersUtil.contains([1, 2], 3, [customTester])).toBe(true); + }); + + it("uses custom equality testers if passed to the constructor and actual is an Array", function() { + var customTester = function(a, b) {return true;}, + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [customTester]}); + + expect(matchersUtil.contains([1, 2], 3)).toBe(true); }); it("fails when actual is undefined", function() { - expect(jasmineUnderTest.matchersUtil.contains(undefined, 'A')).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.contains(undefined, 'A')).toBe(false); }); it("fails when actual is null", function() { - expect(jasmineUnderTest.matchersUtil.contains(null, 'A')).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.contains(null, 'A')).toBe(false); }); it("passes with array-like objects", function() { - var capturedArgs = null; + var capturedArgs = null, + matchersUtil = new jasmineUnderTest.MatchersUtil(); + function testFunction(){ capturedArgs = arguments; } + testFunction('foo', 'bar'); - expect(jasmineUnderTest.matchersUtil.contains(capturedArgs, 'bar')).toBe(true); + expect(matchersUtil.contains(capturedArgs, 'bar')).toBe(true); }); it("passes for set members", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var setItem = {'foo': 'bar'}; var set = new Set(); set.add(setItem); - expect(jasmineUnderTest.matchersUtil.contains(set, setItem)).toBe(true); + expect(matchersUtil.contains(set, setItem)).toBe(true); }); // documenting current behavior it("fails (!) for objects that equal to a set member", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var set = new Set(); set.add({'foo': 'bar'}); - expect(jasmineUnderTest.matchersUtil.contains(set, {'foo': 'bar'})).toBe(false); + expect(matchersUtil.contains(set, {'foo': 'bar'})).toBe(false); }); }); @@ -666,7 +838,8 @@ describe("matchersUtil", function() { it("builds an English sentence for a failure case", function() { var actual = "foo", name = "toBar", - message = jasmineUnderTest.matchersUtil.buildFailureMessage(name, false, actual); + matchersUtil = new jasmineUnderTest.MatchersUtil(), + message = matchersUtil.buildFailureMessage(name, false, actual); expect(message).toEqual("Expected 'foo' to bar."); }); @@ -675,7 +848,8 @@ describe("matchersUtil", function() { var actual = "foo", name = "toBar", isNot = true, - message = message = jasmineUnderTest.matchersUtil.buildFailureMessage(name, isNot, actual); + matchersUtil = new jasmineUnderTest.MatchersUtil(), + message = message = matchersUtil.buildFailureMessage(name, isNot, actual); expect(message).toEqual("Expected 'foo' not to bar."); }); @@ -683,7 +857,8 @@ describe("matchersUtil", function() { it("builds an English sentence for an arbitrary array of expected arguments", function() { var actual = "foo", name = "toBar", - message = jasmineUnderTest.matchersUtil.buildFailureMessage(name, false, actual, "quux", "corge"); + matchersUtil = new jasmineUnderTest.MatchersUtil(), + message = matchersUtil.buildFailureMessage(name, false, actual, "quux", "corge"); expect(message).toEqual("Expected 'foo' to bar 'quux', 'corge'."); }); diff --git a/spec/core/matchers/toBeSpec.js b/spec/core/matchers/toBeSpec.js index e065af57..8e27b992 100644 --- a/spec/core/matchers/toBeSpec.js +++ b/spec/core/matchers/toBeSpec.js @@ -1,6 +1,7 @@ describe("toBe", function() { it("passes with no message when actual === expected", function() { - var matcher = jasmineUnderTest.matchers.toBe(jasmineUnderTest.matchersUtil), + var util = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(util), result; result = matcher.compare(1, 1); @@ -8,7 +9,8 @@ describe("toBe", function() { }); it("passes with a custom message when expected is an array", function() { - var matcher = jasmineUnderTest.matchers.toBe(jasmineUnderTest.matchersUtil), + var util = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(util), result, array = [1]; @@ -18,7 +20,8 @@ describe("toBe", function() { }); it("passes with a custom message when expected is an object", function() { - var matcher = jasmineUnderTest.matchers.toBe(jasmineUnderTest.matchersUtil), + var util = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(util), result, obj = {foo: "bar"}; @@ -28,7 +31,8 @@ describe("toBe", function() { }); it("fails with no message when actual !== expected", function() { - var matcher = jasmineUnderTest.matchers.toBe(jasmineUnderTest.matchersUtil), + var util = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(util), result; result = matcher.compare(1, 2); @@ -37,7 +41,8 @@ describe("toBe", function() { }); it("fails with a custom message when expected is an array", function() { - var matcher = jasmineUnderTest.matchers.toBe(jasmineUnderTest.matchersUtil), + var util = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(util), result; result = matcher.compare([1], [1]); @@ -46,7 +51,8 @@ describe("toBe", function() { }); it("fails with a custom message when expected is an object", function() { - var matcher = jasmineUnderTest.matchers.toBe(jasmineUnderTest.matchersUtil), + var util = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(util), result; result = matcher.compare({foo: "bar"}, {foo: "bar"}); diff --git a/spec/core/matchers/toContainSpec.js b/spec/core/matchers/toContainSpec.js index a56d05e4..effe5862 100644 --- a/spec/core/matchers/toContainSpec.js +++ b/spec/core/matchers/toContainSpec.js @@ -7,20 +7,19 @@ describe("toContain", function() { result; result = matcher.compare("ABC", "B"); - expect(util.contains).toHaveBeenCalledWith("ABC", "B", []); + expect(util.contains).toHaveBeenCalledWith("ABC", "B"); expect(result.pass).toBe(true); }); - it("delegates to jasmineUnderTest.matchersUtil.contains, passing in equality testers if present", function() { - var util = { - contains: jasmine.createSpy('delegated-contains').and.returnValue(true) + it("works with custom equality testers", function() { + var tester = function (a, b) { + return a.toString() === b.toString(); }, - customEqualityTesters = ['a', 'b'], - matcher = jasmineUnderTest.matchers.toContain(util, customEqualityTesters), + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}), + matcher = jasmineUnderTest.matchers.toContain(matchersUtil), result; - result = matcher.compare("ABC", "B"); - expect(util.contains).toHaveBeenCalledWith("ABC", "B", ['a', 'b']); + result = matcher.compare(['1', '2'], 2); expect(result.pass).toBe(true); }); }); diff --git a/spec/core/matchers/toEqualSpec.js b/spec/core/matchers/toEqualSpec.js index 345eb724..2bd2f077 100644 --- a/spec/core/matchers/toEqualSpec.js +++ b/spec/core/matchers/toEqualSpec.js @@ -2,7 +2,7 @@ describe("toEqual", function() { "use strict"; function compareEquals(actual, expected) { - var util = jasmineUnderTest.matchersUtil, + var util = new jasmineUnderTest.MatchersUtil(), matcher = jasmineUnderTest.matchers.toEqual(util); var result = matcher.compare(actual, expected); @@ -16,32 +16,27 @@ describe("toEqual", function() { buildFailureMessage: function() { return 'does not matter' }, - DiffBuilder: jasmineUnderTest.matchersUtil.DiffBuilder + DiffBuilder: new jasmineUnderTest.DiffBuilder() }, matcher = jasmineUnderTest.matchers.toEqual(util), result; result = matcher.compare(1, 1); - expect(util.equals).toHaveBeenCalledWith(1, 1, [], jasmine.anything()); + expect(util.equals).toHaveBeenCalledWith(1, 1, jasmine.anything()); expect(result.pass).toBe(true); }); - it("delegates custom equality testers, if present", function() { - var util = { - equals: jasmine.createSpy('delegated-equals').and.returnValue(true), - buildFailureMessage: function() { - return 'does not matter' - }, - DiffBuilder: jasmineUnderTest.matchersUtil.DiffBuilder + it("works with custom equality testers", function() { + var tester = function (a, b) { + return a.toString() === b.toString(); }, - customEqualityTesters = ['a', 'b'], - matcher = jasmineUnderTest.matchers.toEqual(util, customEqualityTesters), + util = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}), + matcher = jasmineUnderTest.matchers.toEqual(util), result; - result = matcher.compare(1, 1); + result = matcher.compare(1, '1'); - expect(util.equals).toHaveBeenCalledWith(1, 1, ['a', 'b'], jasmine.anything()); expect(result.pass).toBe(true); }); diff --git a/spec/core/matchers/toHaveBeenCalledWithSpec.js b/spec/core/matchers/toHaveBeenCalledWithSpec.js index 4ac7e074..a57bc4a4 100644 --- a/spec/core/matchers/toHaveBeenCalledWithSpec.js +++ b/spec/core/matchers/toHaveBeenCalledWithSpec.js @@ -15,18 +15,16 @@ describe("toHaveBeenCalledWith", function() { expect(result.message()).toEqual("Expected spy called-spy not to have been called with:\n [ 'a', 'b' ]\nbut it was."); }); - it("passes through the custom equality testers", function() { - var util = { - contains: jasmine.createSpy('delegated-contains').and.returnValue(true) - }, - customEqualityTesters = [function() { return true; }], - matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util, customEqualityTesters), - calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'); + it("supports custom equality testers", function() { + var customEqualityTesters = [function() { return true; }], + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: customEqualityTesters}), + matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(matchersUtil), + calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'), + result; calledSpy('a', 'b'); - matcher.compare(calledSpy, 'a', 'b'); - - expect(util.contains).toHaveBeenCalledWith([['a', 'b']], ['a', 'b'], customEqualityTesters); + result = matcher.compare(calledSpy, 'a', 'b'); + expect(result.pass).toBe(true); }); it("fails when the actual was not called", function() { @@ -43,7 +41,7 @@ describe("toHaveBeenCalledWith", function() { }); it("fails when the actual was called with different parameters", function() { - var util = jasmineUnderTest.matchersUtil, + var util = new jasmineUnderTest.MatchersUtil(), matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), calledSpy = new jasmineUnderTest.Env().createSpy('called spy'), result; diff --git a/spec/helpers/integrationMatchers.js b/spec/helpers/integrationMatchers.js index 694ccd6e..f473385c 100644 --- a/spec/helpers/integrationMatchers.js +++ b/spec/helpers/integrationMatchers.js @@ -1,10 +1,7 @@ (function(env) { env.registerIntegrationMatchers = function() { jasmine.addMatchers({ - toHaveFailedExpectationsForRunnable: function( - util, - customeEqualityTesters - ) { + toHaveFailedExpectationsForRunnable: function(util) { return { compare: function(actual, fullName, expectedFailures) { var foundRunnable = false, diff --git a/spec/npmPackage/npmPackageSpec.js b/spec/npmPackage/npmPackageSpec.js index 5248d88c..908b1c06 100644 --- a/spec/npmPackage/npmPackageSpec.js +++ b/spec/npmPackage/npmPackageSpec.js @@ -23,7 +23,7 @@ describe('npm package', function() { beforeEach(function() { jasmine.addMatchers({ - toExistInPath: function(util, customEquality) { + toExistInPath: function(util) { return { compare: function(actual, expected) { var fullPath = path.resolve(expected, actual); diff --git a/src/core/Env.js b/src/core/Env.js index 7d6a1210..e3c5f5c6 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -276,6 +276,7 @@ getJasmineRequireObj().Env = function(j$) { } var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { customMatchers[matcherName] = matchersToAdd[matcherName]; } @@ -289,6 +290,7 @@ getJasmineRequireObj().Env = function(j$) { } var customAsyncMatchers = runnableResources[currentRunnable().id].customAsyncMatchers; + for (var matcherName in matchersToAdd) { customAsyncMatchers[matcherName] = matchersToAdd[matcherName]; } @@ -308,9 +310,12 @@ getJasmineRequireObj().Env = function(j$) { }; var expectationFactory = function(actual, spec) { + var customEqualityTesters = + runnableResources[spec.id].customEqualityTesters; + return j$.Expectation.factory({ - util: j$.matchersUtil, - customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + util: new j$.MatchersUtil({ customTesters: customEqualityTesters }), + customEqualityTesters: customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, actual: actual, addExpectationResult: addExpectationResult @@ -322,8 +327,11 @@ getJasmineRequireObj().Env = function(j$) { }; var asyncExpectationFactory = function(actual, spec) { + var customEqualityTesters = + runnableResources[spec.id].customEqualityTesters; + return j$.Expectation.asyncFactory({ - util: j$.matchersUtil, + util: new j$.MatchersUtil({ customTesters: customEqualityTesters }), customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customAsyncMatchers: runnableResources[spec.id].customAsyncMatchers, actual: actual, diff --git a/src/core/Spy.js b/src/core/Spy.js index ffbb0103..3d4aca21 100644 --- a/src/core/Spy.js +++ b/src/core/Spy.js @@ -7,6 +7,8 @@ getJasmineRequireObj().Spy = function(j$) { }; })(); + var matchersUtil = new j$.MatchersUtil(); + /** * _Note:_ Do not construct this directly, use {@link spyOn}, {@link spyOnProperty}, {@link jasmine.createSpy}, or {@link jasmine.createSpyObj} * @constructor @@ -202,7 +204,7 @@ getJasmineRequireObj().Spy = function(j$) { var i; for (i = 0; i < this.strategies.length; i++) { - if (j$.matchersUtil.equals(args, this.strategies[i].args)) { + if (matchersUtil.equals(args, this.strategies[i].args)) { return this.strategies[i].strategy; } } diff --git a/src/core/asymmetricEqualityTesterArgCompatShim.js b/src/core/asymmetricEqualityTesterArgCompatShim.js new file mode 100644 index 00000000..faa4bf79 --- /dev/null +++ b/src/core/asymmetricEqualityTesterArgCompatShim.js @@ -0,0 +1,105 @@ +getJasmineRequireObj().asymmetricEqualityTesterArgCompatShim = function(j$) { + /* + Older versions of Jasmine passed an array of custom equality testers as the + second argument to each asymmetric equality tester's `asymmetricMatch` + method. Newer versions will pass a `MatchersUtil` instance. The + asymmetricEqualityTesterArgCompatShim allows for a graceful migration from + the old interface to the new by "being" both an array of custom equality + testers and a `MatchersUtil` at the same time. + + This code should be removed in the next major release. + */ + + var likelyArrayProps = [ + 'concat', + 'constructor', + 'copyWithin', + 'entries', + 'every', + 'fill', + 'filter', + 'find', + 'findIndex', + 'flat', + 'flatMap', + 'forEach', + 'includes', + 'indexOf', + 'join', + 'keys', + 'lastIndexOf', + 'length', + 'map', + 'pop', + 'push', + 'reduce', + 'reduceRight', + 'reverse', + 'shift', + 'slice', + 'some', + 'sort', + 'splice', + 'toLocaleString', + 'toSource', + 'toString', + 'unshift', + 'values' + ]; + + function asymmetricEqualityTesterArgCompatShim( + matchersUtil, + customEqualityTesters + ) { + var self = Object.create(matchersUtil), + props, + i, + k; + + copy(self, customEqualityTesters, 'length'); + + for (i = 0; i < customEqualityTesters.length; i++) { + copy(self, customEqualityTesters, i); + } + + var props = arrayProps(); + + for (i = 0; i < props.length; i++) { + k = props[i]; + if (k !== 'length') { + copy(self, Array.prototype, k); + } + } + + return self; + } + + function copy(dest, src, propName) { + Object.defineProperty(dest, propName, { + get: function() { + return src[propName]; + } + }); + } + + function arrayProps() { + var props, a, k; + + if (!Object.getOwnPropertyDescriptors) { + return likelyArrayProps.filter(function(k) { + return Array.prototype.hasOwnProperty(k); + }); + } + + props = Object.getOwnPropertyDescriptors(Array.prototype); + a = []; + + for (k in props) { + a.push(k); + } + + return a; + } + + return asymmetricEqualityTesterArgCompatShim; +}; diff --git a/src/core/asymmetric_equality/ArrayContaining.js b/src/core/asymmetric_equality/ArrayContaining.js index 92a85ed0..95c9f3f1 100644 --- a/src/core/asymmetric_equality/ArrayContaining.js +++ b/src/core/asymmetric_equality/ArrayContaining.js @@ -3,7 +3,7 @@ getJasmineRequireObj().ArrayContaining = function(j$) { this.sample = sample; } - ArrayContaining.prototype.asymmetricMatch = function(other, customTesters) { + ArrayContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isArray_(this.sample)) { throw new Error('You must provide an array to arrayContaining, not ' + j$.pp(this.sample) + '.'); } @@ -17,7 +17,7 @@ getJasmineRequireObj().ArrayContaining = function(j$) { for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; - if (!j$.matchersUtil.contains(other, item, customTesters)) { + if (!matchersUtil.contains(other, item)) { return false; } } diff --git a/src/core/asymmetric_equality/ArrayWithExactContents.js b/src/core/asymmetric_equality/ArrayWithExactContents.js index 37b8ee95..9bdba8d4 100644 --- a/src/core/asymmetric_equality/ArrayWithExactContents.js +++ b/src/core/asymmetric_equality/ArrayWithExactContents.js @@ -4,7 +4,7 @@ getJasmineRequireObj().ArrayWithExactContents = function(j$) { this.sample = sample; } - ArrayWithExactContents.prototype.asymmetricMatch = function(other, customTesters) { + ArrayWithExactContents.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isArray_(this.sample)) { throw new Error('You must provide an array to arrayWithExactContents, not ' + j$.pp(this.sample) + '.'); } @@ -15,7 +15,7 @@ getJasmineRequireObj().ArrayWithExactContents = function(j$) { for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; - if (!j$.matchersUtil.contains(other, item, customTesters)) { + if (!matchersUtil.contains(other, item)) { return false; } } diff --git a/src/core/asymmetric_equality/MapContaining.js b/src/core/asymmetric_equality/MapContaining.js index 061c2666..7c8e3c5b 100644 --- a/src/core/asymmetric_equality/MapContaining.js +++ b/src/core/asymmetric_equality/MapContaining.js @@ -7,7 +7,7 @@ getJasmineRequireObj().MapContaining = function(j$) { this.sample = sample; } - MapContaining.prototype.asymmetricMatch = function(other, customTesters) { + MapContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isMap(other)) return false; var hasAllMatches = true; @@ -17,8 +17,8 @@ getJasmineRequireObj().MapContaining = function(j$) { var hasMatch = false; j$.util.forEachBreakable(other, function(oBreakLoop, oValue, oKey) { if ( - j$.matchersUtil.equals(oKey, key, customTesters) - && j$.matchersUtil.equals(oValue, value, customTesters) + matchersUtil.equals(oKey, key) + && matchersUtil.equals(oValue, value) ) { hasMatch = true; oBreakLoop(); diff --git a/src/core/asymmetric_equality/ObjectContaining.js b/src/core/asymmetric_equality/ObjectContaining.js index 62e9bf17..93069b25 100644 --- a/src/core/asymmetric_equality/ObjectContaining.js +++ b/src/core/asymmetric_equality/ObjectContaining.js @@ -28,12 +28,12 @@ getJasmineRequireObj().ObjectContaining = function(j$) { return hasProperty(getPrototype(obj), property); } - ObjectContaining.prototype.asymmetricMatch = function(other, customTesters) { + ObjectContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } for (var property in this.sample) { if (!hasProperty(other, property) || - !j$.matchersUtil.equals(this.sample[property], other[property], customTesters)) { + !matchersUtil.equals(this.sample[property], other[property])) { return false; } } diff --git a/src/core/asymmetric_equality/SetContaining.js b/src/core/asymmetric_equality/SetContaining.js index d9dd8fd2..00627935 100644 --- a/src/core/asymmetric_equality/SetContaining.js +++ b/src/core/asymmetric_equality/SetContaining.js @@ -7,17 +7,17 @@ getJasmineRequireObj().SetContaining = function(j$) { this.sample = sample; } - SetContaining.prototype.asymmetricMatch = function(other, customTesters) { + SetContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isSet(other)) return false; var hasAllMatches = true; j$.util.forEachBreakable(this.sample, function(breakLoop, item) { // for each item in `sample` there should be at least one matching item in `other` - // (not using `j$.matchersUtil.contains` because it compares set members by reference, + // (not using `matchersUtil.contains` because it compares set members by reference, // not by deep value equality) var hasMatch = false; j$.util.forEachBreakable(other, function(oBreakLoop, oItem) { - if (j$.matchersUtil.equals(oItem, item, customTesters)) { + if (matchersUtil.equals(oItem, item)) { hasMatch = true; oBreakLoop(); } diff --git a/src/core/matchers/async/toBeRejectedWith.js b/src/core/matchers/async/toBeRejectedWith.js index 031230a1..621daf0b 100644 --- a/src/core/matchers/async/toBeRejectedWith.js +++ b/src/core/matchers/async/toBeRejectedWith.js @@ -11,7 +11,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { * @example * return expectAsync(aPromise).toBeRejectedWith({prop: 'value'}); */ - return function toBeRejectedWith(util, customEqualityTesters) { + return function toBeRejectedWith(util) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { @@ -32,7 +32,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { }; }, function(actualValue) { - if (util.equals(actualValue, expectedValue, customEqualityTesters)) { + if (util.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' diff --git a/src/core/matchers/async/toBeResolvedTo.js b/src/core/matchers/async/toBeResolvedTo.js index 9d5aa370..a0295c8a 100644 --- a/src/core/matchers/async/toBeResolvedTo.js +++ b/src/core/matchers/async/toBeResolvedTo.js @@ -11,7 +11,7 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { * @example * return expectAsync(aPromise).toBeResolvedTo({prop: 'value'}); */ - return function toBeResolvedTo(util, customEqualityTesters) { + return function toBeResolvedTo(util) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { @@ -26,7 +26,7 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { return actualPromise.then( function(actualValue) { - if (util.equals(actualValue, expectedValue, customEqualityTesters)) { + if (util.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' diff --git a/src/core/matchers/matchersUtil.js b/src/core/matchers/matchersUtil.js index cfc0c749..1aa496f4 100644 --- a/src/core/matchers/matchersUtil.js +++ b/src/core/matchers/matchersUtil.js @@ -1,63 +1,67 @@ -getJasmineRequireObj().matchersUtil = function(j$) { +getJasmineRequireObj().MatchersUtil = function(j$) { // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? - return { - equals: equals, + function MatchersUtil(options) { + options = options || {}; + this.customTesters_ = options.customTesters || []; - contains: function(haystack, needle, customTesters) { - customTesters = customTesters || []; - - if (j$.isSet(haystack)) { - return haystack.has(needle); - } - - if ((Object.prototype.toString.apply(haystack) === '[object Array]') || - (!!haystack && !haystack.indexOf)) - { - for (var i = 0; i < haystack.length; i++) { - if (equals(haystack[i], needle, customTesters)) { - return true; - } - } - return false; - } - - return !!haystack && haystack.indexOf(needle) >= 0; - }, - - buildFailureMessage: function() { - var args = Array.prototype.slice.call(arguments, 0), - matcherName = args[0], - isNot = args[1], - actual = args[2], - expected = args.slice(3), - englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); - - var message = 'Expected ' + - j$.pp(actual) + - (isNot ? ' not ' : ' ') + - englishyPredicate; - - if (expected.length > 0) { - for (var i = 0; i < expected.length; i++) { - if (i > 0) { - message += ','; - } - message += ' ' + j$.pp(expected[i]); - } - } - - return message + '.'; + if (!j$.isArray_(this.customTesters_)) { + throw new Error("MatchersUtil requires custom equality testers"); } + } + + MatchersUtil.prototype.contains = function(haystack, needle, customTesters) { + if (j$.isSet(haystack)) { + return haystack.has(needle); + } + + if ((Object.prototype.toString.apply(haystack) === '[object Array]') || + (!!haystack && !haystack.indexOf)) + { + for (var i = 0; i < haystack.length; i++) { + if (this.equals(haystack[i], needle, customTesters)) { + return true; + } + } + return false; + } + + return !!haystack && haystack.indexOf(needle) >= 0; + }; + + MatchersUtil.prototype.buildFailureMessage = function() { + var args = Array.prototype.slice.call(arguments, 0), + matcherName = args[0], + isNot = args[1], + actual = args[2], + expected = args.slice(3), + englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + + var message = 'Expected ' + + j$.pp(actual) + + (isNot ? ' not ' : ' ') + + englishyPredicate; + + if (expected.length > 0) { + for (var i = 0; i < expected.length; i++) { + if (i > 0) { + message += ','; + } + message += ' ' + j$.pp(expected[i]); + } + } + + return message + '.'; }; function isAsymmetric(obj) { return obj && j$.isA_('Function', obj.asymmetricMatch); } - function asymmetricMatch(a, b, customTesters, diffBuilder) { + MatchersUtil.prototype.asymmetricMatch_ = function(a, b, customTesters, diffBuilder) { var asymmetricA = isAsymmetric(a), asymmetricB = isAsymmetric(b), + shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters), result; if (asymmetricA && asymmetricB) { @@ -65,7 +69,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { } if (asymmetricA) { - result = a.asymmetricMatch(b, customTesters); + result = a.asymmetricMatch(b, shim); if (!result) { diffBuilder.record(a, b); } @@ -73,27 +77,36 @@ getJasmineRequireObj().matchersUtil = function(j$) { } if (asymmetricB) { - result = b.asymmetricMatch(a, customTesters); + result = b.asymmetricMatch(a, shim); if (!result) { diffBuilder.record(a, b); } return result; } - } + }; - function equals(a, b, customTesters, diffBuilder) { - customTesters = customTesters || []; + MatchersUtil.prototype.equals = function(a, b, customTestersOrDiffBuilder, diffBuilderOrNothing) { + var customTesters, diffBuilder; + + if (isDiffBuilder(customTestersOrDiffBuilder)) { + diffBuilder = customTestersOrDiffBuilder; + } else { + customTesters = customTestersOrDiffBuilder; + diffBuilder = diffBuilderOrNothing; + } + + customTesters = customTesters || this.customTesters_; diffBuilder = diffBuilder || j$.NullDiffBuilder(); - return eq(a, b, [], [], customTesters, diffBuilder); - } + return this.eq_(a, b, [], [], customTesters, diffBuilder); + }; // Equality function lovingly adapted from isEqual in // [Underscore](http://underscorejs.org) - function eq(a, b, aStack, bStack, customTesters, diffBuilder) { - var result = true, i; + MatchersUtil.prototype.eq_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { + var result = true, self = this, i; - var asymmetricResult = asymmetricMatch(a, b, customTesters, diffBuilder); + var asymmetricResult = this.asymmetricMatch_(a, b, customTesters, diffBuilder); if (!j$.util.isUndefined(asymmetricResult)) { return asymmetricResult; } @@ -230,7 +243,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { diffBuilder.record(a[i], void 0, actualArrayIsLongerFormatter); result = false; } else { - result = eq(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0, aStack, bStack, customTesters, diffBuilder) && result; + result = self.eq_(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0, aStack, bStack, customTesters, diffBuilder) && result; } }); } @@ -271,12 +284,12 @@ getJasmineRequireObj().matchersUtil = function(j$) { // otherwise explicitly look up the mapKey in the other Map since we want keys with unique // obj identity (that are otherwise equal) to not match. if (isAsymmetric(mapKey) || isAsymmetric(cmpKey) && - eq(mapKey, cmpKey, aStack, bStack, customTesters, j$.NullDiffBuilder())) { + this.eq_(mapKey, cmpKey, aStack, bStack, customTesters, j$.NullDiffBuilder())) { mapValueB = b.get(cmpKey); } else { mapValueB = b.get(mapKey); } - result = eq(mapValueA, mapValueB, aStack, bStack, customTesters, j$.NullDiffBuilder()); + result = this.eq_(mapValueA, mapValueB, aStack, bStack, customTesters, j$.NullDiffBuilder()); } } @@ -320,7 +333,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { otherValue = otherValues[l]; prevStackSize = baseStack.length; // compare by value equality - found = eq(baseValue, otherValue, baseStack, otherStack, customTesters, j$.NullDiffBuilder()); + found = this.eq_(baseValue, otherValue, baseStack, otherStack, customTesters, j$.NullDiffBuilder()); if (!found && prevStackSize !== baseStack.length) { baseStack.splice(prevStackSize); otherStack.splice(prevStackSize); @@ -369,7 +382,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { } diffBuilder.withPath(key, function() { - if(!eq(a[key], b[key], aStack, bStack, customTesters, diffBuilder)) { + if(!self.eq_(a[key], b[key], aStack, bStack, customTesters, diffBuilder)) { result = false; } }); @@ -384,7 +397,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { bStack.pop(); return result; - } + }; function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : @@ -467,4 +480,10 @@ getJasmineRequireObj().matchersUtil = function(j$) { } return formatted; } + + function isDiffBuilder(obj) { + return obj && typeof obj.record === 'function'; + } + + return MatchersUtil; }; diff --git a/src/core/matchers/toBeInstanceOf.js b/src/core/matchers/toBeInstanceOf.js index 66b658c1..cc0a0679 100644 --- a/src/core/matchers/toBeInstanceOf.js +++ b/src/core/matchers/toBeInstanceOf.js @@ -12,7 +12,7 @@ getJasmineRequireObj().toBeInstanceOf = function(j$) { * expect(3).toBeInstanceOf(Number); * expect(new Error()).toBeInstanceOf(Error); */ - function toBeInstanceOf(util, customEqualityTesters) { + function toBeInstanceOf() { return { compare: function(actual, expected) { var actualType = actual && actual.constructor ? j$.fnNameFor(actual.constructor) : j$.pp(actual), diff --git a/src/core/matchers/toContain.js b/src/core/matchers/toContain.js index 440bb4c4..cf511604 100644 --- a/src/core/matchers/toContain.js +++ b/src/core/matchers/toContain.js @@ -9,14 +9,12 @@ getJasmineRequireObj().toContain = function() { * expect(array).toContain(anElement); * expect(string).toContain(substring); */ - function toContain(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - + function toContain(util) { return { compare: function(actual, expected) { return { - pass: util.contains(actual, expected, customEqualityTesters) + pass: util.contains(actual, expected) }; } }; diff --git a/src/core/matchers/toEqual.js b/src/core/matchers/toEqual.js index 8697ed67..d78bde4c 100644 --- a/src/core/matchers/toEqual.js +++ b/src/core/matchers/toEqual.js @@ -8,9 +8,7 @@ getJasmineRequireObj().toEqual = function(j$) { * @example * expect(bigObject).toEqual({"foo": ['bar', 'baz']}); */ - function toEqual(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - + function toEqual(util) { return { compare: function(actual, expected) { var result = { @@ -18,7 +16,7 @@ getJasmineRequireObj().toEqual = function(j$) { }, diffBuilder = j$.DiffBuilder(); - result.pass = util.equals(actual, expected, customEqualityTesters, diffBuilder); + result.pass = util.equals(actual, expected, diffBuilder); // TODO: only set error message if test fails result.message = diffBuilder.getMessage(); diff --git a/src/core/matchers/toHaveBeenCalledWith.js b/src/core/matchers/toHaveBeenCalledWith.js index 277b33f3..9edde713 100644 --- a/src/core/matchers/toHaveBeenCalledWith.js +++ b/src/core/matchers/toHaveBeenCalledWith.js @@ -11,7 +11,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { * @example * expect(mySpy).toHaveBeenCalledWith('foo', 'bar', 2); */ - function toHaveBeenCalledWith(util, customEqualityTesters) { + function toHaveBeenCalledWith(util) { return { compare: function() { var args = Array.prototype.slice.call(arguments, 0), @@ -32,7 +32,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { return result; } - if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { + if (util.contains(actual.calls.allArgs(), expectedArgs)) { result.pass = true; result.message = function() { return 'Expected spy ' + actual.and.identity + ' not to have been called with:\n' + @@ -47,7 +47,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { var diffs = actual.calls.allArgs().map(function(argsForCall, callIx) { var diffBuilder = new j$.DiffBuilder(); - util.equals(argsForCall, expectedArgs, customEqualityTesters, diffBuilder); + util.equals(argsForCall, expectedArgs, diffBuilder); return 'Call ' + callIx + ':\n' + diffBuilder.getMessage().replace(/^/mg, ' '); }); diff --git a/src/core/matchers/toHaveClass.js b/src/core/matchers/toHaveClass.js index a2fd7507..32dc2ccf 100644 --- a/src/core/matchers/toHaveClass.js +++ b/src/core/matchers/toHaveClass.js @@ -10,7 +10,7 @@ getJasmineRequireObj().toHaveClass = function(j$) { * el.className = 'foo bar baz'; * expect(el).toHaveClass('bar'); */ - function toHaveClass(util, customEqualityTesters) { + function toHaveClass() { return { compare: function(actual, expected) { if (!isElement(actual)) { diff --git a/src/core/requireCore.js b/src/core/requireCore.js index a2c5f177..0d5b76ed 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -51,7 +51,12 @@ var getJasmineRequireObj = (function(jasmineGlobal) { j$.buildExpectationResult = jRequire.buildExpectationResult(); j$.noopTimer = jRequire.noopTimer(); j$.JsApiReporter = jRequire.JsApiReporter(j$); - j$.matchersUtil = jRequire.matchersUtil(j$); + j$.asymmetricEqualityTesterArgCompatShim = jRequire.asymmetricEqualityTesterArgCompatShim( + j$ + ); + j$.MatchersUtil = jRequire.MatchersUtil(j$); + j$.matchersUtil = new j$.MatchersUtil({ customTesters: [] }); + j$.ObjectContaining = jRequire.ObjectContaining(j$); j$.ArrayContaining = jRequire.ArrayContaining(j$); j$.ArrayWithExactContents = jRequire.ArrayWithExactContents(j$); From 1f23f1e4d2f4f6b45b374aa7311a8673f78e9ccd Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 8 Oct 2019 22:57:07 -0700 Subject: [PATCH 2/8] Inject a per-runable pretty printer into MatchersUtil This will allow us to add support for custom object formatters, which will be a per-runable resource like custom matchers, by injecting them into the pretty-printer. --- lib/jasmine-core/jasmine.js | 240 ++++++++++-------- spec/core/AsyncExpectationSpec.js | 15 +- spec/core/EnvSpec.js | 10 +- spec/core/ExpectationSpec.js | 3 +- spec/core/PrettyPrintSpec.js | 186 +++++++------- .../async/toBeRejectedWithErrorSpec.js | 24 +- .../matchers/async/toBeRejectedWithSpec.js | 6 +- .../core/matchers/async/toBeResolvedToSpec.js | 13 +- spec/core/matchers/matchersUtilSpec.js | 41 ++- spec/core/matchers/toBeInstanceOfSpec.js | 12 +- spec/core/matchers/toBeNaNSpec.js | 4 +- .../core/matchers/toBeNegativeInfinitySpec.js | 4 +- .../core/matchers/toBePositiveInfinitySpec.js | 4 +- spec/core/matchers/toBeSpec.js | 8 +- spec/core/matchers/toEqualSpec.js | 6 +- .../matchers/toHaveBeenCalledBeforeSpec.js | 8 +- spec/core/matchers/toHaveBeenCalledSpec.js | 4 +- .../matchers/toHaveBeenCalledTimesSpec.js | 4 +- .../core/matchers/toHaveBeenCalledWithSpec.js | 15 +- spec/core/matchers/toHaveClassSpec.js | 4 +- spec/core/matchers/toThrowErrorSpec.js | 49 ++-- spec/core/matchers/toThrowMatchingSpec.js | 10 +- spec/core/matchers/toThrowSpec.js | 16 +- spec/html/PrettyPrintHtmlSpec.js | 21 +- src/core/Env.js | 22 +- src/core/Expectation.js | 2 +- src/core/Expector.js | 2 +- src/core/PrettyPrinter.js | 43 ++-- src/core/Spy.js | 5 +- src/core/matchers/async/toBeRejectedWith.js | 8 +- .../matchers/async/toBeRejectedWithError.js | 16 +- src/core/matchers/async/toBeResolvedTo.js | 8 +- src/core/matchers/matchersUtil.js | 38 ++- src/core/matchers/toBeInstanceOf.js | 6 +- src/core/matchers/toBeNaN.js | 4 +- src/core/matchers/toBeNegativeInfinity.js | 4 +- src/core/matchers/toBePositiveInfinity.js | 4 +- src/core/matchers/toHaveBeenCalled.js | 4 +- src/core/matchers/toHaveBeenCalledBefore.js | 6 +- src/core/matchers/toHaveBeenCalledTimes.js | 4 +- src/core/matchers/toHaveBeenCalledWith.js | 16 +- src/core/matchers/toHaveClass.js | 4 +- src/core/matchers/toThrow.js | 10 +- src/core/matchers/toThrowError.js | 10 +- src/core/matchers/toThrowMatching.js | 16 +- src/core/requireCore.js | 8 +- 46 files changed, 546 insertions(+), 401 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 555cb046..7c67caa3 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -76,15 +76,19 @@ var getJasmineRequireObj = (function(jasmineGlobal) { j$.asymmetricEqualityTesterArgCompatShim = jRequire.asymmetricEqualityTesterArgCompatShim( j$ ); + j$.makePrettyPrinter = jRequire.makePrettyPrinter(j$); + j$.pp = j$.makePrettyPrinter(); j$.MatchersUtil = jRequire.MatchersUtil(j$); - j$.matchersUtil = new j$.MatchersUtil({ customTesters: [] }); + j$.matchersUtil = new j$.MatchersUtil({ + customTesters: [], + pp: j$.pp + }); j$.ObjectContaining = jRequire.ObjectContaining(j$); j$.ArrayContaining = jRequire.ArrayContaining(j$); j$.ArrayWithExactContents = jRequire.ArrayWithExactContents(j$); j$.MapContaining = jRequire.MapContaining(j$); j$.SetContaining = jRequire.SetContaining(j$); - j$.pp = jRequire.pp(j$); j$.QueueRunner = jRequire.QueueRunner(j$); j$.ReportDispatcher = jRequire.ReportDispatcher(j$); j$.Spec = jRequire.Spec(j$); @@ -1226,12 +1230,25 @@ getJasmineRequireObj().Env = function(j$) { return 'suite' + nextSuiteId++; }; + var makePrettyPrinter = function() { + return j$.makePrettyPrinter(); + }; + + var makeMatchersUtil = function() { + var customEqualityTesters = + runnableResources[currentRunnable().id].customEqualityTesters; + return new j$.MatchersUtil({ + customTesters: customEqualityTesters, + pp: makePrettyPrinter() + }); + }; + var expectationFactory = function(actual, spec) { var customEqualityTesters = runnableResources[spec.id].customEqualityTesters; return j$.Expectation.factory({ - util: new j$.MatchersUtil({ customTesters: customEqualityTesters }), + util: makeMatchersUtil(), customEqualityTesters: customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, actual: actual, @@ -1244,11 +1261,8 @@ getJasmineRequireObj().Env = function(j$) { }; var asyncExpectationFactory = function(actual, spec) { - var customEqualityTesters = - runnableResources[spec.id].customEqualityTesters; - return j$.Expectation.asyncFactory({ - util: new j$.MatchersUtil({ customTesters: customEqualityTesters }), + util: makeMatchersUtil(), customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customAsyncMatchers: runnableResources[spec.id].customAsyncMatchers, actual: actual, @@ -2060,7 +2074,7 @@ getJasmineRequireObj().Env = function(j$) { message += error; } else { // pretty print all kind of objects. This includes arrays. - message += j$.pp(error); + message += makePrettyPrinter()(error); } } @@ -3543,7 +3557,7 @@ getJasmineRequireObj().Expectation = function(j$) { args = args.slice(); args.unshift(true); args.unshift(matcherName); - return util.buildFailureMessage.apply(null, args); + return util.buildFailureMessage.apply(util, args); } function negate(result) { @@ -3761,7 +3775,7 @@ getJasmineRequireObj().Expector = function(j$) { var args = self.args.slice(); args.unshift(false); args.unshift(self.matcherName); - return self.util.buildFailureMessage.apply(null, args); + return self.util.buildFailureMessage.apply(self.util, args); } else if (j$.isFunction_(result.message)) { return result.message(); } else { @@ -3941,7 +3955,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { * @example * return expectAsync(aPromise).toBeRejectedWith({prop: 'value'}); */ - return function toBeRejectedWith(util) { + return function toBeRejectedWith(matchersUtil) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { @@ -3951,7 +3965,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { function prefix(passed) { return 'Expected a promise ' + (passed ? 'not ' : '') + - 'to be rejected with ' + j$.pp(expectedValue); + 'to be rejected with ' + matchersUtil.pp(expectedValue); } return actualPromise.then( @@ -3962,7 +3976,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { }; }, function(actualValue) { - if (util.equals(actualValue, expectedValue)) { + if (matchersUtil.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' @@ -3970,7 +3984,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { } else { return { pass: false, - message: prefix(false) + ' but it was rejected with ' + j$.pp(actualValue) + '.' + message: prefix(false) + ' but it was rejected with ' + matchersUtil.pp(actualValue) + '.' }; } } @@ -3996,14 +4010,14 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) { * await expectAsync(aPromise).toBeRejectedWithError('Error message'); * return expectAsync(aPromise).toBeRejectedWithError(/Error message/); */ - return function toBeRejectedWithError() { + return function toBeRejectedWithError(matchersUtil) { return { compare: function(actualPromise, arg1, arg2) { if (!j$.isPromiseLike(actualPromise)) { throw new Error('Expected toBeRejectedWithError to be called on a promise.'); } - var expected = getExpectedFromArgs(arg1, arg2); + var expected = getExpectedFromArgs(arg1, arg2, matchersUtil); return actualPromise.then( function() { @@ -4012,15 +4026,15 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) { message: 'Expected a promise to be rejected but it was resolved.' }; }, - function(actualValue) { return matchError(actualValue, expected); } + function(actualValue) { return matchError(actualValue, expected, matchersUtil); } ); } }; }; - function matchError(actual, expected) { + function matchError(actual, expected, matchersUtil) { if (!j$.isError_(actual)) { - return fail(expected, 'rejected with ' + j$.pp(actual)); + return fail(expected, 'rejected with ' + matchersUtil.pp(actual)); } if (!(actual instanceof expected.error)) { @@ -4037,7 +4051,7 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) { return pass(expected); } - return fail(expected, 'rejected with ' + j$.pp(actual)); + return fail(expected, 'rejected with ' + matchersUtil.pp(actual)); } function pass(expected) { @@ -4055,7 +4069,7 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) { } - function getExpectedFromArgs(arg1, arg2) { + function getExpectedFromArgs(arg1, arg2, matchersUtil) { var error, message; if (isErrorConstructor(arg1)) { @@ -4069,7 +4083,7 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) { return { error: error, message: message, - printValue: j$.fnNameFor(error) + (typeof message === 'undefined' ? '' : ': ' + j$.pp(message)) + printValue: j$.fnNameFor(error) + (typeof message === 'undefined' ? '' : ': ' + matchersUtil.pp(message)) }; } @@ -4119,7 +4133,7 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { * @example * return expectAsync(aPromise).toBeResolvedTo({prop: 'value'}); */ - return function toBeResolvedTo(util) { + return function toBeResolvedTo(matchersUtil) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { @@ -4129,12 +4143,12 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { function prefix(passed) { return 'Expected a promise ' + (passed ? 'not ' : '') + - 'to be resolved to ' + j$.pp(expectedValue); + 'to be resolved to ' + matchersUtil.pp(expectedValue); } return actualPromise.then( function(actualValue) { - if (util.equals(actualValue, expectedValue)) { + if (matchersUtil.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' @@ -4142,7 +4156,7 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { } else { return { pass: false, - message: prefix(false) + ' but it was resolved to ' + j$.pp(actualValue) + '.' + message: prefix(false) + ' but it was resolved to ' + matchersUtil.pp(actualValue) + '.' }; } }, @@ -4193,16 +4207,13 @@ getJasmineRequireObj().DiffBuilder = function(j$) { }; getJasmineRequireObj().MatchersUtil = function(j$) { - // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? + // TODO: convert all uses of j$.pp to use the injected pp function MatchersUtil(options) { options = options || {}; this.customTesters_ = options.customTesters || []; - - if (!j$.isArray_(this.customTesters_)) { - throw new Error("MatchersUtil requires custom equality testers"); - } - } + this.pp = options.pp || function() {}; + }; MatchersUtil.prototype.contains = function(haystack, needle, customTesters) { if (j$.isSet(haystack)) { @@ -4224,6 +4235,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { }; MatchersUtil.prototype.buildFailureMessage = function() { + var self = this; var args = Array.prototype.slice.call(arguments, 0), matcherName = args[0], isNot = args[1], @@ -4232,7 +4244,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); var message = 'Expected ' + - j$.pp(actual) + + self.pp(actual) + (isNot ? ' not ' : ' ') + englishyPredicate; @@ -4434,7 +4446,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); + diffBuilder.record(a[i], void 0, 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; @@ -4551,7 +4563,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { a instanceof aCtor && b instanceof bCtor && !(aCtor instanceof aCtor && bCtor instanceof bCtor)) { - diffBuilder.record(a, b, constructorsAreDifferentFormatter); + diffBuilder.record(a, b, constructorsAreDifferentFormatter.bind(null, this.pp)); return false; } } @@ -4562,7 +4574,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); + diffBuilder.record(a, b, objectKeysAreDifferentFormatter.bind(null, this.pp)); return false; } @@ -4570,7 +4582,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { key = aKeys[i]; // Deep compare each member if (!j$.util.has(b, key)) { - diffBuilder.record(a, b, objectKeysAreDifferentFormatter); + diffBuilder.record(a, b, objectKeysAreDifferentFormatter.bind(null, this.pp)); result = false; continue; } @@ -4627,11 +4639,11 @@ getJasmineRequireObj().MatchersUtil = function(j$) { return typeof obj === 'function'; } - function objectKeysAreDifferentFormatter(actual, expected, path) { + function objectKeysAreDifferentFormatter(pp, actual, expected, path) { var missingProperties = j$.util.objectDifference(expected, actual), extraProperties = j$.util.objectDifference(actual, expected), - missingPropertiesMessage = formatKeyValuePairs(missingProperties), - extraPropertiesMessage = formatKeyValuePairs(extraProperties), + missingPropertiesMessage = formatKeyValuePairs(pp, missingProperties), + extraPropertiesMessage = formatKeyValuePairs(pp, extraProperties), messages = []; if (!path.depth()) { @@ -4649,7 +4661,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { return messages.join('\n'); } - function constructorsAreDifferentFormatter(actual, expected, path) { + function constructorsAreDifferentFormatter(pp, actual, expected, path) { if (!path.depth()) { path = 'object'; } @@ -4657,20 +4669,20 @@ getJasmineRequireObj().MatchersUtil = function(j$) { return 'Expected ' + path + ' to be a kind of ' + j$.fnNameFor(expected.constructor) + - ', but was ' + j$.pp(actual) + '.'; + ', but was ' + pp(actual) + '.'; } - function actualArrayIsLongerFormatter(actual, expected, path) { + function actualArrayIsLongerFormatter(pp, actual, expected, path) { return 'Unexpected ' + path + (path.depth() ? ' = ' : '') + - j$.pp(actual) + + pp(actual) + ' in array.'; } - function formatKeyValuePairs(obj) { + function formatKeyValuePairs(pp, obj) { var formatted = ''; for (var key in obj) { - formatted += '\n ' + key + ': ' + j$.pp(obj[key]); + formatted += '\n ' + key + ': ' + pp(obj[key]); } return formatted; } @@ -4977,11 +4989,11 @@ getJasmineRequireObj().toBeInstanceOf = function(j$) { * expect(3).toBeInstanceOf(Number); * expect(new Error()).toBeInstanceOf(Error); */ - function toBeInstanceOf() { + function toBeInstanceOf(matchersUtil) { return { compare: function(actual, expected) { - var actualType = actual && actual.constructor ? j$.fnNameFor(actual.constructor) : j$.pp(actual), - expectedType = expected ? j$.fnNameFor(expected) : j$.pp(expected), + var actualType = actual && actual.constructor ? j$.fnNameFor(actual.constructor) : matchersUtil.pp(actual), + expectedType = expected ? j$.fnNameFor(expected) : matchersUtil.pp(expected), expectedMatcher, pass; @@ -5067,7 +5079,7 @@ getJasmineRequireObj().toBeNaN = function(j$) { * @example * expect(thing).toBeNaN(); */ - function toBeNaN() { + function toBeNaN(matchersUtil) { return { compare: function(actual) { var result = { @@ -5077,7 +5089,7 @@ getJasmineRequireObj().toBeNaN = function(j$) { if (result.pass) { result.message = 'Expected actual not to be NaN.'; } else { - result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; + result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be NaN.'; }; } return result; @@ -5097,7 +5109,7 @@ getJasmineRequireObj().toBeNegativeInfinity = function(j$) { * @example * expect(thing).toBeNegativeInfinity(); */ - function toBeNegativeInfinity() { + function toBeNegativeInfinity(matchersUtil) { return { compare: function(actual) { var result = { @@ -5107,7 +5119,7 @@ getJasmineRequireObj().toBeNegativeInfinity = function(j$) { if (result.pass) { result.message = 'Expected actual not to be -Infinity.'; } else { - result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be -Infinity.'; }; + result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be -Infinity.'; }; } return result; @@ -5149,7 +5161,7 @@ getJasmineRequireObj().toBePositiveInfinity = function(j$) { * @example * expect(thing).toBePositiveInfinity(); */ - function toBePositiveInfinity() { + function toBePositiveInfinity(matchersUtil) { return { compare: function(actual) { var result = { @@ -5159,7 +5171,7 @@ getJasmineRequireObj().toBePositiveInfinity = function(j$) { if (result.pass) { result.message = 'Expected actual not to be Infinity.'; } else { - result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be Infinity.'; }; + result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be Infinity.'; }; } return result; @@ -5305,13 +5317,13 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) { * expect(mySpy).toHaveBeenCalled(); * expect(mySpy).not.toHaveBeenCalled(); */ - function toHaveBeenCalled() { + function toHaveBeenCalled(matchersUtil) { return { compare: function(actual) { var result = {}; if (!j$.isSpy(actual)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); + throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(actual) + '.')); } if (arguments.length > 1) { @@ -5345,14 +5357,14 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { * @example * expect(mySpy).toHaveBeenCalledBefore(otherSpy); */ - function toHaveBeenCalledBefore() { + function toHaveBeenCalledBefore(matchersUtil) { return { compare: function(firstSpy, latterSpy) { if (!j$.isSpy(firstSpy)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(firstSpy) + '.')); + throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(firstSpy) + '.')); } if (!j$.isSpy(latterSpy)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(latterSpy) + '.')); + throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(latterSpy) + '.')); } var result = { pass: false }; @@ -5407,11 +5419,11 @@ getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) { * @example * expect(mySpy).toHaveBeenCalledTimes(3); */ - function toHaveBeenCalledTimes() { + function toHaveBeenCalledTimes(matchersUtil) { return { compare: function(actual, expected) { if (!j$.isSpy(actual)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); + throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(actual) + '.')); } var args = Array.prototype.slice.call(arguments, 0), @@ -5449,7 +5461,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { * @example * expect(mySpy).toHaveBeenCalledWith('foo', 'bar', 2); */ - function toHaveBeenCalledWith(util) { + function toHaveBeenCalledWith(matchersUtil) { return { compare: function() { var args = Array.prototype.slice.call(arguments, 0), @@ -5458,40 +5470,40 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { result = { pass: false }; if (!j$.isSpy(actual)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); + throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(actual) + '.')); } if (!actual.calls.any()) { result.message = function() { return 'Expected spy ' + actual.and.identity + ' to have been called with:\n' + - ' ' + j$.pp(expectedArgs) + + ' ' + matchersUtil.pp(expectedArgs) + '\nbut it was never called.'; }; return result; } - if (util.contains(actual.calls.allArgs(), expectedArgs)) { + if (matchersUtil.contains(actual.calls.allArgs(), expectedArgs)) { result.pass = true; result.message = function() { return 'Expected spy ' + actual.and.identity + ' not to have been called with:\n' + - ' ' + j$.pp(expectedArgs) + + ' ' + matchersUtil.pp(expectedArgs) + '\nbut it was.'; }; } else { result.message = function() { var prettyPrintedCalls = actual.calls.allArgs().map(function(argsForCall) { - return ' ' + j$.pp(argsForCall); + return ' ' + matchersUtil.pp(argsForCall); }); var diffs = actual.calls.allArgs().map(function(argsForCall, callIx) { var diffBuilder = new j$.DiffBuilder(); - util.equals(argsForCall, expectedArgs, diffBuilder); + matchersUtil.equals(argsForCall, expectedArgs, diffBuilder); return 'Call ' + callIx + ':\n' + diffBuilder.getMessage().replace(/^/mg, ' '); }); return 'Expected spy ' + actual.and.identity + ' to have been called with:\n' + - ' ' + j$.pp(expectedArgs) + '\n' + '' + + ' ' + matchersUtil.pp(expectedArgs) + '\n' + '' + 'but actual calls were:\n' + prettyPrintedCalls.join(',\n') + '.\n\n' + diffs.join('\n'); @@ -5518,11 +5530,11 @@ getJasmineRequireObj().toHaveClass = function(j$) { * el.className = 'foo bar baz'; * expect(el).toHaveClass('bar'); */ - function toHaveClass() { + function toHaveClass(matchersUtil) { return { compare: function(actual, expected) { if (!isElement(actual)) { - throw new Error(j$.pp(actual) + ' is not a DOM element'); + throw new Error(matchersUtil.pp(actual) + ' is not a DOM element'); } return { @@ -5588,7 +5600,7 @@ getJasmineRequireObj().toThrow = function(j$) { * expect(function() { return 'things'; }).toThrow('foo'); * expect(function() { return 'stuff'; }).toThrow(); */ - function toThrow(util) { + function toThrow(matchersUtil) { return { compare: function(actual, expected) { var result = { pass: false }, @@ -5613,16 +5625,16 @@ getJasmineRequireObj().toThrow = function(j$) { if (arguments.length == 1) { result.pass = true; - result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; + result.message = function() { return 'Expected function not to throw, but it threw ' + matchersUtil.pp(thrown) + '.'; }; return result; } - if (util.equals(thrown, expected)) { + if (matchersUtil.equals(thrown, expected)) { result.pass = true; - result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; + result.message = function() { return 'Expected function not to throw ' + matchersUtil.pp(expected) + '.'; }; } else { - result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; + result.message = function() { return 'Expected function to throw ' + matchersUtil.pp(expected) + ', but it threw ' + matchersUtil.pp(thrown) + '.'; }; } return result; @@ -5651,7 +5663,7 @@ getJasmineRequireObj().toThrowError = function(j$) { * expect(function() { return 'other'; }).toThrowError(/foo/); * expect(function() { return 'other'; }).toThrowError(); */ - function toThrowError () { + function toThrowError(matchersUtil) { return { compare: function(actual) { var errorMatcher = getMatcher.apply(null, arguments), @@ -5669,7 +5681,7 @@ getJasmineRequireObj().toThrowError = function(j$) { } if (!j$.isError_(thrown)) { - return fail(function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }); + return fail(function() { return 'Expected function to throw an Error, but it threw ' + matchersUtil.pp(thrown) + '.'; }); } return errorMatcher.match(thrown); @@ -5732,7 +5744,7 @@ getJasmineRequireObj().toThrowError = function(j$) { thrownMessage = ''; if (expected) { - thrownMessage = ' with message ' + j$.pp(thrown.message); + thrownMessage = ' with message ' + matchersUtil.pp(thrown.message); } return thrownName + thrownMessage; @@ -5742,9 +5754,9 @@ getJasmineRequireObj().toThrowError = function(j$) { if (expected === null) { return ''; } else if (expected instanceof RegExp) { - return ' with a message matching ' + j$.pp(expected); + return ' with a message matching ' + matchersUtil.pp(expected); } else { - return ' with message ' + j$.pp(expected); + return ' with message ' + matchersUtil.pp(expected); } } @@ -5813,7 +5825,7 @@ getJasmineRequireObj().toThrowMatching = function(j$) { * @example * expect(function() { throw new Error('nope'); }).toThrowMatching(function(thrown) { return thrown.message === 'nope'; }); */ - function toThrowMatching() { + function toThrowMatching(matchersUtil) { return { compare: function(actual, predicate) { var thrown; @@ -5843,14 +5855,14 @@ getJasmineRequireObj().toThrowMatching = function(j$) { } } }; - } - function thrownDescription(thrown) { - if (thrown && thrown.constructor) { - return j$.fnNameFor(thrown.constructor) + ' with message ' + - j$.pp(thrown.message); - } else { - return j$.pp(thrown); + function thrownDescription(thrown) { + if (thrown && thrown.constructor) { + return j$.fnNameFor(thrown.constructor) + ' with message ' + + matchersUtil.pp(thrown.message); + } else { + return matchersUtil.pp(thrown); + } } } @@ -5977,8 +5989,8 @@ getJasmineRequireObj().MockDate = function() { return MockDate; }; -getJasmineRequireObj().pp = function(j$) { - function PrettyPrinter() { +getJasmineRequireObj().makePrettyPrinter = function(j$) { + function SinglePrettyPrintRun() { this.ppNestLevel_ = 0; this.seen = []; this.length = 0; @@ -6000,7 +6012,7 @@ getJasmineRequireObj().pp = function(j$) { } } - PrettyPrinter.prototype.format = function(value) { + SinglePrettyPrintRun.prototype.format = function(value) { this.ppNestLevel_++; try { if (j$.util.isUndefined(value)) { @@ -6074,7 +6086,7 @@ getJasmineRequireObj().pp = function(j$) { } }; - PrettyPrinter.prototype.iterateObject = function(obj, fn) { + SinglePrettyPrintRun.prototype.iterateObject = function(obj, fn) { var objKeys = keys(obj, j$.isArray_(obj)); var isGetter = function isGetter(prop) {}; @@ -6093,15 +6105,15 @@ getJasmineRequireObj().pp = function(j$) { return objKeys.length > length; }; - PrettyPrinter.prototype.emitScalar = function(value) { + SinglePrettyPrintRun.prototype.emitScalar = function(value) { this.append(value); }; - PrettyPrinter.prototype.emitString = function(value) { + SinglePrettyPrintRun.prototype.emitString = function(value) { this.append("'" + value + "'"); }; - PrettyPrinter.prototype.emitArray = function(array) { + SinglePrettyPrintRun.prototype.emitArray = function(array) { if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { this.append('Array'); return; @@ -6137,7 +6149,7 @@ getJasmineRequireObj().pp = function(j$) { this.append(' ]'); }; - PrettyPrinter.prototype.emitSet = function(set) { + SinglePrettyPrintRun.prototype.emitSet = function(set) { if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { this.append('Set'); return; @@ -6162,7 +6174,7 @@ getJasmineRequireObj().pp = function(j$) { this.append(' )'); }; - PrettyPrinter.prototype.emitMap = function(map) { + SinglePrettyPrintRun.prototype.emitMap = function(map) { if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { this.append('Map'); return; @@ -6187,7 +6199,7 @@ getJasmineRequireObj().pp = function(j$) { this.append(' )'); }; - PrettyPrinter.prototype.emitObject = function(obj) { + SinglePrettyPrintRun.prototype.emitObject = function(obj) { var ctor = obj.constructor, constructorName; @@ -6223,7 +6235,7 @@ getJasmineRequireObj().pp = function(j$) { this.append(' })'); }; - PrettyPrinter.prototype.emitTypedArray = function(arr) { + SinglePrettyPrintRun.prototype.emitTypedArray = function(arr) { var constructorName = j$.fnNameFor(arr.constructor), limitedArray = Array.prototype.slice.call( arr, @@ -6239,7 +6251,7 @@ getJasmineRequireObj().pp = function(j$) { this.append(constructorName + ' [ ' + itemsString + ' ]'); }; - PrettyPrinter.prototype.emitDomElement = function(el) { + SinglePrettyPrintRun.prototype.emitDomElement = function(el) { var tagName = el.tagName.toLowerCase(), attrs = el.attributes, i, @@ -6265,7 +6277,11 @@ getJasmineRequireObj().pp = function(j$) { this.append(out); }; - PrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) { + SinglePrettyPrintRun.prototype.formatProperty = function( + obj, + property, + isGetter + ) { this.append(property); this.append(': '); if (isGetter) { @@ -6275,7 +6291,7 @@ getJasmineRequireObj().pp = function(j$) { } }; - PrettyPrinter.prototype.append = function(value) { + SinglePrettyPrintRun.prototype.append = function(value) { // This check protects us from the rare case where an object has overriden // `toString()` with an invalid implementation (returning a non-string). if (typeof value !== 'string') { @@ -6339,10 +6355,13 @@ getJasmineRequireObj().pp = function(j$) { return extraKeys; } - return function(value) { - var prettyPrinter = new PrettyPrinter(); - prettyPrinter.format(value); - return prettyPrinter.stringParts.join(''); + + return function() { + return function(value) { + var prettyPrinter = new SinglePrettyPrintRun(); + prettyPrinter.format(value); + return prettyPrinter.stringParts.join(''); + }; }; }; @@ -7033,7 +7052,10 @@ getJasmineRequireObj().Spy = function(j$) { }; })(); - var matchersUtil = new j$.MatchersUtil(); + var matchersUtil = new j$.MatchersUtil({ + customTesters: [], + pp: j$.makePrettyPrinter() + }); /** * _Note:_ Do not construct this directly, use {@link spyOn}, {@link spyOnProperty}, {@link jasmine.createSpy}, or {@link jasmine.createSpyObj} diff --git a/spec/core/AsyncExpectationSpec.js b/spec/core/AsyncExpectationSpec.js index 23d145e1..74eb2dad 100644 --- a/spec/core/AsyncExpectationSpec.js +++ b/spec/core/AsyncExpectationSpec.js @@ -24,8 +24,9 @@ describe('AsyncExpectation', function() { var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.resolve(), + pp = jasmineUnderTest.makePrettyPrinter(), expectation = jasmineUnderTest.Expectation.asyncFactory({ - util: new jasmineUnderTest.MatchersUtil(), + util: new jasmineUnderTest.MatchersUtil({ pp: pp }), actual: actual, addExpectationResult: addExpectationResult }); @@ -47,7 +48,7 @@ describe('AsyncExpectation', function() { var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.reject(), expectation = jasmineUnderTest.Expectation.asyncFactory({ - util: new jasmineUnderTest.MatchersUtil(), + util: new jasmineUnderTest.MatchersUtil({ pp: function() {} }), actual: actual, addExpectationResult: addExpectationResult }); @@ -122,7 +123,8 @@ describe('AsyncExpectation', function() { var util = { buildFailureMessage: function() { return 'failure message'; - } + }, + pp: jasmineUnderTest.makePrettyPrinter() }, addExpectationResult = jasmine.createSpy('addExpectationResult'), expectation = jasmineUnderTest.Expectation.asyncFactory({ @@ -180,10 +182,11 @@ describe('AsyncExpectation', function() { var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.resolve(), + pp = jasmineUnderTest.makePrettyPrinter(), expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, - util: new jasmineUnderTest.MatchersUtil() + util: new jasmineUnderTest.MatchersUtil({ pp: pp }) }); return expectation @@ -208,7 +211,9 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, - util: new jasmineUnderTest.MatchersUtil() + util: new jasmineUnderTest.MatchersUtil({ + pp: jasmineUnderTest.makePrettyPrinter() + }) }); return expectation diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index dc7233c8..80dcffbb 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -307,10 +307,12 @@ describe('Env', function() { it('creates an expectationFactory that uses the current custom equality testers', function(done) { function customEqualityTester() {} + function prettyPrinter() {} var RealSpec = jasmineUnderTest.Spec, specInstance, expectationFactory; spyOn(jasmineUnderTest, 'MatchersUtil'); + spyOn(jasmineUnderTest, 'makePrettyPrinter').and.returnValue(prettyPrinter); spyOn(jasmineUnderTest, 'Spec').and.callFake(function(options) { expectationFactory = options.expectationFactory; specInstance = new RealSpec(options); @@ -325,7 +327,8 @@ describe('Env', function() { env.addReporter({ jasmineDone: function() { expect(jasmineUnderTest.MatchersUtil).toHaveBeenCalledWith({ - customTesters: [customEqualityTester] + customTesters: [customEqualityTester], + pp: prettyPrinter }); done(); } @@ -336,10 +339,12 @@ describe('Env', function() { it('creates an asyncExpectationFactory that uses the current custom equality testers', function(done) { function customEqualityTester() {} + function prettyPrinter() {} var RealSpec = jasmineUnderTest.Spec, specInstance, asyncExpectationFactory; spyOn(jasmineUnderTest, 'MatchersUtil'); + spyOn(jasmineUnderTest, 'makePrettyPrinter').and.returnValue(prettyPrinter); spyOn(jasmineUnderTest, 'Spec').and.callFake(function(options) { asyncExpectationFactory = options.asyncExpectationFactory; specInstance = new RealSpec(options); @@ -354,7 +359,8 @@ describe('Env', function() { env.addReporter({ jasmineDone: function() { expect(jasmineUnderTest.MatchersUtil).toHaveBeenCalledWith({ - customTesters: [customEqualityTester] + customTesters: [customEqualityTester], + pp: prettyPrinter }); done(); } diff --git a/spec/core/ExpectationSpec.js b/spec/core/ExpectationSpec.js index 361224ab..5b205730 100644 --- a/spec/core/ExpectationSpec.js +++ b/spec/core/ExpectationSpec.js @@ -632,9 +632,10 @@ describe('Expectation', function() { } }, addExpectationResult = jasmine.createSpy('addExpectationResult'), + pp = jasmineUnderTest.makePrettyPrinter(), expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, - util: new jasmineUnderTest.MatchersUtil(), + util: new jasmineUnderTest.MatchersUtil({ pp: pp }), actual: 'an actual', addExpectationResult: addExpectationResult }); diff --git a/spec/core/PrettyPrintSpec.js b/spec/core/PrettyPrintSpec.js index 1249d8c6..46f08d6a 100644 --- a/spec/core/PrettyPrintSpec.js +++ b/spec/core/PrettyPrintSpec.js @@ -1,17 +1,19 @@ -describe('jasmineUnderTest.pp', function() { +describe('PrettyPrinter', function() { it('should wrap strings in single quotes', function() { - expect(jasmineUnderTest.pp('some string')).toEqual("'some string'"); - expect(jasmineUnderTest.pp("som' string")).toEqual("'som' string'"); + var pp = jasmineUnderTest.makePrettyPrinter(); + expect(pp('some string')).toEqual("'some string'"); + expect(pp("som' string")).toEqual("'som' string'"); }); it('should stringify primitives properly', function() { - expect(jasmineUnderTest.pp(true)).toEqual('true'); - expect(jasmineUnderTest.pp(false)).toEqual('false'); - expect(jasmineUnderTest.pp(null)).toEqual('null'); - expect(jasmineUnderTest.pp(jasmine.undefined)).toEqual('undefined'); - expect(jasmineUnderTest.pp(3)).toEqual('3'); - expect(jasmineUnderTest.pp(-3.14)).toEqual('-3.14'); - expect(jasmineUnderTest.pp(-0)).toEqual('-0'); + var pp = jasmineUnderTest.makePrettyPrinter(); + expect(pp(true)).toEqual('true'); + expect(pp(false)).toEqual('false'); + expect(pp(null)).toEqual('null'); + expect(pp(jasmine.undefined)).toEqual('undefined'); + expect(pp(3)).toEqual('3'); + expect(pp(-3.14)).toEqual('-3.14'); + expect(pp(-0)).toEqual('-0'); }); describe('stringify sets', function() { @@ -20,7 +22,8 @@ describe('jasmineUnderTest.pp', function() { var set = new Set(); set.add(1); set.add(2); - expect(jasmineUnderTest.pp(set)).toEqual('Set( 1, 2 )'); + var pp = jasmineUnderTest.makePrettyPrinter(); + expect(pp(set)).toEqual('Set( 1, 2 )'); }); it('should truncate sets with more elements than jasmineUnderTest.MAX_PRETTY_PRINT_ARRAY_LENGTH', function() { @@ -33,7 +36,8 @@ describe('jasmineUnderTest.pp', function() { set.add('a'); set.add('b'); set.add('c'); - expect(jasmineUnderTest.pp(set)).toEqual("Set( 'a', 'b', ... )"); + var pp = jasmineUnderTest.makePrettyPrinter(); + expect(pp(set)).toEqual("Set( 'a', 'b', ... )"); } finally { jasmineUnderTest.MAX_PRETTY_PRINT_ARRAY_LENGTH = originalMaxSize; } @@ -45,7 +49,8 @@ describe('jasmineUnderTest.pp', function() { jasmine.getEnv().requireFunctioningMaps(); var map = new Map(); map.set(1, 2); - expect(jasmineUnderTest.pp(map)).toEqual('Map( [ 1, 2 ] )'); + var pp = jasmineUnderTest.makePrettyPrinter(); + expect(pp(map)).toEqual('Map( [ 1, 2 ] )'); }); it('should truncate maps with more elements than jasmineUnderTest.MAX_PRETTY_PRINT_ARRAY_LENGTH', function() { @@ -58,9 +63,8 @@ describe('jasmineUnderTest.pp', function() { map.set('a', 1); map.set('b', 2); map.set('c', 3); - expect(jasmineUnderTest.pp(map)).toEqual( - "Map( [ 'a', 1 ], [ 'b', 2 ], ... )" - ); + var pp = jasmineUnderTest.makePrettyPrinter(); + expect(pp(map)).toEqual("Map( [ 'a', 1 ], [ 'b', 2 ], ... )"); } finally { jasmineUnderTest.MAX_PRETTY_PRINT_ARRAY_LENGTH = originalMaxSize; } @@ -69,43 +73,44 @@ describe('jasmineUnderTest.pp', function() { describe('stringify arrays', function() { it('should stringify arrays properly', function() { - expect(jasmineUnderTest.pp([1, 2])).toEqual('[ 1, 2 ]'); - expect( - jasmineUnderTest.pp([1, 'foo', {}, jasmine.undefined, null]) - ).toEqual("[ 1, 'foo', Object({ }), undefined, null ]"); + var pp = jasmineUnderTest.makePrettyPrinter(); + expect(pp([1, 2])).toEqual('[ 1, 2 ]'); + expect(pp([1, 'foo', {}, jasmine.undefined, null])).toEqual( + "[ 1, 'foo', Object({ }), undefined, null ]" + ); }); it('should truncate arrays that are longer than jasmineUnderTest.MAX_PRETTY_PRINT_ARRAY_LENGTH', function() { var originalMaxLength = jasmineUnderTest.MAX_PRETTY_PRINT_ARRAY_LENGTH; var array = [1, 2, 3]; + var pp = jasmineUnderTest.makePrettyPrinter(); try { jasmineUnderTest.MAX_PRETTY_PRINT_ARRAY_LENGTH = 2; - expect(jasmineUnderTest.pp(array)).toEqual('[ 1, 2, ... ]'); + expect(pp(array)).toEqual('[ 1, 2, ... ]'); } finally { jasmineUnderTest.MAX_PRETTY_PRINT_ARRAY_LENGTH = originalMaxLength; } }); it('should stringify arrays with properties properly', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var arr = [1, 2]; arr.foo = 'bar'; arr.baz = {}; - expect(jasmineUnderTest.pp(arr)).toEqual( - "[ 1, 2, foo: 'bar', baz: Object({ }) ]" - ); + expect(pp(arr)).toEqual("[ 1, 2, foo: 'bar', baz: Object({ }) ]"); }); it('should stringify empty arrays with properties properly', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var empty = []; empty.foo = 'bar'; empty.baz = {}; - expect(jasmineUnderTest.pp(empty)).toEqual( - "[ foo: 'bar', baz: Object({ }) ]" - ); + expect(pp(empty)).toEqual("[ foo: 'bar', baz: Object({ }) ]"); }); it('should stringify long arrays with properties properly', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var originalMaxLength = jasmineUnderTest.MAX_PRETTY_PRINT_ARRAY_LENGTH; var long = [1, 2, 3]; long.foo = 'bar'; @@ -113,7 +118,7 @@ describe('jasmineUnderTest.pp', function() { try { jasmineUnderTest.MAX_PRETTY_PRINT_ARRAY_LENGTH = 2; - expect(jasmineUnderTest.pp(long)).toEqual( + expect(pp(long)).toEqual( "[ 1, 2, ..., foo: 'bar', baz: Object({ }) ]" ); } finally { @@ -122,26 +127,25 @@ describe('jasmineUnderTest.pp', function() { }); it('should indicate circular array references', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var array1 = [1, 2]; var array2 = [array1]; array1.push(array2); - expect(jasmineUnderTest.pp(array1)).toEqual( - '[ 1, 2, [ ] ]' - ); + expect(pp(array1)).toEqual('[ 1, 2, [ ] ]'); }); it('should not indicate circular references incorrectly', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var array = [[1]]; - expect(jasmineUnderTest.pp(array)).toEqual('[ [ 1 ] ]'); + expect(pp(array)).toEqual('[ [ 1 ] ]'); }); }); it('should stringify objects properly', function() { - expect(jasmineUnderTest.pp({ foo: 'bar' })).toEqual( - "Object({ foo: 'bar' })" - ); + var pp = jasmineUnderTest.makePrettyPrinter(); + expect(pp({ foo: 'bar' })).toEqual("Object({ foo: 'bar' })"); expect( - jasmineUnderTest.pp({ + pp({ foo: 'bar', baz: 3, nullValue: null, @@ -150,24 +154,24 @@ describe('jasmineUnderTest.pp', function() { ).toEqual( "Object({ foo: 'bar', baz: 3, nullValue: null, undefinedValue: undefined })" ); - expect(jasmineUnderTest.pp({ foo: function() {}, bar: [1, 2, 3] })).toEqual( + expect(pp({ foo: function() {}, bar: [1, 2, 3] })).toEqual( 'Object({ foo: Function, bar: [ 1, 2, 3 ] })' ); }); it('should stringify objects that almost look like DOM nodes', function() { - expect(jasmineUnderTest.pp({ nodeType: 1 })).toEqual( - 'Object({ nodeType: 1 })' - ); + var pp = jasmineUnderTest.makePrettyPrinter(); + expect(pp({ nodeType: 1 })).toEqual('Object({ nodeType: 1 })'); }); it('should truncate objects with too many keys', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var originalMaxLength = jasmineUnderTest.MAX_PRETTY_PRINT_ARRAY_LENGTH; var long = { a: 1, b: 2, c: 3 }; try { jasmineUnderTest.MAX_PRETTY_PRINT_ARRAY_LENGTH = 2; - expect(jasmineUnderTest.pp(long)).toEqual('Object({ a: 1, b: 2, ... })'); + expect(pp(long)).toEqual('Object({ a: 1, b: 2, ... })'); } finally { jasmineUnderTest.MAX_PRETTY_PRINT_ARRAY_LENGTH = originalMaxLength; } @@ -185,12 +189,11 @@ describe('jasmineUnderTest.pp', function() { } it('should truncate outputs that are too long', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var big = [{ a: 1, b: 'a long string' }, {}]; withMaxChars(34, function() { - expect(jasmineUnderTest.pp(big)).toEqual( - "[ Object({ a: 1, b: 'a long st ..." - ); + expect(pp(big)).toEqual("[ Object({ a: 1, b: 'a long st ..."); }); }); @@ -214,59 +217,55 @@ describe('jasmineUnderTest.pp', function() { jasmineToString: jasmine .createSpy('d jasmineToString') .and.returnValue('') - }; + }, + pp = jasmineUnderTest.makePrettyPrinter(); withMaxChars(30, function() { - jasmineUnderTest.pp([{ a: a, b: b, c: c }, d]); + pp([{ a: a, b: b, c: c }, d]); expect(c.jasmineToString).not.toHaveBeenCalled(); expect(d.jasmineToString).not.toHaveBeenCalled(); }); }); it("should print 'null' as the constructor of an object with its own constructor property", function() { - expect(jasmineUnderTest.pp({ constructor: function() {} })).toContain( - 'null({' - ); - expect(jasmineUnderTest.pp({ constructor: 'foo' })).toContain('null({'); + var pp = jasmineUnderTest.makePrettyPrinter(); + expect(pp({ constructor: function() {} })).toContain('null({'); + expect(pp({ constructor: 'foo' })).toContain('null({'); }); it('should not include inherited properties when stringifying an object', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var SomeClass = function SomeClass() {}; SomeClass.prototype.foo = 'inherited foo'; var instance = new SomeClass(); instance.bar = 'my own bar'; - expect(jasmineUnderTest.pp(instance)).toEqual( - "SomeClass({ bar: 'my own bar' })" - ); + expect(pp(instance)).toEqual("SomeClass({ bar: 'my own bar' })"); }); it('should not recurse objects and arrays more deeply than jasmineUnderTest.MAX_PRETTY_PRINT_DEPTH', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var originalMaxDepth = jasmineUnderTest.MAX_PRETTY_PRINT_DEPTH; var nestedObject = { level1: { level2: { level3: { level4: 'leaf' } } } }; var nestedArray = [1, [2, [3, [4, 'leaf']]]]; try { jasmineUnderTest.MAX_PRETTY_PRINT_DEPTH = 2; - expect(jasmineUnderTest.pp(nestedObject)).toEqual( + expect(pp(nestedObject)).toEqual( 'Object({ level1: Object({ level2: Object }) })' ); - expect(jasmineUnderTest.pp(nestedArray)).toEqual('[ 1, [ 2, Array ] ]'); + expect(pp(nestedArray)).toEqual('[ 1, [ 2, Array ] ]'); jasmineUnderTest.MAX_PRETTY_PRINT_DEPTH = 3; - expect(jasmineUnderTest.pp(nestedObject)).toEqual( + expect(pp(nestedObject)).toEqual( 'Object({ level1: Object({ level2: Object({ level3: Object }) }) })' ); - expect(jasmineUnderTest.pp(nestedArray)).toEqual( - '[ 1, [ 2, [ 3, Array ] ] ]' - ); + expect(pp(nestedArray)).toEqual('[ 1, [ 2, [ 3, Array ] ] ]'); jasmineUnderTest.MAX_PRETTY_PRINT_DEPTH = 4; - expect(jasmineUnderTest.pp(nestedObject)).toEqual( + expect(pp(nestedObject)).toEqual( "Object({ level1: Object({ level2: Object({ level3: Object({ level4: 'leaf' }) }) }) })" ); - expect(jasmineUnderTest.pp(nestedArray)).toEqual( - "[ 1, [ 2, [ 3, [ 4, 'leaf' ] ] ] ]" - ); + expect(pp(nestedArray)).toEqual("[ 1, [ 2, [ 3, [ 4, 'leaf' ] ] ] ]"); } finally { jasmineUnderTest.MAX_PRETTY_PRINT_DEPTH = originalMaxDepth; } @@ -274,28 +273,32 @@ describe('jasmineUnderTest.pp', function() { it('should stringify immutable circular objects', function() { if (Object.freeze) { + var pp = jasmineUnderTest.makePrettyPrinter(); var frozenObject = { foo: { bar: 'baz' } }; frozenObject.circular = frozenObject; frozenObject = Object.freeze(frozenObject); - expect(jasmineUnderTest.pp(frozenObject)).toEqual( + expect(pp(frozenObject)).toEqual( "Object({ foo: Object({ bar: 'baz' }), circular: })" ); } }); it('should stringify RegExp objects properly', function() { - expect(jasmineUnderTest.pp(/x|y|z/)).toEqual('/x|y|z/'); + var pp = jasmineUnderTest.makePrettyPrinter(); + expect(pp(/x|y|z/)).toEqual('/x|y|z/'); }); it('should indicate circular object references', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var sampleValue = { foo: 'hello' }; sampleValue.nested = sampleValue; - expect(jasmineUnderTest.pp(sampleValue)).toEqual( + expect(pp(sampleValue)).toEqual( "Object({ foo: 'hello', nested: })" ); }); it('should indicate getters on objects as such', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var sampleValue = { id: 1 }; if (sampleValue.__defineGetter__) { //not supported in IE! @@ -304,34 +307,38 @@ describe('jasmineUnderTest.pp', function() { }); } if (sampleValue.__defineGetter__) { - expect(jasmineUnderTest.pp(sampleValue)).toEqual( + expect(pp(sampleValue)).toEqual( 'Object({ id: 1, calculatedValue: })' ); } else { - expect(jasmineUnderTest.pp(sampleValue)).toEqual('Object({ id: 1 })'); + expect(pp(sampleValue)).toEqual('Object({ id: 1 })'); } }); it('should not do HTML escaping of strings', function() { - expect(jasmineUnderTest.pp('some html string &', false)).toEqual( + var pp = jasmineUnderTest.makePrettyPrinter(); + expect(pp('some html string &', false)).toEqual( "'some html string &'" ); }); it('should abbreviate the global (usually window) object', function() { - expect(jasmineUnderTest.pp(jasmine.getGlobal())).toEqual(''); + var pp = jasmineUnderTest.makePrettyPrinter(); + expect(pp(jasmine.getGlobal())).toEqual(''); }); it('should stringify Date objects properly', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var now = new Date(); - expect(jasmineUnderTest.pp(now)).toEqual('Date(' + now.toString() + ')'); + expect(pp(now)).toEqual('Date(' + now.toString() + ')'); }); it('should stringify spy objects properly', function() { var TestObject = { someFunction: function() {} }, - env = new jasmineUnderTest.Env(); + env = new jasmineUnderTest.Env(), + pp = jasmineUnderTest.makePrettyPrinter(); var spyRegistry = new jasmineUnderTest.SpyRegistry({ currentSpies: function() { @@ -343,20 +350,17 @@ describe('jasmineUnderTest.pp', function() { }); spyRegistry.spyOn(TestObject, 'someFunction'); - expect(jasmineUnderTest.pp(TestObject.someFunction)).toEqual( - 'spy on someFunction' - ); + expect(pp(TestObject.someFunction)).toEqual('spy on someFunction'); - expect(jasmineUnderTest.pp(env.createSpy('something'))).toEqual( - 'spy on something' - ); + expect(pp(env.createSpy('something'))).toEqual('spy on something'); }); it('should stringify spyOn toString properly', function() { var TestObject = { someFunction: function() {} }, - env = new jasmineUnderTest.Env(); + env = new jasmineUnderTest.Env(), + pp = jasmineUnderTest.makePrettyPrinter(); var spyRegistry = new jasmineUnderTest.SpyRegistry({ currentSpies: function() { @@ -370,29 +374,29 @@ describe('jasmineUnderTest.pp', function() { spyRegistry.spyOn(TestObject, 'toString'); var testSpyObj = env.createSpyObj('TheClassName', ['toString']); - expect(jasmineUnderTest.pp(testSpyObj)).toEqual( - 'spy on TheClassName.toString' - ); + expect(pp(testSpyObj)).toEqual('spy on TheClassName.toString'); }); it('should stringify objects that implement jasmineToString', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var obj = { jasmineToString: function() { return 'strung'; } }; - expect(jasmineUnderTest.pp(obj)).toEqual('strung'); + expect(pp(obj)).toEqual('strung'); }); it('should stringify objects that implement custom toString', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var obj = { toString: function() { return 'my toString'; } }; - expect(jasmineUnderTest.pp(obj)).toEqual('my toString'); + expect(pp(obj)).toEqual('my toString'); // Simulate object from another global context (e.g. an iframe or Web Worker) that does not actually have a custom // toString despite obj.toString !== Object.prototype.toString @@ -403,20 +407,22 @@ describe('jasmineUnderTest.pp', function() { } }; - expect(jasmineUnderTest.pp(objFromOtherContext)).toEqual( + expect(pp(objFromOtherContext)).toEqual( "Object({ foo: 'bar', toString: Function })" ); }); it("should stringify objects have have a toString that isn't a function", function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var obj = { toString: 'foo' }; - expect(jasmineUnderTest.pp(obj)).toEqual("Object({ toString: 'foo' })"); + expect(pp(obj)).toEqual("Object({ toString: 'foo' })"); }); it('should stringify objects from anonymous constructors with custom toString', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var MyAnonymousConstructor = (function() { return function() {}; })(); @@ -426,17 +432,19 @@ describe('jasmineUnderTest.pp', function() { var a = new MyAnonymousConstructor(); - expect(jasmineUnderTest.pp(a)).toEqual('({ })'); + expect(pp(a)).toEqual('({ })'); }); it('should handle objects with null prototype', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var obj = Object.create(null); obj.foo = 'bar'; - expect(jasmineUnderTest.pp(obj)).toEqual("null({ foo: 'bar' })"); + expect(pp(obj)).toEqual("null({ foo: 'bar' })"); }); it('should gracefully handle objects with invalid toString implementations', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var obj = { foo: { toString: function() { @@ -466,7 +474,7 @@ describe('jasmineUnderTest.pp', function() { } }; - expect(jasmineUnderTest.pp(obj)).toEqual( + expect(pp(obj)).toEqual( 'Object({ foo: [object Number], bar: [object Object], baz: 3, qux: Error: bar, baddy: has-invalid-toString-method })' ); }); diff --git a/spec/core/matchers/async/toBeRejectedWithErrorSpec.js b/spec/core/matchers/async/toBeRejectedWithErrorSpec.js index 03a337b0..97260d59 100644 --- a/spec/core/matchers/async/toBeRejectedWithErrorSpec.js +++ b/spec/core/matchers/async/toBeRejectedWithErrorSpec.js @@ -2,7 +2,7 @@ describe('#toBeRejectedWithError', function () { it('passes when Error type matches', function () { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new TypeError('foo')); @@ -17,7 +17,7 @@ describe('#toBeRejectedWithError', function () { it('passes when Error type and message matches', function () { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new TypeError('foo')); @@ -32,7 +32,7 @@ describe('#toBeRejectedWithError', function () { it('passes when Error matches and is exactly Error', function() { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error()); @@ -48,7 +48,7 @@ describe('#toBeRejectedWithError', function () { it('passes when Error message matches a string', function () { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error('foo')); @@ -63,7 +63,7 @@ describe('#toBeRejectedWithError', function () { it('passes when Error message matches a RegExp', function () { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error('foo')); @@ -78,7 +78,7 @@ describe('#toBeRejectedWithError', function () { it('passes when Error message is empty', function () { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error()); @@ -93,7 +93,7 @@ describe('#toBeRejectedWithError', function () { it('passes when no arguments', function () { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error()); @@ -108,7 +108,7 @@ describe('#toBeRejectedWithError', function () { it('fails when resolved', function () { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.resolve(new Error('foo')); @@ -123,7 +123,7 @@ describe('#toBeRejectedWithError', function () { it('fails when rejected with non Error type', function () { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject('foo'); @@ -138,7 +138,7 @@ describe('#toBeRejectedWithError', function () { it('fails when Error type mismatches', function () { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error('foo')); @@ -153,7 +153,7 @@ describe('#toBeRejectedWithError', function () { it('fails when Error message mismatches', function () { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error('foo')); @@ -166,7 +166,7 @@ describe('#toBeRejectedWithError', function () { }); it('fails if actual is not a promise', function() { - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = 'not a promise'; diff --git a/spec/core/matchers/async/toBeRejectedWithSpec.js b/spec/core/matchers/async/toBeRejectedWithSpec.js index c1d22d0b..2c02a03a 100644 --- a/spec/core/matchers/async/toBeRejectedWithSpec.js +++ b/spec/core/matchers/async/toBeRejectedWithSpec.js @@ -26,7 +26,7 @@ describe('#toBeRejectedWith', function () { it('should fail if the promise is rejected with a different value', function () { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = Promise.reject('A Bad Apple'); @@ -41,7 +41,7 @@ describe('#toBeRejectedWith', function () { it('should build its error correctly when negated', function () { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = Promise.reject(true); @@ -67,7 +67,7 @@ describe('#toBeRejectedWith', function () { }); it('fails if actual is not a promise', function() { - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = 'not a promise'; diff --git a/spec/core/matchers/async/toBeResolvedToSpec.js b/spec/core/matchers/async/toBeResolvedToSpec.js index 426f9f12..c68eb0e0 100644 --- a/spec/core/matchers/async/toBeResolvedToSpec.js +++ b/spec/core/matchers/async/toBeResolvedToSpec.js @@ -14,7 +14,7 @@ describe('#toBeResolvedTo', function() { it('fails if the promise is rejected', function() { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.reject('AsyncExpectationSpec error'); @@ -29,7 +29,7 @@ describe('#toBeResolvedTo', function() { it('fails if the promise is resolved to a different value', function() { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.resolve({foo: 17}); @@ -44,7 +44,7 @@ describe('#toBeResolvedTo', function() { it('builds its message correctly when negated', function() { jasmine.getEnv().requirePromises(); - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: new jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.resolve(true); @@ -60,7 +60,10 @@ describe('#toBeResolvedTo', function() { jasmine.getEnv().requirePromises(); var customEqualityTesters = [function() { return true; }], - matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: customEqualityTesters}), + matchersUtil = new jasmineUnderTest.MatchersUtil({ + customTesters: customEqualityTesters, + pp: jasmineUnderTest.makePrettyPrinter() + }), matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.resolve('actual'); @@ -70,7 +73,7 @@ describe('#toBeResolvedTo', function() { }); it('fails if actual is not a promise', function() { - var matchersUtil = new jasmineUnderTest.MatchersUtil(), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = 'not a promise'; diff --git a/spec/core/matchers/matchersUtilSpec.js b/spec/core/matchers/matchersUtilSpec.js index 096d7c86..ff9b159d 100644 --- a/spec/core/matchers/matchersUtilSpec.js +++ b/spec/core/matchers/matchersUtilSpec.js @@ -1,4 +1,11 @@ describe("matchersUtil", function() { + it("exposes the injected pretty-printer as .pp", function() { + var pp = function() {}, + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: pp}); + + expect(matchersUtil.pp).toBe(pp); + }); + describe("equals", function() { it("passes for literals that are triple-equal", function() { var matchersUtil = new jasmineUnderTest.MatchersUtil(); @@ -76,7 +83,7 @@ describe("matchersUtil", function() { }); it("fails for Arrays that have different lengths", function() { - var matchersUtil = new jasmineUnderTest.MatchersUtil(); + matchersUtil = new jasmineUnderTest.MatchersUtil(); expect(matchersUtil.equals([1, 2], [1, 2, 3])).toBe(false); }); @@ -392,7 +399,7 @@ describe("matchersUtil", function() { it("passes when a custom equality matcher passed to the constructor returns true", function() { var tester = function(a, b) { return true; }, - matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester], pp: function() {}}); expect(matchersUtil.equals(1, 2)).toBe(true); }); @@ -416,7 +423,7 @@ describe("matchersUtil", function() { var tester = function(a, b) { return jasmine.undefined; }; it("passes for two empty Objects", function () { - var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); + var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester], pp: function() {}}); expect(matchersUtil.equals({}, {})).toBe(true); }); }); @@ -431,7 +438,7 @@ describe("matchersUtil", function() { it("fails for equivalents when a custom equality matcher passed to the constructor returns false", function() { var tester = function(a, b) { return false; }, - matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester], pp: function() {}}); expect(matchersUtil.equals(1, 1)).toBe(false); }); @@ -449,7 +456,7 @@ describe("matchersUtil", function() { it("passes for an asymmetric equality tester that returns true when a custom equality tester passed to the constructor return false", function() { var asymmetricTester = { asymmetricMatch: function(other) { return true; } }, symmetricTester = function(a, b) { return false; }, - matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [symmetricTester()]}); + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [symmetricTester()], pp: function() {}}); expect(matchersUtil.equals(asymmetricTester, true)).toBe(true); expect(matchersUtil.equals(true, asymmetricTester)).toBe(true); @@ -475,7 +482,7 @@ describe("matchersUtil", function() { it("is both a matchersUtil and the custom equality testers passed to the constructor", function() { var asymmetricTester = jasmine.createSpyObj('tester', ['asymmetricMatch']), symmetricTester = function() { } , - matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [symmetricTester]}), + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [symmetricTester], pp: function() {}}), shim; matchersUtil.equals(true, asymmetricTester); @@ -783,7 +790,7 @@ describe("matchersUtil", function() { it("uses custom equality testers if passed to the constructor and actual is an Array", function() { var customTester = function(a, b) {return true;}, - matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [customTester]}); + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [customTester], pp: function() {}}); expect(matchersUtil.contains([1, 2], 3)).toBe(true); }); @@ -838,7 +845,8 @@ describe("matchersUtil", function() { it("builds an English sentence for a failure case", function() { var actual = "foo", name = "toBar", - matchersUtil = new jasmineUnderTest.MatchersUtil(), + pp = jasmineUnderTest.makePrettyPrinter(), + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: pp}), message = matchersUtil.buildFailureMessage(name, false, actual); expect(message).toEqual("Expected 'foo' to bar."); @@ -848,7 +856,8 @@ describe("matchersUtil", function() { var actual = "foo", name = "toBar", isNot = true, - matchersUtil = new jasmineUnderTest.MatchersUtil(), + pp = jasmineUnderTest.makePrettyPrinter(), + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: pp}), message = message = matchersUtil.buildFailureMessage(name, isNot, actual); expect(message).toEqual("Expected 'foo' not to bar."); @@ -857,10 +866,22 @@ describe("matchersUtil", function() { it("builds an English sentence for an arbitrary array of expected arguments", function() { var actual = "foo", name = "toBar", - matchersUtil = new jasmineUnderTest.MatchersUtil(), + pp = jasmineUnderTest.makePrettyPrinter(), + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: pp}), message = matchersUtil.buildFailureMessage(name, false, actual, "quux", "corge"); expect(message).toEqual("Expected 'foo' to bar 'quux', 'corge'."); }); + + it("uses the injected pretty-printer to format the expected", function() { + var actual = "foo", + name = "toBar", + isNot = false, + pp = function(value) { return '<' + value + '>'; }, + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: pp}), + message = message = matchersUtil.buildFailureMessage(name, isNot, actual); + + expect(message).toEqual("Expected to bar."); + }); }); }); diff --git a/spec/core/matchers/toBeInstanceOfSpec.js b/spec/core/matchers/toBeInstanceOfSpec.js index d31845e9..d8355af7 100644 --- a/spec/core/matchers/toBeInstanceOfSpec.js +++ b/spec/core/matchers/toBeInstanceOfSpec.js @@ -10,7 +10,9 @@ describe('toBeInstanceOf', function() { }); it('passes for NaN', function() { - var matcher = jasmineUnderTest.matchers.toBeInstanceOf(); + var matcher = jasmineUnderTest.matchers.toBeInstanceOf({ + pp: jasmineUnderTest.makePrettyPrinter() + }); var result = matcher.compare(NaN, Number); expect(result).toEqual({ pass: true, @@ -156,7 +158,9 @@ describe('toBeInstanceOf', function() { it('passes for objects with no constructor', function() { var object = Object.create(null); - var matcher = jasmineUnderTest.matchers.toBeInstanceOf(); + var matcher = jasmineUnderTest.matchers.toBeInstanceOf({ + pp: jasmineUnderTest.makePrettyPrinter() + }); var result = matcher.compare(object, Object); expect(result).toEqual({ pass: true, @@ -219,7 +223,9 @@ describe('toBeInstanceOf', function() { }); it('raises an error if missing an expected value', function() { - var matcher = jasmineUnderTest.matchers.toBeInstanceOf(); + var matcher = jasmineUnderTest.matchers.toBeInstanceOf({ + pp: jasmineUnderTest.makePrettyPrinter() + }); expect(function() { matcher.compare({}, undefined); }).toThrowError(' : Expected value is not a constructor function\n' + diff --git a/spec/core/matchers/toBeNaNSpec.js b/spec/core/matchers/toBeNaNSpec.js index b490d089..e859f64f 100644 --- a/spec/core/matchers/toBeNaNSpec.js +++ b/spec/core/matchers/toBeNaNSpec.js @@ -29,7 +29,9 @@ describe("toBeNaN", function() { }); it("has a custom message on failure", function() { - var matcher = jasmineUnderTest.matchers.toBeNaN(), + var matcher = jasmineUnderTest.matchers.toBeNaN({ + pp: jasmineUnderTest.makePrettyPrinter() + }), result = matcher.compare(0); expect(result.message()).toEqual("Expected 0 to be NaN."); diff --git a/spec/core/matchers/toBeNegativeInfinitySpec.js b/spec/core/matchers/toBeNegativeInfinitySpec.js index 4aca8978..876bf209 100644 --- a/spec/core/matchers/toBeNegativeInfinitySpec.js +++ b/spec/core/matchers/toBeNegativeInfinitySpec.js @@ -14,7 +14,9 @@ describe("toBeNegativeInfinity", function() { }); it("has a custom message on failure", function() { - var matcher = jasmineUnderTest.matchers.toBeNegativeInfinity(), + var matcher = jasmineUnderTest.matchers.toBeNegativeInfinity({ + pp: jasmineUnderTest.makePrettyPrinter() + }), result = matcher.compare(0); expect(result.message()).toEqual("Expected 0 to be -Infinity.") diff --git a/spec/core/matchers/toBePositiveInfinitySpec.js b/spec/core/matchers/toBePositiveInfinitySpec.js index 239833d9..6fc91b65 100644 --- a/spec/core/matchers/toBePositiveInfinitySpec.js +++ b/spec/core/matchers/toBePositiveInfinitySpec.js @@ -14,7 +14,9 @@ describe("toBePositiveInfinity", function() { }); it("has a custom message on failure", function() { - var matcher = jasmineUnderTest.matchers.toBePositiveInfinity(), + var matcher = jasmineUnderTest.matchers.toBePositiveInfinity({ + pp: jasmineUnderTest.makePrettyPrinter() + }), result = matcher.compare(0); expect(result.message()).toEqual("Expected 0 to be Infinity.") diff --git a/spec/core/matchers/toBeSpec.js b/spec/core/matchers/toBeSpec.js index 8e27b992..d95f2b49 100644 --- a/spec/core/matchers/toBeSpec.js +++ b/spec/core/matchers/toBeSpec.js @@ -9,7 +9,7 @@ describe("toBe", function() { }); it("passes with a custom message when expected is an array", function() { - var util = new jasmineUnderTest.MatchersUtil(), + var util = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.matchers.toBe(util), result, array = [1]; @@ -20,7 +20,7 @@ describe("toBe", function() { }); it("passes with a custom message when expected is an object", function() { - var util = new jasmineUnderTest.MatchersUtil(), + var util = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.matchers.toBe(util), result, obj = {foo: "bar"}; @@ -41,7 +41,7 @@ describe("toBe", function() { }); it("fails with a custom message when expected is an array", function() { - var util = new jasmineUnderTest.MatchersUtil(), + var util = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.matchers.toBe(util), result; @@ -51,7 +51,7 @@ describe("toBe", function() { }); it("fails with a custom message when expected is an object", function() { - var util = new jasmineUnderTest.MatchersUtil(), + var util = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.matchers.toBe(util), result; diff --git a/spec/core/matchers/toEqualSpec.js b/spec/core/matchers/toEqualSpec.js index 2bd2f077..5702f7bb 100644 --- a/spec/core/matchers/toEqualSpec.js +++ b/spec/core/matchers/toEqualSpec.js @@ -2,7 +2,9 @@ describe("toEqual", function() { "use strict"; function compareEquals(actual, expected) { - var util = new jasmineUnderTest.MatchersUtil(), + var util = new jasmineUnderTest.MatchersUtil({ + pp: jasmineUnderTest.makePrettyPrinter() + }), matcher = jasmineUnderTest.matchers.toEqual(util); var result = matcher.compare(actual, expected); @@ -103,8 +105,6 @@ describe("toEqual", function() { " g: 3\n" + "Expected $.x not to have properties\n" + " f: 4"; - - expect(compareEquals(actual, expected).message).toEqual(message); }); it("reports extra and missing properties of the root-level object", function() { diff --git a/spec/core/matchers/toHaveBeenCalledBeforeSpec.js b/spec/core/matchers/toHaveBeenCalledBeforeSpec.js index f2e89c2f..b47514d5 100644 --- a/spec/core/matchers/toHaveBeenCalledBeforeSpec.js +++ b/spec/core/matchers/toHaveBeenCalledBeforeSpec.js @@ -1,6 +1,8 @@ describe("toHaveBeenCalledBefore", function() { it("throws an exception when the actual is not a spy", function() { - var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore({ + pp: jasmineUnderTest.makePrettyPrinter() + }), fn = function() {}, secondSpy = new jasmineUnderTest.Env().createSpy('second spy'); @@ -8,7 +10,9 @@ describe("toHaveBeenCalledBefore", function() { }); it("throws an exception when the expected is not a spy", function() { - var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore({ + pp: jasmineUnderTest.makePrettyPrinter() + }), firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), fn = function() {}; diff --git a/spec/core/matchers/toHaveBeenCalledSpec.js b/spec/core/matchers/toHaveBeenCalledSpec.js index b14116f0..4f7e1efb 100644 --- a/spec/core/matchers/toHaveBeenCalledSpec.js +++ b/spec/core/matchers/toHaveBeenCalledSpec.js @@ -21,7 +21,9 @@ describe("toHaveBeenCalled", function() { }); it("throws an exception when the actual is not a spy", function() { - var matcher = jasmineUnderTest.matchers.toHaveBeenCalled(), + var matcher = jasmineUnderTest.matchers.toHaveBeenCalled({ + pp: jasmineUnderTest.makePrettyPrinter() + }), fn = function() {}; expect(function() { matcher.compare(fn) }).toThrowError(Error, /Expected a spy, but got Function./); diff --git a/spec/core/matchers/toHaveBeenCalledTimesSpec.js b/spec/core/matchers/toHaveBeenCalledTimesSpec.js index 62778fbd..9102b4b7 100644 --- a/spec/core/matchers/toHaveBeenCalledTimesSpec.js +++ b/spec/core/matchers/toHaveBeenCalledTimesSpec.js @@ -49,7 +49,9 @@ describe("toHaveBeenCalledTimes", function() { }); it("throws an exception when the actual is not a spy", function() { - var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes({ + pp: jasmineUnderTest.makePrettyPrinter() + }), fn = function() {}; expect(function() { diff --git a/spec/core/matchers/toHaveBeenCalledWithSpec.js b/spec/core/matchers/toHaveBeenCalledWithSpec.js index a57bc4a4..852dd837 100644 --- a/spec/core/matchers/toHaveBeenCalledWithSpec.js +++ b/spec/core/matchers/toHaveBeenCalledWithSpec.js @@ -2,7 +2,8 @@ describe("toHaveBeenCalledWith", function() { it("passes when the actual was called with matching parameters", function() { var util = { - contains: jasmine.createSpy('delegated-contains').and.returnValue(true) + contains: jasmine.createSpy('delegated-contains').and.returnValue(true), + pp: jasmineUnderTest.makePrettyPrinter() }, matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'), @@ -29,7 +30,8 @@ describe("toHaveBeenCalledWith", function() { it("fails when the actual was not called", function() { var util = { - contains: jasmine.createSpy('delegated-contains').and.returnValue(false) + contains: jasmine.createSpy('delegated-contains').and.returnValue(false), + pp: jasmineUnderTest.makePrettyPrinter() }, matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), uncalledSpy = new jasmineUnderTest.Env().createSpy('uncalled spy'), @@ -41,7 +43,7 @@ describe("toHaveBeenCalledWith", function() { }); it("fails when the actual was called with different parameters", function() { - var util = new jasmineUnderTest.MatchersUtil(), + var util = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), calledSpy = new jasmineUnderTest.Env().createSpy('called spy'), result; @@ -71,8 +73,11 @@ describe("toHaveBeenCalledWith", function() { }); it("throws an exception when the actual is not a spy", function() { - var matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(), - fn = function() {}; + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith({ + pp: jasmineUnderTest.makePrettyPrinter() + }), + fn = function () { + }; expect(function() { matcher.compare(fn) }).toThrowError(/Expected a spy, but got Function./); }); diff --git a/spec/core/matchers/toHaveClassSpec.js b/spec/core/matchers/toHaveClassSpec.js index 109babbb..5f593abc 100644 --- a/spec/core/matchers/toHaveClassSpec.js +++ b/spec/core/matchers/toHaveClassSpec.js @@ -27,7 +27,9 @@ describe('toHaveClass', function() { }); it('throws an exception when actual is not a DOM element', function() { - var matcher = jasmineUnderTest.matchers.toHaveClass(); + var matcher = jasmineUnderTest.matchers.toHaveClass({ + pp: jasmineUnderTest.makePrettyPrinter() + }); expect(function() { matcher.compare('x', 'foo'); diff --git a/spec/core/matchers/toThrowErrorSpec.js b/spec/core/matchers/toThrowErrorSpec.js index 148997ce..8070719c 100644 --- a/spec/core/matchers/toThrowErrorSpec.js +++ b/spec/core/matchers/toThrowErrorSpec.js @@ -54,7 +54,9 @@ describe("toThrowError", function() { }); it("fails if thrown is not an instanceof Error", function() { - var matcher = jasmineUnderTest.matchers.toThrowError(), + var matcher = jasmineUnderTest.matchers.toThrowError({ + pp: jasmineUnderTest.makePrettyPrinter() + }), fn = function() { throw 4; }, @@ -103,7 +105,9 @@ describe("toThrowError", function() { }); it("fails with the correct message if thrown is a falsy value", function() { - var matcher = jasmineUnderTest.matchers.toThrowError(), + var matcher = jasmineUnderTest.matchers.toThrowError({ + pp: jasmineUnderTest.makePrettyPrinter() + }), fn = function() { throw undefined; }, @@ -128,7 +132,9 @@ describe("toThrowError", function() { }); it("passes if thrown is an Error and the expected is the same message", function() { - var matcher = jasmineUnderTest.matchers.toThrowError(), + var matcher = jasmineUnderTest.matchers.toThrowError({ + pp: jasmineUnderTest.makePrettyPrinter() + }), fn = function() { throw new Error("foo"); }, @@ -141,7 +147,9 @@ describe("toThrowError", function() { }); it("fails if thrown is an Error and the expected is not the same message", function() { - var matcher = jasmineUnderTest.matchers.toThrowError(), + var matcher = jasmineUnderTest.matchers.toThrowError({ + pp: jasmineUnderTest.makePrettyPrinter() + }), fn = function() { throw new Error("foo"); }, @@ -154,7 +162,9 @@ describe("toThrowError", function() { }); it("passes if thrown is an Error and the expected is a RegExp that matches the message", function() { - var matcher = jasmineUnderTest.matchers.toThrowError(), + var matcher = jasmineUnderTest.matchers.toThrowError({ + pp: jasmineUnderTest.makePrettyPrinter() + }), fn = function() { throw new Error("a long message"); }, @@ -167,7 +177,9 @@ describe("toThrowError", function() { }); it("fails if thrown is an Error and the expected is a RegExp that does not match the message", function() { - var matcher = jasmineUnderTest.matchers.toThrowError(), + var matcher = jasmineUnderTest.matchers.toThrowError({ + pp: jasmineUnderTest.makePrettyPrinter() + }), fn = function() { throw new Error("a long message"); }, @@ -232,9 +244,10 @@ describe("toThrowError", function() { it("passes if thrown is a type of Error and it is equal to the expected Error and message", function() { var util = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(true) + equals: jasmine.createSpy('delegated-equal').and.returnValue(true), + pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(), + matcher = jasmineUnderTest.matchers.toThrowError(util), fn = function() { throw new TypeError("foo"); }, @@ -248,9 +261,10 @@ describe("toThrowError", function() { it("passes if thrown is a custom error that takes arguments and it is equal to the expected custom error and message", function() { var util = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(true) + equals: jasmine.createSpy('delegated-equal').and.returnValue(true), + pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(), + matcher = jasmineUnderTest.matchers.toThrowError(util), CustomError = function CustomError(arg) { this.message = arg.message }, fn = function() { throw new CustomError({message: "foo"}); @@ -267,9 +281,10 @@ describe("toThrowError", function() { it("fails if thrown is a type of Error and the expected is a different Error", function() { var util = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(false) + equals: jasmine.createSpy('delegated-equal').and.returnValue(false), + pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(), + matcher = jasmineUnderTest.matchers.toThrowError(util), fn = function() { throw new TypeError("foo"); }, @@ -283,9 +298,10 @@ describe("toThrowError", function() { it("passes if thrown is a type of Error and has the same type as the expected Error and the message matches the expected message", function() { var util = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(true) + equals: jasmine.createSpy('delegated-equal').and.returnValue(true), + pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(), + matcher = jasmineUnderTest.matchers.toThrowError(util), fn = function() { throw new TypeError("foo"); }, @@ -299,9 +315,10 @@ describe("toThrowError", function() { it("fails if thrown is a type of Error and the expected is a different Error", function() { var util = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(false) + equals: jasmine.createSpy('delegated-equal').and.returnValue(false), + pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(), + matcher = jasmineUnderTest.matchers.toThrowError(util), fn = function() { throw new TypeError("foo"); }, diff --git a/spec/core/matchers/toThrowMatchingSpec.js b/spec/core/matchers/toThrowMatchingSpec.js index 1987b73d..507683b0 100644 --- a/spec/core/matchers/toThrowMatchingSpec.js +++ b/spec/core/matchers/toThrowMatchingSpec.js @@ -32,7 +32,9 @@ describe("toThrowMatching", function() { }); it("fails with the correct message if thrown is a falsy value", function() { - var matcher = jasmineUnderTest.matchers.toThrowMatching(), + var matcher = jasmineUnderTest.matchers.toThrowMatching({ + pp: jasmineUnderTest.makePrettyPrinter() + }), fn = function() { throw undefined; }, @@ -58,8 +60,10 @@ describe("toThrowMatching", function() { }); it("fails if the argument is a function that returns false when called with the error", function() { - var matcher = jasmineUnderTest.matchers.toThrowMatching(), - predicate = function(e) { return e.message === "oh no" }, + var matcher = jasmineUnderTest.matchers.toThrowMatching({ + pp: jasmineUnderTest.makePrettyPrinter() + }), + predicate = function(e) { return e.message === "oh no" }, fn = function() { throw new TypeError("nope"); }, diff --git a/spec/core/matchers/toThrowSpec.js b/spec/core/matchers/toThrowSpec.js index 03e11b38..979ddc1d 100644 --- a/spec/core/matchers/toThrowSpec.js +++ b/spec/core/matchers/toThrowSpec.js @@ -24,7 +24,8 @@ describe("toThrow", function() { it("passes if it throws but there is no expected", function() { var util = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(true) + equals: jasmine.createSpy('delegated-equal').and.returnValue(true), + pp: jasmineUnderTest.makePrettyPrinter() }, matcher = jasmineUnderTest.matchers.toThrow(util), fn = function() { @@ -39,7 +40,9 @@ describe("toThrow", function() { }); it("passes even if what is thrown is falsy", function() { - var matcher = jasmineUnderTest.matchers.toThrow(), + var matcher = jasmineUnderTest.matchers.toThrow({ + pp: jasmineUnderTest.makePrettyPrinter() + }), fn = function() { throw undefined; }, @@ -52,7 +55,8 @@ describe("toThrow", function() { it("passes if what is thrown is equivalent to what is expected", function() { var util = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(true) + equals: jasmine.createSpy('delegated-equal').and.returnValue(true), + pp: jasmineUnderTest.makePrettyPrinter() }, matcher = jasmineUnderTest.matchers.toThrow(util), fn = function() { @@ -68,7 +72,8 @@ describe("toThrow", function() { it("fails if what is thrown is not equivalent to what is expected", function() { var util = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(false) + equals: jasmine.createSpy('delegated-equal').and.returnValue(false), + pp: jasmineUnderTest.makePrettyPrinter() }, matcher = jasmineUnderTest.matchers.toThrow(util), fn = function() { @@ -84,7 +89,8 @@ describe("toThrow", function() { it("fails if what is thrown is not equivalent to undefined", function() { var util = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(false) + equals: jasmine.createSpy('delegated-equal').and.returnValue(false), + pp: jasmineUnderTest.makePrettyPrinter() }, matcher = jasmineUnderTest.matchers.toThrow(util), fn = function() { diff --git a/spec/html/PrettyPrintHtmlSpec.js b/spec/html/PrettyPrintHtmlSpec.js index 77bb2bac..fd364d9e 100644 --- a/spec/html/PrettyPrintHtmlSpec.js +++ b/spec/html/PrettyPrintHtmlSpec.js @@ -1,43 +1,46 @@ -describe('jasmineUnderTest.pp (HTML Dependent)', function() { +describe('PrettyPrinter (HTML Dependent)', function() { it('should stringify non-element HTML nodes properly', function() { var sampleNode = document.createTextNode(''); - expect(jasmineUnderTest.pp(sampleNode)).toEqual('HTMLNode'); - expect(jasmineUnderTest.pp({ foo: sampleNode })).toEqual( - 'Object({ foo: HTMLNode })' - ); + var pp = jasmineUnderTest.makePrettyPrinter(); + expect(pp(sampleNode)).toEqual('HTMLNode'); + expect(pp({ foo: sampleNode })).toEqual('Object({ foo: HTMLNode })'); }); it('should stringify empty HTML elements as their opening tags', function() { var simple = document.createElement('div'); + var pp = jasmineUnderTest.makePrettyPrinter(); simple.className = 'foo'; - expect(jasmineUnderTest.pp(simple)).toEqual('
'); + expect(pp(simple)).toEqual('
'); }); it('should stringify non-empty HTML elements as tags with placeholders', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var nonEmpty = document.createElement('div'); nonEmpty.className = 'foo'; nonEmpty.innerHTML = '

Irrelevant

'; - expect(jasmineUnderTest.pp(nonEmpty)).toEqual('
...
'); + expect(pp(nonEmpty)).toEqual('
...
'); }); it("should print Firefox's wrapped native objects correctly", function() { if (jasmine.getEnv().firefoxVersion) { + var pp = jasmineUnderTest.makePrettyPrinter(); try { new CustomEvent(); } catch (e) { var err = e; } // Different versions of FF produce different error messages. - expect(jasmineUnderTest.pp(err)).toMatch( + expect(pp(err)).toMatch( /Not enough arguments|CustomEvent requires at least 1 argument, but only 0 were passed/ ); } }); it('should stringify HTML element with text and attributes', function() { + var pp = jasmineUnderTest.makePrettyPrinter(); var el = document.createElement('div'); el.setAttribute('things', 'foo'); el.innerHTML = 'foo'; - expect(jasmineUnderTest.pp(el)).toEqual('
...
'); + expect(pp(el)).toEqual('
...
'); }); }); diff --git a/src/core/Env.js b/src/core/Env.js index e3c5f5c6..42ba2756 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -309,12 +309,25 @@ getJasmineRequireObj().Env = function(j$) { return 'suite' + nextSuiteId++; }; + var makePrettyPrinter = function() { + return j$.makePrettyPrinter(); + }; + + var makeMatchersUtil = function() { + var customEqualityTesters = + runnableResources[currentRunnable().id].customEqualityTesters; + return new j$.MatchersUtil({ + customTesters: customEqualityTesters, + pp: makePrettyPrinter() + }); + }; + var expectationFactory = function(actual, spec) { var customEqualityTesters = runnableResources[spec.id].customEqualityTesters; return j$.Expectation.factory({ - util: new j$.MatchersUtil({ customTesters: customEqualityTesters }), + util: makeMatchersUtil(), customEqualityTesters: customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, actual: actual, @@ -327,11 +340,8 @@ getJasmineRequireObj().Env = function(j$) { }; var asyncExpectationFactory = function(actual, spec) { - var customEqualityTesters = - runnableResources[spec.id].customEqualityTesters; - return j$.Expectation.asyncFactory({ - util: new j$.MatchersUtil({ customTesters: customEqualityTesters }), + util: makeMatchersUtil(), customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customAsyncMatchers: runnableResources[spec.id].customAsyncMatchers, actual: actual, @@ -1143,7 +1153,7 @@ getJasmineRequireObj().Env = function(j$) { message += error; } else { // pretty print all kind of objects. This includes arrays. - message += j$.pp(error); + message += makePrettyPrinter()(error); } } diff --git a/src/core/Expectation.js b/src/core/Expectation.js index add788cb..ba079532 100644 --- a/src/core/Expectation.js +++ b/src/core/Expectation.js @@ -141,7 +141,7 @@ getJasmineRequireObj().Expectation = function(j$) { args = args.slice(); args.unshift(true); args.unshift(matcherName); - return util.buildFailureMessage.apply(null, args); + return util.buildFailureMessage.apply(util, args); } function negate(result) { diff --git a/src/core/Expector.js b/src/core/Expector.js index 78db5c1b..18e8784e 100644 --- a/src/core/Expector.js +++ b/src/core/Expector.js @@ -44,7 +44,7 @@ getJasmineRequireObj().Expector = function(j$) { var args = self.args.slice(); args.unshift(false); args.unshift(self.matcherName); - return self.util.buildFailureMessage.apply(null, args); + return self.util.buildFailureMessage.apply(self.util, args); } else if (j$.isFunction_(result.message)) { return result.message(); } else { diff --git a/src/core/PrettyPrinter.js b/src/core/PrettyPrinter.js index e01afc80..14091e5e 100644 --- a/src/core/PrettyPrinter.js +++ b/src/core/PrettyPrinter.js @@ -1,5 +1,5 @@ -getJasmineRequireObj().pp = function(j$) { - function PrettyPrinter() { +getJasmineRequireObj().makePrettyPrinter = function(j$) { + function SinglePrettyPrintRun() { this.ppNestLevel_ = 0; this.seen = []; this.length = 0; @@ -21,7 +21,7 @@ getJasmineRequireObj().pp = function(j$) { } } - PrettyPrinter.prototype.format = function(value) { + SinglePrettyPrintRun.prototype.format = function(value) { this.ppNestLevel_++; try { if (j$.util.isUndefined(value)) { @@ -95,7 +95,7 @@ getJasmineRequireObj().pp = function(j$) { } }; - PrettyPrinter.prototype.iterateObject = function(obj, fn) { + SinglePrettyPrintRun.prototype.iterateObject = function(obj, fn) { var objKeys = keys(obj, j$.isArray_(obj)); var isGetter = function isGetter(prop) {}; @@ -114,15 +114,15 @@ getJasmineRequireObj().pp = function(j$) { return objKeys.length > length; }; - PrettyPrinter.prototype.emitScalar = function(value) { + SinglePrettyPrintRun.prototype.emitScalar = function(value) { this.append(value); }; - PrettyPrinter.prototype.emitString = function(value) { + SinglePrettyPrintRun.prototype.emitString = function(value) { this.append("'" + value + "'"); }; - PrettyPrinter.prototype.emitArray = function(array) { + SinglePrettyPrintRun.prototype.emitArray = function(array) { if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { this.append('Array'); return; @@ -158,7 +158,7 @@ getJasmineRequireObj().pp = function(j$) { this.append(' ]'); }; - PrettyPrinter.prototype.emitSet = function(set) { + SinglePrettyPrintRun.prototype.emitSet = function(set) { if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { this.append('Set'); return; @@ -183,7 +183,7 @@ getJasmineRequireObj().pp = function(j$) { this.append(' )'); }; - PrettyPrinter.prototype.emitMap = function(map) { + SinglePrettyPrintRun.prototype.emitMap = function(map) { if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { this.append('Map'); return; @@ -208,7 +208,7 @@ getJasmineRequireObj().pp = function(j$) { this.append(' )'); }; - PrettyPrinter.prototype.emitObject = function(obj) { + SinglePrettyPrintRun.prototype.emitObject = function(obj) { var ctor = obj.constructor, constructorName; @@ -244,7 +244,7 @@ getJasmineRequireObj().pp = function(j$) { this.append(' })'); }; - PrettyPrinter.prototype.emitTypedArray = function(arr) { + SinglePrettyPrintRun.prototype.emitTypedArray = function(arr) { var constructorName = j$.fnNameFor(arr.constructor), limitedArray = Array.prototype.slice.call( arr, @@ -260,7 +260,7 @@ getJasmineRequireObj().pp = function(j$) { this.append(constructorName + ' [ ' + itemsString + ' ]'); }; - PrettyPrinter.prototype.emitDomElement = function(el) { + SinglePrettyPrintRun.prototype.emitDomElement = function(el) { var tagName = el.tagName.toLowerCase(), attrs = el.attributes, i, @@ -286,7 +286,11 @@ getJasmineRequireObj().pp = function(j$) { this.append(out); }; - PrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) { + SinglePrettyPrintRun.prototype.formatProperty = function( + obj, + property, + isGetter + ) { this.append(property); this.append(': '); if (isGetter) { @@ -296,7 +300,7 @@ getJasmineRequireObj().pp = function(j$) { } }; - PrettyPrinter.prototype.append = function(value) { + SinglePrettyPrintRun.prototype.append = function(value) { // This check protects us from the rare case where an object has overriden // `toString()` with an invalid implementation (returning a non-string). if (typeof value !== 'string') { @@ -360,9 +364,12 @@ getJasmineRequireObj().pp = function(j$) { return extraKeys; } - return function(value) { - var prettyPrinter = new PrettyPrinter(); - prettyPrinter.format(value); - return prettyPrinter.stringParts.join(''); + + return function() { + return function(value) { + var prettyPrinter = new SinglePrettyPrintRun(); + prettyPrinter.format(value); + return prettyPrinter.stringParts.join(''); + }; }; }; diff --git a/src/core/Spy.js b/src/core/Spy.js index 3d4aca21..7160f5d5 100644 --- a/src/core/Spy.js +++ b/src/core/Spy.js @@ -7,7 +7,10 @@ getJasmineRequireObj().Spy = function(j$) { }; })(); - var matchersUtil = new j$.MatchersUtil(); + var matchersUtil = new j$.MatchersUtil({ + customTesters: [], + pp: j$.makePrettyPrinter() + }); /** * _Note:_ Do not construct this directly, use {@link spyOn}, {@link spyOnProperty}, {@link jasmine.createSpy}, or {@link jasmine.createSpyObj} diff --git a/src/core/matchers/async/toBeRejectedWith.js b/src/core/matchers/async/toBeRejectedWith.js index 621daf0b..6c0af904 100644 --- a/src/core/matchers/async/toBeRejectedWith.js +++ b/src/core/matchers/async/toBeRejectedWith.js @@ -11,7 +11,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { * @example * return expectAsync(aPromise).toBeRejectedWith({prop: 'value'}); */ - return function toBeRejectedWith(util) { + return function toBeRejectedWith(matchersUtil) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { @@ -21,7 +21,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { function prefix(passed) { return 'Expected a promise ' + (passed ? 'not ' : '') + - 'to be rejected with ' + j$.pp(expectedValue); + 'to be rejected with ' + matchersUtil.pp(expectedValue); } return actualPromise.then( @@ -32,7 +32,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { }; }, function(actualValue) { - if (util.equals(actualValue, expectedValue)) { + if (matchersUtil.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' @@ -40,7 +40,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { } else { return { pass: false, - message: prefix(false) + ' but it was rejected with ' + j$.pp(actualValue) + '.' + message: prefix(false) + ' but it was rejected with ' + matchersUtil.pp(actualValue) + '.' }; } } diff --git a/src/core/matchers/async/toBeRejectedWithError.js b/src/core/matchers/async/toBeRejectedWithError.js index 8a578a60..203cd863 100644 --- a/src/core/matchers/async/toBeRejectedWithError.js +++ b/src/core/matchers/async/toBeRejectedWithError.js @@ -14,14 +14,14 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) { * await expectAsync(aPromise).toBeRejectedWithError('Error message'); * return expectAsync(aPromise).toBeRejectedWithError(/Error message/); */ - return function toBeRejectedWithError() { + return function toBeRejectedWithError(matchersUtil) { return { compare: function(actualPromise, arg1, arg2) { if (!j$.isPromiseLike(actualPromise)) { throw new Error('Expected toBeRejectedWithError to be called on a promise.'); } - var expected = getExpectedFromArgs(arg1, arg2); + var expected = getExpectedFromArgs(arg1, arg2, matchersUtil); return actualPromise.then( function() { @@ -30,15 +30,15 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) { message: 'Expected a promise to be rejected but it was resolved.' }; }, - function(actualValue) { return matchError(actualValue, expected); } + function(actualValue) { return matchError(actualValue, expected, matchersUtil); } ); } }; }; - function matchError(actual, expected) { + function matchError(actual, expected, matchersUtil) { if (!j$.isError_(actual)) { - return fail(expected, 'rejected with ' + j$.pp(actual)); + return fail(expected, 'rejected with ' + matchersUtil.pp(actual)); } if (!(actual instanceof expected.error)) { @@ -55,7 +55,7 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) { return pass(expected); } - return fail(expected, 'rejected with ' + j$.pp(actual)); + return fail(expected, 'rejected with ' + matchersUtil.pp(actual)); } function pass(expected) { @@ -73,7 +73,7 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) { } - function getExpectedFromArgs(arg1, arg2) { + function getExpectedFromArgs(arg1, arg2, matchersUtil) { var error, message; if (isErrorConstructor(arg1)) { @@ -87,7 +87,7 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) { return { error: error, message: message, - printValue: j$.fnNameFor(error) + (typeof message === 'undefined' ? '' : ': ' + j$.pp(message)) + printValue: j$.fnNameFor(error) + (typeof message === 'undefined' ? '' : ': ' + matchersUtil.pp(message)) }; } diff --git a/src/core/matchers/async/toBeResolvedTo.js b/src/core/matchers/async/toBeResolvedTo.js index a0295c8a..61abb9f2 100644 --- a/src/core/matchers/async/toBeResolvedTo.js +++ b/src/core/matchers/async/toBeResolvedTo.js @@ -11,7 +11,7 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { * @example * return expectAsync(aPromise).toBeResolvedTo({prop: 'value'}); */ - return function toBeResolvedTo(util) { + return function toBeResolvedTo(matchersUtil) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { @@ -21,12 +21,12 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { function prefix(passed) { return 'Expected a promise ' + (passed ? 'not ' : '') + - 'to be resolved to ' + j$.pp(expectedValue); + 'to be resolved to ' + matchersUtil.pp(expectedValue); } return actualPromise.then( function(actualValue) { - if (util.equals(actualValue, expectedValue)) { + if (matchersUtil.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' @@ -34,7 +34,7 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { } else { return { pass: false, - message: prefix(false) + ' but it was resolved to ' + j$.pp(actualValue) + '.' + message: prefix(false) + ' but it was resolved to ' + matchersUtil.pp(actualValue) + '.' }; } }, diff --git a/src/core/matchers/matchersUtil.js b/src/core/matchers/matchersUtil.js index 1aa496f4..8a0eb75c 100644 --- a/src/core/matchers/matchersUtil.js +++ b/src/core/matchers/matchersUtil.js @@ -1,14 +1,11 @@ getJasmineRequireObj().MatchersUtil = function(j$) { - // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? + // TODO: convert all uses of j$.pp to use the injected pp function MatchersUtil(options) { options = options || {}; this.customTesters_ = options.customTesters || []; - - if (!j$.isArray_(this.customTesters_)) { - throw new Error("MatchersUtil requires custom equality testers"); - } - } + this.pp = options.pp || function() {}; + }; MatchersUtil.prototype.contains = function(haystack, needle, customTesters) { if (j$.isSet(haystack)) { @@ -30,6 +27,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { }; MatchersUtil.prototype.buildFailureMessage = function() { + var self = this; var args = Array.prototype.slice.call(arguments, 0), matcherName = args[0], isNot = args[1], @@ -38,7 +36,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); var message = 'Expected ' + - j$.pp(actual) + + self.pp(actual) + (isNot ? ' not ' : ' ') + englishyPredicate; @@ -240,7 +238,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); + diffBuilder.record(a[i], void 0, 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; @@ -357,7 +355,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { a instanceof aCtor && b instanceof bCtor && !(aCtor instanceof aCtor && bCtor instanceof bCtor)) { - diffBuilder.record(a, b, constructorsAreDifferentFormatter); + diffBuilder.record(a, b, constructorsAreDifferentFormatter.bind(null, this.pp)); return false; } } @@ -368,7 +366,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); + diffBuilder.record(a, b, objectKeysAreDifferentFormatter.bind(null, this.pp)); return false; } @@ -376,7 +374,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { key = aKeys[i]; // Deep compare each member if (!j$.util.has(b, key)) { - diffBuilder.record(a, b, objectKeysAreDifferentFormatter); + diffBuilder.record(a, b, objectKeysAreDifferentFormatter.bind(null, this.pp)); result = false; continue; } @@ -433,11 +431,11 @@ getJasmineRequireObj().MatchersUtil = function(j$) { return typeof obj === 'function'; } - function objectKeysAreDifferentFormatter(actual, expected, path) { + function objectKeysAreDifferentFormatter(pp, actual, expected, path) { var missingProperties = j$.util.objectDifference(expected, actual), extraProperties = j$.util.objectDifference(actual, expected), - missingPropertiesMessage = formatKeyValuePairs(missingProperties), - extraPropertiesMessage = formatKeyValuePairs(extraProperties), + missingPropertiesMessage = formatKeyValuePairs(pp, missingProperties), + extraPropertiesMessage = formatKeyValuePairs(pp, extraProperties), messages = []; if (!path.depth()) { @@ -455,7 +453,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { return messages.join('\n'); } - function constructorsAreDifferentFormatter(actual, expected, path) { + function constructorsAreDifferentFormatter(pp, actual, expected, path) { if (!path.depth()) { path = 'object'; } @@ -463,20 +461,20 @@ getJasmineRequireObj().MatchersUtil = function(j$) { return 'Expected ' + path + ' to be a kind of ' + j$.fnNameFor(expected.constructor) + - ', but was ' + j$.pp(actual) + '.'; + ', but was ' + pp(actual) + '.'; } - function actualArrayIsLongerFormatter(actual, expected, path) { + function actualArrayIsLongerFormatter(pp, actual, expected, path) { return 'Unexpected ' + path + (path.depth() ? ' = ' : '') + - j$.pp(actual) + + pp(actual) + ' in array.'; } - function formatKeyValuePairs(obj) { + function formatKeyValuePairs(pp, obj) { var formatted = ''; for (var key in obj) { - formatted += '\n ' + key + ': ' + j$.pp(obj[key]); + formatted += '\n ' + key + ': ' + pp(obj[key]); } return formatted; } diff --git a/src/core/matchers/toBeInstanceOf.js b/src/core/matchers/toBeInstanceOf.js index cc0a0679..609b9301 100644 --- a/src/core/matchers/toBeInstanceOf.js +++ b/src/core/matchers/toBeInstanceOf.js @@ -12,11 +12,11 @@ getJasmineRequireObj().toBeInstanceOf = function(j$) { * expect(3).toBeInstanceOf(Number); * expect(new Error()).toBeInstanceOf(Error); */ - function toBeInstanceOf() { + function toBeInstanceOf(matchersUtil) { return { compare: function(actual, expected) { - var actualType = actual && actual.constructor ? j$.fnNameFor(actual.constructor) : j$.pp(actual), - expectedType = expected ? j$.fnNameFor(expected) : j$.pp(expected), + var actualType = actual && actual.constructor ? j$.fnNameFor(actual.constructor) : matchersUtil.pp(actual), + expectedType = expected ? j$.fnNameFor(expected) : matchersUtil.pp(expected), expectedMatcher, pass; diff --git a/src/core/matchers/toBeNaN.js b/src/core/matchers/toBeNaN.js index dcff00a1..7adf5b34 100644 --- a/src/core/matchers/toBeNaN.js +++ b/src/core/matchers/toBeNaN.js @@ -7,7 +7,7 @@ getJasmineRequireObj().toBeNaN = function(j$) { * @example * expect(thing).toBeNaN(); */ - function toBeNaN() { + function toBeNaN(matchersUtil) { return { compare: function(actual) { var result = { @@ -17,7 +17,7 @@ getJasmineRequireObj().toBeNaN = function(j$) { if (result.pass) { result.message = 'Expected actual not to be NaN.'; } else { - result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; + result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be NaN.'; }; } return result; diff --git a/src/core/matchers/toBeNegativeInfinity.js b/src/core/matchers/toBeNegativeInfinity.js index ce60a62d..fe2de803 100644 --- a/src/core/matchers/toBeNegativeInfinity.js +++ b/src/core/matchers/toBeNegativeInfinity.js @@ -7,7 +7,7 @@ getJasmineRequireObj().toBeNegativeInfinity = function(j$) { * @example * expect(thing).toBeNegativeInfinity(); */ - function toBeNegativeInfinity() { + function toBeNegativeInfinity(matchersUtil) { return { compare: function(actual) { var result = { @@ -17,7 +17,7 @@ getJasmineRequireObj().toBeNegativeInfinity = function(j$) { if (result.pass) { result.message = 'Expected actual not to be -Infinity.'; } else { - result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be -Infinity.'; }; + result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be -Infinity.'; }; } return result; diff --git a/src/core/matchers/toBePositiveInfinity.js b/src/core/matchers/toBePositiveInfinity.js index c1c41f8e..39cb5e34 100644 --- a/src/core/matchers/toBePositiveInfinity.js +++ b/src/core/matchers/toBePositiveInfinity.js @@ -7,7 +7,7 @@ getJasmineRequireObj().toBePositiveInfinity = function(j$) { * @example * expect(thing).toBePositiveInfinity(); */ - function toBePositiveInfinity() { + function toBePositiveInfinity(matchersUtil) { return { compare: function(actual) { var result = { @@ -17,7 +17,7 @@ getJasmineRequireObj().toBePositiveInfinity = function(j$) { if (result.pass) { result.message = 'Expected actual not to be Infinity.'; } else { - result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be Infinity.'; }; + result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be Infinity.'; }; } return result; diff --git a/src/core/matchers/toHaveBeenCalled.js b/src/core/matchers/toHaveBeenCalled.js index bf1df2ed..3e6d2601 100644 --- a/src/core/matchers/toHaveBeenCalled.js +++ b/src/core/matchers/toHaveBeenCalled.js @@ -11,13 +11,13 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) { * expect(mySpy).toHaveBeenCalled(); * expect(mySpy).not.toHaveBeenCalled(); */ - function toHaveBeenCalled() { + function toHaveBeenCalled(matchersUtil) { return { compare: function(actual) { var result = {}; if (!j$.isSpy(actual)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); + throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(actual) + '.')); } if (arguments.length > 1) { diff --git a/src/core/matchers/toHaveBeenCalledBefore.js b/src/core/matchers/toHaveBeenCalledBefore.js index 5c9b7063..293e73ff 100644 --- a/src/core/matchers/toHaveBeenCalledBefore.js +++ b/src/core/matchers/toHaveBeenCalledBefore.js @@ -11,14 +11,14 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { * @example * expect(mySpy).toHaveBeenCalledBefore(otherSpy); */ - function toHaveBeenCalledBefore() { + function toHaveBeenCalledBefore(matchersUtil) { return { compare: function(firstSpy, latterSpy) { if (!j$.isSpy(firstSpy)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(firstSpy) + '.')); + throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(firstSpy) + '.')); } if (!j$.isSpy(latterSpy)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(latterSpy) + '.')); + throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(latterSpy) + '.')); } var result = { pass: false }; diff --git a/src/core/matchers/toHaveBeenCalledTimes.js b/src/core/matchers/toHaveBeenCalledTimes.js index 282f8776..fa3eef3d 100644 --- a/src/core/matchers/toHaveBeenCalledTimes.js +++ b/src/core/matchers/toHaveBeenCalledTimes.js @@ -11,11 +11,11 @@ getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) { * @example * expect(mySpy).toHaveBeenCalledTimes(3); */ - function toHaveBeenCalledTimes() { + function toHaveBeenCalledTimes(matchersUtil) { return { compare: function(actual, expected) { if (!j$.isSpy(actual)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); + throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(actual) + '.')); } var args = Array.prototype.slice.call(arguments, 0), diff --git a/src/core/matchers/toHaveBeenCalledWith.js b/src/core/matchers/toHaveBeenCalledWith.js index 9edde713..76628b55 100644 --- a/src/core/matchers/toHaveBeenCalledWith.js +++ b/src/core/matchers/toHaveBeenCalledWith.js @@ -11,7 +11,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { * @example * expect(mySpy).toHaveBeenCalledWith('foo', 'bar', 2); */ - function toHaveBeenCalledWith(util) { + function toHaveBeenCalledWith(matchersUtil) { return { compare: function() { var args = Array.prototype.slice.call(arguments, 0), @@ -20,40 +20,40 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { result = { pass: false }; if (!j$.isSpy(actual)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); + throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(actual) + '.')); } if (!actual.calls.any()) { result.message = function() { return 'Expected spy ' + actual.and.identity + ' to have been called with:\n' + - ' ' + j$.pp(expectedArgs) + + ' ' + matchersUtil.pp(expectedArgs) + '\nbut it was never called.'; }; return result; } - if (util.contains(actual.calls.allArgs(), expectedArgs)) { + if (matchersUtil.contains(actual.calls.allArgs(), expectedArgs)) { result.pass = true; result.message = function() { return 'Expected spy ' + actual.and.identity + ' not to have been called with:\n' + - ' ' + j$.pp(expectedArgs) + + ' ' + matchersUtil.pp(expectedArgs) + '\nbut it was.'; }; } else { result.message = function() { var prettyPrintedCalls = actual.calls.allArgs().map(function(argsForCall) { - return ' ' + j$.pp(argsForCall); + return ' ' + matchersUtil.pp(argsForCall); }); var diffs = actual.calls.allArgs().map(function(argsForCall, callIx) { var diffBuilder = new j$.DiffBuilder(); - util.equals(argsForCall, expectedArgs, diffBuilder); + matchersUtil.equals(argsForCall, expectedArgs, diffBuilder); return 'Call ' + callIx + ':\n' + diffBuilder.getMessage().replace(/^/mg, ' '); }); return 'Expected spy ' + actual.and.identity + ' to have been called with:\n' + - ' ' + j$.pp(expectedArgs) + '\n' + '' + + ' ' + matchersUtil.pp(expectedArgs) + '\n' + '' + 'but actual calls were:\n' + prettyPrintedCalls.join(',\n') + '.\n\n' + diffs.join('\n'); diff --git a/src/core/matchers/toHaveClass.js b/src/core/matchers/toHaveClass.js index 32dc2ccf..efe17f10 100644 --- a/src/core/matchers/toHaveClass.js +++ b/src/core/matchers/toHaveClass.js @@ -10,11 +10,11 @@ getJasmineRequireObj().toHaveClass = function(j$) { * el.className = 'foo bar baz'; * expect(el).toHaveClass('bar'); */ - function toHaveClass() { + function toHaveClass(matchersUtil) { return { compare: function(actual, expected) { if (!isElement(actual)) { - throw new Error(j$.pp(actual) + ' is not a DOM element'); + throw new Error(matchersUtil.pp(actual) + ' is not a DOM element'); } return { diff --git a/src/core/matchers/toThrow.js b/src/core/matchers/toThrow.js index a7e089d6..a471a5d9 100644 --- a/src/core/matchers/toThrow.js +++ b/src/core/matchers/toThrow.js @@ -12,7 +12,7 @@ getJasmineRequireObj().toThrow = function(j$) { * expect(function() { return 'things'; }).toThrow('foo'); * expect(function() { return 'stuff'; }).toThrow(); */ - function toThrow(util) { + function toThrow(matchersUtil) { return { compare: function(actual, expected) { var result = { pass: false }, @@ -37,16 +37,16 @@ getJasmineRequireObj().toThrow = function(j$) { if (arguments.length == 1) { result.pass = true; - result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; + result.message = function() { return 'Expected function not to throw, but it threw ' + matchersUtil.pp(thrown) + '.'; }; return result; } - if (util.equals(thrown, expected)) { + if (matchersUtil.equals(thrown, expected)) { result.pass = true; - result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; + result.message = function() { return 'Expected function not to throw ' + matchersUtil.pp(expected) + '.'; }; } else { - result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; + result.message = function() { return 'Expected function to throw ' + matchersUtil.pp(expected) + ', but it threw ' + matchersUtil.pp(thrown) + '.'; }; } return result; diff --git a/src/core/matchers/toThrowError.js b/src/core/matchers/toThrowError.js index 11c6d746..00f3e69a 100644 --- a/src/core/matchers/toThrowError.js +++ b/src/core/matchers/toThrowError.js @@ -16,7 +16,7 @@ getJasmineRequireObj().toThrowError = function(j$) { * expect(function() { return 'other'; }).toThrowError(/foo/); * expect(function() { return 'other'; }).toThrowError(); */ - function toThrowError () { + function toThrowError(matchersUtil) { return { compare: function(actual) { var errorMatcher = getMatcher.apply(null, arguments), @@ -34,7 +34,7 @@ getJasmineRequireObj().toThrowError = function(j$) { } if (!j$.isError_(thrown)) { - return fail(function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }); + return fail(function() { return 'Expected function to throw an Error, but it threw ' + matchersUtil.pp(thrown) + '.'; }); } return errorMatcher.match(thrown); @@ -97,7 +97,7 @@ getJasmineRequireObj().toThrowError = function(j$) { thrownMessage = ''; if (expected) { - thrownMessage = ' with message ' + j$.pp(thrown.message); + thrownMessage = ' with message ' + matchersUtil.pp(thrown.message); } return thrownName + thrownMessage; @@ -107,9 +107,9 @@ getJasmineRequireObj().toThrowError = function(j$) { if (expected === null) { return ''; } else if (expected instanceof RegExp) { - return ' with a message matching ' + j$.pp(expected); + return ' with a message matching ' + matchersUtil.pp(expected); } else { - return ' with message ' + j$.pp(expected); + return ' with message ' + matchersUtil.pp(expected); } } diff --git a/src/core/matchers/toThrowMatching.js b/src/core/matchers/toThrowMatching.js index bf9be39e..45e3fc85 100644 --- a/src/core/matchers/toThrowMatching.js +++ b/src/core/matchers/toThrowMatching.js @@ -10,7 +10,7 @@ getJasmineRequireObj().toThrowMatching = function(j$) { * @example * expect(function() { throw new Error('nope'); }).toThrowMatching(function(thrown) { return thrown.message === 'nope'; }); */ - function toThrowMatching() { + function toThrowMatching(matchersUtil) { return { compare: function(actual, predicate) { var thrown; @@ -40,14 +40,14 @@ getJasmineRequireObj().toThrowMatching = function(j$) { } } }; - } - function thrownDescription(thrown) { - if (thrown && thrown.constructor) { - return j$.fnNameFor(thrown.constructor) + ' with message ' + - j$.pp(thrown.message); - } else { - return j$.pp(thrown); + function thrownDescription(thrown) { + if (thrown && thrown.constructor) { + return j$.fnNameFor(thrown.constructor) + ' with message ' + + matchersUtil.pp(thrown.message); + } else { + return matchersUtil.pp(thrown); + } } } diff --git a/src/core/requireCore.js b/src/core/requireCore.js index 0d5b76ed..a21044ff 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -54,15 +54,19 @@ var getJasmineRequireObj = (function(jasmineGlobal) { j$.asymmetricEqualityTesterArgCompatShim = jRequire.asymmetricEqualityTesterArgCompatShim( j$ ); + j$.makePrettyPrinter = jRequire.makePrettyPrinter(j$); + j$.pp = j$.makePrettyPrinter(); j$.MatchersUtil = jRequire.MatchersUtil(j$); - j$.matchersUtil = new j$.MatchersUtil({ customTesters: [] }); + j$.matchersUtil = new j$.MatchersUtil({ + customTesters: [], + pp: j$.pp + }); j$.ObjectContaining = jRequire.ObjectContaining(j$); j$.ArrayContaining = jRequire.ArrayContaining(j$); j$.ArrayWithExactContents = jRequire.ArrayWithExactContents(j$); j$.MapContaining = jRequire.MapContaining(j$); j$.SetContaining = jRequire.SetContaining(j$); - j$.pp = jRequire.pp(j$); j$.QueueRunner = jRequire.QueueRunner(j$); j$.ReportDispatcher = jRequire.ReportDispatcher(j$); j$.Spec = jRequire.Spec(j$); From 25816a6e7749bd64d1bb6382579e81099f3490fd Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Sat, 11 Jan 2020 14:51:12 -0800 Subject: [PATCH 3/8] Added support for custom object formatters Custom object formatters allow users to customize how an object is stringified in matcher failure messages. This can already be done by adding a `jasmineToString` method to the objects in question. But it's not always desirable or possible to do that, particularly when objects of a given "type" do not inherit from a specific prototype. For instance, suppose a web service returns a list of foos that are deserialized from JSON, e.g.: { fooId: 42, /* more properties */ } The only way to define `jasmineToString` on those is by writing code to add it to each instance at runtime. But a custom object formatter can recognize that the object it's looking at is a foo and format it accordingly: jasmine.addCustomObjectFormatter(function(obj) { if (typeof obj.fooId !== 'number') { return undefined; } return '[Foo with ID ' + obj.fooId + ']'; }); Unlike `jasmineToString`, custom object formatters are scoped to a particular spec or suite and don't require any changes to the code under test. --- lib/jasmine-core/jasmine.js | 105 ++++++++--- spec/core/EnvSpec.js | 14 +- spec/core/PrettyPrintSpec.js | 42 +++++ .../ArrayContainingSpec.js | 9 +- .../ArrayWithExactContentsSpec.js | 9 +- .../asymmetric_equality/MapContainingSpec.js | 9 +- .../ObjectContainingSpec.js | 10 +- .../asymmetric_equality/SetContainingSpec.js | 9 +- .../integration/CustomObjectFormatterSpec.js | 67 +++++++ spec/core/integration/MatchersSpec.js | 178 ++++++++++++++++++ spec/core/matchers/DiffBuilderSpec.js | 26 +++ spec/core/matchers/matchersUtilSpec.js | 10 +- spec/core/matchers/toBeSpec.js | 12 ++ spec/core/matchers/toEqualSpec.js | 59 +++++- src/core/Env.js | 19 +- src/core/PrettyPrinter.js | 35 +++- .../asymmetric_equality/ArrayContaining.js | 4 +- .../ArrayWithExactContents.js | 4 +- src/core/asymmetric_equality/MapContaining.js | 4 +- .../asymmetric_equality/ObjectContaining.js | 4 +- src/core/asymmetric_equality/SetContaining.js | 4 +- src/core/matchers/DiffBuilder.js | 13 +- src/core/matchers/matchersUtil.js | 2 +- src/core/matchers/toEqual.js | 2 +- src/core/requireInterface.js | 14 ++ 25 files changed, 591 insertions(+), 73 deletions(-) create mode 100644 spec/core/integration/CustomObjectFormatterSpec.js diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 7c67caa3..9f7e3106 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -1217,6 +1217,18 @@ getJasmineRequireObj().Env = function(j$) { } }; + this.addCustomObjectFormatter = function(formatter) { + if (!currentRunnable()) { + throw new Error( + 'Custom object formatters must be added in a before function or a spec' + ); + } + + runnableResources[currentRunnable().id].customObjectFormatters.push( + formatter + ); + }; + j$.Expectation.addCoreMatchers(j$.matchers); j$.Expectation.addAsyncCoreMatchers(j$.asyncMatchers); @@ -1231,7 +1243,9 @@ getJasmineRequireObj().Env = function(j$) { }; var makePrettyPrinter = function() { - return j$.makePrettyPrinter(); + var customObjectFormatters = + runnableResources[currentRunnable().id].customObjectFormatters; + return j$.makePrettyPrinter(customObjectFormatters); }; var makeMatchersUtil = function() { @@ -1281,7 +1295,8 @@ getJasmineRequireObj().Env = function(j$) { customMatchers: {}, customAsyncMatchers: {}, customSpyStrategies: {}, - defaultStrategyFn: undefined + defaultStrategyFn: undefined, + customObjectFormatters: [] }; if (runnableResources[parentRunnableId]) { @@ -2318,8 +2333,8 @@ getJasmineRequireObj().ArrayContaining = function(j$) { return true; }; - ArrayContaining.prototype.jasmineToString = function () { - return ''; + ArrayContaining.prototype.jasmineToString = function (pp) { + return ''; }; return ArrayContaining; @@ -2350,8 +2365,8 @@ getJasmineRequireObj().ArrayWithExactContents = function(j$) { return true; }; - ArrayWithExactContents.prototype.jasmineToString = function() { - return ''; + ArrayWithExactContents.prototype.jasmineToString = function(pp) { + return ''; }; return ArrayWithExactContents; @@ -2433,8 +2448,8 @@ getJasmineRequireObj().MapContaining = function(j$) { return hasAllMatches; }; - MapContaining.prototype.jasmineToString = function() { - return ''; + MapContaining.prototype.jasmineToString = function(pp) { + return ''; }; return MapContaining; @@ -2510,8 +2525,8 @@ getJasmineRequireObj().ObjectContaining = function(j$) { return true; }; - ObjectContaining.prototype.jasmineToString = function() { - return ''; + ObjectContaining.prototype.jasmineToString = function(pp) { + return ''; }; return ObjectContaining; @@ -2550,8 +2565,8 @@ getJasmineRequireObj().SetContaining = function(j$) { return hasAllMatches; }; - SetContaining.prototype.jasmineToString = function() { - return ''; + SetContaining.prototype.jasmineToString = function(pp) { + return ''; }; return SetContaining; @@ -4173,14 +4188,15 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { }; getJasmineRequireObj().DiffBuilder = function(j$) { - return function DiffBuilder() { + return function DiffBuilder(config) { var path = new j$.ObjectPath(), - mismatches = []; + mismatches = [], + prettyPrinter = (config || {}).prettyPrinter || j$.makePrettyPrinter(); return { record: function (actual, expected, formatter) { formatter = formatter || defaultFormatter; - mismatches.push(formatter(actual, expected, path)); + mismatches.push(formatter(actual, expected, path, prettyPrinter)); }, getMessage: function () { @@ -4195,12 +4211,12 @@ getJasmineRequireObj().DiffBuilder = function(j$) { } }; - function defaultFormatter (actual, expected, path) { + function defaultFormatter (actual, expected, path, prettyPrinter) { return 'Expected ' + path + (path.depth() ? ' = ' : '') + - j$.pp(actual) + + prettyPrinter(actual) + ' to equal ' + - j$.pp(expected) + + prettyPrinter(expected) + '.'; } }; @@ -4253,7 +4269,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { if (i > 0) { message += ','; } - message += ' ' + j$.pp(expected[i]); + message += ' ' + self.pp(expected[i]); } } @@ -5289,7 +5305,7 @@ getJasmineRequireObj().toEqual = function(j$) { var result = { pass: false }, - diffBuilder = j$.DiffBuilder(); + diffBuilder = j$.DiffBuilder({prettyPrinter: util.pp}); result.pass = util.equals(actual, expected, diffBuilder); @@ -5990,11 +6006,13 @@ getJasmineRequireObj().MockDate = function() { }; getJasmineRequireObj().makePrettyPrinter = function(j$) { - function SinglePrettyPrintRun() { + function SinglePrettyPrintRun(customObjectFormatters, pp) { + this.customObjectFormatters_ = customObjectFormatters; this.ppNestLevel_ = 0; this.seen = []; this.length = 0; this.stringParts = []; + this.pp_ = pp; } function hasCustomToString(value) { @@ -6015,7 +6033,11 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) { SinglePrettyPrintRun.prototype.format = function(value) { this.ppNestLevel_++; try { - if (j$.util.isUndefined(value)) { + var customFormatResult = this.applyCustomFormatters_(value); + + if (customFormatResult) { + this.emitScalar(customFormatResult); + } else if (j$.util.isUndefined(value)) { this.emitScalar('undefined'); } else if (value === null) { this.emitScalar('null'); @@ -6024,7 +6046,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) { } else if (value === j$.getGlobal()) { this.emitScalar(''); } else if (value.jasmineToString) { - this.emitScalar(value.jasmineToString()); + this.emitScalar(value.jasmineToString(this.pp_)); } else if (typeof value === 'string') { this.emitString(value); } else if (j$.isSpy(value)) { @@ -6086,6 +6108,18 @@ 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; + } + } + }; + SinglePrettyPrintRun.prototype.iterateObject = function(obj, fn) { var objKeys = keys(obj, j$.isArray_(obj)); var isGetter = function isGetter(prop) {}; @@ -6356,12 +6390,17 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) { return extraKeys; } - return function() { - return function(value) { - var prettyPrinter = new SinglePrettyPrintRun(); + return function(customObjectFormatters) { + var pp = function(value) { + var prettyPrinter = new SinglePrettyPrintRun( + customObjectFormatters || [], + pp + ); prettyPrinter.format(value); return prettyPrinter.stringParts.join(''); }; + + return pp; }; }; @@ -6972,6 +7011,20 @@ getJasmineRequireObj().interface = function(jasmine, env) { return env.addAsyncMatchers(matchers); }; + /** + * Add a custom object formatter for the current scope of specs. + * + * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. + * @name jasmine.addCustomObjectFormatter + * @since 3.6.0 + * @function + * @param {Function} formatter - A function which takes a value to format and returns a string if it knows how to format it, and `undefined` otherwise. + * @see custom_object_formatter + */ + jasmine.addCustomObjectFormatter = function(formatter) { + return env.addCustomObjectFormatter(formatter); + }; + /** * Get the currently booted mock {Clock} for this Jasmine environment. * @name jasmine.clock diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index 80dcffbb..9349a78d 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -305,8 +305,9 @@ describe('Env', function() { }); }); - it('creates an expectationFactory that uses the current custom equality testers', function(done) { + it('creates an expectationFactory that uses the current custom equality testers and object formatters', function(done) { function customEqualityTester() {} + function customObjectFormatter() {} function prettyPrinter() {} var RealSpec = jasmineUnderTest.Spec, specInstance, @@ -321,11 +322,15 @@ describe('Env', function() { env.it('spec', function() { env.addCustomEqualityTester(customEqualityTester); + env.addCustomObjectFormatter(customObjectFormatter); expectationFactory('actual', specInstance); }); env.addReporter({ jasmineDone: function() { + expect(jasmineUnderTest.makePrettyPrinter).toHaveBeenCalledWith([ + customObjectFormatter + ]); expect(jasmineUnderTest.MatchersUtil).toHaveBeenCalledWith({ customTesters: [customEqualityTester], pp: prettyPrinter @@ -337,8 +342,9 @@ describe('Env', function() { env.execute(); }); - it('creates an asyncExpectationFactory that uses the current custom equality testers', function(done) { + it('creates an asyncExpectationFactory that uses the current custom equality testers and object formatters', function(done) { function customEqualityTester() {} + function customObjectFormatter() {} function prettyPrinter() {} var RealSpec = jasmineUnderTest.Spec, specInstance, @@ -353,11 +359,15 @@ describe('Env', function() { env.it('spec', function() { env.addCustomEqualityTester(customEqualityTester); + env.addCustomObjectFormatter(customObjectFormatter); asyncExpectationFactory('actual', specInstance); }); env.addReporter({ jasmineDone: function() { + expect(jasmineUnderTest.makePrettyPrinter).toHaveBeenCalledWith([ + customObjectFormatter + ]); expect(jasmineUnderTest.MatchersUtil).toHaveBeenCalledWith({ customTesters: [customEqualityTester], pp: prettyPrinter diff --git a/spec/core/PrettyPrintSpec.js b/spec/core/PrettyPrintSpec.js index 46f08d6a..4aafefcd 100644 --- a/spec/core/PrettyPrintSpec.js +++ b/spec/core/PrettyPrintSpec.js @@ -388,6 +388,16 @@ describe('PrettyPrinter', function() { expect(pp(obj)).toEqual('strung'); }); + it('should pass itself to jasmineToString', function() { + var pp = jasmineUnderTest.makePrettyPrinter([]); + var obj = { + jasmineToString: jasmine.createSpy('jasmineToString').and.returnValue('') + }; + + pp(obj); + expect(obj.jasmineToString).toHaveBeenCalledWith(pp); + }); + it('should stringify objects that implement custom toString', function() { var pp = jasmineUnderTest.makePrettyPrinter(); var obj = { @@ -478,4 +488,36 @@ describe('PrettyPrinter', function() { 'Object({ foo: [object Number], bar: [object Object], baz: 3, qux: Error: bar, baddy: has-invalid-toString-method })' ); }); + + describe('Custom object formatters', 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(obj, customObjectFormatters)).toEqual('2nd: bar'); + }); + + it('should fall back to built in logic if all custom object formatters return undefined', function() { + var customObjectFormatters = [ + function(obj) { + return undefined; + } + ], + pp = jasmineUnderTest.makePrettyPrinter(customObjectFormatters), + obj = { foo: 'bar' }; + + expect(pp(obj, customObjectFormatters)).toEqual("Object({ foo: 'bar' })"); + }); + }); }); diff --git a/spec/core/asymmetric_equality/ArrayContainingSpec.js b/spec/core/asymmetric_equality/ArrayContainingSpec.js index 4cbfa66d..33e411e6 100644 --- a/spec/core/asymmetric_equality/ArrayContainingSpec.js +++ b/spec/core/asymmetric_equality/ArrayContainingSpec.js @@ -42,9 +42,14 @@ describe("ArrayContaining", function() { }); it("jasmineToStrings itself", function() { - var containing = new jasmineUnderTest.ArrayContaining([]); + var sample = [], + matcher = new jasmineUnderTest.ArrayContaining(sample), + pp = jasmine.createSpy('pp').and.returnValue('sample'); - expect(containing.jasmineToString()).toMatch("' + ); + expect(pp).toHaveBeenCalledWith(sample); }); it("uses custom equality testers", function() { diff --git a/spec/core/asymmetric_equality/ArrayWithExactContentsSpec.js b/spec/core/asymmetric_equality/ArrayWithExactContentsSpec.js index 5f8795d6..fd0bb30c 100644 --- a/spec/core/asymmetric_equality/ArrayWithExactContentsSpec.js +++ b/spec/core/asymmetric_equality/ArrayWithExactContentsSpec.js @@ -30,9 +30,14 @@ describe("ArrayWithExactContents", function() { }); it("jasmineToStrings itself", function() { - var matcher = new jasmineUnderTest.ArrayWithExactContents([]); + var sample = [], + matcher = new jasmineUnderTest.ArrayWithExactContents(sample), + pp = jasmine.createSpy('pp').and.returnValue('sample'); - expect(matcher.jasmineToString()).toMatch("' + ); + expect(pp).toHaveBeenCalledWith(sample); }); it("uses custom equality testers", function() { diff --git a/spec/core/asymmetric_equality/MapContainingSpec.js b/spec/core/asymmetric_equality/MapContainingSpec.js index f264e693..c9ea83a6 100644 --- a/spec/core/asymmetric_equality/MapContainingSpec.js +++ b/spec/core/asymmetric_equality/MapContainingSpec.js @@ -184,8 +184,13 @@ describe('MapContaining', function() { }); it('defines a `jasmineToString` method', function() { - var containing = new jasmineUnderTest.MapContaining(new Map()); + var sample = new Map(), + containing = new jasmineUnderTest.MapContaining(sample), + pp = jasmine.createSpy('pp').and.returnValue('sample'); - expect(containing.jasmineToString()).toMatch(/^' + ); + expect(pp).toHaveBeenCalledWith(sample); }); }); diff --git a/spec/core/asymmetric_equality/ObjectContainingSpec.js b/spec/core/asymmetric_equality/ObjectContainingSpec.js index 13fa0a90..4fc16d6b 100644 --- a/spec/core/asymmetric_equality/ObjectContainingSpec.js +++ b/spec/core/asymmetric_equality/ObjectContainingSpec.js @@ -37,9 +37,15 @@ describe("ObjectContaining", function() { }); it("jasmineToString's itself", function() { - var containing = new jasmineUnderTest.ObjectContaining({}); + var sample = {}, + matcher = new jasmineUnderTest.ObjectContaining(sample), + pp = jasmine.createSpy('pp').and.returnValue('sample'); + + expect(matcher.jasmineToString(pp)).toEqual( + '' + ); + expect(pp).toHaveBeenCalledWith(sample); - expect(containing.jasmineToString()).toMatch("' + ); + expect(pp).toHaveBeenCalledWith(sample); }); }); diff --git a/spec/core/integration/CustomObjectFormatterSpec.js b/spec/core/integration/CustomObjectFormatterSpec.js new file mode 100644 index 00000000..10ed4d71 --- /dev/null +++ b/spec/core/integration/CustomObjectFormatterSpec.js @@ -0,0 +1,67 @@ +describe("Custom object formatters", function() { + var env; + + beforeEach(function() { + env = new jasmineUnderTest.Env(); + env.configure({random: false}); + }); + + it("scopes custom object formatters to a spec", function(done) { + env.it('a spec with custom pretty-printer', function() { + env.addCustomObjectFormatter(function(obj) { return 'custom(' + obj + ')'; }); + env.expect(42).toBeUndefined(); + }); + + env.it('a spec without custom pretty-printer', function() { + env.expect(42).toBeUndefined(); + }); + + var specResults = []; + var specDone = function(result) { + specResults.push(result); + }; + var expectations = function() { + expect(specResults[0].failedExpectations[0].message).toEqual("Expected custom(42) to be undefined."); + expect(specResults[1].failedExpectations[0].message).toEqual("Expected 42 to be undefined."); + done(); + }; + env.addReporter({ specDone:specDone, jasmineDone: expectations}); + + env.execute(); + }); + + it("scopes custom object formatters to a suite", function(done) { + env.it('a spec without custom pretty-printer', function() { + env.expect(42).toBeUndefined(); + }); + + env.describe('with custom pretty-printer', function() { + env.beforeEach(function() { + env.addCustomObjectFormatter(function(obj) { return 'custom(' + obj + ')'; }); + }); + + env.it('a spec', function() { + env.expect(42).toBeUndefined(); + }); + }); + + var specResults = []; + var specDone = function(result) { + specResults.push(result); + }; + var expectations = function() { + expect(specResults[0].failedExpectations[0].message).toEqual("Expected 42 to be undefined."); + expect(specResults[1].failedExpectations[0].message).toEqual("Expected custom(42) to be undefined."); + done(); + }; + env.addReporter({ specDone:specDone, jasmineDone: expectations}); + + env.execute(); + }); + + it("throws an exception if you try to add a custom object formatter outside a runable", function() { + expect(function() { + env.addCustomObjectFormatter(function() {}); + }).toThrowError('Custom object formatters must be added in a before function or a spec') + }); +}); diff --git a/spec/core/integration/MatchersSpec.js b/spec/core/integration/MatchersSpec.js index b21208d5..337f9eb2 100644 --- a/spec/core/integration/MatchersSpec.js +++ b/spec/core/integration/MatchersSpec.js @@ -48,6 +48,28 @@ describe('Matchers (Integration)', function() { }); } + function verifyFailsWithCustomObjectFormatters(config) { + it('uses custom object formatters', function(done) { + var env = new jasmineUnderTest.Env(); + env.it('a spec', function () { + env.addCustomObjectFormatter(config.formatter); + config.expectations(env); + }); + + var specExpectations = function (result) { + expect(result.status).toEqual('failed'); + expect(result.failedExpectations.length) + .withContext('Number of failed expectations') + .toEqual(1); + expect(result.failedExpectations[0].message) + .toEqual(config.expectedMessage); + }; + + env.addReporter({specDone: specExpectations, jasmineDone: done}); + env.execute(); + }); + } + function verifyPassesAsync(expectations) { it('passes', function(done) { jasmine.getEnv().requirePromises(); @@ -101,6 +123,30 @@ describe('Matchers (Integration)', function() { }); } + function verifyFailsWithCustomObjectFormattersAsync(config) { + it('uses custom object formatters', function(done) { + var env = new jasmineUnderTest.Env(); + jasmine.getEnv().requirePromises(); + env.it('a spec', function () { + env.addCustomObjectFormatter(config.formatter); + return config.expectations(env); + }); + + var specExpectations = function (result) { + expect(result.status).toEqual('failed'); + expect(result.failedExpectations.length) + .withContext('Number of failed expectations') + .toEqual(1); + expect(result.failedExpectations[0].message) + .toEqual(config.expectedMessage); + }; + + env.addReporter({specDone: specExpectations, jasmineDone: done}); + env.execute(); + }); + } + + describe('nothing', function() { verifyPasses(function(env) { env.expect().nothing(); @@ -217,6 +263,16 @@ describe('Matchers (Integration)', function() { verifyFails(function(env) { env.expect(2).toBeNaN(); }); + + verifyFailsWithCustomObjectFormatters({ + formatter: function(val) { + return '|' + val + '|'; + }, + expectations: function(env) { + env.expect(1).toBeNaN(); + }, + expectedMessage: 'Expected |1| to be NaN.' + }); }); describe('toBeNegativeInfinity', function() { @@ -227,6 +283,16 @@ describe('Matchers (Integration)', function() { verifyFails(function(env) { env.expect(2).toBeNegativeInfinity(); }); + + verifyFailsWithCustomObjectFormatters({ + formatter: function(val) { + return '|' + val + '|'; + }, + expectations: function(env) { + env.expect(1).toBeNegativeInfinity(); + }, + expectedMessage: 'Expected |1| to be -Infinity.' + }); }); describe('toBeNull', function() { @@ -247,6 +313,16 @@ describe('Matchers (Integration)', function() { verifyFails(function(env) { env.expect(2).toBePositiveInfinity(); }); + + verifyFailsWithCustomObjectFormatters({ + formatter: function(val) { + return '|' + val + '|'; + }, + expectations: function(env) { + env.expect(1).toBePositiveInfinity(); + }, + expectedMessage: 'Expected |1| to be Infinity.' + }) }); describe('toBeResolved', function() { @@ -270,6 +346,17 @@ describe('Matchers (Integration)', function() { verifyFailsAsync(function(env) { return env.expectAsync(Promise.resolve('foo')).toBeResolvedTo('bar'); }); + + verifyFailsWithCustomObjectFormattersAsync({ + formatter: function(val) { + return '|' + val + '|'; + }, + expectations: function(env) { + return env.expectAsync(Promise.resolve('x')).toBeResolvedTo('y'); + }, + expectedMessage: 'Expected a promise to be resolved to |y| ' + + 'but it was resolved to |x|.' + }); }); describe('toBeRejected', function() { @@ -293,6 +380,17 @@ describe('Matchers (Integration)', function() { verifyFailsAsync(function(env) { return env.expectAsync(Promise.resolve()).toBeRejectedWith('nope'); }); + + verifyFailsWithCustomObjectFormattersAsync({ + formatter: function(val) { + return '|' + val + '|'; + }, + expectations: function(env) { + return env.expectAsync(Promise.reject('x')).toBeRejectedWith('y'); + }, + expectedMessage: 'Expected a promise to be rejected with |y| ' + + 'but it was rejected with |x|.' + }); }); describe('toBeRejectedWithError', function() { @@ -303,6 +401,17 @@ describe('Matchers (Integration)', function() { verifyFailsAsync(function(env) { return env.expectAsync(Promise.resolve()).toBeRejectedWithError(Error); }); + + verifyFailsWithCustomObjectFormattersAsync({ + formatter: function(val) { + return '|' + val + '|'; + }, + expectations: function(env) { + return env.expectAsync(Promise.reject('foo')).toBeRejectedWithError('foo'); + }, + expectedMessage: 'Expected a promise to be rejected with Error: |foo| ' + + 'but it was rejected with |foo|.' + }); }); describe('toBeTrue', function() { @@ -359,6 +468,20 @@ describe('Matchers (Integration)', function() { verifyFails(function(env) { env.expect('a').toEqual('b'); }); + + verifyFailsWithCustomObjectFormatters({ + formatter: function(val) { + if (val === 5) { + return "five" + } else if (val === 4) { + return "four" + } + }, + expectations: function(env) { + env.expect([{foo: 4}]).toEqual([{foo: 5}]); + }, + expectedMessage: 'Expected $[0].foo = four to equal five.' + }); }); describe('toHaveBeenCalled', function() { @@ -417,6 +540,19 @@ describe('Matchers (Integration)', function() { var spy = env.createSpy(); env.expect(spy).toHaveBeenCalledWith('foo'); }); + + verifyFailsWithCustomObjectFormatters({ + formatter: function(val) { + return '|' + val + '|'; + }, + expectations: function(env) { + var spy = env.createSpy('foo'); + env.expect(spy).toHaveBeenCalledWith('x'); + }, + expectedMessage: 'Expected spy foo to have been called with:\n' + + ' |x|\n' + + 'but it was never called.' + }); }); describe('toHaveClass', function() { @@ -458,6 +594,19 @@ describe('Matchers (Integration)', function() { verifyFails(function(env) { env.expect(function() {}).toThrow(); }); + + verifyFailsWithCustomObjectFormatters({ + formatter: function(val) { + return '|' + val + '|'; + }, + expectations: function(env) { + var spy = env.createSpy('foo'); + env.expect(function() { + throw 'x' + }).not.toThrow(); + }, + expectedMessage: 'Expected function not to throw, but it threw |x|.' + }); }); describe('toThrowError', function() { @@ -468,6 +617,19 @@ describe('Matchers (Integration)', function() { verifyFails(function(env) { env.expect(function() { }).toThrowError(); }); + + verifyFailsWithCustomObjectFormatters({ + formatter: function(val) { + return '|' + val + '|'; + }, + expectations: function(env) { + var spy = env.createSpy('foo'); + env.expect(function() { + throw 'x' + }).toThrowError(); + }, + expectedMessage: 'Expected function to throw an Error, but it threw |x|.' + }); }); describe('toThrowMatching', function() { @@ -482,5 +644,21 @@ describe('Matchers (Integration)', function() { verifyFails(function(env) { env.expect(throws).toThrowMatching(function() { return false; }); }); + + verifyFailsWithCustomObjectFormatters({ + formatter: function(val) { + return '|' + val + '|'; + }, + expectations: function(env) { + var spy = env.createSpy('foo'); + env.expect(function() { + throw new Error('nope') + }).toThrowMatching(function() { + return false; + }); + }, + expectedMessage: 'Expected function to throw an exception matching ' + + 'a predicate, but it threw Error with message |nope|.' + }); }); }); diff --git a/spec/core/matchers/DiffBuilderSpec.js b/spec/core/matchers/DiffBuilderSpec.js index 2fe8f1bf..3b68207d 100644 --- a/spec/core/matchers/DiffBuilderSpec.js +++ b/spec/core/matchers/DiffBuilderSpec.js @@ -44,4 +44,30 @@ describe("DiffBuilder", function() { 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) { + return '|' + val + '|'; + }, + diffBuilder = jasmineUnderTest.DiffBuilder({prettyPrinter: prettyPrinter}); + + diffBuilder.withPath('foo', function() { + diffBuilder.record('actual', 'expected'); + }); + + expect(diffBuilder.getMessage()).toEqual("Expected $.foo = |actual| to equal |expected|."); + }); + + + it("passes the injected pretty-printer to the diff formatter", function() { + var diffFormatter = jasmine.createSpy('diffFormatter'), + prettyPrinter = function() {}, + diffBuilder = jasmineUnderTest.DiffBuilder({prettyPrinter: prettyPrinter}); + + diffBuilder.withPath('x', function() { + diffBuilder.record('bar', 'foo', diffFormatter); + }); + + expect(diffFormatter).toHaveBeenCalledWith('bar', 'foo', jasmine.anything(), prettyPrinter); + }); }); diff --git a/spec/core/matchers/matchersUtilSpec.js b/spec/core/matchers/matchersUtilSpec.js index ff9b159d..1c0903ba 100644 --- a/spec/core/matchers/matchersUtilSpec.js +++ b/spec/core/matchers/matchersUtilSpec.js @@ -840,7 +840,7 @@ describe("matchersUtil", function() { }); }); - describe("buildMessage", function() { + describe("buildFailureMessage", function() { it("builds an English sentence for a failure case", function() { var actual = "foo", @@ -873,15 +873,17 @@ describe("matchersUtil", function() { expect(message).toEqual("Expected 'foo' to bar 'quux', 'corge'."); }); - it("uses the injected pretty-printer to format the expected", function() { + it("uses the injected pretty-printer to format the expecteds and actual", function() { var actual = "foo", + expected1 = "qux", + expected2 = "grault", name = "toBar", isNot = false, pp = function(value) { return '<' + value + '>'; }, matchersUtil = new jasmineUnderTest.MatchersUtil({pp: pp}), - message = message = matchersUtil.buildFailureMessage(name, isNot, actual); + message = message = matchersUtil.buildFailureMessage(name, isNot, actual, expected1, expected2); - expect(message).toEqual("Expected to bar."); + expect(message).toEqual("Expected to bar , ."); }); }); }); diff --git a/spec/core/matchers/toBeSpec.js b/spec/core/matchers/toBeSpec.js index d95f2b49..49484f5f 100644 --- a/spec/core/matchers/toBeSpec.js +++ b/spec/core/matchers/toBeSpec.js @@ -59,4 +59,16 @@ describe("toBe", function() { expect(result.pass).toBe(false); expect(result.message).toBe("Expected Object({ foo: 'bar' }) to be Object({ foo: 'bar' }). Tip: To check for deep equality, use .toEqual() instead of .toBe().") }); + + it("works with custom object formatters when expected is an object", function() { + var formatter = function(x) { return '<' + x.foo + '>'; }, + prettyPrinter = jasmineUnderTest.makePrettyPrinter([formatter]), + util = new jasmineUnderTest.MatchersUtil({pp: prettyPrinter}), + matcher = jasmineUnderTest.matchers.toBe(util), + result; + + result = matcher.compare({foo: "bar"}, {foo: "bar"}); + expect(result.pass).toBe(false); + expect(result.message).toBe("Expected to be . Tip: To check for deep equality, use .toEqual() instead of .toBe().") + }); }); diff --git a/spec/core/matchers/toEqualSpec.js b/spec/core/matchers/toEqualSpec.js index 5702f7bb..5bfc7715 100644 --- a/spec/core/matchers/toEqualSpec.js +++ b/spec/core/matchers/toEqualSpec.js @@ -97,14 +97,34 @@ describe("toEqual", function() { expect(compareEquals(actual, expected).message).toEqual(message); }); - it("reports extra and missing properties together", function() { + it("uses custom object formatters to pretty-print properties", function() { + function formatter(x) { return '|' + x + '|'; } + var actual = {x: {y: 1, z: 2, f: 4}}, expected = {x: {y: 1, z: 2, g: 3}}, + pp = jasmineUnderTest.makePrettyPrinter([formatter]), + util = new jasmineUnderTest.MatchersUtil({pp: pp}), + matcher = jasmineUnderTest.matchers.toEqual(util), message = "Expected $.x to have properties\n" + - " g: 3\n" + + " g: |3|\n" + "Expected $.x not to have properties\n" + - " f: 4"; + " f: |4|"; + + expect(matcher.compare(actual, expected).message).toEqual(message); + }); + + it("uses custom object formatters to build diffs", function() { + function formatter(x) { return '|' + x + '|'; } + + var actual = [{foo: 4}], + expected = [{foo: 5}], + prettyPrinter = jasmineUnderTest.makePrettyPrinter([formatter]), + util = new jasmineUnderTest.MatchersUtil({pp: prettyPrinter}), + matcher = jasmineUnderTest.matchers.toEqual(util), + message = "Expected $[0].foo = |4| to equal |5|."; + + expect(matcher.compare(actual, expected).message).toEqual(message); }); it("reports extra and missing properties of the root-level object", function() { @@ -277,12 +297,27 @@ describe("toEqual", function() { function Bar() {} var actual = {x: new Foo()}, - expected = {x: new Bar()}, - message = "Expected $.x to be a kind of Bar, but was Foo({ })."; + expected = {x: new Bar()}, + message = "Expected $.x to be a kind of Bar, but was Foo({ })."; expect(compareEquals(actual, expected).message).toEqual(message); }); + it("uses custom object formatters to report objects with different constructors", function () { + function Foo() {} + function Bar() {} + function formatter(x) { return '|' + x + '|'; } + + var actual = {x: new Foo()}, + expected = {x: new Bar()}, + message = "Expected $.x to be a kind of Bar, but was |[object Object]|.", + pp = jasmineUnderTest.makePrettyPrinter([formatter]), + util = new jasmineUnderTest.MatchersUtil({pp: pp}), + matcher = jasmineUnderTest.matchers.toEqual(util); + + expect(matcher.compare(actual, expected).message).toEqual(message); + }); + it("reports type mismatches at the root level", function () { function Foo() {} function Bar() {} @@ -803,6 +838,20 @@ describe("toEqual", function() { expect(compareEquals(actual, expected).message).toEqual(message); }); + it("uses custom object formatters when the actual array is longer", function() { + function formatter(x) { return '|' + x + '|'; } + + var actual = [1, 1, 2, 3, 5], + expected = [1, 1, 2, 3], + pp = jasmineUnderTest.makePrettyPrinter([formatter]), + util = new jasmineUnderTest.MatchersUtil({pp: pp}), + matcher = jasmineUnderTest.matchers.toEqual(util), + message = 'Expected $.length = |5| to equal |4|.\n' + + 'Unexpected $[4] = |5| in array.'; + + expect(matcher.compare(actual, expected).message).toEqual(message); + }); + it("expected array is longer", function() { var actual = [1, 1, 2, 3], expected = [1, 1, 2, 3, 5], diff --git a/src/core/Env.js b/src/core/Env.js index 42ba2756..62510708 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -296,6 +296,18 @@ getJasmineRequireObj().Env = function(j$) { } }; + this.addCustomObjectFormatter = function(formatter) { + if (!currentRunnable()) { + throw new Error( + 'Custom object formatters must be added in a before function or a spec' + ); + } + + runnableResources[currentRunnable().id].customObjectFormatters.push( + formatter + ); + }; + j$.Expectation.addCoreMatchers(j$.matchers); j$.Expectation.addAsyncCoreMatchers(j$.asyncMatchers); @@ -310,7 +322,9 @@ getJasmineRequireObj().Env = function(j$) { }; var makePrettyPrinter = function() { - return j$.makePrettyPrinter(); + var customObjectFormatters = + runnableResources[currentRunnable().id].customObjectFormatters; + return j$.makePrettyPrinter(customObjectFormatters); }; var makeMatchersUtil = function() { @@ -360,7 +374,8 @@ getJasmineRequireObj().Env = function(j$) { customMatchers: {}, customAsyncMatchers: {}, customSpyStrategies: {}, - defaultStrategyFn: undefined + defaultStrategyFn: undefined, + customObjectFormatters: [] }; if (runnableResources[parentRunnableId]) { diff --git a/src/core/PrettyPrinter.js b/src/core/PrettyPrinter.js index 14091e5e..a15177ce 100644 --- a/src/core/PrettyPrinter.js +++ b/src/core/PrettyPrinter.js @@ -1,9 +1,11 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) { - function SinglePrettyPrintRun() { + function SinglePrettyPrintRun(customObjectFormatters, pp) { + this.customObjectFormatters_ = customObjectFormatters; this.ppNestLevel_ = 0; this.seen = []; this.length = 0; this.stringParts = []; + this.pp_ = pp; } function hasCustomToString(value) { @@ -24,7 +26,11 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) { SinglePrettyPrintRun.prototype.format = function(value) { this.ppNestLevel_++; try { - if (j$.util.isUndefined(value)) { + var customFormatResult = this.applyCustomFormatters_(value); + + if (customFormatResult) { + this.emitScalar(customFormatResult); + } else if (j$.util.isUndefined(value)) { this.emitScalar('undefined'); } else if (value === null) { this.emitScalar('null'); @@ -33,7 +39,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) { } else if (value === j$.getGlobal()) { this.emitScalar(''); } else if (value.jasmineToString) { - this.emitScalar(value.jasmineToString()); + this.emitScalar(value.jasmineToString(this.pp_)); } else if (typeof value === 'string') { this.emitString(value); } else if (j$.isSpy(value)) { @@ -95,6 +101,18 @@ 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; + } + } + }; + SinglePrettyPrintRun.prototype.iterateObject = function(obj, fn) { var objKeys = keys(obj, j$.isArray_(obj)); var isGetter = function isGetter(prop) {}; @@ -365,11 +383,16 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) { return extraKeys; } - return function() { - return function(value) { - var prettyPrinter = new SinglePrettyPrintRun(); + return function(customObjectFormatters) { + var pp = function(value) { + var prettyPrinter = new SinglePrettyPrintRun( + customObjectFormatters || [], + pp + ); prettyPrinter.format(value); return prettyPrinter.stringParts.join(''); }; + + return pp; }; }; diff --git a/src/core/asymmetric_equality/ArrayContaining.js b/src/core/asymmetric_equality/ArrayContaining.js index 95c9f3f1..7faea8b4 100644 --- a/src/core/asymmetric_equality/ArrayContaining.js +++ b/src/core/asymmetric_equality/ArrayContaining.js @@ -25,8 +25,8 @@ getJasmineRequireObj().ArrayContaining = function(j$) { return true; }; - ArrayContaining.prototype.jasmineToString = function () { - return ''; + ArrayContaining.prototype.jasmineToString = function (pp) { + return ''; }; return ArrayContaining; diff --git a/src/core/asymmetric_equality/ArrayWithExactContents.js b/src/core/asymmetric_equality/ArrayWithExactContents.js index 9bdba8d4..d2049047 100644 --- a/src/core/asymmetric_equality/ArrayWithExactContents.js +++ b/src/core/asymmetric_equality/ArrayWithExactContents.js @@ -23,8 +23,8 @@ getJasmineRequireObj().ArrayWithExactContents = function(j$) { return true; }; - ArrayWithExactContents.prototype.jasmineToString = function() { - return ''; + ArrayWithExactContents.prototype.jasmineToString = function(pp) { + return ''; }; return ArrayWithExactContents; diff --git a/src/core/asymmetric_equality/MapContaining.js b/src/core/asymmetric_equality/MapContaining.js index 7c8e3c5b..ba6b0ce9 100644 --- a/src/core/asymmetric_equality/MapContaining.js +++ b/src/core/asymmetric_equality/MapContaining.js @@ -33,8 +33,8 @@ getJasmineRequireObj().MapContaining = function(j$) { return hasAllMatches; }; - MapContaining.prototype.jasmineToString = function() { - return ''; + MapContaining.prototype.jasmineToString = function(pp) { + return ''; }; return MapContaining; diff --git a/src/core/asymmetric_equality/ObjectContaining.js b/src/core/asymmetric_equality/ObjectContaining.js index 93069b25..7d9f4cf6 100644 --- a/src/core/asymmetric_equality/ObjectContaining.js +++ b/src/core/asymmetric_equality/ObjectContaining.js @@ -41,8 +41,8 @@ getJasmineRequireObj().ObjectContaining = function(j$) { return true; }; - ObjectContaining.prototype.jasmineToString = function() { - return ''; + ObjectContaining.prototype.jasmineToString = function(pp) { + return ''; }; return ObjectContaining; diff --git a/src/core/asymmetric_equality/SetContaining.js b/src/core/asymmetric_equality/SetContaining.js index 00627935..f3ad3527 100644 --- a/src/core/asymmetric_equality/SetContaining.js +++ b/src/core/asymmetric_equality/SetContaining.js @@ -31,8 +31,8 @@ getJasmineRequireObj().SetContaining = function(j$) { return hasAllMatches; }; - SetContaining.prototype.jasmineToString = function() { - return ''; + SetContaining.prototype.jasmineToString = function(pp) { + return ''; }; return SetContaining; diff --git a/src/core/matchers/DiffBuilder.js b/src/core/matchers/DiffBuilder.js index 131b5b92..f5517e17 100644 --- a/src/core/matchers/DiffBuilder.js +++ b/src/core/matchers/DiffBuilder.js @@ -1,12 +1,13 @@ getJasmineRequireObj().DiffBuilder = function(j$) { - return function DiffBuilder() { + return function DiffBuilder(config) { var path = new j$.ObjectPath(), - mismatches = []; + mismatches = [], + prettyPrinter = (config || {}).prettyPrinter || j$.makePrettyPrinter(); return { record: function (actual, expected, formatter) { formatter = formatter || defaultFormatter; - mismatches.push(formatter(actual, expected, path)); + mismatches.push(formatter(actual, expected, path, prettyPrinter)); }, getMessage: function () { @@ -21,12 +22,12 @@ getJasmineRequireObj().DiffBuilder = function(j$) { } }; - function defaultFormatter (actual, expected, path) { + function defaultFormatter (actual, expected, path, prettyPrinter) { return 'Expected ' + path + (path.depth() ? ' = ' : '') + - j$.pp(actual) + + prettyPrinter(actual) + ' to equal ' + - j$.pp(expected) + + prettyPrinter(expected) + '.'; } }; diff --git a/src/core/matchers/matchersUtil.js b/src/core/matchers/matchersUtil.js index 8a0eb75c..f38b9185 100644 --- a/src/core/matchers/matchersUtil.js +++ b/src/core/matchers/matchersUtil.js @@ -45,7 +45,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { if (i > 0) { message += ','; } - message += ' ' + j$.pp(expected[i]); + message += ' ' + self.pp(expected[i]); } } diff --git a/src/core/matchers/toEqual.js b/src/core/matchers/toEqual.js index d78bde4c..54aea12f 100644 --- a/src/core/matchers/toEqual.js +++ b/src/core/matchers/toEqual.js @@ -14,7 +14,7 @@ getJasmineRequireObj().toEqual = function(j$) { var result = { pass: false }, - diffBuilder = j$.DiffBuilder(); + diffBuilder = j$.DiffBuilder({prettyPrinter: util.pp}); result.pass = util.equals(actual, expected, diffBuilder); diff --git a/src/core/requireInterface.js b/src/core/requireInterface.js index f8f15ac6..52569888 100644 --- a/src/core/requireInterface.js +++ b/src/core/requireInterface.js @@ -316,6 +316,20 @@ getJasmineRequireObj().interface = function(jasmine, env) { return env.addAsyncMatchers(matchers); }; + /** + * Add a custom object formatter for the current scope of specs. + * + * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. + * @name jasmine.addCustomObjectFormatter + * @since 3.6.0 + * @function + * @param {Function} formatter - A function which takes a value to format and returns a string if it knows how to format it, and `undefined` otherwise. + * @see custom_object_formatter + */ + jasmine.addCustomObjectFormatter = function(formatter) { + return env.addCustomObjectFormatter(formatter); + }; + /** * Get the currently booted mock {Clock} for this Jasmine environment. * @name jasmine.clock From 873d1c294552e90bbcb96b5589324a3bdf5ff597 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Sat, 1 Feb 2020 18:49:06 -0800 Subject: [PATCH 4/8] Use custom object formatters for any part of a diff, not just leaf nodes --- lib/jasmine-core/jasmine.js | 223 +++++++++++++++++----- spec/core/PrettyPrintSpec.js | 36 +++- spec/core/matchers/DiffBuilderSpec.js | 109 ++++++++--- spec/core/matchers/MismatchTreeSpec.js | 136 +++++++++++++ spec/core/matchers/NullDiffBuilderSpec.js | 9 +- spec/core/matchers/ObjectPathSpec.js | 10 +- spec/core/matchers/matchersUtilSpec.js | 8 +- spec/core/matchers/toEqualSpec.js | 59 +++++- src/core/PrettyPrinter.js | 33 ++-- src/core/matchers/DiffBuilder.js | 60 ++++-- src/core/matchers/MismatchTree.js | 62 ++++++ src/core/matchers/NullDiffBuilder.js | 3 +- src/core/matchers/ObjectPath.js | 14 ++ src/core/matchers/matchersUtil.js | 47 ++--- src/core/requireCore.js | 1 + 15 files changed, 669 insertions(+), 141 deletions(-) create mode 100644 spec/core/matchers/MismatchTreeSpec.js create mode 100644 src/core/matchers/MismatchTree.js diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 9f7e3106..2616482f 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2008-2019 Pivotal Labs +Copyright (c) 2008-2020 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -106,6 +106,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$); @@ -4187,20 +4188,54 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { }; }; -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) { @@ -4211,12 +4246,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 + '.'; } }; @@ -4293,7 +4332,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { if (asymmetricA) { result = a.asymmetricMatch(b, shim); if (!result) { - diffBuilder.record(a, b); + diffBuilder.recordMismatch(); } return result; } @@ -4301,7 +4340,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { if (asymmetricB) { result = b.asymmetricMatch(a, shim); if (!result) { - diffBuilder.record(a, b); + diffBuilder.recordMismatch(); } return result; } @@ -4319,6 +4358,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { customTesters = customTesters || this.customTesters_; diffBuilder = diffBuilder || j$.NullDiffBuilder(); + diffBuilder.setRoots(a, b); return this.eq_(a, b, [], [], customTesters, diffBuilder); }; @@ -4337,7 +4377,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; } @@ -4346,7 +4386,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; } @@ -4356,7 +4396,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; } @@ -4364,13 +4404,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) { @@ -4380,7 +4420,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]': @@ -4388,7 +4428,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]': @@ -4398,7 +4438,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. @@ -4409,7 +4449,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { a.ignoreCase == b.ignoreCase; } if (typeof a != 'object' || typeof b != 'object') { - diffBuilder.record(a, b); + diffBuilder.recordMismatch(); return false; } @@ -4419,12 +4459,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; } @@ -4454,7 +4494,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { diffBuilder.withPath('length', function() { if (aLength !== bLength) { - diffBuilder.record(aLength, bLength); + diffBuilder.recordMismatch(); result = false; } }); @@ -4462,7 +4502,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; @@ -4474,7 +4514,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; } @@ -4516,12 +4556,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; } @@ -4566,7 +4606,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { } if (!result) { - diffBuilder.record(a, b); + diffBuilder.recordMismatch(); return false; } } else { @@ -4579,7 +4619,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; } } @@ -4590,7 +4630,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; } @@ -4598,7 +4638,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; } @@ -4704,12 +4744,75 @@ getJasmineRequireObj().MatchersUtil = function(j$) { } function isDiffBuilder(obj) { - return obj && typeof obj.record === 'function'; + return obj && typeof obj.recordMismatch === 'function'; } return MatchersUtil; }; +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; +}; + + getJasmineRequireObj().nothing = function() { /** * {@link expect} nothing explicitly. @@ -4738,7 +4841,8 @@ getJasmineRequireObj().NullDiffBuilder = function(j$) { withPath: function(_, block) { block(); }, - record: function() {} + setRoots: function() {}, + recordMismatch: function() {} }; }; }; @@ -4756,10 +4860,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; }; @@ -6109,15 +6227,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) { @@ -6390,16 +6500,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; }; }; diff --git a/spec/core/PrettyPrintSpec.js b/spec/core/PrettyPrintSpec.js index 4aafefcd..f12469b7 100644 --- a/spec/core/PrettyPrintSpec.js +++ b/spec/core/PrettyPrintSpec.js @@ -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(); }); }); }); diff --git a/spec/core/matchers/DiffBuilderSpec.js b/spec/core/matchers/DiffBuilderSpec.js index 3b68207d..6d82c3f8 100644 --- a/spec/core/matchers/DiffBuilderSpec.js +++ b/spec/core/matchers/DiffBuilderSpec.js @@ -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); + }); }); diff --git a/spec/core/matchers/MismatchTreeSpec.js b/spec/core/matchers/MismatchTreeSpec.js new file mode 100644 index 00000000..ea053eb7 --- /dev/null +++ b/spec/core/matchers/MismatchTreeSpec.js @@ -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() { + } + +}); diff --git a/spec/core/matchers/NullDiffBuilderSpec.js b/spec/core/matchers/NullDiffBuilderSpec.js index a9aac3db..3333776b 100644 --- a/spec/core/matchers/NullDiffBuilderSpec.js +++ b/spec/core/matchers/NullDiffBuilderSpec.js @@ -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(); - }) }); diff --git a/spec/core/matchers/ObjectPathSpec.js b/spec/core/matchers/ObjectPathSpec.js index 88b023d1..9750272e 100644 --- a/spec/core/matchers/ObjectPathSpec.js +++ b/spec/core/matchers/ObjectPathSpec.js @@ -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); + }); + }); }); diff --git a/spec/core/matchers/matchersUtilSpec.js b/spec/core/matchers/matchersUtilSpec.js index 1c0903ba..48f4781a 100644 --- a/spec/core/matchers/matchersUtilSpec.js +++ b/spec/core/matchers/matchersUtilSpec.js @@ -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(); }); }); diff --git a/spec/core/matchers/toEqualSpec.js b/spec/core/matchers/toEqualSpec.js index 5bfc7715..78b40ef0 100644 --- a/spec/core/matchers/toEqualSpec.js +++ b/spec/core/matchers/toEqualSpec.js @@ -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], diff --git a/src/core/PrettyPrinter.js b/src/core/PrettyPrinter.js index a15177ce..5faa3bbf 100644 --- a/src/core/PrettyPrinter.js +++ b/src/core/PrettyPrinter.js @@ -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; }; }; diff --git a/src/core/matchers/DiffBuilder.js b/src/core/matchers/DiffBuilder.js index f5517e17..197e4c71 100644 --- a/src/core/matchers/DiffBuilder.js +++ b/src/core/matchers/DiffBuilder.js @@ -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 + '.'; } }; diff --git a/src/core/matchers/MismatchTree.js b/src/core/matchers/MismatchTree.js new file mode 100644 index 00000000..1ce356de --- /dev/null +++ b/src/core/matchers/MismatchTree.js @@ -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; +}; + diff --git a/src/core/matchers/NullDiffBuilder.js b/src/core/matchers/NullDiffBuilder.js index de7d3464..cb6672e4 100644 --- a/src/core/matchers/NullDiffBuilder.js +++ b/src/core/matchers/NullDiffBuilder.js @@ -4,7 +4,8 @@ getJasmineRequireObj().NullDiffBuilder = function(j$) { withPath: function(_, block) { block(); }, - record: function() {} + setRoots: function() {}, + recordMismatch: function() {} }; }; }; diff --git a/src/core/matchers/ObjectPath.js b/src/core/matchers/ObjectPath.js index cd1629e2..266e8e62 100644 --- a/src/core/matchers/ObjectPath.js +++ b/src/core/matchers/ObjectPath.js @@ -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; }; diff --git a/src/core/matchers/matchersUtil.js b/src/core/matchers/matchersUtil.js index f38b9185..bd7664a0 100644 --- a/src/core/matchers/matchersUtil.js +++ b/src/core/matchers/matchersUtil.js @@ -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; diff --git a/src/core/requireCore.js b/src/core/requireCore.js index a21044ff..0a30f424 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -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$); From d41139fea285f6f7906a82059f803f60f7400c22 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Fri, 7 Feb 2020 13:03:35 -0800 Subject: [PATCH 5/8] Added jsdocs for MatchersUtil --- lib/jasmine-core/jasmine.js | 35 +++++++++++++++++++++++++++++++ src/core/matchers/matchersUtil.js | 35 +++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 2616482f..15627639 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -4264,12 +4264,38 @@ getJasmineRequireObj().DiffBuilder = function (j$) { getJasmineRequireObj().MatchersUtil = function(j$) { // TODO: convert all uses of j$.pp to use the injected pp + /** + * _Note:_ Do not construct this directly. Jasmine will construct one and + * pass it to matchers and asymmetric equality testers. + * @name MatchersUtil + * @since 2.0.0 + * @classdesc Utilities for use in implementing matchers + * @constructor + */ function MatchersUtil(options) { options = options || {}; this.customTesters_ = options.customTesters || []; + /** + * Formats a value for use in matcher failure messages and similar contexts, + * taking into account the current set of custom value formatters. + * @function + * @name MatchersUtil#pp + * @param {*} value The value to pretty-print + * @return {string} The pretty-printed value + */ this.pp = options.pp || function() {}; }; + /** + * Determines whether `haystack` contains `needle`, using the same comparison + * logic as {@link MatchersUtil#equals}. + * @function + * @name MatchersUtil#contains + * @param {*} haystack The collection to search + * @param {*} needle The value to search for + * @param [customTesters] An array of custom equality testers + * @returns {boolean} True if `needle` was found in `haystack` + */ MatchersUtil.prototype.contains = function(haystack, needle, customTesters) { if (j$.isSet(haystack)) { return haystack.has(needle); @@ -4346,6 +4372,15 @@ getJasmineRequireObj().MatchersUtil = function(j$) { } }; + /** + * Determines whether two values are deeply equal to each other. + * @function + * @name MatchersUtil#equals + * @param {*} a The first value to compare + * @param {*} b The second value to compare + * @param [customTesters] An array of custom equality testers + * @returns {boolean} True if the values are equal + */ MatchersUtil.prototype.equals = function(a, b, customTestersOrDiffBuilder, diffBuilderOrNothing) { var customTesters, diffBuilder; diff --git a/src/core/matchers/matchersUtil.js b/src/core/matchers/matchersUtil.js index bd7664a0..bd3d46cd 100644 --- a/src/core/matchers/matchersUtil.js +++ b/src/core/matchers/matchersUtil.js @@ -1,12 +1,38 @@ getJasmineRequireObj().MatchersUtil = function(j$) { // TODO: convert all uses of j$.pp to use the injected pp + /** + * _Note:_ Do not construct this directly. Jasmine will construct one and + * pass it to matchers and asymmetric equality testers. + * @name MatchersUtil + * @since 2.0.0 + * @classdesc Utilities for use in implementing matchers + * @constructor + */ function MatchersUtil(options) { options = options || {}; this.customTesters_ = options.customTesters || []; + /** + * Formats a value for use in matcher failure messages and similar contexts, + * taking into account the current set of custom value formatters. + * @function + * @name MatchersUtil#pp + * @param {*} value The value to pretty-print + * @return {string} The pretty-printed value + */ this.pp = options.pp || function() {}; }; + /** + * Determines whether `haystack` contains `needle`, using the same comparison + * logic as {@link MatchersUtil#equals}. + * @function + * @name MatchersUtil#contains + * @param {*} haystack The collection to search + * @param {*} needle The value to search for + * @param [customTesters] An array of custom equality testers + * @returns {boolean} True if `needle` was found in `haystack` + */ MatchersUtil.prototype.contains = function(haystack, needle, customTesters) { if (j$.isSet(haystack)) { return haystack.has(needle); @@ -83,6 +109,15 @@ getJasmineRequireObj().MatchersUtil = function(j$) { } }; + /** + * Determines whether two values are deeply equal to each other. + * @function + * @name MatchersUtil#equals + * @param {*} a The first value to compare + * @param {*} b The second value to compare + * @param [customTesters] An array of custom equality testers + * @returns {boolean} True if the values are equal + */ MatchersUtil.prototype.equals = function(a, b, customTestersOrDiffBuilder, diffBuilderOrNothing) { var customTesters, diffBuilder; From ea3dd9dffc66942cd1bdf5367cce1cdcf34b7f59 Mon Sep 17 00:00:00 2001 From: Pivotal Date: Mon, 10 Feb 2020 14:12:56 -0800 Subject: [PATCH 6/8] Refer to MatchersUtil instances as matchersUtil, not util --- lib/jasmine-core/jasmine.js | 41 +++++++++-------- spec/core/AsyncExpectationSpec.js | 44 ++++++++++--------- spec/core/ExpectationFilterChainSpec.js | 6 +-- spec/core/ExpectationSpec.js | 31 +++++++------ spec/core/matchers/toBeSpec.js | 28 ++++++------ spec/core/matchers/toContainSpec.js | 6 +-- spec/core/matchers/toEqualSpec.js | 36 +++++++-------- .../core/matchers/toHaveBeenCalledWithSpec.js | 12 ++--- spec/core/matchers/toThrowErrorSpec.js | 37 ++++++---------- spec/core/matchers/toThrowSpec.js | 16 +++---- spec/helpers/integrationMatchers.js | 2 +- spec/npmPackage/npmPackageSpec.js | 2 +- src/core/Env.js | 4 +- src/core/Expectation.js | 4 +- src/core/ExpectationFilterChain.js | 2 +- src/core/Expector.js | 13 ++++-- src/core/matchers/async/toBeRejected.js | 2 +- src/core/matchers/async/toBeResolved.js | 2 +- src/core/matchers/toBe.js | 4 +- src/core/matchers/toContain.js | 4 +- src/core/matchers/toEqual.js | 6 +-- 21 files changed, 154 insertions(+), 148 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 15627639..cce34724 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -1263,7 +1263,7 @@ getJasmineRequireObj().Env = function(j$) { runnableResources[spec.id].customEqualityTesters; return j$.Expectation.factory({ - util: makeMatchersUtil(), + matchersUtil: makeMatchersUtil(), customEqualityTesters: customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, actual: actual, @@ -1277,7 +1277,7 @@ getJasmineRequireObj().Env = function(j$) { var asyncExpectationFactory = function(actual, spec) { return j$.Expectation.asyncFactory({ - util: makeMatchersUtil(), + matchersUtil: makeMatchersUtil(), customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customAsyncMatchers: runnableResources[spec.id].customAsyncMatchers, actual: actual, @@ -3561,7 +3561,7 @@ getJasmineRequireObj().Expectation = function(j$) { return result; } - function negatedFailureMessage(result, matcherName, args, util) { + function negatedFailureMessage(result, matcherName, args, matchersUtil) { if (result.message) { if (j$.isFunction_(result.message)) { return result.message(); @@ -3573,7 +3573,7 @@ getJasmineRequireObj().Expectation = function(j$) { args = args.slice(); args.unshift(true); args.unshift(matcherName); - return util.buildFailureMessage.apply(util, args); + return matchersUtil.buildFailureMessage.apply(matchersUtil, args); } function negate(result) { @@ -3645,7 +3645,7 @@ getJasmineRequireObj().ExpectationFilterChain = function() { result, matcherName, args, - util + matchersUtil ) { return this.callFirst_('buildFailureMessage', arguments).result; }; @@ -3747,7 +3747,9 @@ getJasmineRequireObj().buildExpectationResult = function() { getJasmineRequireObj().Expector = function(j$) { function Expector(options) { - this.util = options.util || { buildFailureMessage: function() {} }; + this.matchersUtil = options.matchersUtil || { + buildFailureMessage: function() {} + }; this.customEqualityTesters = options.customEqualityTesters || []; this.actual = options.actual; this.addExpectationResult = options.addExpectationResult || function() {}; @@ -3765,7 +3767,7 @@ getJasmineRequireObj().Expector = function(j$) { this.args.unshift(this.actual); - var matcher = matcherFactory(this.util, this.customEqualityTesters); + var matcher = matcherFactory(this.matchersUtil, this.customEqualityTesters); var comparisonFunc = this.filters.selectComparisonFunc(matcher); return comparisonFunc || matcher.compare; }; @@ -3781,7 +3783,7 @@ getJasmineRequireObj().Expector = function(j$) { result, this.matcherName, this.args, - this.util, + this.matchersUtil, defaultMessage ); return this.filters.modifyFailureMessage(msg || defaultMessage()); @@ -3791,7 +3793,10 @@ getJasmineRequireObj().Expector = function(j$) { var args = self.args.slice(); args.unshift(false); args.unshift(self.matcherName); - return self.util.buildFailureMessage.apply(self.util, args); + return self.matchersUtil.buildFailureMessage.apply( + self.matchersUtil, + args + ); } else if (j$.isFunction_(result.message)) { return result.message(); } else { @@ -3943,7 +3948,7 @@ getJasmineRequireObj().toBeRejected = function(j$) { * @example * return expectAsync(aPromise).toBeRejected(); */ - return function toBeRejected(util) { + return function toBeRejected() { return { compare: function(actual) { if (!j$.isPromiseLike(actual)) { @@ -4120,7 +4125,7 @@ getJasmineRequireObj().toBeResolved = function(j$) { * @example * return expectAsync(aPromise).toBeResolved(); */ - return function toBeResolved(util) { + return function toBeResolved() { return { compare: function(actual) { if (!j$.isPromiseLike(actual)) { @@ -4972,7 +4977,7 @@ getJasmineRequireObj().toBe = function(j$) { * @example * expect(thing).toBe(realThing); */ - function toBe(util) { + function toBe(matchersUtil) { var tip = ' Tip: To check for deep equality, use .toEqual() instead of .toBe().'; return { @@ -4982,7 +4987,7 @@ getJasmineRequireObj().toBe = function(j$) { }; if (typeof expected === 'object') { - result.message = util.buildFailureMessage('toBe', result.pass, actual, expected) + tip; + result.message = matchersUtil.buildFailureMessage('toBe', result.pass, actual, expected) + tip; } return result; @@ -5428,12 +5433,12 @@ getJasmineRequireObj().toContain = function() { * expect(array).toContain(anElement); * expect(string).toContain(substring); */ - function toContain(util) { + function toContain(matchersUtil) { return { compare: function(actual, expected) { return { - pass: util.contains(actual, expected) + pass: matchersUtil.contains(actual, expected) }; } }; @@ -5452,15 +5457,15 @@ getJasmineRequireObj().toEqual = function(j$) { * @example * expect(bigObject).toEqual({"foo": ['bar', 'baz']}); */ - function toEqual(util) { + function toEqual(matchersUtil) { return { compare: function(actual, expected) { var result = { pass: false }, - diffBuilder = j$.DiffBuilder({prettyPrinter: util.pp}); + diffBuilder = j$.DiffBuilder({prettyPrinter: matchersUtil.pp}); - result.pass = util.equals(actual, expected, diffBuilder); + result.pass = matchersUtil.equals(actual, expected, diffBuilder); // TODO: only set error message if test fails result.message = diffBuilder.getMessage(); diff --git a/spec/core/AsyncExpectationSpec.js b/spec/core/AsyncExpectationSpec.js index 74eb2dad..40f63731 100644 --- a/spec/core/AsyncExpectationSpec.js +++ b/spec/core/AsyncExpectationSpec.js @@ -26,7 +26,7 @@ describe('AsyncExpectation', function() { actual = Promise.resolve(), pp = jasmineUnderTest.makePrettyPrinter(), expectation = jasmineUnderTest.Expectation.asyncFactory({ - util: new jasmineUnderTest.MatchersUtil({ pp: pp }), + matchersUtil: new jasmineUnderTest.MatchersUtil({ pp: pp }), actual: actual, addExpectationResult: addExpectationResult }); @@ -48,7 +48,9 @@ describe('AsyncExpectation', function() { var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.reject(), expectation = jasmineUnderTest.Expectation.asyncFactory({ - util: new jasmineUnderTest.MatchersUtil({ pp: function() {} }), + matchersUtil: new jasmineUnderTest.MatchersUtil({ + pp: function() {} + }), actual: actual, addExpectationResult: addExpectationResult }); @@ -92,7 +94,7 @@ describe('AsyncExpectation', function() { it('prepends the context to the generated failure message', function() { jasmine.getEnv().requirePromises(); - var util = { + var matchersUtil = { buildFailureMessage: function() { return 'failure message'; } @@ -101,7 +103,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: Promise.reject('rejected'), addExpectationResult: addExpectationResult, - util: util + matchersUtil: matchersUtil }); return expectation @@ -120,7 +122,7 @@ describe('AsyncExpectation', function() { it('prepends the context to a custom failure message', function() { jasmine.getEnv().requirePromises(); - var util = { + var matchersUtil = { buildFailureMessage: function() { return 'failure message'; }, @@ -130,7 +132,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: Promise.reject('b'), addExpectationResult: addExpectationResult, - util: util + matchersUtil: matchersUtil }); return expectation @@ -151,7 +153,7 @@ describe('AsyncExpectation', function() { pending('should actually work, but no custom matchers for async yet'); jasmine.getEnv().requirePromises(); - var util = { + var matchersUtil = { buildFailureMessage: function() { return 'failure message'; } @@ -161,7 +163,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, - util: util + matchersUtil: matchersUtil }); return expectation @@ -186,7 +188,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, - util: new jasmineUnderTest.MatchersUtil({ pp: pp }) + matchersUtil: new jasmineUnderTest.MatchersUtil({ pp: pp }) }); return expectation @@ -211,7 +213,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, - util: new jasmineUnderTest.MatchersUtil({ + matchersUtil: new jasmineUnderTest.MatchersUtil({ pp: jasmineUnderTest.makePrettyPrinter() }) }); @@ -261,7 +263,7 @@ describe('AsyncExpectation', function() { matchers = { toFoo: matcherFactory }, - util = { + matchersUtil = { buildFailureMessage: jasmine.createSpy('buildFailureMessage') }, customEqualityTesters = ['a'], @@ -269,7 +271,7 @@ describe('AsyncExpectation', function() { expectation; expectation = jasmineUnderTest.Expectation.asyncFactory({ - util: util, + matchersUtil: matchersUtil, customAsyncMatchers: matchers, customEqualityTesters: customEqualityTesters, actual: 'an actual', @@ -278,7 +280,7 @@ describe('AsyncExpectation', function() { return expectation.toFoo('hello').then(function() { expect(matcherFactory).toHaveBeenCalledWith( - util, + matchersUtil, customEqualityTesters ); }); @@ -297,14 +299,14 @@ describe('AsyncExpectation', function() { }; } }, - util = { + matchersUtil = { buildFailureMessage: jasmine.createSpy('buildFailureMessage') }, addExpectationResult = jasmine.createSpy('addExpectationResult'), expectation; expectation = jasmineUnderTest.Expectation.asyncFactory({ - util: util, + matchersUtil: matchersUtil, customAsyncMatchers: matchers, actual: 'an actual', addExpectationResult: addExpectationResult @@ -327,7 +329,7 @@ describe('AsyncExpectation', function() { }; } }, - util = { + matchersUtil = { buildFailureMessage: jasmine.createSpy('buildFailureMessage') }, addExpectationResult = jasmine.createSpy('addExpectationResult'), @@ -340,7 +342,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ customAsyncMatchers: matchers, - util: util, + matchersUtil: matchersUtil, actual: 'an actual', addExpectationResult: addExpectationResult }); @@ -370,7 +372,7 @@ describe('AsyncExpectation', function() { }; } }, - util = { + matchersUtil = { buildFailureMessage: function() { return ''; } @@ -385,7 +387,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ customAsyncMatchers: matchers, - util: util, + matchersUtil: matchersUtil, actual: 'an actual', addExpectationResult: addExpectationResult }); @@ -541,7 +543,7 @@ describe('AsyncExpectation', function() { }; } }, - util = { + matchersUtil = { buildFailureMessage: function() { return 'default message'; } @@ -558,7 +560,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ customAsyncMatchers: matchers, actual: 'an actual', - util: util, + matchersUtil: matchersUtil, addExpectationResult: addExpectationResult }).not; diff --git a/spec/core/ExpectationFilterChainSpec.js b/spec/core/ExpectationFilterChainSpec.js index 911f6b30..009a5ba3 100644 --- a/spec/core/ExpectationFilterChainSpec.js +++ b/spec/core/ExpectationFilterChainSpec.js @@ -72,21 +72,21 @@ describe('ExpectationFilterChain', function() { matcherResult = { pass: false }, matcherName = 'foo', args = [], - util = {}, + matchersUtil = {}, result; result = chain.buildFailureMessage( matcherResult, matcherName, args, - util + matchersUtil ); expect(first).toHaveBeenCalledWith( matcherResult, matcherName, args, - util + matchersUtil ); expect(second).not.toHaveBeenCalled(); expect(result).toEqual('first'); diff --git a/spec/core/ExpectationSpec.js b/spec/core/ExpectationSpec.js index 5b205730..0ad411b9 100644 --- a/spec/core/ExpectationSpec.js +++ b/spec/core/ExpectationSpec.js @@ -37,7 +37,7 @@ describe('Expectation', function() { matchers = { toFoo: matcherFactory }, - util = { + matchersUtil = { buildFailureMessage: jasmine.createSpy('buildFailureMessage') }, customEqualityTesters = ['a'], @@ -45,7 +45,7 @@ describe('Expectation', function() { expectation; expectation = jasmineUnderTest.Expectation.factory({ - util: util, + matchersUtil: matchersUtil, customMatchers: matchers, customEqualityTesters: customEqualityTesters, actual: 'an actual', @@ -54,7 +54,10 @@ describe('Expectation', function() { expectation.toFoo('hello'); - expect(matcherFactory).toHaveBeenCalledWith(util, customEqualityTesters); + expect(matcherFactory).toHaveBeenCalledWith( + matchersUtil, + customEqualityTesters + ); }); it("wraps matchers's compare functions, passing the actual and expected", function() { @@ -68,14 +71,14 @@ describe('Expectation', function() { }; } }, - util = { + matchersUtil = { buildFailureMessage: jasmine.createSpy('buildFailureMessage') }, addExpectationResult = jasmine.createSpy('addExpectationResult'), expectation; expectation = jasmineUnderTest.Expectation.factory({ - util: util, + matchersUtil: matchersUtil, customMatchers: matchers, actual: 'an actual', addExpectationResult: addExpectationResult @@ -96,7 +99,7 @@ describe('Expectation', function() { }; } }, - util = { + matchersUtil = { buildFailureMessage: jasmine.createSpy('buildFailureMessage') }, addExpectationResult = jasmine.createSpy('addExpectationResult'), @@ -104,7 +107,7 @@ describe('Expectation', function() { expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, - util: util, + matchersUtil: matchersUtil, actual: 'an actual', addExpectationResult: addExpectationResult }); @@ -132,7 +135,7 @@ describe('Expectation', function() { }; } }, - util = { + matchersUtil = { buildFailureMessage: function() { return ''; } @@ -142,7 +145,7 @@ describe('Expectation', function() { expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, - util: util, + matchersUtil: matchersUtil, actual: 'an actual', addExpectationResult: addExpectationResult }); @@ -275,7 +278,7 @@ describe('Expectation', function() { }; } }, - util = { + matchersUtil = { buildFailureMessage: function() { return 'default message'; } @@ -287,7 +290,7 @@ describe('Expectation', function() { expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, actual: 'an actual', - util: util, + matchersUtil: matchersUtil, addExpectationResult: addExpectationResult }).not; @@ -539,7 +542,7 @@ describe('Expectation', function() { }; } }, - util = { + matchersUtil = { buildFailureMessage: function() { return 'failure message'; } @@ -547,7 +550,7 @@ describe('Expectation', function() { addExpectationResult = jasmine.createSpy('addExpectationResult'), expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, - util: util, + matchersUtil: matchersUtil, actual: 'an actual', addExpectationResult: addExpectationResult }); @@ -635,7 +638,7 @@ describe('Expectation', function() { pp = jasmineUnderTest.makePrettyPrinter(), expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, - util: new jasmineUnderTest.MatchersUtil({ pp: pp }), + matchersUtil: new jasmineUnderTest.MatchersUtil({ pp: pp }), actual: 'an actual', addExpectationResult: addExpectationResult }); diff --git a/spec/core/matchers/toBeSpec.js b/spec/core/matchers/toBeSpec.js index 49484f5f..4d74a6de 100644 --- a/spec/core/matchers/toBeSpec.js +++ b/spec/core/matchers/toBeSpec.js @@ -1,7 +1,7 @@ describe("toBe", function() { it("passes with no message when actual === expected", function() { - var util = new jasmineUnderTest.MatchersUtil(), - matcher = jasmineUnderTest.matchers.toBe(util), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(matchersUtil), result; result = matcher.compare(1, 1); @@ -9,8 +9,8 @@ describe("toBe", function() { }); it("passes with a custom message when expected is an array", function() { - var util = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), - matcher = jasmineUnderTest.matchers.toBe(util), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), + matcher = jasmineUnderTest.matchers.toBe(matchersUtil), result, array = [1]; @@ -20,8 +20,8 @@ describe("toBe", function() { }); it("passes with a custom message when expected is an object", function() { - var util = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), - matcher = jasmineUnderTest.matchers.toBe(util), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), + matcher = jasmineUnderTest.matchers.toBe(matchersUtil), result, obj = {foo: "bar"}; @@ -31,8 +31,8 @@ describe("toBe", function() { }); it("fails with no message when actual !== expected", function() { - var util = new jasmineUnderTest.MatchersUtil(), - matcher = jasmineUnderTest.matchers.toBe(util), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(matchersUtil), result; result = matcher.compare(1, 2); @@ -41,8 +41,8 @@ describe("toBe", function() { }); it("fails with a custom message when expected is an array", function() { - var util = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), - matcher = jasmineUnderTest.matchers.toBe(util), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), + matcher = jasmineUnderTest.matchers.toBe(matchersUtil), result; result = matcher.compare([1], [1]); @@ -51,8 +51,8 @@ describe("toBe", function() { }); it("fails with a custom message when expected is an object", function() { - var util = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), - matcher = jasmineUnderTest.matchers.toBe(util), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), + matcher = jasmineUnderTest.matchers.toBe(matchersUtil), result; result = matcher.compare({foo: "bar"}, {foo: "bar"}); @@ -63,8 +63,8 @@ describe("toBe", function() { it("works with custom object formatters when expected is an object", function() { var formatter = function(x) { return '<' + x.foo + '>'; }, prettyPrinter = jasmineUnderTest.makePrettyPrinter([formatter]), - util = new jasmineUnderTest.MatchersUtil({pp: prettyPrinter}), - matcher = jasmineUnderTest.matchers.toBe(util), + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: prettyPrinter}), + matcher = jasmineUnderTest.matchers.toBe(matchersUtil), result; result = matcher.compare({foo: "bar"}, {foo: "bar"}); diff --git a/spec/core/matchers/toContainSpec.js b/spec/core/matchers/toContainSpec.js index effe5862..5fd1c8f8 100644 --- a/spec/core/matchers/toContainSpec.js +++ b/spec/core/matchers/toContainSpec.js @@ -1,13 +1,13 @@ describe("toContain", function() { it("delegates to jasmineUnderTest.matchersUtil.contains", function() { - var util = { + var matchersUtil = { contains: jasmine.createSpy('delegated-contains').and.returnValue(true) }, - matcher = jasmineUnderTest.matchers.toContain(util), + matcher = jasmineUnderTest.matchers.toContain(matchersUtil), result; result = matcher.compare("ABC", "B"); - expect(util.contains).toHaveBeenCalledWith("ABC", "B"); + expect(matchersUtil.contains).toHaveBeenCalledWith("ABC", "B"); expect(result.pass).toBe(true); }); diff --git a/spec/core/matchers/toEqualSpec.js b/spec/core/matchers/toEqualSpec.js index 78b40ef0..be8e9f6d 100644 --- a/spec/core/matchers/toEqualSpec.js +++ b/spec/core/matchers/toEqualSpec.js @@ -2,10 +2,10 @@ describe("toEqual", function() { "use strict"; function compareEquals(actual, expected) { - var util = new jasmineUnderTest.MatchersUtil({ + var matchersUtil = new jasmineUnderTest.MatchersUtil({ pp: jasmineUnderTest.makePrettyPrinter() }), - matcher = jasmineUnderTest.matchers.toEqual(util); + matcher = jasmineUnderTest.matchers.toEqual(matchersUtil); var result = matcher.compare(actual, expected); @@ -13,19 +13,19 @@ describe("toEqual", function() { } it("delegates to equals function", function() { - var util = { + var matchersUtil = { equals: jasmine.createSpy('delegated-equals').and.returnValue(true), buildFailureMessage: function() { - return 'does not matter' + return 'does not matter'; }, DiffBuilder: new jasmineUnderTest.DiffBuilder() }, - matcher = jasmineUnderTest.matchers.toEqual(util), + matcher = jasmineUnderTest.matchers.toEqual(matchersUtil), result; result = matcher.compare(1, 1); - expect(util.equals).toHaveBeenCalledWith(1, 1, jasmine.anything()); + expect(matchersUtil.equals).toHaveBeenCalledWith(1, 1, jasmine.anything()); expect(result.pass).toBe(true); }); @@ -33,8 +33,8 @@ describe("toEqual", function() { var tester = function (a, b) { return a.toString() === b.toString(); }, - util = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}), - matcher = jasmineUnderTest.matchers.toEqual(util), + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}), + matcher = jasmineUnderTest.matchers.toEqual(matchersUtil), result; result = matcher.compare(1, '1'); @@ -107,8 +107,8 @@ describe("toEqual", function() { var actual = {x: {y: 1, z: 2, f: 4}}, expected = {x: {y: 1, z: 2, g: 3}}, pp = jasmineUnderTest.makePrettyPrinter([formatter]), - util = new jasmineUnderTest.MatchersUtil({pp: pp}), - matcher = jasmineUnderTest.matchers.toEqual(util), + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: pp}), + matcher = jasmineUnderTest.matchers.toEqual(matchersUtil), message = "Expected $.x to have properties\n" + " g: |3|\n" + @@ -128,8 +128,8 @@ describe("toEqual", function() { var actual = [{foo: 4}], expected = [{foo: 5}], prettyPrinter = jasmineUnderTest.makePrettyPrinter([formatter]), - util = new jasmineUnderTest.MatchersUtil({pp: prettyPrinter}), - matcher = jasmineUnderTest.matchers.toEqual(util), + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: prettyPrinter}), + matcher = jasmineUnderTest.matchers.toEqual(matchersUtil), message = "Expected $[0].foo = |4| to equal |5|."; expect(matcher.compare(actual, expected).message).toEqual(message); @@ -151,8 +151,8 @@ describe("toEqual", function() { bar: "shouldn't be pretty printed" }], prettyPrinter = jasmineUnderTest.makePrettyPrinter([formatter]), - util = new jasmineUnderTest.MatchersUtil({pp: prettyPrinter}), - matcher = jasmineUnderTest.matchers.toEqual(util), + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: prettyPrinter}), + matcher = jasmineUnderTest.matchers.toEqual(matchersUtil), 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'."; @@ -348,8 +348,8 @@ describe("toEqual", function() { expected = {x: new Bar()}, message = "Expected $.x to be a kind of Bar, but was |[object Object]|.", pp = jasmineUnderTest.makePrettyPrinter([formatter]), - util = new jasmineUnderTest.MatchersUtil({pp: pp}), - matcher = jasmineUnderTest.matchers.toEqual(util); + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: pp}), + matcher = jasmineUnderTest.matchers.toEqual(matchersUtil); expect(matcher.compare(actual, expected).message).toEqual(message); }); @@ -889,8 +889,8 @@ describe("toEqual", function() { var actual = [1, 1, 2, 3, 5], expected = [1, 1, 2, 3], pp = jasmineUnderTest.makePrettyPrinter([formatter]), - util = new jasmineUnderTest.MatchersUtil({pp: pp}), - matcher = jasmineUnderTest.matchers.toEqual(util), + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: pp}), + matcher = jasmineUnderTest.matchers.toEqual(matchersUtil), message = 'Expected $.length = |5| to equal |4|.\n' + 'Unexpected $[4] = |5| in array.'; diff --git a/spec/core/matchers/toHaveBeenCalledWithSpec.js b/spec/core/matchers/toHaveBeenCalledWithSpec.js index 852dd837..5a320f68 100644 --- a/spec/core/matchers/toHaveBeenCalledWithSpec.js +++ b/spec/core/matchers/toHaveBeenCalledWithSpec.js @@ -1,11 +1,11 @@ describe("toHaveBeenCalledWith", function() { it("passes when the actual was called with matching parameters", function() { - var util = { + var matchersUtil = { contains: jasmine.createSpy('delegated-contains').and.returnValue(true), pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), + matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(matchersUtil), calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'), result; @@ -29,11 +29,11 @@ describe("toHaveBeenCalledWith", function() { }); it("fails when the actual was not called", function() { - var util = { + var matchersUtil = { contains: jasmine.createSpy('delegated-contains').and.returnValue(false), pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), + matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(matchersUtil), uncalledSpy = new jasmineUnderTest.Env().createSpy('uncalled spy'), result; @@ -43,8 +43,8 @@ describe("toHaveBeenCalledWith", function() { }); it("fails when the actual was called with different parameters", function() { - var util = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), - matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), + matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(matchersUtil), calledSpy = new jasmineUnderTest.Env().createSpy('called spy'), result; diff --git a/spec/core/matchers/toThrowErrorSpec.js b/spec/core/matchers/toThrowErrorSpec.js index 8070719c..68fdf8be 100644 --- a/spec/core/matchers/toThrowErrorSpec.js +++ b/spec/core/matchers/toThrowErrorSpec.js @@ -192,10 +192,7 @@ describe("toThrowError", function() { }); it("passes if thrown is an Error and the expected the same Error", function() { - var util = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(true) - }, - matcher = jasmineUnderTest.matchers.toThrowError(), + var matcher = jasmineUnderTest.matchers.toThrowError(), fn = function() { throw new Error(); }, @@ -208,10 +205,7 @@ describe("toThrowError", function() { }); it("passes if thrown is a custom error that takes arguments and the expected is the same error", function() { - var util = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(true) - }, - matcher = jasmineUnderTest.matchers.toThrowError(), + var matcher = jasmineUnderTest.matchers.toThrowError(), CustomError = function CustomError(arg) { arg.x }, fn = function() { throw new CustomError({ x: 1 }); @@ -227,10 +221,7 @@ describe("toThrowError", function() { }); it("fails if thrown is an Error and the expected is a different Error", function() { - var util = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(false) - }, - matcher = jasmineUnderTest.matchers.toThrowError(), + var matcher = jasmineUnderTest.matchers.toThrowError(), fn = function() { throw new Error(); }, @@ -243,11 +234,11 @@ describe("toThrowError", function() { }); it("passes if thrown is a type of Error and it is equal to the expected Error and message", function() { - var util = { + var matchersUtil = { equals: jasmine.createSpy('delegated-equal').and.returnValue(true), pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(util), + matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil), fn = function() { throw new TypeError("foo"); }, @@ -260,12 +251,12 @@ describe("toThrowError", function() { }); it("passes if thrown is a custom error that takes arguments and it is equal to the expected custom error and message", function() { - var util = { + var matchersUtil = { equals: jasmine.createSpy('delegated-equal').and.returnValue(true), pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(util), - CustomError = function CustomError(arg) { this.message = arg.message }, + matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil), + CustomError = function CustomError(arg) { this.message = arg.message; }, fn = function() { throw new CustomError({message: "foo"}); }, @@ -280,11 +271,11 @@ describe("toThrowError", function() { }); it("fails if thrown is a type of Error and the expected is a different Error", function() { - var util = { + var matchersUtil = { equals: jasmine.createSpy('delegated-equal').and.returnValue(false), pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(util), + matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil), fn = function() { throw new TypeError("foo"); }, @@ -297,11 +288,11 @@ describe("toThrowError", function() { }); it("passes if thrown is a type of Error and has the same type as the expected Error and the message matches the expected message", function() { - var util = { + var matchersUtil = { equals: jasmine.createSpy('delegated-equal').and.returnValue(true), pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(util), + matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil), fn = function() { throw new TypeError("foo"); }, @@ -314,11 +305,11 @@ describe("toThrowError", function() { }); it("fails if thrown is a type of Error and the expected is a different Error", function() { - var util = { + var matchersUtil = { equals: jasmine.createSpy('delegated-equal').and.returnValue(false), pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(util), + matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil), fn = function() { throw new TypeError("foo"); }, diff --git a/spec/core/matchers/toThrowSpec.js b/spec/core/matchers/toThrowSpec.js index 979ddc1d..7144b2b3 100644 --- a/spec/core/matchers/toThrowSpec.js +++ b/spec/core/matchers/toThrowSpec.js @@ -23,11 +23,11 @@ describe("toThrow", function() { }); it("passes if it throws but there is no expected", function() { - var util = { + var matchersUtil = { equals: jasmine.createSpy('delegated-equal').and.returnValue(true), pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrow(util), + matcher = jasmineUnderTest.matchers.toThrow(matchersUtil), fn = function() { throw 5; }, @@ -54,11 +54,11 @@ describe("toThrow", function() { }); it("passes if what is thrown is equivalent to what is expected", function() { - var util = { + var matchersUtil = { equals: jasmine.createSpy('delegated-equal').and.returnValue(true), pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrow(util), + matcher = jasmineUnderTest.matchers.toThrow(matchersUtil), fn = function() { throw 5; }, @@ -71,11 +71,11 @@ describe("toThrow", function() { }); it("fails if what is thrown is not equivalent to what is expected", function() { - var util = { + var matchersUtil = { equals: jasmine.createSpy('delegated-equal').and.returnValue(false), pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrow(util), + matcher = jasmineUnderTest.matchers.toThrow(matchersUtil), fn = function() { throw 5; }, @@ -88,11 +88,11 @@ describe("toThrow", function() { }); it("fails if what is thrown is not equivalent to undefined", function() { - var util = { + var matchersUtil = { equals: jasmine.createSpy('delegated-equal').and.returnValue(false), pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrow(util), + matcher = jasmineUnderTest.matchers.toThrow(matchersUtil), fn = function() { throw 5; }, diff --git a/spec/helpers/integrationMatchers.js b/spec/helpers/integrationMatchers.js index f473385c..75bec185 100644 --- a/spec/helpers/integrationMatchers.js +++ b/spec/helpers/integrationMatchers.js @@ -1,7 +1,7 @@ (function(env) { env.registerIntegrationMatchers = function() { jasmine.addMatchers({ - toHaveFailedExpectationsForRunnable: function(util) { + toHaveFailedExpectationsForRunnable: function() { return { compare: function(actual, fullName, expectedFailures) { var foundRunnable = false, diff --git a/spec/npmPackage/npmPackageSpec.js b/spec/npmPackage/npmPackageSpec.js index 908b1c06..ea292bc2 100644 --- a/spec/npmPackage/npmPackageSpec.js +++ b/spec/npmPackage/npmPackageSpec.js @@ -23,7 +23,7 @@ describe('npm package', function() { beforeEach(function() { jasmine.addMatchers({ - toExistInPath: function(util) { + toExistInPath: function() { return { compare: function(actual, expected) { var fullPath = path.resolve(expected, actual); diff --git a/src/core/Env.js b/src/core/Env.js index 62510708..9ead1348 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -341,7 +341,7 @@ getJasmineRequireObj().Env = function(j$) { runnableResources[spec.id].customEqualityTesters; return j$.Expectation.factory({ - util: makeMatchersUtil(), + matchersUtil: makeMatchersUtil(), customEqualityTesters: customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, actual: actual, @@ -355,7 +355,7 @@ getJasmineRequireObj().Env = function(j$) { var asyncExpectationFactory = function(actual, spec) { return j$.Expectation.asyncFactory({ - util: makeMatchersUtil(), + matchersUtil: makeMatchersUtil(), customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customAsyncMatchers: runnableResources[spec.id].customAsyncMatchers, actual: actual, diff --git a/src/core/Expectation.js b/src/core/Expectation.js index ba079532..90580211 100644 --- a/src/core/Expectation.js +++ b/src/core/Expectation.js @@ -129,7 +129,7 @@ getJasmineRequireObj().Expectation = function(j$) { return result; } - function negatedFailureMessage(result, matcherName, args, util) { + function negatedFailureMessage(result, matcherName, args, matchersUtil) { if (result.message) { if (j$.isFunction_(result.message)) { return result.message(); @@ -141,7 +141,7 @@ getJasmineRequireObj().Expectation = function(j$) { args = args.slice(); args.unshift(true); args.unshift(matcherName); - return util.buildFailureMessage.apply(util, args); + return matchersUtil.buildFailureMessage.apply(matchersUtil, args); } function negate(result) { diff --git a/src/core/ExpectationFilterChain.js b/src/core/ExpectationFilterChain.js index 6ecbd17c..d7f020b8 100644 --- a/src/core/ExpectationFilterChain.js +++ b/src/core/ExpectationFilterChain.js @@ -16,7 +16,7 @@ getJasmineRequireObj().ExpectationFilterChain = function() { result, matcherName, args, - util + matchersUtil ) { return this.callFirst_('buildFailureMessage', arguments).result; }; diff --git a/src/core/Expector.js b/src/core/Expector.js index 18e8784e..e0f7ae9c 100644 --- a/src/core/Expector.js +++ b/src/core/Expector.js @@ -1,6 +1,8 @@ getJasmineRequireObj().Expector = function(j$) { function Expector(options) { - this.util = options.util || { buildFailureMessage: function() {} }; + this.matchersUtil = options.matchersUtil || { + buildFailureMessage: function() {} + }; this.customEqualityTesters = options.customEqualityTesters || []; this.actual = options.actual; this.addExpectationResult = options.addExpectationResult || function() {}; @@ -18,7 +20,7 @@ getJasmineRequireObj().Expector = function(j$) { this.args.unshift(this.actual); - var matcher = matcherFactory(this.util, this.customEqualityTesters); + var matcher = matcherFactory(this.matchersUtil, this.customEqualityTesters); var comparisonFunc = this.filters.selectComparisonFunc(matcher); return comparisonFunc || matcher.compare; }; @@ -34,7 +36,7 @@ getJasmineRequireObj().Expector = function(j$) { result, this.matcherName, this.args, - this.util, + this.matchersUtil, defaultMessage ); return this.filters.modifyFailureMessage(msg || defaultMessage()); @@ -44,7 +46,10 @@ getJasmineRequireObj().Expector = function(j$) { var args = self.args.slice(); args.unshift(false); args.unshift(self.matcherName); - return self.util.buildFailureMessage.apply(self.util, args); + return self.matchersUtil.buildFailureMessage.apply( + self.matchersUtil, + args + ); } else if (j$.isFunction_(result.message)) { return result.message(); } else { diff --git a/src/core/matchers/async/toBeRejected.js b/src/core/matchers/async/toBeRejected.js index 62f1a3e9..3ec95bf3 100644 --- a/src/core/matchers/async/toBeRejected.js +++ b/src/core/matchers/async/toBeRejected.js @@ -10,7 +10,7 @@ getJasmineRequireObj().toBeRejected = function(j$) { * @example * return expectAsync(aPromise).toBeRejected(); */ - return function toBeRejected(util) { + return function toBeRejected() { return { compare: function(actual) { if (!j$.isPromiseLike(actual)) { diff --git a/src/core/matchers/async/toBeResolved.js b/src/core/matchers/async/toBeResolved.js index efe367e9..997ddeea 100644 --- a/src/core/matchers/async/toBeResolved.js +++ b/src/core/matchers/async/toBeResolved.js @@ -10,7 +10,7 @@ getJasmineRequireObj().toBeResolved = function(j$) { * @example * return expectAsync(aPromise).toBeResolved(); */ - return function toBeResolved(util) { + return function toBeResolved() { return { compare: function(actual) { if (!j$.isPromiseLike(actual)) { diff --git a/src/core/matchers/toBe.js b/src/core/matchers/toBe.js index 3aab9416..8115adc0 100644 --- a/src/core/matchers/toBe.js +++ b/src/core/matchers/toBe.js @@ -8,7 +8,7 @@ getJasmineRequireObj().toBe = function(j$) { * @example * expect(thing).toBe(realThing); */ - function toBe(util) { + function toBe(matchersUtil) { var tip = ' Tip: To check for deep equality, use .toEqual() instead of .toBe().'; return { @@ -18,7 +18,7 @@ getJasmineRequireObj().toBe = function(j$) { }; if (typeof expected === 'object') { - result.message = util.buildFailureMessage('toBe', result.pass, actual, expected) + tip; + result.message = matchersUtil.buildFailureMessage('toBe', result.pass, actual, expected) + tip; } return result; diff --git a/src/core/matchers/toContain.js b/src/core/matchers/toContain.js index cf511604..5732447e 100644 --- a/src/core/matchers/toContain.js +++ b/src/core/matchers/toContain.js @@ -9,12 +9,12 @@ getJasmineRequireObj().toContain = function() { * expect(array).toContain(anElement); * expect(string).toContain(substring); */ - function toContain(util) { + function toContain(matchersUtil) { return { compare: function(actual, expected) { return { - pass: util.contains(actual, expected) + pass: matchersUtil.contains(actual, expected) }; } }; diff --git a/src/core/matchers/toEqual.js b/src/core/matchers/toEqual.js index 54aea12f..3a7add38 100644 --- a/src/core/matchers/toEqual.js +++ b/src/core/matchers/toEqual.js @@ -8,15 +8,15 @@ getJasmineRequireObj().toEqual = function(j$) { * @example * expect(bigObject).toEqual({"foo": ['bar', 'baz']}); */ - function toEqual(util) { + function toEqual(matchersUtil) { return { compare: function(actual, expected) { var result = { pass: false }, - diffBuilder = j$.DiffBuilder({prettyPrinter: util.pp}); + diffBuilder = j$.DiffBuilder({prettyPrinter: matchersUtil.pp}); - result.pass = util.equals(actual, expected, diffBuilder); + result.pass = matchersUtil.equals(actual, expected, diffBuilder); // TODO: only set error message if test fails result.message = diffBuilder.getMessage(); From 3be797c8d8b86cf37f116573bc55b487cf9fcb82 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 11 Feb 2020 16:25:53 -0800 Subject: [PATCH 7/8] Fixed diffs involving jasmine.objectContaining --- lib/jasmine-core/jasmine.js | 51 +++++++++++-------- .../ObjectContainingSpec.js | 3 +- spec/core/matchers/ObjectPathSpec.js | 8 --- spec/core/matchers/toEqualSpec.js | 4 +- src/core/base.js | 4 ++ src/core/matchers/DiffBuilder.js | 23 ++++++++- src/core/matchers/ObjectPath.js | 10 ---- src/core/matchers/matchersUtil.js | 14 ++--- 8 files changed, 62 insertions(+), 55 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index fc466dbe..65146cf8 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -278,6 +278,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { return false; }; + j$.isAsymmetricEqualityTester_ = function(obj) { + return obj && j$.isA_('Function', obj.asymmetricMatch); + }; + j$.getType_ = function(value) { return Object.prototype.toString.apply(value); }; @@ -4329,8 +4333,9 @@ getJasmineRequireObj().DiffBuilder = function (j$) { mismatches.traverse(function (path, isLeaf, formatter) { var actualCustom, expectedCustom, useCustom, - actual = path.dereference(actualRoot), - expected = path.dereference(expectedRoot); + derefResult = dereferencePath(path, actualRoot, expectedRoot, prettyPrinter), + actual = derefResult.actual, + expected = derefResult.expected; if (formatter) { messages.push(formatter(actual, expected, path, prettyPrinter)); @@ -4377,6 +4382,24 @@ getJasmineRequireObj().DiffBuilder = function (j$) { '.'; } }; + + function dereferencePath(objectPath, actual, expected, pp) { + var i, asymmetricResult + + for (i = 0; i < objectPath.components.length; i++) { + actual = actual[objectPath.components[i]]; + expected = expected[objectPath.components[i]]; + + if (j$.isAsymmetricEqualityTester_(expected) && j$.isFunction_(expected.valuesForDiff_)) { + var asymmetricResult = expected.valuesForDiff_(actual, pp); + expected = asymmetricResult.self; + actual = asymmetricResult.other; + } + } + + return {actual: actual, expected: expected}; + } + }; getJasmineRequireObj().MatchersUtil = function(j$) { @@ -4459,13 +4482,9 @@ getJasmineRequireObj().MatchersUtil = function(j$) { return message + '.'; }; - function isAsymmetric(obj) { - return obj && j$.isA_('Function', obj.asymmetricMatch); - } - MatchersUtil.prototype.asymmetricDiff_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { if (j$.isFunction_(b.valuesForDiff_)) { - var values = b.valuesForDiff_(a); + var values = b.valuesForDiff_(a, this.pp); this.eq_(values.other, values.self, aStack, bStack, customTesters, diffBuilder); } else { diffBuilder.recordMismatch(); @@ -4473,8 +4492,8 @@ getJasmineRequireObj().MatchersUtil = function(j$) { }; MatchersUtil.prototype.asymmetricMatch_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { - var asymmetricA = isAsymmetric(a), - asymmetricB = isAsymmetric(b), + var asymmetricA = j$.isAsymmetricEqualityTester_(a), + asymmetricB = j$.isAsymmetricEqualityTester_(b), shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters), result; @@ -4485,8 +4504,6 @@ getJasmineRequireObj().MatchersUtil = function(j$) { if (asymmetricA) { result = a.asymmetricMatch(b, shim); if (!result) { - // TODO: Do we want to build an asymmetric diff when the actual was an - // asymmeteric equality tester? Might be confusing. diffBuilder.recordMismatch(); } return result; @@ -4709,7 +4726,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { // Only use the cmpKey when one of the keys is asymmetric and the corresponding key matches, // otherwise explicitly look up the mapKey in the other Map since we want keys with unique // obj identity (that are otherwise equal) to not match. - if (isAsymmetric(mapKey) || isAsymmetric(cmpKey) && + if (j$.isAsymmetricEqualityTester_(mapKey) || j$.isAsymmetricEqualityTester_(cmpKey) && this.eq_(mapKey, cmpKey, aStack, bStack, customTesters, j$.NullDiffBuilder())) { mapValueB = b.get(cmpKey); } else { @@ -5024,16 +5041,6 @@ 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])); }; diff --git a/spec/core/asymmetric_equality/ObjectContainingSpec.js b/spec/core/asymmetric_equality/ObjectContainingSpec.js index bec44fe2..8cad948d 100644 --- a/spec/core/asymmetric_equality/ObjectContainingSpec.js +++ b/spec/core/asymmetric_equality/ObjectContainingSpec.js @@ -125,7 +125,8 @@ describe("ObjectContaining", function() { describe("when other is not an object", function() { it("sets self to jasmineToString()", function () { var containing = new jasmineUnderTest.ObjectContaining({}), - result = containing.valuesForDiff_('a'); + pp = jasmineUnderTest.makePrettyPrinter(), + result = containing.valuesForDiff_('a', pp); expect(result).toEqual({ self: '', diff --git a/spec/core/matchers/ObjectPathSpec.js b/spec/core/matchers/ObjectPathSpec.js index 9750272e..4e0d99fa 100644 --- a/spec/core/matchers/ObjectPathSpec.js +++ b/spec/core/matchers/ObjectPathSpec.js @@ -40,12 +40,4 @@ 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); - }); - }); }); diff --git a/spec/core/matchers/toEqualSpec.js b/spec/core/matchers/toEqualSpec.js index 3a1ce985..3a249364 100644 --- a/spec/core/matchers/toEqualSpec.js +++ b/spec/core/matchers/toEqualSpec.js @@ -429,10 +429,10 @@ describe("toEqual", function() { expect(compareEquals(actual, expected).message).toEqual(message); }); - it("reports mismatches involving objectContaining", function() { + it("reports mismatches involving objectContaining and an object", 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.') + expect(compareEquals(actual, expected).message).toEqual('Expected $.x.b = 4 to equal 2.'); }); it("reports mismatches between a non-object and objectContaining", function() { diff --git a/src/core/base.js b/src/core/base.js index 3a2a097d..37aa692e 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -111,6 +111,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { return false; }; + j$.isAsymmetricEqualityTester_ = function(obj) { + return obj && j$.isA_('Function', obj.asymmetricMatch); + }; + j$.getType_ = function(value) { return Object.prototype.toString.apply(value); }; diff --git a/src/core/matchers/DiffBuilder.js b/src/core/matchers/DiffBuilder.js index 197e4c71..4e43aeaa 100644 --- a/src/core/matchers/DiffBuilder.js +++ b/src/core/matchers/DiffBuilder.js @@ -21,8 +21,9 @@ getJasmineRequireObj().DiffBuilder = function (j$) { mismatches.traverse(function (path, isLeaf, formatter) { var actualCustom, expectedCustom, useCustom, - actual = path.dereference(actualRoot), - expected = path.dereference(expectedRoot); + derefResult = dereferencePath(path, actualRoot, expectedRoot, prettyPrinter), + actual = derefResult.actual, + expected = derefResult.expected; if (formatter) { messages.push(formatter(actual, expected, path, prettyPrinter)); @@ -69,4 +70,22 @@ getJasmineRequireObj().DiffBuilder = function (j$) { '.'; } }; + + function dereferencePath(objectPath, actual, expected, pp) { + var i, asymmetricResult + + for (i = 0; i < objectPath.components.length; i++) { + actual = actual[objectPath.components[i]]; + expected = expected[objectPath.components[i]]; + + if (j$.isAsymmetricEqualityTester_(expected) && j$.isFunction_(expected.valuesForDiff_)) { + var asymmetricResult = expected.valuesForDiff_(actual, pp); + expected = asymmetricResult.self; + actual = asymmetricResult.other; + } + } + + return {actual: actual, expected: expected}; + } + }; diff --git a/src/core/matchers/ObjectPath.js b/src/core/matchers/ObjectPath.js index 266e8e62..a9acfdb2 100644 --- a/src/core/matchers/ObjectPath.js +++ b/src/core/matchers/ObjectPath.js @@ -11,16 +11,6 @@ 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])); }; diff --git a/src/core/matchers/matchersUtil.js b/src/core/matchers/matchersUtil.js index e79dd075..6fa812bf 100644 --- a/src/core/matchers/matchersUtil.js +++ b/src/core/matchers/matchersUtil.js @@ -78,13 +78,9 @@ getJasmineRequireObj().MatchersUtil = function(j$) { return message + '.'; }; - function isAsymmetric(obj) { - return obj && j$.isA_('Function', obj.asymmetricMatch); - } - MatchersUtil.prototype.asymmetricDiff_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { if (j$.isFunction_(b.valuesForDiff_)) { - var values = b.valuesForDiff_(a); + var values = b.valuesForDiff_(a, this.pp); this.eq_(values.other, values.self, aStack, bStack, customTesters, diffBuilder); } else { diffBuilder.recordMismatch(); @@ -92,8 +88,8 @@ getJasmineRequireObj().MatchersUtil = function(j$) { }; MatchersUtil.prototype.asymmetricMatch_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { - var asymmetricA = isAsymmetric(a), - asymmetricB = isAsymmetric(b), + var asymmetricA = j$.isAsymmetricEqualityTester_(a), + asymmetricB = j$.isAsymmetricEqualityTester_(b), shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters), result; @@ -104,8 +100,6 @@ getJasmineRequireObj().MatchersUtil = function(j$) { if (asymmetricA) { result = a.asymmetricMatch(b, shim); if (!result) { - // TODO: Do we want to build an asymmetric diff when the actual was an - // asymmeteric equality tester? Might be confusing. diffBuilder.recordMismatch(); } return result; @@ -328,7 +322,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { // Only use the cmpKey when one of the keys is asymmetric and the corresponding key matches, // otherwise explicitly look up the mapKey in the other Map since we want keys with unique // obj identity (that are otherwise equal) to not match. - if (isAsymmetric(mapKey) || isAsymmetric(cmpKey) && + if (j$.isAsymmetricEqualityTester_(mapKey) || j$.isAsymmetricEqualityTester_(cmpKey) && this.eq_(mapKey, cmpKey, aStack, bStack, customTesters, j$.NullDiffBuilder())) { mapValueB = b.get(cmpKey); } else { From 5096d9af4e9d02553f1f0a5947f81f9a0319001f Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 12 Feb 2020 13:52:58 -0800 Subject: [PATCH 8/8] Don't construct unnecessarily asymmetricEqualityTesterArgCompatShims This speeds up MatchersUtil#equals by about 6-7x. --- lib/jasmine-core/jasmine.js | 8 +++++--- spec/core/baseSpec.js | 21 +++++++++++++++++++++ src/core/base.js | 2 +- src/core/matchers/matchersUtil.js | 6 ++++-- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 65146cf8..cf4191b5 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -279,7 +279,7 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { }; j$.isAsymmetricEqualityTester_ = function(obj) { - return obj && j$.isA_('Function', obj.asymmetricMatch); + return obj ? j$.isA_('Function', obj.asymmetricMatch) : false; }; j$.getType_ = function(value) { @@ -4494,13 +4494,15 @@ getJasmineRequireObj().MatchersUtil = function(j$) { MatchersUtil.prototype.asymmetricMatch_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { var asymmetricA = j$.isAsymmetricEqualityTester_(a), asymmetricB = j$.isAsymmetricEqualityTester_(b), - shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters), + shim, result; - if (asymmetricA && asymmetricB) { + if (asymmetricA === asymmetricB) { return undefined; } + shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters); + if (asymmetricA) { result = a.asymmetricMatch(b, shim); if (!result) { diff --git a/spec/core/baseSpec.js b/spec/core/baseSpec.js index 07f94e19..4fce2254 100644 --- a/spec/core/baseSpec.js +++ b/spec/core/baseSpec.js @@ -30,4 +30,25 @@ describe('base helpers', function() { }, 100); }); }); + + describe('isAsymmetricEqualityTester_', function() { + it('returns false when the argument is falsy', function() { + expect(jasmineUnderTest.isAsymmetricEqualityTester_(null)).toBe(false); + }); + + it('returns false when the argument does not have a asymmetricMatch property', function() { + var obj = {}; + expect(jasmineUnderTest.isAsymmetricEqualityTester_(obj)).toBe(false); + }); + + it("returns false when the argument's asymmetricMatch is not a function", function() { + var obj = { asymmetricMatch: 'yes' }; + expect(jasmineUnderTest.isAsymmetricEqualityTester_(obj)).toBe(false); + }); + + it("returns true when the argument's asymmetricMatch is a function", function() { + var obj = { asymmetricMatch: function() {} }; + expect(jasmineUnderTest.isAsymmetricEqualityTester_(obj)).toBe(true); + }); + }); }); diff --git a/src/core/base.js b/src/core/base.js index 37aa692e..e86a12c8 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -112,7 +112,7 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { }; j$.isAsymmetricEqualityTester_ = function(obj) { - return obj && j$.isA_('Function', obj.asymmetricMatch); + return obj ? j$.isA_('Function', obj.asymmetricMatch) : false; }; j$.getType_ = function(value) { diff --git a/src/core/matchers/matchersUtil.js b/src/core/matchers/matchersUtil.js index 6fa812bf..8ee6368e 100644 --- a/src/core/matchers/matchersUtil.js +++ b/src/core/matchers/matchersUtil.js @@ -90,13 +90,15 @@ getJasmineRequireObj().MatchersUtil = function(j$) { MatchersUtil.prototype.asymmetricMatch_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { var asymmetricA = j$.isAsymmetricEqualityTester_(a), asymmetricB = j$.isAsymmetricEqualityTester_(b), - shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters), + shim, result; - if (asymmetricA && asymmetricB) { + if (asymmetricA === asymmetricB) { return undefined; } + shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters); + if (asymmetricA) { result = a.asymmetricMatch(b, shim); if (!result) {