diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 0e6c91d4..4c17bf3c 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -290,9 +290,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 +763,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 +828,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 +1170,7 @@ getJasmineRequireObj().Env = function(j$) { } runnableResources[ currentRunnable().id - ].defaultStrategyFn = defaultStrategyFn; + ].defaultStrategyFn = defaultStrategyFn; }; this.addSpyStrategy = function(name, fn) { @@ -2565,7 +2565,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 +3373,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 +4108,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 rejected with ' + matchersUtil.pp(actualValue) + '.' + 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 rejected with ' + matchersUtil.pp(actualValue) + '.' + }; + } } - } ); } }; @@ -4494,9 +4494,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 +4731,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 +4800,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 +4849,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 +4863,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) { } if (allKeys.length === 0) { - return allKeys; + return allKeys; } var extraKeys = []; @@ -4882,10 +4882,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 +5302,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 +5805,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, ' '); @@ -6050,7 +6050,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 +6157,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 +6378,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 +6648,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 +6841,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 +7563,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 +7789,8 @@ getJasmineRequireObj().SpyRegistry = function(j$) { throw new Error( getErrorMsg( 'spyOn could not find an object to spy upon for ' + - propertyName + - '' + propertyName + + '' ) ); } @@ -7815,9 +7815,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 +7948,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()`' ); } diff --git a/package.json b/package.json index bc602f6f..8db8eace 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jasmine-core", "license": "MIT", - "version": "3.5.0", + "version": "3.5.1", "repository": { "type": "git", "url": "https://github.com/jasmine/jasmine.git" diff --git a/spec/core/integration/MatchersSpec.js b/spec/core/integration/MatchersSpec.js index 7f19a95c..ec3404ff 100644 --- a/spec/core/integration/MatchersSpec.js +++ b/spec/core/integration/MatchersSpec.js @@ -47,6 +47,9 @@ describe('Matchers (Integration)', function() { expect(result.failedExpectations[0].message) .withContext('Failed with a thrown error rather than a matcher failure') .not.toMatch(/^Error: /); + expect(result.failedExpectations[0].message) + .withContext('Failed with a thrown type error rather than a matcher failure') + .not.toMatch(/^TypeError: /); expect(result.failedExpectations[0].matcherName).withContext('Matcher name') .not.toEqual(''); }; @@ -477,9 +480,9 @@ describe('Matchers (Integration)', function() { verifyFailsWithCustomObjectFormatters({ formatter: function(val) { if (val === 5) { - return "five" + return 'five'; } else if (val === 4) { - return "four" + return 'four'; } }, expectations: function(env) { @@ -489,6 +492,72 @@ describe('Matchers (Integration)', function() { }); }); + describe('toHaveSize', 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() { verifyPasses(function(env) { var spy = env.createSpy('spy'); diff --git a/spec/core/matchers/toHaveSizeSpec.js b/spec/core/matchers/toHaveSizeSpec.js new file mode 100644 index 00000000..08045432 --- /dev/null +++ b/spec/core/matchers/toHaveSizeSpec.js @@ -0,0 +1,21 @@ +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; + + result = matcher.compare([1], 1); + + expect(matchersUtil.equals).toHaveBeenCalledWith(1, 1, jasmine.anything(), jasmine.anything()); + expect(result.pass).toBe(true); + }); + +}); diff --git a/src/core/matchers/requireMatchers.js b/src/core/matchers/requireMatchers.js index e8b1fd58..e0b256e5 100644 --- a/src/core/matchers/requireMatchers.js +++ b/src/core/matchers/requireMatchers.js @@ -20,6 +20,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) { 'toBeUndefined', 'toContain', 'toEqual', + 'toHaveSize', 'toHaveBeenCalled', 'toHaveBeenCalledBefore', 'toHaveBeenCalledTimes', diff --git a/src/core/matchers/toHaveSize.js b/src/core/matchers/toHaveSize.js new file mode 100644 index 00000000..b4ea9c55 --- /dev/null +++ b/src/core/matchers/toHaveSize.js @@ -0,0 +1,79 @@ +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(matchersUtil) { + 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; + } + + // 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(!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; + 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 toHaveSize; +};