diff --git a/jasmine-core.gemspec b/jasmine-core.gemspec index 15999b31..fece3c1c 100644 --- a/jasmine-core.gemspec +++ b/jasmine-core.gemspec @@ -8,7 +8,18 @@ Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.authors = ["Gregg Van Hove"] s.summary = %q{JavaScript BDD framework} - s.description = %q{Test your JavaScript without any framework dependencies, in any environment, and with a nice descriptive syntax.} + s.description = <<~DESC + Test your JavaScript without any framework dependencies, in any environment, + and with a nice descriptive syntax. + + Jasmine for Ruby is deprecated. The direct replacment for the jasmine-core + gem is the jasmine-core NPM package. If you are also using the jasmine gem, + we recommend using the jasmine-browser-runner NPM package instead. It + supports all the same scenarios as the jasmine gem gem plus Webpacker. See + https://jasmine.github.io/setup/browser.html for setup instructions, and + https://github.com/jasmine/jasmine-gem/blob/main/release_notes/3.9.0.md + for other options. + DESC s.email = %q{jasmine-js@googlegroups.com} s.homepage = "http://jasmine.github.io" s.license = "MIT" diff --git a/lib/jasmine-core.rb b/lib/jasmine-core.rb index 947f5ba2..55a03fba 100644 --- a/lib/jasmine-core.rb +++ b/lib/jasmine-core.rb @@ -1,3 +1,28 @@ +if ENV["SUPPRESS_JASMINE_DEPRECATION"].nil? + puts <<~END_DEPRECATION_MSG + The Jasmine Ruby gems are deprecated. There will be no further releases after + the end of the Jasmine 3.x series. We recommend that most users migrate to the + jasmine-browser-runner npm package, which is the direct replacement for the + jasmine gem. See for setup + instructions, including for Rails applications that use either Sprockets or + Webpacker. + + If jasmine-browser-runner doesn't meet your needs, one of these might: + + * The jasmine npm package to run specs in Node.js: + + * The standalone distribution to run specs in browsers with no additional + tools: + * The jasmine-core npm package if all you need is the Jasmine assets: + . This is the direct equivalent of the + jasmine-core Ruby gem. + + To prevent this message from appearing, set the SUPPRESS_JASMINE_DEPRECATION + environment variable. + + END_DEPRECATION_MSG +end + module Jasmine module Core class << self diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index ee4a6534..406ce514 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -1096,6 +1096,8 @@ getJasmineRequireObj().Env = function(j$) { * @since 3.5.0 * @type function * @default undefined + * @deprecated In a future version, Jasmine will ignore the Promise config + * property and always create native promises instead. */ Promise: undefined, /** @@ -1190,6 +1192,11 @@ getJasmineRequireObj().Env = function(j$) { typeof configuration.Promise.reject === 'function' ) { customPromise = configuration.Promise; + self.deprecated( + 'The `Promise` config property is deprecated. Future versions ' + + 'of Jasmine will create native promises even if the `Promise` ' + + 'config property is set. Please remove it.' + ); } else { throw new Error( 'Custom promise library missing `resolve`/`reject` functions' @@ -1619,11 +1626,17 @@ getJasmineRequireObj().Env = function(j$) { * * execute should not be called more than once. * + * If the environment supports promises, execute will return a promise that + * is resolved after the suite finishes executing. The promise will be + * resolved (not rejected) as long as the suite runs to completion. Use a + * {@link Reporter} to determine whether or not the suite passed. + * * @name Env#execute * @since 2.0.0 * @function * @param {(string[])=} runnablesToRun IDs of suites and/or specs to run * @param {Function=} onComplete Function that will be called after all specs have run + * @return {Promise} */ this.execute = function(runnablesToRun, onComplete) { installGlobalErrors(); @@ -1683,65 +1696,86 @@ getJasmineRequireObj().Env = function(j$) { var jasmineTimer = new j$.Timer(); jasmineTimer.start(); - /** - * Information passed to the {@link Reporter#jasmineStarted} event. - * @typedef JasmineStartedInfo - * @property {Int} totalSpecsDefined - The total number of specs defined in this suite. - * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. - */ - reporter.jasmineStarted( - { - totalSpecsDefined: totalSpecsDefined, - order: order - }, - function() { - currentlyExecutingSuites.push(topSuite); + var Promise = customPromise || global.Promise; - processor.execute(function() { - clearResourcesForRunnable(topSuite.id); - currentlyExecutingSuites.pop(); - var overallStatus, incompleteReason; - - if (hasFailures || topSuite.result.failedExpectations.length > 0) { - overallStatus = 'failed'; - } else if (focusedRunnables.length > 0) { - overallStatus = 'incomplete'; - incompleteReason = 'fit() or fdescribe() was found'; - } else if (totalSpecsDefined === 0) { - overallStatus = 'incomplete'; - incompleteReason = 'No specs found'; - } else { - overallStatus = 'passed'; + if (Promise) { + return new Promise(function(resolve) { + runAll(function() { + if (onComplete) { + onComplete(); } - /** - * Information passed to the {@link Reporter#jasmineDone} event. - * @typedef JasmineDoneInfo - * @property {OverallStatus} overallStatus - The overall result of the suite: 'passed', 'failed', or 'incomplete'. - * @property {Int} totalTime - The total time (in ms) that it took to execute the suite - * @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete. - * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. - * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level. - * @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level. - */ - reporter.jasmineDone( - { - overallStatus: overallStatus, - totalTime: jasmineTimer.elapsed(), - incompleteReason: incompleteReason, - order: order, - failedExpectations: topSuite.result.failedExpectations, - deprecationWarnings: topSuite.result.deprecationWarnings - }, - function() { - if (onComplete) { - onComplete(); - } - } - ); + resolve(); }); - } - ); + }); + } else { + runAll(function() { + if (onComplete) { + onComplete(); + } + }); + } + + function runAll(done) { + /** + * Information passed to the {@link Reporter#jasmineStarted} event. + * @typedef JasmineStartedInfo + * @property {Int} totalSpecsDefined - The total number of specs defined in this suite. + * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. + */ + reporter.jasmineStarted( + { + totalSpecsDefined: totalSpecsDefined, + order: order + }, + function() { + currentlyExecutingSuites.push(topSuite); + + processor.execute(function() { + clearResourcesForRunnable(topSuite.id); + currentlyExecutingSuites.pop(); + var overallStatus, incompleteReason; + + if ( + hasFailures || + topSuite.result.failedExpectations.length > 0 + ) { + overallStatus = 'failed'; + } else if (focusedRunnables.length > 0) { + overallStatus = 'incomplete'; + incompleteReason = 'fit() or fdescribe() was found'; + } else if (totalSpecsDefined === 0) { + overallStatus = 'incomplete'; + incompleteReason = 'No specs found'; + } else { + overallStatus = 'passed'; + } + + /** + * Information passed to the {@link Reporter#jasmineDone} event. + * @typedef JasmineDoneInfo + * @property {OverallStatus} overallStatus - The overall result of the suite: 'passed', 'failed', or 'incomplete'. + * @property {Int} totalTime - The total time (in ms) that it took to execute the suite + * @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete. + * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. + * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level. + * @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level. + */ + reporter.jasmineDone( + { + overallStatus: overallStatus, + totalTime: jasmineTimer.elapsed(), + incompleteReason: incompleteReason, + order: order, + failedExpectations: topSuite.result.failedExpectations, + deprecationWarnings: topSuite.result.deprecationWarnings + }, + done + ); + }); + } + ); + } }; /** @@ -4081,16 +4115,22 @@ getJasmineRequireObj().GlobalErrors = function(j$) { function taggedOnError(error) { var substituteMsg; - if (error) { + if (j$.isError_(error)) { error.jasmineMessage = jasmineMessage + ': ' + error; } else { - substituteMsg = jasmineMessage + ' with no error or message'; + if (error) { + substituteMsg = jasmineMessage + ': ' + error; + } else { + substituteMsg = jasmineMessage + ' with no error or message'; + } if (errorType === 'unhandledRejection') { substituteMsg += '\n' + '(Tip: to get a useful stack trace, use ' + - 'Promise.reject(new Error(...)) instead of Promise.reject().)'; + 'Promise.reject(new Error(...)) instead of Promise.reject(' + + (error ? '...' : '') + + ').)'; } error = new Error(substituteMsg); @@ -9606,5 +9646,5 @@ getJasmineRequireObj().UserContext = function(j$) { }; getJasmineRequireObj().version = function() { - return '3.8.0'; + return '3.9.0'; }; diff --git a/lib/jasmine-core/version.rb b/lib/jasmine-core/version.rb index f879e1d5..ccee40c5 100644 --- a/lib/jasmine-core/version.rb +++ b/lib/jasmine-core/version.rb @@ -4,6 +4,6 @@ # module Jasmine module Core - VERSION = "3.8.0" + VERSION = "3.9.0" end end diff --git a/package.json b/package.json index 436593d2..9edf722e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jasmine-core", "license": "MIT", - "version": "3.8.0", + "version": "3.9.0", "repository": { "type": "git", "url": "https://github.com/jasmine/jasmine.git" diff --git a/release_notes/3.9.0.md b/release_notes/3.9.0.md new file mode 100644 index 00000000..ca12f32b --- /dev/null +++ b/release_notes/3.9.0.md @@ -0,0 +1,65 @@ +# Jasmine Core 3.9 Release Notes + +## New features and bug fixes + +* Fixed Trusted Types error in `j$.isError_` in Chromium-based browsers + * Merges [#1921](https://github.com/jasmine/jasmine/pull/1921) from @bjarkler + * Fixes [#1910](https://github.com/jasmine/jasmine/issues/1910) + * Fixes [#1653](https://github.com/jasmine/jasmine/issues/1653) + +* Better reporting of unhandled promise rejections with truthy but non-`Error` + reasons on Node +* `Env#execute` returns a promise in environments that support promises +* Renamed `failFast` and `oneFailurePerSpec` config props to `stopOnSpecFailure` + and `stopSpecOnExpectationFailure` + + The new names are more self-explanatory and consistent with jasmine-npm. The + old names are deprecated but will still work until the next major release. + +* Split `boot.js` into two files to allow the env to be configured in between + + This is mainly intended to support jasmine-browser-runner, which will load + a script that configures the env in between the two boot files (`boot0.js` and + `boot1.js`). The single-file `boot.js` will still be included until the next + major release. + +## Ruby deprecation + +The Jasmine Ruby gems are deprecated. There will be no further releases after +the end of the Jasmine 3.x series. We recommend that most users migrate to the +[jasmine-browser-runner](https://github.com/jasmine/jasmine-browser) +npm package, which is the direct replacement for the `jasmine` gem. + +If `jasmine-browser-runner` doesn't meet your needs, one of these might: + +* The [jasmine](https://github.com/jasmine/jasmine-npm) npm package to run + specs in Node.js. +* The [standalone distribution](https://github.com/jasmine/jasmine#installation) + to run specs in browsers with no additional tools. +* The [jasmine-core](https://github.com/jasmine/jasmine) npm package if all + you need is the Jasmine assets. This is the direct equivalent of the + `jasmine-core` Ruby gem. + +## Documentation updates + +* Added API docs for `Suite#id` and `Spec#id` +* Marked `Env#hideDisabled` deprecated in jsdocs + + +------ + +## Supported environments + +jasmine-core 3.9.0 has been tested in the following environments. + +| Environment | Supported versions | +|-------------------|--------------------| +| Node | 10, 12, 14, 16 | +| Safari | 8-14 | +| Chrome | 92 | +| Firefox | 91, 78, 68 | +| Edge | 92 | +| Internet Explorer | 10, 11 | + + +_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_ diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index d6e5fb32..34e8bb49 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -144,11 +144,17 @@ describe('Env', function() { }); it('can be configured with a custom library', function() { + spyOn(env, 'deprecated'); var myLibrary = { resolve: jasmine.createSpy(), reject: jasmine.createSpy() }; env.configure({ Promise: myLibrary }); + expect(env.deprecated).toHaveBeenCalledWith( + 'The `Promise` config property is deprecated. Future versions of ' + + 'Jasmine will create native promises even if the `Promise` config ' + + 'property is set. Please remove it.' + ); }); it('cannot be configured with an invalid promise library', function() { @@ -540,4 +546,10 @@ describe('Env', function() { expect(suiteThis).not.toBeInstanceOf(jasmineUnderTest.Suite); }); + + describe('#execute', function() { + it('returns a promise', function() { + expect(env.execute()).toBeInstanceOf(Promise); + }); + }); }); diff --git a/spec/core/GlobalErrorsSpec.js b/spec/core/GlobalErrorsSpec.js index 8e3ffbc5..07e2f48c 100644 --- a/spec/core/GlobalErrorsSpec.js +++ b/spec/core/GlobalErrorsSpec.js @@ -170,84 +170,118 @@ describe('GlobalErrors', function() { ); }); - it('reports unhandled promise rejections in node.js', function() { - var fakeGlobal = { - process: { - on: jasmine.createSpy('process.on'), - removeListener: jasmine.createSpy('process.removeListener'), - listeners: jasmine - .createSpy('process.listeners') - .and.returnValue(['foo']), - removeAllListeners: jasmine.createSpy('process.removeAllListeners') - } - }, - handler = jasmine.createSpy('errorHandler'), - errors = new jasmineUnderTest.GlobalErrors(fakeGlobal); + describe('Reporting unhandled promise rejections in node.js', function() { + it('reports rejections with `Error` reasons', function() { + var fakeGlobal = { + process: { + on: jasmine.createSpy('process.on'), + removeListener: jasmine.createSpy('process.removeListener'), + listeners: jasmine + .createSpy('process.listeners') + .and.returnValue(['foo']), + removeAllListeners: jasmine.createSpy('process.removeAllListeners') + } + }, + handler = jasmine.createSpy('errorHandler'), + errors = new jasmineUnderTest.GlobalErrors(fakeGlobal); - errors.install(); - expect(fakeGlobal.process.on).toHaveBeenCalledWith( - 'unhandledRejection', - jasmine.any(Function) - ); - expect(fakeGlobal.process.listeners).toHaveBeenCalledWith( - 'unhandledRejection' - ); - expect(fakeGlobal.process.removeAllListeners).toHaveBeenCalledWith( - 'unhandledRejection' - ); + errors.install(); + expect(fakeGlobal.process.on).toHaveBeenCalledWith( + 'unhandledRejection', + jasmine.any(Function) + ); + expect(fakeGlobal.process.listeners).toHaveBeenCalledWith( + 'unhandledRejection' + ); + expect(fakeGlobal.process.removeAllListeners).toHaveBeenCalledWith( + 'unhandledRejection' + ); - errors.pushListener(handler); + errors.pushListener(handler); - var addedListener = fakeGlobal.process.on.calls.argsFor(1)[1]; - addedListener(new Error('bar')); + var addedListener = fakeGlobal.process.on.calls.argsFor(1)[1]; + addedListener(new Error('bar')); - expect(handler).toHaveBeenCalledWith(new Error('bar')); - expect(handler.calls.argsFor(0)[0].jasmineMessage).toBe( - 'Unhandled promise rejection: Error: bar' - ); + expect(handler).toHaveBeenCalledWith(new Error('bar')); + expect(handler.calls.argsFor(0)[0].jasmineMessage).toBe( + 'Unhandled promise rejection: Error: bar' + ); - errors.uninstall(); + errors.uninstall(); - expect(fakeGlobal.process.removeListener).toHaveBeenCalledWith( - 'unhandledRejection', - addedListener - ); - expect(fakeGlobal.process.on).toHaveBeenCalledWith( - 'unhandledRejection', - 'foo' - ); - }); + expect(fakeGlobal.process.removeListener).toHaveBeenCalledWith( + 'unhandledRejection', + addedListener + ); + expect(fakeGlobal.process.on).toHaveBeenCalledWith( + 'unhandledRejection', + 'foo' + ); + }); - it('reports unhandled promise rejections in node.js when no error is provided', function() { - var fakeGlobal = { - process: { - on: jasmine.createSpy('process.on'), - removeListener: function() {}, - listeners: function() { - return []; - }, - removeAllListeners: function() {} - } - }, - handler = jasmine.createSpy('errorHandler'), - errors = new jasmineUnderTest.GlobalErrors(fakeGlobal); + it('reports rejections with non-`Error` reasons', function() { + var fakeGlobal = { + process: { + on: jasmine.createSpy('process.on'), + removeListener: function() {}, + listeners: function() { + return []; + }, + removeAllListeners: function() {} + } + }, + handler = jasmine.createSpy('errorHandler'), + errors = new jasmineUnderTest.GlobalErrors(fakeGlobal); - errors.install(); - errors.pushListener(handler); + errors.install(); + errors.pushListener(handler); - expect(fakeGlobal.process.on.calls.argsFor(1)[0]).toEqual( - 'unhandledRejection' - ); - var addedListener = fakeGlobal.process.on.calls.argsFor(1)[1]; - addedListener(undefined); + expect(fakeGlobal.process.on.calls.argsFor(1)[0]).toEqual( + 'unhandledRejection' + ); + var addedListener = fakeGlobal.process.on.calls.argsFor(1)[1]; + addedListener(17); - expect(handler).toHaveBeenCalledWith( - new Error( - 'Unhandled promise rejection with no error or message\n' + - '(Tip: to get a useful stack trace, use ' + - 'Promise.reject(new Error(...)) instead of Promise.reject().)' - ) - ); + expect(handler).toHaveBeenCalledWith( + new Error( + 'Unhandled promise rejection: 17\n' + + '(Tip: to get a useful stack trace, use ' + + 'Promise.reject(new Error(...)) instead of Promise.reject(...).)' + ) + ); + }); + + it('reports rejections with no reason provided', function() { + var fakeGlobal = { + process: { + on: jasmine.createSpy('process.on'), + removeListener: function() {}, + listeners: function() { + return []; + }, + removeAllListeners: function() {} + } + }, + handler = jasmine.createSpy('errorHandler'), + errors = new jasmineUnderTest.GlobalErrors(fakeGlobal); + + errors.install(); + errors.pushListener(handler); + + expect(fakeGlobal.process.on.calls.argsFor(1)[0]).toEqual( + 'unhandledRejection' + ); + var addedListener = fakeGlobal.process.on.calls.argsFor(1)[1]; + addedListener(undefined); + + expect(handler).toHaveBeenCalledWith( + new Error( + 'Unhandled promise rejection with no error or message\n' + + '(Tip: to get a useful stack trace, use ' + + 'Promise.reject(new Error(...)) instead of Promise.reject().)' + ) + ); + }); }); describe('Reporting unhandled promise rejections in the browser', function() { diff --git a/spec/core/SpySpec.js b/spec/core/SpySpec.js index 4614a396..6a8f2e7e 100644 --- a/spec/core/SpySpec.js +++ b/spec/core/SpySpec.js @@ -264,6 +264,7 @@ describe('Spies', function() { reject: jasmine.createSpy() }; customPromise.resolve.and.returnValue('resolved'); + spyOn(env, 'deprecated'); env.configure({ Promise: customPromise }); var spy = env.createSpy('foo').and.resolveTo(42); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 7d15d639..5766f2bf 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -2961,6 +2961,108 @@ describe('Env integration', function() { env.execute(null, done); }); + describe('The promise returned by #execute', function() { + beforeEach(function() { + this.savedInterval = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL; + }); + + afterEach(function() { + jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = this.savedInterval; + }); + + it('is resolved after reporter events are dispatched', function() { + var reporter = jasmine.createSpyObj('reporter', [ + 'specDone', + 'suiteDone', + 'jasmineDone' + ]); + + env.addReporter(reporter); + env.describe('suite', function() { + env.it('spec', function() {}); + }); + + return env.execute(null).then(function() { + expect(reporter.specDone).toHaveBeenCalled(); + expect(reporter.suiteDone).toHaveBeenCalled(); + expect(reporter.jasmineDone).toHaveBeenCalled(); + }); + }); + + it('is resolved after the stack is cleared', function(done) { + var realClearStack = jasmineUnderTest.getClearStack( + jasmineUnderTest.getGlobal() + ), + clearStackSpy = jasmine + .createSpy('clearStack') + .and.callFake(realClearStack); + spyOn(jasmineUnderTest, 'getClearStack').and.returnValue(clearStackSpy); + + // Create a new env that has the clearStack defined above + env.cleanup_(); + env = new jasmineUnderTest.Env(); + + env.describe('suite', function() { + env.it('spec', function() {}); + }); + + env.execute(null).then(function() { + expect(clearStackSpy).toHaveBeenCalled(); // (many times) + clearStackSpy.calls.reset(); + setTimeout(function() { + expect(clearStackSpy).not.toHaveBeenCalled(); + done(); + }); + }); + }); + + it('is resolved after QueueRunner timeouts are cleared', function() { + var setTimeoutSpy = spyOn( + jasmineUnderTest.getGlobal(), + 'setTimeout' + ).and.callThrough(); + var clearTimeoutSpy = spyOn( + jasmineUnderTest.getGlobal(), + 'clearTimeout' + ).and.callThrough(); + + jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = 123456; // a distinctive value + + env = new jasmineUnderTest.Env(); + + env.describe('suite', function() { + env.it('spec', function() {}); + }); + + return env.execute(null).then(function() { + var timeoutIds = setTimeoutSpy.calls + .all() + .filter(function(call) { + return call.args[1] === 123456; + }) + .map(function(call) { + return call.returnValue; + }); + + expect(timeoutIds.length).toBeGreaterThan(0); + + timeoutIds.forEach(function(timeoutId) { + expect(clearTimeoutSpy).toHaveBeenCalledWith(timeoutId); + }); + }); + }); + + it('is resolved even if specs fail', function() { + env.describe('suite', function() { + env.it('spec', function() { + env.expect(true).toBe(false); + }); + }); + + return expectAsync(env.execute(null)).toBeResolved(); + }); + }); + describe('The optional callback argument to #execute', function() { beforeEach(function() { this.savedInterval = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL; diff --git a/src/core/Env.js b/src/core/Env.js index b16dabc8..13274dfa 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -119,6 +119,8 @@ getJasmineRequireObj().Env = function(j$) { * @since 3.5.0 * @type function * @default undefined + * @deprecated In a future version, Jasmine will ignore the Promise config + * property and always create native promises instead. */ Promise: undefined, /** @@ -213,6 +215,11 @@ getJasmineRequireObj().Env = function(j$) { typeof configuration.Promise.reject === 'function' ) { customPromise = configuration.Promise; + self.deprecated( + 'The `Promise` config property is deprecated. Future versions ' + + 'of Jasmine will create native promises even if the `Promise` ' + + 'config property is set. Please remove it.' + ); } else { throw new Error( 'Custom promise library missing `resolve`/`reject` functions' @@ -642,11 +649,17 @@ getJasmineRequireObj().Env = function(j$) { * * execute should not be called more than once. * + * If the environment supports promises, execute will return a promise that + * is resolved after the suite finishes executing. The promise will be + * resolved (not rejected) as long as the suite runs to completion. Use a + * {@link Reporter} to determine whether or not the suite passed. + * * @name Env#execute * @since 2.0.0 * @function * @param {(string[])=} runnablesToRun IDs of suites and/or specs to run * @param {Function=} onComplete Function that will be called after all specs have run + * @return {Promise} */ this.execute = function(runnablesToRun, onComplete) { installGlobalErrors(); @@ -706,65 +719,86 @@ getJasmineRequireObj().Env = function(j$) { var jasmineTimer = new j$.Timer(); jasmineTimer.start(); - /** - * Information passed to the {@link Reporter#jasmineStarted} event. - * @typedef JasmineStartedInfo - * @property {Int} totalSpecsDefined - The total number of specs defined in this suite. - * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. - */ - reporter.jasmineStarted( - { - totalSpecsDefined: totalSpecsDefined, - order: order - }, - function() { - currentlyExecutingSuites.push(topSuite); + var Promise = customPromise || global.Promise; - processor.execute(function() { - clearResourcesForRunnable(topSuite.id); - currentlyExecutingSuites.pop(); - var overallStatus, incompleteReason; - - if (hasFailures || topSuite.result.failedExpectations.length > 0) { - overallStatus = 'failed'; - } else if (focusedRunnables.length > 0) { - overallStatus = 'incomplete'; - incompleteReason = 'fit() or fdescribe() was found'; - } else if (totalSpecsDefined === 0) { - overallStatus = 'incomplete'; - incompleteReason = 'No specs found'; - } else { - overallStatus = 'passed'; + if (Promise) { + return new Promise(function(resolve) { + runAll(function() { + if (onComplete) { + onComplete(); } - /** - * Information passed to the {@link Reporter#jasmineDone} event. - * @typedef JasmineDoneInfo - * @property {OverallStatus} overallStatus - The overall result of the suite: 'passed', 'failed', or 'incomplete'. - * @property {Int} totalTime - The total time (in ms) that it took to execute the suite - * @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete. - * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. - * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level. - * @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level. - */ - reporter.jasmineDone( - { - overallStatus: overallStatus, - totalTime: jasmineTimer.elapsed(), - incompleteReason: incompleteReason, - order: order, - failedExpectations: topSuite.result.failedExpectations, - deprecationWarnings: topSuite.result.deprecationWarnings - }, - function() { - if (onComplete) { - onComplete(); - } - } - ); + resolve(); }); - } - ); + }); + } else { + runAll(function() { + if (onComplete) { + onComplete(); + } + }); + } + + function runAll(done) { + /** + * Information passed to the {@link Reporter#jasmineStarted} event. + * @typedef JasmineStartedInfo + * @property {Int} totalSpecsDefined - The total number of specs defined in this suite. + * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. + */ + reporter.jasmineStarted( + { + totalSpecsDefined: totalSpecsDefined, + order: order + }, + function() { + currentlyExecutingSuites.push(topSuite); + + processor.execute(function() { + clearResourcesForRunnable(topSuite.id); + currentlyExecutingSuites.pop(); + var overallStatus, incompleteReason; + + if ( + hasFailures || + topSuite.result.failedExpectations.length > 0 + ) { + overallStatus = 'failed'; + } else if (focusedRunnables.length > 0) { + overallStatus = 'incomplete'; + incompleteReason = 'fit() or fdescribe() was found'; + } else if (totalSpecsDefined === 0) { + overallStatus = 'incomplete'; + incompleteReason = 'No specs found'; + } else { + overallStatus = 'passed'; + } + + /** + * Information passed to the {@link Reporter#jasmineDone} event. + * @typedef JasmineDoneInfo + * @property {OverallStatus} overallStatus - The overall result of the suite: 'passed', 'failed', or 'incomplete'. + * @property {Int} totalTime - The total time (in ms) that it took to execute the suite + * @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete. + * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. + * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level. + * @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level. + */ + reporter.jasmineDone( + { + overallStatus: overallStatus, + totalTime: jasmineTimer.elapsed(), + incompleteReason: incompleteReason, + order: order, + failedExpectations: topSuite.result.failedExpectations, + deprecationWarnings: topSuite.result.deprecationWarnings + }, + done + ); + }); + } + ); + } }; /** diff --git a/src/core/GlobalErrors.js b/src/core/GlobalErrors.js index b0c6e20e..d96e26c8 100644 --- a/src/core/GlobalErrors.js +++ b/src/core/GlobalErrors.js @@ -19,16 +19,22 @@ getJasmineRequireObj().GlobalErrors = function(j$) { function taggedOnError(error) { var substituteMsg; - if (error) { + if (j$.isError_(error)) { error.jasmineMessage = jasmineMessage + ': ' + error; } else { - substituteMsg = jasmineMessage + ' with no error or message'; + if (error) { + substituteMsg = jasmineMessage + ': ' + error; + } else { + substituteMsg = jasmineMessage + ' with no error or message'; + } if (errorType === 'unhandledRejection') { substituteMsg += '\n' + '(Tip: to get a useful stack trace, use ' + - 'Promise.reject(new Error(...)) instead of Promise.reject().)'; + 'Promise.reject(new Error(...)) instead of Promise.reject(' + + (error ? '...' : '') + + ').)'; } error = new Error(substituteMsg);