diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 4c17bf3c..824036b1 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -144,6 +144,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) { 'toBeUndefined', 'toContain', 'toEqual', + 'toHaveSize', 'toHaveBeenCalled', 'toHaveBeenCalledBefore', 'toHaveBeenCalledTimes', @@ -290,9 +291,9 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { return typeof jasmineGlobal.Node !== 'undefined' ? obj instanceof jasmineGlobal.Node : obj !== null && - typeof obj === 'object' && - typeof obj.nodeType === 'number' && - typeof obj.nodeName === 'string'; + typeof obj === 'object' && + typeof obj.nodeType === 'number' && + typeof obj.nodeName === 'string'; // return obj.nodeType > 0; }; @@ -763,7 +764,7 @@ getJasmineRequireObj().Spec = function(j$) { onComplete: function() { onComplete( self.result.status === 'failed' && - new j$.StopExecutionError('spec failed') + new j$.StopExecutionError('spec failed') ); }, userContext: this.userContext() @@ -828,8 +829,8 @@ getJasmineRequireObj().Spec = function(j$) { this.result.failedExpectations.length > 0 || (failSpecWithNoExpectations && this.result.failedExpectations.length + - this.result.passedExpectations.length === - 0) + this.result.passedExpectations.length === + 0) ) { return 'failed'; } @@ -1170,7 +1171,7 @@ getJasmineRequireObj().Env = function(j$) { } runnableResources[ currentRunnable().id - ].defaultStrategyFn = defaultStrategyFn; + ].defaultStrategyFn = defaultStrategyFn; }; this.addSpyStrategy = function(name, fn) { @@ -2565,7 +2566,7 @@ getJasmineRequireObj().ObjectContaining = function(j$) { for (var property in this.sample) { if (!hasProperty(other, property) || - !matchersUtil.equals(this.sample[property], other[property])) { + !matchersUtil.equals(this.sample[property], other[property])) { return false; } } @@ -3373,7 +3374,7 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) { // scheduled in a funcToRun from forcing an extra iteration currentTime !== endTime && scheduledLookup[0] <= endTime - ); + ); // ran out of functions to call, but still time left on the clock if (currentTime !== endTime) { @@ -4108,24 +4109,24 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { return actualPromise.then( function() { + return { + pass: false, + message: prefix(false) + ' but it was resolved.' + }; + }, + function(actualValue) { + if (matchersUtil.equals(actualValue, expectedValue)) { + return { + pass: true, + message: prefix(true) + '.' + }; + } else { return { pass: false, - message: prefix(false) + ' but it was resolved.' + message: prefix(false) + ' but it was rejected with ' + matchersUtil.pp(actualValue) + '.' }; - }, - function(actualValue) { - if (matchersUtil.equals(actualValue, expectedValue)) { - return { - pass: true, - message: prefix(true) + '.' - }; - } else { - return { - pass: false, - message: prefix(false) + ' but it was rejected with ' + matchersUtil.pp(actualValue) + '.' - }; - } } + } ); } }; @@ -4494,9 +4495,9 @@ getJasmineRequireObj().MatchersUtil = function(j$) { MatchersUtil.prototype.asymmetricMatch_ = function(a, b, aStack, bStack, customTesters, diffBuilder) { var asymmetricA = j$.isAsymmetricEqualityTester_(a), - asymmetricB = j$.isAsymmetricEqualityTester_(b), - shim, - result; + asymmetricB = j$.isAsymmetricEqualityTester_(b), + shim, + result; if (asymmetricA === asymmetricB) { return undefined; @@ -4731,7 +4732,7 @@ 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 (j$.isAsymmetricEqualityTester_(mapKey) || j$.isAsymmetricEqualityTester_(cmpKey) && - this.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); @@ -4800,9 +4801,9 @@ getJasmineRequireObj().MatchersUtil = function(j$) { // or `Array`s from different frames are. var aCtor = a.constructor, bCtor = b.constructor; if (aCtor !== bCtor && - isFunction(aCtor) && isFunction(bCtor) && - a instanceof aCtor && b instanceof bCtor && - !(aCtor instanceof aCtor && bCtor instanceof bCtor)) { + isFunction(aCtor) && isFunction(bCtor) && + a instanceof aCtor && b instanceof bCtor && + !(aCtor instanceof aCtor && bCtor instanceof bCtor)) { diffBuilder.recordMismatch(constructorsAreDifferentFormatter.bind(null, this.pp)); return false; @@ -4849,13 +4850,13 @@ getJasmineRequireObj().MatchersUtil = function(j$) { function keys(obj, isArray) { var allKeys = Object.keys ? Object.keys(obj) : (function(o) { - var keys = []; - for (var key in o) { - if (j$.util.has(o, key)) { - keys.push(key); + var keys = []; + for (var key in o) { + if (j$.util.has(o, key)) { + keys.push(key); + } } - } - return keys; + return keys; })(obj); if (!isArray) { @@ -4863,7 +4864,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { } if (allKeys.length === 0) { - return allKeys; + return allKeys; } var extraKeys = []; @@ -4882,10 +4883,10 @@ getJasmineRequireObj().MatchersUtil = function(j$) { function objectKeysAreDifferentFormatter(pp, actual, expected, path) { var missingProperties = j$.util.objectDifference(expected, actual), - extraProperties = j$.util.objectDifference(actual, expected), - missingPropertiesMessage = formatKeyValuePairs(pp, missingProperties), - extraPropertiesMessage = formatKeyValuePairs(pp, extraProperties), - messages = []; + extraProperties = j$.util.objectDifference(actual, expected), + missingPropertiesMessage = formatKeyValuePairs(pp, missingProperties), + extraPropertiesMessage = formatKeyValuePairs(pp, extraProperties), + messages = []; if (!path.depth()) { path = 'object'; @@ -5302,15 +5303,15 @@ getJasmineRequireObj().toBeInstanceOf = function(j$) { return { compare: function(actual, expected) { var actualType = actual && actual.constructor ? j$.fnNameFor(actual.constructor) : matchersUtil.pp(actual), - expectedType = expected ? j$.fnNameFor(expected) : matchersUtil.pp(expected), - expectedMatcher, - pass; + expectedType = expected ? j$.fnNameFor(expected) : matchersUtil.pp(expected), + expectedMatcher, + pass; try { - expectedMatcher = new j$.Any(expected); - pass = expectedMatcher.asymmetricMatch(actual); + expectedMatcher = new j$.Any(expected); + pass = expectedMatcher.asymmetricMatch(actual); } catch (error) { - throw new Error(usageError('Expected value is not a constructor function')); + throw new Error(usageError('Expected value is not a constructor function')); } if (pass) { @@ -5805,7 +5806,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { }); var diffs = actual.calls.allArgs().map(function(argsForCall, callIx) { - var diffBuilder = new j$.DiffBuilder(); + var diffBuilder = new j$.DiffBuilder(); matchersUtil.equals(argsForCall, expectedArgs, diffBuilder); return 'Call ' + callIx + ':\n' + diffBuilder.getMessage().replace(/^/mg, ' '); @@ -5862,6 +5863,49 @@ getJasmineRequireObj().toHaveClass = function(j$) { return toHaveClass; }; +getJasmineRequireObj().toHaveSize = function(j$) { + /** + * {@link expect} the actual size to be equal to the expected, using array-like length or object keys size. + * @function + * @name matchers#toHaveSize + * @since 3.5.1 + * @param {Object} expected - Expected size + * @example + * array = [1,2]; + * expect(array).toHaveSize(2); + */ + function toHaveSize() { + return { + compare: function(actual, expected) { + var result = { + pass: false + }; + + if (actual instanceof WeakSet || actual instanceof WeakMap || actual instanceof DataView) { + throw new Error('Cannot get size of ' + actual + '.'); + } + + if (actual instanceof Set || actual instanceof Map) { + result.pass = actual.size === expected; + } else if (isLength(actual.length)) { + result.pass = actual.length === expected; + } else { + result.pass = Object.keys(actual).length === expected; + } + + return result; + } + }; + } + + var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; + function isLength(value) { + return (typeof value == 'number') && value > -1 && value % 1 === 0 && value <= MAX_SAFE_INTEGER; + } + + return toHaveSize; +}; + getJasmineRequireObj().toMatch = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'expect().toMatch( || )'); @@ -6050,7 +6094,7 @@ getJasmineRequireObj().toThrowError = function(j$) { function thrownDescription(thrown) { var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', - thrownMessage = ''; + thrownMessage = ''; if (expected) { thrownMessage = ' with message ' + matchersUtil.pp(thrown.message); @@ -6157,10 +6201,10 @@ getJasmineRequireObj().toThrowMatching = function(j$) { if (predicate(thrown)) { return pass('Expected function not to throw an exception matching a predicate.'); } else { - return fail(function() { - return 'Expected function to throw an exception matching a predicate, ' + - 'but it threw ' + thrownDescription(thrown) + '.'; - }); + return fail(function() { + return 'Expected function to throw an exception matching a predicate, ' + + 'but it threw ' + thrownDescription(thrown) + '.'; + }); } } }; @@ -6378,8 +6422,8 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) { } else if (j$.util.arrayContains(this.seen, value)) { this.emitScalar( '' + (j$.isArray_(value) ? 'Array' : 'Object') + + '>' ); } else if (j$.isArray_(value) || j$.isA_('Object', value)) { this.seen.push(value); @@ -6648,14 +6692,14 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) { var allKeys = Object.keys ? Object.keys(obj) : (function(o) { - var keys = []; - for (var key in o) { - if (j$.util.has(o, key)) { - keys.push(key); + var keys = []; + for (var key in o) { + if (j$.util.has(o, key)) { + keys.push(key); + } } - } - return keys; - })(obj); + return keys; + })(obj); if (!isArray) { return allKeys; @@ -6841,11 +6885,11 @@ getJasmineRequireObj().QueueRunner = function(j$) { timeoutId = self.setTimeout(function() { var error = new Error( 'Timeout - Async function did not complete within ' + - timeoutInterval + - 'ms ' + - (queueableFn.timeout - ? '(custom timeout)' - : '(set by jasmine.DEFAULT_TIMEOUT_INTERVAL)') + timeoutInterval + + 'ms ' + + (queueableFn.timeout + ? '(custom timeout)' + : '(set by jasmine.DEFAULT_TIMEOUT_INTERVAL)') ); onException(error); next(); @@ -7563,10 +7607,10 @@ getJasmineRequireObj().Spy = function(j$) { if (argsStrategies.any() && !baseStrategy.isConfigured()) { throw new Error( "Spy '" + - strategyArgs.name + - "' received a call with arguments " + - j$.pp(Array.prototype.slice.call(args)) + - ' but all configured strategies specify other arguments.' + strategyArgs.name + + "' received a call with arguments " + + j$.pp(Array.prototype.slice.call(args)) + + ' but all configured strategies specify other arguments.' ); } else { strategy = baseStrategy; @@ -7789,8 +7833,8 @@ getJasmineRequireObj().SpyRegistry = function(j$) { throw new Error( getErrorMsg( 'spyOn could not find an object to spy upon for ' + - propertyName + - '' + propertyName + + '' ) ); } @@ -7815,9 +7859,9 @@ getJasmineRequireObj().SpyRegistry = function(j$) { throw new Error( getErrorMsg( 'Property ' + - propertyName + - ' does not have access type ' + - accessType + propertyName + + ' does not have access type ' + + accessType ) ); } @@ -7948,7 +7992,7 @@ getJasmineRequireObj().SpyStrategy = function(j$) { if (!Promise) { throw new Error( name + - ' requires global Promise, or `Promise` configured with `jasmine.getEnv().configure()`' + ' requires global Promise, or `Promise` configured with `jasmine.getEnv().configure()`' ); } @@ -8724,5 +8768,5 @@ getJasmineRequireObj().UserContext = function(j$) { }; getJasmineRequireObj().version = function() { - return '3.5.0'; + return '3.5.1'; }; diff --git a/spec/core/integration/MatchersSpec.js b/spec/core/integration/MatchersSpec.js index ec3404ff..c1d2dd09 100644 --- a/spec/core/integration/MatchersSpec.js +++ b/spec/core/integration/MatchersSpec.js @@ -496,66 +496,10 @@ describe('Matchers (Integration)', function() { verifyPasses(function(env) { env.expect(['a','b']).toHaveSize(2); }); + verifyFails(function(env) { env.expect(['a','b']).toHaveSize(1); }); - - verifyPasses(function(env) { - env.expect({a: 1, b: 2}).toHaveSize(2); - }); - verifyFails(function(env) { - env.expect({a: 1, b: 2}).toHaveSize(1); - }); - - verifyPasses(function(env) { - env.expect({a: 1, b: 2, length: 5}).toHaveSize(5); - }); - verifyFails(function(env) { - env.expect({a: 1, b: 2, length: 5}).toHaveSize(1); - }); - - verifyPasses(function(env) { - env.expect('ab').toHaveSize(2); - }); - verifyFails(function(env) { - env.expect('ab').toHaveSize(1); - }); - - verifyPasses(function(env) { - var map = new Map(); - map.set('a',1); - map.set('b',2); - env.expect(map).toHaveSize(2); - }); - verifyFails(function(env) { - var map = new Map(); - map.set('a',1); - map.set('b',2); - env.expect(map).toHaveSize(1); - }); - - verifyPasses(function(env) { - var set = new Set(); - set.add('a'); - set.add('b'); - env.expect(set).toHaveSize(2); - }); - verifyFails(function(env) { - var set = new Set(); - set.add('a'); - set.add('b'); - env.expect(set).toHaveSize(1); - }); - - verifyFails(function(env) { - env.expect(new WeakSet()).toHaveSize(1); - }); - verifyFails(function(env) { - env.expect(new WeakMap()).toHaveSize(1); - }); - verifyFails(function(env) { - env.expect(new DataView(new ArrayBuffer(128))).toHaveSize(1); - }); }); describe('toHaveBeenCalled', function() { diff --git a/spec/core/matchers/toHaveSizeSpec.js b/spec/core/matchers/toHaveSizeSpec.js index 08045432..a1b4cccb 100644 --- a/spec/core/matchers/toHaveSizeSpec.js +++ b/spec/core/matchers/toHaveSizeSpec.js @@ -1,21 +1,135 @@ describe('toHaveSize', function() { 'use strict'; - it('delegates to equals function', function() { - var matchersUtil = { - equals: jasmine.createSpy('delegated-equals').and.returnValue(true), - buildFailureMessage: function() { - return 'does not matter'; - }, - DiffBuilder: new jasmineUnderTest.DiffBuilder() - }, - matcher = jasmineUnderTest.matchers.toHaveSize(matchersUtil), - result; + it('passes for an array whose length matches', function() { + var matcher = jasmineUnderTest.matchers.toHaveSize(), + result = matcher.compare([1, 2], 2); - result = matcher.compare([1], 1); - - expect(matchersUtil.equals).toHaveBeenCalledWith(1, 1, jasmine.anything(), jasmine.anything()); expect(result.pass).toBe(true); }); + it('fails for an array whose length does not match', function() { + var matcher = jasmineUnderTest.matchers.toHaveSize(), + result = matcher.compare([1, 2, 3], 2); + + expect(result.pass).toBe(false); + }); + + it('passes for an object with the proper number of keys', function() { + var matcher = jasmineUnderTest.matchers.toHaveSize(), + result = matcher.compare({a: 1, b: 2}, 2); + + expect(result.pass).toBe(true); + }); + + it('fails for an object with a different number of keys', function() { + var matcher = jasmineUnderTest.matchers.toHaveSize(), + result = matcher.compare({a: 1, b: 2}, 1); + + expect(result.pass).toBe(false); + }); + + it('passes for an object with an explicit `length` property that matches', function() { + var matcher = jasmineUnderTest.matchers.toHaveSize(), + result = matcher.compare({a: 1, b: 2, length: 5}, 5); + + expect(result.pass).toBe(true); + }); + + it('fails for an object with an explicit `length` property that does not match', function() { + var matcher = jasmineUnderTest.matchers.toHaveSize(), + result = matcher.compare({a: 1, b: 2, length: 5}, 1); + + expect(result.pass).toBe(false); + }); + + it('passes for a string whose length matches', function() { + var matcher = jasmineUnderTest.matchers.toHaveSize(), + result = matcher.compare('ab', 2); + + expect(result.pass).toBe(true); + }); + + it('fails for a string whose length does not match', function() { + var matcher = jasmineUnderTest.matchers.toHaveSize(), + result = matcher.compare('abc', 2); + + expect(result.pass).toBe(false); + }); + + it('passes for a Map whose length matches', function() { + jasmine.getEnv().requireFunctioningMaps(); + + var map = new Map(); + map.set('a',1); + map.set('b',2); + + var matcher = jasmineUnderTest.matchers.toHaveSize(), + result = matcher.compare(map, 2); + + expect(result.pass).toBe(true); + }); + + it('fails for a Map whose length does not match', function() { + jasmine.getEnv().requireFunctioningMaps(); + + var map = new Map(); + map.set('a',1); + map.set('b',2); + + var matcher = jasmineUnderTest.matchers.toHaveSize(), + result = matcher.compare(map, 1); + + expect(result.pass).toBe(false); + }); + + it('passes for a Set whose length matches', function() { + jasmine.getEnv().requireFunctioningSets(); + + var set = new Set(); + set.add('a'); + set.add('b'); + + var matcher = jasmineUnderTest.matchers.toHaveSize(), + result = matcher.compare(set, 2); + + expect(result.pass).toBe(true); + }); + + it('fails for a Set whose length does not match', function() { + jasmine.getEnv().requireFunctioningSets(); + + var set = new Set(); + set.add('a'); + set.add('b'); + + var matcher = jasmineUnderTest.matchers.toHaveSize(), + result = matcher.compare(set, 1); + + expect(result.pass).toBe(false); + }); + + it('throws an error for WeakSet', function() { + var matcher = jasmineUnderTest.matchers.toHaveSize(); + + expect(function() { + matcher.compare(new WeakSet(), 2); + }).toThrowError('Cannot get size of [object WeakSet].'); + }); + + it('throws an error for WeakMap', function() { + var matcher = jasmineUnderTest.matchers.toHaveSize(); + + expect(function() { + matcher.compare(new WeakMap(), 2); + }).toThrowError('Cannot get size of [object WeakMap].'); + }); + + it('throws an error for DataView', function() { + var matcher = jasmineUnderTest.matchers.toHaveSize(); + + expect(function() { + matcher.compare(new DataView(new ArrayBuffer(128)), 2); + }).toThrowError('Cannot get size of [object DataView].'); + }); }); diff --git a/src/core/matchers/toHaveSize.js b/src/core/matchers/toHaveSize.js index b4ea9c55..ae344ca9 100644 --- a/src/core/matchers/toHaveSize.js +++ b/src/core/matchers/toHaveSize.js @@ -9,70 +9,33 @@ getJasmineRequireObj().toHaveSize = function(j$) { * array = [1,2]; * expect(array).toHaveSize(2); */ - function toHaveSize(matchersUtil) { + function toHaveSize() { return { compare: function(actual, expected) { var result = { pass: false - }, - simpleEqualityTesters = [function(a, b) { - return a === b; - }], - diffBuilder = j$.DiffBuilder(); + }; - // Avoid misleading collections size matching - if (actual instanceof WeakSet - || actual instanceof WeakMap - || actual instanceof DataView) { - result.message = 'Cannot get size of ' + actual + '.'; - return result; + if (actual instanceof WeakSet || actual instanceof WeakMap || actual instanceof DataView) { + throw new Error('Cannot get size of ' + actual + '.'); } - // Ref https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects - if (Array.isArray(actual) || isArrayLike(actual)) - result.pass = matchersUtil.equals(actual.length, expected, simpleEqualityTesters, diffBuilder); - else if ( actual instanceof String || typeof actual === 'string') - result.pass = matchersUtil.equals(actual.length, expected, simpleEqualityTesters, diffBuilder); - else if (actual instanceof Set || actual instanceof Map) - result.pass = matchersUtil.equals(actual.size, expected, simpleEqualityTesters, diffBuilder); - // instanceof Object - else - result.pass = matchersUtil.equals(Object.keys(actual).length, expected, simpleEqualityTesters, diffBuilder); + if (actual instanceof Set || actual instanceof Map) { + result.pass = actual.size === expected; + } else if (isLength(actual.length)) { + result.pass = actual.length === expected; + } else { + result.pass = Object.keys(actual).length === expected; + } - if(!result.pass) - result.message = diffBuilder.getMessage() ; return result; } }; } - /** - * Checks if `value` is array-like. A value is considered array-like if it's - * not a function and has a `value.length` that's an integer greater than or - * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. - * From lodash - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is array-like, else `false`. - * @example - * _.isArrayLike([1, 2, 3]); - * // => true - * _.isArrayLike(document.body.children); - * // => true - * _.isArrayLike('abc'); - * // => true - * _.isArrayLike(_.noop); - * // => false - */ - function isArrayLike(value) { - return value != null && isLength(value.length) && !isFunction(value); - } - var MAX_SAFE_INTEGER = 9007199254740991; + var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; function isLength(value) { - return (typeof value == 'number') && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; - } - var functionTags = ['[object Function]','[object GeneratorFunction]','[object AsyncFunction]','[object Proxy]']; - function isFunction(functionToCheck) { - return functionToCheck && functionTags.indexOf( Object.prototype.toString.call(functionToCheck) ) != -1; + return (typeof value == 'number') && value > -1 && value % 1 === 0 && value <= MAX_SAFE_INTEGER; } return toHaveSize;