Compare commits

...

14 Commits

Author SHA1 Message Date
Steve Gravrock
481f1e7c5c Bump version to 4.5.0 2022-10-29 14:48:32 -07:00
Steve Gravrock
5e650953cd Added Safari 16 to supported browsers 2022-10-22 13:08:10 -07:00
Steve Gravrock
87f9ab29df Fixed the jsdoc types of SuiteResult and SpecResult ids 2022-10-19 17:20:24 -07:00
Steve Gravrock
b831e81074 Include inner exceptions in stack traces 2022-09-24 12:12:21 -07:00
Steve Gravrock
4c13c2b00b Replaced var with const in API doc examples 2022-09-24 10:12:22 -07:00
Steve Gravrock
4cd190b232 Merge branch 'internal-async' 2022-09-18 13:31:43 -07:00
Steve Gravrock
d4025999b7 Report exceptions thrown by a describe before any it calls
Previously, these were masked by the "describe with no children" error.
Now they're reported as suite level errors on an empty suite.
2022-09-17 13:24:45 -07:00
Steve Gravrock
44f331f43d Updated the style of the examples
* const/let instead of var
* classes
* pass our own eslint checks
2022-09-17 12:00:20 -07:00
Steve Gravrock
59848ca151 Coerce the random string to a seed before sending it to reporters
This fixes an error in HTMLReporter when the configured seed is a
number rather than a string, which has been allowed since 3.8.0
2022-09-03 12:36:35 -07:00
Steve Gravrock
c14bfe3e5f Updated release process doc
* Fixed description of patch releases
* Moved -npm release docmentation to that repo
* Refer to -npm specifically rather than "binding libraries" generally,
  now that we only have one of those that versions in lockstep with core.
2022-09-03 11:02:13 -07:00
Steve Gravrock
b3d9435dbb Convreted TreeProcessor to async/await 2022-08-22 17:04:44 -07:00
Steve Gravrock
fec8dd37b0 Merge branch 'main' into internal-async 2022-08-22 10:46:03 -07:00
Steve Gravrock
5f3475342e Re-added missing JasmineStartedInfo jsdoc 2022-08-06 10:53:28 -07:00
Steve Gravrock
21f25972bb Converted ReportDispatcher to promises 2022-07-01 17:25:22 -07:00
29 changed files with 549 additions and 378 deletions

View File

@@ -33,7 +33,7 @@ Microsoft Edge) as well as Node.
| Environment | Supported versions | | Environment | Supported versions |
|-------------------|--------------------| |-------------------|--------------------|
| Node | 12.17+, 14, 16, 18 | | Node | 12.17+, 14, 16, 18 |
| Safari | 14-15 | | Safari | 14-16 |
| Chrome | Evergreen | | Chrome | Evergreen |
| Firefox | Evergreen, 91 | | Firefox | Evergreen, 91 |
| Edge | Evergreen | | Edge | Evergreen |

View File

@@ -18,12 +18,11 @@ copied to `jasmine.js` when the distribution is built. When releasing a new
version, update `package.json` with the new version and `npm run build` to version, update `package.json` with the new version and `npm run build` to
update the gem version number. update the gem version number.
Note that Jasmine should only use the "patch" version number in the following cases: Note that Jasmine should only use the "patch" version number if the new release
contains only bug fixes.
* Changes related to packaging for a specific binding library (npm or browser-runner) When `jasmine-core` revs its major or minor version, the `jasmine` NPM package
* Fixes for regressions. should also rev to that version.
When jasmine-core revs its major or minor version, the binding libraries should also rev to that version.
## Release ## Release
@@ -61,20 +60,13 @@ for instructions.
1. `rake release[${version}]` to copy the current edge docs to the new version 1. `rake release[${version}]` to copy the current edge docs to the new version
1. Commit and push. 1. Commit and push.
### Release the binding libraries ### Release the `jasmine` NPM package
#### NPM See <https://github.com/jasmine/jasmine-npm/blob/main/RELEASE.md>.
1. Create release notes using Anchorman as above ### Publish the GitHub release
1. In `package.json`, update both the package version and the jasmine-core dependency version
1. Commit and push.
1. Wait for Circle CI to go green again.
1. `grunt release `. (Note: This will publish the package by running `npm publish`.)
### Finally
For each of the above GitHub repos:
1. Visit the releases page and find the tag just published. 1. Visit the releases page and find the tag just published.
1. Paste in a link to the correct release notes for this release. The link should reference the blob and tag correctly, and the markdown file for the notes. 2. Paste in a link to the correct release notes for this release.
1. If it is a pre-release, mark it as such. 3. If it is a pre-release, mark it as such.
1. For core, attach the standalone zipfile. 4. Attach the standalone zipfile.

View File

@@ -1,24 +1,24 @@
function Player() { class Player {
} play(song) {
Player.prototype.play = function(song) { this.currentlyPlayingSong = song;
this.currentlyPlayingSong = song; this.isPlaying = true;
this.isPlaying = true;
};
Player.prototype.pause = function() {
this.isPlaying = false;
};
Player.prototype.resume = function() {
if (this.isPlaying) {
throw new Error("song is already playing");
} }
this.isPlaying = true; pause() {
}; this.isPlaying = false;
}
Player.prototype.makeFavorite = function() { resume() {
this.currentlyPlayingSong.persistFavoriteStatus(true); if (this.isPlaying) {
}; throw new Error('song is already playing');
}
this.isPlaying = true;
}
makeFavorite() {
this.currentlyPlayingSong.persistFavoriteStatus(true);
}
}
module.exports = Player; module.exports = Player;

View File

@@ -1,9 +1,8 @@
function Song() { class Song {
persistFavoriteStatus(value) {
// something complicated
throw new Error('not yet implemented');
}
} }
Song.prototype.persistFavoriteStatus = function(value) {
// something complicated
throw new Error("not yet implemented");
};
module.exports = Song; module.exports = Song;

View File

@@ -3,11 +3,11 @@ beforeEach(function () {
toBePlaying: function () { toBePlaying: function () {
return { return {
compare: function (actual, expected) { compare: function (actual, expected) {
var player = actual; const player = actual;
return { return {
pass: player.currentlyPlayingSong === expected && player.isPlaying pass: player.currentlyPlayingSong === expected && player.isPlaying
} };
} }
}; };
} }

View File

@@ -1,36 +1,37 @@
describe("Player", function() { const Player = require('../../lib/jasmine_examples/Player');
var Player = require('../../lib/jasmine_examples/Player'); const Song = require('../../lib/jasmine_examples/Song');
var Song = require('../../lib/jasmine_examples/Song');
var player; describe('Player', function() {
var song; let player;
let song;
beforeEach(function() { beforeEach(function() {
player = new Player(); player = new Player();
song = new Song(); song = new Song();
}); });
it("should be able to play a Song", function() { it('should be able to play a Song', function() {
player.play(song); player.play(song);
expect(player.currentlyPlayingSong).toEqual(song); expect(player.currentlyPlayingSong).toEqual(song);
//demonstrates use of custom matcher // demonstrates use of custom matcher
expect(player).toBePlaying(song); expect(player).toBePlaying(song);
}); });
describe("when song has been paused", function() { describe('when song has been paused', function() {
beforeEach(function() { beforeEach(function() {
player.play(song); player.play(song);
player.pause(); player.pause();
}); });
it("should indicate that the song is currently paused", function() { it('should indicate that the song is currently paused', function() {
expect(player.isPlaying).toBeFalsy(); expect(player.isPlaying).toBeFalsy();
// demonstrates use of 'not' with a custom matcher // demonstrates use of 'not' with a custom matcher
expect(player).not.toBePlaying(song); expect(player).not.toBePlaying(song);
}); });
it("should be possible to resume", function() { it('should be possible to resume', function() {
player.resume(); player.resume();
expect(player.isPlaying).toBeTruthy(); expect(player.isPlaying).toBeTruthy();
expect(player.currentlyPlayingSong).toEqual(song); expect(player.currentlyPlayingSong).toEqual(song);
@@ -38,7 +39,7 @@ describe("Player", function() {
}); });
// demonstrates use of spies to intercept and test method calls // demonstrates use of spies to intercept and test method calls
it("tells the current song if the user has made it a favorite", function() { it('tells the current song if the user has made it a favorite', function() {
spyOn(song, 'persistFavoriteStatus'); spyOn(song, 'persistFavoriteStatus');
player.play(song); player.play(song);
@@ -48,13 +49,13 @@ describe("Player", function() {
}); });
//demonstrates use of expected exceptions //demonstrates use of expected exceptions
describe("#resume", function() { describe('#resume', function() {
it("should throw an exception if song is already playing", function() { it('should throw an exception if song is already playing', function() {
player.play(song); player.play(song);
expect(function() { expect(function() {
player.resume(); player.resume();
}).toThrowError("song is already playing"); }).toThrowError('song is already playing');
}); });
}); });
}); });

View File

@@ -1,34 +1,34 @@
describe("Player", function() { describe('Player', function() {
var player; let player;
var song; let song;
beforeEach(function() { beforeEach(function() {
player = new Player(); player = new Player();
song = new Song(); song = new Song();
}); });
it("should be able to play a Song", function() { it('should be able to play a Song', function() {
player.play(song); player.play(song);
expect(player.currentlyPlayingSong).toEqual(song); expect(player.currentlyPlayingSong).toEqual(song);
//demonstrates use of custom matcher // demonstrates use of custom matcher
expect(player).toBePlaying(song); expect(player).toBePlaying(song);
}); });
describe("when song has been paused", function() { describe('when song has been paused', function() {
beforeEach(function() { beforeEach(function() {
player.play(song); player.play(song);
player.pause(); player.pause();
}); });
it("should indicate that the song is currently paused", function() { it('should indicate that the song is currently paused', function() {
expect(player.isPlaying).toBeFalsy(); expect(player.isPlaying).toBeFalsy();
// demonstrates use of 'not' with a custom matcher // demonstrates use of 'not' with a custom matcher
expect(player).not.toBePlaying(song); expect(player).not.toBePlaying(song);
}); });
it("should be possible to resume", function() { it('should be possible to resume', function() {
player.resume(); player.resume();
expect(player.isPlaying).toBeTruthy(); expect(player.isPlaying).toBeTruthy();
expect(player.currentlyPlayingSong).toEqual(song); expect(player.currentlyPlayingSong).toEqual(song);
@@ -36,7 +36,7 @@ describe("Player", function() {
}); });
// demonstrates use of spies to intercept and test method calls // demonstrates use of spies to intercept and test method calls
it("tells the current song if the user has made it a favorite", function() { it('tells the current song if the user has made it a favorite', function() {
spyOn(song, 'persistFavoriteStatus'); spyOn(song, 'persistFavoriteStatus');
player.play(song); player.play(song);
@@ -46,13 +46,13 @@ describe("Player", function() {
}); });
//demonstrates use of expected exceptions //demonstrates use of expected exceptions
describe("#resume", function() { describe('#resume', function() {
it("should throw an exception if song is already playing", function() { it('should throw an exception if song is already playing', function() {
player.play(song); player.play(song);
expect(function() { expect(function() {
player.resume(); player.resume();
}).toThrowError("song is already playing"); }).toThrowError('song is already playing');
}); });
}); });
}); });

View File

@@ -3,7 +3,7 @@ beforeEach(function () {
toBePlaying: function () { toBePlaying: function () {
return { return {
compare: function (actual, expected) { compare: function (actual, expected) {
var player = actual; const player = actual;
return { return {
pass: player.currentlyPlayingSong === expected && player.isPlaying pass: player.currentlyPlayingSong === expected && player.isPlaying

View File

@@ -1,22 +1,22 @@
function Player() { class Player {
} play(song) {
Player.prototype.play = function(song) { this.currentlyPlayingSong = song;
this.currentlyPlayingSong = song; this.isPlaying = true;
this.isPlaying = true;
};
Player.prototype.pause = function() {
this.isPlaying = false;
};
Player.prototype.resume = function() {
if (this.isPlaying) {
throw new Error("song is already playing");
} }
this.isPlaying = true; pause() {
}; this.isPlaying = false;
}
Player.prototype.makeFavorite = function() { resume() {
this.currentlyPlayingSong.persistFavoriteStatus(true); if (this.isPlaying) {
}; throw new Error('song is already playing');
}
this.isPlaying = true;
}
makeFavorite() {
this.currentlyPlayingSong.persistFavoriteStatus(true);
}
}

View File

@@ -1,7 +1,6 @@
function Song() { class Song {
persistFavoriteStatus(value) {
// something complicated
throw new Error('not yet implemented');
}
} }
Song.prototype.persistFavoriteStatus = function(value) {
// something complicated
throw new Error("not yet implemented");
};

View File

@@ -776,7 +776,7 @@ getJasmineRequireObj().Spec = function(j$) {
/** /**
* @typedef SpecResult * @typedef SpecResult
* @property {Int} id - The unique id of this spec. * @property {String} id - The unique id of this spec.
* @property {String} description - The description passed to the {@link it} that created this spec. * @property {String} description - The description passed to the {@link it} that created this spec.
* @property {String} fullName - The full description including all ancestors of this spec. * @property {String} fullName - The full description including all ancestors of this spec.
* @property {Expectation[]} failedExpectations - The list of expectations that failed during execution of this spec. * @property {Expectation[]} failedExpectations - The list of expectations that failed during execution of this spec.
@@ -1843,12 +1843,12 @@ getJasmineRequireObj().Env = function(j$) {
function specStarted(spec, suite, next) { function specStarted(spec, suite, next) {
runner.currentSpec = spec; runner.currentSpec = spec;
runableResources.initForRunable(spec.id, suite.id); runableResources.initForRunable(spec.id, suite.id);
reporter.specStarted(spec.result, next); reporter.specStarted(spec.result).then(next);
} }
function reportSpecDone(spec, result, next) { function reportSpecDone(spec, result, next) {
spec.reportedDone = true; spec.reportedDone = true;
reporter.specDone(result, next); reporter.specDone(result).then(next);
} }
this.it = function(description, fn, timeout) { this.it = function(description, fn, timeout) {
@@ -2868,7 +2868,8 @@ getJasmineRequireObj().clearStack = function(j$) {
SAFARI || SAFARI ||
j$.util.isUndefined(global.MessageChannel) /* tests */ j$.util.isUndefined(global.MessageChannel) /* tests */
) { ) {
// queueMicrotask is dramatically faster than MessageChannel in Safari. // queueMicrotask is dramatically faster than MessageChannel in Safari,
// at least through version 16.
// Some of our own integration tests provide a mock queueMicrotask in all // Some of our own integration tests provide a mock queueMicrotask in all
// environments because it's simpler to mock than MessageChannel. // environments because it's simpler to mock than MessageChannel.
return browserQueueMicrotaskImpl(global); return browserQueueMicrotaskImpl(global);
@@ -3494,18 +3495,38 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) {
return null; return null;
} }
const stackTrace = new j$.StackTrace(error); const lines = this.stack_(error, {
const lines = filterJasmine(stackTrace); messageHandling: omitMessage ? 'omit' : undefined
let result = ''; });
return lines.join('\n');
};
if (stackTrace.message && !omitMessage) { // messageHandling can be falsy (unspecified), 'omit', or 'require'
this.stack_ = function(error, { messageHandling }) {
let lines = formatProperties(error).split('\n');
if (lines[lines.length - 1] === '') {
lines.pop();
}
const stackTrace = new j$.StackTrace(error);
lines = lines.concat(filterJasmine(stackTrace));
if (messageHandling === 'require') {
lines.unshift(stackTrace.message || 'Error: ' + error.message);
} else if (messageHandling !== 'omit' && stackTrace.message) {
lines.unshift(stackTrace.message); lines.unshift(stackTrace.message);
} }
result += formatProperties(error); if (error.cause) {
result += lines.join('\n'); const substack = this.stack_(error.cause, {
messageHandling: 'require'
});
substack[0] = 'Caused by: ' + substack[0];
lines = lines.concat(substack);
}
return result; return lines;
}; };
function filterJasmine(stackTrace) { function filterJasmine(stackTrace) {
@@ -5241,7 +5262,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
* }; * };
* } * }
* *
* var actual = { * const actual = {
* n: 2, * n: 2,
* otherFields: "don't care" * otherFields: "don't care"
* }; * };
@@ -6387,7 +6408,7 @@ getJasmineRequireObj().toHaveClass = function(j$) {
* @since 3.0.0 * @since 3.0.0
* @param {Object} expected - The class name to test for * @param {Object} expected - The class name to test for
* @example * @example
* var el = document.createElement('div'); * const el = document.createElement('div');
* el.className = 'foo bar baz'; * el.className = 'foo bar baz';
* expect(el).toHaveClass('bar'); * expect(el).toHaveClass('bar');
*/ */
@@ -7719,7 +7740,7 @@ getJasmineRequireObj().ReportDispatcher = function(j$) {
for (const method of dispatchedMethods) { for (const method of dispatchedMethods) {
this[method] = (function(m) { this[method] = (function(m) {
return function() { return function() {
dispatch(m, arguments); return dispatch(m, arguments);
}; };
})(method); })(method);
} }
@@ -7745,25 +7766,25 @@ getJasmineRequireObj().ReportDispatcher = function(j$) {
if (reporters.length === 0 && fallbackReporter !== null) { if (reporters.length === 0 && fallbackReporter !== null) {
reporters.push(fallbackReporter); reporters.push(fallbackReporter);
} }
const onComplete = args[args.length - 1];
args = Array.from(args).splice(0, args.length - 1);
const fns = []; const fns = [];
for (const reporter of reporters) { for (const reporter of reporters) {
addFn(fns, reporter, method, args); addFn(fns, reporter, method, args);
} }
queueRunnerFactory({ return new Promise(function(resolve) {
queueableFns: fns, queueRunnerFactory({
onComplete: onComplete, queueableFns: fns,
isReporter: true, onComplete: resolve,
onMultipleDone: function() { isReporter: true,
onLateError( onMultipleDone: function() {
new Error( onLateError(
"An asynchronous reporter callback called its 'done' callback " + new Error(
'more than once.' "An asynchronous reporter callback called its 'done' callback " +
) 'more than once.'
); )
} );
}
});
}); });
} }
@@ -8419,7 +8440,6 @@ getJasmineRequireObj().Runner = function(j$) {
this.executedBefore_ = true; this.executedBefore_ = true;
this.hasFailures = false; this.hasFailures = false;
const totalSpecsDefined = this.totalSpecsDefined_();
const focusedRunables = this.focusedRunables_(); const focusedRunables = this.focusedRunables_();
const config = this.getConfig_(); const config = this.getConfig_();
@@ -8433,7 +8453,7 @@ getJasmineRequireObj().Runner = function(j$) {
const order = new j$.Order({ const order = new j$.Order({
random: config.random, random: config.random,
seed: config.seed seed: j$.isNumber_(config.seed) ? config.seed + '' : config.seed
}); });
const processor = new j$.TreeProcessor({ const processor = new j$.TreeProcessor({
@@ -8458,7 +8478,7 @@ getJasmineRequireObj().Runner = function(j$) {
nodeStart: (suite, next) => { nodeStart: (suite, next) => {
this.currentlyExecutingSuites_.push(suite); this.currentlyExecutingSuites_.push(suite);
this.runableResources_.initForRunable(suite.id, suite.parentSuite.id); this.runableResources_.initForRunable(suite.id, suite.parentSuite.id);
this.reporter_.suiteStarted(suite.result, next); this.reporter_.suiteStarted(suite.result).then(next);
suite.startTimer(); suite.startTimer();
}, },
nodeComplete: (suite, result, next) => { nodeComplete: (suite, result, next) => {
@@ -8496,106 +8516,97 @@ getJasmineRequireObj().Runner = function(j$) {
); );
} }
return this.execute2_(runablesToRun, order, processor);
}
async execute2_(runablesToRun, order, processor) {
const totalSpecsDefined = this.totalSpecsDefined_();
this.runableResources_.initForRunable(this.topSuite_.id); this.runableResources_.initForRunable(this.topSuite_.id);
const jasmineTimer = new j$.Timer(); const jasmineTimer = new j$.Timer();
jasmineTimer.start(); jasmineTimer.start();
return new Promise(resolve => { /**
/** * Information passed to the {@link Reporter#jasmineStarted} event.
* Information passed to the {@link Reporter#jasmineStarted} event. * @typedef JasmineStartedInfo
* @typedef JasmineStartedInfo * @property {Int} totalSpecsDefined - The total number of specs defined in this suite.
* @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.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite. * @since 2.0.0
* @since 2.0.0 */
*/ await this.reporter_.jasmineStarted({
this.reporter_.jasmineStarted( totalSpecsDefined,
{ order: order
totalSpecsDefined,
order: order
},
() => {
this.currentlyExecutingSuites_.push(this.topSuite_);
processor.execute(() => {
(async () => {
if (this.topSuite_.hadBeforeAllFailure) {
await this.reportChildrenOfBeforeAllFailure_(this.topSuite_);
}
this.runableResources_.clearForRunable(this.topSuite_.id);
this.currentlyExecutingSuites_.pop();
let overallStatus, incompleteReason;
if (
this.hasFailures ||
this.topSuite_.result.failedExpectations.length > 0
) {
overallStatus = 'failed';
} else if (focusedRunables.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.
* @since 2.4.0
*/
const jasmineDoneInfo = {
overallStatus: overallStatus,
totalTime: jasmineTimer.elapsed(),
incompleteReason: incompleteReason,
order: order,
failedExpectations: this.topSuite_.result.failedExpectations,
deprecationWarnings: this.topSuite_.result.deprecationWarnings
};
this.topSuite_.reportedDone = true;
this.reporter_.jasmineDone(jasmineDoneInfo, function() {
resolve(jasmineDoneInfo);
});
})();
});
}
);
}); });
this.currentlyExecutingSuites_.push(this.topSuite_);
await processor.execute();
if (this.topSuite_.hadBeforeAllFailure) {
await this.reportChildrenOfBeforeAllFailure_(this.topSuite_);
}
this.runableResources_.clearForRunable(this.topSuite_.id);
this.currentlyExecutingSuites_.pop();
let overallStatus, incompleteReason;
if (
this.hasFailures ||
this.topSuite_.result.failedExpectations.length > 0
) {
overallStatus = 'failed';
} else if (this.focusedRunables_().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.
* @since 2.4.0
*/
const jasmineDoneInfo = {
overallStatus: overallStatus,
totalTime: jasmineTimer.elapsed(),
incompleteReason: incompleteReason,
order: order,
failedExpectations: this.topSuite_.result.failedExpectations,
deprecationWarnings: this.topSuite_.result.deprecationWarnings
};
this.topSuite_.reportedDone = true;
await this.reporter_.jasmineDone(jasmineDoneInfo);
return jasmineDoneInfo;
} }
reportSuiteDone_(suite, result, next) { reportSuiteDone_(suite, result, next) {
suite.reportedDone = true; suite.reportedDone = true;
this.reporter_.suiteDone(result, next); this.reporter_.suiteDone(result).then(next);
} }
async reportChildrenOfBeforeAllFailure_(suite) { async reportChildrenOfBeforeAllFailure_(suite) {
for (const child of suite.children) { for (const child of suite.children) {
if (child instanceof j$.Suite) { if (child instanceof j$.Suite) {
await new Promise(resolve => { await this.reporter_.suiteStarted(child.result);
this.reporter_.suiteStarted(child.result, resolve);
});
await this.reportChildrenOfBeforeAllFailure_(child); await this.reportChildrenOfBeforeAllFailure_(child);
// Marking the suite passed is consistent with how suites that // Marking the suite passed is consistent with how suites that
// contain failed specs but no suite-level failures are reported. // contain failed specs but no suite-level failures are reported.
child.result.status = 'passed'; child.result.status = 'passed';
await new Promise(resolve => { await this.reporter_.suiteDone(child.result);
this.reporter_.suiteDone(child.result, resolve);
});
} else { } else {
/* a spec */ /* a spec */
await new Promise(resolve => { await this.reporter_.specStarted(child.result);
this.reporter_.specStarted(child.result, resolve);
});
child.addExpectationResult( child.addExpectationResult(
false, false,
@@ -9621,7 +9632,7 @@ getJasmineRequireObj().Suite = function(j$) {
Suite.prototype.reset = function() { Suite.prototype.reset = function() {
/** /**
* @typedef SuiteResult * @typedef SuiteResult
* @property {Int} id - The unique id of this suite. * @property {String} id - The unique id of this suite.
* @property {String} description - The description text passed to the {@link describe} that made this suite. * @property {String} description - The description text passed to the {@link describe} that made this suite.
* @property {String} fullName - The full description including all ancestors of this suite. * @property {String} fullName - The full description including all ancestors of this suite.
* @property {Expectation[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite. * @property {Expectation[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
@@ -9872,11 +9883,6 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
suite.exclude(); suite.exclude();
} }
this.addSpecsToSuite_(suite, definitionFn); this.addSpecsToSuite_(suite, definitionFn);
if (suite.parentSuite && !suite.children.length) {
throw new Error(
`describe with no children (describe() or it()): ${suite.getFullName()}`
);
}
return suite; return suite;
} }
@@ -10023,11 +10029,19 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
const parentSuite = this.currentDeclarationSuite_; const parentSuite = this.currentDeclarationSuite_;
parentSuite.addChild(suite); parentSuite.addChild(suite);
this.currentDeclarationSuite_ = suite; this.currentDeclarationSuite_ = suite;
let threw = false;
try { try {
definitionFn(); definitionFn();
} catch (e) { } catch (e) {
suite.handleException(e); suite.handleException(e);
threw = true;
}
if (suite.parentSuite && !suite.children.length && !threw) {
throw new Error(
`describe with no children (describe() or it()): ${suite.getFullName()}`
);
} }
this.currentDeclarationSuite_ = parentSuite; this.currentDeclarationSuite_ = parentSuite;
@@ -10195,7 +10209,7 @@ getJasmineRequireObj().TreeProcessor = function() {
return stats; return stats;
}; };
this.execute = function(done) { this.execute = async function() {
if (!processed) { if (!processed) {
this.processTree(); this.processTree();
} }
@@ -10206,16 +10220,18 @@ getJasmineRequireObj().TreeProcessor = function() {
const childFns = wrapChildren(tree, 0); const childFns = wrapChildren(tree, 0);
queueRunnerFactory({ await new Promise(function(resolve) {
queueableFns: childFns, queueRunnerFactory({
userContext: tree.sharedUserContext(), queueableFns: childFns,
onException: function() { userContext: tree.sharedUserContext(),
tree.handleException.apply(tree, arguments); onException: function() {
}, tree.handleException.apply(tree, arguments);
onComplete: done, },
onMultipleDone: tree.onMultipleDone onComplete: resolve,
? tree.onMultipleDone.bind(tree) onMultipleDone: tree.onMultipleDone
: null ? tree.onMultipleDone.bind(tree)
: null
});
}); });
}; };

View File

@@ -1,7 +1,7 @@
{ {
"name": "jasmine-core", "name": "jasmine-core",
"license": "MIT", "license": "MIT",
"version": "4.4.0", "version": "4.5.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/jasmine/jasmine.git" "url": "https://github.com/jasmine/jasmine.git"

40
release_notes/4.5.0.md Normal file
View File

@@ -0,0 +1,40 @@
# Jasmine 4.5.0 Release Notes
## New Features
* Added Safari 16 to supported browsers
* Include inner exceptions in stack traces
## Bug Fixes
* Report exceptions thrown by a describe before any it calls
* Coerce the random string to a seed before sending it to reporters
* This fixes an error in HTMLReporter when the configured seed is a
number rather than a string, which has been allowed since 3.8.0
## Documentation updates
* Fixed the jsdoc types of SuiteResult and SpecResult ids
* Replaced var with const in API doc examples
* Updated the style of the examples that are included in jasmine-core
## Internal improvements
* Converted TreeProcessor to async/await
* Converted ReportDispatcher to promises
## Supported environments
jasmine-core 4.5.0 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 12.17+, 14, 16, 18 |
| Safari | 14-16 |
| Chrome | 107 |
| Firefox | 91, 102, 106 |
| Edge | 106 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

View File

@@ -27,6 +27,7 @@ run_browser chrome latest
run_browser firefox latest run_browser firefox latest
run_browser firefox 102 run_browser firefox 102
run_browser firefox 91 run_browser firefox 91
run_browser safari 16
run_browser safari 15 run_browser safari 15
run_browser safari 14 run_browser safari 14
run_browser MicrosoftEdge latest run_browser MicrosoftEdge latest

View File

@@ -256,5 +256,51 @@ describe('ExceptionFormatter', function() {
expect(result).not.toContain('an error'); expect(result).not.toContain('an error');
}); });
}); });
describe('In environments that support the cause property of Errors', function() {
beforeEach(function() {
const inner = new Error('inner');
const outer = new Error('outer', { cause: inner });
if (!outer.cause) {
// Currently: Node 12, Node 14, Safari 14
pending('Environment does not support error cause');
}
});
it('recursively includes the cause in the stack trace in this environment', function() {
const subject = new jasmineUnderTest.ExceptionFormatter();
const rootCause = new Error('root cause');
const proximateCause = new Error('proximate cause', {
cause: rootCause
});
const symptom = new Error('symptom', { cause: proximateCause });
const lines = subject.stack(symptom).split('\n');
// Not all environments include the message in the stack trace.
const hasRootMessage = lines[0].indexOf('symptom') !== -1;
const firstSymptomStackIx = hasRootMessage ? 1 : 0;
expect(lines[firstSymptomStackIx])
.withContext('first symptom stack frame')
.toContain('ExceptionFormatterSpec.js');
const proximateCauseMsgIx = lines.indexOf(
'Caused by: Error: proximate cause'
);
expect(proximateCauseMsgIx)
.withContext('index of proximate cause message')
.toBeGreaterThan(firstSymptomStackIx);
expect(lines[proximateCauseMsgIx + 1])
.withContext('first proximate cause stack frame')
.toContain('ExceptionFormatterSpec.js');
const rootCauseMsgIx = lines.indexOf('Caused by: Error: root cause');
expect(rootCauseMsgIx)
.withContext('index of root cause message')
.toBeGreaterThan(proximateCauseMsgIx + 1);
expect(lines[rootCauseMsgIx + 1])
.withContext('first root cause stack frame')
.toContain('ExceptionFormatterSpec.js');
});
});
}); });
}); });

View File

@@ -18,13 +18,12 @@ describe('ReportDispatcher', function() {
queueRunnerFactory queueRunnerFactory
), ),
reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']), reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']),
anotherReporter = jasmine.createSpyObj('reporter', ['foo', 'bar']), anotherReporter = jasmine.createSpyObj('reporter', ['foo', 'bar']);
completeCallback = jasmine.createSpy('complete');
dispatcher.addReporter(reporter); dispatcher.addReporter(reporter);
dispatcher.addReporter(anotherReporter); dispatcher.addReporter(anotherReporter);
dispatcher.foo(123, 456, completeCallback); dispatcher.foo(123, 456);
expect(queueRunnerFactory).toHaveBeenCalledWith( expect(queueRunnerFactory).toHaveBeenCalledWith(
jasmine.objectContaining({ jasmine.objectContaining({
@@ -47,7 +46,7 @@ describe('ReportDispatcher', function() {
queueRunnerFactory.calls.reset(); queueRunnerFactory.calls.reset();
dispatcher.bar('a', 'b', completeCallback); dispatcher.bar('a', 'b');
expect(queueRunnerFactory).toHaveBeenCalledWith( expect(queueRunnerFactory).toHaveBeenCalledWith(
jasmine.objectContaining({ jasmine.objectContaining({
@@ -91,11 +90,10 @@ describe('ReportDispatcher', function() {
['foo', 'bar'], ['foo', 'bar'],
queueRunnerFactory queueRunnerFactory
), ),
reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']), reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']);
completeCallback = jasmine.createSpy('complete');
dispatcher.provideFallbackReporter(reporter); dispatcher.provideFallbackReporter(reporter);
dispatcher.foo(123, 456, completeCallback); dispatcher.foo(123, 456);
expect(queueRunnerFactory).toHaveBeenCalledWith( expect(queueRunnerFactory).toHaveBeenCalledWith(
jasmine.objectContaining({ jasmine.objectContaining({
@@ -116,12 +114,11 @@ describe('ReportDispatcher', function() {
queueRunnerFactory queueRunnerFactory
), ),
reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']), reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']),
fallbackReporter = jasmine.createSpyObj('otherReporter', ['foo', 'bar']), fallbackReporter = jasmine.createSpyObj('otherReporter', ['foo', 'bar']);
completeCallback = jasmine.createSpy('complete');
dispatcher.provideFallbackReporter(fallbackReporter); dispatcher.provideFallbackReporter(fallbackReporter);
dispatcher.addReporter(reporter); dispatcher.addReporter(reporter);
dispatcher.foo(123, 456, completeCallback); dispatcher.foo(123, 456);
expect(queueRunnerFactory).toHaveBeenCalledWith( expect(queueRunnerFactory).toHaveBeenCalledWith(
jasmine.objectContaining({ jasmine.objectContaining({
@@ -143,11 +140,10 @@ describe('ReportDispatcher', function() {
queueRunnerFactory queueRunnerFactory
), ),
reporter1 = jasmine.createSpyObj('reporter1', ['foo', 'bar']), reporter1 = jasmine.createSpyObj('reporter1', ['foo', 'bar']),
reporter2 = jasmine.createSpyObj('reporter2', ['foo', 'bar']), reporter2 = jasmine.createSpyObj('reporter2', ['foo', 'bar']);
completeCallback = jasmine.createSpy('complete');
dispatcher.addReporter(reporter1); dispatcher.addReporter(reporter1);
dispatcher.foo(123, completeCallback); dispatcher.foo(123);
expect(queueRunnerFactory).toHaveBeenCalledWith( expect(queueRunnerFactory).toHaveBeenCalledWith(
jasmine.objectContaining({ jasmine.objectContaining({
queueableFns: [{ fn: jasmine.any(Function) }], queueableFns: [{ fn: jasmine.any(Function) }],
@@ -161,7 +157,7 @@ describe('ReportDispatcher', function() {
dispatcher.clearReporters(); dispatcher.clearReporters();
dispatcher.addReporter(reporter2); dispatcher.addReporter(reporter2);
dispatcher.bar(456, completeCallback); dispatcher.bar(456);
expect(queueRunnerFactory).toHaveBeenCalledWith( expect(queueRunnerFactory).toHaveBeenCalledWith(
jasmine.objectContaining({ jasmine.objectContaining({

View File

@@ -274,7 +274,7 @@ describe('TreeProcessor', function() {
expect(result.valid).toBe(true); expect(result.valid).toBe(true);
}); });
it('runs a single leaf', function() { it('runs a single leaf', async function() {
const leaf = new Leaf(), const leaf = new Leaf(),
node = new Node({ children: [leaf], userContext: { root: 'context' } }), node = new Node({ children: [leaf], userContext: { root: 'context' } }),
queueRunner = jasmine.createSpy('queueRunner'), queueRunner = jasmine.createSpy('queueRunner'),
@@ -282,25 +282,27 @@ describe('TreeProcessor', function() {
tree: node, tree: node,
runnableIds: [leaf.id], runnableIds: [leaf.id],
queueRunnerFactory: queueRunner queueRunnerFactory: queueRunner
}), });
treeComplete = jasmine.createSpy('treeComplete');
processor.execute(treeComplete); const promise = processor.execute();
expect(queueRunner).toHaveBeenCalledWith({ expect(queueRunner).toHaveBeenCalledWith({
onComplete: treeComplete, onComplete: jasmine.any(Function),
onException: jasmine.any(Function), onException: jasmine.any(Function),
userContext: { root: 'context' }, userContext: { root: 'context' },
queueableFns: [{ fn: jasmine.any(Function) }], queueableFns: [{ fn: jasmine.any(Function) }],
onMultipleDone: null onMultipleDone: null
}); });
queueRunner.calls.mostRecent().args[0].queueableFns[0].fn('foo'); const queueRunnerArgs = queueRunner.calls.mostRecent().args[0];
queueRunnerArgs.queueableFns[0].fn('foo');
expect(leaf.execute).toHaveBeenCalledWith(queueRunner, 'foo', false, false); expect(leaf.execute).toHaveBeenCalledWith(queueRunner, 'foo', false, false);
queueRunnerArgs.onComplete();
await expectAsync(promise).toBeResolvedTo(undefined);
}); });
it('runs a node with no children', function() { it('runs a node with no children', async function() {
const node = new Node({ userContext: { node: 'context' } }), const node = new Node({ userContext: { node: 'context' } }),
root = new Node({ children: [node], userContext: { root: 'context' } }), root = new Node({ children: [node], userContext: { root: 'context' } }),
nodeStart = jasmine.createSpy('nodeStart'), nodeStart = jasmine.createSpy('nodeStart'),
@@ -313,21 +315,20 @@ describe('TreeProcessor', function() {
nodeComplete: nodeComplete, nodeComplete: nodeComplete,
queueRunnerFactory: queueRunner queueRunnerFactory: queueRunner
}), }),
treeComplete = jasmine.createSpy('treeComplete'),
nodeDone = jasmine.createSpy('nodeDone'); nodeDone = jasmine.createSpy('nodeDone');
processor.execute(treeComplete); const promise = processor.execute();
expect(queueRunner).toHaveBeenCalledWith({ expect(queueRunner).toHaveBeenCalledWith({
onComplete: treeComplete, onComplete: jasmine.any(Function),
onException: jasmine.any(Function), onException: jasmine.any(Function),
userContext: { root: 'context' }, userContext: { root: 'context' },
queueableFns: [{ fn: jasmine.any(Function) }], queueableFns: [{ fn: jasmine.any(Function) }],
onMultipleDone: null onMultipleDone: null
}); });
queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(nodeDone); const queueRunnerArgs = queueRunner.calls.mostRecent().args[0];
queueRunnerArgs.queueableFns[0].fn(nodeDone);
expect(queueRunner).toHaveBeenCalledWith({ expect(queueRunner).toHaveBeenCalledWith({
onComplete: jasmine.any(Function), onComplete: jasmine.any(Function),
onMultipleDone: null, onMultipleDone: null,
@@ -348,6 +349,9 @@ describe('TreeProcessor', function() {
{ my: 'result' }, { my: 'result' },
jasmine.any(Function) jasmine.any(Function)
); );
queueRunnerArgs.onComplete();
await expectAsync(promise).toBeResolvedTo(undefined);
}); });
it('runs a node with children', function() { it('runs a node with children', function() {

View File

@@ -2026,6 +2026,29 @@ describe('Env integration', function() {
expect(doneArg.order.seed).toEqual('123456'); expect(doneArg.order.seed).toEqual('123456');
}); });
it('coerces the random seed to a string if it is a number', async function() {
const reporter = jasmine.createSpyObj('fakeReporter', [
'jasmineStarted',
'jasmineDone',
'suiteStarted',
'suiteDone',
'specStarted',
'specDone'
]);
env.configure({ random: true, seed: 123456 });
env.addReporter(reporter);
env.configure({ random: true });
await env.execute();
expect(reporter.jasmineStarted).toHaveBeenCalled();
const startedArg = reporter.jasmineStarted.calls.argsFor(0)[0];
expect(startedArg.order.seed).toEqual('123456');
const doneArg = reporter.jasmineDone.calls.argsFor(0)[0];
expect(doneArg.order.seed).toEqual('123456');
});
it('should report pending spec messages', async function() { it('should report pending spec messages', async function() {
const reporter = jasmine.createSpyObj('fakeReporter', ['specDone']); const reporter = jasmine.createSpyObj('fakeReporter', ['specDone']);
@@ -3943,6 +3966,44 @@ describe('Env integration', function() {
}); });
}); });
it('reports a suite level error when a describe fn throws', async function() {
const reporter = jasmine.createSpyObj('reporter', ['suiteDone']);
env.addReporter(reporter);
env.describe('throws before defining specs', function() {
throw new Error('nope');
});
env.describe('throws after defining specs', function() {
env.it('is a spec');
throw new Error('nope');
});
await env.execute();
expect(reporter.suiteDone).toHaveBeenCalledWith(
jasmine.objectContaining({
fullName: 'throws after defining specs',
failedExpectations: [
jasmine.objectContaining({
message: jasmine.stringContaining('Error: nope')
})
]
})
);
expect(reporter.suiteDone).toHaveBeenCalledWith(
jasmine.objectContaining({
fullName: 'throws after defining specs',
failedExpectations: [
jasmine.objectContaining({
message: jasmine.stringContaining('Error: nope')
})
]
})
);
});
function browserEventMethods() { function browserEventMethods() {
return { return {
addEventListener() {}, addEventListener() {},

View File

@@ -80,7 +80,8 @@ getJasmineRequireObj().clearStack = function(j$) {
SAFARI || SAFARI ||
j$.util.isUndefined(global.MessageChannel) /* tests */ j$.util.isUndefined(global.MessageChannel) /* tests */
) { ) {
// queueMicrotask is dramatically faster than MessageChannel in Safari. // queueMicrotask is dramatically faster than MessageChannel in Safari,
// at least through version 16.
// Some of our own integration tests provide a mock queueMicrotask in all // Some of our own integration tests provide a mock queueMicrotask in all
// environments because it's simpler to mock than MessageChannel. // environments because it's simpler to mock than MessageChannel.
return browserQueueMicrotaskImpl(global); return browserQueueMicrotaskImpl(global);

View File

@@ -701,12 +701,12 @@ getJasmineRequireObj().Env = function(j$) {
function specStarted(spec, suite, next) { function specStarted(spec, suite, next) {
runner.currentSpec = spec; runner.currentSpec = spec;
runableResources.initForRunable(spec.id, suite.id); runableResources.initForRunable(spec.id, suite.id);
reporter.specStarted(spec.result, next); reporter.specStarted(spec.result).then(next);
} }
function reportSpecDone(spec, result, next) { function reportSpecDone(spec, result, next) {
spec.reportedDone = true; spec.reportedDone = true;
reporter.specDone(result, next); reporter.specDone(result).then(next);
} }
this.it = function(description, fn, timeout) { this.it = function(description, fn, timeout) {

View File

@@ -44,18 +44,38 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) {
return null; return null;
} }
const stackTrace = new j$.StackTrace(error); const lines = this.stack_(error, {
const lines = filterJasmine(stackTrace); messageHandling: omitMessage ? 'omit' : undefined
let result = ''; });
return lines.join('\n');
};
if (stackTrace.message && !omitMessage) { // messageHandling can be falsy (unspecified), 'omit', or 'require'
this.stack_ = function(error, { messageHandling }) {
let lines = formatProperties(error).split('\n');
if (lines[lines.length - 1] === '') {
lines.pop();
}
const stackTrace = new j$.StackTrace(error);
lines = lines.concat(filterJasmine(stackTrace));
if (messageHandling === 'require') {
lines.unshift(stackTrace.message || 'Error: ' + error.message);
} else if (messageHandling !== 'omit' && stackTrace.message) {
lines.unshift(stackTrace.message); lines.unshift(stackTrace.message);
} }
result += formatProperties(error); if (error.cause) {
result += lines.join('\n'); const substack = this.stack_(error.cause, {
messageHandling: 'require'
});
substack[0] = 'Caused by: ' + substack[0];
lines = lines.concat(substack);
}
return result; return lines;
}; };
function filterJasmine(stackTrace) { function filterJasmine(stackTrace) {

View File

@@ -5,7 +5,7 @@ getJasmineRequireObj().ReportDispatcher = function(j$) {
for (const method of dispatchedMethods) { for (const method of dispatchedMethods) {
this[method] = (function(m) { this[method] = (function(m) {
return function() { return function() {
dispatch(m, arguments); return dispatch(m, arguments);
}; };
})(method); })(method);
} }
@@ -31,25 +31,25 @@ getJasmineRequireObj().ReportDispatcher = function(j$) {
if (reporters.length === 0 && fallbackReporter !== null) { if (reporters.length === 0 && fallbackReporter !== null) {
reporters.push(fallbackReporter); reporters.push(fallbackReporter);
} }
const onComplete = args[args.length - 1];
args = Array.from(args).splice(0, args.length - 1);
const fns = []; const fns = [];
for (const reporter of reporters) { for (const reporter of reporters) {
addFn(fns, reporter, method, args); addFn(fns, reporter, method, args);
} }
queueRunnerFactory({ return new Promise(function(resolve) {
queueableFns: fns, queueRunnerFactory({
onComplete: onComplete, queueableFns: fns,
isReporter: true, onComplete: resolve,
onMultipleDone: function() { isReporter: true,
onLateError( onMultipleDone: function() {
new Error( onLateError(
"An asynchronous reporter callback called its 'done' callback " + new Error(
'more than once.' "An asynchronous reporter callback called its 'done' callback " +
) 'more than once.'
); )
} );
}
});
}); });
} }

View File

@@ -37,7 +37,6 @@ getJasmineRequireObj().Runner = function(j$) {
this.executedBefore_ = true; this.executedBefore_ = true;
this.hasFailures = false; this.hasFailures = false;
const totalSpecsDefined = this.totalSpecsDefined_();
const focusedRunables = this.focusedRunables_(); const focusedRunables = this.focusedRunables_();
const config = this.getConfig_(); const config = this.getConfig_();
@@ -51,7 +50,7 @@ getJasmineRequireObj().Runner = function(j$) {
const order = new j$.Order({ const order = new j$.Order({
random: config.random, random: config.random,
seed: config.seed seed: j$.isNumber_(config.seed) ? config.seed + '' : config.seed
}); });
const processor = new j$.TreeProcessor({ const processor = new j$.TreeProcessor({
@@ -76,7 +75,7 @@ getJasmineRequireObj().Runner = function(j$) {
nodeStart: (suite, next) => { nodeStart: (suite, next) => {
this.currentlyExecutingSuites_.push(suite); this.currentlyExecutingSuites_.push(suite);
this.runableResources_.initForRunable(suite.id, suite.parentSuite.id); this.runableResources_.initForRunable(suite.id, suite.parentSuite.id);
this.reporter_.suiteStarted(suite.result, next); this.reporter_.suiteStarted(suite.result).then(next);
suite.startTimer(); suite.startTimer();
}, },
nodeComplete: (suite, result, next) => { nodeComplete: (suite, result, next) => {
@@ -114,106 +113,97 @@ getJasmineRequireObj().Runner = function(j$) {
); );
} }
return this.execute2_(runablesToRun, order, processor);
}
async execute2_(runablesToRun, order, processor) {
const totalSpecsDefined = this.totalSpecsDefined_();
this.runableResources_.initForRunable(this.topSuite_.id); this.runableResources_.initForRunable(this.topSuite_.id);
const jasmineTimer = new j$.Timer(); const jasmineTimer = new j$.Timer();
jasmineTimer.start(); jasmineTimer.start();
return new Promise(resolve => { /**
/** * Information passed to the {@link Reporter#jasmineStarted} event.
* Information passed to the {@link Reporter#jasmineStarted} event. * @typedef JasmineStartedInfo
* @typedef JasmineStartedInfo * @property {Int} totalSpecsDefined - The total number of specs defined in this suite.
* @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.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite. * @since 2.0.0
* @since 2.0.0 */
*/ await this.reporter_.jasmineStarted({
this.reporter_.jasmineStarted( totalSpecsDefined,
{ order: order
totalSpecsDefined,
order: order
},
() => {
this.currentlyExecutingSuites_.push(this.topSuite_);
processor.execute(() => {
(async () => {
if (this.topSuite_.hadBeforeAllFailure) {
await this.reportChildrenOfBeforeAllFailure_(this.topSuite_);
}
this.runableResources_.clearForRunable(this.topSuite_.id);
this.currentlyExecutingSuites_.pop();
let overallStatus, incompleteReason;
if (
this.hasFailures ||
this.topSuite_.result.failedExpectations.length > 0
) {
overallStatus = 'failed';
} else if (focusedRunables.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.
* @since 2.4.0
*/
const jasmineDoneInfo = {
overallStatus: overallStatus,
totalTime: jasmineTimer.elapsed(),
incompleteReason: incompleteReason,
order: order,
failedExpectations: this.topSuite_.result.failedExpectations,
deprecationWarnings: this.topSuite_.result.deprecationWarnings
};
this.topSuite_.reportedDone = true;
this.reporter_.jasmineDone(jasmineDoneInfo, function() {
resolve(jasmineDoneInfo);
});
})();
});
}
);
}); });
this.currentlyExecutingSuites_.push(this.topSuite_);
await processor.execute();
if (this.topSuite_.hadBeforeAllFailure) {
await this.reportChildrenOfBeforeAllFailure_(this.topSuite_);
}
this.runableResources_.clearForRunable(this.topSuite_.id);
this.currentlyExecutingSuites_.pop();
let overallStatus, incompleteReason;
if (
this.hasFailures ||
this.topSuite_.result.failedExpectations.length > 0
) {
overallStatus = 'failed';
} else if (this.focusedRunables_().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.
* @since 2.4.0
*/
const jasmineDoneInfo = {
overallStatus: overallStatus,
totalTime: jasmineTimer.elapsed(),
incompleteReason: incompleteReason,
order: order,
failedExpectations: this.topSuite_.result.failedExpectations,
deprecationWarnings: this.topSuite_.result.deprecationWarnings
};
this.topSuite_.reportedDone = true;
await this.reporter_.jasmineDone(jasmineDoneInfo);
return jasmineDoneInfo;
} }
reportSuiteDone_(suite, result, next) { reportSuiteDone_(suite, result, next) {
suite.reportedDone = true; suite.reportedDone = true;
this.reporter_.suiteDone(result, next); this.reporter_.suiteDone(result).then(next);
} }
async reportChildrenOfBeforeAllFailure_(suite) { async reportChildrenOfBeforeAllFailure_(suite) {
for (const child of suite.children) { for (const child of suite.children) {
if (child instanceof j$.Suite) { if (child instanceof j$.Suite) {
await new Promise(resolve => { await this.reporter_.suiteStarted(child.result);
this.reporter_.suiteStarted(child.result, resolve);
});
await this.reportChildrenOfBeforeAllFailure_(child); await this.reportChildrenOfBeforeAllFailure_(child);
// Marking the suite passed is consistent with how suites that // Marking the suite passed is consistent with how suites that
// contain failed specs but no suite-level failures are reported. // contain failed specs but no suite-level failures are reported.
child.result.status = 'passed'; child.result.status = 'passed';
await new Promise(resolve => { await this.reporter_.suiteDone(child.result);
this.reporter_.suiteDone(child.result, resolve);
});
} else { } else {
/* a spec */ /* a spec */
await new Promise(resolve => { await this.reporter_.specStarted(child.result);
this.reporter_.specStarted(child.result, resolve);
});
child.addExpectationResult( child.addExpectationResult(
false, false,

View File

@@ -39,7 +39,7 @@ getJasmineRequireObj().Spec = function(j$) {
/** /**
* @typedef SpecResult * @typedef SpecResult
* @property {Int} id - The unique id of this spec. * @property {String} id - The unique id of this spec.
* @property {String} description - The description passed to the {@link it} that created this spec. * @property {String} description - The description passed to the {@link it} that created this spec.
* @property {String} fullName - The full description including all ancestors of this spec. * @property {String} fullName - The full description including all ancestors of this spec.
* @property {Expectation[]} failedExpectations - The list of expectations that failed during execution of this spec. * @property {Expectation[]} failedExpectations - The list of expectations that failed during execution of this spec.

View File

@@ -106,7 +106,7 @@ getJasmineRequireObj().Suite = function(j$) {
Suite.prototype.reset = function() { Suite.prototype.reset = function() {
/** /**
* @typedef SuiteResult * @typedef SuiteResult
* @property {Int} id - The unique id of this suite. * @property {String} id - The unique id of this suite.
* @property {String} description - The description text passed to the {@link describe} that made this suite. * @property {String} description - The description text passed to the {@link describe} that made this suite.
* @property {String} fullName - The full description including all ancestors of this suite. * @property {String} fullName - The full description including all ancestors of this suite.
* @property {Expectation[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite. * @property {Expectation[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.

View File

@@ -32,11 +32,6 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
suite.exclude(); suite.exclude();
} }
this.addSpecsToSuite_(suite, definitionFn); this.addSpecsToSuite_(suite, definitionFn);
if (suite.parentSuite && !suite.children.length) {
throw new Error(
`describe with no children (describe() or it()): ${suite.getFullName()}`
);
}
return suite; return suite;
} }
@@ -183,11 +178,19 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
const parentSuite = this.currentDeclarationSuite_; const parentSuite = this.currentDeclarationSuite_;
parentSuite.addChild(suite); parentSuite.addChild(suite);
this.currentDeclarationSuite_ = suite; this.currentDeclarationSuite_ = suite;
let threw = false;
try { try {
definitionFn(); definitionFn();
} catch (e) { } catch (e) {
suite.handleException(e); suite.handleException(e);
threw = true;
}
if (suite.parentSuite && !suite.children.length && !threw) {
throw new Error(
`describe with no children (describe() or it()): ${suite.getFullName()}`
);
} }
this.currentDeclarationSuite_ = parentSuite; this.currentDeclarationSuite_ = parentSuite;

View File

@@ -27,7 +27,7 @@ getJasmineRequireObj().TreeProcessor = function() {
return stats; return stats;
}; };
this.execute = function(done) { this.execute = async function() {
if (!processed) { if (!processed) {
this.processTree(); this.processTree();
} }
@@ -38,16 +38,18 @@ getJasmineRequireObj().TreeProcessor = function() {
const childFns = wrapChildren(tree, 0); const childFns = wrapChildren(tree, 0);
queueRunnerFactory({ await new Promise(function(resolve) {
queueableFns: childFns, queueRunnerFactory({
userContext: tree.sharedUserContext(), queueableFns: childFns,
onException: function() { userContext: tree.sharedUserContext(),
tree.handleException.apply(tree, arguments); onException: function() {
}, tree.handleException.apply(tree, arguments);
onComplete: done, },
onMultipleDone: tree.onMultipleDone onComplete: resolve,
? tree.onMultipleDone.bind(tree) onMultipleDone: tree.onMultipleDone
: null ? tree.onMultipleDone.bind(tree)
: null
});
}); });
}; };

View File

@@ -652,7 +652,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
* }; * };
* } * }
* *
* var actual = { * const actual = {
* n: 2, * n: 2,
* otherFields: "don't care" * otherFields: "don't care"
* }; * };

View File

@@ -6,7 +6,7 @@ getJasmineRequireObj().toHaveClass = function(j$) {
* @since 3.0.0 * @since 3.0.0
* @param {Object} expected - The class name to test for * @param {Object} expected - The class name to test for
* @example * @example
* var el = document.createElement('div'); * const el = document.createElement('div');
* el.className = 'foo bar baz'; * el.className = 'foo bar baz';
* expect(el).toHaveClass('bar'); * expect(el).toHaveClass('bar');
*/ */