diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 0e6c91d4..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', @@ -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( || )'); @@ -8724,5 +8768,5 @@ getJasmineRequireObj().UserContext = function(j$) { }; getJasmineRequireObj().version = function() { - return '3.5.0'; + return '3.5.1'; }; 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..c1d2dd09 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,16 @@ 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); + }); + }); + 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..a1b4cccb --- /dev/null +++ b/spec/core/matchers/toHaveSizeSpec.js @@ -0,0 +1,135 @@ +describe('toHaveSize', function() { + 'use strict'; + + it('passes for an array whose length matches', function() { + var matcher = jasmineUnderTest.matchers.toHaveSize(), + result = matcher.compare([1, 2], 2); + + 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/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..ae344ca9 --- /dev/null +++ b/src/core/matchers/toHaveSize.js @@ -0,0 +1,42 @@ +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; +};