diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 4875624e..555cb046 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -73,7 +73,12 @@ var getJasmineRequireObj = (function(jasmineGlobal) { j$.buildExpectationResult = jRequire.buildExpectationResult(); j$.noopTimer = jRequire.noopTimer(); j$.JsApiReporter = jRequire.JsApiReporter(j$); - j$.matchersUtil = jRequire.matchersUtil(j$); + j$.asymmetricEqualityTesterArgCompatShim = jRequire.asymmetricEqualityTesterArgCompatShim( + j$ + ); + j$.MatchersUtil = jRequire.MatchersUtil(j$); + j$.matchersUtil = new j$.MatchersUtil({ customTesters: [] }); + j$.ObjectContaining = jRequire.ObjectContaining(j$); j$.ArrayContaining = jRequire.ArrayContaining(j$); j$.ArrayWithExactContents = jRequire.ArrayWithExactContents(j$); @@ -1188,6 +1193,7 @@ getJasmineRequireObj().Env = function(j$) { } var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { customMatchers[matcherName] = matchersToAdd[matcherName]; } @@ -1201,6 +1207,7 @@ getJasmineRequireObj().Env = function(j$) { } var customAsyncMatchers = runnableResources[currentRunnable().id].customAsyncMatchers; + for (var matcherName in matchersToAdd) { customAsyncMatchers[matcherName] = matchersToAdd[matcherName]; } @@ -1220,9 +1227,12 @@ getJasmineRequireObj().Env = function(j$) { }; var expectationFactory = function(actual, spec) { + var customEqualityTesters = + runnableResources[spec.id].customEqualityTesters; + return j$.Expectation.factory({ - util: j$.matchersUtil, - customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + util: new j$.MatchersUtil({ customTesters: customEqualityTesters }), + customEqualityTesters: customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, actual: actual, addExpectationResult: addExpectationResult @@ -1234,8 +1244,11 @@ getJasmineRequireObj().Env = function(j$) { }; var asyncExpectationFactory = function(actual, spec) { + var customEqualityTesters = + runnableResources[spec.id].customEqualityTesters; + return j$.Expectation.asyncFactory({ - util: j$.matchersUtil, + util: new j$.MatchersUtil({ customTesters: customEqualityTesters }), customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customAsyncMatchers: runnableResources[spec.id].customAsyncMatchers, actual: actual, @@ -2269,7 +2282,7 @@ getJasmineRequireObj().ArrayContaining = function(j$) { this.sample = sample; } - ArrayContaining.prototype.asymmetricMatch = function(other, customTesters) { + ArrayContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isArray_(this.sample)) { throw new Error('You must provide an array to arrayContaining, not ' + j$.pp(this.sample) + '.'); } @@ -2283,7 +2296,7 @@ getJasmineRequireObj().ArrayContaining = function(j$) { for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; - if (!j$.matchersUtil.contains(other, item, customTesters)) { + if (!matchersUtil.contains(other, item)) { return false; } } @@ -2304,7 +2317,7 @@ getJasmineRequireObj().ArrayWithExactContents = function(j$) { this.sample = sample; } - ArrayWithExactContents.prototype.asymmetricMatch = function(other, customTesters) { + ArrayWithExactContents.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isArray_(this.sample)) { throw new Error('You must provide an array to arrayWithExactContents, not ' + j$.pp(this.sample) + '.'); } @@ -2315,7 +2328,7 @@ getJasmineRequireObj().ArrayWithExactContents = function(j$) { for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; - if (!j$.matchersUtil.contains(other, item, customTesters)) { + if (!matchersUtil.contains(other, item)) { return false; } } @@ -2380,7 +2393,7 @@ getJasmineRequireObj().MapContaining = function(j$) { this.sample = sample; } - MapContaining.prototype.asymmetricMatch = function(other, customTesters) { + MapContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isMap(other)) return false; var hasAllMatches = true; @@ -2390,8 +2403,8 @@ getJasmineRequireObj().MapContaining = function(j$) { var hasMatch = false; j$.util.forEachBreakable(other, function(oBreakLoop, oValue, oKey) { if ( - j$.matchersUtil.equals(oKey, key, customTesters) - && j$.matchersUtil.equals(oValue, value, customTesters) + matchersUtil.equals(oKey, key) + && matchersUtil.equals(oValue, value) ) { hasMatch = true; oBreakLoop(); @@ -2470,12 +2483,12 @@ getJasmineRequireObj().ObjectContaining = function(j$) { return hasProperty(getPrototype(obj), property); } - ObjectContaining.prototype.asymmetricMatch = function(other, customTesters) { + ObjectContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } for (var property in this.sample) { if (!hasProperty(other, property) || - !j$.matchersUtil.equals(this.sample[property], other[property], customTesters)) { + !matchersUtil.equals(this.sample[property], other[property])) { return false; } } @@ -2499,17 +2512,17 @@ getJasmineRequireObj().SetContaining = function(j$) { this.sample = sample; } - SetContaining.prototype.asymmetricMatch = function(other, customTesters) { + SetContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isSet(other)) return false; var hasAllMatches = true; j$.util.forEachBreakable(this.sample, function(breakLoop, item) { // for each item in `sample` there should be at least one matching item in `other` - // (not using `j$.matchersUtil.contains` because it compares set members by reference, + // (not using `matchersUtil.contains` because it compares set members by reference, // not by deep value equality) var hasMatch = false; j$.util.forEachBreakable(other, function(oBreakLoop, oItem) { - if (j$.matchersUtil.equals(oItem, item, customTesters)) { + if (matchersUtil.equals(oItem, item)) { hasMatch = true; oBreakLoop(); } @@ -2566,6 +2579,112 @@ getJasmineRequireObj().Truthy = function(j$) { return Truthy; }; +getJasmineRequireObj().asymmetricEqualityTesterArgCompatShim = function(j$) { + /* + Older versions of Jasmine passed an array of custom equality testers as the + second argument to each asymmetric equality tester's `asymmetricMatch` + method. Newer versions will pass a `MatchersUtil` instance. The + asymmetricEqualityTesterArgCompatShim allows for a graceful migration from + the old interface to the new by "being" both an array of custom equality + testers and a `MatchersUtil` at the same time. + + This code should be removed in the next major release. + */ + + var likelyArrayProps = [ + 'concat', + 'constructor', + 'copyWithin', + 'entries', + 'every', + 'fill', + 'filter', + 'find', + 'findIndex', + 'flat', + 'flatMap', + 'forEach', + 'includes', + 'indexOf', + 'join', + 'keys', + 'lastIndexOf', + 'length', + 'map', + 'pop', + 'push', + 'reduce', + 'reduceRight', + 'reverse', + 'shift', + 'slice', + 'some', + 'sort', + 'splice', + 'toLocaleString', + 'toSource', + 'toString', + 'unshift', + 'values' + ]; + + function asymmetricEqualityTesterArgCompatShim( + matchersUtil, + customEqualityTesters + ) { + var self = Object.create(matchersUtil), + props, + i, + k; + + copy(self, customEqualityTesters, 'length'); + + for (i = 0; i < customEqualityTesters.length; i++) { + copy(self, customEqualityTesters, i); + } + + var props = arrayProps(); + + for (i = 0; i < props.length; i++) { + k = props[i]; + if (k !== 'length') { + copy(self, Array.prototype, k); + } + } + + return self; + } + + function copy(dest, src, propName) { + Object.defineProperty(dest, propName, { + get: function() { + return src[propName]; + } + }); + } + + function arrayProps() { + var props, a, k; + + if (!Object.getOwnPropertyDescriptors) { + return likelyArrayProps.filter(function(k) { + return Array.prototype.hasOwnProperty(k); + }); + } + + props = Object.getOwnPropertyDescriptors(Array.prototype); + a = []; + + for (k in props) { + a.push(k); + } + + return a; + } + + return asymmetricEqualityTesterArgCompatShim; +}; + getJasmineRequireObj().CallTracker = function(j$) { /** * @namespace Spy#calls @@ -3822,7 +3941,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { * @example * return expectAsync(aPromise).toBeRejectedWith({prop: 'value'}); */ - return function toBeRejectedWith(util, customEqualityTesters) { + return function toBeRejectedWith(util) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { @@ -3843,7 +3962,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { }; }, function(actualValue) { - if (util.equals(actualValue, expectedValue, customEqualityTesters)) { + if (util.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' @@ -4000,7 +4119,7 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { * @example * return expectAsync(aPromise).toBeResolvedTo({prop: 'value'}); */ - return function toBeResolvedTo(util, customEqualityTesters) { + return function toBeResolvedTo(util) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { @@ -4015,7 +4134,7 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { return actualPromise.then( function(actualValue) { - if (util.equals(actualValue, expectedValue, customEqualityTesters)) { + if (util.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' @@ -4073,66 +4192,70 @@ getJasmineRequireObj().DiffBuilder = function(j$) { }; }; -getJasmineRequireObj().matchersUtil = function(j$) { +getJasmineRequireObj().MatchersUtil = function(j$) { // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? - return { - equals: equals, + function MatchersUtil(options) { + options = options || {}; + this.customTesters_ = options.customTesters || []; - contains: function(haystack, needle, customTesters) { - customTesters = customTesters || []; - - if (j$.isSet(haystack)) { - return haystack.has(needle); - } - - if ((Object.prototype.toString.apply(haystack) === '[object Array]') || - (!!haystack && !haystack.indexOf)) - { - for (var i = 0; i < haystack.length; i++) { - if (equals(haystack[i], needle, customTesters)) { - return true; - } - } - return false; - } - - return !!haystack && haystack.indexOf(needle) >= 0; - }, - - buildFailureMessage: function() { - var args = Array.prototype.slice.call(arguments, 0), - matcherName = args[0], - isNot = args[1], - actual = args[2], - expected = args.slice(3), - englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); - - var message = 'Expected ' + - j$.pp(actual) + - (isNot ? ' not ' : ' ') + - englishyPredicate; - - if (expected.length > 0) { - for (var i = 0; i < expected.length; i++) { - if (i > 0) { - message += ','; - } - message += ' ' + j$.pp(expected[i]); - } - } - - return message + '.'; + if (!j$.isArray_(this.customTesters_)) { + throw new Error("MatchersUtil requires custom equality testers"); } + } + + MatchersUtil.prototype.contains = function(haystack, needle, customTesters) { + if (j$.isSet(haystack)) { + return haystack.has(needle); + } + + if ((Object.prototype.toString.apply(haystack) === '[object Array]') || + (!!haystack && !haystack.indexOf)) + { + for (var i = 0; i < haystack.length; i++) { + if (this.equals(haystack[i], needle, customTesters)) { + return true; + } + } + return false; + } + + return !!haystack && haystack.indexOf(needle) >= 0; + }; + + MatchersUtil.prototype.buildFailureMessage = function() { + var args = Array.prototype.slice.call(arguments, 0), + matcherName = args[0], + isNot = args[1], + actual = args[2], + expected = args.slice(3), + englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + + var message = 'Expected ' + + j$.pp(actual) + + (isNot ? ' not ' : ' ') + + englishyPredicate; + + if (expected.length > 0) { + for (var i = 0; i < expected.length; i++) { + if (i > 0) { + message += ','; + } + message += ' ' + j$.pp(expected[i]); + } + } + + return message + '.'; }; function isAsymmetric(obj) { return obj && j$.isA_('Function', obj.asymmetricMatch); } - function asymmetricMatch(a, b, customTesters, diffBuilder) { + MatchersUtil.prototype.asymmetricMatch_ = function(a, b, customTesters, diffBuilder) { var asymmetricA = isAsymmetric(a), asymmetricB = isAsymmetric(b), + shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters), result; if (asymmetricA && asymmetricB) { @@ -4140,7 +4263,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { } if (asymmetricA) { - result = a.asymmetricMatch(b, customTesters); + result = a.asymmetricMatch(b, shim); if (!result) { diffBuilder.record(a, b); } @@ -4148,27 +4271,36 @@ getJasmineRequireObj().matchersUtil = function(j$) { } if (asymmetricB) { - result = b.asymmetricMatch(a, customTesters); + result = b.asymmetricMatch(a, shim); if (!result) { diffBuilder.record(a, b); } return result; } - } + }; - function equals(a, b, customTesters, diffBuilder) { - customTesters = customTesters || []; + MatchersUtil.prototype.equals = function(a, b, customTestersOrDiffBuilder, diffBuilderOrNothing) { + var customTesters, diffBuilder; + + if (isDiffBuilder(customTestersOrDiffBuilder)) { + diffBuilder = customTestersOrDiffBuilder; + } else { + customTesters = customTestersOrDiffBuilder; + diffBuilder = diffBuilderOrNothing; + } + + customTesters = customTesters || this.customTesters_; diffBuilder = diffBuilder || j$.NullDiffBuilder(); - return eq(a, b, [], [], customTesters, diffBuilder); - } + return this.eq_(a, b, [], [], customTesters, diffBuilder); + }; // Equality function lovingly adapted from isEqual in // [Underscore](http://underscorejs.org) - function eq(a, b, aStack, bStack, customTesters, diffBuilder) { - var result = true, i; + MatchersUtil.prototype.eq_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { + var result = true, self = this, i; - var asymmetricResult = asymmetricMatch(a, b, customTesters, diffBuilder); + var asymmetricResult = this.asymmetricMatch_(a, b, customTesters, diffBuilder); if (!j$.util.isUndefined(asymmetricResult)) { return asymmetricResult; } @@ -4305,7 +4437,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { diffBuilder.record(a[i], void 0, actualArrayIsLongerFormatter); result = false; } else { - result = eq(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0, aStack, bStack, customTesters, diffBuilder) && result; + result = self.eq_(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0, aStack, bStack, customTesters, diffBuilder) && result; } }); } @@ -4346,12 +4478,12 @@ getJasmineRequireObj().matchersUtil = function(j$) { // otherwise explicitly look up the mapKey in the other Map since we want keys with unique // obj identity (that are otherwise equal) to not match. if (isAsymmetric(mapKey) || isAsymmetric(cmpKey) && - eq(mapKey, cmpKey, aStack, bStack, customTesters, j$.NullDiffBuilder())) { + this.eq_(mapKey, cmpKey, aStack, bStack, customTesters, j$.NullDiffBuilder())) { mapValueB = b.get(cmpKey); } else { mapValueB = b.get(mapKey); } - result = eq(mapValueA, mapValueB, aStack, bStack, customTesters, j$.NullDiffBuilder()); + result = this.eq_(mapValueA, mapValueB, aStack, bStack, customTesters, j$.NullDiffBuilder()); } } @@ -4395,7 +4527,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { otherValue = otherValues[l]; prevStackSize = baseStack.length; // compare by value equality - found = eq(baseValue, otherValue, baseStack, otherStack, customTesters, j$.NullDiffBuilder()); + found = this.eq_(baseValue, otherValue, baseStack, otherStack, customTesters, j$.NullDiffBuilder()); if (!found && prevStackSize !== baseStack.length) { baseStack.splice(prevStackSize); otherStack.splice(prevStackSize); @@ -4444,7 +4576,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { } diffBuilder.withPath(key, function() { - if(!eq(a[key], b[key], aStack, bStack, customTesters, diffBuilder)) { + if(!self.eq_(a[key], b[key], aStack, bStack, customTesters, diffBuilder)) { result = false; } }); @@ -4459,7 +4591,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { bStack.pop(); return result; - } + }; function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : @@ -4542,6 +4674,12 @@ getJasmineRequireObj().matchersUtil = function(j$) { } return formatted; } + + function isDiffBuilder(obj) { + return obj && typeof obj.record === 'function'; + } + + return MatchersUtil; }; getJasmineRequireObj().nothing = function() { @@ -4839,7 +4977,7 @@ getJasmineRequireObj().toBeInstanceOf = function(j$) { * expect(3).toBeInstanceOf(Number); * expect(new Error()).toBeInstanceOf(Error); */ - function toBeInstanceOf(util, customEqualityTesters) { + function toBeInstanceOf() { return { compare: function(actual, expected) { var actualType = actual && actual.constructor ? j$.fnNameFor(actual.constructor) : j$.pp(actual), @@ -5109,14 +5247,12 @@ getJasmineRequireObj().toContain = function() { * expect(array).toContain(anElement); * expect(string).toContain(substring); */ - function toContain(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - + function toContain(util) { return { compare: function(actual, expected) { return { - pass: util.contains(actual, expected, customEqualityTesters) + pass: util.contains(actual, expected) }; } }; @@ -5135,9 +5271,7 @@ getJasmineRequireObj().toEqual = function(j$) { * @example * expect(bigObject).toEqual({"foo": ['bar', 'baz']}); */ - function toEqual(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - + function toEqual(util) { return { compare: function(actual, expected) { var result = { @@ -5145,7 +5279,7 @@ getJasmineRequireObj().toEqual = function(j$) { }, diffBuilder = j$.DiffBuilder(); - result.pass = util.equals(actual, expected, customEqualityTesters, diffBuilder); + result.pass = util.equals(actual, expected, diffBuilder); // TODO: only set error message if test fails result.message = diffBuilder.getMessage(); @@ -5315,7 +5449,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { * @example * expect(mySpy).toHaveBeenCalledWith('foo', 'bar', 2); */ - function toHaveBeenCalledWith(util, customEqualityTesters) { + function toHaveBeenCalledWith(util) { return { compare: function() { var args = Array.prototype.slice.call(arguments, 0), @@ -5336,7 +5470,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { return result; } - if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { + if (util.contains(actual.calls.allArgs(), expectedArgs)) { result.pass = true; result.message = function() { return 'Expected spy ' + actual.and.identity + ' not to have been called with:\n' + @@ -5351,7 +5485,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { var diffs = actual.calls.allArgs().map(function(argsForCall, callIx) { var diffBuilder = new j$.DiffBuilder(); - util.equals(argsForCall, expectedArgs, customEqualityTesters, diffBuilder); + util.equals(argsForCall, expectedArgs, diffBuilder); return 'Call ' + callIx + ':\n' + diffBuilder.getMessage().replace(/^/mg, ' '); }); @@ -5384,7 +5518,7 @@ getJasmineRequireObj().toHaveClass = function(j$) { * el.className = 'foo bar baz'; * expect(el).toHaveClass('bar'); */ - function toHaveClass(util, customEqualityTesters) { + function toHaveClass() { return { compare: function(actual, expected) { if (!isElement(actual)) { @@ -6899,6 +7033,8 @@ getJasmineRequireObj().Spy = function(j$) { }; })(); + var matchersUtil = new j$.MatchersUtil(); + /** * _Note:_ Do not construct this directly, use {@link spyOn}, {@link spyOnProperty}, {@link jasmine.createSpy}, or {@link jasmine.createSpyObj} * @constructor @@ -7094,7 +7230,7 @@ getJasmineRequireObj().Spy = function(j$) { var i; for (i = 0; i < this.strategies.length; i++) { - if (j$.matchersUtil.equals(args, this.strategies[i].args)) { + if (matchersUtil.equals(args, this.strategies[i].args)) { return this.strategies[i].strategy; } } diff --git a/spec/core/AsyncExpectationSpec.js b/spec/core/AsyncExpectationSpec.js index 9b2e3f26..23d145e1 100644 --- a/spec/core/AsyncExpectationSpec.js +++ b/spec/core/AsyncExpectationSpec.js @@ -25,7 +25,7 @@ describe('AsyncExpectation', function() { var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.resolve(), expectation = jasmineUnderTest.Expectation.asyncFactory({ - util: jasmineUnderTest.matchersUtil, + util: new jasmineUnderTest.MatchersUtil(), actual: actual, addExpectationResult: addExpectationResult }); @@ -47,7 +47,7 @@ describe('AsyncExpectation', function() { var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.reject(), expectation = jasmineUnderTest.Expectation.asyncFactory({ - util: jasmineUnderTest.matchersUtil, + util: new jasmineUnderTest.MatchersUtil(), actual: actual, addExpectationResult: addExpectationResult }); @@ -183,7 +183,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, - util: jasmineUnderTest.matchersUtil + util: new jasmineUnderTest.MatchersUtil() }); return expectation @@ -208,7 +208,7 @@ describe('AsyncExpectation', function() { expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, - util: jasmineUnderTest.matchersUtil + util: new jasmineUnderTest.MatchersUtil() }); return expectation diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index aa87a997..dc7233c8 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -304,4 +304,62 @@ describe('Env', function() { expect(globalErrors.install).toHaveBeenCalled(); }); }); + + it('creates an expectationFactory that uses the current custom equality testers', function(done) { + function customEqualityTester() {} + var RealSpec = jasmineUnderTest.Spec, + specInstance, + expectationFactory; + spyOn(jasmineUnderTest, 'MatchersUtil'); + spyOn(jasmineUnderTest, 'Spec').and.callFake(function(options) { + expectationFactory = options.expectationFactory; + specInstance = new RealSpec(options); + return specInstance; + }); + + env.it('spec', function() { + env.addCustomEqualityTester(customEqualityTester); + expectationFactory('actual', specInstance); + }); + + env.addReporter({ + jasmineDone: function() { + expect(jasmineUnderTest.MatchersUtil).toHaveBeenCalledWith({ + customTesters: [customEqualityTester] + }); + done(); + } + }); + + env.execute(); + }); + + it('creates an asyncExpectationFactory that uses the current custom equality testers', function(done) { + function customEqualityTester() {} + var RealSpec = jasmineUnderTest.Spec, + specInstance, + asyncExpectationFactory; + spyOn(jasmineUnderTest, 'MatchersUtil'); + spyOn(jasmineUnderTest, 'Spec').and.callFake(function(options) { + asyncExpectationFactory = options.asyncExpectationFactory; + specInstance = new RealSpec(options); + return specInstance; + }); + + env.it('spec', function() { + env.addCustomEqualityTester(customEqualityTester); + asyncExpectationFactory('actual', specInstance); + }); + + env.addReporter({ + jasmineDone: function() { + expect(jasmineUnderTest.MatchersUtil).toHaveBeenCalledWith({ + customTesters: [customEqualityTester] + }); + done(); + } + }); + + env.execute(); + }); }); diff --git a/spec/core/ExpectationSpec.js b/spec/core/ExpectationSpec.js index 45f69eb5..361224ab 100644 --- a/spec/core/ExpectationSpec.js +++ b/spec/core/ExpectationSpec.js @@ -634,7 +634,7 @@ describe('Expectation', function() { addExpectationResult = jasmine.createSpy('addExpectationResult'), expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, - util: jasmineUnderTest.matchersUtil, + util: new jasmineUnderTest.MatchersUtil(), actual: 'an actual', addExpectationResult: addExpectationResult }); diff --git a/spec/core/asymmetricEqualityTesterArgCompatShimSpec.js b/spec/core/asymmetricEqualityTesterArgCompatShimSpec.js new file mode 100644 index 00000000..c7c4d9b6 --- /dev/null +++ b/spec/core/asymmetricEqualityTesterArgCompatShimSpec.js @@ -0,0 +1,101 @@ +describe('asymmetricEqualityTesterArgCompatShim', function() { + it('provides all the properties of the MatchersUtil', function() { + var matchersUtil = { + foo: function() {}, + bar: function() {} + }, + shim = jasmineUnderTest.asymmetricEqualityTesterArgCompatShim( + matchersUtil, + [] + ); + + expect(shim.foo).toBe(matchersUtil.foo); + expect(shim.bar).toBe(matchersUtil.bar); + }); + + it('provides all the properties of the customEqualityTesters', function() { + var customEqualityTesters = [function() {}, function() {}], + shim = jasmineUnderTest.asymmetricEqualityTesterArgCompatShim( + {}, + customEqualityTesters + ); + + expect(shim.length).toBe(2); + expect(shim[0]).toBe(customEqualityTesters[0]); + expect(shim[1]).toBe(customEqualityTesters[1]); + }); + + it('provides all the properties of Array.prototype', function() { + var shim = jasmineUnderTest.asymmetricEqualityTesterArgCompatShim({}, []); + + expect(shim.filter).toBe(Array.prototype.filter); + expect(shim.forEach).toBe(Array.prototype.forEach); + expect(shim.map).toBe(Array.prototype.map); + }); + + it('provides properties of Array.prototype', function() { + var keys = [ + 'concat', + 'constructor', + 'every', + 'filter', + 'forEach', + 'indexOf', + 'join', + 'lastIndexOf', + 'length', + 'map', + 'pop', + 'push', + 'reduce', + 'reduceRight', + 'reverse', + 'shift', + 'slice', + 'some', + 'sort', + 'splice', + 'toLocaleString', + 'toString', + 'unshift' + ], + optionalKeys = [ + 'copyWithin', + 'entries', + 'fill', + 'find', + 'findIndex', + 'flat', + 'flatMap', + 'includes', + 'keys', + 'toSource', + 'values' + ], + shim = jasmineUnderTest.asymmetricEqualityTesterArgCompatShim({}, []), + i, + k; + + // Properties that are present on all supported runtimes + for (i = 0; i < keys.length; i++) { + k = keys[i]; + expect(shim[k]) + .withContext(k) + .not.toBeUndefined(); + expect(shim[k]) + .withContext(k) + .toBe(Array.prototype[k]); + } + + // Properties that are present on only some supported runtimes + for (i = 0; i < optionalKeys.length; i++) { + k = optionalKeys[i]; + + if (shim[k] !== undefined) { + expect(shim[k]) + .withContext(k) + .toBe(Array.prototype[k]); + } + } + }); +}); diff --git a/spec/core/asymmetric_equality/ArrayContainingSpec.js b/spec/core/asymmetric_equality/ArrayContainingSpec.js index 30b5aaad..4cbfa66d 100644 --- a/spec/core/asymmetric_equality/ArrayContainingSpec.js +++ b/spec/core/asymmetric_equality/ArrayContainingSpec.js @@ -15,26 +15,30 @@ describe("ArrayContaining", function() { it("matches when the item is in the actual", function() { var containing = new jasmineUnderTest.ArrayContaining(["foo"]); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(["foo"])).toBe(true); + expect(containing.asymmetricMatch(["foo"], matchersUtil)).toBe(true); }); it("matches when additional items are in the actual", function() { var containing = new jasmineUnderTest.ArrayContaining(["foo"]); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(["foo", "bar"])).toBe(true); + expect(containing.asymmetricMatch(["foo", "bar"], matchersUtil)).toBe(true); }); it("does not match when the item is not in the actual", function() { var containing = new jasmineUnderTest.ArrayContaining(["foo"]); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(["bar"])).toBe(false); + expect(containing.asymmetricMatch(["bar"], matchersUtil)).toBe(false); }); it("does not match when the actual is not an array", function() { var containing = new jasmineUnderTest.ArrayContaining(["foo"]); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch("foo")).toBe(false); + expect(containing.asymmetricMatch("foo", matchersUtil)).toBe(false); }); it("jasmineToStrings itself", function() { @@ -52,7 +56,8 @@ describe("ArrayContaining", function() { } }; var containing = new jasmineUnderTest.ArrayContaining(["fooVal"]); + var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); - expect(containing.asymmetricMatch(["fooBar"], [tester])).toBe(true); + expect(containing.asymmetricMatch(["fooBar"], matchersUtil)).toBe(true); }); }); diff --git a/spec/core/asymmetric_equality/ArrayWithExactContentsSpec.js b/spec/core/asymmetric_equality/ArrayWithExactContentsSpec.js index 1cae0a0b..5f8795d6 100644 --- a/spec/core/asymmetric_equality/ArrayWithExactContentsSpec.js +++ b/spec/core/asymmetric_equality/ArrayWithExactContentsSpec.js @@ -1,8 +1,9 @@ describe("ArrayWithExactContents", function() { it("matches an array with the same items in a different order", function() { var matcher = new jasmineUnderTest.ArrayWithExactContents(['a', 2, /a/]); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(matcher.asymmetricMatch([2, 'a', /a/])).toBe(true); + expect(matcher.asymmetricMatch([2, 'a', /a/], matchersUtil)).toBe(true); }); it("does not work when not passed an array", function() { @@ -15,15 +16,17 @@ describe("ArrayWithExactContents", function() { it("does not match when an item is missing", function() { var matcher = new jasmineUnderTest.ArrayWithExactContents(['a', 2, /a/]); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(matcher.asymmetricMatch(['a', 2])).toBe(false); - expect(matcher.asymmetricMatch(['a', 2, undefined])).toBe(false); + expect(matcher.asymmetricMatch(['a', 2], matchersUtil)).toBe(false); + expect(matcher.asymmetricMatch(['a', 2, undefined], matchersUtil)).toBe(false); }); it("does not match when there is an extra item", function() { var matcher = new jasmineUnderTest.ArrayWithExactContents(['a']); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(matcher.asymmetricMatch(['a', 2])).toBe(false); + expect(matcher.asymmetricMatch(['a', 2], matchersUtil)).toBe(false); }); it("jasmineToStrings itself", function() { @@ -41,7 +44,8 @@ describe("ArrayWithExactContents", function() { } }; var matcher = new jasmineUnderTest.ArrayWithExactContents(["fooVal"]); + var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); - expect(matcher.asymmetricMatch(["fooBar"], [tester])).toBe(true); + expect(matcher.asymmetricMatch(["fooBar"], matchersUtil)).toBe(true); }); }); diff --git a/spec/core/asymmetric_equality/MapContainingSpec.js b/spec/core/asymmetric_equality/MapContainingSpec.js index a214dce8..f264e693 100644 --- a/spec/core/asymmetric_equality/MapContainingSpec.js +++ b/spec/core/asymmetric_equality/MapContainingSpec.js @@ -31,8 +31,9 @@ describe('MapContaining', function() { ['foo', [1, 2, 3]], ]); var containing = new jasmineUnderTest.MapContaining(containingMap); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualMap)).toBe(true); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(true); }); it('does not match when a key is not in actual', function() { @@ -46,8 +47,9 @@ describe('MapContaining', function() { ['foo', [1, 2, 3]], ]); var containing = new jasmineUnderTest.MapContaining(containingMap); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualMap)).toBe(false); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(false); }); it('does not match when a value is not in actual', function() { @@ -61,8 +63,9 @@ describe('MapContaining', function() { ['foo', [1, 2]], ]); var containing = new jasmineUnderTest.MapContaining(containingMap); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualMap)).toBe(false); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(false); }); it('matches when all the key/value pairs in sample have asymmetric matches in actual', function() { @@ -74,17 +77,18 @@ describe('MapContaining', function() { var containingMap = new MapI([ [ - jasmine.stringMatching(/^foo\d/), + jasmineUnderTest.stringMatching(/^foo\d/), 'bar' ], [ 'baz', - jasmine.arrayContaining([2, 3]) + jasmineUnderTest.arrayContaining([2, 3]) ], ]); var containing = new jasmineUnderTest.MapContaining(containingMap); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualMap)).toBe(true); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(true); }); it('does not match when a key in sample has no asymmetric matches in actual', function() { @@ -95,17 +99,18 @@ describe('MapContaining', function() { var containingMap = new MapI([ [ - jasmine.stringMatching(/^foo\d/), + jasmineUnderTest.stringMatching(/^foo\d/), 'bar' ], [ 'baz', - jasmine.arrayContaining([2, 3]) + jasmineUnderTest.arrayContaining([2, 3]) ], ]); var containing = new jasmineUnderTest.MapContaining(containingMap); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualMap)).toBe(false); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(false); }); it('does not match when a value in sample has no asymmetric matches in actual', function() { @@ -116,17 +121,18 @@ describe('MapContaining', function() { var containingMap = new MapI([ [ - jasmine.stringMatching(/^foo\d/), + jasmineUnderTest.stringMatching(/^foo\d/), 'bar' ], [ 'baz', - jasmine.arrayContaining([4, 5]) + jasmineUnderTest.arrayContaining([4, 5]) ], ]); var containing = new jasmineUnderTest.MapContaining(containingMap); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualMap)).toBe(false); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(false); }); it('matches recursively', function() { @@ -147,8 +153,9 @@ describe('MapContaining', function() { ], ]); var containing = new jasmineUnderTest.MapContaining(containingMap); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualMap)).toBe(true); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(true); }); it('uses custom equality testers', function() { @@ -158,8 +165,9 @@ describe('MapContaining', function() { } var actualMap = new MapI([['foo', -1]]); var containing = new jasmineUnderTest.MapContaining(new MapI([['foo', -2]])); + var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); - expect(containing.asymmetricMatch(actualMap, [tester])).toBe(true); + expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(true); }); it('does not match when actual is not a map', function() { diff --git a/spec/core/asymmetric_equality/ObjectContainingSpec.js b/spec/core/asymmetric_equality/ObjectContainingSpec.js index e2741a9d..13fa0a90 100644 --- a/spec/core/asymmetric_equality/ObjectContainingSpec.js +++ b/spec/core/asymmetric_equality/ObjectContainingSpec.js @@ -2,8 +2,9 @@ describe("ObjectContaining", function() { it("matches any actual to an empty object", function() { var containing = new jasmineUnderTest.ObjectContaining({}); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch("foo")).toBe(true); + expect(containing.asymmetricMatch("foo", matchersUtil)).toBe(true); }); it("does not match an empty object actual", function() { @@ -16,20 +17,23 @@ describe("ObjectContaining", function() { it("matches when the key/value pair is present in the actual", function() { var containing = new jasmineUnderTest.ObjectContaining({foo: "fooVal"}); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch({foo: "fooVal", bar: "barVal"})).toBe(true); + expect(containing.asymmetricMatch({foo: "fooVal", bar: "barVal"}, matchersUtil)).toBe(true); }); it("does not match when the key/value pair is not present in the actual", function() { var containing = new jasmineUnderTest.ObjectContaining({foo: "fooVal"}); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch({bar: "barVal", quux: "quuxVal"})).toBe(false); + expect(containing.asymmetricMatch({bar: "barVal", quux: "quuxVal"}, matchersUtil)).toBe(false); }); it("does not match when the key is present but the value is different in the actual", function() { var containing = new jasmineUnderTest.ObjectContaining({foo: "other"}); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch({foo: "fooVal", bar: "barVal"})).toBe(false); + expect(containing.asymmetricMatch({foo: "fooVal", bar: "barVal"}, matchersUtil)).toBe(false); }); it("jasmineToString's itself", function() { @@ -40,34 +44,39 @@ describe("ObjectContaining", function() { it("matches recursively", function() { var containing = new jasmineUnderTest.ObjectContaining({one: new jasmineUnderTest.ObjectContaining({two: {}})}); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch({one: {two: {}}})).toBe(true); + expect(containing.asymmetricMatch({one: {two: {}}}, matchersUtil)).toBe(true); }); it("matches when key is present with undefined value", function() { var containing = new jasmineUnderTest.ObjectContaining({ one: undefined }); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch({ one: undefined })).toBe(true); + expect(containing.asymmetricMatch({ one: undefined }, matchersUtil)).toBe(true); }); it("does not match when key with undefined value is not present", function() { var containing = new jasmineUnderTest.ObjectContaining({ one: undefined }); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch({})).toBe(false); + expect(containing.asymmetricMatch({}, matchersUtil)).toBe(false); }); it("matches defined properties", function(){ var containing = new jasmineUnderTest.ObjectContaining({ foo: "fooVal" }); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var definedPropertyObject = {}; Object.defineProperty(definedPropertyObject, "foo", { get: function() { return "fooVal" } }); - expect(containing.asymmetricMatch(definedPropertyObject)).toBe(true); + expect(containing.asymmetricMatch(definedPropertyObject, matchersUtil)).toBe(true); }); it("matches prototype properties", function(){ var containing = new jasmineUnderTest.ObjectContaining({ foo: "fooVal" }); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var prototypeObject = {foo: "fooVal"}; var obj; @@ -81,7 +90,7 @@ describe("ObjectContaining", function() { obj = new Foo(); } - expect(containing.asymmetricMatch(obj)).toBe(true); + expect(containing.asymmetricMatch(obj, matchersUtil)).toBe(true); }); it("uses custom equality testers", function() { @@ -93,7 +102,8 @@ describe("ObjectContaining", function() { } }; var containing = new jasmineUnderTest.ObjectContaining({foo: "fooVal"}); + var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); - expect(containing.asymmetricMatch({foo: "fooBar"}, [tester])).toBe(true); + expect(containing.asymmetricMatch({foo: "fooBar"}, matchersUtil)).toBe(true); }); }); diff --git a/spec/core/asymmetric_equality/SetContainingSpec.js b/spec/core/asymmetric_equality/SetContainingSpec.js index d1ae7a5a..f882621f 100644 --- a/spec/core/asymmetric_equality/SetContainingSpec.js +++ b/spec/core/asymmetric_equality/SetContainingSpec.js @@ -28,8 +28,9 @@ describe('SetContaining', function() { [1, 2, 3], {'foo': 'bar'} ]); var containing = new jasmineUnderTest.SetContaining(containingSet); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualSet)).toBe(true); + expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(true); }); it('does not match when a value is not in actual', function() { @@ -41,8 +42,9 @@ describe('SetContaining', function() { [1, 2], {'foo': 'bar'} ]); var containing = new jasmineUnderTest.SetContaining(containingSet); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualSet)).toBe(false); + expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(false); }); it('matches when all the values in sample have asymmetric matches in actual', function() { @@ -51,12 +53,13 @@ describe('SetContaining', function() { ]); var containingSet = new SetI([ - jasmine.stringMatching(/^foo\d/), - jasmine.arrayContaining([2, 3]), + jasmineUnderTest.stringMatching(/^foo\d/), + jasmineUnderTest.arrayContaining([2, 3]), ]); var containing = new jasmineUnderTest.SetContaining(containingSet); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualSet)).toBe(true); + expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(true); }); it('does not match when a value in sample has no asymmetric matches in actual', function() { @@ -69,8 +72,9 @@ describe('SetContaining', function() { jasmine.arrayContaining([2, 3]), ]); var containing = new jasmineUnderTest.SetContaining(containingSet); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualSet)).toBe(false); + expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(false); }); it('matches recursively', function() { @@ -82,8 +86,9 @@ describe('SetContaining', function() { new jasmineUnderTest.SetContaining(new SetI(['bar'])), 'foo' ]); var containing = new jasmineUnderTest.SetContaining(containingSet); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(containing.asymmetricMatch(actualSet)).toBe(true); + expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(true); }); it('uses custom equality testers', function() { @@ -93,8 +98,9 @@ describe('SetContaining', function() { } var actualSet = new SetI(['foo', -1]); var containing = new jasmineUnderTest.SetContaining(new SetI([-2, 'foo'])); + var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); - expect(containing.asymmetricMatch(actualSet, [tester])).toBe(true); + expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(true); }); it('does not match when actual is not a set', function() { diff --git a/spec/core/integration/CustomAsyncMatchersSpec.js b/spec/core/integration/CustomAsyncMatchersSpec.js index 51ddef34..de4806c3 100644 --- a/spec/core/integration/CustomAsyncMatchersSpec.js +++ b/spec/core/integration/CustomAsyncMatchersSpec.js @@ -111,4 +111,36 @@ describe('Custom Async Matchers (Integration)', function() { env.addReporter({ specDone: specExpectations, jasmineDone: done }); env.execute(); }); + + it("provides custom equality testers to the matcher factory via matchersUtil", function(done) { + jasmine.getEnv().requirePromises(); + + var matcherFactory = function (matchersUtil) { + return { + compare: function (actual, expected) { + return Promise.resolve({pass: matchersUtil.equals(actual[0], expected)}); + } + }; + }, + customEqualityFn = jasmine.createSpy("customEqualityFn").and.callFake(function (a, b) { + return a.toString() === b; + }); + + env.it("spec with expectation", function() { + env.addCustomEqualityTester(customEqualityFn); + env.addAsyncMatchers({ + toBeArrayWithFirstElement: matcherFactory + }); + + return env.expectAsync([1, 2]).toBeArrayWithFirstElement("1"); + }); + + var specExpectations = function(result) { + expect(customEqualityFn).toHaveBeenCalledWith(1, "1"); + expect(result.failedExpectations).toEqual([]); + }; + + env.addReporter({ specDone: specExpectations, jasmineDone: done }); + env.execute(); + }); }); diff --git a/spec/core/integration/CustomMatchersSpec.js b/spec/core/integration/CustomMatchersSpec.js index 4bb0c352..dfca7a78 100644 --- a/spec/core/integration/CustomMatchersSpec.js +++ b/spec/core/integration/CustomMatchersSpec.js @@ -1,19 +1,19 @@ -describe("Custom Matchers (Integration)", function() { +describe("Custom Matchers (Integration)", function () { var env; var fakeTimer; - beforeEach(function() { + beforeEach(function () { env = new jasmineUnderTest.Env(); env.configure({random: false}); }); - it("allows adding more matchers local to a spec", function(done) { - env.it('spec defining a custom matcher', function() { + it("allows adding more matchers local to a spec", function (done) { + env.it('spec defining a custom matcher', function () { env.addMatchers({ - matcherForSpec: function() { + matcherForSpec: function () { return { - compare: function(actual, expected) { - return { pass: false, message: "matcherForSpec: actual: " + actual + "; expected: " + expected }; + compare: function (actual, expected) { + return {pass: false, message: "matcherForSpec: actual: " + actual + "; expected: " + expected}; } } } @@ -83,6 +83,8 @@ describe("Custom Matchers (Integration)", function() { it("supports asymmetric equality testers that take a list of custom equality testers", function(done) { // TODO: remove this in the next major release. + spyOn(jasmineUnderTest, 'getEnv').and.returnValue(env); + env.it("spec using custom asymmetric equality tester", function() { var customEqualityFn = function(a, b) { if (a === 2 && b === "two") { @@ -229,4 +231,34 @@ describe("Custom Matchers (Integration)", function() { env.addReporter({ specDone: specExpectations, jasmineDone: done }); env.execute(); }); + + it("provides custom equality testers to the matcher factory via matchersUtil", function (done) { + var matcherFactory = function (matchersUtil) { + return { + compare: function (actual, expected) { + return {pass: matchersUtil.equals(actual[0], expected)}; + } + }; + }, + customEqualityFn = jasmine.createSpy("customEqualityFn").and.callFake(function (a, b) { + return a.toString() === b; + }); + + env.it("spec with expectation", function () { + env.addCustomEqualityTester(customEqualityFn); + env.addMatchers({ + toBeArrayWithFirstElement: matcherFactory + }); + + env.expect([1, 2]).toBeArrayWithFirstElement("1"); + }); + + var specExpectations = function (result) { + expect(customEqualityFn).toHaveBeenCalledWith(1, "1"); + expect(result.failedExpectations).toEqual([]); + }; + + env.addReporter({specDone: specExpectations, jasmineDone: done}); + env.execute(); + }); }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index e31f7d9d..2536fa07 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -2149,7 +2149,7 @@ describe("Env integration", function() { env.it('is a spec without any expectations', function() { // does nothing, just a mock spec without expectations }); - + }); it('should report "failed" status if "failSpecWithNoExpectations" is enabled', function(done) { @@ -2556,4 +2556,33 @@ describe("Env integration", function() { env.execute(); }); + + it("supports asymmetric equality testers that take a matchersUtil", function(done) { + var env = new jasmineUnderTest.Env(); + + env.it("spec using custom asymmetric equality tester", function() { + var customEqualityFn = function(a, b) { + if (a === 2 && b === "two") { + return true; + } + }; + var arrayWithFirstElement = function(sample) { + return { + asymmetricMatch: function (actual, matchersUtil) { + return matchersUtil.equals(sample, actual[0]); + } + }; + }; + + env.addCustomEqualityTester(customEqualityFn); + env.expect(["two"]).toEqual(arrayWithFirstElement(2)); + }); + + var specExpectations = function(result) { + expect(result.status).toEqual('passed'); + }; + + env.addReporter({ specDone: specExpectations, jasmineDone: done }); + env.execute(); + }); }); diff --git a/spec/core/integration/MatchersSpec.js b/spec/core/integration/MatchersSpec.js index 6f81ea04..b21208d5 100644 --- a/spec/core/integration/MatchersSpec.js +++ b/spec/core/integration/MatchersSpec.js @@ -449,7 +449,10 @@ describe('Matchers (Integration)', function() { describe('toThrow', function() { verifyPasses(function(env) { - env.expect(function() { throw new Error(); }).toThrow(); + env.addCustomEqualityTester(function(a, b) { + return a.toString() === b.toString(); + }); + env.expect(function() { throw '5'; }).toThrow(5); }); verifyFails(function(env) { diff --git a/spec/core/matchers/async/toBeRejectedSpec.js b/spec/core/matchers/async/toBeRejectedSpec.js index 442ddba2..61bd2a5a 100644 --- a/spec/core/matchers/async/toBeRejectedSpec.js +++ b/spec/core/matchers/async/toBeRejectedSpec.js @@ -2,7 +2,8 @@ describe('toBeRejected', function() { it('passes if the actual is rejected', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejected(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejected(matchersUtil), actual = Promise.reject('AsyncExpectationSpec rejection'); return matcher.compare(actual).then(function(result) { @@ -13,7 +14,8 @@ describe('toBeRejected', function() { it('fails if the actual is resolved', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejected(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejected(matchersUtil), actual = Promise.resolve(); return matcher.compare(actual).then(function(result) { @@ -22,7 +24,8 @@ describe('toBeRejected', function() { }); it('fails if actual is not a promise', function() { - var matcher = jasmineUnderTest.asyncMatchers.toBeRejected(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejected(matchersUtil), actual = 'not a promise'; function f() { diff --git a/spec/core/matchers/async/toBeRejectedWithErrorSpec.js b/spec/core/matchers/async/toBeRejectedWithErrorSpec.js index 3b207645..03a337b0 100644 --- a/spec/core/matchers/async/toBeRejectedWithErrorSpec.js +++ b/spec/core/matchers/async/toBeRejectedWithErrorSpec.js @@ -2,7 +2,8 @@ describe('#toBeRejectedWithError', function () { it('passes when Error type matches', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new TypeError('foo')); return matcher.compare(actual, TypeError).then(function (result) { @@ -16,7 +17,8 @@ describe('#toBeRejectedWithError', function () { it('passes when Error type and message matches', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new TypeError('foo')); return matcher.compare(actual, TypeError, 'foo').then(function (result) { @@ -30,7 +32,8 @@ describe('#toBeRejectedWithError', function () { it('passes when Error matches and is exactly Error', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error()); return matcher.compare(actual, Error).then(function (result) { @@ -45,7 +48,8 @@ describe('#toBeRejectedWithError', function () { it('passes when Error message matches a string', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error('foo')); return matcher.compare(actual, 'foo').then(function (result) { @@ -59,7 +63,8 @@ describe('#toBeRejectedWithError', function () { it('passes when Error message matches a RegExp', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error('foo')); return matcher.compare(actual, /foo/).then(function (result) { @@ -73,7 +78,8 @@ describe('#toBeRejectedWithError', function () { it('passes when Error message is empty', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error()); return matcher.compare(actual, '').then(function (result) { @@ -87,7 +93,8 @@ describe('#toBeRejectedWithError', function () { it('passes when no arguments', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error()); return matcher.compare(actual, void 0).then(function (result) { @@ -101,7 +108,8 @@ describe('#toBeRejectedWithError', function () { it('fails when resolved', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.resolve(new Error('foo')); return matcher.compare(actual, 'foo').then(function (result) { @@ -115,7 +123,8 @@ describe('#toBeRejectedWithError', function () { it('fails when rejected with non Error type', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject('foo'); return matcher.compare(actual, 'foo').then(function (result) { @@ -129,7 +138,8 @@ describe('#toBeRejectedWithError', function () { it('fails when Error type mismatches', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error('foo')); return matcher.compare(actual, TypeError, 'foo').then(function (result) { @@ -143,7 +153,8 @@ describe('#toBeRejectedWithError', function () { it('fails when Error message mismatches', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = Promise.reject(new Error('foo')); return matcher.compare(actual, 'bar').then(function (result) { @@ -155,7 +166,8 @@ describe('#toBeRejectedWithError', function () { }); it('fails if actual is not a promise', function() { - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWithError(matchersUtil), actual = 'not a promise'; function f() { diff --git a/spec/core/matchers/async/toBeRejectedWithSpec.js b/spec/core/matchers/async/toBeRejectedWithSpec.js index ea927c20..c1d22d0b 100644 --- a/spec/core/matchers/async/toBeRejectedWithSpec.js +++ b/spec/core/matchers/async/toBeRejectedWithSpec.js @@ -2,7 +2,8 @@ describe('#toBeRejectedWith', function () { it('should return true if the promise is rejected with the expected value', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = Promise.reject({error: 'PEBCAK'}); return matcher.compare(actual, {error: 'PEBCAK'}).then(function (result) { @@ -13,7 +14,8 @@ describe('#toBeRejectedWith', function () { it('should fail if the promise resolves', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = Promise.resolve(); return matcher.compare(actual, '').then(function (result) { @@ -24,7 +26,8 @@ describe('#toBeRejectedWith', function () { it('should fail if the promise is rejected with a different value', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = Promise.reject('A Bad Apple'); return matcher.compare(actual, 'Some Cool Thing').then(function (result) { @@ -38,7 +41,8 @@ describe('#toBeRejectedWith', function () { it('should build its error correctly when negated', function () { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = Promise.reject(true); return matcher.compare(actual, true).then(function (result) { @@ -53,7 +57,8 @@ describe('#toBeRejectedWith', function () { jasmine.getEnv().requirePromises(); var customEqualityTesters = [function() { return true; }], - matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(jasmineUnderTest.matchersUtil, customEqualityTesters), + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: customEqualityTesters}), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = Promise.reject('actual'); return matcher.compare(actual, 'expected').then(function(result) { @@ -62,7 +67,8 @@ describe('#toBeRejectedWith', function () { }); it('fails if actual is not a promise', function() { - var matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeRejectedWith(matchersUtil), actual = 'not a promise'; function f() { diff --git a/spec/core/matchers/async/toBeResolvedSpec.js b/spec/core/matchers/async/toBeResolvedSpec.js index 28297e30..a6ab25ba 100644 --- a/spec/core/matchers/async/toBeResolvedSpec.js +++ b/spec/core/matchers/async/toBeResolvedSpec.js @@ -2,7 +2,8 @@ describe('toBeResolved', function() { it('passes if the actual is resolved', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeResolved(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolved(matchersUtil), actual = Promise.resolve(); return matcher.compare(actual).then(function(result) { @@ -13,7 +14,8 @@ describe('toBeResolved', function() { it('fails if the actual is rejected', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeResolved(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolved(matchersUtil), actual = Promise.reject('AsyncExpectationSpec rejection'); return matcher.compare(actual).then(function(result) { @@ -22,7 +24,8 @@ describe('toBeResolved', function() { }); it('fails if actual is not a promise', function() { - var matcher = jasmineUnderTest.asyncMatchers.toBeResolved(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolved(matchersUtil), actual = 'not a promise'; function f() { diff --git a/spec/core/matchers/async/toBeResolvedToSpec.js b/spec/core/matchers/async/toBeResolvedToSpec.js index dc330920..426f9f12 100644 --- a/spec/core/matchers/async/toBeResolvedToSpec.js +++ b/spec/core/matchers/async/toBeResolvedToSpec.js @@ -2,7 +2,8 @@ describe('#toBeResolvedTo', function() { it('passes if the promise is resolved to the expected value', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.resolve({foo: 42}); return matcher.compare(actual, {foo: 42}).then(function(result) { @@ -13,7 +14,8 @@ describe('#toBeResolvedTo', function() { it('fails if the promise is rejected', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.reject('AsyncExpectationSpec error'); return matcher.compare(actual, '').then(function(result) { @@ -27,7 +29,8 @@ describe('#toBeResolvedTo', function() { it('fails if the promise is resolved to a different value', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.resolve({foo: 17}); return matcher.compare(actual, {foo: 42}).then(function(result) { @@ -41,7 +44,8 @@ describe('#toBeResolvedTo', function() { it('builds its message correctly when negated', function() { jasmine.getEnv().requirePromises(); - var matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.resolve(true); return matcher.compare(actual, true).then(function(result) { @@ -56,7 +60,8 @@ describe('#toBeResolvedTo', function() { jasmine.getEnv().requirePromises(); var customEqualityTesters = [function() { return true; }], - matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(jasmineUnderTest.matchersUtil, customEqualityTesters), + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: customEqualityTesters}), + matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = Promise.resolve('actual'); return matcher.compare(actual, 'expected').then(function(result) { @@ -65,7 +70,8 @@ describe('#toBeResolvedTo', function() { }); it('fails if actual is not a promise', function() { - var matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(jasmineUnderTest.matchersUtil), + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil), actual = 'not a promise'; function f() { diff --git a/spec/core/matchers/matchersUtilSpec.js b/spec/core/matchers/matchersUtilSpec.js index 570f8f20..096d7c86 100644 --- a/spec/core/matchers/matchersUtilSpec.js +++ b/spec/core/matchers/matchersUtilSpec.js @@ -1,178 +1,210 @@ describe("matchersUtil", function() { describe("equals", function() { it("passes for literals that are triple-equal", function() { - expect(jasmineUnderTest.matchersUtil.equals(null, null)).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(void 0, void 0)).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(null, null)).toBe(true); + expect(matchersUtil.equals(void 0, void 0)).toBe(true); }); it("fails for things that are not equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals({a: "foo"}, 1)).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals({a: "foo"}, 1)).toBe(false); }); it("passes for Strings that are equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals("foo", "foo")).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals("foo", "foo")).toBe(true); }); it("fails for Strings that are not equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals("foo", "bar")).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals("foo", "bar")).toBe(false); }); it("passes for Numbers that are equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(123, 123)).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(123, 123)).toBe(true); }); it("fails for Numbers that are not equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(123, 456)).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(123, 456)).toBe(false); }); it("passes for Dates that are equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(new Date("Jan 1, 1970"), new Date("Jan 1, 1970"))).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(new Date("Jan 1, 1970"), new Date("Jan 1, 1970"))).toBe(true); }); it("fails for Dates that are not equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(new Date("Jan 1, 1970"), new Date("Feb 3, 1991"))).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(new Date("Jan 1, 1970"), new Date("Feb 3, 1991"))).toBe(false); }); it("passes for Booleans that are equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(true, true)).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(true, true)).toBe(true); }); it("fails for Booleans that are not equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(true, false)).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(true, false)).toBe(false); }); it("passes for RegExps that are equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(/foo/, /foo/)).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(/foo/, /foo/)).toBe(true); }); it("fails for RegExps that are not equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals(/foo/, /bar/)).toBe(false); - expect(jasmineUnderTest.matchersUtil.equals(new RegExp("foo", "i"), new RegExp("foo"))).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(/foo/, /bar/)).toBe(false); + expect(matchersUtil.equals(new RegExp("foo", "i"), new RegExp("foo"))).toBe(false); }); it("passes for Arrays that are equivalent", function() { - expect(jasmineUnderTest.matchersUtil.equals([1, 2], [1, 2])).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals([1, 2], [1, 2])).toBe(true); }); it("passes for Arrays that are equivalent, with elements added by changing length", function() { - var foo = []; + var foo = [], + matchersUtil = new jasmineUnderTest.MatchersUtil(); foo.length = 1; - expect(jasmineUnderTest.matchersUtil.equals(foo, [undefined])).toBe(true); + expect(matchersUtil.equals(foo, [undefined])).toBe(true); }); it("fails for Arrays that have different lengths", function() { - expect(jasmineUnderTest.matchersUtil.equals([1, 2], [1, 2, 3])).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals([1, 2], [1, 2, 3])).toBe(false); }); it("fails for Arrays that have different elements", function() { - expect(jasmineUnderTest.matchersUtil.equals([1, 2, 3], [1, 5, 3])).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals([1, 2, 3], [1, 5, 3])).toBe(false); }); it("fails for Arrays whose contents are equivalent, but have differing properties", function() { var one = [1,2,3], - two = [1,2,3]; + two = [1,2,3], + matchersUtil = new jasmineUnderTest.MatchersUtil(); one.foo = 'bar'; two.foo = 'baz'; - expect(jasmineUnderTest.matchersUtil.equals(one, two)).toBe(false); + expect(matchersUtil.equals(one, two)).toBe(false); }); it("passes for Arrays with equivalent contents and properties", function() { var one = [1,2,3], - two = [1,2,3]; + two = [1,2,3], + matchersUtil = new jasmineUnderTest.MatchersUtil(); one.foo = 'bar'; two.foo = 'bar'; - expect(jasmineUnderTest.matchersUtil.equals(one, two)).toBe(true); + expect(matchersUtil.equals(one, two)).toBe(true); }); it("passes for Errors that are the same type and have the same message", function() { - expect(jasmineUnderTest.matchersUtil.equals(new Error("foo"), new Error("foo"))).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(new Error("foo"), new Error("foo"))).toBe(true); }); it("fails for Errors that are the same type and have different messages", function() { - expect(jasmineUnderTest.matchersUtil.equals(new Error("foo"), new Error("bar"))).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(new Error("foo"), new Error("bar"))).toBe(false); }); it("fails for objects with different constructors", function() { + var matchersUtil = new jasmineUnderTest.MatchersUtil(); function One() {} function Two() {} - expect(jasmineUnderTest.matchersUtil.equals(new One(), new Two())).toBe(false); + expect(matchersUtil.equals(new One(), new Two())).toBe(false); }); it("passes for Objects that are equivalent (simple case)", function() { - expect(jasmineUnderTest.matchersUtil.equals({a: "foo"}, {a: "foo"})).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals({a: "foo"}, {a: "foo"})).toBe(true); }); it("fails for Objects that are not equivalent (simple case)", function() { - expect(jasmineUnderTest.matchersUtil.equals({a: "foo"}, {a: "bar"})).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals({a: "foo"}, {a: "bar"})).toBe(false); }); it("passes for Objects that are equivalent (deep case)", function() { - expect(jasmineUnderTest.matchersUtil.equals({a: "foo", b: { c: "bar"}}, {a: "foo", b: { c: "bar"}})).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals({a: "foo", b: { c: "bar"}}, {a: "foo", b: { c: "bar"}})).toBe(true); }); it("fails for Objects that are not equivalent (deep case)", function() { - expect(jasmineUnderTest.matchersUtil.equals({a: "foo", b: { c: "baz"}}, {a: "foo", b: { c: "bar"}})).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals({a: "foo", b: { c: "baz"}}, {a: "foo", b: { c: "bar"}})).toBe(false); }); it("passes for Objects that are equivalent (with cycles)", function() { var actual = { a: "foo" }, - expected = { a: "foo" }; + expected = { a: "foo" }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); actual.b = actual; expected.b = actual; - expect(jasmineUnderTest.matchersUtil.equals(actual, expected)).toBe(true); + expect(matchersUtil.equals(actual, expected)).toBe(true); }); it("fails for Objects that are not equivalent (with cycles)", function() { var actual = { a: "foo" }, - expected = { a: "bar" }; + expected = { a: "bar" }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); actual.b = actual; expected.b = actual; - expect(jasmineUnderTest.matchersUtil.equals(actual, expected)).toBe(false); + expect(matchersUtil.equals(actual, expected)).toBe(false); }); it("fails for Objects that have the same number of keys, but different keys/values", function () { var expected = { a: undefined }, - actual = { b: 1 }; + actual = { b: 1 }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(actual, expected)).toBe(false); + expect(matchersUtil.equals(actual, expected)).toBe(false); }); it("fails when comparing an empty object to an empty array (issue #114)", function() { var emptyObject = {}, - emptyArray = []; + emptyArray = [], + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(emptyObject, emptyArray)).toBe(false); - expect(jasmineUnderTest.matchersUtil.equals(emptyArray, emptyObject)).toBe(false); + + expect(matchersUtil.equals(emptyObject, emptyArray)).toBe(false); + expect(matchersUtil.equals(emptyArray, emptyObject)).toBe(false); }); it("passes for equivalent frozen objects (GitHub issue #266)", function() { var a = { foo: 1 }, - b = {foo: 1 }; + b = {foo: 1 }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); Object.freeze(a); Object.freeze(b); - expect(jasmineUnderTest.matchersUtil.equals(a,b)).toBe(true); + expect(matchersUtil.equals(a,b)).toBe(true); }); it("passes for equivalent Promises (GitHub issue #1314)", function() { if (typeof Promise === 'undefined') { return; } var p1 = new Promise(function () {}), - p2 = new Promise(function () {}); + p2 = new Promise(function () {}), + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(p1, p1)).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(p1, p2)).toBe(false); + expect(matchersUtil.equals(p1, p1)).toBe(true); + expect(matchersUtil.equals(p1, p2)).toBe(false); }); describe("when running in a browser", function() { @@ -185,6 +217,8 @@ describe("matchersUtil", function() { return; } var a = document.createElement("div"); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + a.setAttribute("test-attr", "attr-value"); a.appendChild(document.createTextNode('test')); @@ -192,17 +226,18 @@ describe("matchersUtil", function() { b.setAttribute("test-attr", "attr-value"); b.appendChild(document.createTextNode('test')); - expect(jasmineUnderTest.matchersUtil.equals(a,b)).toBe(true); + expect(matchersUtil.equals(a,b)).toBe(true); }); it("passes for equivalent objects from different frames", function() { if (isNotRunningInBrowser()) { return; } + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var iframe = document.createElement('iframe'); document.body.appendChild(iframe); iframe.contentWindow.eval('window.testObject = {}'); - expect(jasmineUnderTest.matchersUtil.equals({}, iframe.contentWindow.testObject)).toBe(true); + expect(matchersUtil.equals({}, iframe.contentWindow.testObject)).toBe(true); document.body.removeChild(iframe); }); @@ -210,6 +245,7 @@ describe("matchersUtil", function() { if (isNotRunningInBrowser()) { return; } + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var a = document.createElement("div"); a.setAttribute("test-attr", "attr-value"); a.appendChild(document.createTextNode('test')); @@ -218,16 +254,16 @@ describe("matchersUtil", function() { b.setAttribute("test-attr", "attr-value2"); b.appendChild(document.createTextNode('test')); - expect(jasmineUnderTest.matchersUtil.equals(a,b)).toBe(false); + expect(matchersUtil.equals(a,b)).toBe(false); b.setAttribute("test-attr", "attr-value"); - expect(jasmineUnderTest.matchersUtil.equals(a,b)).toBe(true); + expect(matchersUtil.equals(a,b)).toBe(true); b.appendChild(document.createTextNode('2')); - expect(jasmineUnderTest.matchersUtil.equals(a,b)).toBe(false); + expect(matchersUtil.equals(a,b)).toBe(false); a.appendChild(document.createTextNode('2')); - expect(jasmineUnderTest.matchersUtil.equals(a,b)).toBe(true); + expect(matchersUtil.equals(a,b)).toBe(true); }); }); @@ -240,43 +276,47 @@ describe("matchersUtil", function() { if (isNotRunningInNode()) { return; } + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var vm = require('vm'); var sandbox = { obj: null }; vm.runInNewContext('obj = {a: 1, b: 2}', sandbox); - expect(jasmineUnderTest.matchersUtil.equals(sandbox.obj, {a: 1, b: 2})).toBe(true); + expect(matchersUtil.equals(sandbox.obj, {a: 1, b: 2})).toBe(true); }); it("passes for equivalent arrays from different vm contexts", function() { if (isNotRunningInNode()) { return; } + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var vm = require('vm'); var sandbox = { arr: null }; vm.runInNewContext('arr = [1, 2]', sandbox); - expect(jasmineUnderTest.matchersUtil.equals(sandbox.arr, [1, 2])).toBe(true); + expect(matchersUtil.equals(sandbox.arr, [1, 2])).toBe(true); }); }); it("passes when Any is used", function() { var number = 3, - anyNumber = new jasmineUnderTest.Any(Number); + anyNumber = new jasmineUnderTest.Any(Number), + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(number, anyNumber)).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(anyNumber, number)).toBe(true); + expect(matchersUtil.equals(number, anyNumber)).toBe(true); + expect(matchersUtil.equals(anyNumber, number)).toBe(true); }); it("fails when Any is compared to something unexpected", function() { var number = 3, - anyString = new jasmineUnderTest.Any(String); + anyString = new jasmineUnderTest.Any(String), + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(number, anyString)).toBe(false); - expect(jasmineUnderTest.matchersUtil.equals(anyString, number)).toBe(false); + expect(matchersUtil.equals(number, anyString)).toBe(false); + expect(matchersUtil.equals(anyString, number)).toBe(false); }); it("passes when ObjectContaining is used", function() { @@ -284,135 +324,209 @@ describe("matchersUtil", function() { foo: 3, bar: 7 }, - containing = new jasmineUnderTest.ObjectContaining({foo: 3}); + containing = new jasmineUnderTest.ObjectContaining({foo: 3}), + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(obj, containing)).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(containing, obj)).toBe(true); + expect(matchersUtil.equals(obj, containing)).toBe(true); + expect(matchersUtil.equals(containing, obj)).toBe(true); }); it("passes when MapContaining is used", function() { jasmine.getEnv().requireFunctioningMaps(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var obj = new Map(); obj.set(1, 2); obj.set('foo', 'bar'); var containing = new jasmineUnderTest.MapContaining(new Map()); containing.sample.set('foo', 'bar'); - expect(jasmineUnderTest.matchersUtil.equals(obj, containing)).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(containing, obj)).toBe(true); + expect(matchersUtil.equals(obj, containing)).toBe(true); + expect(matchersUtil.equals(containing, obj)).toBe(true); }); it("passes when SetContaining is used", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var obj = new Set(); obj.add(1); obj.add('foo'); var containing = new jasmineUnderTest.SetContaining(new Set()); containing.sample.add(1); - expect(jasmineUnderTest.matchersUtil.equals(obj, containing)).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(containing, obj)).toBe(true); + expect(matchersUtil.equals(obj, containing)).toBe(true); + expect(matchersUtil.equals(containing, obj)).toBe(true); }); it("passes when an asymmetric equality tester returns true", function() { - var tester = { asymmetricMatch: function(other) { return true; } }; + var tester = { asymmetricMatch: function(other) { return true; } }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(false, tester)).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(tester, false)).toBe(true); + expect(matchersUtil.equals(false, tester)).toBe(true); + expect(matchersUtil.equals(tester, false)).toBe(true); }); it("fails when an asymmetric equality tester returns false", function() { - var tester = { asymmetricMatch: function(other) { return false; } }; + var tester = { asymmetricMatch: function(other) { return false; } }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(true, tester)).toBe(false); - expect(jasmineUnderTest.matchersUtil.equals(tester, true)).toBe(false); + expect(matchersUtil.equals(true, tester)).toBe(false); + expect(matchersUtil.equals(tester, true)).toBe(false); }); it("passes when ArrayContaining is used", function() { - var arr = ["foo", "bar"]; + var arr = ["foo", "bar"], + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(arr, new jasmineUnderTest.ArrayContaining(["bar"]))).toBe(true); + expect(matchersUtil.equals(arr, new jasmineUnderTest.ArrayContaining(["bar"]))).toBe(true); }); - it("passes when a custom equality matcher returns true", function() { - var tester = function(a, b) { return true; }; + it("passes when a custom equality matcher passed to equals returns true", function() { + // TODO: remove this in the next major release. + var tester = function(a, b) { return true; }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(1, 2, [tester])).toBe(true); + expect(matchersUtil.equals(1, 2, [tester])).toBe(true); + }); + + it("passes when a custom equality matcher passed to the constructor returns true", function() { + var tester = function(a, b) { return true; }, + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); + + expect(matchersUtil.equals(1, 2)).toBe(true); }); it("passes for two empty Objects", function () { - expect(jasmineUnderTest.matchersUtil.equals({}, {})).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals({}, {})).toBe(true); }); - describe("when a custom equality matcher is installed that returns 'undefined'", function () { + describe("when a custom equality matcher is passed to equals that returns 'undefined'", function () { + // TODO: remove this in the next major release. var tester = function(a, b) { return jasmine.undefined; }; it("passes for two empty Objects", function () { - expect(jasmineUnderTest.matchersUtil.equals({}, {}, [tester])).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals({}, {}, [tester])).toBe(true); }); }); - it("fails for equivalents when a custom equality matcher returns false", function() { - var tester = function(a, b) { return false; }; + describe("when a custom equality matcher is passed to the constructor that returns 'undefined'", function () { + var tester = function(a, b) { return jasmine.undefined; }; - expect(jasmineUnderTest.matchersUtil.equals(1, 1, [tester])).toBe(false); + it("passes for two empty Objects", function () { + var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); + expect(matchersUtil.equals({}, {})).toBe(true); + }); }); - it("passes for an asymmetric equality tester that returns true when a custom equality tester return false", function() { + it("fails for equivalents when a custom equality matcher passed to equals returns false", function() { + // TODO: remove this in the next major release. + var tester = function(a, b) { return false; }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); + + expect(matchersUtil.equals(1, 1, [tester])).toBe(false); + }); + + it("fails for equivalents when a custom equality matcher passed to the constructor returns false", function() { + var tester = function(a, b) { return false; }, + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}); + + expect(matchersUtil.equals(1, 1)).toBe(false); + }); + + it("passes for an asymmetric equality tester that returns true when a custom equality tester passed to equals return false", function() { + // TODO: remove this in the next major release. var asymmetricTester = { asymmetricMatch: function(other) { return true; } }, - symmetricTester = function(a, b) { return false; }; + symmetricTester = function(a, b) { return false; }, + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(asymmetricTester, true, [symmetricTester])).toBe(true); - expect(jasmineUnderTest.matchersUtil.equals(true, asymmetricTester, [symmetricTester])).toBe(true); + expect(matchersUtil.equals(asymmetricTester, true, [symmetricTester])).toBe(true); + expect(matchersUtil.equals(true, asymmetricTester, [symmetricTester])).toBe(true); }); - it("passes custom equality matchers to asymmetric equality testers", function() { - var tester = function(a, b) {}; - var asymmetricTester = { asymmetricMatch: jasmine.createSpy('asymmetricMatch') }; - asymmetricTester.asymmetricMatch.and.returnValue(true); - var other = {}; + it("passes for an asymmetric equality tester that returns true when a custom equality tester passed to the constructor return false", function() { + var asymmetricTester = { asymmetricMatch: function(other) { return true; } }, + symmetricTester = function(a, b) { return false; }, + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [symmetricTester()]}); - expect(jasmineUnderTest.matchersUtil.equals(asymmetricTester, other, [tester])).toBe(true); - expect(asymmetricTester.asymmetricMatch).toHaveBeenCalledWith(other, [tester]); + expect(matchersUtil.equals(asymmetricTester, true)).toBe(true); + expect(matchersUtil.equals(true, asymmetricTester)).toBe(true); + }); + + describe("The compatibility shim passed to asymmetric equality testers", function() { + describe("When equals is called with custom equality testers", function() { + it("is both a matchersUtil and the custom equality testers passed to equals", function() { + var asymmetricTester = jasmine.createSpyObj('tester', ['asymmetricMatch']), + symmetricTester = function() { } , + matchersUtil = new jasmineUnderTest.MatchersUtil(), + shim; + + matchersUtil.equals(true, asymmetricTester, [symmetricTester]); + shim = asymmetricTester.asymmetricMatch.calls.argsFor(0)[1]; + expect(shim).toEqual(jasmine.any(jasmineUnderTest.MatchersUtil)); + expect(shim.length).toEqual(1); + expect(shim[0]).toEqual(symmetricTester); + }); + }); + + describe("When equals is called with custom equality testers", function() { + it("is both a matchersUtil and the custom equality testers passed to the constructor", function() { + var asymmetricTester = jasmine.createSpyObj('tester', ['asymmetricMatch']), + symmetricTester = function() { } , + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [symmetricTester]}), + shim; + + matchersUtil.equals(true, asymmetricTester); + shim = asymmetricTester.asymmetricMatch.calls.argsFor(0)[1]; + expect(shim).toEqual(jasmine.any(jasmineUnderTest.MatchersUtil)); + expect(shim.length).toEqual(1); + expect(shim[0]).toEqual(symmetricTester); + }); + }); }); it("passes when an Any is compared to an Any that checks for the same type", function() { var any1 = new jasmineUnderTest.Any(Function), - any2 = new jasmineUnderTest.Any(Function); + any2 = new jasmineUnderTest.Any(Function), + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.equals(any1, any2)).toBe(true); + expect(matchersUtil.equals(any1, any2)).toBe(true); }); it("passes for null prototype objects with same properties", function () { var objA = Object.create(null), - objB = Object.create(null); + objB = Object.create(null), + matchersUtil = new jasmineUnderTest.MatchersUtil(); objA.name = 'test'; objB.name = 'test'; - expect(jasmineUnderTest.matchersUtil.equals(objA, objB)).toBe(true); + expect(matchersUtil.equals(objA, objB)).toBe(true); }); it("fails for null prototype objects with different properties", function () { var objA = Object.create(null), - objB = Object.create(null); + objB = Object.create(null), + matchersUtil = new jasmineUnderTest.MatchersUtil(); objA.name = 'test'; objB.test = 'name'; - expect(jasmineUnderTest.matchersUtil.equals(objA, objB)).toBe(false); + expect(matchersUtil.equals(objA, objB)).toBe(false); }); it("passes when comparing two empty sets", function() { jasmine.getEnv().requireFunctioningSets(); - expect(jasmineUnderTest.matchersUtil.equals(new Set(), new Set())).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(new Set(), new Set())).toBe(true); }); it("passes when comparing identical sets", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var setA = new Set(); setA.add(6); setA.add(5); @@ -420,12 +534,13 @@ describe("matchersUtil", function() { setB.add(6); setB.add(5); - expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(true); + expect(matchersUtil.equals(setA, setB)).toBe(true); }); it("passes when comparing identical sets with different insertion order and simple elements", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var setA = new Set(); setA.add(3); setA.add(6); @@ -433,12 +548,13 @@ describe("matchersUtil", function() { setB.add(6); setB.add(3); - expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(true); + expect(matchersUtil.equals(setA, setB)).toBe(true); }); it("passes when comparing identical sets with different insertion order and complex elements 1", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var setA1 = new Set(); setA1.add(['a',3]); setA1.add([6,1]); @@ -460,12 +576,13 @@ describe("matchersUtil", function() { setB.add(setB1); setB.add(setB2); - expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(true); + expect(matchersUtil.equals(setA, setB)).toBe(true); }); it("passes when comparing identical sets with different insertion order and complex elements 2", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var setA = new Set(); setA.add([[1,2], [3,4]]); setA.add([[5,6], [7,8]]); @@ -473,11 +590,12 @@ describe("matchersUtil", function() { setB.add([[5,6], [7,8]]); setB.add([[1,2], [3,4]]); - expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(true); + expect(matchersUtil.equals(setA, setB)).toBe(true); }); it("fails for sets with different elements", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var setA = new Set(); setA.add(6); setA.add(3); @@ -487,11 +605,12 @@ describe("matchersUtil", function() { setB.add(4); setB.add(5); - expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(false); + expect(matchersUtil.equals(setA, setB)).toBe(false); }); it("fails for sets of different size", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var setA = new Set(); setA.add(6); setA.add(3); @@ -500,36 +619,40 @@ describe("matchersUtil", function() { setB.add(4); setB.add(5); - expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(false); + expect(matchersUtil.equals(setA, setB)).toBe(false); }); it("passes when comparing two empty maps", function() { jasmine.getEnv().requireFunctioningMaps(); - expect(jasmineUnderTest.matchersUtil.equals(new Map(), new Map())).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.equals(new Map(), new Map())).toBe(true); }); it("passes when comparing identical maps", function() { jasmine.getEnv().requireFunctioningMaps(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var mapA = new Map(); mapA.set(6, 5); var mapB = new Map(); mapB.set(6, 5); - expect(jasmineUnderTest.matchersUtil.equals(mapA, mapB)).toBe(true); + expect(matchersUtil.equals(mapA, mapB)).toBe(true); }); it("passes when comparing identical maps with different insertion order", function() { jasmine.getEnv().requireFunctioningMaps(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var mapA = new Map(); mapA.set("a", 3); mapA.set(6, 1); var mapB = new Map(); mapB.set(6, 1); mapB.set("a", 3); - expect(jasmineUnderTest.matchersUtil.equals(mapA, mapB)).toBe(true); + expect(matchersUtil.equals(mapA, mapB)).toBe(true); }); it("fails for maps with different elements", function() { jasmine.getEnv().requireFunctioningMaps(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var mapA = new Map(); mapA.set(6, 3); mapA.set(5, 1); @@ -537,17 +660,18 @@ describe("matchersUtil", function() { mapB.set(6, 4); mapB.set(5, 1); - expect(jasmineUnderTest.matchersUtil.equals(mapA, mapB)).toBe(false); + expect(matchersUtil.equals(mapA, mapB)).toBe(false); }); it("fails for maps of different size", function() { jasmine.getEnv().requireFunctioningMaps(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var mapA = new Map(); mapA.set(6, 3); var mapB = new Map(); mapB.set(6, 4); mapB.set(5, 1); - expect(jasmineUnderTest.matchersUtil.equals(mapA, mapB)).toBe(false); + expect(matchersUtil.equals(mapA, mapB)).toBe(false); }); describe("when running in an environment with array polyfills", function() { @@ -593,71 +717,119 @@ describe("matchersUtil", function() { expect(['foo']).toEqual(['foo']); }); }); + + it('uses a diffBuilder if one is provided as the fourth argument', function() { + // TODO: remove this in the next major release. + var diffBuilder = new jasmineUnderTest.DiffBuilder(), + matchersUtil = new jasmineUnderTest.MatchersUtil(); + + spyOn(diffBuilder, 'record'); + spyOn(diffBuilder, 'withPath').and.callThrough(); + + matchersUtil.equals([1], [2], [], diffBuilder); + expect(diffBuilder.withPath).toHaveBeenCalledWith('length', jasmine.any(Function)); + expect(diffBuilder.withPath).toHaveBeenCalledWith(0, jasmine.any(Function)); + expect(diffBuilder.record).toHaveBeenCalledWith(1, 2); + }); + + it('uses a diffBuilder if one is provided as the third argument', function() { + var diffBuilder = new jasmineUnderTest.DiffBuilder(), + matchersUtil = new jasmineUnderTest.MatchersUtil(); + + spyOn(diffBuilder, 'record'); + spyOn(diffBuilder, 'withPath').and.callThrough(); + + matchersUtil.equals([1], [2], diffBuilder); + expect(diffBuilder.withPath).toHaveBeenCalledWith('length', jasmine.any(Function)); + expect(diffBuilder.withPath).toHaveBeenCalledWith(0, jasmine.any(Function)); + expect(diffBuilder.record).toHaveBeenCalledWith(1, 2); + }); }); describe("contains", function() { it("passes when expected is a substring of actual", function() { - expect(jasmineUnderTest.matchersUtil.contains("ABC", "BC")).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.contains("ABC", "BC")).toBe(true); }); it("fails when expected is a not substring of actual", function() { - expect(jasmineUnderTest.matchersUtil.contains("ABC", "X")).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.contains("ABC", "X")).toBe(false); }); it("passes when expected is an element in an actual array", function() { - expect(jasmineUnderTest.matchersUtil.contains(['foo', 'bar'], 'foo')).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.contains(['foo', 'bar'], 'foo')).toBe(true); }); it("fails when expected is not an element in an actual array", function() { - expect(jasmineUnderTest.matchersUtil.contains(['foo', 'bar'], 'baz')).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.contains(['foo', 'bar'], 'baz')).toBe(false); }); it("passes with mixed-element arrays", function() { - expect(jasmineUnderTest.matchersUtil.contains(["foo", {some: "bar"}], "foo")).toBe(true); - expect(jasmineUnderTest.matchersUtil.contains(["foo", {some: "bar"}], {some: "bar"})).toBe(true); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.contains(["foo", {some: "bar"}], "foo")).toBe(true); + expect(matchersUtil.contains(["foo", {some: "bar"}], {some: "bar"})).toBe(true); }); - it("uses custom equality testers if passed in and actual is an Array", function() { - var customTester = function(a, b) {return true;}; + it("uses custom equality testers if passed to contains and actual is an Array", function() { + // TODO: remove this in the next major release. + var customTester = function(a, b) {return true;}, + matchersUtil = new jasmineUnderTest.MatchersUtil(); - expect(jasmineUnderTest.matchersUtil.contains([1, 2], 3, [customTester])).toBe(true); + expect(matchersUtil.contains([1, 2], 3, [customTester])).toBe(true); + }); + + it("uses custom equality testers if passed to the constructor and actual is an Array", function() { + var customTester = function(a, b) {return true;}, + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [customTester]}); + + expect(matchersUtil.contains([1, 2], 3)).toBe(true); }); it("fails when actual is undefined", function() { - expect(jasmineUnderTest.matchersUtil.contains(undefined, 'A')).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.contains(undefined, 'A')).toBe(false); }); it("fails when actual is null", function() { - expect(jasmineUnderTest.matchersUtil.contains(null, 'A')).toBe(false); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); + expect(matchersUtil.contains(null, 'A')).toBe(false); }); it("passes with array-like objects", function() { - var capturedArgs = null; + var capturedArgs = null, + matchersUtil = new jasmineUnderTest.MatchersUtil(); + function testFunction(){ capturedArgs = arguments; } + testFunction('foo', 'bar'); - expect(jasmineUnderTest.matchersUtil.contains(capturedArgs, 'bar')).toBe(true); + expect(matchersUtil.contains(capturedArgs, 'bar')).toBe(true); }); it("passes for set members", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var setItem = {'foo': 'bar'}; var set = new Set(); set.add(setItem); - expect(jasmineUnderTest.matchersUtil.contains(set, setItem)).toBe(true); + expect(matchersUtil.contains(set, setItem)).toBe(true); }); // documenting current behavior it("fails (!) for objects that equal to a set member", function() { jasmine.getEnv().requireFunctioningSets(); + var matchersUtil = new jasmineUnderTest.MatchersUtil(); var set = new Set(); set.add({'foo': 'bar'}); - expect(jasmineUnderTest.matchersUtil.contains(set, {'foo': 'bar'})).toBe(false); + expect(matchersUtil.contains(set, {'foo': 'bar'})).toBe(false); }); }); @@ -666,7 +838,8 @@ describe("matchersUtil", function() { it("builds an English sentence for a failure case", function() { var actual = "foo", name = "toBar", - message = jasmineUnderTest.matchersUtil.buildFailureMessage(name, false, actual); + matchersUtil = new jasmineUnderTest.MatchersUtil(), + message = matchersUtil.buildFailureMessage(name, false, actual); expect(message).toEqual("Expected 'foo' to bar."); }); @@ -675,7 +848,8 @@ describe("matchersUtil", function() { var actual = "foo", name = "toBar", isNot = true, - message = message = jasmineUnderTest.matchersUtil.buildFailureMessage(name, isNot, actual); + matchersUtil = new jasmineUnderTest.MatchersUtil(), + message = message = matchersUtil.buildFailureMessage(name, isNot, actual); expect(message).toEqual("Expected 'foo' not to bar."); }); @@ -683,7 +857,8 @@ describe("matchersUtil", function() { it("builds an English sentence for an arbitrary array of expected arguments", function() { var actual = "foo", name = "toBar", - message = jasmineUnderTest.matchersUtil.buildFailureMessage(name, false, actual, "quux", "corge"); + matchersUtil = new jasmineUnderTest.MatchersUtil(), + message = matchersUtil.buildFailureMessage(name, false, actual, "quux", "corge"); expect(message).toEqual("Expected 'foo' to bar 'quux', 'corge'."); }); diff --git a/spec/core/matchers/toBeSpec.js b/spec/core/matchers/toBeSpec.js index e065af57..8e27b992 100644 --- a/spec/core/matchers/toBeSpec.js +++ b/spec/core/matchers/toBeSpec.js @@ -1,6 +1,7 @@ describe("toBe", function() { it("passes with no message when actual === expected", function() { - var matcher = jasmineUnderTest.matchers.toBe(jasmineUnderTest.matchersUtil), + var util = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(util), result; result = matcher.compare(1, 1); @@ -8,7 +9,8 @@ describe("toBe", function() { }); it("passes with a custom message when expected is an array", function() { - var matcher = jasmineUnderTest.matchers.toBe(jasmineUnderTest.matchersUtil), + var util = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(util), result, array = [1]; @@ -18,7 +20,8 @@ describe("toBe", function() { }); it("passes with a custom message when expected is an object", function() { - var matcher = jasmineUnderTest.matchers.toBe(jasmineUnderTest.matchersUtil), + var util = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(util), result, obj = {foo: "bar"}; @@ -28,7 +31,8 @@ describe("toBe", function() { }); it("fails with no message when actual !== expected", function() { - var matcher = jasmineUnderTest.matchers.toBe(jasmineUnderTest.matchersUtil), + var util = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(util), result; result = matcher.compare(1, 2); @@ -37,7 +41,8 @@ describe("toBe", function() { }); it("fails with a custom message when expected is an array", function() { - var matcher = jasmineUnderTest.matchers.toBe(jasmineUnderTest.matchersUtil), + var util = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(util), result; result = matcher.compare([1], [1]); @@ -46,7 +51,8 @@ describe("toBe", function() { }); it("fails with a custom message when expected is an object", function() { - var matcher = jasmineUnderTest.matchers.toBe(jasmineUnderTest.matchersUtil), + var util = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.matchers.toBe(util), result; result = matcher.compare({foo: "bar"}, {foo: "bar"}); diff --git a/spec/core/matchers/toContainSpec.js b/spec/core/matchers/toContainSpec.js index a56d05e4..effe5862 100644 --- a/spec/core/matchers/toContainSpec.js +++ b/spec/core/matchers/toContainSpec.js @@ -7,20 +7,19 @@ describe("toContain", function() { result; result = matcher.compare("ABC", "B"); - expect(util.contains).toHaveBeenCalledWith("ABC", "B", []); + expect(util.contains).toHaveBeenCalledWith("ABC", "B"); expect(result.pass).toBe(true); }); - it("delegates to jasmineUnderTest.matchersUtil.contains, passing in equality testers if present", function() { - var util = { - contains: jasmine.createSpy('delegated-contains').and.returnValue(true) + it("works with custom equality testers", function() { + var tester = function (a, b) { + return a.toString() === b.toString(); }, - customEqualityTesters = ['a', 'b'], - matcher = jasmineUnderTest.matchers.toContain(util, customEqualityTesters), + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}), + matcher = jasmineUnderTest.matchers.toContain(matchersUtil), result; - result = matcher.compare("ABC", "B"); - expect(util.contains).toHaveBeenCalledWith("ABC", "B", ['a', 'b']); + result = matcher.compare(['1', '2'], 2); expect(result.pass).toBe(true); }); }); diff --git a/spec/core/matchers/toEqualSpec.js b/spec/core/matchers/toEqualSpec.js index 345eb724..2bd2f077 100644 --- a/spec/core/matchers/toEqualSpec.js +++ b/spec/core/matchers/toEqualSpec.js @@ -2,7 +2,7 @@ describe("toEqual", function() { "use strict"; function compareEquals(actual, expected) { - var util = jasmineUnderTest.matchersUtil, + var util = new jasmineUnderTest.MatchersUtil(), matcher = jasmineUnderTest.matchers.toEqual(util); var result = matcher.compare(actual, expected); @@ -16,32 +16,27 @@ describe("toEqual", function() { buildFailureMessage: function() { return 'does not matter' }, - DiffBuilder: jasmineUnderTest.matchersUtil.DiffBuilder + DiffBuilder: new jasmineUnderTest.DiffBuilder() }, matcher = jasmineUnderTest.matchers.toEqual(util), result; result = matcher.compare(1, 1); - expect(util.equals).toHaveBeenCalledWith(1, 1, [], jasmine.anything()); + expect(util.equals).toHaveBeenCalledWith(1, 1, jasmine.anything()); expect(result.pass).toBe(true); }); - it("delegates custom equality testers, if present", function() { - var util = { - equals: jasmine.createSpy('delegated-equals').and.returnValue(true), - buildFailureMessage: function() { - return 'does not matter' - }, - DiffBuilder: jasmineUnderTest.matchersUtil.DiffBuilder + it("works with custom equality testers", function() { + var tester = function (a, b) { + return a.toString() === b.toString(); }, - customEqualityTesters = ['a', 'b'], - matcher = jasmineUnderTest.matchers.toEqual(util, customEqualityTesters), + util = new jasmineUnderTest.MatchersUtil({customTesters: [tester]}), + matcher = jasmineUnderTest.matchers.toEqual(util), result; - result = matcher.compare(1, 1); + result = matcher.compare(1, '1'); - expect(util.equals).toHaveBeenCalledWith(1, 1, ['a', 'b'], jasmine.anything()); expect(result.pass).toBe(true); }); diff --git a/spec/core/matchers/toHaveBeenCalledWithSpec.js b/spec/core/matchers/toHaveBeenCalledWithSpec.js index 4ac7e074..a57bc4a4 100644 --- a/spec/core/matchers/toHaveBeenCalledWithSpec.js +++ b/spec/core/matchers/toHaveBeenCalledWithSpec.js @@ -15,18 +15,16 @@ describe("toHaveBeenCalledWith", function() { expect(result.message()).toEqual("Expected spy called-spy not to have been called with:\n [ 'a', 'b' ]\nbut it was."); }); - it("passes through the custom equality testers", function() { - var util = { - contains: jasmine.createSpy('delegated-contains').and.returnValue(true) - }, - customEqualityTesters = [function() { return true; }], - matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util, customEqualityTesters), - calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'); + it("supports custom equality testers", function() { + var customEqualityTesters = [function() { return true; }], + matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: customEqualityTesters}), + matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(matchersUtil), + calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'), + result; calledSpy('a', 'b'); - matcher.compare(calledSpy, 'a', 'b'); - - expect(util.contains).toHaveBeenCalledWith([['a', 'b']], ['a', 'b'], customEqualityTesters); + result = matcher.compare(calledSpy, 'a', 'b'); + expect(result.pass).toBe(true); }); it("fails when the actual was not called", function() { @@ -43,7 +41,7 @@ describe("toHaveBeenCalledWith", function() { }); it("fails when the actual was called with different parameters", function() { - var util = jasmineUnderTest.matchersUtil, + var util = new jasmineUnderTest.MatchersUtil(), matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), calledSpy = new jasmineUnderTest.Env().createSpy('called spy'), result; diff --git a/spec/helpers/integrationMatchers.js b/spec/helpers/integrationMatchers.js index 694ccd6e..f473385c 100644 --- a/spec/helpers/integrationMatchers.js +++ b/spec/helpers/integrationMatchers.js @@ -1,10 +1,7 @@ (function(env) { env.registerIntegrationMatchers = function() { jasmine.addMatchers({ - toHaveFailedExpectationsForRunnable: function( - util, - customeEqualityTesters - ) { + toHaveFailedExpectationsForRunnable: function(util) { return { compare: function(actual, fullName, expectedFailures) { var foundRunnable = false, diff --git a/spec/npmPackage/npmPackageSpec.js b/spec/npmPackage/npmPackageSpec.js index 5248d88c..908b1c06 100644 --- a/spec/npmPackage/npmPackageSpec.js +++ b/spec/npmPackage/npmPackageSpec.js @@ -23,7 +23,7 @@ describe('npm package', function() { beforeEach(function() { jasmine.addMatchers({ - toExistInPath: function(util, customEquality) { + toExistInPath: function(util) { return { compare: function(actual, expected) { var fullPath = path.resolve(expected, actual); diff --git a/src/core/Env.js b/src/core/Env.js index 7d6a1210..e3c5f5c6 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -276,6 +276,7 @@ getJasmineRequireObj().Env = function(j$) { } var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { customMatchers[matcherName] = matchersToAdd[matcherName]; } @@ -289,6 +290,7 @@ getJasmineRequireObj().Env = function(j$) { } var customAsyncMatchers = runnableResources[currentRunnable().id].customAsyncMatchers; + for (var matcherName in matchersToAdd) { customAsyncMatchers[matcherName] = matchersToAdd[matcherName]; } @@ -308,9 +310,12 @@ getJasmineRequireObj().Env = function(j$) { }; var expectationFactory = function(actual, spec) { + var customEqualityTesters = + runnableResources[spec.id].customEqualityTesters; + return j$.Expectation.factory({ - util: j$.matchersUtil, - customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + util: new j$.MatchersUtil({ customTesters: customEqualityTesters }), + customEqualityTesters: customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, actual: actual, addExpectationResult: addExpectationResult @@ -322,8 +327,11 @@ getJasmineRequireObj().Env = function(j$) { }; var asyncExpectationFactory = function(actual, spec) { + var customEqualityTesters = + runnableResources[spec.id].customEqualityTesters; + return j$.Expectation.asyncFactory({ - util: j$.matchersUtil, + util: new j$.MatchersUtil({ customTesters: customEqualityTesters }), customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customAsyncMatchers: runnableResources[spec.id].customAsyncMatchers, actual: actual, diff --git a/src/core/Spy.js b/src/core/Spy.js index ffbb0103..3d4aca21 100644 --- a/src/core/Spy.js +++ b/src/core/Spy.js @@ -7,6 +7,8 @@ getJasmineRequireObj().Spy = function(j$) { }; })(); + var matchersUtil = new j$.MatchersUtil(); + /** * _Note:_ Do not construct this directly, use {@link spyOn}, {@link spyOnProperty}, {@link jasmine.createSpy}, or {@link jasmine.createSpyObj} * @constructor @@ -202,7 +204,7 @@ getJasmineRequireObj().Spy = function(j$) { var i; for (i = 0; i < this.strategies.length; i++) { - if (j$.matchersUtil.equals(args, this.strategies[i].args)) { + if (matchersUtil.equals(args, this.strategies[i].args)) { return this.strategies[i].strategy; } } diff --git a/src/core/asymmetricEqualityTesterArgCompatShim.js b/src/core/asymmetricEqualityTesterArgCompatShim.js new file mode 100644 index 00000000..faa4bf79 --- /dev/null +++ b/src/core/asymmetricEqualityTesterArgCompatShim.js @@ -0,0 +1,105 @@ +getJasmineRequireObj().asymmetricEqualityTesterArgCompatShim = function(j$) { + /* + Older versions of Jasmine passed an array of custom equality testers as the + second argument to each asymmetric equality tester's `asymmetricMatch` + method. Newer versions will pass a `MatchersUtil` instance. The + asymmetricEqualityTesterArgCompatShim allows for a graceful migration from + the old interface to the new by "being" both an array of custom equality + testers and a `MatchersUtil` at the same time. + + This code should be removed in the next major release. + */ + + var likelyArrayProps = [ + 'concat', + 'constructor', + 'copyWithin', + 'entries', + 'every', + 'fill', + 'filter', + 'find', + 'findIndex', + 'flat', + 'flatMap', + 'forEach', + 'includes', + 'indexOf', + 'join', + 'keys', + 'lastIndexOf', + 'length', + 'map', + 'pop', + 'push', + 'reduce', + 'reduceRight', + 'reverse', + 'shift', + 'slice', + 'some', + 'sort', + 'splice', + 'toLocaleString', + 'toSource', + 'toString', + 'unshift', + 'values' + ]; + + function asymmetricEqualityTesterArgCompatShim( + matchersUtil, + customEqualityTesters + ) { + var self = Object.create(matchersUtil), + props, + i, + k; + + copy(self, customEqualityTesters, 'length'); + + for (i = 0; i < customEqualityTesters.length; i++) { + copy(self, customEqualityTesters, i); + } + + var props = arrayProps(); + + for (i = 0; i < props.length; i++) { + k = props[i]; + if (k !== 'length') { + copy(self, Array.prototype, k); + } + } + + return self; + } + + function copy(dest, src, propName) { + Object.defineProperty(dest, propName, { + get: function() { + return src[propName]; + } + }); + } + + function arrayProps() { + var props, a, k; + + if (!Object.getOwnPropertyDescriptors) { + return likelyArrayProps.filter(function(k) { + return Array.prototype.hasOwnProperty(k); + }); + } + + props = Object.getOwnPropertyDescriptors(Array.prototype); + a = []; + + for (k in props) { + a.push(k); + } + + return a; + } + + return asymmetricEqualityTesterArgCompatShim; +}; diff --git a/src/core/asymmetric_equality/ArrayContaining.js b/src/core/asymmetric_equality/ArrayContaining.js index 92a85ed0..95c9f3f1 100644 --- a/src/core/asymmetric_equality/ArrayContaining.js +++ b/src/core/asymmetric_equality/ArrayContaining.js @@ -3,7 +3,7 @@ getJasmineRequireObj().ArrayContaining = function(j$) { this.sample = sample; } - ArrayContaining.prototype.asymmetricMatch = function(other, customTesters) { + ArrayContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isArray_(this.sample)) { throw new Error('You must provide an array to arrayContaining, not ' + j$.pp(this.sample) + '.'); } @@ -17,7 +17,7 @@ getJasmineRequireObj().ArrayContaining = function(j$) { for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; - if (!j$.matchersUtil.contains(other, item, customTesters)) { + if (!matchersUtil.contains(other, item)) { return false; } } diff --git a/src/core/asymmetric_equality/ArrayWithExactContents.js b/src/core/asymmetric_equality/ArrayWithExactContents.js index 37b8ee95..9bdba8d4 100644 --- a/src/core/asymmetric_equality/ArrayWithExactContents.js +++ b/src/core/asymmetric_equality/ArrayWithExactContents.js @@ -4,7 +4,7 @@ getJasmineRequireObj().ArrayWithExactContents = function(j$) { this.sample = sample; } - ArrayWithExactContents.prototype.asymmetricMatch = function(other, customTesters) { + ArrayWithExactContents.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isArray_(this.sample)) { throw new Error('You must provide an array to arrayWithExactContents, not ' + j$.pp(this.sample) + '.'); } @@ -15,7 +15,7 @@ getJasmineRequireObj().ArrayWithExactContents = function(j$) { for (var i = 0; i < this.sample.length; i++) { var item = this.sample[i]; - if (!j$.matchersUtil.contains(other, item, customTesters)) { + if (!matchersUtil.contains(other, item)) { return false; } } diff --git a/src/core/asymmetric_equality/MapContaining.js b/src/core/asymmetric_equality/MapContaining.js index 061c2666..7c8e3c5b 100644 --- a/src/core/asymmetric_equality/MapContaining.js +++ b/src/core/asymmetric_equality/MapContaining.js @@ -7,7 +7,7 @@ getJasmineRequireObj().MapContaining = function(j$) { this.sample = sample; } - MapContaining.prototype.asymmetricMatch = function(other, customTesters) { + MapContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isMap(other)) return false; var hasAllMatches = true; @@ -17,8 +17,8 @@ getJasmineRequireObj().MapContaining = function(j$) { var hasMatch = false; j$.util.forEachBreakable(other, function(oBreakLoop, oValue, oKey) { if ( - j$.matchersUtil.equals(oKey, key, customTesters) - && j$.matchersUtil.equals(oValue, value, customTesters) + matchersUtil.equals(oKey, key) + && matchersUtil.equals(oValue, value) ) { hasMatch = true; oBreakLoop(); diff --git a/src/core/asymmetric_equality/ObjectContaining.js b/src/core/asymmetric_equality/ObjectContaining.js index 62e9bf17..93069b25 100644 --- a/src/core/asymmetric_equality/ObjectContaining.js +++ b/src/core/asymmetric_equality/ObjectContaining.js @@ -28,12 +28,12 @@ getJasmineRequireObj().ObjectContaining = function(j$) { return hasProperty(getPrototype(obj), property); } - ObjectContaining.prototype.asymmetricMatch = function(other, customTesters) { + ObjectContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } for (var property in this.sample) { if (!hasProperty(other, property) || - !j$.matchersUtil.equals(this.sample[property], other[property], customTesters)) { + !matchersUtil.equals(this.sample[property], other[property])) { return false; } } diff --git a/src/core/asymmetric_equality/SetContaining.js b/src/core/asymmetric_equality/SetContaining.js index d9dd8fd2..00627935 100644 --- a/src/core/asymmetric_equality/SetContaining.js +++ b/src/core/asymmetric_equality/SetContaining.js @@ -7,17 +7,17 @@ getJasmineRequireObj().SetContaining = function(j$) { this.sample = sample; } - SetContaining.prototype.asymmetricMatch = function(other, customTesters) { + SetContaining.prototype.asymmetricMatch = function(other, matchersUtil) { if (!j$.isSet(other)) return false; var hasAllMatches = true; j$.util.forEachBreakable(this.sample, function(breakLoop, item) { // for each item in `sample` there should be at least one matching item in `other` - // (not using `j$.matchersUtil.contains` because it compares set members by reference, + // (not using `matchersUtil.contains` because it compares set members by reference, // not by deep value equality) var hasMatch = false; j$.util.forEachBreakable(other, function(oBreakLoop, oItem) { - if (j$.matchersUtil.equals(oItem, item, customTesters)) { + if (matchersUtil.equals(oItem, item)) { hasMatch = true; oBreakLoop(); } diff --git a/src/core/matchers/async/toBeRejectedWith.js b/src/core/matchers/async/toBeRejectedWith.js index 031230a1..621daf0b 100644 --- a/src/core/matchers/async/toBeRejectedWith.js +++ b/src/core/matchers/async/toBeRejectedWith.js @@ -11,7 +11,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { * @example * return expectAsync(aPromise).toBeRejectedWith({prop: 'value'}); */ - return function toBeRejectedWith(util, customEqualityTesters) { + return function toBeRejectedWith(util) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { @@ -32,7 +32,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { }; }, function(actualValue) { - if (util.equals(actualValue, expectedValue, customEqualityTesters)) { + if (util.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' diff --git a/src/core/matchers/async/toBeResolvedTo.js b/src/core/matchers/async/toBeResolvedTo.js index 9d5aa370..a0295c8a 100644 --- a/src/core/matchers/async/toBeResolvedTo.js +++ b/src/core/matchers/async/toBeResolvedTo.js @@ -11,7 +11,7 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { * @example * return expectAsync(aPromise).toBeResolvedTo({prop: 'value'}); */ - return function toBeResolvedTo(util, customEqualityTesters) { + return function toBeResolvedTo(util) { return { compare: function(actualPromise, expectedValue) { if (!j$.isPromiseLike(actualPromise)) { @@ -26,7 +26,7 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { return actualPromise.then( function(actualValue) { - if (util.equals(actualValue, expectedValue, customEqualityTesters)) { + if (util.equals(actualValue, expectedValue)) { return { pass: true, message: prefix(true) + '.' diff --git a/src/core/matchers/matchersUtil.js b/src/core/matchers/matchersUtil.js index cfc0c749..1aa496f4 100644 --- a/src/core/matchers/matchersUtil.js +++ b/src/core/matchers/matchersUtil.js @@ -1,63 +1,67 @@ -getJasmineRequireObj().matchersUtil = function(j$) { +getJasmineRequireObj().MatchersUtil = function(j$) { // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? - return { - equals: equals, + function MatchersUtil(options) { + options = options || {}; + this.customTesters_ = options.customTesters || []; - contains: function(haystack, needle, customTesters) { - customTesters = customTesters || []; - - if (j$.isSet(haystack)) { - return haystack.has(needle); - } - - if ((Object.prototype.toString.apply(haystack) === '[object Array]') || - (!!haystack && !haystack.indexOf)) - { - for (var i = 0; i < haystack.length; i++) { - if (equals(haystack[i], needle, customTesters)) { - return true; - } - } - return false; - } - - return !!haystack && haystack.indexOf(needle) >= 0; - }, - - buildFailureMessage: function() { - var args = Array.prototype.slice.call(arguments, 0), - matcherName = args[0], - isNot = args[1], - actual = args[2], - expected = args.slice(3), - englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); - - var message = 'Expected ' + - j$.pp(actual) + - (isNot ? ' not ' : ' ') + - englishyPredicate; - - if (expected.length > 0) { - for (var i = 0; i < expected.length; i++) { - if (i > 0) { - message += ','; - } - message += ' ' + j$.pp(expected[i]); - } - } - - return message + '.'; + if (!j$.isArray_(this.customTesters_)) { + throw new Error("MatchersUtil requires custom equality testers"); } + } + + MatchersUtil.prototype.contains = function(haystack, needle, customTesters) { + if (j$.isSet(haystack)) { + return haystack.has(needle); + } + + if ((Object.prototype.toString.apply(haystack) === '[object Array]') || + (!!haystack && !haystack.indexOf)) + { + for (var i = 0; i < haystack.length; i++) { + if (this.equals(haystack[i], needle, customTesters)) { + return true; + } + } + return false; + } + + return !!haystack && haystack.indexOf(needle) >= 0; + }; + + MatchersUtil.prototype.buildFailureMessage = function() { + var args = Array.prototype.slice.call(arguments, 0), + matcherName = args[0], + isNot = args[1], + actual = args[2], + expected = args.slice(3), + englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + + var message = 'Expected ' + + j$.pp(actual) + + (isNot ? ' not ' : ' ') + + englishyPredicate; + + if (expected.length > 0) { + for (var i = 0; i < expected.length; i++) { + if (i > 0) { + message += ','; + } + message += ' ' + j$.pp(expected[i]); + } + } + + return message + '.'; }; function isAsymmetric(obj) { return obj && j$.isA_('Function', obj.asymmetricMatch); } - function asymmetricMatch(a, b, customTesters, diffBuilder) { + MatchersUtil.prototype.asymmetricMatch_ = function(a, b, customTesters, diffBuilder) { var asymmetricA = isAsymmetric(a), asymmetricB = isAsymmetric(b), + shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters), result; if (asymmetricA && asymmetricB) { @@ -65,7 +69,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { } if (asymmetricA) { - result = a.asymmetricMatch(b, customTesters); + result = a.asymmetricMatch(b, shim); if (!result) { diffBuilder.record(a, b); } @@ -73,27 +77,36 @@ getJasmineRequireObj().matchersUtil = function(j$) { } if (asymmetricB) { - result = b.asymmetricMatch(a, customTesters); + result = b.asymmetricMatch(a, shim); if (!result) { diffBuilder.record(a, b); } return result; } - } + }; - function equals(a, b, customTesters, diffBuilder) { - customTesters = customTesters || []; + MatchersUtil.prototype.equals = function(a, b, customTestersOrDiffBuilder, diffBuilderOrNothing) { + var customTesters, diffBuilder; + + if (isDiffBuilder(customTestersOrDiffBuilder)) { + diffBuilder = customTestersOrDiffBuilder; + } else { + customTesters = customTestersOrDiffBuilder; + diffBuilder = diffBuilderOrNothing; + } + + customTesters = customTesters || this.customTesters_; diffBuilder = diffBuilder || j$.NullDiffBuilder(); - return eq(a, b, [], [], customTesters, diffBuilder); - } + return this.eq_(a, b, [], [], customTesters, diffBuilder); + }; // Equality function lovingly adapted from isEqual in // [Underscore](http://underscorejs.org) - function eq(a, b, aStack, bStack, customTesters, diffBuilder) { - var result = true, i; + MatchersUtil.prototype.eq_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { + var result = true, self = this, i; - var asymmetricResult = asymmetricMatch(a, b, customTesters, diffBuilder); + var asymmetricResult = this.asymmetricMatch_(a, b, customTesters, diffBuilder); if (!j$.util.isUndefined(asymmetricResult)) { return asymmetricResult; } @@ -230,7 +243,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { diffBuilder.record(a[i], void 0, actualArrayIsLongerFormatter); result = false; } else { - result = eq(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0, aStack, bStack, customTesters, diffBuilder) && result; + result = self.eq_(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0, aStack, bStack, customTesters, diffBuilder) && result; } }); } @@ -271,12 +284,12 @@ getJasmineRequireObj().matchersUtil = function(j$) { // otherwise explicitly look up the mapKey in the other Map since we want keys with unique // obj identity (that are otherwise equal) to not match. if (isAsymmetric(mapKey) || isAsymmetric(cmpKey) && - eq(mapKey, cmpKey, aStack, bStack, customTesters, j$.NullDiffBuilder())) { + this.eq_(mapKey, cmpKey, aStack, bStack, customTesters, j$.NullDiffBuilder())) { mapValueB = b.get(cmpKey); } else { mapValueB = b.get(mapKey); } - result = eq(mapValueA, mapValueB, aStack, bStack, customTesters, j$.NullDiffBuilder()); + result = this.eq_(mapValueA, mapValueB, aStack, bStack, customTesters, j$.NullDiffBuilder()); } } @@ -320,7 +333,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { otherValue = otherValues[l]; prevStackSize = baseStack.length; // compare by value equality - found = eq(baseValue, otherValue, baseStack, otherStack, customTesters, j$.NullDiffBuilder()); + found = this.eq_(baseValue, otherValue, baseStack, otherStack, customTesters, j$.NullDiffBuilder()); if (!found && prevStackSize !== baseStack.length) { baseStack.splice(prevStackSize); otherStack.splice(prevStackSize); @@ -369,7 +382,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { } diffBuilder.withPath(key, function() { - if(!eq(a[key], b[key], aStack, bStack, customTesters, diffBuilder)) { + if(!self.eq_(a[key], b[key], aStack, bStack, customTesters, diffBuilder)) { result = false; } }); @@ -384,7 +397,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { bStack.pop(); return result; - } + }; function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : @@ -467,4 +480,10 @@ getJasmineRequireObj().matchersUtil = function(j$) { } return formatted; } + + function isDiffBuilder(obj) { + return obj && typeof obj.record === 'function'; + } + + return MatchersUtil; }; diff --git a/src/core/matchers/toBeInstanceOf.js b/src/core/matchers/toBeInstanceOf.js index 66b658c1..cc0a0679 100644 --- a/src/core/matchers/toBeInstanceOf.js +++ b/src/core/matchers/toBeInstanceOf.js @@ -12,7 +12,7 @@ getJasmineRequireObj().toBeInstanceOf = function(j$) { * expect(3).toBeInstanceOf(Number); * expect(new Error()).toBeInstanceOf(Error); */ - function toBeInstanceOf(util, customEqualityTesters) { + function toBeInstanceOf() { return { compare: function(actual, expected) { var actualType = actual && actual.constructor ? j$.fnNameFor(actual.constructor) : j$.pp(actual), diff --git a/src/core/matchers/toContain.js b/src/core/matchers/toContain.js index 440bb4c4..cf511604 100644 --- a/src/core/matchers/toContain.js +++ b/src/core/matchers/toContain.js @@ -9,14 +9,12 @@ getJasmineRequireObj().toContain = function() { * expect(array).toContain(anElement); * expect(string).toContain(substring); */ - function toContain(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - + function toContain(util) { return { compare: function(actual, expected) { return { - pass: util.contains(actual, expected, customEqualityTesters) + pass: util.contains(actual, expected) }; } }; diff --git a/src/core/matchers/toEqual.js b/src/core/matchers/toEqual.js index 8697ed67..d78bde4c 100644 --- a/src/core/matchers/toEqual.js +++ b/src/core/matchers/toEqual.js @@ -8,9 +8,7 @@ getJasmineRequireObj().toEqual = function(j$) { * @example * expect(bigObject).toEqual({"foo": ['bar', 'baz']}); */ - function toEqual(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - + function toEqual(util) { return { compare: function(actual, expected) { var result = { @@ -18,7 +16,7 @@ getJasmineRequireObj().toEqual = function(j$) { }, diffBuilder = j$.DiffBuilder(); - result.pass = util.equals(actual, expected, customEqualityTesters, diffBuilder); + result.pass = util.equals(actual, expected, diffBuilder); // TODO: only set error message if test fails result.message = diffBuilder.getMessage(); diff --git a/src/core/matchers/toHaveBeenCalledWith.js b/src/core/matchers/toHaveBeenCalledWith.js index 277b33f3..9edde713 100644 --- a/src/core/matchers/toHaveBeenCalledWith.js +++ b/src/core/matchers/toHaveBeenCalledWith.js @@ -11,7 +11,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { * @example * expect(mySpy).toHaveBeenCalledWith('foo', 'bar', 2); */ - function toHaveBeenCalledWith(util, customEqualityTesters) { + function toHaveBeenCalledWith(util) { return { compare: function() { var args = Array.prototype.slice.call(arguments, 0), @@ -32,7 +32,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { return result; } - if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { + if (util.contains(actual.calls.allArgs(), expectedArgs)) { result.pass = true; result.message = function() { return 'Expected spy ' + actual.and.identity + ' not to have been called with:\n' + @@ -47,7 +47,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { var diffs = actual.calls.allArgs().map(function(argsForCall, callIx) { var diffBuilder = new j$.DiffBuilder(); - util.equals(argsForCall, expectedArgs, customEqualityTesters, diffBuilder); + util.equals(argsForCall, expectedArgs, diffBuilder); return 'Call ' + callIx + ':\n' + diffBuilder.getMessage().replace(/^/mg, ' '); }); diff --git a/src/core/matchers/toHaveClass.js b/src/core/matchers/toHaveClass.js index a2fd7507..32dc2ccf 100644 --- a/src/core/matchers/toHaveClass.js +++ b/src/core/matchers/toHaveClass.js @@ -10,7 +10,7 @@ getJasmineRequireObj().toHaveClass = function(j$) { * el.className = 'foo bar baz'; * expect(el).toHaveClass('bar'); */ - function toHaveClass(util, customEqualityTesters) { + function toHaveClass() { return { compare: function(actual, expected) { if (!isElement(actual)) { diff --git a/src/core/requireCore.js b/src/core/requireCore.js index a2c5f177..0d5b76ed 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -51,7 +51,12 @@ var getJasmineRequireObj = (function(jasmineGlobal) { j$.buildExpectationResult = jRequire.buildExpectationResult(); j$.noopTimer = jRequire.noopTimer(); j$.JsApiReporter = jRequire.JsApiReporter(j$); - j$.matchersUtil = jRequire.matchersUtil(j$); + j$.asymmetricEqualityTesterArgCompatShim = jRequire.asymmetricEqualityTesterArgCompatShim( + j$ + ); + j$.MatchersUtil = jRequire.MatchersUtil(j$); + j$.matchersUtil = new j$.MatchersUtil({ customTesters: [] }); + j$.ObjectContaining = jRequire.ObjectContaining(j$); j$.ArrayContaining = jRequire.ArrayContaining(j$); j$.ArrayWithExactContents = jRequire.ArrayWithExactContents(j$);