diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 6761367b..fc466dbe 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -73,13 +73,22 @@ var getJasmineRequireObj = (function(jasmineGlobal) { j$.buildExpectationResult = jRequire.buildExpectationResult(j$); j$.noopTimer = jRequire.noopTimer(); j$.JsApiReporter = jRequire.JsApiReporter(j$); - j$.matchersUtil = jRequire.matchersUtil(j$); + 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: [], + 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$); @@ -97,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$); @@ -1188,6 +1198,7 @@ getJasmineRequireObj().Env = function(j$) { } var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { customMatchers[matcherName] = matchersToAdd[matcherName]; } @@ -1201,11 +1212,24 @@ getJasmineRequireObj().Env = function(j$) { } var customAsyncMatchers = runnableResources[currentRunnable().id].customAsyncMatchers; + for (var matcherName in matchersToAdd) { customAsyncMatchers[matcherName] = matchersToAdd[matcherName]; } }; + 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); @@ -1219,10 +1243,28 @@ getJasmineRequireObj().Env = function(j$) { return 'suite' + nextSuiteId++; }; + var makePrettyPrinter = function() { + var customObjectFormatters = + runnableResources[currentRunnable().id].customObjectFormatters; + return j$.makePrettyPrinter(customObjectFormatters); + }; + + 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: j$.matchersUtil, - customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + matchersUtil: makeMatchersUtil(), + customEqualityTesters: customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, actual: actual, addExpectationResult: addExpectationResult @@ -1261,7 +1303,7 @@ getJasmineRequireObj().Env = function(j$) { var asyncExpectationFactory = function(actual, spec, runableType) { return j$.Expectation.asyncFactory({ - util: j$.matchersUtil, + matchersUtil: makeMatchersUtil(), customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customAsyncMatchers: runnableResources[spec.id].customAsyncMatchers, actual: actual, @@ -1290,7 +1332,8 @@ getJasmineRequireObj().Env = function(j$) { customMatchers: {}, customAsyncMatchers: {}, customSpyStrategies: {}, - defaultStrategyFn: undefined + defaultStrategyFn: undefined, + customObjectFormatters: [] }; if (runnableResources[parentRunnableId]) { @@ -2083,7 +2126,7 @@ getJasmineRequireObj().Env = function(j$) { message += error; } else { // pretty print all kind of objects. This includes arrays. - message += j$.pp(error); + message += makePrettyPrinter()(error); } } @@ -2311,7 +2354,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) + '.'); } @@ -2325,7 +2368,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; } } @@ -2333,8 +2376,8 @@ getJasmineRequireObj().ArrayContaining = function(j$) { return true; }; - ArrayContaining.prototype.jasmineToString = function () { - return ''; + ArrayContaining.prototype.jasmineToString = function (pp) { + return ''; }; return ArrayContaining; @@ -2346,7 +2389,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) + '.'); } @@ -2357,7 +2400,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; } } @@ -2365,8 +2408,8 @@ getJasmineRequireObj().ArrayWithExactContents = function(j$) { return true; }; - ArrayWithExactContents.prototype.jasmineToString = function() { - return ''; + ArrayWithExactContents.prototype.jasmineToString = function(pp) { + return ''; }; return ArrayWithExactContents; @@ -2422,7 +2465,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; @@ -2432,8 +2475,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(); @@ -2448,8 +2491,8 @@ getJasmineRequireObj().MapContaining = function(j$) { return hasAllMatches; }; - MapContaining.prototype.jasmineToString = function() { - return ''; + MapContaining.prototype.jasmineToString = function(pp) { + return ''; }; return MapContaining; @@ -2512,13 +2555,13 @@ 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+'\'.'); } if (typeof(other) !== 'object') { return false; } 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; } } @@ -2526,10 +2569,10 @@ getJasmineRequireObj().ObjectContaining = function(j$) { return true; }; - ObjectContaining.prototype.valuesForDiff_ = function(other) { + ObjectContaining.prototype.valuesForDiff_ = function(other, pp) { if (!j$.isObject_(other)) { return { - self: this.jasmineToString(), + self: this.jasmineToString(pp), other: other }; } @@ -2547,8 +2590,8 @@ getJasmineRequireObj().ObjectContaining = function(j$) { }; }; - ObjectContaining.prototype.jasmineToString = function() { - return ''; + ObjectContaining.prototype.jasmineToString = function(pp) { + return ''; }; return ObjectContaining; @@ -2563,17 +2606,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(); } @@ -2587,8 +2630,8 @@ getJasmineRequireObj().SetContaining = function(j$) { return hasAllMatches; }; - SetContaining.prototype.jasmineToString = function() { - return ''; + SetContaining.prototype.jasmineToString = function(pp) { + return ''; }; return SetContaining; @@ -2630,6 +2673,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 @@ -3476,7 +3625,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(); @@ -3488,7 +3637,7 @@ getJasmineRequireObj().Expectation = function(j$) { args = args.slice(); args.unshift(true); args.unshift(matcherName); - return util.buildFailureMessage.apply(null, args); + return matchersUtil.buildFailureMessage.apply(matchersUtil, args); } function negate(result) { @@ -3570,7 +3719,7 @@ getJasmineRequireObj().ExpectationFilterChain = function() { result, matcherName, args, - util + matchersUtil ) { return this.callFirst_('buildFailureMessage', arguments).result; }; @@ -3688,7 +3837,9 @@ getJasmineRequireObj().buildExpectationResult = function(j$) { 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() {}; @@ -3706,7 +3857,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; }; @@ -3722,7 +3873,7 @@ getJasmineRequireObj().Expector = function(j$) { result, this.matcherName, this.args, - this.util, + this.matchersUtil, defaultMessage ); return this.filters.modifyFailureMessage(msg || defaultMessage()); @@ -3732,7 +3883,10 @@ 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.matchersUtil.buildFailureMessage.apply( + self.matchersUtil, + args + ); } else if (j$.isFunction_(result.message)) { return result.message(); } else { @@ -3856,7 +4010,8 @@ getJasmineRequireObj().GlobalErrors = function(j$) { var browserRejectionHandler = function browserRejectionHandler(event) { if (j$.isError_(event.reason)) { - event.reason.jasmineMessage = 'Unhandled promise rejection: ' + event.reason; + event.reason.jasmineMessage = + 'Unhandled promise rejection: ' + event.reason; onerror(event.reason); } else { onerror('Unhandled promise rejection: ' + event.reason); @@ -3906,7 +4061,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)) { @@ -3934,7 +4089,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { * @example * return expectAsync(aPromise).toBeRejectedWith({prop: 'value'}); */ - return function toBeRejectedWith(util, customEqualityTesters) { + return function toBeRejectedWith(matchersUtil) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { @@ -3944,7 +4099,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( @@ -3955,7 +4110,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { }; }, function(actualValue) { - if (util.equals(actualValue, expectedValue, customEqualityTesters)) { + if (matchersUtil.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' @@ -3963,7 +4118,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) + '.' }; } } @@ -3989,14 +4144,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() { @@ -4005,15 +4160,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)) { @@ -4030,7 +4185,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) { @@ -4048,7 +4203,7 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) { } - function getExpectedFromArgs(arg1, arg2) { + function getExpectedFromArgs(arg1, arg2, matchersUtil) { var error, message; if (isErrorConstructor(arg1)) { @@ -4062,7 +4217,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)) }; } @@ -4083,7 +4238,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)) { @@ -4112,7 +4267,7 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { * @example * return expectAsync(aPromise).toBeResolvedTo({prop: 'value'}); */ - return function toBeResolvedTo(util, customEqualityTesters) { + return function toBeResolvedTo(matchersUtil) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { @@ -4122,12 +4277,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, customEqualityTesters)) { + if (matchersUtil.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' @@ -4135,7 +4290,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) + '.' }; } }, @@ -4151,19 +4306,54 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { }; }; -getJasmineRequireObj().DiffBuilder = function(j$) { - return function DiffBuilder() { - var path = new j$.ObjectPath(), - mismatches = []; +getJasmineRequireObj().DiffBuilder = function (j$) { + return function DiffBuilder(config) { + 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)); + 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) { @@ -4174,86 +4364,118 @@ getJasmineRequireObj().DiffBuilder = function(j$) { } }; - function defaultFormatter (actual, expected, path) { + function defaultFormatter(actual, expected, path, prettyPrinter) { + return wrapPrettyPrinted(prettyPrinter(actual), prettyPrinter(expected), path); + } + + function wrapPrettyPrinted(actual, expected, path) { return 'Expected ' + path + (path.depth() ? ' = ' : '') + - j$.pp(actual) + + actual + ' to equal ' + - j$.pp(expected) + + expected + '.'; } }; }; -getJasmineRequireObj().matchersUtil = function(j$) { - // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? +getJasmineRequireObj().MatchersUtil = function(j$) { + // TODO: convert all uses of j$.pp to use the injected pp - return { - equals: equals, + /** + * _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() {}; + }; - 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 + '.'; + /** + * 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); } + + 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 self = this; + 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 ' + + self.pp(actual) + + (isNot ? ' not ' : ' ') + + englishyPredicate; + + if (expected.length > 0) { + for (var i = 0; i < expected.length; i++) { + if (i > 0) { + message += ','; + } + message += ' ' + self.pp(expected[i]); + } + } + + return message + '.'; }; function isAsymmetric(obj) { return obj && j$.isA_('Function', obj.asymmetricMatch); } - function asymmetricDiff(a, b, aStack, bStack, customTesters, diffBuilder) { + MatchersUtil.prototype.asymmetricDiff_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { if (j$.isFunction_(b.valuesForDiff_)) { var values = b.valuesForDiff_(a); - eq(values.other, values.self, aStack, bStack, customTesters, diffBuilder); + this.eq_(values.other, values.self, aStack, bStack, customTesters, diffBuilder); } else { - diffBuilder.record(a, b); + diffBuilder.recordMismatch(); } - } + }; - function asymmetricMatch(a, b, aStack, bStack, customTesters, diffBuilder) { + MatchersUtil.prototype.asymmetricMatch_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { var asymmetricA = isAsymmetric(a), asymmetricB = isAsymmetric(b), + shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters), result; if (asymmetricA && asymmetricB) { @@ -4261,37 +4483,56 @@ getJasmineRequireObj().matchersUtil = function(j$) { } if (asymmetricA) { - result = a.asymmetricMatch(b, customTesters); + 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.record(a, b); + diffBuilder.recordMismatch(); } return result; } if (asymmetricB) { - result = b.asymmetricMatch(a, customTesters); + result = b.asymmetricMatch(a, shim); if (!result) { - asymmetricDiff(a, b, aStack, bStack, customTesters, diffBuilder); + this.asymmetricDiff_(a, b, aStack, bStack, customTesters, diffBuilder); } return result; } - } + }; - function equals(a, b, customTesters, diffBuilder) { - customTesters = customTesters || []; + /** + * 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; + + if (isDiffBuilder(customTestersOrDiffBuilder)) { + diffBuilder = customTestersOrDiffBuilder; + } else { + customTesters = customTestersOrDiffBuilder; + diffBuilder = diffBuilderOrNothing; + } + + customTesters = customTesters || this.customTesters_; diffBuilder = diffBuilder || j$.NullDiffBuilder(); + diffBuilder.setRoots(a, b); - 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, aStack, bStack, customTesters, diffBuilder); + var asymmetricResult = this.asymmetricMatch_(a, b, aStack, bStack, customTesters, diffBuilder); if (!j$.util.isUndefined(asymmetricResult)) { return asymmetricResult; } @@ -4300,7 +4541,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; } @@ -4309,7 +4550,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; } @@ -4319,7 +4560,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; } @@ -4327,13 +4568,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) { @@ -4343,7 +4584,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]': @@ -4351,7 +4592,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { // other numeric values. result = a != +a ? b != +b : (a === 0 && b === 0 ? 1 / a == 1 / b : a == +b); if (!result) { - diffBuilder.record(a, b); + diffBuilder.recordMismatch(); } return result; case '[object Date]': @@ -4361,7 +4602,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. @@ -4372,7 +4613,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { a.ignoreCase == b.ignoreCase; } if (typeof a != 'object' || typeof b != 'object') { - diffBuilder.record(a, b); + diffBuilder.recordMismatch(); return false; } @@ -4382,12 +4623,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; } @@ -4417,7 +4658,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { diffBuilder.withPath('length', function() { if (aLength !== bLength) { - diffBuilder.record(aLength, bLength); + diffBuilder.recordMismatch(); result = false; } }); @@ -4425,10 +4666,10 @@ 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.recordMismatch(actualArrayIsLongerFormatter.bind(null, self.pp)); 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; } }); } @@ -4437,7 +4678,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; } @@ -4469,22 +4710,22 @@ 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()); } } 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; } @@ -4518,7 +4759,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); @@ -4529,7 +4770,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { } if (!result) { - diffBuilder.record(a, b); + diffBuilder.recordMismatch(); return false; } } else { @@ -4542,7 +4783,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { a instanceof aCtor && b instanceof bCtor && !(aCtor instanceof aCtor && bCtor instanceof bCtor)) { - diffBuilder.record(a, b, constructorsAreDifferentFormatter); + diffBuilder.recordMismatch(constructorsAreDifferentFormatter.bind(null, this.pp)); return false; } } @@ -4553,7 +4794,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.recordMismatch(objectKeysAreDifferentFormatter.bind(null, this.pp)); return false; } @@ -4561,13 +4802,13 @@ getJasmineRequireObj().matchersUtil = function(j$) { key = aKeys[i]; // Deep compare each member if (!j$.util.has(b, key)) { - diffBuilder.record(a, b, objectKeysAreDifferentFormatter); + diffBuilder.recordMismatch(objectKeysAreDifferentFormatter.bind(null, this.pp)); result = false; continue; } 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; } }); @@ -4582,7 +4823,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { bStack.pop(); return result; - } + }; function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : @@ -4618,11 +4859,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()) { @@ -4640,7 +4881,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'; } @@ -4648,25 +4889,94 @@ 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; } + + function isDiffBuilder(obj) { + 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. @@ -4695,7 +5005,8 @@ getJasmineRequireObj().NullDiffBuilder = function(j$) { withPath: function(_, block) { block(); }, - record: function() {} + setRoots: function() {}, + recordMismatch: function() {} }; }; }; @@ -4713,10 +5024,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; }; @@ -4776,7 +5101,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 { @@ -4786,7 +5111,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; @@ -4962,11 +5287,11 @@ getJasmineRequireObj().toBeInstanceOf = function(j$) { * expect(3).toBeInstanceOf(Number); * expect(new Error()).toBeInstanceOf(Error); */ - function toBeInstanceOf(util, customEqualityTesters) { + 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; @@ -5052,7 +5377,7 @@ getJasmineRequireObj().toBeNaN = function(j$) { * @example * expect(thing).toBeNaN(); */ - function toBeNaN() { + function toBeNaN(matchersUtil) { return { compare: function(actual) { var result = { @@ -5062,7 +5387,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; @@ -5082,7 +5407,7 @@ getJasmineRequireObj().toBeNegativeInfinity = function(j$) { * @example * expect(thing).toBeNegativeInfinity(); */ - function toBeNegativeInfinity() { + function toBeNegativeInfinity(matchersUtil) { return { compare: function(actual) { var result = { @@ -5092,7 +5417,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; @@ -5134,7 +5459,7 @@ getJasmineRequireObj().toBePositiveInfinity = function(j$) { * @example * expect(thing).toBePositiveInfinity(); */ - function toBePositiveInfinity() { + function toBePositiveInfinity(matchersUtil) { return { compare: function(actual) { var result = { @@ -5144,7 +5469,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; @@ -5232,14 +5557,12 @@ getJasmineRequireObj().toContain = function() { * expect(array).toContain(anElement); * expect(string).toContain(substring); */ - function toContain(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - + function toContain(matchersUtil) { return { compare: function(actual, expected) { return { - pass: util.contains(actual, expected, customEqualityTesters) + pass: matchersUtil.contains(actual, expected) }; } }; @@ -5258,17 +5581,15 @@ getJasmineRequireObj().toEqual = function(j$) { * @example * expect(bigObject).toEqual({"foo": ['bar', 'baz']}); */ - function toEqual(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - + function toEqual(matchersUtil) { return { compare: function(actual, expected) { var result = { pass: false }, - diffBuilder = j$.DiffBuilder(); + diffBuilder = j$.DiffBuilder({prettyPrinter: matchersUtil.pp}); - result.pass = util.equals(actual, expected, customEqualityTesters, diffBuilder); + result.pass = matchersUtil.equals(actual, expected, diffBuilder); // TODO: only set error message if test fails result.message = diffBuilder.getMessage(); @@ -5294,13 +5615,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) { @@ -5334,14 +5655,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 }; @@ -5396,11 +5717,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), @@ -5438,7 +5759,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { * @example * expect(mySpy).toHaveBeenCalledWith('foo', 'bar', 2); */ - function toHaveBeenCalledWith(util, customEqualityTesters) { + function toHaveBeenCalledWith(matchersUtil) { return { compare: function() { var args = Array.prototype.slice.call(arguments, 0), @@ -5447,40 +5768,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, customEqualityTesters)) { + 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, customEqualityTesters, 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'); @@ -5507,11 +5828,11 @@ getJasmineRequireObj().toHaveClass = function(j$) { * el.className = 'foo bar baz'; * expect(el).toHaveClass('bar'); */ - function toHaveClass(util, customEqualityTesters) { + 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 { @@ -5577,7 +5898,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 }, @@ -5602,16 +5923,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; @@ -5640,7 +5961,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), @@ -5658,7 +5979,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); @@ -5721,7 +6042,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; @@ -5731,9 +6052,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); } } @@ -5802,7 +6123,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; @@ -5832,14 +6153,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); + } } } @@ -5966,12 +6287,14 @@ getJasmineRequireObj().MockDate = function() { return MockDate; }; -getJasmineRequireObj().pp = function(j$) { - function PrettyPrinter() { +getJasmineRequireObj().makePrettyPrinter = function(j$) { + function SinglePrettyPrintRun(customObjectFormatters, pp) { + this.customObjectFormatters_ = customObjectFormatters; this.ppNestLevel_ = 0; this.seen = []; this.length = 0; this.stringParts = []; + this.pp_ = pp; } function hasCustomToString(value) { @@ -5989,10 +6312,14 @@ getJasmineRequireObj().pp = function(j$) { } } - PrettyPrinter.prototype.format = function(value) { + 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'); @@ -6001,7 +6328,7 @@ getJasmineRequireObj().pp = 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)) { @@ -6063,7 +6390,11 @@ getJasmineRequireObj().pp = function(j$) { } }; - PrettyPrinter.prototype.iterateObject = function(obj, fn) { + SinglePrettyPrintRun.prototype.applyCustomFormatters_ = function(value) { + return customFormat(value, this.customObjectFormatters_); + }; + + SinglePrettyPrintRun.prototype.iterateObject = function(obj, fn) { var objKeys = keys(obj, j$.isArray_(obj)); var isGetter = function isGetter(prop) {}; @@ -6082,15 +6413,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; @@ -6126,7 +6457,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; @@ -6151,7 +6482,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; @@ -6176,7 +6507,7 @@ getJasmineRequireObj().pp = function(j$) { this.append(' )'); }; - PrettyPrinter.prototype.emitObject = function(obj) { + SinglePrettyPrintRun.prototype.emitObject = function(obj) { var ctor = obj.constructor, constructorName; @@ -6212,7 +6543,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, @@ -6228,7 +6559,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, @@ -6254,7 +6585,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) { @@ -6264,7 +6599,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') { @@ -6328,10 +6663,33 @@ getJasmineRequireObj().pp = function(j$) { return extraKeys; } - return function(value) { - var prettyPrinter = new PrettyPrinter(); - prettyPrinter.format(value); - return prettyPrinter.stringParts.join(''); + + 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); + prettyPrinter.format(value); + return prettyPrinter.stringParts.join(''); + }; + + pp.customFormat_ = function(value) { + return customFormat(value, customObjectFormatters); + }; + + return pp; }; }; @@ -6945,6 +7303,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 @@ -7025,6 +7397,11 @@ getJasmineRequireObj().Spy = function(j$) { }; })(); + 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} * @constructor @@ -7220,7 +7597,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..40f63731 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: jasmineUnderTest.matchersUtil, + matchersUtil: new jasmineUnderTest.MatchersUtil({ pp: pp }), actual: actual, addExpectationResult: addExpectationResult }); @@ -47,7 +48,9 @@ describe('AsyncExpectation', function() { var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.reject(), expectation = jasmineUnderTest.Expectation.asyncFactory({ - util: jasmineUnderTest.matchersUtil, + matchersUtil: new jasmineUnderTest.MatchersUtil({ + pp: function() {} + }), actual: actual, addExpectationResult: addExpectationResult }); @@ -91,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'; } @@ -100,7 +103,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: Promise.reject('rejected'), addExpectationResult: addExpectationResult, - util: util + matchersUtil: matchersUtil }); return expectation @@ -119,16 +122,17 @@ 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'; - } + }, + pp: jasmineUnderTest.makePrettyPrinter() }, addExpectationResult = jasmine.createSpy('addExpectationResult'), expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: Promise.reject('b'), addExpectationResult: addExpectationResult, - util: util + matchersUtil: matchersUtil }); return expectation @@ -149,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'; } @@ -159,7 +163,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, - util: util + matchersUtil: matchersUtil }); return expectation @@ -180,10 +184,11 @@ describe('AsyncExpectation', function() { var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.resolve(), + pp = jasmineUnderTest.makePrettyPrinter(), expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, - util: jasmineUnderTest.matchersUtil + matchersUtil: new jasmineUnderTest.MatchersUtil({ pp: pp }) }); return expectation @@ -208,7 +213,9 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, - util: jasmineUnderTest.matchersUtil + matchersUtil: new jasmineUnderTest.MatchersUtil({ + pp: jasmineUnderTest.makePrettyPrinter() + }) }); return expectation @@ -256,7 +263,7 @@ describe('AsyncExpectation', function() { matchers = { toFoo: matcherFactory }, - util = { + matchersUtil = { buildFailureMessage: jasmine.createSpy('buildFailureMessage') }, customEqualityTesters = ['a'], @@ -264,7 +271,7 @@ describe('AsyncExpectation', function() { expectation; expectation = jasmineUnderTest.Expectation.asyncFactory({ - util: util, + matchersUtil: matchersUtil, customAsyncMatchers: matchers, customEqualityTesters: customEqualityTesters, actual: 'an actual', @@ -273,7 +280,7 @@ describe('AsyncExpectation', function() { return expectation.toFoo('hello').then(function() { expect(matcherFactory).toHaveBeenCalledWith( - util, + matchersUtil, customEqualityTesters ); }); @@ -292,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 @@ -322,7 +329,7 @@ describe('AsyncExpectation', function() { }; } }, - util = { + matchersUtil = { buildFailureMessage: jasmine.createSpy('buildFailureMessage') }, addExpectationResult = jasmine.createSpy('addExpectationResult'), @@ -335,7 +342,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ customAsyncMatchers: matchers, - util: util, + matchersUtil: matchersUtil, actual: 'an actual', addExpectationResult: addExpectationResult }); @@ -365,7 +372,7 @@ describe('AsyncExpectation', function() { }; } }, - util = { + matchersUtil = { buildFailureMessage: function() { return ''; } @@ -380,7 +387,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ customAsyncMatchers: matchers, - util: util, + matchersUtil: matchersUtil, actual: 'an actual', addExpectationResult: addExpectationResult }); @@ -536,7 +543,7 @@ describe('AsyncExpectation', function() { }; } }, - util = { + matchersUtil = { buildFailureMessage: function() { return 'default message'; } @@ -553,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/EnvSpec.js b/spec/core/EnvSpec.js index 61b962b1..e32d8eda 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -312,4 +312,78 @@ describe('Env', function() { expect(globalErrors.install).toHaveBeenCalled(); }); }); + + 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, + expectationFactory; + spyOn(jasmineUnderTest, 'MatchersUtil'); + spyOn(jasmineUnderTest, 'makePrettyPrinter').and.returnValue(prettyPrinter); + spyOn(jasmineUnderTest, 'Spec').and.callFake(function(options) { + expectationFactory = options.expectationFactory; + specInstance = new RealSpec(options); + return specInstance; + }); + + 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 + }); + done(); + } + }); + + env.execute(); + }); + + 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, + asyncExpectationFactory; + spyOn(jasmineUnderTest, 'MatchersUtil'); + spyOn(jasmineUnderTest, 'makePrettyPrinter').and.returnValue(prettyPrinter); + spyOn(jasmineUnderTest, 'Spec').and.callFake(function(options) { + asyncExpectationFactory = options.asyncExpectationFactory; + specInstance = new RealSpec(options); + return specInstance; + }); + + 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 + }); + done(); + } + }); + + env.execute(); + }); }); 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 599b0558..6ab37713 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 }); @@ -658,9 +661,10 @@ describe('Expectation', function() { } }, addExpectationResult = jasmine.createSpy('addExpectationResult'), + pp = jasmineUnderTest.makePrettyPrinter(), expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, - util: jasmineUnderTest.matchersUtil, + matchersUtil: new jasmineUnderTest.MatchersUtil({ pp: pp }), actual: 'an actual', addExpectationResult: addExpectationResult }); diff --git a/spec/core/PrettyPrintSpec.js b/spec/core/PrettyPrintSpec.js index 5c397eb5..6c43cbd4 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() + ')'); }); describe('with a spy object', function() { - var env; + var env, pp; beforeEach(function() { env = new jasmineUnderTest.Env(); + pp = jasmineUnderTest.makePrettyPrinter(); }); afterEach(function() { @@ -353,19 +360,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() {} - }; + someFunction: function() {} + }, + env = new jasmineUnderTest.Env(), + pp = jasmineUnderTest.makePrettyPrinter(); var spyRegistry = new jasmineUnderTest.SpyRegistry({ currentSpies: function() { @@ -379,30 +384,40 @@ 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 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 = { 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 @@ -413,20 +428,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() {}; })(); @@ -436,17 +453,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() { @@ -476,8 +495,72 @@ 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 })' ); }); + + 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)).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)).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/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..33e411e6 100644 --- a/spec/core/asymmetric_equality/ArrayContainingSpec.js +++ b/spec/core/asymmetric_equality/ArrayContainingSpec.js @@ -15,32 +15,41 @@ 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() { - 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() { @@ -52,7 +61,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..fd0bb30c 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,21 +16,28 @@ 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() { - 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() { @@ -41,7 +49,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..c9ea83a6 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() { @@ -176,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 cdcbf19f..bec44fe2 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 object actual to an empty object", function() { var containing = new jasmineUnderTest.ObjectContaining({}); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch({foo: 1})).toBe(true); + expect(containing.asymmetricMatch({foo: 1}, matchersUtil)).toBe(true); }); it("does not match when the actual is not an object", function() { @@ -24,58 +25,72 @@ 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() { - 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/CustomAsyncMatchersSpec.js b/spec/core/integration/CustomAsyncMatchersSpec.js index 9f0f4a16..e1a8286c 100644 --- a/spec/core/integration/CustomAsyncMatchersSpec.js +++ b/spec/core/integration/CustomAsyncMatchersSpec.js @@ -115,4 +115,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 af8f4cbc..c9812949 100644 --- a/spec/core/integration/CustomMatchersSpec.js +++ b/spec/core/integration/CustomMatchersSpec.js @@ -1,8 +1,8 @@ -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}); }); @@ -11,13 +11,13 @@ describe("Custom Matchers (Integration)", function() { env.cleanup_(); }); - 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}; } } } @@ -87,6 +87,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") { @@ -233,4 +235,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/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/EnvSpec.js b/spec/core/integration/EnvSpec.js index 490e9d94..8ecd8ee4 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -2584,4 +2584,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 25af3217..7f19a95c 100644 --- a/spec/core/integration/MatchersSpec.js +++ b/spec/core/integration/MatchersSpec.js @@ -56,6 +56,27 @@ describe('Matchers (Integration)', function() { }); } + function verifyFailsWithCustomObjectFormatters(config) { + it('uses custom object formatters', function(done) { + 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(); @@ -107,6 +128,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(); @@ -223,6 +268,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() { @@ -233,6 +288,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() { @@ -253,6 +318,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() { @@ -276,6 +351,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() { @@ -299,6 +385,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() { @@ -309,6 +406,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() { @@ -365,6 +473,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() { @@ -423,6 +545,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() { @@ -455,12 +590,28 @@ 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) { 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() { @@ -471,6 +622,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() { @@ -485,5 +649,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..6d82c3f8 100644 --- a/spec/core/matchers/DiffBuilderSpec.js +++ b/spec/core/matchers/DiffBuilderSpec.js @@ -1,47 +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) { + return '|' + val + '|'; + }, + diffBuilder = jasmineUnderTest.DiffBuilder({prettyPrinter: prettyPrinter}); + prettyPrinter.customFormat_ = function () { + }; + + 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 () { + var diffFormatter = jasmine.createSpy('diffFormatter'), + prettyPrinter = function () { + }, + diffBuilder = jasmineUnderTest.DiffBuilder({prettyPrinter: prettyPrinter}); + prettyPrinter.customFormat_ = function () { + }; + + 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/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..97260d59 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + 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..2c02a03a 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({pp: jasmineUnderTest.makePrettyPrinter()}), + 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({pp: jasmineUnderTest.makePrettyPrinter()}), + 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({pp: jasmineUnderTest.makePrettyPrinter()}), + 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..c68eb0e0 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + 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({pp: new jasmineUnderTest.makePrettyPrinter()}), + matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.resolve(true); return matcher.compare(actual, true).then(function(result) { @@ -56,7 +60,11 @@ describe('#toBeResolvedTo', function() { jasmine.getEnv().requirePromises(); var customEqualityTesters = [function() { return true; }], - matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(jasmineUnderTest.matchersUtil, customEqualityTesters), + matchersUtil = new jasmineUnderTest.MatchersUtil({ + customTesters: customEqualityTesters, + pp: jasmineUnderTest.makePrettyPrinter() + }), + matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.resolve('actual'); return matcher.compare(actual, 'expected').then(function(result) { @@ -65,7 +73,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({pp: jasmineUnderTest.makePrettyPrinter()}), + 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 038d101a..4894fefa 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() { describe('Properties', function() { var fc; @@ -72,178 +79,210 @@ describe("matchersUtil", 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); + 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() { @@ -256,6 +295,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')); @@ -263,17 +304,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); }); @@ -281,6 +323,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')); @@ -289,16 +332,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); }); }); @@ -311,43 +354,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() { @@ -355,135 +402,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], pp: function() {}}); + + 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], pp: function() {}}); + 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], pp: function() {}}); + + 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()], pp: function() {}}); - 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], pp: function() {}}), + 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); @@ -491,12 +612,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); @@ -504,12 +626,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]); @@ -531,12 +654,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]]); @@ -544,11 +668,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); @@ -558,11 +683,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); @@ -571,36 +697,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); @@ -608,17 +738,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() { @@ -673,105 +804,160 @@ describe("matchersUtil", function() { return { self: 'asymmetric tester value', other: 'other value' - } + }; } }, - diffBuilder = jasmine.createSpyObj('diffBuilder', ['record', 'withPath']); - diffBuilder.withPath.and.callFake(function(p, block) { block() }); - jasmineUnderTest.matchersUtil.equals({x: 42}, {x: tester}, [], diffBuilder); + actual = {x: 42}, + expected = {x: tester}, + diffBuilder = jasmine.createSpyObj('diffBuilder', ['recordMismatch', 'withPath', 'setRoots']); + diffBuilder.withPath.and.callFake(function(p, block) { block(); }); + jasmineUnderTest.matchersUtil.equals(actual, expected, [], diffBuilder); + + expect(diffBuilder.setRoots).toHaveBeenCalledWith(actual, expected); expect(diffBuilder.withPath).toHaveBeenCalledWith('x', jasmine.any(Function)); - expect(diffBuilder.record). toHaveBeenCalledWith( - 'other value', 'asymmetric tester value' - ); + expect(diffBuilder.recordMismatch). toHaveBeenCalledWith(); }); it("records both objects when the tester does not implement valuesForDiff", function() { var tester = { asymmetricMatch: function() { return false; }, }, - diffBuilder = jasmine.createSpyObj('diffBuilder', ['record', 'withPath']); - diffBuilder.withPath.and.callFake(function(p, block) { block() }); - jasmineUnderTest.matchersUtil.equals({x: 42}, {x: tester}, [], diffBuilder); + actual = {x: 42}, + expected = {x: tester}, + diffBuilder = jasmine.createSpyObj('diffBuilder', ['recordMismatch', 'withPath', 'setRoots']); + diffBuilder.withPath.and.callFake(function(p, block) { block(); }); + jasmineUnderTest.matchersUtil.equals(actual, expected, [], diffBuilder); + expect(diffBuilder.setRoots).toHaveBeenCalledWith(actual, expected); expect(diffBuilder.withPath).toHaveBeenCalledWith('x', jasmine.any(Function)); - expect(diffBuilder.record). toHaveBeenCalledWith(42, tester); + expect(diffBuilder.recordMismatch). toHaveBeenCalledWith(); }); }); + + 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, '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.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, '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.recordMismatch).toHaveBeenCalled(); + }); }); 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], pp: function() {}}); + + 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); }); }); - describe("buildMessage", function() { + describe("buildFailureMessage", function() { it("builds an English sentence for a failure case", function() { var actual = "foo", name = "toBar", - message = jasmineUnderTest.matchersUtil.buildFailureMessage(name, false, actual); + pp = jasmineUnderTest.makePrettyPrinter(), + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: pp}), + message = matchersUtil.buildFailureMessage(name, false, actual); expect(message).toEqual("Expected 'foo' to bar."); }); @@ -780,7 +966,9 @@ describe("matchersUtil", function() { var actual = "foo", name = "toBar", isNot = true, - message = message = jasmineUnderTest.matchersUtil.buildFailureMessage(name, isNot, actual); + pp = jasmineUnderTest.makePrettyPrinter(), + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: pp}), + message = message = matchersUtil.buildFailureMessage(name, isNot, actual); expect(message).toEqual("Expected 'foo' not to bar."); }); @@ -788,9 +976,24 @@ 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"); + 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 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, expected1, expected2); + + 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 e065af57..4d74a6de 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 matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(matchersUtil), 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 matchersUtil = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), + matcher = jasmineUnderTest.matchers.toBe(matchersUtil), 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 matchersUtil = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), + matcher = jasmineUnderTest.matchers.toBe(matchersUtil), 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 matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(matchersUtil), 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 matchersUtil = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), + matcher = jasmineUnderTest.matchers.toBe(matchersUtil), result; result = matcher.compare([1], [1]); @@ -46,11 +51,24 @@ describe("toBe", function() { }); it("fails with a custom message when expected is an object", function() { - var matcher = jasmineUnderTest.matchers.toBe(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), + matcher = jasmineUnderTest.matchers.toBe(matchersUtil), result; result = matcher.compare({foo: "bar"}, {foo: "bar"}); 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]), + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: prettyPrinter}), + matcher = jasmineUnderTest.matchers.toBe(matchersUtil), + 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/toContainSpec.js b/spec/core/matchers/toContainSpec.js index a56d05e4..5fd1c8f8 100644 --- a/spec/core/matchers/toContainSpec.js +++ b/spec/core/matchers/toContainSpec.js @@ -1,26 +1,25 @@ 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); }); - 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 69651a81..3a1ce985 100644 --- a/spec/core/matchers/toEqualSpec.js +++ b/spec/core/matchers/toEqualSpec.js @@ -2,8 +2,10 @@ describe("toEqual", function() { "use strict"; function compareEquals(actual, expected) { - var util = jasmineUnderTest.matchersUtil, - matcher = jasmineUnderTest.matchers.toEqual(util); + var matchersUtil = new jasmineUnderTest.MatchersUtil({ + pp: jasmineUnderTest.makePrettyPrinter() + }), + matcher = jasmineUnderTest.matchers.toEqual(matchersUtil); var result = matcher.compare(actual, expected); @@ -11,37 +13,32 @@ 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: jasmineUnderTest.matchersUtil.DiffBuilder + 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); }); - 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), + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}), + matcher = jasmineUnderTest.matchers.toEqual(matchersUtil), 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); }); @@ -100,16 +97,66 @@ 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 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}}, + pp = jasmineUnderTest.makePrettyPrinter([formatter]), + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: pp}), + matcher = jasmineUnderTest.matchers.toEqual(matchersUtil), 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(compareEquals(actual, expected).message).toEqual(message); + expect(matcher.compare(actual, expected).message).toEqual(message); + }); + + 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}], + prettyPrinter = jasmineUnderTest.makePrettyPrinter([formatter]), + 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); + }); + + 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]), + 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'."; + + expect(matcher.compare(actual, expected).message).toEqual(message); }); it("reports extra and missing properties of the root-level object", function() { @@ -282,12 +329,31 @@ 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 for the value but not the type when reporting objects with different constructors", function () { + function Foo() {} + function Bar() {} + function formatter(x) { + if (x instanceof Foo || x instanceof Bar) { + 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]), + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: pp}), + matcher = jasmineUnderTest.matchers.toEqual(matchersUtil); + + expect(matcher.compare(actual, expected).message).toEqual(message); + }); + it("reports type mismatches at the root level", function () { function Foo() {} function Bar() {} @@ -299,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() {} @@ -830,6 +901,24 @@ 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) { + if (typeof x === 'number') { + return '|' + x + '|'; + } + } + + var actual = [1, 1, 2, 3, 5], + expected = [1, 1, 2, 3], + pp = jasmineUnderTest.makePrettyPrinter([formatter]), + matchersUtil = new jasmineUnderTest.MatchersUtil({pp: pp}), + matcher = jasmineUnderTest.matchers.toEqual(matchersUtil), + 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/spec/core/matchers/toHaveBeenCalledBeforeSpec.js b/spec/core/matchers/toHaveBeenCalledBeforeSpec.js index 53aa3fdb..266758fe 100644 --- a/spec/core/matchers/toHaveBeenCalledBeforeSpec.js +++ b/spec/core/matchers/toHaveBeenCalledBeforeSpec.js @@ -1,23 +1,25 @@ -describe("toHaveBeenCalledBefore", function () { - it("throws an exception when the actual is not a spy", function () { - var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - fn = function () { - }, - spy = new jasmineUnderTest.Spy('a spy'); +describe("toHaveBeenCalledBefore", function() { + it("throws an exception when the actual is not a spy", function() { + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore({ + pp: jasmineUnderTest.makePrettyPrinter() + }), + fn = function() {}, + spy = new jasmineUnderTest.Env().createSpy('a spy'); expect(function () { - matcher.compare(fn, spy) + matcher.compare(fn, spy); }).toThrowError(Error, /Expected a spy, but got Function./); }); - it("throws an exception when the expected is not a spy", function () { - var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - spy = new jasmineUnderTest.Spy('a spy'), - fn = function () { - }; + it("throws an exception when the expected is not a spy", function() { + var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore({ + pp: jasmineUnderTest.makePrettyPrinter() + }), + spy = new jasmineUnderTest.Env().createSpy('a spy'), + fn = function() {}; expect(function () { - matcher.compare(spy, fn) + matcher.compare(spy, fn); }).toThrowError(Error, /Expected a spy, but got Function./); }); diff --git a/spec/core/matchers/toHaveBeenCalledSpec.js b/spec/core/matchers/toHaveBeenCalledSpec.js index d39236c0..65106cf5 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 7b3ad2b5..e2c0c0ed 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 9cc3415b..5f391d6c 100644 --- a/spec/core/matchers/toHaveBeenCalledWithSpec.js +++ b/spec/core/matchers/toHaveBeenCalledWithSpec.js @@ -1,10 +1,11 @@ describe("toHaveBeenCalledWith", function() { it("passes when the actual was called with matching parameters", function() { - var util = { - contains: jasmine.createSpy('delegated-contains').and.returnValue(true) + 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.Spy('called-spy'), result; @@ -15,25 +16,24 @@ 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.Spy('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.Spy('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() { - var util = { - contains: jasmine.createSpy('delegated-contains').and.returnValue(false) + 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.Spy('uncalled spy'), result; @@ -43,8 +43,8 @@ describe("toHaveBeenCalledWith", function() { }); it("fails when the actual was called with different parameters", function() { - var util = jasmineUnderTest.matchersUtil, - matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), + var matchersUtil = new jasmineUnderTest.MatchersUtil({pp: jasmineUnderTest.makePrettyPrinter()}), + matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(matchersUtil), calledSpy = new jasmineUnderTest.Spy('called spy'), result; @@ -73,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..68fdf8be 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"); }, @@ -180,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(); }, @@ -196,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 }); @@ -215,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(); }, @@ -231,10 +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 = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(true) + var matchersUtil = { + equals: jasmine.createSpy('delegated-equal').and.returnValue(true), + pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(), + matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil), fn = function() { throw new TypeError("foo"); }, @@ -247,11 +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 = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(true) + var matchersUtil = { + equals: jasmine.createSpy('delegated-equal').and.returnValue(true), + pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(), - 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"}); }, @@ -266,10 +271,11 @@ 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) + var matchersUtil = { + equals: jasmine.createSpy('delegated-equal').and.returnValue(false), + pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(), + matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil), fn = function() { throw new TypeError("foo"); }, @@ -282,10 +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 = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(true) + var matchersUtil = { + equals: jasmine.createSpy('delegated-equal').and.returnValue(true), + pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(), + matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil), fn = function() { throw new TypeError("foo"); }, @@ -298,10 +305,11 @@ 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) + var matchersUtil = { + equals: jasmine.createSpy('delegated-equal').and.returnValue(false), + pp: jasmineUnderTest.makePrettyPrinter() }, - matcher = jasmineUnderTest.matchers.toThrowError(), + matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil), 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..7144b2b3 100644 --- a/spec/core/matchers/toThrowSpec.js +++ b/spec/core/matchers/toThrowSpec.js @@ -23,10 +23,11 @@ describe("toThrow", function() { }); it("passes if it throws but there is no expected", function() { - var util = { - equals: jasmine.createSpy('delegated-equal').and.returnValue(true) + 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; }, @@ -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; }, @@ -51,10 +54,11 @@ 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) + 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; }, @@ -67,10 +71,11 @@ 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) + 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; }, @@ -83,10 +88,11 @@ 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) + 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 694ccd6e..75bec185 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() { return { compare: function(actual, fullName, expectedFailures) { var foundRunnable = false, 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/spec/npmPackage/npmPackageSpec.js b/spec/npmPackage/npmPackageSpec.js index 5248d88c..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, customEquality) { + 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 6cc9329c..2cb9c2cc 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,11 +290,24 @@ getJasmineRequireObj().Env = function(j$) { } var customAsyncMatchers = runnableResources[currentRunnable().id].customAsyncMatchers; + for (var matcherName in matchersToAdd) { customAsyncMatchers[matcherName] = matchersToAdd[matcherName]; } }; + 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); @@ -307,10 +321,28 @@ getJasmineRequireObj().Env = function(j$) { return 'suite' + nextSuiteId++; }; + var makePrettyPrinter = function() { + var customObjectFormatters = + runnableResources[currentRunnable().id].customObjectFormatters; + return j$.makePrettyPrinter(customObjectFormatters); + }; + + 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: j$.matchersUtil, - customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + matchersUtil: makeMatchersUtil(), + customEqualityTesters: customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, actual: actual, addExpectationResult: addExpectationResult @@ -349,7 +381,7 @@ getJasmineRequireObj().Env = function(j$) { var asyncExpectationFactory = function(actual, spec, runableType) { return j$.Expectation.asyncFactory({ - util: j$.matchersUtil, + matchersUtil: makeMatchersUtil(), customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customAsyncMatchers: runnableResources[spec.id].customAsyncMatchers, actual: actual, @@ -378,7 +410,8 @@ getJasmineRequireObj().Env = function(j$) { customMatchers: {}, customAsyncMatchers: {}, customSpyStrategies: {}, - defaultStrategyFn: undefined + defaultStrategyFn: undefined, + customObjectFormatters: [] }; if (runnableResources[parentRunnableId]) { @@ -1171,7 +1204,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 37e1b7d6..940a8cfe 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(null, 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 78db5c1b..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(null, args); + return self.matchersUtil.buildFailureMessage.apply( + self.matchersUtil, + 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..5faa3bbf 100644 --- a/src/core/PrettyPrinter.js +++ b/src/core/PrettyPrinter.js @@ -1,9 +1,11 @@ -getJasmineRequireObj().pp = function(j$) { - function PrettyPrinter() { +getJasmineRequireObj().makePrettyPrinter = function(j$) { + function SinglePrettyPrintRun(customObjectFormatters, pp) { + this.customObjectFormatters_ = customObjectFormatters; this.ppNestLevel_ = 0; this.seen = []; this.length = 0; this.stringParts = []; + this.pp_ = pp; } function hasCustomToString(value) { @@ -21,10 +23,14 @@ getJasmineRequireObj().pp = function(j$) { } } - PrettyPrinter.prototype.format = function(value) { + 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().pp = 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,7 +101,11 @@ getJasmineRequireObj().pp = function(j$) { } }; - PrettyPrinter.prototype.iterateObject = function(obj, fn) { + SinglePrettyPrintRun.prototype.applyCustomFormatters_ = function(value) { + return customFormat(value, this.customObjectFormatters_); + }; + + SinglePrettyPrintRun.prototype.iterateObject = function(obj, fn) { var objKeys = keys(obj, j$.isArray_(obj)); var isGetter = function isGetter(prop) {}; @@ -114,15 +124,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 +168,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 +193,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 +218,7 @@ getJasmineRequireObj().pp = function(j$) { this.append(' )'); }; - PrettyPrinter.prototype.emitObject = function(obj) { + SinglePrettyPrintRun.prototype.emitObject = function(obj) { var ctor = obj.constructor, constructorName; @@ -244,7 +254,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 +270,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 +296,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 +310,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 +374,32 @@ getJasmineRequireObj().pp = function(j$) { return extraKeys; } - return function(value) { - var prettyPrinter = new PrettyPrinter(); - prettyPrinter.format(value); - return prettyPrinter.stringParts.join(''); + + 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); + prettyPrinter.format(value); + return prettyPrinter.stringParts.join(''); + }; + + pp.customFormat_ = function(value) { + return customFormat(value, customObjectFormatters); + }; + + return pp; }; }; diff --git a/src/core/Spy.js b/src/core/Spy.js index ffbb0103..7160f5d5 100644 --- a/src/core/Spy.js +++ b/src/core/Spy.js @@ -7,6 +7,11 @@ getJasmineRequireObj().Spy = function(j$) { }; })(); + 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} * @constructor @@ -202,7 +207,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..7faea8b4 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; } } @@ -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 37b8ee95..d2049047 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; } } @@ -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 061c2666..ba6b0ce9 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(); @@ -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 a0d69135..4a8f4385 100644 --- a/src/core/asymmetric_equality/ObjectContaining.js +++ b/src/core/asymmetric_equality/ObjectContaining.js @@ -28,13 +28,13 @@ 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+'\'.'); } if (typeof(other) !== 'object') { return false; } 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; } } @@ -42,10 +42,10 @@ getJasmineRequireObj().ObjectContaining = function(j$) { return true; }; - ObjectContaining.prototype.valuesForDiff_ = function(other) { + ObjectContaining.prototype.valuesForDiff_ = function(other, pp) { if (!j$.isObject_(other)) { return { - self: this.jasmineToString(), + self: this.jasmineToString(pp), other: other }; } @@ -63,8 +63,8 @@ getJasmineRequireObj().ObjectContaining = function(j$) { }; }; - 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 d9dd8fd2..f3ad3527 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(); } @@ -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..197e4c71 100644 --- a/src/core/matchers/DiffBuilder.js +++ b/src/core/matchers/DiffBuilder.js @@ -1,16 +1,51 @@ -getJasmineRequireObj().DiffBuilder = function(j$) { - return function DiffBuilder() { - var path = new j$.ObjectPath(), - mismatches = []; +getJasmineRequireObj().DiffBuilder = function (j$) { + return function DiffBuilder(config) { + 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)); + 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) { @@ -21,12 +56,16 @@ getJasmineRequireObj().DiffBuilder = function(j$) { } }; - function defaultFormatter (actual, expected, path) { + function defaultFormatter(actual, expected, path, prettyPrinter) { + return wrapPrettyPrinted(prettyPrinter(actual), prettyPrinter(expected), path); + } + + function wrapPrettyPrinted(actual, expected, path) { return 'Expected ' + path + (path.depth() ? ' = ' : '') + - j$.pp(actual) + + actual + ' to equal ' + - j$.pp(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/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/toBeRejectedWith.js b/src/core/matchers/async/toBeRejectedWith.js index 031230a1..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, customEqualityTesters) { + 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, customEqualityTesters)) { + 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/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/async/toBeResolvedTo.js b/src/core/matchers/async/toBeResolvedTo.js index 9d5aa370..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, customEqualityTesters) { + 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, customEqualityTesters)) { + 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 7183d5e7..e79dd075 100644 --- a/src/core/matchers/matchersUtil.js +++ b/src/core/matchers/matchersUtil.js @@ -1,72 +1,100 @@ -getJasmineRequireObj().matchersUtil = function(j$) { - // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? +getJasmineRequireObj().MatchersUtil = function(j$) { + // TODO: convert all uses of j$.pp to use the injected pp - return { - equals: equals, + /** + * _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() {}; + }; - 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 + '.'; + /** + * 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); } + + 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 self = this; + 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 ' + + self.pp(actual) + + (isNot ? ' not ' : ' ') + + englishyPredicate; + + if (expected.length > 0) { + for (var i = 0; i < expected.length; i++) { + if (i > 0) { + message += ','; + } + message += ' ' + self.pp(expected[i]); + } + } + + return message + '.'; }; function isAsymmetric(obj) { return obj && j$.isA_('Function', obj.asymmetricMatch); } - function asymmetricDiff(a, b, aStack, bStack, customTesters, diffBuilder) { + MatchersUtil.prototype.asymmetricDiff_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { if (j$.isFunction_(b.valuesForDiff_)) { var values = b.valuesForDiff_(a); - eq(values.other, values.self, aStack, bStack, customTesters, diffBuilder); + this.eq_(values.other, values.self, aStack, bStack, customTesters, diffBuilder); } else { - diffBuilder.record(a, b); + diffBuilder.recordMismatch(); } - } + }; - function asymmetricMatch(a, b, aStack, bStack, customTesters, diffBuilder) { + MatchersUtil.prototype.asymmetricMatch_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { var asymmetricA = isAsymmetric(a), asymmetricB = isAsymmetric(b), + shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters), result; if (asymmetricA && asymmetricB) { @@ -74,37 +102,56 @@ getJasmineRequireObj().matchersUtil = function(j$) { } if (asymmetricA) { - result = a.asymmetricMatch(b, customTesters); + 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.record(a, b); + diffBuilder.recordMismatch(); } return result; } if (asymmetricB) { - result = b.asymmetricMatch(a, customTesters); + result = b.asymmetricMatch(a, shim); if (!result) { - asymmetricDiff(a, b, aStack, bStack, customTesters, diffBuilder); + this.asymmetricDiff_(a, b, aStack, bStack, customTesters, diffBuilder); } return result; } - } + }; - function equals(a, b, customTesters, diffBuilder) { - customTesters = customTesters || []; + /** + * 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; + + if (isDiffBuilder(customTestersOrDiffBuilder)) { + diffBuilder = customTestersOrDiffBuilder; + } else { + customTesters = customTestersOrDiffBuilder; + diffBuilder = diffBuilderOrNothing; + } + + customTesters = customTesters || this.customTesters_; diffBuilder = diffBuilder || j$.NullDiffBuilder(); + diffBuilder.setRoots(a, b); - 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, aStack, bStack, customTesters, diffBuilder); + var asymmetricResult = this.asymmetricMatch_(a, b, aStack, bStack, customTesters, diffBuilder); if (!j$.util.isUndefined(asymmetricResult)) { return asymmetricResult; } @@ -113,7 +160,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 +169,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 +179,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 +187,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 +203,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 +211,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { // other numeric values. result = a != +a ? b != +b : (a === 0 && b === 0 ? 1 / a == 1 / b : a == +b); if (!result) { - diffBuilder.record(a, b); + diffBuilder.recordMismatch(); } return result; case '[object Date]': @@ -174,7 +221,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 +232,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 +242,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 +277,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { diffBuilder.withPath('length', function() { if (aLength !== bLength) { - diffBuilder.record(aLength, bLength); + diffBuilder.recordMismatch(); result = false; } }); @@ -238,10 +285,10 @@ 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.recordMismatch(actualArrayIsLongerFormatter.bind(null, self.pp)); 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; } }); } @@ -250,7 +297,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; } @@ -282,22 +329,22 @@ 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()); } } 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; } @@ -331,7 +378,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); @@ -342,7 +389,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { } if (!result) { - diffBuilder.record(a, b); + diffBuilder.recordMismatch(); return false; } } else { @@ -355,7 +402,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { a instanceof aCtor && b instanceof bCtor && !(aCtor instanceof aCtor && bCtor instanceof bCtor)) { - diffBuilder.record(a, b, constructorsAreDifferentFormatter); + diffBuilder.recordMismatch(constructorsAreDifferentFormatter.bind(null, this.pp)); return false; } } @@ -366,7 +413,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.recordMismatch(objectKeysAreDifferentFormatter.bind(null, this.pp)); return false; } @@ -374,13 +421,13 @@ getJasmineRequireObj().matchersUtil = function(j$) { key = aKeys[i]; // Deep compare each member if (!j$.util.has(b, key)) { - diffBuilder.record(a, b, objectKeysAreDifferentFormatter); + diffBuilder.recordMismatch(objectKeysAreDifferentFormatter.bind(null, this.pp)); result = false; continue; } 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; } }); @@ -395,7 +442,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { bStack.pop(); return result; - } + }; function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : @@ -431,11 +478,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()) { @@ -453,7 +500,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'; } @@ -461,21 +508,27 @@ 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; } + + function isDiffBuilder(obj) { + return obj && typeof obj.recordMismatch === 'function'; + } + + return MatchersUtil; }; 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/toBeInstanceOf.js b/src/core/matchers/toBeInstanceOf.js index 66b658c1..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(util, customEqualityTesters) { + 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/toContain.js b/src/core/matchers/toContain.js index 440bb4c4..5732447e 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(matchersUtil) { return { compare: function(actual, expected) { return { - pass: util.contains(actual, expected, customEqualityTesters) + pass: matchersUtil.contains(actual, expected) }; } }; diff --git a/src/core/matchers/toEqual.js b/src/core/matchers/toEqual.js index 8697ed67..3a7add38 100644 --- a/src/core/matchers/toEqual.js +++ b/src/core/matchers/toEqual.js @@ -8,17 +8,15 @@ getJasmineRequireObj().toEqual = function(j$) { * @example * expect(bigObject).toEqual({"foo": ['bar', 'baz']}); */ - function toEqual(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - + function toEqual(matchersUtil) { return { compare: function(actual, expected) { var result = { pass: false }, - diffBuilder = j$.DiffBuilder(); + diffBuilder = j$.DiffBuilder({prettyPrinter: matchersUtil.pp}); - result.pass = util.equals(actual, expected, customEqualityTesters, diffBuilder); + result.pass = matchersUtil.equals(actual, expected, diffBuilder); // TODO: only set error message if test fails result.message = diffBuilder.getMessage(); 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 277b33f3..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, customEqualityTesters) { + 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, customEqualityTesters)) { + 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, customEqualityTesters, 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 a2fd7507..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(util, customEqualityTesters) { + 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 baacd713..e2d7ef87 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -51,13 +51,22 @@ var getJasmineRequireObj = (function(jasmineGlobal) { j$.buildExpectationResult = jRequire.buildExpectationResult(j$); j$.noopTimer = jRequire.noopTimer(); j$.JsApiReporter = jRequire.JsApiReporter(j$); - j$.matchersUtil = jRequire.matchersUtil(j$); + 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: [], + 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$); @@ -75,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$); diff --git a/src/core/requireInterface.js b/src/core/requireInterface.js index 012f7303..15a13cd1 100644 --- a/src/core/requireInterface.js +++ b/src/core/requireInterface.js @@ -319,6 +319,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