diff --git a/lib/jasmine-core/example/src/Player.js b/lib/jasmine-core/example/src/Player.js index fcce8268..11851966 100644 --- a/lib/jasmine-core/example/src/Player.js +++ b/lib/jasmine-core/example/src/Player.js @@ -19,4 +19,4 @@ Player.prototype.resume = function() { Player.prototype.makeFavorite = function() { this.currentlyPlayingSong.persistFavoriteStatus(true); -}; \ No newline at end of file +}; diff --git a/lib/jasmine-core/example/src/Song.js b/lib/jasmine-core/example/src/Song.js index a8a3f2dd..02527cb1 100644 --- a/lib/jasmine-core/example/src/Song.js +++ b/lib/jasmine-core/example/src/Song.js @@ -4,4 +4,4 @@ function Song() { Song.prototype.persistFavoriteStatus = function(value) { // something complicated throw new Error("not yet implemented"); -}; \ No newline at end of file +}; diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index c53a1e36..1a696303 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -350,9 +350,11 @@ jasmineRequire.HtmlReporter = function(j$) { find('.jasmine-failures-menu').onclick = function() { setMenuModeTo('jasmine-failure-list'); + return false; }; find('.jasmine-spec-list-menu').onclick = function() { setMenuModeTo('jasmine-spec-list'); + return false; }; setMenuModeTo('jasmine-failure-list'); diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 0e6c91d4..211a9da4 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', @@ -314,6 +315,24 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { ); }; + j$.isWeakMap = function(obj) { + return ( + obj !== null && + typeof obj !== 'undefined' && + typeof jasmineGlobal.WeakMap !== 'undefined' && + obj.constructor === jasmineGlobal.WeakMap + ); + }; + + j$.isDataView = function(obj) { + return ( + obj !== null && + typeof obj !== 'undefined' && + typeof jasmineGlobal.DataView !== 'undefined' && + obj.constructor === jasmineGlobal.DataView + ); + }; + j$.isPromise = function(obj) { return ( typeof jasmineGlobal.Promise !== 'undefined' && @@ -697,6 +716,7 @@ getJasmineRequireObj().Spec = function(j$) { * @property {String} pendingReason - If the spec is {@link pending}, this will be the reason. * @property {String} status - Once the spec has completed, this string represents the pass/fail status of this spec. * @property {number} duration - The time in ms used by the spec execution, including any before/afterEach. + * @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSpecProperty} */ this.result = { id: this.id, @@ -706,7 +726,8 @@ getJasmineRequireObj().Spec = function(j$) { passedExpectations: [], deprecationWarnings: [], pendingReason: '', - duration: null + duration: null, + properties: null }; } @@ -723,6 +744,11 @@ getJasmineRequireObj().Spec = function(j$) { } }; + Spec.prototype.setSpecProperty = function(key, value) { + this.result.properties = this.result.properties || {}; + this.result.properties[key] = value; + }; + Spec.prototype.expect = function(actual) { return this.expectationFactory(actual, this); }; @@ -2049,6 +2075,40 @@ getJasmineRequireObj().Env = function(j$) { return spec; }; + /** + * Sets a user-defined property that will be provided to reporters as part of {@link SpecResult#properties} + * @name Env#setSpecProperty + * @since 3.6.0 + * @function + * @param {String} key The name of the property + * @param {*} value The value of the property + */ + this.setSpecProperty = function(key, value) { + if (!currentRunnable() || currentRunnable() == currentSuite()) { + throw new Error( + "'setSpecProperty' was used when there was no current spec" + ); + } + currentRunnable().setSpecProperty(key, value); + }; + + /** + * Sets a user-defined property that will be provided to reporters as part of {@link SuiteResult#properties} + * @name Env#setSuiteProperty + * @since 3.6.0 + * @function + * @param {String} key The name of the property + * @param {*} value The value of the property + */ + this.setSuiteProperty = function(key, value) { + if (!currentSuite()) { + throw new Error( + "'setSuiteProperty' was used when there was no current suite" + ); + } + currentSuite().setSuiteProperty(key, value); + }; + this.expect = function(actual) { if (!currentRunnable()) { throw new Error( @@ -4053,6 +4113,32 @@ getJasmineRequireObj().GlobalErrors = function(j$) { return GlobalErrors; }; +getJasmineRequireObj().toBePending = function(j$) { + /** + * Expect a promise to be pending, ie. the promise is neither resolved nor rejected. + * @function + * @async + * @name async-matchers#toBePending + * @since 3.5.1 (should this be the next version or the version when it was added?) + * @example + * await expectAsync(aPromise).toBePending(); + */ + return function toBePending() { + return { + compare: function(actual) { + if (!j$.isPromiseLike(actual)) { + throw new Error('Expected toBePending to be called on a promise.'); + } + var want = {}; + return Promise.race([actual, Promise.resolve(want)]).then( + function(got) { return {pass: want === got}; }, + function() { return {pass: false}; } + ); + } + }; + }; +}; + getJasmineRequireObj().toBeRejected = function(j$) { /** * Expect a promise to be rejected. @@ -5086,6 +5172,7 @@ getJasmineRequireObj().ObjectPath = function(j$) { getJasmineRequireObj().requireAsyncMatchers = function(jRequire, j$) { var availableMatchers = [ + 'toBePending', 'toBeResolved', 'toBeRejected', 'toBeResolvedTo', @@ -5862,6 +5949,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 (j$.isA_('WeakSet', actual) || j$.isWeakMap(actual) || j$.isDataView(actual)) { + throw new Error('Cannot get size of ' + actual + '.'); + } + + if (j$.isSet(actual) || j$.isMap(actual)) { + 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( || )'); @@ -7163,6 +7293,30 @@ getJasmineRequireObj().interface = function(jasmine, env) { return env.afterAll.apply(env, arguments); }, + /** + * Sets a user-defined property that will be provided to reporters as part of {@link SpecResult#properties} + * @name Env#setSpecProperty + * @since 3.6.0 + * @function + * @param {String} key The name of the property + * @param {*} value The value of the property + */ + setSpecProperty: function(key, value) { + return env.setSpecProperty(key, value); + }, + + /** + * Sets a user-defined property that will be provided to reporters as part of {@link SuiteResult#properties} + * @name Env#setSuiteProperty + * @since 3.6.0 + * @function + * @param {String} key The name of the property + * @param {*} value The value of the property + */ + setSuiteProperty: function(key, value) { + return env.setSuiteProperty(key, value); + }, + /** * Create an expectation for a spec. * @name expect @@ -8259,6 +8413,7 @@ getJasmineRequireObj().Suite = function(j$) { * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite. * @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite. * @property {number} duration - The time in ms for Suite execution, including any before/afterAll, before/afterEach. + * @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSuiteProperty} */ this.result = { id: this.id, @@ -8266,10 +8421,16 @@ getJasmineRequireObj().Suite = function(j$) { fullName: this.getFullName(), failedExpectations: [], deprecationWarnings: [], - duration: null + duration: null, + properties: null }; } + Suite.prototype.setSuiteProperty = function(key, value) { + this.result.properties = this.result.properties || {}; + this.result.properties[key] = value; + }; + Suite.prototype.expect = function(actual) { return this.expectationFactory(actual, this); }; @@ -8724,5 +8885,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/SpecSpec.js b/spec/core/SpecSpec.js index 38a8e780..fa530049 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -227,7 +227,8 @@ describe('Spec', function() { passedExpectations: [], deprecationWarnings: [], pendingReason: '', - duration: jasmine.any(Number) + duration: jasmine.any(Number), + properties: null }, 'things' ); @@ -299,6 +300,23 @@ describe('Spec', function() { expect(duration).toBe(77000); }); + it('should report properties set during the test', function() { + var done = jasmine.createSpy('done callback'), + spec = new jasmineUnderTest.Spec({ + queueableFn: { fn: jasmine.createSpy('spec body') }, + catchExceptions: function() { + return false; + }, + resultCallback: function() {}, + queueRunnerFactory: function(attrs) { + attrs.onComplete(); + } + }); + spec.setSpecProperty('a', 4); + spec.execute(done); + expect(spec.result.properties).toEqual({ a: 4 }); + }); + it('#status returns passing by default', function() { var spec = new jasmineUnderTest.Spec({ queueableFn: { fn: jasmine.createSpy('spec body') } diff --git a/spec/core/SpyStrategySpec.js b/spec/core/SpyStrategySpec.js index e861dc8b..5e454b64 100644 --- a/spec/core/SpyStrategySpec.js +++ b/spec/core/SpyStrategySpec.js @@ -140,6 +140,28 @@ describe('SpyStrategy', function() { .catch(done.fail); }); + it('allows an empty resolved promise to be returned', function(done) { + jasmine.getEnv().requirePromises(); + + var originalFn = jasmine.createSpy('original'), + getPromise = function() { + return Promise; + }, + spyStrategy = new jasmineUnderTest.SpyStrategy({ + fn: originalFn, + getPromise: getPromise + }); + + spyStrategy.resolveTo(); + spyStrategy + .exec() + .then(function(returnValue) { + expect(returnValue).toBe(); + done(); + }) + .catch(done.fail); + }); + it('fails if promises are not available', function() { var originalFn = jasmine.createSpy('original'), spyStrategy = new jasmineUnderTest.SpyStrategy({ fn: originalFn }); @@ -176,6 +198,29 @@ describe('SpyStrategy', function() { .catch(done.fail); }); + it('allows an empty rejected promise to be returned', function(done) { + jasmine.getEnv().requirePromises(); + + var originalFn = jasmine.createSpy('original'), + getPromise = function() { + return Promise; + }, + spyStrategy = new jasmineUnderTest.SpyStrategy({ + fn: originalFn, + getPromise: getPromise + }); + + spyStrategy.rejectWith(); + spyStrategy + .exec() + .then(done.fail) + .catch(function(error) { + expect(error).toBe(); + done(); + }) + .catch(done.fail); + }); + it('allows a non-Error to be rejected', function(done) { jasmine.getEnv().requirePromises(); diff --git a/spec/core/baseSpec.js b/spec/core/baseSpec.js index 4fce2254..b85721b9 100644 --- a/spec/core/baseSpec.js +++ b/spec/core/baseSpec.js @@ -51,4 +51,15 @@ describe('base helpers', function() { expect(jasmineUnderTest.isAsymmetricEqualityTester_(obj)).toBe(true); }); }); + + describe('isSet', function() { + it('returns true when the object is a Set', function() { + jasmine.getEnv().requireFunctioningSets(); + expect(jasmineUnderTest.isSet(new Set())).toBe(true); + }); + + it('returns false when the object is not a Set', function() { + expect(jasmineUnderTest.isSet({})).toBe(false); + }); + }); }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 8c5c1941..247744d3 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -1971,6 +1971,77 @@ describe("Env integration", function() { env.execute(); }); + it('reports test properties on specs', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.specDone.and.callFake(function(e) { + expect(e.properties).toEqual({a: 'Bee'}); + done(); + }); + + env.addReporter(reporter); + env.it('calls setSpecProperty', function() { + env.setSpecProperty('a', 'Bee') + }); + env.execute(); + }); + + it('throws an exception if you try to setSpecProperty outside of a spec', function (done) { + var env = new jasmineUnderTest.Env(), + exception; + + env.describe("a suite", function () { + try { + env.setSpecProperty('a prop', 'val'); + } catch(e) { + exception = e; + } + }); + + var assertions = function() { + expect(exception.message).toBe(`'setSpecProperty' was used when there was no current spec`); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.execute(); + }); + + it('reports test properties on suites', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.suiteDone.and.callFake(function(e) { + expect(e.properties).toEqual({b: 'Sweet'}); + done(); + }); + + env.addReporter(reporter); + env.describe('calls setSuiteProperty', function() { + env.beforeEach(() => { + env.setSuiteProperty('b', 'Sweet'); + }); + env.it('a passing spec', () => { + expect.nothing(); + }); + }); + + env.execute(); + }); + + it('throws an exception if you try to setSuiteProperty outside of a suite', function (done) { + var env = new jasmineUnderTest.Env(); + + try { + env.setSuiteProperty('a', 'Bee'); + } catch(e) { + expect(e.message).toBe(`'setSuiteProperty' was used when there was no current suite`); + done(); + } + }); + it("should associate errors thrown from async code with the correct runnable", function(done) { var reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone','specDone']); 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/async/toBePendingSpec.js b/spec/core/matchers/async/toBePendingSpec.js new file mode 100644 index 00000000..318e22b7 --- /dev/null +++ b/spec/core/matchers/async/toBePendingSpec.js @@ -0,0 +1,51 @@ +describe('toBePending', function() { + it('passes if the actual promise is pending', function() { + jasmine.getEnv().requirePromises(); + + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBePending(matchersUtil), + actual = new Promise(function() {}); + + return matcher.compare(actual).then(function(result) { + expect(result).toEqual(jasmine.objectContaining({pass: true})); + }); + }); + + it('fails if the actual promise is resolved', function() { + jasmine.getEnv().requirePromises(); + + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBePending(matchersUtil), + actual = Promise.resolve(); + + return matcher.compare(actual).then(function(result) { + expect(result).toEqual(jasmine.objectContaining({pass: false})); + }); + }); + + it('fails if the actual promise is rejected', function() { + jasmine.getEnv().requirePromises(); + + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBePending(matchersUtil), + actual = Promise.reject(new Error('promise was rejected')); + + return matcher.compare(actual).then(function(result) { + expect(result).toEqual(jasmine.objectContaining({pass: false})); + }); + }); + + it('fails if actual is not a promise', function() { + var matchersUtil = new jasmineUnderTest.MatchersUtil(), + matcher = jasmineUnderTest.asyncMatchers.toBePending(matchersUtil), + actual = 'not a promise'; + + function f() { + return matcher.compare(actual); + } + + expect(f).toThrowError( + 'Expected toBePending to be called on a promise.' + ); + }); +}); diff --git a/spec/core/matchers/toHaveSizeSpec.js b/spec/core/matchers/toHaveSizeSpec.js new file mode 100644 index 00000000..a14a24e5 --- /dev/null +++ b/spec/core/matchers/toHaveSizeSpec.js @@ -0,0 +1,137 @@ +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() { + jasmine.getEnv().requireWeakSets(); + 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() { + jasmine.getEnv().requireWeakMaps(); + var matcher = jasmineUnderTest.matchers.toHaveSize(); + + expect(function() { + matcher.compare(new WeakMap(), 2); + }).toThrowError(/Cannot get size of \[object (WeakMap|Object)\]\./); + }); + + 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|Object)\]\./); + }); +}); diff --git a/spec/helpers/checkForMap.js b/spec/helpers/checkForMap.js index 46e346a8..77fa1233 100644 --- a/spec/helpers/checkForMap.js +++ b/spec/helpers/checkForMap.js @@ -43,4 +43,10 @@ env.pending('Browser has incomplete or missing support for Maps'); } }; + + env.requireWeakMaps = function() { + if (typeof WeakMap === 'undefined') { + env.pending('Browser does not have support for WeakMap'); + } + }; })(jasmine.getEnv()); diff --git a/spec/helpers/checkForSet.js b/spec/helpers/checkForSet.js index dacb06aa..8656342d 100644 --- a/spec/helpers/checkForSet.js +++ b/spec/helpers/checkForSet.js @@ -47,4 +47,10 @@ env.pending('Browser has incomplete or missing support for Sets'); } }; + + env.requireWeakSets = function() { + if (typeof WeakSet === 'undefined') { + env.pending('Browser does not have support for WeakSet'); + } + }; })(jasmine.getEnv()); diff --git a/spec/html/PrettyPrintHtmlSpec.js b/spec/html/PrettyPrintHtmlSpec.js index fd364d9e..738753ba 100644 --- a/spec/html/PrettyPrintHtmlSpec.js +++ b/spec/html/PrettyPrintHtmlSpec.js @@ -31,7 +31,7 @@ describe('PrettyPrinter (HTML Dependent)', function() { } // Different versions of FF produce different error messages. expect(pp(err)).toMatch( - /Not enough arguments|CustomEvent requires at least 1 argument, but only 0 were passed/ + /Not enough arguments|CustomEvent.*only 0.*passed/ ); } }); diff --git a/src/core/Env.js b/src/core/Env.js index 47915c0a..60025850 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -1124,6 +1124,40 @@ getJasmineRequireObj().Env = function(j$) { return spec; }; + /** + * Sets a user-defined property that will be provided to reporters as part of {@link SpecResult#properties} + * @name Env#setSpecProperty + * @since 3.6.0 + * @function + * @param {String} key The name of the property + * @param {*} value The value of the property + */ + this.setSpecProperty = function(key, value) { + if (!currentRunnable() || currentRunnable() == currentSuite()) { + throw new Error( + "'setSpecProperty' was used when there was no current spec" + ); + } + currentRunnable().setSpecProperty(key, value); + }; + + /** + * Sets a user-defined property that will be provided to reporters as part of {@link SuiteResult#properties} + * @name Env#setSuiteProperty + * @since 3.6.0 + * @function + * @param {String} key The name of the property + * @param {*} value The value of the property + */ + this.setSuiteProperty = function(key, value) { + if (!currentSuite()) { + throw new Error( + "'setSuiteProperty' was used when there was no current suite" + ); + } + currentSuite().setSuiteProperty(key, value); + }; + this.expect = function(actual) { if (!currentRunnable()) { throw new Error( diff --git a/src/core/Spec.js b/src/core/Spec.js index db35de64..83d389d4 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -48,6 +48,7 @@ getJasmineRequireObj().Spec = function(j$) { * @property {String} pendingReason - If the spec is {@link pending}, this will be the reason. * @property {String} status - Once the spec has completed, this string represents the pass/fail status of this spec. * @property {number} duration - The time in ms used by the spec execution, including any before/afterEach. + * @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSpecProperty} */ this.result = { id: this.id, @@ -57,7 +58,8 @@ getJasmineRequireObj().Spec = function(j$) { passedExpectations: [], deprecationWarnings: [], pendingReason: '', - duration: null + duration: null, + properties: null }; } @@ -74,6 +76,11 @@ getJasmineRequireObj().Spec = function(j$) { } }; + Spec.prototype.setSpecProperty = function(key, value) { + this.result.properties = this.result.properties || {}; + this.result.properties[key] = value; + }; + Spec.prototype.expect = function(actual) { return this.expectationFactory(actual, this); }; diff --git a/src/core/Suite.js b/src/core/Suite.js index 4d33ceaa..e660d1f1 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -27,6 +27,7 @@ getJasmineRequireObj().Suite = function(j$) { * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite. * @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite. * @property {number} duration - The time in ms for Suite execution, including any before/afterAll, before/afterEach. + * @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSuiteProperty} */ this.result = { id: this.id, @@ -34,10 +35,16 @@ getJasmineRequireObj().Suite = function(j$) { fullName: this.getFullName(), failedExpectations: [], deprecationWarnings: [], - duration: null + duration: null, + properties: null }; } + Suite.prototype.setSuiteProperty = function(key, value) { + this.result.properties = this.result.properties || {}; + this.result.properties[key] = value; + }; + Suite.prototype.expect = function(actual) { return this.expectationFactory(actual, this); }; diff --git a/src/core/base.js b/src/core/base.js index e86a12c8..121ff614 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -148,6 +148,24 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { ); }; + j$.isWeakMap = function(obj) { + return ( + obj !== null && + typeof obj !== 'undefined' && + typeof jasmineGlobal.WeakMap !== 'undefined' && + obj.constructor === jasmineGlobal.WeakMap + ); + }; + + j$.isDataView = function(obj) { + return ( + obj !== null && + typeof obj !== 'undefined' && + typeof jasmineGlobal.DataView !== 'undefined' && + obj.constructor === jasmineGlobal.DataView + ); + }; + j$.isPromise = function(obj) { return ( typeof jasmineGlobal.Promise !== 'undefined' && diff --git a/src/core/matchers/async/toBePending.js b/src/core/matchers/async/toBePending.js new file mode 100644 index 00000000..7ea12df9 --- /dev/null +++ b/src/core/matchers/async/toBePending.js @@ -0,0 +1,25 @@ +getJasmineRequireObj().toBePending = function(j$) { + /** + * Expect a promise to be pending, ie. the promise is neither resolved nor rejected. + * @function + * @async + * @name async-matchers#toBePending + * @since 3.5.1 (should this be the next version or the version when it was added?) + * @example + * await expectAsync(aPromise).toBePending(); + */ + return function toBePending() { + return { + compare: function(actual) { + if (!j$.isPromiseLike(actual)) { + throw new Error('Expected toBePending to be called on a promise.'); + } + var want = {}; + return Promise.race([actual, Promise.resolve(want)]).then( + function(got) { return {pass: want === got}; }, + function() { return {pass: false}; } + ); + } + }; + }; +}; diff --git a/src/core/matchers/requireAsyncMatchers.js b/src/core/matchers/requireAsyncMatchers.js index faa91c02..abe9b8ef 100644 --- a/src/core/matchers/requireAsyncMatchers.js +++ b/src/core/matchers/requireAsyncMatchers.js @@ -1,5 +1,6 @@ getJasmineRequireObj().requireAsyncMatchers = function(jRequire, j$) { var availableMatchers = [ + 'toBePending', 'toBeResolved', 'toBeRejected', 'toBeResolvedTo', 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..4f6ace0c --- /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 (j$.isA_('WeakSet', actual) || j$.isWeakMap(actual) || j$.isDataView(actual)) { + throw new Error('Cannot get size of ' + actual + '.'); + } + + if (j$.isSet(actual) || j$.isMap(actual)) { + 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; +}; diff --git a/src/core/requireInterface.js b/src/core/requireInterface.js index 15a13cd1..adc503f8 100644 --- a/src/core/requireInterface.js +++ b/src/core/requireInterface.js @@ -168,6 +168,30 @@ getJasmineRequireObj().interface = function(jasmine, env) { return env.afterAll.apply(env, arguments); }, + /** + * Sets a user-defined property that will be provided to reporters as part of {@link SpecResult#properties} + * @name Env#setSpecProperty + * @since 3.6.0 + * @function + * @param {String} key The name of the property + * @param {*} value The value of the property + */ + setSpecProperty: function(key, value) { + return env.setSpecProperty(key, value); + }, + + /** + * Sets a user-defined property that will be provided to reporters as part of {@link SuiteResult#properties} + * @name Env#setSuiteProperty + * @since 3.6.0 + * @function + * @param {String} key The name of the property + * @param {*} value The value of the property + */ + setSuiteProperty: function(key, value) { + return env.setSuiteProperty(key, value); + }, + /** * Create an expectation for a spec. * @name expect diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index 1c6478cf..24d3a404 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -321,9 +321,11 @@ jasmineRequire.HtmlReporter = function(j$) { find('.jasmine-failures-menu').onclick = function() { setMenuModeTo('jasmine-failure-list'); + return false; }; find('.jasmine-spec-list-menu').onclick = function() { setMenuModeTo('jasmine-spec-list'); + return false; }; setMenuModeTo('jasmine-failure-list');