Compare commits

...

32 Commits

Author SHA1 Message Date
Steve Gravrock
18d4d38655 Fix version number in 5.12.1 release notes 2025-10-29 19:55:00 -07:00
Steve Gravrock
53e9bc68d2 Bump version to 5.12.1 2025-10-29 19:53:34 -07:00
Steve Gravrock
2be50e1b87 Merge branch 'bonkevin-fix-custom-matcher'
* Fixes custom matchers in top-level specs
* Merges #2088 from @bonkevin
2025-10-29 19:44:06 -07:00
bonkevin
27a1257b6d fix: unavailable custom matchers on top-it 2025-10-29 13:04:10 -04:00
Steve Gravrock
9a67c4e24d Copy 6.0.0-alpha.1 release notes from branch 2025-10-18 13:05:29 -07:00
Steve Gravrock
7ba53b25f7 Bump version to 5.12.0 2025-10-05 12:02:56 -07:00
Steve Gravrock
dbc1f9244e Revert "Clicking a link in the HTML reporter does exact filtering"
This change broke spec filtering in Karma by changing the format of the
`spec` query parameter. Although karma-jasmine-html-reporter uses
jasmine-core's HtmlSpecFilter, karm-jasmine provides its own spec filter
that interprets the query parameters itself.

This feature may be reintroduced in 6.0 as a breaking change.

This reverts commit 8309416cb2.
2025-10-05 09:59:36 -07:00
Steve Gravrock
489b83c61b Revert "Move knowledge of query parameters out of boot1.js"
This reverts commit 6715f24fd0.
2025-10-05 09:54:25 -07:00
Steve Gravrock
c590095662 Copy 6.0.0-alpha.0 release notes from branch 2025-09-29 19:53:25 -07:00
Steve Gravrock
0688db88e9 Bump version to 5.11.0 2025-09-26 16:53:15 -07:00
Steve Gravrock
124effe04b API reference docs for QueryString 2025-09-17 20:22:47 -07:00
Steve Gravrock
418e9a7728 Convert QueryString to an ES6 class 2025-09-17 18:35:01 -07:00
Steve Gravrock
6715f24fd0 Move knowledge of query parameters out of boot1.js 2025-09-17 18:23:45 -07:00
Steve Gravrock
fa481b2bd1 API reference docs for HTML reporter and spec filters 2025-09-17 07:30:34 -07:00
Steve Gravrock
8309416cb2 Clicking a link in the HTML reporter does exact filtering
This feature requires an update to boot1.js, as shown in this commit.
Users with an older boot1.js will get the older inexact filtering.
2025-09-17 07:30:20 -07:00
Steve Gravrock
4ccc7bf3ac Document the order property of jasmineStarted and jasmineDone 2025-09-15 18:38:09 -07:00
Steve Gravrock
cca6b2aa07 Adopt forbidDuplicateNames: true in jasmine-core's own tests 2025-09-15 18:38:09 -07:00
Steve Gravrock
78940aa0fb Drop support for Safari 15
Safari 15:

* Lacks structuredClone, which is starting to become useful
* Has stack trace quirks that are not well understood, not properly handled
  by Jasmine, and can cause problems in Jasmine's own tests
* Is not widely used
* Does not run on any OS that still receives security updates
2025-09-15 18:37:59 -07:00
Steve Gravrock
3d8396da0a More precisely characterize suite/spec reporting 2025-09-13 10:10:42 -07:00
Steve Gravrock
0bf9aff195 Extract configuration out of Env 2025-09-07 15:53:24 -07:00
Steve Gravrock
55b2e8846f Disambiguate options params in Env 2025-09-06 13:04:32 -07:00
Steve Gravrock
3493519c9f Fixed global error handling when the env is executed repeatedly 2025-09-06 10:32:56 -07:00
Steve Gravrock
62b5698a99 Clean up TreeRunner onComplete callback 2025-09-06 09:42:39 -07:00
Steve Gravrock
98849882a2 rm TODO comment about integrating detectLateRejectionHandling with clearStack
In theory, resetting clearStack's inline call count every time late
rejection handling does a setTimeout should reduce the performance penalty
in some environments.  In practice, it doesn't:

* In Chrome and FF, late rejection handling has no measurable penalty.
* In Safari, resetting the inline call count actually slows things down
  considerably(!).
* In Node, clearStack doesn't use setTimeout so there is no benefit.
2025-09-06 08:32:37 -07:00
Steve Gravrock
6665c4e123 Don't remove before and after fns from the top suite 2025-09-02 10:23:44 -07:00
Steve Gravrock
3698f6fb5d Support detectLateRejectionHandling in beforeAll and afterAll 2025-09-02 09:58:46 -07:00
Steve Gravrock
60f34ec087 Unify top suite and regular suite execution 2025-09-02 08:05:30 -07:00
Steve Gravrock
91bd3f8201 Optionally test in a subset of browsers locally
When run with --not-actually-all, scripts/run-all-browsers skips all
but the oldest and newest supported Firefox and Safari versions. This
provides a faster but still quite reliable mergeability check.

CI still tests against all supported browsers.
2025-09-01 08:58:32 -07:00
Steve Gravrock
ca4fbcbccb Clarify what's currently treated as private vs internal in Suite and Spec 2025-09-01 08:58:32 -07:00
Steve Gravrock
e1532be726 Convert Suite and SuiteMetadata to ES6 classes 2025-09-01 08:58:32 -07:00
Steve Gravrock
54465f6f6a Convert Spec to an es6 class 2025-09-01 08:58:32 -07:00
Steve Gravrock
fa9939ae94 Improve description of detectLateRejectionHandling feature in release notes 2025-08-30 14:00:00 -07:00
32 changed files with 3275 additions and 2171 deletions

View File

@@ -30,7 +30,7 @@ Microsoft Edge) as well as Node.
| Environment | Supported versions |
|-------------------|----------------------------------|
| Node | 18.20.5+*, 20, 22, 24 |
| Safari | 15*, 16*, 17* |
| Safari | 16*, 17* |
| Chrome | Evergreen |
| Firefox | Evergreen, 102*, 115*, 128*, 140 |
| Edge | Evergreen |

View File

@@ -81,6 +81,13 @@ jasmineRequire.HtmlReporter = function(j$) {
}
};
/**
* @class HtmlReporter
* @classdesc Displays results and allows re-running individual specs and suites.
* @implements {Reporter}
* @param options Options object. See lib/jasmine-core/boot1.js for details.
* @since 1.2.0
*/
function HtmlReporter(options) {
function config() {
return (options.env && options.env.configuration()) || {};
@@ -98,6 +105,11 @@ jasmineRequire.HtmlReporter = function(j$) {
const deprecationWarnings = [];
const failures = [];
/**
* Initializes the reporter. Should be called before {@link Env#execute}.
* @function
* @name HtmlReporter#initialize
*/
this.initialize = function() {
clearPrior();
htmlReporterMain = createDom(
@@ -881,6 +893,8 @@ jasmineRequire.HtmlReporter = function(j$) {
};
jasmineRequire.HtmlSpecFilter = function() {
// Legacy HTML spec filter, preserved for backward compatibility with
// boot files that predate HtmlExactSpecFilterV2
function HtmlSpecFilter(options) {
const filterString =
options &&
@@ -888,6 +902,13 @@ jasmineRequire.HtmlSpecFilter = function() {
options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
const filterPattern = new RegExp(filterString);
/**
* Determines whether the spec with the specified name should be executed.
* @name HtmlSpecFilter#matches
* @function
* @param {string} specName The full name of the spec
* @returns {boolean}
*/
this.matches = function(specName) {
return filterPattern.test(specName);
};
@@ -921,38 +942,57 @@ jasmineRequire.ResultsNode = function() {
};
jasmineRequire.QueryString = function() {
function QueryString(options) {
this.navigateWithNewParam = function(key, value) {
options.getWindowLocation().search = this.fullStringWithNewParam(
/**
* Reads and manipulates the query string.
* @since 2.0.0
*/
class QueryString {
#getWindowLocation;
/**
* @param options Object with a getWindowLocation property, which should be
* a function returning the current value of window.location.
*/
constructor(options) {
this.#getWindowLocation = options.getWindowLocation;
}
/**
* Sets the specified query parameter and navigates to the resulting URL.
* @param {string} key
* @param {string} value
*/
navigateWithNewParam(key, value) {
this.#getWindowLocation().search = this.fullStringWithNewParam(
key,
value
);
};
this.fullStringWithNewParam = function(key, value) {
const paramMap = queryStringToParamMap();
paramMap[key] = value;
return toQueryString(paramMap);
};
this.getParam = function(key) {
return queryStringToParamMap()[key];
};
return this;
function toQueryString(paramMap) {
const qStrPairs = [];
for (const prop in paramMap) {
qStrPairs.push(
encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])
);
}
return '?' + qStrPairs.join('&');
}
function queryStringToParamMap() {
const paramStr = options.getWindowLocation().search.substring(1);
/**
* Returns a new URL based on the current location, with the specified
* query parameter set.
* @param {string} key
* @param {string} value
* @return {string}
*/
fullStringWithNewParam(key, value) {
const paramMap = this.#queryStringToParamMap();
paramMap[key] = value;
return toQueryString(paramMap);
}
/**
* Gets the value of the specified query parameter.
* @param {string} key
* @return {string}
*/
getParam(key) {
return this.#queryStringToParamMap()[key];
}
#queryStringToParamMap() {
const paramStr = this.#getWindowLocation().search.substring(1);
let params = [];
const paramMap = {};
@@ -972,5 +1012,15 @@ jasmineRequire.QueryString = function() {
}
}
function toQueryString(paramMap) {
const qStrPairs = [];
for (const prop in paramMap) {
qStrPairs.push(
encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])
);
}
return '?' + qStrPairs.join('&');
}
return QueryString;
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "jasmine-core",
"license": "MIT",
"version": "5.10.0",
"version": "5.12.1",
"repository": {
"type": "git",
"url": "https://github.com/jasmine/jasmine.git"
@@ -52,7 +52,7 @@
"sass": "^1.58.3"
},
"browserslist": [
"Safari >= 15",
"Safari >= 16",
"Firefox >= 102",
"last 2 Chrome versions",
"last 2 Edge versions"

View File

@@ -2,7 +2,8 @@
## New Features
* Optionally detect late promise rejections and don't report them as errors
* Optionally detect promise rejections that are handled after an initial
unhandled promise rejection event and don't report them as errors.
This is off by default because it comes with a performance cost. It can be
enabled by setting the `detectLateRejectionHandling` config property to true.
* Add `getSpecProperty` to retrieve data that was set with `setSpecProperty`.

66
release_notes/5.11.0.md Normal file
View File

@@ -0,0 +1,66 @@
# Jasmine Core 5.11.0 Release Notes
## New features
* `detectLateRejectionHandling` works in `beforeAll` and `afterAll` as well as
in specs.
* Clicking a link in the HTML reporter does exact filtering rather than a
substring match.
If you use jasmine-browser-runner or load boot1.js directly from jasmine-core,
you'll get the new filtering behavior automatically. Otherwise, it requires
several changes to boot1.js:
1. Add `queryString` to the options object passed to `HtmlReporter`, and
remove `filterSpecs`.
2. Instantiate a `jasmine.HtmlExactSpecFilter` instead of a
`jasmine.HtmlSpecFilter`.
2. Change the body of `config.specFilter` from
`return specFilter.matches(spec.getFullName());` to
`return specFilter.matches(spec)`
For a working example, see lib/jasmine-core/boot1.js in this package.
Old boot1.js files will still work, but you'll get the old filtering behavior.
## Bug fixes
* Fixed global error handling when the env is executed repeatedly
## Changes to supported environments
* Safari 15 is no longer supported.
## Documentation improvements
* Added API reference docs for classes used in browser boot files
* Documented the order properties of `jasmineStarted` and `jasmineDone` events
## Internal improvements
* Unified top suite and regular suite execution
* Converted `Spec`, `Suite`, and `QueryString` to ES6 classes
* Extracted configuration out of `Env`
* Updated tests to characterize suite/spec reporting more completely
* Adopted `forbidDuplicateNames: true` in jasmine-core's own tests
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------------------|
| Node | 18.20.5**, 20, 22, 24 |
| Safari | 16**, 17** |
| Chrome | 140* |
| Firefox | 102**, 115**, 128**, 140, 143* |
| Edge | 140* |
\* Evergreen browser. Each version of Jasmine is tested against the latest
version available at release time.<br>
\** Supported on a best-effort basis. Support for these versions may be dropped
if it becomes impractical, and bugs affecting only these versions may not be
treated as release blockers.
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

27
release_notes/5.12.0.md Normal file
View File

@@ -0,0 +1,27 @@
# Jasmine Core 5.12.0 Release Notes
This release reverts the exact spec filtering feature introduced in 5.11.0,
which broke spec filtering in Karma.
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------|--------------------------------|
| Node | 18.20.5**, 20, 22, 24 |
| Safari | 16**, 17** |
| Chrome | 141* |
| Firefox | 102**, 115**, 128**, 140, 143* |
| Edge | 140* |
\* Evergreen browser. Each version of Jasmine is tested against the latest
version available at release time.<br>
\** Supported on a best-effort basis. Support for these versions may be dropped
if it becomes impractical, and bugs affecting only these versions may not be
treated as release blockers.
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

29
release_notes/5.12.1.md Normal file
View File

@@ -0,0 +1,29 @@
Jasmine Core 5.12.1 Release Notes
## Bug fixes
* Fix custom matchers in top-level specs
* Merges [#2088](https://github.com/jasmine/jasmine/pull/2088) from @bonkevin
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------|--------------------------------|
| Node | 18.20.5**, 20, 22, 24 |
| Safari | 16**, 17** |
| Chrome | 141* |
| Firefox | 102**, 115**, 128**, 140, 144* |
| Edge | 141* |
\* Evergreen browser. Each version of Jasmine is tested against the latest
version available at release time.<br>
\** Supported on a best-effort basis. Support for these versions may be dropped
if it becomes impractical, and bugs affecting only these versions may not be
treated as release blockers.
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

View File

@@ -0,0 +1,100 @@
# Jasmine Core 6.0.0-alpha.0 Release Notes
This is a pre-release, intended to offer a preview of breaking changes and to
solicit feedback.
## A Note About Pre-Release Compatibility
There may be additional breaking changes in future 6.0 pre-releases or in the
final 6.0 release. That's allowed by the semver specification, but users are
sometimes unpleasantly surprised by it.
NPM's implementation of carat version ranges assumes that subsequent
pre-releases and final releases are fully compatible with earlier pre-releases.
If your package.json contains `"jasmine-core": "^6.0.0-alpha.0`,
NPM might install any later 6.x version even though there is no guarantee of
compatibility. If that isn't ok, you should specify an exact pre-release version:
`"jasmine-core": "6.0.0-alpha.0`.
## Changes to supported environments
* Node 18 is no longer supported.
## Breaking changes
### General
* Private APIs have been removed from the `jasmine` namespace.
The purpose of this change is to reduce the risk of users inadvertently
depending on private APIs. Anything that's not covered by
[the documentation](https://jasmine.github.io/pages/docs_home.html) remains
private regardless of namespacing. Private APIs may be changed or removed in
any release. This change is being made in a major release as a courtesy to users of
libraries that depend on private APIs.
* Arguments to `setSpecProperty`/`setSuiteProperty` must be both
structured-cloneable and JSON-serializable.
* Mock clock timing functions cannot be spied on. Previously this "worked" but
prevented the mock clock from uninstalling itself.
* The default value of the `forbidDuplicateNames` config option has been
changed to true.
* The mock clock no longer supports the eval forms of `setTimeout` and
`setInterval`.
* If an execution order is passed to `Env#execute`, it must not enter any suite
more than once.
* The argument passed to spec filters is a
[spec metadata](https://jasmine.github.io/api/6.0.0-alpha.0/Spec.html)
instance, not the internal spec object.
### Changes that affect reporters
This release includes changes that are intended to streamline and clarify the
reporter interface, prevent sharing of mutable state, and prevent bugs involving
non-serializable objects. These changes should be compatible with most existing
reporters but could break reporters that manage their internal state in unusual
ways. Please [open an issue](https://github.com/jasmine/jasmine/issues/new?template=bug_report.yml)
if you find a published reporter package that works with jasmine-core 5.x but
not with this release.
* Irrelevant properties such as `status` and `failedExpectations` are omitted
from [the event passed to specStarted](https://jasmine.github.io/api/6.0.0-alpha.0/global.html#SpecStartedEvent).
* Reporter events are deep-cloned before being passed to each reporter. This
protects reporters against later mutation by jasmine-core or other reporters.
* The `expected` and `actual` properties of
[passed and failed expectations](https://jasmine.github.io/api/6.0.0-alpha.0/global.html#ExpectationResult)
have been removed.
* The [order](https://jasmine.github.io/api/6.0.0-alpha.0/global.html#Order)
property of the`jasmineStarted` and `jasmineDone` reporter events no longer
includes undocumented properties.
### Changes to Node boot functions
* [boot](https://jasmine.github.io/api/6.0.0-alpha.0/module-jasmine-core.html#.boot)
defaults to creating a new core instance each time it's called. This restores
the pre-5.0 default behavior.
* [noGlobals](https://jasmine.github.io/api/6.0.0-alpha.0/module-jasmine-core.html#.noGlobals)
no longer takes a parameter. It always returns the same object when called
repeatedly.
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------------------|
| Node | 20, 22, 24 |
| Safari | 16**, 17** |
| Chrome | 140* |
| Firefox | 102**, 115**, 128**, 140, 143* |
| Edge | 140* |
\* Evergreen browser. Each version of Jasmine is tested against the latest
version available at release time.<br>
\** Supported on a best-effort basis. Support for these versions may be dropped
if it becomes impractical, and bugs affecting only these versions may not be
treated as release blockers.
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

View File

@@ -0,0 +1,116 @@
# Jasmine Core 6.0.0-alpha.1 Release Notes
This is a pre-release, intended to offer a preview of breaking changes and to
solicit feedback.
## A Note About Pre-Release Compatibility
There may be additional breaking changes in future 6.0 pre-releases or in the
final 6.0 release. That's allowed by the semver specification, but users are
sometimes unpleasantly surprised by it.
NPM's implementation of carat version ranges assumes that subsequent
pre-releases and final releases are fully compatible with earlier pre-releases.
If your package.json contains `"jasmine-core": "^6.0.0-alpha.1`,
NPM might install any later 6.x version even though there is no guarantee of
compatibility. If that isn't ok, you should specify an exact pre-release version:
`"jasmine-core": "6.0.0-alpha.1`.
## Breaking changes
### Changes that affect reporters
* Irrelevant properties such as `status` and `failedExpectations` are omitted
from [the event passed to suiteStarted](https://jasmine.github.io/api/6.0.0-alpha.1/global.html#SuiteStartedEvent).
This change should be compatible with most existing reporters but could break
reporters that manage their internal state in unusual ways. Please
[open an issue](https://github.com/jasmine/jasmine/issues/new?template=bug_report.yml)
if you find a published reporter package that works with jasmine-core 5.x but
not with this release.
### Changes that affect browser boot files
* The `createElement` and `createTextNode` options of `HtmlReporter` are ignored.
`HtmlReporter` now unconditionally uses `document.createElement` and
`document.createTextNode`.
### Changes that affect spec writing
* HTML reporters cache configuration throughout each run. Configuration changes
made while specs are running will not affect reporter behavior.
* Global error spies always receive a single argument. Previously, the browser
error event was passed as the second argument.
## New features
* A new `HtmlReporterV2` with several improvements over the old `HtmlReporter`:
* Clicking a spec/suite link does exact filtering rather than a substring
match.
* The old dots are replaced with a progress bar. This improves usability with
large suites and fixes an accessibility problem.
* Details of failed specs are displayed as soon as each spec finishes.
* Initialization and wire-up in boot files are much simpler.
If you're using jasmine-browser-runner or copying boot1.js from the standalone
distribution, you'll automatically get the new reporter. If you maintain your
own boot files, you'll get the old reporter unless you update your boot1.js
to match the one that's in this package.
The new reporter produces `spec` query string parameters that are different
from those created by the old reporter. If you use non-Jasmine software that
interprets the `spec` parameter, such as karma-jasmine, you may not be able to
adopt `HtmlReporterV2` unlesss it's updated.
* Use `globalThis` to determine the global object during initialization
This makes jasmine-core more tolerant of buggy bundlers or loaders that
cause `this` to be undefined in the global context.
## Deprecations
* Warn if jasmine-core is loaded as an ES module in a browser.
This is an untested and unsupported configuration that has been known to cause
problems in the past.
* Deprecated `HtmlReporter` and `HtmlSpecFilter` in favor of `HtmlReporterV2`.
## Documentation improvements
* Improved API reference documentation for APIs that are used from browser boot
files.
## Internal improvements
* Removed remaining code that supported suite re-entry.
* Encapsulated suite and spec result and status management.
* Adopted strict mode throughout the codebase.
* Decomposed `HtmlReporter` into components and converted to ES6 classes.
* Made global error handling more uniform between browsers and Node.
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------------------|
| Node | 20, 22, 24 |
| Safari | 16**, 17** |
| Chrome | 141* |
| Firefox | 102**, 115**, 128**, 140, 143* |
| Edge | 141* |
\* Evergreen browser. Each version of Jasmine is tested against the latest
version available at release time.<br>
\** Supported on a best-effort basis. Support for these versions may be dropped
if it becomes impractical, and bugs affecting only these versions may not be
treated as release blockers.
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

View File

@@ -28,13 +28,20 @@ failfile=`mktemp -t jasmine-results.XXXXXX` || exit 1
run_browser chrome latest
run_browser firefox latest
run_browser firefox 140
run_browser firefox 128
run_browser firefox 115
if [ "$1" = "--not-actually-all" ]; then
echo "SKIPPED: firefox 140" >> "$passfile"
echo "SKIPPED: firefox 128" >> "$passfile"
echo "SKIPPED: firefox 115" >> "$passfile"
else
run_browser firefox 140
run_browser firefox 128
run_browser firefox 115
fi
run_browser firefox 102
run_browser safari 17
run_browser safari 16
run_browser safari 15
run_browser MicrosoftEdge latest
echo

View File

@@ -242,7 +242,7 @@ describe('Clock', function() {
expect(fakeGlobal.clearInterval).toBe(replacedClearInterval);
});
it('replaces the global timer functions on uninstall', function() {
it('restores the global timer functions on uninstall', function() {
const fakeSetTimeout = jasmine.createSpy('global setTimeout'),
fakeClearTimeout = jasmine.createSpy('global clearTimeout'),
fakeSetInterval = jasmine.createSpy('global setInterval'),
@@ -408,211 +408,219 @@ describe('Clock', function() {
expect(delayedFunctionScheduler.scheduleFunction).not.toHaveBeenCalled();
});
it('schedules the delayed function (via setTimeout) with the fake timer', function() {
const fakeSetTimeout = jasmine.createSpy('setTimeout'),
scheduleFunction = jasmine.createSpy('scheduleFunction'),
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
fakeGlobal = { setTimeout: fakeSetTimeout },
delayedFn = jasmine.createSpy('delayedFn'),
mockDate = {
install: function() {},
tick: function() {},
uninstall: function() {}
},
clock = new jasmineUnderTest.Clock(
fakeGlobal,
function() {
return delayedFunctionScheduler;
describe('setTimeout', function() {
it('schedules the delayed function with the fake timer', function() {
const fakeSetTimeout = jasmine.createSpy('setTimeout'),
scheduleFunction = jasmine.createSpy('scheduleFunction'),
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
fakeGlobal = { setTimeout: fakeSetTimeout },
delayedFn = jasmine.createSpy('delayedFn'),
mockDate = {
install: function() {},
tick: function() {},
uninstall: function() {}
},
mockDate
),
timeout = new clock.FakeTimeout();
clock = new jasmineUnderTest.Clock(
fakeGlobal,
function() {
return delayedFunctionScheduler;
},
mockDate
),
timeout = new clock.FakeTimeout();
clock.install();
clock.setTimeout(delayedFn, 0, 'a', 'b');
clock.install();
clock.setTimeout(delayedFn, 0, 'a', 'b');
expect(fakeSetTimeout).not.toHaveBeenCalled();
expect(fakeSetTimeout).not.toHaveBeenCalled();
if (!NODE_JS) {
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
delayedFn,
0,
['a', 'b']
);
} else {
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
delayedFn,
0,
['a', 'b'],
false,
timeout
);
}
if (!NODE_JS) {
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
delayedFn,
0,
['a', 'b']
);
} else {
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
delayedFn,
0,
['a', 'b'],
false,
timeout
);
}
});
it('returns an id for the delayed function', function() {
const fakeSetTimeout = jasmine.createSpy('setTimeout'),
scheduleId = 123,
scheduleFunction = jasmine
.createSpy('scheduleFunction')
.and.returnValue(scheduleId),
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
fakeGlobal = { setTimeout: fakeSetTimeout },
delayedFn = jasmine.createSpy('delayedFn'),
mockDate = {
install: function() {},
tick: function() {},
uninstall: function() {}
},
clock = new jasmineUnderTest.Clock(
fakeGlobal,
function() {
return delayedFunctionScheduler;
},
mockDate
);
clock.install();
const timeout = clock.setTimeout(delayedFn, 0);
if (!NODE_JS) {
expect(timeout).toEqual(123);
} else {
expect(timeout.constructor.name).toEqual('FakeTimeout');
}
});
});
it('returns an id for the delayed function', function() {
const fakeSetTimeout = jasmine.createSpy('setTimeout'),
scheduleId = 123,
scheduleFunction = jasmine
.createSpy('scheduleFunction')
.and.returnValue(scheduleId),
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
fakeGlobal = { setTimeout: fakeSetTimeout },
delayedFn = jasmine.createSpy('delayedFn'),
mockDate = {
install: function() {},
tick: function() {},
uninstall: function() {}
},
clock = new jasmineUnderTest.Clock(
fakeGlobal,
function() {
return delayedFunctionScheduler;
describe('clearTimeout', function() {
it('clears the scheduled function with the scheduler', function() {
const fakeClearTimeout = jasmine.createSpy('clearTimeout'),
delayedFunctionScheduler = jasmine.createSpyObj(
'delayedFunctionScheduler',
['removeFunctionWithId']
),
fakeGlobal = { setTimeout: fakeClearTimeout },
mockDate = {
install: function() {},
tick: function() {},
uninstall: function() {}
},
mockDate
);
clock = new jasmineUnderTest.Clock(
fakeGlobal,
function() {
return delayedFunctionScheduler;
},
mockDate
);
clock.install();
const timeout = clock.setTimeout(delayedFn, 0);
clock.install();
clock.clearTimeout(123);
if (!NODE_JS) {
expect(timeout).toEqual(123);
} else {
expect(timeout.constructor.name).toEqual('FakeTimeout');
}
expect(fakeClearTimeout).not.toHaveBeenCalled();
expect(
delayedFunctionScheduler.removeFunctionWithId
).toHaveBeenCalledWith(123);
});
});
it('clears the scheduled function with the scheduler', function() {
const fakeClearTimeout = jasmine.createSpy('clearTimeout'),
delayedFunctionScheduler = jasmine.createSpyObj(
'delayedFunctionScheduler',
['removeFunctionWithId']
),
fakeGlobal = { setTimeout: fakeClearTimeout },
mockDate = {
install: function() {},
tick: function() {},
uninstall: function() {}
},
clock = new jasmineUnderTest.Clock(
fakeGlobal,
function() {
return delayedFunctionScheduler;
describe('setInterval', function() {
it('schedules the delayed function with the fake timer', function() {
const fakeSetInterval = jasmine.createSpy('setInterval'),
scheduleFunction = jasmine.createSpy('scheduleFunction'),
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
fakeGlobal = { setInterval: fakeSetInterval },
delayedFn = jasmine.createSpy('delayedFn'),
mockDate = {
install: function() {},
tick: function() {},
uninstall: function() {}
},
mockDate
);
clock = new jasmineUnderTest.Clock(
fakeGlobal,
function() {
return delayedFunctionScheduler;
},
mockDate
),
timeout = new clock.FakeTimeout();
clock.install();
clock.clearTimeout(123);
clock.install();
clock.setInterval(delayedFn, 0, 'a', 'b');
expect(fakeClearTimeout).not.toHaveBeenCalled();
expect(delayedFunctionScheduler.removeFunctionWithId).toHaveBeenCalledWith(
123
);
expect(fakeSetInterval).not.toHaveBeenCalled();
if (!NODE_JS) {
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
delayedFn,
0,
['a', 'b'],
true
);
} else {
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
delayedFn,
0,
['a', 'b'],
true,
timeout
);
}
});
it('returns an id for the delayed function', function() {
const fakeSetInterval = jasmine.createSpy('setInterval'),
scheduleId = 123,
scheduleFunction = jasmine
.createSpy('scheduleFunction')
.and.returnValue(scheduleId),
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
fakeGlobal = { setInterval: fakeSetInterval },
delayedFn = jasmine.createSpy('delayedFn'),
mockDate = {
install: function() {},
tick: function() {},
uninstall: function() {}
},
clock = new jasmineUnderTest.Clock(
fakeGlobal,
function() {
return delayedFunctionScheduler;
},
mockDate
);
clock.install();
const interval = clock.setInterval(delayedFn, 0);
if (!NODE_JS) {
expect(interval).toEqual(123);
} else {
expect(interval.constructor.name).toEqual('FakeTimeout');
}
});
});
it('schedules the delayed function with the fake timer', function() {
const fakeSetInterval = jasmine.createSpy('setInterval'),
scheduleFunction = jasmine.createSpy('scheduleFunction'),
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
fakeGlobal = { setInterval: fakeSetInterval },
delayedFn = jasmine.createSpy('delayedFn'),
mockDate = {
install: function() {},
tick: function() {},
uninstall: function() {}
},
clock = new jasmineUnderTest.Clock(
fakeGlobal,
function() {
return delayedFunctionScheduler;
describe('clearInterval', function() {
it('clears the scheduled function with the scheduler', function() {
const clearInterval = jasmine.createSpy('clearInterval'),
delayedFunctionScheduler = jasmine.createSpyObj(
'delayedFunctionScheduler',
['removeFunctionWithId']
),
fakeGlobal = { setInterval: clearInterval },
mockDate = {
install: function() {},
tick: function() {},
uninstall: function() {}
},
mockDate
),
timeout = new clock.FakeTimeout();
clock = new jasmineUnderTest.Clock(
fakeGlobal,
function() {
return delayedFunctionScheduler;
},
mockDate
);
clock.install();
clock.setInterval(delayedFn, 0, 'a', 'b');
clock.install();
clock.clearInterval(123);
expect(fakeSetInterval).not.toHaveBeenCalled();
if (!NODE_JS) {
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
delayedFn,
0,
['a', 'b'],
true
);
} else {
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
delayedFn,
0,
['a', 'b'],
true,
timeout
);
}
});
it('returns an id for the delayed function', function() {
const fakeSetInterval = jasmine.createSpy('setInterval'),
scheduleId = 123,
scheduleFunction = jasmine
.createSpy('scheduleFunction')
.and.returnValue(scheduleId),
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
fakeGlobal = { setInterval: fakeSetInterval },
delayedFn = jasmine.createSpy('delayedFn'),
mockDate = {
install: function() {},
tick: function() {},
uninstall: function() {}
},
clock = new jasmineUnderTest.Clock(
fakeGlobal,
function() {
return delayedFunctionScheduler;
},
mockDate
);
clock.install();
const interval = clock.setInterval(delayedFn, 0);
if (!NODE_JS) {
expect(interval).toEqual(123);
} else {
expect(interval.constructor.name).toEqual('FakeTimeout');
}
});
it('clears the scheduled function with the scheduler', function() {
const clearInterval = jasmine.createSpy('clearInterval'),
delayedFunctionScheduler = jasmine.createSpyObj(
'delayedFunctionScheduler',
['removeFunctionWithId']
),
fakeGlobal = { setInterval: clearInterval },
mockDate = {
install: function() {},
tick: function() {},
uninstall: function() {}
},
clock = new jasmineUnderTest.Clock(
fakeGlobal,
function() {
return delayedFunctionScheduler;
},
mockDate
);
clock.install();
clock.clearInterval(123);
expect(clearInterval).not.toHaveBeenCalled();
expect(delayedFunctionScheduler.removeFunctionWithId).toHaveBeenCalledWith(
123
);
expect(clearInterval).not.toHaveBeenCalled();
expect(
delayedFunctionScheduler.removeFunctionWithId
).toHaveBeenCalledWith(123);
});
});
it('gives you a friendly reminder if the Clock is not installed and you tick', function() {

View File

@@ -0,0 +1,134 @@
describe('Configuration', function() {
const standardBooleanKeys = [
'random',
'stopOnSpecFailure',
'stopSpecOnExpectationFailure',
'failSpecWithNoExpectations',
'hideDisabled',
'autoCleanClosures',
'forbidDuplicateNames',
'detectLateRejectionHandling'
];
const allKeys = [
...standardBooleanKeys,
'seed',
'specFilter',
'verboseDeprecations'
];
Object.freeze(standardBooleanKeys);
Object.freeze(allKeys);
it('provides defaults', function() {
const subject = new jasmineUnderTest.Configuration();
expect(subject.random).toEqual(true);
expect(subject.seed).toBeNull();
expect(subject.stopOnSpecFailure).toEqual(false);
expect(subject.stopSpecOnExpectationFailure).toEqual(false);
expect(subject.failSpecWithNoExpectations).toEqual(false);
expect(subject.specFilter).toEqual(jasmine.any(Function));
expect(subject.specFilter()).toEqual(true);
expect(subject.hideDisabled).toEqual(false);
expect(subject.autoCleanClosures).toEqual(true);
expect(subject.forbidDuplicateNames).toEqual(false);
expect(subject.verboseDeprecations).toEqual(false);
expect(subject.detectLateRejectionHandling).toEqual(false);
});
describe('copy()', function() {
it('returns a copy of the configuration as a plain old JS object', function() {
const subject = new jasmineUnderTest.Configuration();
const copy = subject.copy();
expect(copy.constructor.name).toEqual('Object');
expect(new Set(Object.keys(copy))).toEqual(new Set(allKeys));
for (const k of allKeys) {
expect(copy[k]).toEqual(subject[k]);
}
});
});
describe('update()', function() {
it('does not update properties that are absent from the parameter', function() {
const subject = new jasmineUnderTest.Configuration();
const originalValues = subject.copy();
subject.update({});
expect(subject.copy()).toEqual(originalValues);
});
function booleanPropertyBehavior(key) {
it('does not update the property if the specified value is undefined', function() {
const subject = new jasmineUnderTest.Configuration();
const orig = subject[key];
subject.update({ [key]: undefined });
expect(subject[key]).toEqual(orig);
});
it('updates the property if the specified value is not undefined', function() {
const subject = new jasmineUnderTest.Configuration();
const orig = subject[key];
subject.update({ [key]: !orig });
expect(subject[key]).toEqual(!orig);
subject.update({ [key]: orig });
expect(subject[key]).toEqual(orig);
});
}
for (const k of standardBooleanKeys) {
describe(k, function() {
booleanPropertyBehavior(k);
});
}
// TODO: in the next major release, treat verboseDeprecations like other booleans
it('sets verboseDeprecations when present', function() {
const subject = new jasmineUnderTest.Configuration();
const orig = subject.verboseDeprecations;
subject.update({ verboseDeprecations: !orig });
expect(subject.verboseDeprecations).toEqual(!orig);
subject.update({ verboseDeprecations: orig });
expect(subject.verboseDeprecations).toEqual(orig);
// For backwards compatibility, explicitly setting to undefined should
// work. Undefined isn't officially valid but gets treated like false.
subject.update({ verboseDeprecations: undefined });
expect(subject.verboseDeprecations).toBeUndefined();
});
it('sets specFilter when truthy', function() {
const subject = new jasmineUnderTest.Configuration();
const orig = subject.specFilter;
subject.update({ specFilter: undefined });
expect(subject.specFilter).toBe(orig);
subject.update({ specFilter: false });
expect(subject.specFilter).toBe(orig);
function newSpecFilter() {}
subject.update({ specFilter: newSpecFilter });
expect(subject.specFilter).toBe(newSpecFilter);
});
it('sets seed when not undefined', function() {
const subject = new jasmineUnderTest.Configuration();
subject.update({ seed: undefined });
expect(subject.seed).toBeNull();
subject.update({ seed: 1234 });
expect(subject.seed).toEqual(1234);
subject.update({ seed: null });
expect(subject.seed).toBeNull();
});
});
});

View File

@@ -33,18 +33,6 @@ describe('Spec', function() {
expect(jasmineUnderTest.Spec.isPendingSpecException(void 0)).toBe(false);
});
it('is marked pending if created without a function body', function() {
const startCallback = jasmine.createSpy('startCallback'),
resultCallback = jasmine.createSpy('resultCallback'),
spec = new jasmineUnderTest.Spec({
onStart: startCallback,
queueableFn: { fn: null },
resultCallback: resultCallback
});
expect(spec.status()).toBe('pending');
});
describe('#executionFinished', function() {
it('removes the fn if autoCleanClosures is true', function() {
const spec = new jasmineUnderTest.Spec({
@@ -116,32 +104,25 @@ describe('Spec', function() {
});
});
it('#status returns passing by default', function() {
const spec = new jasmineUnderTest.Spec({
queueableFn: { fn: jasmine.createSpy('spec body') }
});
expect(spec.status()).toBe('passed');
});
describe('#status', function() {
it('returns "passed"" by default', function() {
describe('status', function() {
it('is "passed" by default', function() {
const spec = new jasmineUnderTest.Spec({
queueableFn: { fn: () => {} }
});
expect(spec.status()).toBe('passed');
expect(spec.getResult().status).toBe('passed');
});
it('returns "passed"" if all expectations passed', function() {
it('is "passed" if all expectations passed', function() {
const spec = new jasmineUnderTest.Spec({
queueableFn: { fn: () => {} }
});
spec.addExpectationResult(true, {});
expect(spec.status()).toBe('passed');
expect(spec.getResult().status).toBe('passed');
});
it('returns "failed" if any expectation failed', function() {
it('is "failed" if any expectation failed', function() {
const spec = new jasmineUnderTest.Spec({
queueableFn: { fn: () => {} }
});
@@ -149,7 +130,19 @@ describe('Spec', function() {
spec.addExpectationResult(true, {});
spec.addExpectationResult(false, {});
expect(spec.status()).toBe('failed');
expect(spec.getResult().status).toBe('failed');
});
it('is "pending" if created without a function body', function() {
const startCallback = jasmine.createSpy('startCallback'),
resultCallback = jasmine.createSpy('resultCallback'),
spec = new jasmineUnderTest.Spec({
onStart: startCallback,
queueableFn: { fn: null },
resultCallback: resultCallback
});
expect(spec.getResult().status).toBe('pending');
});
});

View File

@@ -115,7 +115,7 @@ describe('Suite', function() {
const suite = new jasmineUnderTest.Suite({});
suite.addExpectationResult(false, {});
expect(suite.status()).toBe('failed');
expect(suite.getResult().status).toBe('failed');
});
it('retrieves a result with updated status', function() {
@@ -140,7 +140,7 @@ describe('Suite', function() {
suite.addExpectationResult(false, { message: 'failed' });
}).toThrowError(jasmineUnderTest.errors.ExpectationFailed);
expect(suite.status()).toBe('failed');
expect(suite.getResult().status).toBe('failed');
expect(suite.result.failedExpectations).toEqual([
jasmine.objectContaining({ message: 'failed' })
]);

View File

@@ -114,7 +114,6 @@ describe('TreeRunner', function() {
expect(specRunQueueArgs.queueableFns[1]).toEqual(queueableFn);
queueableFn.fn();
expect(spec.status()).toEqual('pending');
expect(spec.getResult().status).toEqual('pending');
expect(spec.getResult().pendingReason).toEqual('');
});
@@ -136,7 +135,6 @@ describe('TreeRunner', function() {
expect(specRunQueueArgs.queueableFns[1]).toEqual(queueableFn);
queueableFn.fn();
expect(spec.status()).toEqual('pending');
expect(spec.getResult().status).toEqual('pending');
expect(spec.getResult().pendingReason).toEqual('some reason');
});
@@ -163,111 +161,6 @@ describe('TreeRunner', function() {
expect(spec.executionFinished).toHaveBeenCalledWith(false, true);
await expectAsync(executePromise).toBePending();
});
describe('Late promise rejection handling', function() {
it('is enabled when the detectLateRejectionHandling param is true', function() {
const before = jasmine.createSpy('before');
const after = jasmine.createSpy('after');
const queueableFn = {
fn: jasmine.createSpy('test body').and.callFake(function() {
expect(before).toHaveBeenCalled();
expect(after).not.toHaveBeenCalled();
})
};
const spec = new jasmineUnderTest.Spec({
queueableFn,
beforeAndAfterFns: function() {
return { befores: [before], afters: [after] };
}
});
const {
runQueue,
setTimeout,
suiteRunQueueArgs,
globalErrors
} = runSingleSpecSuite(spec, { detectLateRejectionHandling: true });
suiteRunQueueArgs.queueableFns[0].fn();
expect(runQueue).toHaveBeenCalledTimes(1);
const specRunQueueOpts = runQueue.calls.mostRecent().args[0];
expect(specRunQueueOpts.queueableFns).toEqual([
{ fn: jasmine.any(Function) },
before,
queueableFn,
after,
{ fn: jasmine.any(Function) },
{
fn: jasmine.any(Function),
type: 'specCleanup'
}
]);
const done = jasmine.createSpy('done');
specRunQueueOpts.queueableFns[4].fn(done);
expect(globalErrors.reportUnhandledRejections).not.toHaveBeenCalled();
expect(done).not.toHaveBeenCalled();
expect(setTimeout).toHaveBeenCalledOnceWith(jasmine.any(Function));
setTimeout.calls.argsFor(0)[0]();
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalled();
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalledBefore(
done
);
});
});
function runSingleSpecSuite(spec, optionalConfig) {
const topSuiteId = 'suite1';
spec.parentSuiteId = topSuiteId;
const topSuite = new jasmineUnderTest.Suite({ id: topSuiteId });
topSuite.addChild(spec);
const executionTree = {
topSuite,
childrenOfTopSuite() {
return [{ spec }];
},
isExcluded() {
return false;
}
};
const runQueue = jasmine.createSpy('runQueue');
const reportDispatcher = mockReportDispatcher();
const runableResources = mockRunableResources();
const globalErrors = mockGlobalErrors();
const setTimeout = jasmine.createSpy('setTimeout');
const currentRunableTracker = new jasmineUnderTest.CurrentRunableTracker();
const subject = new jasmineUnderTest.TreeRunner({
executionTree,
runQueue,
globalErrors,
setTimeout,
runableResources,
reportDispatcher,
currentRunableTracker,
getConfig() {
return optionalConfig || {};
},
reportChildrenOfBeforeAllFailure() {}
});
const executePromise = subject.execute();
expect(runQueue).toHaveBeenCalledTimes(1);
const suiteRunQueueArgs = runQueue.calls.mostRecent().args[0];
runQueue.calls.reset();
return {
runQueue,
globalErrors,
setTimeout,
currentRunableTracker,
runableResources,
reportDispatcher,
suiteRunQueueArgs,
executePromise
};
}
});
describe('Suite execution', function() {
@@ -516,6 +409,288 @@ describe('TreeRunner', function() {
});
});
it('does not remove before and after fns from the top suite', async function() {
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
spyOn(topSuite, 'cleanupBeforeAfter');
const executionTree = {
topSuite,
childrenOfTopSuite() {
return [];
},
isExcluded() {
return false;
}
};
const runQueue = jasmine.createSpy('runQueue');
const subject = new jasmineUnderTest.TreeRunner({
executionTree,
runQueue,
globalErrors: mockGlobalErrors(),
runableResources: mockRunableResources(),
reportDispatcher: mockReportDispatcher(),
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
getConfig() {
return {};
}
});
const executePromise = subject.execute();
expect(runQueue).toHaveBeenCalledTimes(1);
const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
runQueue.calls.reset();
for (const qfn of topSuiteRunQueueOpts.queueableFns) {
qfn.fn();
}
topSuiteRunQueueOpts.onComplete();
await expectAsync(executePromise).toBeResolved();
expect(topSuite.cleanupBeforeAfter).not.toHaveBeenCalled();
});
describe('Late promise rejection handling', function() {
it('works for specs when the detectLateRejectionHandling param is true', function() {
const before = jasmine.createSpy('before');
const after = jasmine.createSpy('after');
const queueableFn = {
fn: jasmine.createSpy('test body').and.callFake(function() {
expect(before).toHaveBeenCalled();
expect(after).not.toHaveBeenCalled();
})
};
const spec = new jasmineUnderTest.Spec({
queueableFn,
beforeAndAfterFns: function() {
return { befores: [before], afters: [after] };
}
});
const {
runQueue,
setTimeout,
suiteRunQueueArgs,
globalErrors
} = runSingleSpecSuite(spec, { detectLateRejectionHandling: true });
suiteRunQueueArgs.queueableFns[0].fn();
expect(runQueue).toHaveBeenCalledTimes(1);
const specRunQueueOpts = runQueue.calls.mostRecent().args[0];
expect(specRunQueueOpts.queueableFns).toEqual([
{ fn: jasmine.any(Function) },
before,
queueableFn,
after,
{ fn: jasmine.any(Function) },
{
fn: jasmine.any(Function),
type: 'specCleanup'
}
]);
const done = jasmine.createSpy('done');
specRunQueueOpts.queueableFns[4].fn(done);
expect(globalErrors.reportUnhandledRejections).not.toHaveBeenCalled();
expect(done).not.toHaveBeenCalled();
expect(setTimeout).toHaveBeenCalledOnceWith(jasmine.any(Function));
setTimeout.calls.argsFor(0)[0]();
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalled();
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalledBefore(
done
);
});
it('works for beforeAll when the detectLateRejectionHandling param is true', async function() {
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
const suite = new jasmineUnderTest.Suite({
id: 'suite',
parentSuite: topSuite
});
suite.beforeAll(function() {});
const spec = new jasmineUnderTest.Spec({
queueableFn: { fn() {} },
parentSuite: suite
});
const executionTree = {
topSuite,
childrenOfTopSuite() {
return [{ suite }];
},
childrenOfSuiteSegment() {
return [{ spec }];
},
isExcluded() {
return false;
}
};
const runQueue = jasmine.createSpy('runQueue');
const reportDispatcher = mockReportDispatcher();
const globalErrors = mockGlobalErrors();
const setTimeout = jasmine.createSpy('setTimeout');
const subject = new jasmineUnderTest.TreeRunner({
executionTree,
runQueue,
globalErrors,
runableResources: mockRunableResources(),
reportDispatcher,
setTimeout,
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
getConfig() {
return { detectLateRejectionHandling: true };
},
reportChildrenOfBeforeAllFailure() {}
});
const executePromise = subject.execute();
expect(runQueue).toHaveBeenCalledTimes(1);
const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
runQueue.calls.reset();
topSuiteRunQueueOpts.queueableFns[0].fn(function() {});
expect(runQueue).toHaveBeenCalledTimes(1);
const suiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
expect(suiteRunQueueOpts.queueableFns).toEqual([
{ fn: jasmine.any(Function) }, // onStart
jasmine.objectContaining({ type: 'beforeAll' }),
{ fn: jasmine.any(Function) }, // detect late rejection handling
{ fn: jasmine.any(Function) } // spec
]);
suiteRunQueueOpts.queueableFns[0].fn();
const done = jasmine.createSpy('done');
suiteRunQueueOpts.queueableFns[2].fn(done);
expect(globalErrors.reportUnhandledRejections).not.toHaveBeenCalled();
expect(setTimeout).toHaveBeenCalledOnceWith(jasmine.any(Function));
setTimeout.calls.argsFor(0)[0]();
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalledBefore(
done
);
await expectAsync(executePromise).toBePending();
});
it('works for afterAll when the detectLateRejectionHandling param is true', async function() {
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
const suite = new jasmineUnderTest.Suite({
id: 'suite',
parentSuite: topSuite
});
suite.afterAll(function() {});
const spec = new jasmineUnderTest.Spec({
queueableFn: { fn() {} },
parentSuite: suite
});
const executionTree = {
topSuite,
childrenOfTopSuite() {
return [{ suite }];
},
childrenOfSuiteSegment() {
return [{ spec }];
},
isExcluded() {
return false;
}
};
const runQueue = jasmine.createSpy('runQueue');
const reportDispatcher = mockReportDispatcher();
const globalErrors = mockGlobalErrors();
const setTimeout = jasmine.createSpy('setTimeout');
const subject = new jasmineUnderTest.TreeRunner({
executionTree,
runQueue,
globalErrors,
runableResources: mockRunableResources(),
reportDispatcher,
setTimeout,
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
getConfig() {
return { detectLateRejectionHandling: true };
},
reportChildrenOfBeforeAllFailure() {}
});
const executePromise = subject.execute();
expect(runQueue).toHaveBeenCalledTimes(1);
const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
runQueue.calls.reset();
topSuiteRunQueueOpts.queueableFns[0].fn(function() {});
expect(runQueue).toHaveBeenCalledTimes(1);
const suiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
expect(suiteRunQueueOpts.queueableFns).toEqual([
{ fn: jasmine.any(Function) }, // onStart
{ fn: jasmine.any(Function) }, // spec
jasmine.objectContaining({ type: 'afterAll' }),
{ fn: jasmine.any(Function) } // detect late rejection handling
]);
suiteRunQueueOpts.queueableFns[0].fn();
const done = jasmine.createSpy('done');
suiteRunQueueOpts.queueableFns[3].fn(done);
expect(globalErrors.reportUnhandledRejections).not.toHaveBeenCalled();
expect(setTimeout).toHaveBeenCalledOnceWith(jasmine.any(Function));
setTimeout.calls.argsFor(0)[0]();
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalledBefore(
done
);
await expectAsync(executePromise).toBePending();
});
});
function runSingleSpecSuite(spec, optionalConfig) {
const topSuiteId = 'suite1';
spec.parentSuiteId = topSuiteId;
const topSuite = new jasmineUnderTest.Suite({ id: topSuiteId });
topSuite.addChild(spec);
const executionTree = {
topSuite,
childrenOfTopSuite() {
return [{ spec }];
},
isExcluded() {
return false;
}
};
const runQueue = jasmine.createSpy('runQueue');
const reportDispatcher = mockReportDispatcher();
const runableResources = mockRunableResources();
const globalErrors = mockGlobalErrors();
const setTimeout = jasmine.createSpy('setTimeout');
const currentRunableTracker = new jasmineUnderTest.CurrentRunableTracker();
const subject = new jasmineUnderTest.TreeRunner({
executionTree,
runQueue,
globalErrors,
setTimeout,
runableResources,
reportDispatcher,
currentRunableTracker,
getConfig() {
return optionalConfig || {};
},
reportChildrenOfBeforeAllFailure() {}
});
const executePromise = subject.execute();
expect(runQueue).toHaveBeenCalledTimes(1);
const suiteRunQueueArgs = runQueue.calls.mostRecent().args[0];
runQueue.calls.reset();
return {
runQueue,
globalErrors,
setTimeout,
currentRunableTracker,
runableResources,
reportDispatcher,
suiteRunQueueArgs,
executePromise
};
}
function mockReportDispatcher() {
const reportDispatcher = jasmine.createSpyObj(
'reportDispatcher',

View File

@@ -1600,6 +1600,16 @@ describe('Env integration', function() {
suiteFullNameToId[e.fullName] = e.id;
});
// Clone args to work around Jasmine mutating the result after passing it
// to the reporter event.
// TODO: remove this once Jasmine no longer does that
const clone = structuredClone.bind(globalThis);
reporter.specStarted.calls.saveArgumentsByValue(clone);
reporter.specDone.calls.saveArgumentsByValue(clone);
reporter.specStarted.calls.saveArgumentsByValue(clone);
reporter.suiteDone.calls.saveArgumentsByValue(clone);
env.configure({ random: false });
env.addReporter(reporter);
env.it('a top level spec', function() {});
@@ -1636,110 +1646,179 @@ describe('Env integration', function() {
expect(reporter.specStarted.calls.count()).toBe(6);
expect(reporter.specDone.calls.count()).toBe(6);
expect(reporter.specStarted).toHaveBeenCalledWith(
jasmine.objectContaining({
description: 'a top level spec',
parentSuiteId: null
})
);
expect(reporter.specDone).toHaveBeenCalledWith(
jasmine.objectContaining({
description: 'a top level spec',
status: 'passed',
parentSuiteId: null
})
);
expect(reporter.specStarted).toHaveBeenCalledWith(
jasmine.objectContaining({
description: 'with a spec',
parentSuiteId: suiteFullNameToId['A Suite']
})
);
expect(reporter.specDone).toHaveBeenCalledWith(
jasmine.objectContaining({
description: 'with a spec',
status: 'passed',
parentSuiteId: suiteFullNameToId['A Suite']
})
);
const baseSpecEvent = {
passedExpectations: [],
failedExpectations: [],
deprecationWarnings: [],
pendingReason: '',
duration: null,
properties: null,
debugLogs: null,
id: jasmine.any(String),
filename: jasmine.any(String)
};
expect(reporter.specStarted).toHaveBeenCalledWith(
jasmine.objectContaining({
description: "with an x'ed spec",
parentSuiteId: suiteFullNameToId['A Suite with a nested suite']
})
);
expect(reporter.specDone).toHaveBeenCalledWith(
jasmine.objectContaining({
description: "with an x'ed spec",
status: 'pending',
parentSuiteId: suiteFullNameToId['A Suite with a nested suite']
})
);
expect(reporter.specStarted.calls.argsFor(0)[0]).toEqual({
...baseSpecEvent,
description: 'a top level spec',
fullName: 'a top level spec',
parentSuiteId: null
});
expect(reporter.specDone.calls.argsFor(0)[0]).toEqual({
...baseSpecEvent,
description: 'a top level spec',
fullName: 'a top level spec',
status: 'passed',
parentSuiteId: null,
duration: jasmine.any(Number)
});
expect(reporter.specStarted.calls.argsFor(1)[0]).toEqual({
...baseSpecEvent,
description: 'with a spec',
fullName: 'A Suite with a spec',
parentSuiteId: suiteFullNameToId['A Suite']
});
expect(reporter.specDone.calls.argsFor(1)[0]).toEqual({
...baseSpecEvent,
description: 'with a spec',
fullName: 'A Suite with a spec',
status: 'passed',
parentSuiteId: suiteFullNameToId['A Suite'],
passedExpectations: [
{ matcherName: 'toBe', message: 'Passed.', stack: '', passed: true }
],
duration: jasmine.any(Number)
});
expect(reporter.specStarted).toHaveBeenCalledWith(
jasmine.objectContaining({
description: 'with a spec',
parentSuiteId: suiteFullNameToId['A Suite with a nested suite']
})
);
expect(reporter.specDone).toHaveBeenCalledWith(
jasmine.objectContaining({
description: 'with a spec',
status: 'failed',
parentSuiteId: suiteFullNameToId['A Suite with a nested suite']
})
);
expect(reporter.specStarted.calls.argsFor(2)[0]).toEqual({
...baseSpecEvent,
description: "with an x'ed spec",
fullName: "A Suite with a nested suite with an x'ed spec",
parentSuiteId: suiteFullNameToId['A Suite with a nested suite'],
pendingReason: 'Temporarily disabled with xit'
});
expect(reporter.specDone.calls.argsFor(2)[0]).toEqual({
...baseSpecEvent,
description: "with an x'ed spec",
fullName: "A Suite with a nested suite with an x'ed spec",
status: 'pending',
parentSuiteId: suiteFullNameToId['A Suite with a nested suite'],
pendingReason: 'Temporarily disabled with xit',
duration: jasmine.any(Number)
});
expect(reporter.specStarted).toHaveBeenCalledWith(
jasmine.objectContaining({
description: 'is pending',
parentSuiteId:
suiteFullNameToId['A Suite with only non-executable specs']
})
);
expect(reporter.specDone).toHaveBeenCalledWith(
jasmine.objectContaining({
description: 'is pending',
status: 'pending',
parentSuiteId:
suiteFullNameToId['A Suite with only non-executable specs']
})
);
expect(reporter.specStarted.calls.argsFor(3)[0]).toEqual({
...baseSpecEvent,
description: 'with a spec',
fullName: 'A Suite with a nested suite with a spec',
parentSuiteId: suiteFullNameToId['A Suite with a nested suite']
});
expect(reporter.specDone.calls.argsFor(3)[0]).toEqual({
...baseSpecEvent,
description: 'with a spec',
fullName: 'A Suite with a nested suite with a spec',
status: 'failed',
parentSuiteId: suiteFullNameToId['A Suite with a nested suite'],
failedExpectations: [
jasmine.objectContaining({
matcherName: 'toBe',
message: 'Expected true to be false.'
})
],
duration: jasmine.any(Number)
});
expect(reporter.suiteStarted).toHaveBeenCalledWith(
jasmine.objectContaining({
description: 'A Suite',
parentSuiteId: null
})
);
expect(reporter.suiteDone).toHaveBeenCalledWith(
jasmine.objectContaining({
description: 'A Suite',
status: 'passed',
parentSuiteId: null
})
);
expect(reporter.specStarted.calls.argsFor(4)[0]).toEqual({
...baseSpecEvent,
description: 'is pending',
fullName: 'A Suite with only non-executable specs is pending',
parentSuiteId: suiteFullNameToId['A Suite with only non-executable specs']
});
expect(reporter.specDone.calls.argsFor(4)[0]).toEqual({
...baseSpecEvent,
description: 'is pending',
status: 'pending',
fullName: 'A Suite with only non-executable specs is pending',
parentSuiteId:
suiteFullNameToId['A Suite with only non-executable specs'],
duration: jasmine.any(Number)
});
expect(reporter.suiteStarted).toHaveBeenCalledWith(
jasmine.objectContaining({
description: 'with a nested suite',
parentSuiteId: suiteFullNameToId['A Suite']
})
);
expect(reporter.suiteDone).toHaveBeenCalledWith(
jasmine.objectContaining({
description: 'with a nested suite',
status: 'passed',
parentSuiteId: suiteFullNameToId['A Suite']
})
);
expect(reporter.specStarted.calls.argsFor(5)[0]).toEqual({
...baseSpecEvent,
description: 'is xed',
fullName: 'A Suite with only non-executable specs is xed',
parentSuiteId:
suiteFullNameToId['A Suite with only non-executable specs'],
pendingReason: 'Temporarily disabled with xit'
});
expect(reporter.specDone.calls.argsFor(5)[0]).toEqual({
...baseSpecEvent,
description: 'is xed',
status: 'pending',
fullName: 'A Suite with only non-executable specs is xed',
parentSuiteId:
suiteFullNameToId['A Suite with only non-executable specs'],
pendingReason: 'Temporarily disabled with xit',
duration: jasmine.any(Number)
});
const suiteDone = reporter.suiteDone.calls.argsFor(0)[0];
expect(typeof suiteDone.duration).toBe('number');
expect(reporter.suiteStarted.calls.count()).toBe(3);
expect(reporter.suiteDone.calls.count()).toBe(3);
const suiteResult = reporter.suiteStarted.calls.argsFor(0)[0];
expect(suiteResult.description).toEqual('A Suite');
const baseSuiteEvent = {
id: jasmine.any(String),
filename: jasmine.any(String),
failedExpectations: [],
deprecationWarnings: [],
duration: null,
properties: null
};
expect(reporter.suiteStarted.calls.argsFor(0)[0]).toEqual({
...baseSuiteEvent,
description: 'A Suite',
fullName: 'A Suite',
parentSuiteId: null
});
expect(reporter.suiteDone.calls.argsFor(2)[0]).toEqual({
...baseSuiteEvent,
description: 'A Suite',
fullName: 'A Suite',
status: 'passed',
parentSuiteId: null,
duration: jasmine.any(Number)
});
expect(reporter.suiteStarted.calls.argsFor(1)[0]).toEqual({
...baseSuiteEvent,
description: 'with a nested suite',
fullName: 'A Suite with a nested suite',
parentSuiteId: suiteFullNameToId['A Suite']
});
expect(reporter.suiteDone.calls.argsFor(0)[0]).toEqual({
...baseSuiteEvent,
description: 'with a nested suite',
status: 'passed',
fullName: 'A Suite with a nested suite',
parentSuiteId: suiteFullNameToId['A Suite'],
duration: jasmine.any(Number)
});
expect(reporter.suiteStarted.calls.argsFor(2)[0]).toEqual({
...baseSuiteEvent,
description: 'with only non-executable specs',
fullName: 'A Suite with only non-executable specs',
parentSuiteId: suiteFullNameToId['A Suite']
});
expect(reporter.suiteDone.calls.argsFor(1)[0]).toEqual({
...baseSuiteEvent,
description: 'with only non-executable specs',
status: 'passed',
fullName: 'A Suite with only non-executable specs',
parentSuiteId: suiteFullNameToId['A Suite'],
duration: jasmine.any(Number)
});
});
it('reports focused specs and suites as expected', async function() {
@@ -2198,6 +2277,34 @@ describe('Env integration', function() {
await env.execute();
});
it('Custom matchers set in top-level beforeAll should be available to all specs and suites', async function() {
const matchers = {
toFoo: function() {}
};
env.beforeAll(function() {
env.addMatchers(matchers);
});
env.describe('suite - top-level', function() {
env.it('has access to the custom matcher', function() {
expect(env.expect().toFoo).toBeDefined();
});
env.describe('suite - nested', function() {
env.it('has access to the custom matcher', function() {
expect(env.expect().toFoo).toBeDefined();
});
});
});
env.it('spec - top-level - has access to the custom matcher', function() {
expect(env.expect().toFoo).toBeDefined();
});
await env.execute();
});
it('throws an exception if you try to create a spy outside of a runnable', async function() {
const obj = { fn: function() {} };
let exception;

View File

@@ -809,15 +809,17 @@ describe('Global error handling (integration)', function() {
describe('When the detectLateRejectionHandling config option is set', function() {
describe('When the unhandled rejection event has a promise', function() {
it('reports the rejection unless a corresponding rejection handled event occurs', async function() {
function makeEvent(suffix) {
const reason = `rejection ${suffix}`;
const promise = Promise.reject(reason);
promise.catch(() => {});
return { reason, promise };
}
function makeEvent(suffix) {
const reason = `rejection ${suffix}`;
const promise = Promise.reject(reason);
promise.catch(() => {});
return { reason, promise };
}
const global = {
let global, reporter;
beforeEach(function() {
global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
@@ -833,43 +835,132 @@ describe('Global error handling (integration)', function() {
env.cleanup_();
env = new jasmineUnderTest.Env();
env.configure({ detectLateRejectionHandling: true });
const reporter = jasmine.createSpyObj('fakeReporter', [
reporter = jasmine.createSpyObj('fakeReporter', [
'specDone',
'suiteDone'
]);
env.addReporter(reporter);
});
env.describe('A suite', function() {
env.it('fails', function(specDone) {
setTimeout(function() {
const events = ['spec 1', 'spec 2'].map(makeEvent);
describe('During spec execution', function() {
it('reports the rejection unless a corresponding rejection handled event occurs', async function() {
env.describe('A suite', function() {
env.it('fails', function(specDone) {
setTimeout(function() {
const events = ['spec 1', 'spec 2'].map(makeEvent);
for (const e of events) {
dispatchErrorEvent(global, 'unhandledrejection', e);
}
for (const e of events) {
dispatchErrorEvent(global, 'unhandledrejection', e);
}
dispatchErrorEvent(global, 'rejectionhandled', events[0]);
specDone();
dispatchErrorEvent(global, 'rejectionhandled', events[0]);
specDone();
});
});
});
await env.execute();
expect(reporter.specDone).toHaveBeenCalledWith(
jasmine.objectContaining({
fullName: 'A suite fails',
failedExpectations: [
// Only the second rejection should be reported, since the first
// one was eventually handled.
jasmine.objectContaining({
message:
'Unhandled promise rejection: rejection spec 2 thrown'
})
]
})
);
});
});
await env.execute();
describe('During beforeAll execution', function() {
it('reports the rejection unless a corresponding rejection handled event occurs by the end of the beforeAll', async function() {
env.describe('A suite', function() {
let events;
expect(reporter.specDone).toHaveBeenCalledWith(
jasmine.objectContaining({
fullName: 'A suite fails',
failedExpectations: [
// Only the second rejection should be reported, since the first
// one was eventually handled.
jasmine.objectContaining({
message:
'Unhandled promise rejection: rejection spec 2 thrown'
})
]
})
);
env.beforeAll(function(beforeAllDone) {
setTimeout(function() {
events = ['suite 1', 'suite 2'].map(makeEvent);
for (const e of events) {
dispatchErrorEvent(global, 'unhandledrejection', e);
}
dispatchErrorEvent(global, 'rejectionhandled', events[0]);
beforeAllDone();
});
});
env.it('is a spec', function(specDone) {
setTimeout(function() {
// Should not prevent the second rejection from being reported
dispatchErrorEvent(global, 'rejectionhandled', events[1]);
specDone();
});
});
});
await env.execute();
expect(reporter.suiteDone).toHaveBeenCalledWith(
jasmine.objectContaining({
fullName: 'A suite',
failedExpectations: [
// Only the second rejection should be reported, since the first
// one was eventually handled.
jasmine.objectContaining({
message:
'Unhandled promise rejection: rejection suite 2 thrown'
})
]
})
);
});
});
describe('During afterAll execution', function() {
it('reports the rejection unless a corresponding rejection handled event occurs by the end of the afterAll', async function() {
env.describe('A suite', function() {
let events;
env.afterAll(function(beforeAllDone) {
setTimeout(function() {
events = ['suite 1', 'suite 2'].map(makeEvent);
for (const e of events) {
dispatchErrorEvent(global, 'unhandledrejection', e);
}
dispatchErrorEvent(global, 'rejectionhandled', events[0]);
beforeAllDone();
});
});
env.it('is a spec', function() {});
});
await env.execute();
expect(reporter.suiteDone).toHaveBeenCalledWith(
jasmine.objectContaining({
fullName: 'A suite',
failedExpectations: [
// Only the second rejection should be reported, since the first
// one was eventually handled.
jasmine.objectContaining({
message:
'Unhandled promise rejection: rejection suite 2 thrown'
})
]
})
);
});
});
});
@@ -920,6 +1011,43 @@ describe('Global error handling (integration)', function() {
});
});
it('works when the suite is run multiple times', async function() {
const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
},
clearTimeout: function(fn, delay) {
clearTimeout(fn, delay);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
}
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
env.cleanup_();
env = new jasmineUnderTest.Env();
env.configure({ autoCleanClosures: false });
const reporter = jasmine.createSpyObj('fakeReporter', ['specDone']);
env.addReporter(reporter);
env.it('fails', function(specDone) {
setTimeout(function() {
dispatchErrorEvent(global, 'error', { error: 'fail' });
specDone();
});
});
await env.execute();
reporter.specDone.calls.reset();
await env.execute();
expect(reporter.specDone).toHaveFailedExpectationsForRunnable('fails', [
'fail thrown'
]);
});
describe('#spyOnGlobalErrorsAsync', function() {
const leftInstalledMessage =
'Global error spy was not uninstalled. ' +

View File

@@ -470,20 +470,28 @@ describe('Matchers (Integration)', function() {
});
describe('toBeNullish', function() {
verifyPasses(function(env) {
env.expect(undefined).toBeNullish();
describe('with undefined', function() {
verifyPasses(function(env) {
env.expect(undefined).toBeNullish();
});
});
verifyPasses(function(env) {
env.expect(null).toBeNullish();
describe('with null', function() {
verifyPasses(function(env) {
env.expect(null).toBeNullish();
});
});
verifyFails(function(env) {
env.expect(1).toBeNullish();
describe('with a truthy value', function() {
verifyFails(function(env) {
env.expect(1).toBeNullish();
});
});
verifyFails(function(env) {
env.expect('').toBeNullish();
describe('with a non-null falsy value', function() {
verifyFails(function(env) {
env.expect('').toBeNullish();
});
});
});
@@ -665,13 +673,17 @@ describe('Matchers (Integration)', function() {
env.expect(spyObj).toHaveSpyInteractions();
});
verifyFails(function(env) {
env.expect(spyObj).toHaveSpyInteractions();
describe('with no methods called', function() {
verifyFails(function(env) {
env.expect(spyObj).toHaveSpyInteractions();
});
});
verifyFails(function(env) {
spyObj.otherMethod();
env.expect(spyObj).toHaveSpyInteractions();
describe('with only non-spy methods called', function() {
verifyFails(function(env) {
spyObj.otherMethod();
env.expect(spyObj).toHaveSpyInteractions();
});
});
});

View File

@@ -281,22 +281,44 @@ describe('toThrowError', function() {
);
});
it('fails if thrown is a type of Error and the expected is a different Error', function() {
const matchersUtil = {
equals: jasmine.createSpy('delegated-equal').and.returnValue(false),
pp: jasmineUnderTest.makePrettyPrinter()
},
matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil),
fn = function() {
throw new TypeError('foo');
};
describe('with a string', function() {
it('fails if thrown is a type of Error and the expected is a different Error', function() {
const matchersUtil = {
equals: jasmine.createSpy('delegated-equal').and.returnValue(false),
pp: jasmineUnderTest.makePrettyPrinter()
},
matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil),
fn = function() {
throw new TypeError('foo');
};
const result = matcher.compare(fn, TypeError, 'bar');
const result = matcher.compare(fn, TypeError, 'bar');
expect(result.pass).toBe(false);
expect(result.message()).toEqual(
"Expected function to throw TypeError with message 'bar', but it threw TypeError with message 'foo'."
);
expect(result.pass).toBe(false);
expect(result.message()).toEqual(
"Expected function to throw TypeError with message 'bar', but it threw TypeError with message 'foo'."
);
});
});
describe('with a regex', function() {
it('fails if thrown is a type of Error and the expected is a different Error', function() {
const matchersUtil = {
equals: jasmine.createSpy('delegated-equal').and.returnValue(false),
pp: jasmineUnderTest.makePrettyPrinter()
},
matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil),
fn = function() {
throw new TypeError('foo');
};
const result = matcher.compare(fn, TypeError, /bar/);
expect(result.pass).toBe(false);
expect(result.message()).toEqual(
"Expected function to throw TypeError with a message matching /bar/, but it threw TypeError with message 'foo'."
);
});
});
it('passes if thrown is a type of Error and has the same type as the expected Error and the message matches the expected message', function() {
@@ -316,22 +338,4 @@ describe('toThrowError', function() {
'Expected function not to throw TypeError with a message matching /foo/.'
);
});
it('fails if thrown is a type of Error and the expected is a different Error', function() {
const matchersUtil = {
equals: jasmine.createSpy('delegated-equal').and.returnValue(false),
pp: jasmineUnderTest.makePrettyPrinter()
},
matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil),
fn = function() {
throw new TypeError('foo');
};
const result = matcher.compare(fn, TypeError, /bar/);
expect(result.pass).toBe(false);
expect(result.message()).toEqual(
"Expected function to throw TypeError with a message matching /bar/, but it threw TypeError with message 'foo'."
);
});
});

View File

@@ -26,6 +26,9 @@ module.exports = {
'helpers/defineJasmineUnderTest.js',
'helpers/resetEnv.js'
],
env: {
forbidDuplicateNames: true
},
random: true,
browser: {
name: process.env.JASMINE_BROWSER || 'firefox',

View File

@@ -12,5 +12,8 @@
"helpers/nodeDefineJasmineUnderTest.js",
"helpers/resetEnv.js"
],
"env": {
"forbidDuplicateNames": true
},
"random": true
}

186
src/core/Configuration.js Normal file
View File

@@ -0,0 +1,186 @@
getJasmineRequireObj().Configuration = function(j$) {
/**
* This represents the available options to configure Jasmine.
* Options that are not provided will use their default values.
* @see Env#configure
* @interface Configuration
* @since 3.3.0
*/
const defaultConfig = {
/**
* Whether to randomize spec execution order
* @name Configuration#random
* @since 3.3.0
* @type Boolean
* @default true
*/
random: true,
/**
* Seed to use as the basis of randomization.
* Null causes the seed to be determined randomly at the start of execution.
* @name Configuration#seed
* @since 3.3.0
* @type (number|string)
* @default null
*/
seed: null,
/**
* Whether to stop execution of the suite after the first spec failure
*
* <p>In parallel mode, `stopOnSpecFailure` works on a "best effort"
* basis. Jasmine will stop execution as soon as practical after a failure
* but it might not be immediate.</p>
* @name Configuration#stopOnSpecFailure
* @since 3.9.0
* @type Boolean
* @default false
*/
stopOnSpecFailure: false,
/**
* Whether to fail the spec if it ran no expectations. By default
* a spec that ran no expectations is reported as passed. Setting this
* to true will report such spec as a failure.
* @name Configuration#failSpecWithNoExpectations
* @since 3.5.0
* @type Boolean
* @default false
*/
failSpecWithNoExpectations: false,
/**
* Whether to cause specs to only have one expectation failure.
* @name Configuration#stopSpecOnExpectationFailure
* @since 3.3.0
* @type Boolean
* @default false
*/
stopSpecOnExpectationFailure: false,
/**
* A function that takes a spec and returns true if it should be executed
* or false if it should be skipped.
* @callback SpecFilter
* @param {Spec} spec - The spec that the filter is being applied to.
* @return boolean
*/
/**
* Function to use to filter specs
* @name Configuration#specFilter
* @since 3.3.0
* @type SpecFilter
* @default A function that always returns true.
*/
specFilter: function() {
return true;
},
/**
* Whether reporters should hide disabled specs from their output.
* Currently only supported by Jasmine's HTMLReporter
* @name Configuration#hideDisabled
* @since 3.3.0
* @type Boolean
* @default false
*/
hideDisabled: false,
/**
* Clean closures when a suite is done running (done by clearing the stored function reference).
* This prevents memory leaks, but you won't be able to run jasmine multiple times.
* @name Configuration#autoCleanClosures
* @since 3.10.0
* @type boolean
* @default true
*/
autoCleanClosures: true,
/**
* Whether to forbid duplicate spec or suite names. If set to true, using
* the same name multiple times in the same immediate parent suite is an
* error.
* @name Configuration#forbidDuplicateNames
* @type boolean
* @default false
*/
forbidDuplicateNames: false,
/**
* Whether to issue warnings for certain deprecated functionality
* every time it's used. If not set or set to false, deprecation warnings
* for methods that tend to be called frequently will be issued only once
* or otherwise throttled to prevent the suite output from being flooded
* with warnings.
* @name Configuration#verboseDeprecations
* @since 3.6.0
* @type Boolean
* @default false
*/
verboseDeprecations: false,
/**
* Whether to detect late promise rejection handling during spec
* execution. If this option is enabled, a promise rejection that triggers
* the JavaScript runtime's unhandled rejection event will not be treated
* as an error as long as it's handled before the spec finishes.
*
* This option is off by default because it imposes a performance penalty.
* @name Configuration#detectLateRejectionHandling
* @since 5.10.0
* @type Boolean
* @default false
*/
detectLateRejectionHandling: false
};
Object.freeze(defaultConfig);
class Configuration {
#values;
constructor() {
this.#values = { ...defaultConfig };
for (const k of Object.keys(defaultConfig)) {
Object.defineProperty(this, k, {
enumerable: true,
get() {
return this.#values[k];
}
});
}
}
copy() {
return { ...this.#values };
}
update(changes) {
const booleanProps = [
'random',
'failSpecWithNoExpectations',
'hideDisabled',
'stopOnSpecFailure',
'stopSpecOnExpectationFailure',
'autoCleanClosures',
'forbidDuplicateNames',
'detectLateRejectionHandling'
];
for (const k of booleanProps) {
if (typeof changes[k] !== 'undefined') {
this.#values[k] = changes[k];
}
}
if (changes.specFilter) {
this.#values.specFilter = changes.specFilter;
}
// 0 and null are valid values, so a truthiness check wouldn't work
if (typeof changes.seed !== 'undefined') {
this.#values.seed = changes.seed;
}
// TODO: in the next major release, make verboseDeprecations work like
// other boolean properties.
if (changes.hasOwnProperty('verboseDeprecations')) {
this.#values.verboseDeprecations = changes.verboseDeprecations;
}
}
}
return Configuration;
};

View File

@@ -7,12 +7,12 @@ getJasmineRequireObj().Env = function(j$) {
* calling {@link jasmine.getEnv}.
* @hideconstructor
*/
function Env(options) {
options = options || {};
function Env(envOptions) {
envOptions = envOptions || {};
const self = this;
const GlobalErrors = options.GlobalErrors || j$.GlobalErrors;
const global = options.global || j$.getGlobal();
const GlobalErrors = envOptions.GlobalErrors || j$.GlobalErrors;
const global = envOptions.global || j$.getGlobal();
const realSetTimeout = global.setTimeout;
const realClearTimeout = global.clearTimeout;
@@ -31,12 +31,21 @@ getJasmineRequireObj().Env = function(j$) {
// before it's set to detect load-time errors in browsers
() => this.configuration()
);
const installGlobalErrors = (function() {
const { installGlobalErrors, uninstallGlobalErrors } = (function() {
let installed = false;
return function() {
if (!installed) {
globalErrors.install();
installed = true;
return {
installGlobalErrors() {
if (!installed) {
globalErrors.install();
installed = true;
}
},
uninstallGlobalErrors() {
if (installed) {
globalErrors.uninstall();
installed = false;
}
}
};
})();
@@ -54,134 +63,9 @@ getJasmineRequireObj().Env = function(j$) {
let runner;
let parallelLoadingState = null; // 'specs', 'helpers', or null for non-parallel
/**
* This represents the available options to configure Jasmine.
* Options that are not provided will use their default values.
* @see Env#configure
* @interface Configuration
* @since 3.3.0
*/
const config = {
/**
* Whether to randomize spec execution order
* @name Configuration#random
* @since 3.3.0
* @type Boolean
* @default true
*/
random: true,
/**
* Seed to use as the basis of randomization.
* Null causes the seed to be determined randomly at the start of execution.
* @name Configuration#seed
* @since 3.3.0
* @type (number|string)
* @default null
*/
seed: null,
/**
* Whether to stop execution of the suite after the first spec failure
*
* <p>In parallel mode, `stopOnSpecFailure` works on a "best effort"
* basis. Jasmine will stop execution as soon as practical after a failure
* but it might not be immediate.</p>
* @name Configuration#stopOnSpecFailure
* @since 3.9.0
* @type Boolean
* @default false
*/
stopOnSpecFailure: false,
/**
* Whether to fail the spec if it ran no expectations. By default
* a spec that ran no expectations is reported as passed. Setting this
* to true will report such spec as a failure.
* @name Configuration#failSpecWithNoExpectations
* @since 3.5.0
* @type Boolean
* @default false
*/
failSpecWithNoExpectations: false,
/**
* Whether to cause specs to only have one expectation failure.
* @name Configuration#stopSpecOnExpectationFailure
* @since 3.3.0
* @type Boolean
* @default false
*/
stopSpecOnExpectationFailure: false,
/**
* A function that takes a spec and returns true if it should be executed
* or false if it should be skipped.
* @callback SpecFilter
* @param {Spec} spec - The spec that the filter is being applied to.
* @return boolean
*/
/**
* Function to use to filter specs
* @name Configuration#specFilter
* @since 3.3.0
* @type SpecFilter
* @default A function that always returns true.
*/
specFilter: function() {
return true;
},
/**
* Whether reporters should hide disabled specs from their output.
* Currently only supported by Jasmine's HTMLReporter
* @name Configuration#hideDisabled
* @since 3.3.0
* @type Boolean
* @default false
*/
hideDisabled: false,
/**
* Clean closures when a suite is done running (done by clearing the stored function reference).
* This prevents memory leaks, but you won't be able to run jasmine multiple times.
* @name Configuration#autoCleanClosures
* @since 3.10.0
* @type boolean
* @default true
*/
autoCleanClosures: true,
/**
* Whether to forbid duplicate spec or suite names. If set to true, using
* the same name multiple times in the same immediate parent suite is an
* error.
* @name Configuration#forbidDuplicateNames
* @type boolean
* @default false
*/
forbidDuplicateNames: false,
/**
* Whether to issue warnings for certain deprecated functionality
* every time it's used. If not set or set to false, deprecation warnings
* for methods that tend to be called frequently will be issued only once
* or otherwise throttled to prevent the suite output from being flooded
* with warnings.
* @name Configuration#verboseDeprecations
* @since 3.6.0
* @type Boolean
* @default false
*/
verboseDeprecations: false,
const config = new j$.Configuration();
/**
* Whether to detect late promise rejection handling during spec
* execution. If this option is enabled, a promise rejection that triggers
* the JavaScript runtime's unhandled rejection event will not be treated
* as an error as long as it's handled before the spec finishes.
*
* This option is off by default because it imposes a performance penalty.
* @name Configuration#detectLateRejectionHandling
* @since 5.10.0
* @type Boolean
* @default false
*/
detectLateRejectionHandling: false
};
if (!options.suppressLoadErrors) {
if (!envOptions.suppressLoadErrors) {
installGlobalErrors();
globalErrors.pushListener(function loadtimeErrorHandler(error, event) {
topSuite.result.failedExpectations.push({
@@ -202,42 +86,15 @@ getJasmineRequireObj().Env = function(j$) {
* @argument {Configuration} configuration
* @function
*/
this.configure = function(configuration) {
this.configure = function(changes) {
if (parallelLoadingState) {
throw new Error(
'Jasmine cannot be configured via Env in parallel mode'
);
}
const booleanProps = [
'random',
'failSpecWithNoExpectations',
'hideDisabled',
'stopOnSpecFailure',
'stopSpecOnExpectationFailure',
'autoCleanClosures',
'forbidDuplicateNames',
'detectLateRejectionHandling'
];
booleanProps.forEach(function(prop) {
if (typeof configuration[prop] !== 'undefined') {
config[prop] = !!configuration[prop];
}
});
if (configuration.specFilter) {
config.specFilter = configuration.specFilter;
}
if (typeof configuration.seed !== 'undefined') {
config.seed = configuration.seed;
}
if (configuration.hasOwnProperty('verboseDeprecations')) {
config.verboseDeprecations = configuration.verboseDeprecations;
deprecator.verboseDeprecations(config.verboseDeprecations);
}
config.update(changes);
deprecator.verboseDeprecations(config.verboseDeprecations);
};
/**
@@ -248,11 +105,7 @@ getJasmineRequireObj().Env = function(j$) {
* @returns {Configuration}
*/
this.configuration = function() {
const result = {};
for (const property in config) {
result[property] = config[property];
}
return result;
return config.copy();
};
this.setDefaultSpyStrategy = function(defaultStrategyFn) {
@@ -960,9 +813,7 @@ getJasmineRequireObj().Env = function(j$) {
};
this.cleanup_ = function() {
if (globalErrors) {
globalErrors.uninstall();
}
uninstallGlobalErrors();
};
}

View File

@@ -103,6 +103,12 @@ getJasmineRequireObj().Runner = function(j$) {
// In parallel mode, the jasmineStarted event is separately dispatched
// by jasmine-npm. This event only reaches reporters in non-parallel.
totalSpecsDefined,
/**
* Information about the ordering (random or not) of this execution of the suite.
* @typedef Order
* @property {boolean} random - Whether the suite is running in random order
* @property {string} seed - The random seed
*/
order: order,
parallel: false
});

View File

@@ -1,274 +1,249 @@
getJasmineRequireObj().Spec = function(j$) {
function Spec(attrs) {
this.expectationFactory = attrs.expectationFactory;
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
this.id = attrs.id;
this.filename = attrs.filename;
this.parentSuiteId = attrs.parentSuiteId;
this.description = attrs.description || '';
this.queueableFn = attrs.queueableFn;
this.beforeAndAfterFns =
attrs.beforeAndAfterFns ||
function() {
return { befores: [], afters: [] };
};
this.userContext =
attrs.userContext ||
function() {
return {};
};
this.autoCleanClosures =
attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures;
class Spec {
#autoCleanClosures;
#throwOnExpectationFailure;
#timer;
#metadata;
this.getPath = function() {
return attrs.getPath ? attrs.getPath(this) : [];
};
this.onLateError = attrs.onLateError || function() {};
this.catchingExceptions =
attrs.catchingExceptions ||
function() {
return true;
constructor(attrs) {
this.expectationFactory = attrs.expectationFactory;
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
this.id = attrs.id;
this.filename = attrs.filename;
this.parentSuiteId = attrs.parentSuiteId;
this.description = attrs.description || '';
this.queueableFn = attrs.queueableFn;
this.beforeAndAfterFns =
attrs.beforeAndAfterFns ||
function() {
return { befores: [], afters: [] };
};
this.userContext =
attrs.userContext ||
function() {
return {};
};
this.getPath = function() {
return attrs.getPath ? attrs.getPath(this) : [];
};
this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
this.timer = attrs.timer || new j$.Timer();
if (!this.queueableFn.fn) {
this.exclude();
this.#autoCleanClosures =
attrs.autoCleanClosures === undefined
? true
: !!attrs.autoCleanClosures;
this.onLateError = attrs.onLateError || function() {};
this.#throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
this.#timer = attrs.timer || new j$.Timer();
if (!this.queueableFn.fn) {
this.exclude();
}
this.reset();
}
this.reset();
}
addExpectationResult(passed, data, isError) {
const expectationResult = j$.buildExpectationResult(data);
Spec.prototype.addExpectationResult = function(passed, data, isError) {
const expectationResult = j$.buildExpectationResult(data);
if (passed) {
this.result.passedExpectations.push(expectationResult);
} else {
if (this.reportedDone) {
this.onLateError(expectationResult);
if (passed) {
this.result.passedExpectations.push(expectationResult);
} else {
this.result.failedExpectations.push(expectationResult);
if (this.reportedDone) {
this.onLateError(expectationResult);
} else {
this.result.failedExpectations.push(expectationResult);
// TODO: refactor so that we don't need to override cached status
if (this.result.status) {
this.result.status = 'failed';
// TODO: refactor so that we don't need to override cached status
if (this.result.status) {
this.result.status = 'failed';
}
}
if (this.#throwOnExpectationFailure && !isError) {
throw new j$.errors.ExpectationFailed();
}
}
}
if (this.throwOnExpectationFailure && !isError) {
throw new j$.errors.ExpectationFailed();
getSpecProperty(key) {
this.result.properties = this.result.properties || {};
return this.result.properties[key];
}
setSpecProperty(key, value) {
this.result.properties = this.result.properties || {};
this.result.properties[key] = value;
}
executionStarted() {
this.#timer.start();
}
executionFinished(excluded, failSpecWithNoExp) {
if (this.#autoCleanClosures) {
this.queueableFn.fn = null;
}
this.result.status = this.#status(excluded, failSpecWithNoExp);
this.result.duration = this.#timer.elapsed();
if (this.result.status !== 'failed') {
this.result.debugLogs = null;
}
}
};
Spec.prototype.getSpecProperty = function(key) {
this.result.properties = this.result.properties || {};
return this.result.properties[key];
};
Spec.prototype.setSpecProperty = function(key, value) {
this.result.properties = this.result.properties || {};
this.result.properties[key] = value;
};
Spec.prototype.executionStarted = function() {
this.timer.start();
};
Spec.prototype.executionFinished = function(excluded, failSpecWithNoExp) {
if (this.autoCleanClosures) {
this.queueableFn.fn = null;
reset() {
/**
* @typedef SpecResult
* @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} fullName - The full description including all ancestors of this spec.
* @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe().
* @property {String} filename - Deprecated. The name of the file the spec was defined in.
* Note: The value may be incorrect if zone.js is installed or
* `it`/`fit`/`xit` have been replaced with versions that don't maintain the
* same call stack height as the originals. This property may be removed in
* a future version unless there is enough user interest in keeping it.
* See {@link https://github.com/jasmine/jasmine/issues/2065}.
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec.
* @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec.
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec.
* @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}
* @property {DebugLogEntry[]|null} debugLogs - Messages, if any, that were logged using {@link jasmine.debugLog} during a failing spec.
* @since 2.0.0
*/
this.result = {
id: this.id,
description: this.description,
fullName: this.getFullName(),
parentSuiteId: this.parentSuiteId,
filename: this.filename,
failedExpectations: [],
passedExpectations: [],
deprecationWarnings: [],
pendingReason: this.excludeMessage || '',
duration: null,
properties: null,
debugLogs: null
};
this.markedPending = this.markedExcluding;
this.reportedDone = false;
}
this.result.status = this.status(excluded, failSpecWithNoExp);
this.result.duration = this.timer.elapsed();
handleException(e) {
if (Spec.isPendingSpecException(e)) {
this.pend(extractCustomPendingMessage(e));
return;
}
if (this.result.status !== 'failed') {
this.result.debugLogs = null;
if (e instanceof j$.errors.ExpectationFailed) {
return;
}
this.addExpectationResult(
false,
{
matcherName: '',
passed: false,
expected: '',
actual: '',
error: e
},
true
);
}
pend(message) {
this.markedPending = true;
if (message) {
this.result.pendingReason = message;
}
}
// Like pend(), but pending state will survive reset().
// Useful for fit, xit, where pending state remains.
exclude(message) {
this.markedExcluding = true;
if (this.message) {
this.excludeMessage = message;
}
this.pend(message);
}
// TODO: ensure that all access to result goes through .getResult()
// so that the status is correct.
getResult() {
this.result.status = this.#status();
return this.result;
}
#status(excluded, failSpecWithNoExpectations) {
if (excluded === true) {
return 'excluded';
}
if (this.markedPending) {
return 'pending';
}
if (
this.result.failedExpectations.length > 0 ||
(failSpecWithNoExpectations &&
this.result.failedExpectations.length +
this.result.passedExpectations.length ===
0)
) {
return 'failed';
}
return 'passed';
}
getFullName() {
return this.getPath().join(' ');
}
addDeprecationWarning(deprecation) {
if (typeof deprecation === 'string') {
deprecation = { message: deprecation };
}
this.result.deprecationWarnings.push(
j$.buildExpectationResult(deprecation)
);
}
debugLog(msg) {
if (!this.result.debugLogs) {
this.result.debugLogs = [];
}
/**
* @typedef DebugLogEntry
* @property {String} message - The message that was passed to {@link jasmine.debugLog}.
* @property {number} timestamp - The time when the entry was added, in
* milliseconds from the spec's start time
*/
this.result.debugLogs.push({
message: msg,
timestamp: this.#timer.elapsed()
});
}
};
Spec.prototype.reset = function() {
/**
* @typedef SpecResult
* @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} fullName - The full description including all ancestors of this spec.
* @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe().
* @property {String} filename - Deprecated. The name of the file the spec was defined in.
* Note: The value may be incorrect if zone.js is installed or
* `it`/`fit`/`xit` have been replaced with versions that don't maintain the
* same call stack height as the originals. This property may be removed in
* a future version unless there is enough user interest in keeping it.
* See {@link https://github.com/jasmine/jasmine/issues/2065}.
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec.
* @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec.
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec.
* @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}
* @property {DebugLogEntry[]|null} debugLogs - Messages, if any, that were logged using {@link jasmine.debugLog} during a failing spec.
* @interface Spec
* @see Configuration#specFilter
* @since 2.0.0
*/
this.result = {
id: this.id,
description: this.description,
fullName: this.getFullName(),
parentSuiteId: this.parentSuiteId,
filename: this.filename,
failedExpectations: [],
passedExpectations: [],
deprecationWarnings: [],
pendingReason: this.excludeMessage || '',
duration: null,
properties: null,
debugLogs: null
};
this.markedPending = this.markedExcluding;
this.reportedDone = false;
};
Spec.prototype.handleException = function handleException(e) {
if (Spec.isPendingSpecException(e)) {
this.pend(extractCustomPendingMessage(e));
return;
}
if (e instanceof j$.errors.ExpectationFailed) {
return;
}
this.addExpectationResult(
false,
{
matcherName: '',
passed: false,
expected: '',
actual: '',
error: e
},
true
);
};
/*
* Marks state as pending
* @param {string} [message] An optional reason message
*/
Spec.prototype.pend = function(message) {
this.markedPending = true;
if (message) {
this.result.pendingReason = message;
}
};
/*
* Like {@link Spec#pend}, but pending state will survive {@link Spec#reset}
* Useful for fit, xit, where pending state remains.
* @param {string} [message] An optional reason message
*/
Spec.prototype.exclude = function(message) {
this.markedExcluding = true;
if (this.message) {
this.excludeMessage = message;
}
this.pend(message);
};
// TODO: ensure that all access to result goes through .getResult()
// so that the status is correct.
Spec.prototype.getResult = function() {
this.result.status = this.status();
return this.result;
};
Spec.prototype.status = function(excluded, failSpecWithNoExpectations) {
if (excluded === true) {
return 'excluded';
}
if (this.markedPending) {
return 'pending';
}
if (
this.result.failedExpectations.length > 0 ||
(failSpecWithNoExpectations &&
this.result.failedExpectations.length +
this.result.passedExpectations.length ===
0)
) {
return 'failed';
}
return 'passed';
};
Spec.prototype.getFullName = function() {
return this.getPath().join(' ');
};
Spec.prototype.addDeprecationWarning = function(deprecation) {
if (typeof deprecation === 'string') {
deprecation = { message: deprecation };
}
this.result.deprecationWarnings.push(
j$.buildExpectationResult(deprecation)
);
};
Spec.prototype.debugLog = function(msg) {
if (!this.result.debugLogs) {
this.result.debugLogs = [];
}
/**
* @typedef DebugLogEntry
* @property {String} message - The message that was passed to {@link jasmine.debugLog}.
* @property {number} timestamp - The time when the entry was added, in
* milliseconds from the spec's start time
*/
this.result.debugLogs.push({
message: msg,
timestamp: this.timer.elapsed()
});
};
const extractCustomPendingMessage = function(e) {
const fullMessage = e.toString(),
boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage),
boilerplateEnd =
boilerplateStart + Spec.pendingSpecExceptionMessage.length;
return fullMessage.slice(boilerplateEnd);
};
Spec.pendingSpecExceptionMessage = '=> marked Pending';
Spec.isPendingSpecException = function(e) {
return !!(
e &&
e.toString &&
e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1
);
};
/**
* @interface Spec
* @see Configuration#specFilter
* @since 2.0.0
*/
Object.defineProperty(Spec.prototype, 'metadata', {
// NOTE: Although most of jasmine-core only exposes these metadata objects,
// actual Spec instances are still passed to Configuration#specFilter. Until
// that is fixed, it's important to make sure that all metadata properties
// also exist in compatible form on the underlying Spec.
get: function() {
if (!this.metadata_) {
this.metadata_ = {
get metadata() {
// NOTE: Although most of jasmine-core only exposes these metadata objects,
// actual Spec instances are still passed to Configuration#specFilter. Until
// that is fixed, it's important to make sure that all metadata properties
// also exist in compatible form on the underlying Spec.
if (!this.#metadata) {
this.#metadata = {
/**
* The unique ID of this spec.
* @name Spec#id
@@ -307,9 +282,28 @@ getJasmineRequireObj().Spec = function(j$) {
};
}
return this.metadata_;
return this.#metadata;
}
});
}
const extractCustomPendingMessage = function(e) {
const fullMessage = e.toString(),
boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage),
boilerplateEnd =
boilerplateStart + Spec.pendingSpecExceptionMessage.length;
return fullMessage.slice(boilerplateEnd);
};
Spec.pendingSpecExceptionMessage = '=> marked Pending';
Spec.isPendingSpecException = function(e) {
return !!(
e &&
e.toString &&
e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1
);
};
return Spec;
};

View File

@@ -1,86 +1,276 @@
getJasmineRequireObj().Suite = function(j$) {
function Suite(attrs) {
this.env = attrs.env;
this.id = attrs.id;
this.parentSuite = attrs.parentSuite;
this.description = attrs.description;
this.reportedParentSuiteId = attrs.reportedParentSuiteId;
this.filename = attrs.filename;
this.expectationFactory = attrs.expectationFactory;
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
this.autoCleanClosures =
attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures;
this.onLateError = attrs.onLateError || function() {};
class Suite {
#reportedParentSuiteId;
#throwOnExpectationFailure;
#autoCleanClosures;
#timer;
this.beforeFns = [];
this.afterFns = [];
this.beforeAllFns = [];
this.afterAllFns = [];
this.timer = attrs.timer || new j$.Timer();
this.children = [];
constructor(attrs) {
this.id = attrs.id;
this.parentSuite = attrs.parentSuite;
this.description = attrs.description;
this.filename = attrs.filename;
this.expectationFactory = attrs.expectationFactory;
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
this.onLateError = attrs.onLateError || function() {};
this.#reportedParentSuiteId = attrs.reportedParentSuiteId;
this.#throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
this.#autoCleanClosures =
attrs.autoCleanClosures === undefined
? true
: !!attrs.autoCleanClosures;
this.#timer = attrs.timer || new j$.Timer();
this.reset();
}
this.beforeFns = [];
this.afterFns = [];
this.beforeAllFns = [];
this.afterAllFns = [];
this.children = [];
Suite.prototype.setSuiteProperty = function(key, value) {
this.result.properties = this.result.properties || {};
this.result.properties[key] = value;
};
this.reset();
}
Suite.prototype.getFullName = function() {
const fullName = [];
for (
let parentSuite = this;
parentSuite;
parentSuite = parentSuite.parentSuite
) {
if (parentSuite.parentSuite) {
fullName.unshift(parentSuite.description);
setSuiteProperty(key, value) {
this.result.properties = this.result.properties || {};
this.result.properties[key] = value;
}
getFullName() {
const fullName = [];
for (
let parentSuite = this;
parentSuite;
parentSuite = parentSuite.parentSuite
) {
if (parentSuite.parentSuite) {
fullName.unshift(parentSuite.description);
}
}
return fullName.join(' ');
}
// Mark the suite with "pending" status
pend() {
this.markedPending = true;
}
// Like pend(), but pending state will survive reset().
// Useful for fdescribe, xdescribe, where pending state should remain.
exclude() {
this.pend();
this.markedExcluding = true;
}
beforeEach(fn) {
this.beforeFns.unshift({ ...fn, suite: this });
}
beforeAll(fn) {
this.beforeAllFns.push({ ...fn, type: 'beforeAll', suite: this });
}
afterEach(fn) {
this.afterFns.unshift({ ...fn, suite: this, type: 'afterEach' });
}
afterAll(fn) {
this.afterAllFns.unshift({ ...fn, type: 'afterAll' });
}
startTimer() {
this.#timer.start();
}
endTimer() {
this.result.duration = this.#timer.elapsed();
}
cleanupBeforeAfter() {
if (this.#autoCleanClosures) {
removeFns(this.beforeAllFns);
removeFns(this.afterAllFns);
removeFns(this.beforeFns);
removeFns(this.afterFns);
}
}
return fullName.join(' ');
};
/*
* Mark the suite with "pending" status
*/
Suite.prototype.pend = function() {
this.markedPending = true;
};
reset() {
/**
* @typedef SuiteResult
* @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} fullName - The full description including all ancestors of this suite.
* @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe().
* @property {String} filename - Deprecated. The name of the file the suite was defined in.
* Note: The value may be incorrect if zone.js is installed or
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions that
* don't maintain the same call stack height as the originals. This property
* may be removed in a future version unless there is enough user interest
* in keeping it. See {@link https://github.com/jasmine/jasmine/issues/2065}.
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
* @property {ExpectationResult[]} 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}
* @since 2.0.0
*/
this.result = {
id: this.id,
description: this.description,
fullName: this.getFullName(),
parentSuiteId: this.#reportedParentSuiteId,
filename: this.filename,
failedExpectations: [],
deprecationWarnings: [],
duration: null,
properties: null
};
this.markedPending = this.markedExcluding;
this.children.forEach(function(child) {
child.reset();
});
this.reportedDone = false;
}
/*
* Like {@link Suite#pend}, but pending state will survive {@link Spec#reset}
* Useful for fdescribe, xdescribe, where pending state should remain.
*/
Suite.prototype.exclude = function() {
this.pend();
this.markedExcluding = true;
};
removeChildren() {
this.children = [];
}
Suite.prototype.beforeEach = function(fn) {
this.beforeFns.unshift({ ...fn, suite: this });
};
addChild(child) {
this.children.push(child);
}
Suite.prototype.beforeAll = function(fn) {
this.beforeAllFns.push({ ...fn, type: 'beforeAll', suite: this });
};
#status() {
if (this.markedPending) {
return 'pending';
}
Suite.prototype.afterEach = function(fn) {
this.afterFns.unshift({ ...fn, suite: this, type: 'afterEach' });
};
if (this.result.failedExpectations.length > 0) {
return 'failed';
} else {
return 'passed';
}
}
Suite.prototype.afterAll = function(fn) {
this.afterAllFns.unshift({ ...fn, type: 'afterAll' });
};
getResult() {
this.result.status = this.#status();
return this.result;
}
Suite.prototype.startTimer = function() {
this.timer.start();
};
canBeReentered() {
return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
}
Suite.prototype.endTimer = function() {
this.result.duration = this.timer.elapsed();
};
sharedUserContext() {
if (!this.sharedContext) {
this.sharedContext = this.parentSuite
? this.parentSuite.clonedSharedUserContext()
: new j$.UserContext();
}
return this.sharedContext;
}
clonedSharedUserContext() {
return j$.UserContext.fromExisting(this.sharedUserContext());
}
handleException() {
if (arguments[0] instanceof j$.errors.ExpectationFailed) {
return;
}
const data = {
matcherName: '',
passed: false,
expected: '',
actual: '',
error: arguments[0]
};
const failedExpectation = j$.buildExpectationResult(data);
if (!this.parentSuite) {
failedExpectation.globalErrorType = 'afterAll';
}
if (this.reportedDone) {
this.onLateError(failedExpectation);
} else {
this.result.failedExpectations.push(failedExpectation);
}
}
onMultipleDone() {
let msg;
// Issue an error. Include the context ourselves and pass
// ignoreRunnable: true, since getting here always means that we've already
// moved on and the current runnable isn't the one that caused the problem.
if (this.parentSuite) {
msg =
"An asynchronous beforeAll or afterAll function called its 'done' " +
'callback more than once.\n' +
'(in suite: ' +
this.getFullName() +
')';
} else {
msg =
'A top-level beforeAll or afterAll function called its ' +
"'done' callback more than once.";
}
this.onLateError(new Error(msg));
}
addExpectationResult() {
if (isFailure(arguments)) {
const data = arguments[1];
const expectationResult = j$.buildExpectationResult(data);
if (this.reportedDone) {
this.onLateError(expectationResult);
} else {
this.result.failedExpectations.push(expectationResult);
// TODO: refactor so that we don't need to override cached status
if (this.result.status) {
this.result.status = 'failed';
}
}
if (this.#throwOnExpectationFailure) {
throw new j$.errors.ExpectationFailed();
}
}
}
addDeprecationWarning(deprecation) {
if (typeof deprecation === 'string') {
deprecation = { message: deprecation };
}
this.result.deprecationWarnings.push(
j$.buildExpectationResult(deprecation)
);
}
hasChildWithDescription(description) {
for (const child of this.children) {
if (child.description === description) {
return true;
}
}
return false;
}
get metadata() {
if (!this.metadata_) {
this.metadata_ = new SuiteMetadata(this);
}
return this.metadata_;
}
}
function removeFns(queueableFns) {
for (const qf of queueableFns) {
@@ -88,250 +278,64 @@ getJasmineRequireObj().Suite = function(j$) {
}
}
Suite.prototype.cleanupBeforeAfter = function() {
if (this.autoCleanClosures) {
removeFns(this.beforeAllFns);
removeFns(this.afterAllFns);
removeFns(this.beforeFns);
removeFns(this.afterFns);
}
};
Suite.prototype.reset = function() {
/**
* @typedef SuiteResult
* @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} fullName - The full description including all ancestors of this suite.
* @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe().
* @property {String} filename - Deprecated. The name of the file the suite was defined in.
* Note: The value may be incorrect if zone.js is installed or
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions that
* don't maintain the same call stack height as the originals. This property
* may be removed in a future version unless there is enough user interest
* in keeping it. See {@link https://github.com/jasmine/jasmine/issues/2065}.
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
* @property {ExpectationResult[]} 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}
* @since 2.0.0
*/
this.result = {
id: this.id,
description: this.description,
fullName: this.getFullName(),
parentSuiteId: this.reportedParentSuiteId,
filename: this.filename,
failedExpectations: [],
deprecationWarnings: [],
duration: null,
properties: null
};
this.markedPending = this.markedExcluding;
this.children.forEach(function(child) {
child.reset();
});
this.reportedDone = false;
};
Suite.prototype.removeChildren = function() {
this.children = [];
};
Suite.prototype.addChild = function(child) {
this.children.push(child);
};
Suite.prototype.status = function() {
if (this.markedPending) {
return 'pending';
}
if (this.result.failedExpectations.length > 0) {
return 'failed';
} else {
return 'passed';
}
};
Suite.prototype.canBeReentered = function() {
return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
};
Suite.prototype.getResult = function() {
this.result.status = this.status();
return this.result;
};
Suite.prototype.sharedUserContext = function() {
if (!this.sharedContext) {
this.sharedContext = this.parentSuite
? this.parentSuite.clonedSharedUserContext()
: new j$.UserContext();
}
return this.sharedContext;
};
Suite.prototype.clonedSharedUserContext = function() {
return j$.UserContext.fromExisting(this.sharedUserContext());
};
Suite.prototype.handleException = function() {
if (arguments[0] instanceof j$.errors.ExpectationFailed) {
return;
}
const data = {
matcherName: '',
passed: false,
expected: '',
actual: '',
error: arguments[0]
};
const failedExpectation = j$.buildExpectationResult(data);
if (!this.parentSuite) {
failedExpectation.globalErrorType = 'afterAll';
}
if (this.reportedDone) {
this.onLateError(failedExpectation);
} else {
this.result.failedExpectations.push(failedExpectation);
}
};
Suite.prototype.onMultipleDone = function() {
let msg;
// Issue a deprecation. Include the context ourselves and pass
// ignoreRunnable: true, since getting here always means that we've already
// moved on and the current runnable isn't the one that caused the problem.
if (this.parentSuite) {
msg =
"An asynchronous beforeAll or afterAll function called its 'done' " +
'callback more than once.\n' +
'(in suite: ' +
this.getFullName() +
')';
} else {
msg =
'A top-level beforeAll or afterAll function called its ' +
"'done' callback more than once.";
}
this.onLateError(new Error(msg));
};
Suite.prototype.addExpectationResult = function() {
if (isFailure(arguments)) {
const data = arguments[1];
const expectationResult = j$.buildExpectationResult(data);
if (this.reportedDone) {
this.onLateError(expectationResult);
} else {
this.result.failedExpectations.push(expectationResult);
// TODO: refactor so that we don't need to override cached status
if (this.result.status) {
this.result.status = 'failed';
}
}
if (this.throwOnExpectationFailure) {
throw new j$.errors.ExpectationFailed();
}
}
};
Suite.prototype.addDeprecationWarning = function(deprecation) {
if (typeof deprecation === 'string') {
deprecation = { message: deprecation };
}
this.result.deprecationWarnings.push(
j$.buildExpectationResult(deprecation)
);
};
Suite.prototype.hasChildWithDescription = function(description) {
for (const child of this.children) {
if (child.description === description) {
return true;
}
}
return false;
};
Object.defineProperty(Suite.prototype, 'metadata', {
get: function() {
if (!this.metadata_) {
this.metadata_ = new SuiteMetadata(this);
}
return this.metadata_;
}
});
/**
* @interface Suite
* @see Env#topSuite
* @since 2.0.0
*/
function SuiteMetadata(suite) {
this.suite_ = suite;
/**
* The unique ID of this suite.
* @name Suite#id
* @readonly
* @type {string}
* @since 2.0.0
*/
this.id = suite.id;
class SuiteMetadata {
#suite;
/**
* The parent of this suite, or null if this is the top suite.
* @name Suite#parentSuite
* @readonly
* @type {Suite}
*/
this.parentSuite = suite.parentSuite ? suite.parentSuite.metadata : null;
constructor(suite) {
this.#suite = suite;
/**
* The unique ID of this suite.
* @name Suite#id
* @readonly
* @type {string}
* @since 2.0.0
*/
this.id = suite.id;
/**
* The description passed to the {@link describe} that created this suite.
* @name Suite#description
* @readonly
* @type {string}
* @since 2.0.0
*/
this.description = suite.description;
}
/**
* The parent of this suite, or null if this is the top suite.
* @name Suite#parentSuite
* @readonly
* @type {Suite}
*/
this.parentSuite = suite.parentSuite ? suite.parentSuite.metadata : null;
/**
* The full description including all ancestors of this suite.
* @name Suite#getFullName
* @function
* @returns {string}
* @since 2.0.0
*/
SuiteMetadata.prototype.getFullName = function() {
return this.suite_.getFullName();
};
/**
* The suite's children.
* @name Suite#children
* @type {Array.<(Spec|Suite)>}
* @since 2.0.0
*/
Object.defineProperty(SuiteMetadata.prototype, 'children', {
get: function() {
return this.suite_.children.map(child => child.metadata);
/**
* The description passed to the {@link describe} that created this suite.
* @name Suite#description
* @readonly
* @type {string}
* @since 2.0.0
*/
this.description = suite.description;
}
});
/**
* The full description including all ancestors of this suite.
* @name Suite#getFullName
* @function
* @returns {string}
* @since 2.0.0
*/
getFullName() {
return this.#suite.getFullName();
}
/**
* The suite's children.
* @name Suite#children
* @type {Array.<(Spec|Suite)>}
* @since 2.0.0
*/
get children() {
return this.#suite.children.map(child => child.metadata);
}
}
function isFailure(args) {
return !args[0];

View File

@@ -23,34 +23,9 @@ getJasmineRequireObj().TreeRunner = function(j$) {
async execute() {
this.#hasFailures = false;
const topSuite = this.#executionTree.topSuite;
const wrappedChildren = this.#wrapNodes(
this.#executionTree.childrenOfTopSuite()
);
const queueableFns = this.#addBeforeAndAfterAlls(
topSuite,
wrappedChildren
);
await new Promise(resolve => {
this.#runQueue({
queueableFns,
userContext: this.#executionTree.topSuite.sharedUserContext(),
onException: function() {
topSuite.handleException.apply(topSuite, arguments);
}.bind(this),
onComplete: resolve,
onMultipleDone: topSuite.onMultipleDone
? topSuite.onMultipleDone.bind(topSuite)
: null,
SkipPolicy: this.#suiteSkipPolicy()
});
this.#executeSuiteSegment(this.#executionTree.topSuite, 0, resolve);
});
if (topSuite.hadBeforeAllFailure) {
await this.#reportChildrenOfBeforeAllFailure(topSuite);
}
return { hasFailures: this.#hasFailures };
}
@@ -72,7 +47,10 @@ getJasmineRequireObj().TreeRunner = function(j$) {
_executeSpec(spec, specOverallDone) {
const onStart = next => {
this.#currentRunableTracker.setCurrentSpec(spec);
this.#runableResources.initForRunable(spec.id, spec.parentSuiteId);
this.#runableResources.initForRunable(
spec.id,
spec.parentSuiteId || this.#executionTree.topSuite.id
);
this.#reportDispatcher.specStarted(spec.result).then(next);
};
const resultCallback = (result, next) => {
@@ -142,19 +120,7 @@ getJasmineRequireObj().TreeRunner = function(j$) {
fns.unshift(start);
if (config.detectLateRejectionHandling) {
// Conditional because the setTimeout imposes a significant performance
// penalty in suites with lots of fast specs.
const globalErrors = this.#globalErrors;
fns.push({
fn: done => {
// setTimeout is necessary to trigger rejectionhandled events
// TODO: let clearStack know about this so it doesn't do redundant setTimeouts
this.#setTimeout(function() {
globalErrors.reportUnhandledRejections();
done();
});
}
});
fns.push(this.#lateUnhandledRejectionChecker());
}
fns.push(complete);
@@ -162,28 +128,48 @@ getJasmineRequireObj().TreeRunner = function(j$) {
}
#executeSuiteSegment(suite, segmentNumber, done) {
const wrappedChildren = this.#wrapNodes(
this.#executionTree.childrenOfSuiteSegment(suite, segmentNumber)
);
const onStart = {
fn: next => {
this.#suiteSegmentStart(suite, next);
const isTopSuite = suite === this.#executionTree.topSuite;
const isExcluded = this.#executionTree.isExcluded(suite);
let befores = [];
let afters = [];
if (suite.beforeAllFns.length > 0 && !isExcluded) {
befores = [...suite.beforeAllFns];
if (this.#getConfig().detectLateRejectionHandling) {
befores.push(this.#lateUnhandledRejectionChecker());
}
};
}
if (suite.afterAllFns.length > 0 && !isExcluded) {
afters = [...suite.afterAllFns];
if (this.#getConfig().detectLateRejectionHandling) {
afters.push(this.#lateUnhandledRejectionChecker());
}
}
const children = isTopSuite
? this.#executionTree.childrenOfTopSuite()
: this.#executionTree.childrenOfSuiteSegment(suite, segmentNumber);
const queueableFns = [
onStart,
...this.#addBeforeAndAfterAlls(suite, wrappedChildren)
...befores,
...this.#wrapNodes(children),
...afters
];
if (!isTopSuite) {
queueableFns.unshift({
fn: next => {
this.#suiteSegmentStart(suite, next);
}
});
}
this.#runQueue({
// TODO: if onComplete always takes 0-1 arguments (and it probably does)
// then it can be switched to an arrow fn with a named arg.
onComplete: function() {
const args = Array.prototype.slice.call(arguments, [0]);
onComplete: maybeError => {
this.#suiteSegmentComplete(suite, suite.getResult(), () => {
done.apply(undefined, args);
done(maybeError);
});
}.bind(this),
},
queueableFns,
userContext: suite.sharedUserContext(),
onException: function() {
@@ -196,6 +182,24 @@ getJasmineRequireObj().TreeRunner = function(j$) {
});
}
// Returns a queueable fn that reports any still-unhandled rejections in
// cases where detectLateRejectionHandling is enabled. Should only be called
// when detectLateRejectionHandling is enabled, because the setTimeout
// imposes a significant performance penalty in suites with lots of fast
// specs.
#lateUnhandledRejectionChecker() {
const globalErrors = this.#globalErrors;
return {
fn: done => {
// setTimeout is necessary to trigger rejectionhandled events
this.#setTimeout(function() {
globalErrors.reportUnhandledRejections();
done();
});
}
};
}
#suiteSegmentStart(suite, next) {
this.#currentRunableTracker.pushSuite(suite);
this.#runableResources.initForRunable(suite.id, suite.parentSuite.id);
@@ -204,26 +208,35 @@ getJasmineRequireObj().TreeRunner = function(j$) {
}
#suiteSegmentComplete(suite, result, next) {
suite.cleanupBeforeAfter();
const isTopSuite = suite === this.#executionTree.topSuite;
if (suite !== this.#currentRunableTracker.currentSuite()) {
throw new Error('Tried to complete the wrong suite');
if (!isTopSuite) {
if (suite !== this.#currentRunableTracker.currentSuite()) {
throw new Error('Tried to complete the wrong suite');
}
// suite.cleanupBeforeAfter() is conditional because calling it on the
// top suite breaks parallel mode. The top suite is reentered every time
// a runner runs another file, so its before and after fns need to be
// preserved.
suite.cleanupBeforeAfter();
this.#runableResources.clearForRunable(suite.id);
this.#currentRunableTracker.popSuite();
if (result.status === 'failed') {
this.#hasFailures = true;
}
suite.endTimer();
}
this.#runableResources.clearForRunable(suite.id);
this.#currentRunableTracker.popSuite();
if (result.status === 'failed') {
this.#hasFailures = true;
}
suite.endTimer();
const finish = isTopSuite
? next
: () => this.#reportSuiteDone(suite, result, next);
if (suite.hadBeforeAllFailure) {
this.#reportChildrenOfBeforeAllFailure(suite).then(() => {
this.#reportSuiteDone(suite, result, next);
});
this.#reportChildrenOfBeforeAllFailure(suite).then(finish);
} else {
this.#reportSuiteDone(suite, result, next);
finish();
}
}
@@ -280,16 +293,6 @@ getJasmineRequireObj().TreeRunner = function(j$) {
}
}
#addBeforeAndAfterAlls(suite, wrappedChildren) {
if (this.#executionTree.isExcluded(suite)) {
return wrappedChildren;
}
return suite.beforeAllFns
.concat(wrappedChildren)
.concat(suite.afterAllFns);
}
#suiteSkipPolicy() {
if (this.#getConfig().stopOnSpecFailure) {
return j$.CompleteOnFirstErrorSkipPolicy;

View File

@@ -43,6 +43,7 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
j$.Clock = jRequire.Clock();
j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$);
j$.Deprecator = jRequire.Deprecator(j$);
j$.Configuration = jRequire.Configuration(j$);
j$.Env = jRequire.Env(j$);
j$.StackTrace = jRequire.StackTrace(j$);
j$.ExceptionFormatter = jRequire.ExceptionFormatter(j$);

View File

@@ -47,6 +47,13 @@ jasmineRequire.HtmlReporter = function(j$) {
}
};
/**
* @class HtmlReporter
* @classdesc Displays results and allows re-running individual specs and suites.
* @implements {Reporter}
* @param options Options object. See lib/jasmine-core/boot1.js for details.
* @since 1.2.0
*/
function HtmlReporter(options) {
function config() {
return (options.env && options.env.configuration()) || {};
@@ -64,6 +71,11 @@ jasmineRequire.HtmlReporter = function(j$) {
const deprecationWarnings = [];
const failures = [];
/**
* Initializes the reporter. Should be called before {@link Env#execute}.
* @function
* @name HtmlReporter#initialize
*/
this.initialize = function() {
clearPrior();
htmlReporterMain = createDom(

View File

@@ -1,4 +1,6 @@
jasmineRequire.HtmlSpecFilter = function() {
// Legacy HTML spec filter, preserved for backward compatibility with
// boot files that predate HtmlExactSpecFilterV2
function HtmlSpecFilter(options) {
const filterString =
options &&
@@ -6,6 +8,13 @@ jasmineRequire.HtmlSpecFilter = function() {
options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
const filterPattern = new RegExp(filterString);
/**
* Determines whether the spec with the specified name should be executed.
* @name HtmlSpecFilter#matches
* @function
* @param {string} specName The full name of the spec
* @returns {boolean}
*/
this.matches = function(specName) {
return filterPattern.test(specName);
};

View File

@@ -1,36 +1,55 @@
jasmineRequire.QueryString = function() {
function QueryString(options) {
this.navigateWithNewParam = function(key, value) {
options.getWindowLocation().search = this.fullStringWithNewParam(
/**
* Reads and manipulates the query string.
* @since 2.0.0
*/
class QueryString {
#getWindowLocation;
/**
* @param options Object with a getWindowLocation property, which should be
* a function returning the current value of window.location.
*/
constructor(options) {
this.#getWindowLocation = options.getWindowLocation;
}
/**
* Sets the specified query parameter and navigates to the resulting URL.
* @param {string} key
* @param {string} value
*/
navigateWithNewParam(key, value) {
this.#getWindowLocation().search = this.fullStringWithNewParam(
key,
value
);
};
this.fullStringWithNewParam = function(key, value) {
const paramMap = queryStringToParamMap();
paramMap[key] = value;
return toQueryString(paramMap);
};
this.getParam = function(key) {
return queryStringToParamMap()[key];
};
return this;
function toQueryString(paramMap) {
const qStrPairs = [];
for (const prop in paramMap) {
qStrPairs.push(
encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])
);
}
return '?' + qStrPairs.join('&');
}
function queryStringToParamMap() {
const paramStr = options.getWindowLocation().search.substring(1);
/**
* Returns a new URL based on the current location, with the specified
* query parameter set.
* @param {string} key
* @param {string} value
* @return {string}
*/
fullStringWithNewParam(key, value) {
const paramMap = this.#queryStringToParamMap();
paramMap[key] = value;
return toQueryString(paramMap);
}
/**
* Gets the value of the specified query parameter.
* @param {string} key
* @return {string}
*/
getParam(key) {
return this.#queryStringToParamMap()[key];
}
#queryStringToParamMap() {
const paramStr = this.#getWindowLocation().search.substring(1);
let params = [];
const paramMap = {};
@@ -50,5 +69,15 @@ jasmineRequire.QueryString = function() {
}
}
function toQueryString(paramMap) {
const qStrPairs = [];
for (const prop in paramMap) {
qStrPairs.push(
encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])
);
}
return '?' + qStrPairs.join('&');
}
return QueryString;
};