Compare commits

...

113 Commits

Author SHA1 Message Date
Steve Gravrock
3de4512681 Bump version to 6.1.0
Some checks are pending
Test in latest available Safari / build (push) Waiting to run
2026-02-22 10:10:40 -08:00
Steve Gravrock
4bf333ed38 rm unnecessary and deprecated eslint-env comments 2026-02-21 11:15:57 -08:00
Steve Gravrock
4f5ef7c2d7 Drop support for Safari 16 and 17
Safari 16 and 17 runners are no longer reliably available in CI. Saucelabs
still provides them, but session creation failures have been frequent for
weeks now. When this has happened in the past, it's been a prelude to
Saucelabs dropping the affected Safari versions altogether.

We could live with retrying ~30-50% of test runs in the hope that things
might improve, but it's probably better to just rip the band-aid off.
2026-02-21 11:14:32 -08:00
Steve Gravrock
5de03beea1 Fix test failure in Node 20 and 22 parallel tests 2026-02-07 20:03:59 -08:00
Steve Gravrock
42baa422b3 Formatting 2026-02-07 18:36:36 -08:00
Steve Gravrock
6af5d24b3b Improve formatting of AggregateErrors 2026-02-07 18:22:12 -08:00
Steve Gravrock
b88ce2d49f Improve AggregateError specs
* Assert that the right inner stack traces are in the right places
* Drop the integration tests. All of the AggregateError-specific code
  is in ExceptionFormatter, so the integration tests just duplicate
  existing integration tests and the ExceptionFormatter specs.
2026-02-07 17:41:39 -08:00
Steve Gravrock
c216ae1d13 Merge branch 'puglyfe-aggregate-errors'
* Adds support for AggregateError
* Merges #2093 from @puglyfe
* Fixes #2063
2026-02-07 16:01:06 -08:00
Charley
319776d241 Report the constituent errors of an AggregateError
Fixes #2063
2026-02-07 11:16:57 -08:00
Steve Gravrock
1d0718dc2f Fix MAX_PRETTY_PRINT_CHARS default jsdoc in source code too 2026-01-19 14:58:15 -08:00
Steve Gravrock
929694310e Merge branch 'fixDefaultsJsDoc' of github.com:HolgerJeromin/jasmine
* Merges #2091 from @HolgerJeromin
2026-01-19 08:15:42 -08:00
Steve Gravrock
7379a3a11b Bump version to 6.0.1
Some checks are pending
Test in latest available Safari / build (push) Waiting to run
2026-01-19 07:59:36 -08:00
Holger Jeromin
4db18aafce Fix default MAX_PRETTY_PRINT_CHARS in JsDoc 2026-01-19 13:41:08 +01:00
Steve Gravrock
066669cfee Revert "Temporarily (I hope) disable testing against Safari 16 and 17"
This reverts commit 6755b03f12.
2026-01-18 20:51:20 -08:00
Steve Gravrock
87177d9d43 Fix browser ESM deprecation wraning
Previously, the warning was issued if jasmineRequire.core was called from
an ES module rather than being defined in an ES module.
2026-01-17 17:12:32 -08:00
Steve Gravrock
0c75a154a8 Fix typo 2026-01-17 12:00:58 -08:00
Steve Gravrock
0a6f6d2b0e Bump version to 6.0.0
Some checks are pending
Test in latest available Safari / build (push) Waiting to run
2026-01-17 11:35:56 -08:00
Steve Gravrock
6755b03f12 Temporarily (I hope) disable testing against Safari 16 and 17
Saucelabs VMs for these browsers currently fail to start. There was a
successful run last night against commit e759ddced2.
2026-01-17 11:25:22 -08:00
Steve Gravrock
e759ddced2 Clarify monkey patching deprecation warning 2026-01-11 19:23:51 -08:00
Steve Gravrock
1ad28d8515 Update copyright date 2026-01-11 19:23:25 -08:00
Steve Gravrock
3d36b11c8f rm bogus @optional jsdoc tags 2025-12-30 12:18:22 -08:00
Steve Gravrock
a15df6d455 Document that globals may be overwritten 2025-12-30 12:15:33 -08:00
Steve Gravrock
9f0488dc32 Bump version to 6.0.0-beta.1
Some checks are pending
Test in latest available Safari / build (push) Waiting to run
2025-12-06 10:56:56 -08:00
Steve Gravrock
f3dba82b04 Revert to using window.onload
To support top level await, jasmine-browser-runner needs to be able
to delay env execution until after spec files have initialized. The
old-fashioned event listener style makes that straightforward.
2025-12-01 18:33:08 -08:00
Steve Gravrock
c999ce0787 Update some dev dependencies 2025-12-01 17:57:37 -08:00
Steve Gravrock
5b76bf9552 Merge branch '6.0' 2025-12-01 17:48:49 -08:00
Steve Gravrock
9cf9b856b0 Bump version to 5.13.0
Some checks are pending
Test in latest available Safari / build (push) Waiting to run
2025-12-01 17:25:03 -08:00
Steve Gravrock
db6c142afd Copy 6.0.0-beta.0 release notes from branch 2025-11-28 11:49:05 -08:00
Steve Gravrock
79405426fa Bump version to 6.0.0-beta.0
Some checks are pending
Test in latest available Safari / build (push) Waiting to run
2025-11-28 11:35:46 -08:00
Steve Gravrock
00b09a9a04 Simplify HtmlReporterV2 initialization and boot1.js 2025-11-28 09:47:31 -08:00
Steve Gravrock
f5e9b61f73 Replace isArray helper with native Array.isArray 2025-11-28 08:09:51 -08:00
Steve Gravrock
4371081763 Deprecate jsApiReporter and remove it from boot1.js
jsApiReporter was initially added as part of the pre-1.0 Ruby based browser
runner. It looks like it was designed to resolve a race condition betweeen
jasmine-core's startup in the browser and the Ruby runner's startup. Modern
runners handle that either by buffering messages in a custom reporter (e.g.
jasmine-browser-runner's BatchReporter) or by calling env.execute() after a
communication channel has been set up (e.g. the old Jasmine ruby gem). In
any other context, a custom reporter is easier to use than jsApiReporter
because it doesn't require polling.

Adding jsApiReporter to the env imposes small but measurable penalties in
time and space, both of which are proportional to the size of the test
suite.

Other than jasmine-py and Testdouble's jasmine-rails gem, neither of which
ever supported jasmine-core 4 or later, I can find scant evidence of
interest and no evidence of usage after about 2012.
2025-11-28 08:08:50 -08:00
Steve Gravrock
9530ff68ab Fix event listener leaks in own specs 2025-11-28 07:13:52 -08:00
Steve Gravrock
51dc79dc22 rm dead code 2025-11-27 08:45:24 -08:00
Steve Gravrock
b559faec2a HtmlReporterV2: show correct progress when running a subset of specs 2025-11-27 07:24:45 -08:00
Steve Gravrock
d7b1456584 Document the set of possible spec statuses 2025-11-27 07:24:44 -08:00
Steve Gravrock
23894c1a0a Detect monkey patching and emit a deprecation warning.
This isn't comprehensive but it should be broad enough to ensure that most
people who would be affected by blocking monkey patching see a warning.
Covers the jasmine namespace as well as classes that are monkey patched by
zone.js.

Replacing globals (describe/it/etc) doesn't trigger a warning because they
belong to the user and are expected to be replaced.
2025-11-27 07:23:57 -08:00
Steve Gravrock
1e691b2470 Prettier 2025-11-27 06:53:27 -08:00
Steve Gravrock
c5555dd8cc Better debug logging for spec that occasionally fails in FF 2025-11-27 06:30:09 -08:00
Steve Gravrock
32168be6c7 Statically expose pretty printer as jasmine.pp
Pretty printing is occasionally useful outside of the places where a
configured pretty printer is injected (matchers and asymmetric equality
testers). Users sometimes use the private basicPrettyPrinter for that.
jasmine.pp is part of the public interface and uses the current runable's
custom object formatters.
2025-11-22 09:46:45 -08:00
Steve Gravrock
78c14f81a8 Copy 6.0.0-alpha.2 release notes from branch 2025-11-15 14:40:39 -08:00
Steve Gravrock
788eba34b6 Bump version to 6.0.0-alpha.2
Some checks are pending
Test in latest available Safari / build (push) Waiting to run
2025-11-15 10:22:27 -08:00
Steve Gravrock
1f31b4b0f6 Increase font size in HTML reporters 2025-11-15 09:06:19 -08:00
Steve Gravrock
00a8a11904 Merge branch 'slow-reporter' into 6.0 2025-11-15 09:01:44 -08:00
Steve Gravrock
3899d83fb6 HtmlReporterV2: show median and mean spec run time 2025-11-15 09:01:06 -08:00
Steve Gravrock
8f13684a01 Add a slowest specs list to HTMLReporterV2 2025-11-15 08:57:25 -08:00
Steve Gravrock
bdf63f2402 Remove code to support browsers that don't have MessageChannel
Jasmine hasn't actually run in any such browsers since 2.x.
2025-11-12 21:59:17 -08:00
Steve Gravrock
9c2ffae2f9 Add experimental safariYieldStrategy: "time" config option
This greatly improves speed, at least in jasmine-core's own tests.
2025-11-12 21:08:59 -08:00
Steve Gravrock
7b2807b321 Convert clearStack from a function to an object 2025-11-11 18:54:25 -08:00
Steve Gravrock
e930622548 Merge branch 'main' into 6.0 2025-11-11 09:14:14 -08:00
Steve Gravrock
56e2832ebe Add manual and cron triggers to Safari build 2025-11-11 09:13:34 -08:00
Steve Gravrock
d31d33aeb3 Introduce a tab bar
This will make it easier to add a third tab to HtmlReporterV2.
2025-11-09 09:58:57 -08:00
Steve Gravrock
e4c69e960e Add 'use strict' to AllOf.js 2025-11-04 05:52:45 -08:00
Steve Gravrock
a8431f33bd Merge branch '5.99' into 6.0 2025-11-03 17:22:11 -08:00
Steve Gravrock
4995c967ac Merge branch 'main' into 5.99 2025-11-03 17:14:24 -08:00
Steve Gravrock
9a9d3994da Add Safari 26 to supported browsers 2025-11-03 07:37:11 -08:00
Steve Gravrock
ff9feb29d3 Configurable spec/suite filename detection
* Adds extraItStackFrames and extraDescribeStackFrames config properties.
* Un-deprecates the filename properties of reporter events.
* Fixes #2065.
2025-11-01 14:17:01 -07:00
Steve Gravrock
fee7e6e64e Merge branch 'jonahd-g-main'
* Adds jasmine.allOf asymmetric equality tester
* Merges #2087 from @jonahd-g
* Fixes #2083
2025-11-01 09:01:53 -07:00
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
Jonah Bron
75658e0566 jasmine.allOf AsymmetricEqualityTester
New asymmetric equality tester that accepts a variable number of arguments, and will pass if all of them evaluate as being equal to the input value.
Includes unit tests
2025-10-27 10:10:16 -07:00
Steve Gravrock
85322d1877 Re-add support for partial spec name filtering
No UI for this but users can construct URLs manually.
Fixes #2085.
2025-10-24 17:26:49 -07:00
Steve Gravrock
6667a42301 Docs: Fix HtmlReporterV2 ctor example 2025-10-22 16:36:58 -07:00
Steve Gravrock
020dffd504 Don't spy on getGlobal 2025-10-19 10:08:05 -07:00
Steve Gravrock
4201fd848f Require spec/suite property keys to be strings, not just anything that's cloneable and serializable
This matches the jsdoc.
2025-10-19 09:15:12 -07:00
Steve Gravrock
9a67c4e24d Copy 6.0.0-alpha.1 release notes from branch 2025-10-18 13:05:29 -07:00
Steve Gravrock
d66d0d9d2e Fixed standalone distribution 2025-10-18 12:31:48 -07:00
Steve Gravrock
f4b9c889b9 Bump version to 6.0.0-alpha.1 2025-10-18 11:58:17 -07:00
Steve Gravrock
388d7fb1a2 Merge branch '5.99' into 6.0 2025-10-18 10:23:37 -07:00
Steve Gravrock
e993a4a363 Merge branch 'main' into 5.99 2025-10-18 10:21:13 -07:00
Steve Gravrock
54ac39a192 Fix position of duration in both HTML reporters 2025-10-18 09:58:55 -07:00
Steve Gravrock
ea882c2f1e HtmlReporterV2: Show a non-color indication of status while running 2025-10-18 09:46:16 -07:00
Steve Gravrock
4cc605756a Extract an OverallStatusBar widget 2025-10-17 07:58:01 -07:00
Steve Gravrock
b6426d2414 Flesh out jasmine-html.js API reference 2025-10-15 21:50:56 -07:00
Steve Gravrock
2352249441 Clean up progress bar CSS 2025-10-15 19:42:46 -07:00
Steve Gravrock
4dfc34a7a0 Make AlertsView#add private 2025-10-15 19:37:18 -07:00
Steve Gravrock
695a805844 Backfill missing HTML reporter tests 2025-10-15 19:32:47 -07:00
Steve Gravrock
10bc655622 HtmlReporterV2: Show details of failures as soon as they occur 2025-10-14 21:49:33 -07:00
Steve Gravrock
4663280528 Move state tracking out of FailuresView 2025-10-14 21:46:07 -07:00
Steve Gravrock
75347d9ba0 Removed slow test for long-removed IE specific code 2025-10-09 16:42:42 -07:00
Steve Gravrock
86387c9068 HtmlReporterV2: replace dots with progress bar 2025-10-09 16:42:42 -07:00
Steve Gravrock
9b3cc08818 Deprecate HtmlReporter and HtmlSpecFilter 2025-10-09 16:42:42 -07:00
Steve Gravrock
0ad54fc6f0 Clicking a link in HtmlReporterV2 does exact filtering 2025-10-09 16:42:42 -07:00
Steve Gravrock
c042665d9c HtmlReporterV2 doesn't need specStarted 2025-10-09 16:42:42 -07:00
Steve Gravrock
a457cf1b81 Simplify boot1.js 2025-10-09 16:42:42 -07:00
Steve Gravrock
fb814b5f94 Refactor HtmlReporterV2 test setup 2025-10-09 16:42:42 -07:00
Steve Gravrock
77c3b8b07e Clone HtmlSpecFilter and HtmlReporter in preparation for backward-incompatible changes 2025-10-09 16:42:42 -07:00
Steve Gravrock
bd89ef66c8 Move HtmlReporter components to their own files 2025-10-09 16:42:42 -07:00
Steve Gravrock
01f050eeaa Turn DomContext class back into a function 2025-10-09 16:42:42 -07:00
Steve Gravrock
de44e909f2 Ignore HtmlReporter's createElement and createTextNode options
Injected DOM wrappers were a nice idea in theory but everyone just passes
wrappers around document.createElement/document.createTextNode. That
includes HtmlReporter's unit tests and karma-jasmine-html-reporter, the
only known 5.x-compatible library that constructs an HtmlReporter.
2025-10-09 16:42:42 -07:00
Steve Gravrock
10ad40357a HTML reporter: cache configuration throughout each run 2025-10-09 16:42:42 -07:00
Steve Gravrock
1f521f2a7f Convert what's left of HtmlReporter to an ES6 class 2025-10-09 16:42:42 -07:00
Steve Gravrock
5b3e12e4c5 Decompose HtmlReporter into smaller components 2025-10-09 16:42:37 -07:00
Steve Gravrock
7ba53b25f7 Bump version to 5.12.0 2025-10-05 12:02:56 -07:00
Steve Gravrock
cfd8f11b30 Merge branch '5.99' into 6.0 2025-10-05 10:08:40 -07:00
Steve Gravrock
d95ebf303a Merge branch 'main' into 5.99 2025-10-05 10:04:05 -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
b881b0077d Warn if jasmine-core is loaded as an ES module in browsers 2025-10-05 09:03:27 -07:00
Steve Gravrock
67ef721c85 Fix startup crash in Karma
The previous commit left one code path un-converted to globalThis. That
exposed a bug in Karma: Karma loads jasmine-core via <script type="module">
even though it's not an ES module. In ES modules, the value of `this`
outside of a method is undefined rather than window. The Angular dev tools
try to work around that bug by monkey-patching window to look like GJS's
window object, which worked until the GJS hack was removed in the previous
commit.
2025-10-05 09:00:37 -07:00
Steve Gravrock
c4abf3265d Use globalThis to determine the global object during initialization
This slightly simplifies the init code and hardens Jasmine against broken
bundlers that assume everything can be wrapped in a "use strict" context.

This removes a workaround for incompatible `this` behavior in GJS. GJS was
never a supported envronment, but in any case the change is unlikely to
cause problems since GJS ha supported globalThis since 2020.
2025-10-05 07:23:54 -07:00
Steve Gravrock
68a7cbb991 Adopt strict mode throughout the codebase 2025-10-05 06:53:54 -07:00
Steve Gravrock
73a30ffc3e Made SpyRegistry compatible with strict mode 2025-10-05 06:53:39 -07:00
Steve Gravrock
18491e9b84 Encapsulate suite result 2025-10-05 06:19:16 -07:00
Steve Gravrock
0738ba6462 Omit irrelevant properties from suiteStarted 2025-10-05 06:16:57 -07:00
Steve Gravrock
712f9bac29 Encapsulate spec result 2025-10-05 06:01:27 -07:00
Steve Gravrock
d99bc3ab58 Encapsulate spec status 2025-10-05 06:01:27 -07:00
Steve Gravrock
418393c496 rm more vestiges of suite re-entry support 2025-10-05 06:01:27 -07:00
Steve Gravrock
2a83f5cc30 Don't mutate suite's failedExpectations from env 2025-10-05 06:01:15 -07:00
Steve Gravrock
bca56032e0 Expose browser errors uniformly outside of GlobalErrors 2025-10-04 12:48:14 -07:00
Steve Gravrock
c590095662 Copy 6.0.0-alpha.0 release notes from branch 2025-09-29 19:53:25 -07:00
184 changed files with 8821 additions and 3557 deletions

View File

@@ -1,5 +1,5 @@
# Run tests against supported Node versions, and (except for pull requests)
# against supported browsers.
# against supported browsers that are available on Saucelabs.
version: 2.1
@@ -89,7 +89,7 @@ jobs:
export SAUCE_TUNNEL_NAME=$CIRCLE_WORKFLOW_JOB_ID
scripts/start-sauce-connect
set +o errexit
scripts/run-all-browsers
scripts/run-sauce-browsers
exitcode=$?
set -o errexit
scripts/stop-sauce-connect

23
.github/workflows/safari.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Test in latest available Safari
on:
push:
pull_request:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
jobs:
build:
runs-on: macos-latest
steps:
- name: Report Safari version
run: osascript -e 'get version of application "Safari"'
- uses: actions/checkout@v4
- name: Use Node.js 22.x
uses: actions/setup-node@v4
with:
node-version: 22.x
- run: npm install
- run: npm run build
- run: JASMINE_BROWSER=safari npm run ci

View File

@@ -1,5 +1,5 @@
Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-2025 The Jasmine developers
Copyright (c) 2008-2026 The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@@ -30,7 +30,7 @@ Microsoft Edge) as well as Node.
| Environment | Supported versions |
|-------------------|----------------------------------|
| Node | 20, 22, 24 |
| Safari | 16*, 17* |
| Safari | 26* |
| Chrome | Evergreen |
| Firefox | Evergreen, 102*, 115*, 128*, 140 |
| Edge | Evergreen |
@@ -60,5 +60,5 @@ To find out what environments work with a particular Jasmine release, see the [r
* Sheel Choksi
Copyright (c) 2008-2019 Pivotal Labs<br>
Copyright (c) 2008-2025 The Jasmine developers<br>
Copyright (c) 2008-2026 The Jasmine developers<br>
This software is licensed under the [MIT License](https://github.com/jasmine/jasmine/blob/main/LICENSE).

View File

@@ -28,9 +28,18 @@ should also rev to that version.
When ready to release - specs are all green and the stories are done:
1. Update the release notes in `release_notes` - use the Anchorman gem to generate the markdown file and edit accordingly. Include a list of supported environments.
1. Update the version in `package.json`
1. Run `npm run build`.
1. Update the release notes in `release_notes` - use the Anchorman gem to
generate the Markdown file and edit accordingly. Include a list of supported
environments. Get that information from these places:
* For Node, see .circleci/config.yml or the README.
* For Firefox ESR and Safari <=17, see scripts/run-sauce-browsers or the README.
* For evergreen browsers, trigger a Circle CI run and check the
[Saucelabs dashboard](https://app.saucelabs.com/dashboard/tests?ownerId=90a771d55857492da3bd5251a2d92457&ownerType=user&ownerName=jasmine-js&start=last7days)
once it's finished.
* For Safari >17, trigger the [Safari action](https://github.com/jasmine/jasmine/actions/workflows/safari.yml)
and get the version from the output.
2. Update the version in `package.json`
3. Run `npm run build`.
### Commit and push core changes

View File

@@ -1,6 +1,6 @@
/*
Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-2025 The Jasmine developers
Copyright (c) 2008-2026 The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -22,6 +22,8 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
'use strict';
/**
* Note: Only available on Node.
* @module jasmine-core

View File

@@ -1,6 +1,6 @@
/*
Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-2025 The Jasmine developers
Copyright (c) 2008-2026 The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -22,6 +22,8 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
'use strict';
/**
This file starts the process of "booting" Jasmine. It initializes Jasmine,
makes its globals available, and creates the env. This file should be loaded

View File

@@ -1,6 +1,6 @@
/*
Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-2025 The Jasmine developers
Copyright (c) 2008-2026 The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -22,6 +22,8 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
'use strict';
/**
This file finishes 'booting' Jasmine, performing all of the necessary
initialization before executing the loaded environment and all of a project's
@@ -37,85 +39,26 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
(function() {
const env = jasmine.getEnv();
const queryString = new jasmine.QueryString({
getWindowLocation: function() {
return window.location;
}
});
const config = {
stopOnSpecFailure: queryString.getParam('stopOnSpecFailure'),
stopSpecOnExpectationFailure: queryString.getParam(
'stopSpecOnExpectationFailure'
),
hideDisabled: queryString.getParam('hideDisabled')
};
const random = queryString.getParam('random');
if (random !== undefined && random !== '') {
config.random = random;
}
const seed = queryString.getParam('seed');
if (seed) {
config.seed = seed;
}
const urls = new jasmine.HtmlReporterV2Urls();
/**
* ## Reporters
* The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
* Configures Jasmine based on the current set of query parameters. This
* supports all parameters set by the HTML reporter as well as
* spec=partialPath, which filters out specs whose paths don't contain the
* parameter.
*/
const htmlReporter = new jasmine.HtmlReporter({
env: env,
navigateWithNewParam: function(key, value) {
return queryString.navigateWithNewParam(key, value);
},
addToExistingQueryString: function(key, value) {
return queryString.fullStringWithNewParam(key, value);
},
getContainer: function() {
return document.body;
},
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
},
timer: new jasmine.Timer(),
queryString
});
env.configure(urls.configFromCurrentUrl());
/**
* The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
*/
env.addReporter(jsApiReporter);
env.addReporter(htmlReporter);
/**
* Filter which specs will be run by matching the start of the full name against the `spec` query param.
*/
const specFilter = new jasmine.HtmlExactSpecFilter({ queryString });
config.specFilter = function(spec) {
return specFilter.matches(spec);
};
env.configure(config);
/**
* ## Execution
*
* Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
*/
const currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
htmlReporter.initialize();
// The HTML reporter needs to be set up here so it can access the DOM. Other
// reporters can be added at any time before env.execute() is called.
const htmlReporter = new jasmine.HtmlReporterV2({ env, urls });
env.addReporter(htmlReporter);
env.execute();
};
})();

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ body {
background-color: #eee;
padding: 5px;
margin: -8px;
font-size: 11px;
font-size: 12px;
font-family: Monaco, "Lucida Console", monospace;
line-height: 14px;
color: #333;
@@ -63,6 +63,7 @@ body {
float: right;
line-height: 28px;
padding-right: 9px;
font-size: 12px;
}
.jasmine_html-reporter .jasmine-symbol-summary {
overflow: hidden;
@@ -115,6 +116,23 @@ body {
color: #ba9d37;
content: "•";
}
.jasmine_html-reporter progress {
width: 100%;
}
.jasmine_html-reporter progress[value] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
.jasmine_html-reporter progress[value]::-webkit-progress-value, .jasmine_html-reporter progress[value]::-moz-progress-bar {
background: #007069;
}
.failed .jasmine_html-reporter progress[value]::-webkit-progress-value, .failed .jasmine_html-reporter progress[value]::-moz-progress-bar {
background: #ca3a11;
}
.jasmine_html-reporter progress.failed[value]::-webkit-progress-value, .jasmine_html-reporter progress.failed[value]::-moz-progress-bar {
background: #ca3a11;
}
.jasmine_html-reporter .jasmine-run-options {
float: right;
margin-right: 5px;
@@ -145,8 +163,12 @@ body {
display: block;
color: #eee;
}
.jasmine_html-reporter .jasmine-bar.jasmine-in-progress {
color: #333;
}
.jasmine_html-reporter .jasmine-bar.jasmine-failed, .jasmine_html-reporter .jasmine-bar.jasmine-errored {
background-color: #ca3a11;
color: #eee;
border-bottom: 1px solid #eee;
}
.jasmine_html-reporter .jasmine-bar.jasmine-passed {
@@ -176,11 +198,17 @@ body {
color: white;
}
.jasmine_html-reporter.jasmine-spec-list .jasmine-bar.jasmine-menu.jasmine-failure-list,
.jasmine_html-reporter.jasmine-spec-list .jasmine-results .jasmine-failures {
.jasmine_html-reporter.jasmine-spec-list .jasmine-results .jasmine-failures,
.jasmine_html-reporter.jasmine-spec-list .jasmine-performance-view {
display: none;
}
.jasmine_html-reporter.jasmine-failure-list .jasmine-bar.jasmine-menu.jasmine-spec-list,
.jasmine_html-reporter.jasmine-failure-list .jasmine-summary {
.jasmine_html-reporter.jasmine-failure-list .jasmine-summary,
.jasmine_html-reporter.jasmine-failure-list .jasmine-performance-view {
display: none;
}
.jasmine_html-reporter.jasmine-performance .jasmine-results .jasmine-failures,
.jasmine_html-reporter.jasmine-performance .jasmine-summary {
display: none;
}
.jasmine_html-reporter .jasmine-results {
@@ -301,4 +329,23 @@ body {
}
.jasmine_html-reporter .jasmine-debug-log .jasmine-debug-log-msg {
white-space: pre;
}
.jasmine-hidden {
display: none;
}
.jasmine-tab + .jasmine-tab:before {
content: " | ";
}
.jasmine-performance-view h2, .jasmine-performance-view h3 {
margin-top: 1em;
margin-bottom: 1em;
}
.jasmine-performance-view table {
border-spacing: 5px;
}
.jasmine-performance-view th, .jasmine-performance-view td {
text-align: left;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "jasmine-core",
"license": "MIT",
"version": "6.0.0-alpha.0",
"version": "6.1.0",
"repository": {
"type": "git",
"url": "https://github.com/jasmine/jasmine.git"
@@ -43,10 +43,10 @@
"ejs": "^3.1.10",
"eslint": "^9.24.0",
"eslint-plugin-compat": "^6.0.2",
"glob": "^10.2.3",
"glob": "^13.0.0",
"globals": "^16.0.0",
"jasmine": "^5.0.0",
"jasmine-browser-runner": "github:jasmine/jasmine-browser-runner#4.0",
"jasmine": "github:jasmine/jasmine-npm",
"jasmine-browser-runner": "github:jasmine/jasmine-browser-runner",
"jsdom": "^26.0.0",
"prettier": "1.17.1",
"sass": "^1.58.3"

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)_

44
release_notes/5.13.0.md Normal file
View File

@@ -0,0 +1,44 @@
# Jasmine Core 5.13.0 Release Notes
## Changes to supported environments
Safari 26 is now supported on a best-effort basis.
Due to the limited availability of Safari 18 and later on free CI services,
Safari support in future jasmine-core versions will be limited to:
* Best-effort support for the latest Safari version available on GitHub Actions,
which may change at any time.
* Best-effort support for Safari 16 and 17 for as long as it remains practical.
## New Features
* New `extraItStackFrames` and `extraDescribeStackFrames` config options to fix
the filename properties of reporter events in configurations that wrap
`it`/`describe`, such as zone.js. The `filename` properties of reporter events
are no longer deprecated.
* `jasmine.allOf` asymmetric equality tester
* Merges [#2087](https://github.com/jasmine/jasmine/issues/2083) from @jonahd-g
* Fixes [#2083](https://github.com/jasmine/jasmine/pull/2087)
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------|--------------------------------|
| Node | 18.20.5**, 20, 22, 24 |
| Safari** | 16, 17, 26.1 |
| Chrome | 142* |
| Firefox | 102**, 115**, 128**, 140, 145* |
| Edge | 142* |
\* 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

@@ -0,0 +1,90 @@
# Jasmine Core 6.0.0-alpha.2 Release Notes
This is a pre-release, intended to offer a preview of upcoming 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.2`,
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.2`.
## Changes to supported environments
Safari 26 is now supported on a best-effort basis.†
Due to the limited availability of Safari 18 and later on free CI services,
Safari support in future jasmine-core versions will be limited to:
* Best-effort support for the latest Safari version available on GitHub Actions,
which may change at any time.
* Best-effort support for Safari 16 and 17 for as long as it remains practical.
## Bug Fixes
* Fix custom matchers in top-level specs††
* Merges [#2088](https://github.com/jasmine/jasmine/pull/2088) from @bonkevin
## New features
* Larger body font size in HTML reporters
* New Performance tab in HtmlReporterV2 shows metrics and a list of the slowest
specs.
* Experimental `safariYieldStrategy: "time"` config option, which may make
Jasmine run significantly faster in Safari and similar browsers. So far, this
option has not been tested on a wide variety of workloads. Feedback is
appreciated.
* New `extraItStackFrames` and `extraDescribeStackFrames` config options to fix
the filename properties of reporter events in configurations that wrap
`it`/`describe`, such as zone.js.†
* `jasmine.allOf asymmetric` equality tester†
* Merges [#2087](https://github.com/jasmine/jasmine/pull/2087) from @jonahd-g
* Fixes [#2083](https://github.com/jasmine/jasmine/issues/2083)
* Re-add support for partial spec name filtering via `spec` query parameter
* Fixes [#2085](https://github.com/jasmine/jasmine/issues/2085).
* Require spec/suite property keys to be strings, not just anything that's
cloneable and serializable. This matches the existing API reference
documentation.
## Documentation improvements
* Fix HtmlReporterV2 ctor example
## Internal Improvements
* Remove code to support browsers that don't have MessageChannel. Jasmine hasn't
run in any such browsers since 2.x.
† Also likely to be included in a future 5.x release.<br>
†† Also released in 5.12.1.
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------------------|
| Node | 20, 22, 24 |
| Safari | 16**, 17**, 26** |
| Chrome | 142* |
| Firefox | 102**, 115**, 128**, 140, 145* |
| Edge | 142* |
\* 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,74 @@
# Jasmine Core 6.0.0-beta.0 Release Notes
This is a pre-release, intended to offer a preview of upcoming 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-beta.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-beta.0`.
## Breaking changes
* boot1.js no longer adds jsApiReporter to the env.
* HtmlReporterV2 initialization and boot1.js have been simplified. If you
maintain your own boot file, update it to match the boot1.js included in this
package.
## New features
* Statically exposed pretty printer as jasmine.pp().
## Bug fixes
* Fixed HtmlReporterV2 progress bar when running a subset of specs.
## Deprecations
* jsApiReporter is deprecated.
* Detect monkey patching and emit a deprecation warning.
## Documentation improvements
* Documented the set of possible spec statuses.
## Internal improvements
* Replaced isArray helper with native Array.isArray
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------------------|
| Node | 20, 22, 24 |
| Safari | 16**, 17**, 26.1** |
| Chrome | 142* |
| Firefox | 102**, 115**, 128**, 140, 145* |
| Edge | 142* |
\* 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,47 @@
# Jasmine Core 6.0.0-beta.1 Release Notes
This is a pre-release, intended to offer a preview of upcoming changes and to
solicit feedback.
A corresponding release of the `jasmine` package is not planned because the
change in this release only affects browser users.
## 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-beta.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-beta.1`.
## Bug Fixes
* Revert to using window.onload in boot1.js. This fixes top level await in
jasmine-browser-runner.
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------------------|
| Node | 20, 22, 24 |
| Safari** | 16, 17, 26.1 |
| Chrome | 143* |
| Firefox | 102**, 115**, 128**, 140, 145* |
| Edge | 142* |
\* 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)_

190
release_notes/6.0.0.md Normal file
View File

@@ -0,0 +1,190 @@
# Jasmine Core 6.0.0 Release Notes
## Summary
This is a major release that includes breaking changes as well as significant
new features. Many of the breaking changes and deprecations in this release are
intended to improve the stability of the Jasmine ecosystem by making the
distinction between public and private APIs more obvious, reducing exposure of
jasmine-core's internal state, removing ambiguities from the reporter API, and
warning about monkey patching.
6.x is intended to ba a relatively short-lived, transitional series. It is
compatible with the current versions of karma-jasmine and other legacy Angular
tools but emits deprecation warnings when used with them. 7.0 will drop
compatibility with those tools. If you use Karma in a non-Angular context,
consider migrating to a maintained alternative such as jasmine-browser-runner or
web-test-runner. If you use Angular, please direct any questions about support
for future versions of Jasmine to the Angular team.
Please see the [migration guide](https://jasmine.github.io/upgrade-guides/6.0)
for more information. If you use Jasmine via the `jasmine` package, please see
[its release notes](https://github.com/jasmine/jasmine-npm/blob/main/release_notes/6.0.0.md)
as well.
## 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 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.
### Changes that affect spec writing
* Global error spies always receive a single argument. Previously, the browser
error event was passed as the second argument.
* If a spy is invoked via `.call(null, ...)` or `.apply(null, ...)`, the spy's
`this` argument will be `null`. Previously it was `globalThis`.
* Mock clock timing functions cannot be spied on. Previously this "worked" but
prevented the mock clock from uninstalling itself.
* The mock clock no longer supports the eval forms of `setTimeout` and
`setInterval`.
* Keys passed to `setSpecProperty`/`setSuiteProperty` must be strings. Values
must be both structured-cloneable and JSON-serializable.
### Changes that affect configuration
* HTML reporters cache configuration throughout each run. Configuration changes
made while specs are running will not affect reporter behavior.
* 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/Spec.html)
instance, not the internal spec object.
* The default value of the `forbidDuplicateNames` config option has been
changed to true.
### 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.
* Meaningless properties such as `status` and `failedExpectations` are omitted
from the events passed to [suiteStarted](https://jasmine.github.io/api/6.0/global.html#SuiteStartedEvent)
and [specStarted](https://jasmine.github.io/api/6.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/global.html#ExpectationResult)
have been removed.
* The [order](https://jasmine.github.io/api/6.0/global.html#Order)
property of the`jasmineStarted` and `jasmineDone` reporter events no longer
includes undocumented properties.
* boot1.js no longer adds `jsApiReporter` to the env.
### Changes that affect browser boot files
* The `createElement` and `createTextNode` options of the legacy `HtmlReporter`
are ignored. `HtmlReporter` now unconditionally uses `document.createElement`
and `document.createTextNode`.
### Changes to Node boot functions
* [boot](https://jasmine.github.io/api/6.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/module-jasmine-core.html#.noGlobals)
no longer takes a parameter. It always returns the same object when called
repeatedly.
## 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.
* A Performance tab shows metrics and a list of the slowest specs.
* 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 boot files
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 other software that
interprets the `spec` parameter, such as karma-jasmine, you won't be able to
adopt `HtmlReporterV2` unlesss that other software is updated.
* Larger body font size in HTML reporters
* `globalThis` is used 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.
* Experimental [`safariYieldStrategy: "time"`](https://jasmine.github.io/api/6.0/Configuration.html#safariYieldStrategy)
config option, which may make Jasmine run significantly faster in Safari and
similar browsers. So far, this option has not been tested on a wide variety of
workloads. Feedback is appreciated.
* Statically exposed pretty printer as `jasmine.pp()`.
## Deprecations
* Common monkey patching patterns are detected and result in a deprecation
warning. Code that overwrites anything provided by jasmine-core (other than
globals like `it`/`expect`/etc or configuration properties like
`jasmine.DEFAULT_TIMEOUT_INTERVAL`) may be broken by any jasmine-core release.
* 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.
* `HtmlReporter` and `HtmlSpecFilter` are deprecated in favor of `HtmlReporterV2`.
* `jsApiReporter` is deprecated.
## Documentation improvements
* Improved API reference documentation for APIs that are used from browser boot
files.
* Documented the set of possible spec statuses.
* Documented that globals are overwriteable.
## Internal improvements
* 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.
* Removed code to support browsers that don't have `MessageChannel`. Jasmine
hasn't run in any such browsers since 2.x.
* Replaced `isArray` helper with native `Array.isArray`.
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------------------|
| Node | 20, 22, 24 |
| Safari** | 16, 17, 26.2 |
| Chrome | 143* |
| Firefox | 102**, 115**, 128**, 140, 147* |
| Edge | 143* |
\* 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/6.0.1.md Normal file
View File

@@ -0,0 +1,29 @@
# Jasmine Core 6.0.1 Release Notes
## Bug fixes
* Don't emit a deprecation warning when `jasmineRequire.core` is called from an
ES module
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------------------|
| Node | 20, 22, 24 |
| Safari** | 16, 17, 26.2 |
| Chrome | 143* |
| Firefox | 102**, 115**, 128**, 140, 147* |
| Edge | 143* |
\* 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)_

40
release_notes/6.1.0.md Normal file
View File

@@ -0,0 +1,40 @@
# Jasmine Core 6.1.0 Release Notes
## Changes to supported environments
* Safari 16 and 17 are no longer supported. Although jasmine-core may still work
in those versions, we no longer have a reliable way to test them and won't try
to maintain compatibility with them in future releases.
## New features
* Report the underlying errors that make up an `AggregateError`.
* Merges [#2093](https://github.com/jasmine/jasmine/pull/2093) from @puglyfe
* Fixes [#2063](https://github.com/jasmine/jasmine/issues/2063)
## Documentation improvements
* Fix default MAX_PRETTY_PRINT_CHARS in JsDoc.
* Merges [#2091](https://github.com/jasmine/jasmine/pull/2091) from @HolgerJeromin
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------------------|
| Node | 20, 22, 24 |
| Safari | 26.3** |
| Chrome | 144* |
| Firefox | 102**, 115**, 128**, 140, 147* |
| Edge | 144* |
\* 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

@@ -1,5 +1,10 @@
#!/bin/sh
# Run tests in supported browsers that are available on Saucelabs.
# Note: Safari is tested via GitHub Actions because Saucelabs only makes Safari
# 18 and later available to paid enterprise accounts. See
# .github/workflows/safari.yml.
run_browser() {
browser=$1
version=$2
@@ -39,9 +44,6 @@ else
fi
run_browser firefox 102
run_browser safari 17
run_browser safari 16
run_browser MicrosoftEdge latest
echo

View File

@@ -1247,4 +1247,25 @@ describe('Clock (acceptance)', function() {
clock.tick(400);
});
describe('Warning about monkey patching', function() {
for (const name of ['tick', 'mockDate', 'install', 'uninstall']) {
it(`warns if Clock#${name} is monkey patched`, function() {
spyOn(console, 'error');
const clock = new privateUnderTest.Clock({}, function() {}, {});
const patch = {};
clock[name] = patch;
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalledOnceWith(
jasmine.stringContaining('DEPRECATION: Monkey patching detected.')
);
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalledOnceWith(
jasmine.stringContaining('ClockSpec.js')
);
expect(clock[name]).toBe(patch);
});
}
});
});

View File

@@ -10,7 +10,14 @@ describe('Configuration', function() {
'detectLateRejectionHandling',
'verboseDeprecations'
];
const allKeys = [...standardBooleanKeys, 'seed', 'specFilter'];
const allKeys = [
...standardBooleanKeys,
'seed',
'specFilter',
'extraItStackFrames',
'extraDescribeStackFrames',
'safariYieldStrategy'
];
Object.freeze(standardBooleanKeys);
Object.freeze(allKeys);
@@ -28,6 +35,9 @@ describe('Configuration', function() {
expect(subject.forbidDuplicateNames).toEqual(true);
expect(subject.verboseDeprecations).toEqual(false);
expect(subject.detectLateRejectionHandling).toEqual(false);
expect(subject.extraItStackFrames).toEqual(0);
expect(subject.extraDescribeStackFrames).toEqual(0);
expect(subject.safariYieldStrategy).toEqual('count');
});
describe('copy()', function() {
@@ -109,5 +119,48 @@ describe('Configuration', function() {
subject.update({ seed: null });
expect(subject.seed).toBeNull();
});
it('sets extraItStackFrames when not undefined', function() {
const subject = new privateUnderTest.Configuration();
subject.update({ extraItStackFrames: undefined });
expect(subject.extraItStackFrames).toEqual(0);
subject.update({ extraItStackFrames: 100000 });
expect(subject.extraItStackFrames).toEqual(100000);
});
it('sets extraDescribeStackFrames when not undefined', function() {
const subject = new privateUnderTest.Configuration();
subject.update({ extraDescribeStackFrames: undefined });
expect(subject.extraDescribeStackFrames).toEqual(0);
subject.update({ extraDescribeStackFrames: 100000 });
expect(subject.extraDescribeStackFrames).toEqual(100000);
});
it('sets safariYieldStrategy when valid', function() {
const subject = new privateUnderTest.Configuration();
subject.update({ safariYieldStrategy: undefined });
expect(subject.safariYieldStrategy).toEqual('count');
subject.update({ safariYieldStrategy: 'time' });
expect(subject.safariYieldStrategy).toEqual('time');
subject.update({ safariYieldStrategy: 'count' });
expect(subject.safariYieldStrategy).toEqual('count');
});
it('rejcts invalid safariYieldStrategy values', function() {
const subject = new privateUnderTest.Configuration();
expect(function() {
subject.update({ safariYieldStrategy: 'thyme' });
}).toThrowError(
"Invalid safariYieldStrategy value. Valid values are 'count' and 'time'."
);
});
});
});

View File

@@ -1,4 +1,3 @@
// TODO: Fix these unit tests!
describe('Env', function() {
let env;
beforeEach(function() {
@@ -27,8 +26,6 @@ describe('Env', function() {
describe('#topSuite', function() {
it('returns an object that describes the tree of suites and specs', function() {
spyOn(env, 'deprecated');
env.it('a top level spec');
env.describe('a suite', function() {
env.it('a spec');
@@ -95,7 +92,7 @@ describe('Env', function() {
});
});
it('accepts its own current configureation', function() {
it('accepts its own current configuration', function() {
env.configure(env.configuration());
});
@@ -124,7 +121,6 @@ describe('Env', function() {
});
it('ignores configuration properties that are present but undefined', function() {
spyOn(env, 'deprecated');
const initialConfig = {
random: true,
seed: '123',
@@ -198,6 +194,29 @@ describe('Env', function() {
expect(innerSuite.parentSuite).toBe(suite);
expect(spec.getFullName()).toEqual('outer suite inner suite a spec');
});
it('sets the caller filename correctly when extraDescribeStackFrames is not set', function() {
// IIFE is used to match the stack depth when global describe() is called
const suite = (function() {
return env[methodName]('a suite', function() {
env.it('a spec');
});
})();
expect(suite.filename).toMatch(/EnvSpec\.js$/);
});
it('sets the caller filename correctly when extraDescribeStackFrames is set', function() {
env.configure({ extraDescribeStackFrames: 2 });
// IIFE is used to match the stack depth when global describe() is called
const suite = (function() {
return specHelpers.callerFilenameShim(function() {
return env[methodName]('a suite', function() {
env.it('a spec');
});
});
})();
expect(suite.filename).toMatch(/EnvSpec\.js$/);
});
}
describe('#describe', function() {
@@ -300,6 +319,25 @@ describe('Env', function() {
.not.toEqual('');
expect(spec.pend).toBeFalsy();
});
it('sets the caller filename correctly when extraItStackFrames is not set', function() {
// IIFE is used to match the stack depth when global it() is called
const spec = (function() {
return env[methodName]('a spec', function() {});
})();
expect(spec.filename).toMatch(/EnvSpec\.js$/);
});
it('sets the caller filename correctly when extraItStackFrames is set', function() {
env.configure({ extraItStackFrames: 2 });
// IIFE is used to match the stack depth when global it() is called
const spec = (function() {
return specHelpers.callerFilenameShim(function() {
return env[methodName]('a spec', function() {});
});
})();
expect(spec.filename).toMatch(/EnvSpec\.js$/);
});
}
describe('#it', function() {
@@ -722,7 +760,6 @@ describe('Env', function() {
it("does not expose the suite as 'this'", function() {
let suiteThis;
spyOn(env, 'deprecated');
env.describe('a suite', function() {
suiteThis = this;
@@ -837,4 +874,44 @@ describe('Env', function() {
}).toThrowError('Jasmine cannot be configured via Env in parallel mode');
});
});
describe('Warning about monkey patching', function() {
afterEach(function() {
// deprecateMonkeyPatching() uses jasmine.getEnv(), not the env from
// this suite. Clean it up so we don't leak event listeners.
jasmineUnderTest.getEnv().cleanup_();
privateUnderTest.currentEnv_ = null;
});
const names = [
'describe',
'xdescribe',
'fdescribe',
'it',
'xit',
'fit',
'beforeEach',
'afterEach',
'beforeAll',
'afterAll'
];
for (const name of names) {
it(`warns if Env#${name} is monkey patched`, function() {
spyOn(console, 'error');
const patch = {};
env[name] = patch;
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalledOnceWith(
jasmine.stringContaining('DEPRECATION: Monkey patching detected.')
);
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalledOnceWith(
jasmine.stringContaining('EnvSpec.js')
);
expect(env[name]).toBe(patch);
});
}
});
});

View File

@@ -346,5 +346,184 @@ describe('ExceptionFormatter', function() {
}).not.toThrowError();
});
});
describe('when the error has an errors array (AggregateError)', function() {
it('includes all aggregated errors in the stack trace', function() {
const subject = new privateUnderTest.ExceptionFormatter();
const error1 = (function fn1() {
return new Error('first error');
})();
const error2 = (function fn2() {
return new Error('second error');
})();
const aggregateError = (function fn3() {
return new Error('Multiple errors occurred');
})();
aggregateError.errors = [error1, error2];
const lines = subject.stack(aggregateError).split('\n');
// TODO: be consistent across environments about whether the message is
// included in the stack trace
if (lines[0] === 'Error: Multiple errors occurred') {
lines.shift();
}
// Exclude lines that vary from environment to environment
const filteredLines = lines.filter(
x =>
!x.includes('/jasmine.js:') &&
// Some Node 20 and 22 minors when running in parallel
!x.includes('process.processTicksAndRejections')
);
for (let i = 0; i < filteredLines.length; i++) {
jasmine.debugLog(`Line ${i} after filtering: ${filteredLines[i]}`);
}
// Inexact matching because stack frame formatting varies from runtime
// to runtime
const expectedPatterns = [
// Overall error
/fn3.*ExceptionFormatterSpec\.js/,
/ExceptionFormatterSpec\.js/,
/^$/,
// First nested error
/^ Error 1: Error: first error$/,
/^ .*fn1.*ExceptionFormatterSpec\.js/,
/^ .*ExceptionFormatterSpec\.js/,
/^$/,
// Second nested error
/^ .*Error 2: Error: second error$/,
/^ .*fn2.*ExceptionFormatterSpec\.js/,
/^ .*ExceptionFormatterSpec\.js/
];
expect(filteredLines).toEqual(
expectedPatterns.map(p => jasmine.stringMatching(p))
);
});
it('handles empty errors array', function() {
const subject = new privateUnderTest.ExceptionFormatter();
const aggregateError = new Error('No errors');
aggregateError.errors = [];
expect(function() {
subject.stack(aggregateError);
}).not.toThrowError();
});
it('handles nested AggregateError', function() {
const subject = new privateUnderTest.ExceptionFormatter();
const innerError1 = new Error('inner error 1');
const innerError2 = new Error('inner error 2');
const innerAggregate = new Error('Inner aggregate');
innerAggregate.errors = [innerError1, innerError2];
const outerError = new Error('outer error');
const outerAggregate = new Error('Outer aggregate');
outerAggregate.errors = [innerAggregate, outerError];
const lines = subject.stack(outerAggregate).split('\n');
const innerAggMsgIx = lines.findIndex(line =>
line.includes('Error 1: Error: Inner aggregate')
);
expect(innerAggMsgIx).toBeGreaterThan(-1);
const innerError1MsgIx = lines.findIndex(line =>
line.includes('Error 1: Error: inner error 1')
);
expect(innerError1MsgIx).toBeGreaterThan(innerAggMsgIx);
const innerError2MsgIx = lines.findIndex(line =>
line.includes('Error 2: Error: inner error 2')
);
expect(innerError2MsgIx).toBeGreaterThan(innerError1MsgIx);
const outerErrorMsgIx = lines.findIndex(line =>
line.includes('Error 2: Error: outer error')
);
expect(outerErrorMsgIx).toBeGreaterThan(innerError2MsgIx);
});
it('handles AggregateError containing error with cause', function() {
const subject = new privateUnderTest.ExceptionFormatter();
const rootCause = new Error('root cause');
const errorWithCause = new Error('error with cause', {
cause: rootCause
});
const aggregateError = new Error('Aggregate with cause chain');
aggregateError.errors = [errorWithCause];
const lines = subject.stack(aggregateError).split('\n');
const error1MsgIx = lines.findIndex(line =>
line.includes('Error 1: Error: error with cause')
);
expect(error1MsgIx).toBeGreaterThan(-1);
const causeMsgIx = lines.findIndex(line =>
line.includes('Caused by: Error: root cause')
);
expect(causeMsgIx).toBeGreaterThan(error1MsgIx);
});
it('skips non-Error items in errors array', function() {
const subject = new privateUnderTest.ExceptionFormatter();
const error1 = new Error('real error');
const aggregateError = new Error('Mixed array');
aggregateError.errors = [
error1,
'string error',
{ message: 'object error' },
null,
undefined,
42
];
const lines = subject.stack(aggregateError).split('\n');
const error1MsgIx = lines.findIndex(line =>
line.includes('Error 1: Error: real error')
);
expect(error1MsgIx).toBeGreaterThan(-1);
const hasStringError = lines.some(line =>
line.includes('string error')
);
expect(hasStringError).toBe(false);
const hasObjectError = lines.some(line =>
line.includes('object error')
);
expect(hasObjectError).toBe(false);
});
it('works with native AggregateError constructor', function() {
const subject = new privateUnderTest.ExceptionFormatter();
const error1 = new Error('first error');
const error2 = new Error('second error');
const aggregateError = new AggregateError(
[error1, error2],
'Multiple errors'
);
const lines = subject.stack(aggregateError).split('\n');
const error1MsgIx = lines.findIndex(line =>
line.includes('Error 1: Error: first error')
);
expect(error1MsgIx).toBeGreaterThan(-1);
const error2MsgIx = lines.findIndex(line =>
line.includes('Error 2: Error: second error')
);
expect(error2MsgIx).toBeGreaterThan(error1MsgIx);
});
});
});
});

View File

@@ -13,10 +13,7 @@ describe('GlobalErrors', function() {
const error = new Error('nope');
dispatchEvent(globals.listeners, 'error', { error });
expect(handler).toHaveBeenCalledWith(
jasmine.is(error),
jasmine.objectContaining({ error: jasmine.is(error) })
);
expect(handler).toHaveBeenCalledWith(jasmine.is(error));
});
it('is not affected by overriding global.onerror', function() {
@@ -35,10 +32,7 @@ describe('GlobalErrors', function() {
const error = new Error('nope');
dispatchEvent(globals.listeners, 'error', { error });
expect(handler).toHaveBeenCalledWith(
jasmine.is(error),
jasmine.objectContaining({ error: jasmine.is(error) })
);
expect(handler).toHaveBeenCalledWith(jasmine.is(error));
});
it('only calls the most recent handler', function() {
@@ -58,10 +52,7 @@ describe('GlobalErrors', function() {
dispatchEvent(globals.listeners, 'error', { error });
expect(handler1).not.toHaveBeenCalled();
expect(handler2).toHaveBeenCalledWith(
jasmine.is(error),
jasmine.objectContaining({ error: jasmine.is(error) })
);
expect(handler2).toHaveBeenCalledWith(jasmine.is(error));
});
it('calls previous handlers when one is removed', function() {
@@ -82,10 +73,7 @@ describe('GlobalErrors', function() {
const error = new Error('nope');
dispatchEvent(globals.listeners, 'error', { error });
expect(handler1).toHaveBeenCalledWith(
jasmine.is(error),
jasmine.objectContaining({ error: jasmine.is(error) })
);
expect(handler1).toHaveBeenCalledWith(jasmine.is(error));
expect(handler2).not.toHaveBeenCalled();
});
@@ -130,6 +118,32 @@ describe('GlobalErrors', function() {
errors.uninstall();
});
it("reports browser error events that don't have errors", function() {
const globals = browserGlobals();
const handler = jasmine.createSpy('errorHandler');
const errors = new privateUnderTest.GlobalErrors(
globals.global,
() => ({})
);
errors.install();
errors.pushListener(handler);
const event = {
message: 'Uncaught SyntaxError: Unexpected end of input',
error: undefined,
filename: 'borkenSpec.js',
lineno: 42
};
dispatchEvent(globals.listeners, 'error', event);
expect(handler).toHaveBeenCalledWith({
message: 'Uncaught SyntaxError: Unexpected end of input',
filename: 'borkenSpec.js',
lineno: 42,
stack: '@borkenSpec.js:42'
});
});
it('reports uncaught exceptions in node.js', function() {
const globals = nodeGlobals();
const errors = new privateUnderTest.GlobalErrors(
@@ -152,7 +166,7 @@ describe('GlobalErrors', function() {
dispatchEvent(globals.listeners, 'uncaughtException', new Error('bar'));
expect(handler).toHaveBeenCalledWith(new Error('bar'), undefined);
expect(handler).toHaveBeenCalledWith(new Error('bar'));
expect(handler.calls.argsFor(0)[0].jasmineMessage).toBe(
'Uncaught exception: Error: bar'
);
@@ -185,7 +199,7 @@ describe('GlobalErrors', function() {
dispatchEvent(globals.listeners, 'unhandledRejection', new Error('bar'));
expect(handler).toHaveBeenCalledWith(new Error('bar'), undefined);
expect(handler).toHaveBeenCalledWith(new Error('bar'));
expect(handler.calls.argsFor(0)[0].jasmineMessage).toBe(
'Unhandled promise rejection: Error: bar'
);
@@ -213,8 +227,7 @@ describe('GlobalErrors', function() {
'Unhandled promise rejection: 17\n' +
'(Tip: to get a useful stack trace, use ' +
'Promise.reject(new Error(...)) instead of Promise.reject(...).)'
),
undefined
)
);
});
@@ -236,8 +249,7 @@ describe('GlobalErrors', function() {
'Unhandled promise rejection with no error or message\n' +
'(Tip: to get a useful stack trace, use ' +
'Promise.reject(new Error(...)) instead of Promise.reject().)'
),
undefined
)
);
});
@@ -281,7 +293,7 @@ describe('GlobalErrors', function() {
undefined
);
expect(handler).toHaveBeenCalledWith(new Error('nope'), undefined);
expect(handler).toHaveBeenCalledWith(new Error('nope'));
expect(handler.calls.argsFor(0)[0].jasmineMessage).toBe(
'Unhandled promise rejection: Error: nope'
);
@@ -324,7 +336,7 @@ describe('GlobalErrors', function() {
);
errors.reportUnhandledRejections();
expect(handler).toHaveBeenCalledWith(new Error('nope'), undefined);
expect(handler).toHaveBeenCalledWith(new Error('nope'));
expect(handler.calls.argsFor(0)[0].jasmineMessage).toBe(
'Unhandled promise rejection: Error: nope'
);
@@ -407,10 +419,7 @@ describe('GlobalErrors', function() {
const event = { reason: 'nope' };
dispatchEvent(globals.listeners, 'unhandledrejection', event);
expect(handler).toHaveBeenCalledWith(
'Unhandled promise rejection: nope',
event
);
expect(handler).toHaveBeenCalledWith('Unhandled promise rejection: nope');
});
it('reports rejections whose reason is an Error', function() {
@@ -428,13 +437,15 @@ describe('GlobalErrors', function() {
const event = { reason };
dispatchEvent(globals.listeners, 'unhandledrejection', event);
expect(handler).toHaveBeenCalledWith(
expect(handler).toHaveBeenCalledTimes(1);
const received = handler.calls.argsFor(0)[0];
expect(received).toBeInstanceOf(Error);
expect(received).toEqual(
jasmine.objectContaining({
jasmineMessage: 'Unhandled promise rejection: Error: bar',
message: reason.message,
stack: reason.stack
}),
event
})
);
});
@@ -469,8 +480,7 @@ describe('GlobalErrors', function() {
dispatchEvent(globals.listeners, 'unhandledrejection', event);
expect(handler).toHaveBeenCalledWith(
'Unhandled promise rejection: nope',
event
'Unhandled promise rejection: nope'
);
});
});
@@ -507,8 +517,7 @@ describe('GlobalErrors', function() {
errors.reportUnhandledRejections();
expect(handler).toHaveBeenCalledWith(
'Unhandled promise rejection: nope',
{ reason: 'nope', promise }
'Unhandled promise rejection: nope'
);
});
@@ -567,10 +576,7 @@ describe('GlobalErrors', function() {
dispatchEvent(globals.listeners, 'uncaughtException', 17);
expect(handler).toHaveBeenCalledWith(
new Error('Uncaught exception: 17'),
undefined
);
expect(handler).toHaveBeenCalledWith(new Error('Uncaught exception: 17'));
});
it('substitutes a descriptive message when the error is falsy', function() {
@@ -587,8 +593,7 @@ describe('GlobalErrors', function() {
dispatchEvent(globals.listeners, 'uncaughtException', undefined);
expect(handler).toHaveBeenCalledWith(
new Error('Uncaught exception with no error or message'),
undefined
new Error('Uncaught exception with no error or message')
);
});
});
@@ -619,7 +624,7 @@ describe('GlobalErrors', function() {
const event = { error: 'baz' };
dispatchEvent(globals.listeners, 'error', event);
expect(overrideHandler).not.toHaveBeenCalledWith('baz');
expect(handler1).toHaveBeenCalledWith('baz', event);
expect(handler1).toHaveBeenCalledWith('baz');
});
it('overrides the existing handlers in Node until removed', function() {
@@ -648,7 +653,7 @@ describe('GlobalErrors', function() {
dispatchEvent(globals.listeners, 'uncaughtException', new Error('bar'));
expect(overrideHandler).not.toHaveBeenCalled();
expect(handler1).toHaveBeenCalledWith(new Error('bar'), undefined);
expect(handler1).toHaveBeenCalledWith(new Error('bar'));
});
it('handles unhandled promise rejections in browsers', function() {

View File

@@ -471,31 +471,6 @@ describe('QueueRunner', function() {
expect(nextQueueableFn.fn).toHaveBeenCalled();
});
it('handles a global error event with a message but no error', function() {
const queueableFn = {
fn: function(done) {
const currentHandler = globalErrors.pushListener.calls.mostRecent()
.args[0];
currentHandler(undefined, { message: 'nope' });
},
timeout: 1
};
const onException = jasmine.createSpy('onException');
const globalErrors = {
pushListener: jasmine.createSpy('pushListener'),
popListener: jasmine.createSpy('popListener')
};
const queueRunner = new privateUnderTest.QueueRunner({
queueableFns: [queueableFn],
onException: onException,
globalErrors: globalErrors
});
queueRunner.execute();
expect(onException).toHaveBeenCalledWith('nope');
});
it('handles exceptions thrown while waiting for the stack to clear', function() {
const queueableFn = {
fn: function(done) {
@@ -511,7 +486,7 @@ describe('QueueRunner', function() {
errorListeners.pop();
}
},
clearStack = jasmine.createSpy('clearStack'),
clearStack = jasmine.createSpyObj('clearStack', ['clearStack']),
onException = jasmine.createSpy('onException'),
queueRunner = new privateUnderTest.QueueRunner({
queueableFns: [queueableFn],
@@ -523,46 +498,12 @@ describe('QueueRunner', function() {
queueRunner.execute();
jasmine.clock().tick();
expect(clearStack).toHaveBeenCalled();
expect(clearStack.clearStack).toHaveBeenCalled();
expect(errorListeners.length).toEqual(1);
errorListeners[0](error);
clearStack.calls.argsFor(0)[0]();
clearStack.clearStack.calls.argsFor(0)[0]();
expect(onException).toHaveBeenCalledWith(error);
});
it('handles a global error event with no error while waiting for the stack to clear', function() {
const queueableFn = {
fn: function(done) {
done();
}
};
const errorListeners = [];
const globalErrors = {
pushListener: function(f) {
errorListeners.push(f);
},
popListener: function() {
errorListeners.pop();
}
};
const clearStack = jasmine.createSpy('clearStack');
const onException = jasmine.createSpy('onException');
const queueRunner = new privateUnderTest.QueueRunner({
queueableFns: [queueableFn],
globalErrors: globalErrors,
clearStack: clearStack,
onException: onException
});
queueRunner.execute();
jasmine.clock().tick();
expect(clearStack).toHaveBeenCalled();
expect(errorListeners.length).toEqual(1);
errorListeners[0](undefined, { message: 'nope' });
clearStack.calls.argsFor(0)[0]();
expect(onException).toHaveBeenCalledWith('nope');
});
});
describe('with a function that returns a promise', function() {
@@ -967,22 +908,22 @@ describe('QueueRunner', function() {
},
afterFn = { fn: jasmine.createSpy('afterFn') },
completeCallback = jasmine.createSpy('completeCallback'),
clearStack = jasmine.createSpy('clearStack'),
clearStack = jasmine.createSpyObj('clearStack', ['clearStack']),
queueRunner = new privateUnderTest.QueueRunner({
queueableFns: [asyncFn, afterFn],
clearStack: clearStack,
onComplete: completeCallback
});
clearStack.and.callFake(function(fn) {
clearStack.clearStack.and.callFake(function(fn) {
fn();
});
queueRunner.execute();
jasmine.clock().tick();
expect(afterFn.fn).toHaveBeenCalled();
expect(clearStack).toHaveBeenCalled();
clearStack.calls.argsFor(0)[0]();
expect(clearStack.clearStack).toHaveBeenCalled();
clearStack.clearStack.calls.argsFor(0)[0]();
expect(completeCallback).toHaveBeenCalled();
});
});

View File

@@ -33,18 +33,15 @@ describe('Runner', function() {
attrs = attrs || {};
this.id = 'suite' + suiteNumber++;
this.children = attrs.children || [];
this.canBeReentered = function() {
return !attrs.noReenter;
};
this.markedPending = attrs.markedPending || false;
this.sharedUserContext = function() {
return attrs.userContext || {};
};
this.result = {
id: this.id,
failedExpectations: []
};
this.getResult = jasmine.createSpy('getResult');
this.startedEvent = jasmine.createSpy('startedEvent');
this.doneEvent = jasmine.createSpy('doneEvent');
this.hasOwnFailedExpectations = jasmine.createSpy(
'hasOwnFailedExpectations'
);
this.beforeAllFns = attrs.beforeAllFns || [];
this.afterAllFns = attrs.afterAllFns || [];
this.cleanupBeforeAfter = function() {};
@@ -90,7 +87,7 @@ describe('Runner', function() {
function arrayNotContaining(item) {
return {
asymmetricMatch(other, matchersUtil) {
if (!jasmine.private.isArray(other)) {
if (!Array.isArray(other)) {
return false;
}
@@ -130,6 +127,7 @@ describe('Runner', function() {
children: [spec],
userContext: { root: 'context' }
});
topSuite.doneEvent.and.returnValue({});
detectLateRejectionHandling = true;
const subject = makeRunner(topSuite);
@@ -157,6 +155,7 @@ describe('Runner', function() {
children: [suite],
userContext: { for: 'topSuite' }
});
topSuite.doneEvent.and.returnValue({});
suite.parentSuite = topSuite;
const subject = makeRunner(topSuite);
@@ -185,10 +184,13 @@ describe('Runner', function() {
SkipPolicy: privateUnderTest.SkipAfterBeforeAllErrorPolicy
});
suite.startedEvent.and.returnValue('suite started event');
runQueue.calls.mostRecent().args[0].queueableFns[0].fn('foo');
expect(reportDispatcher.suiteStarted).toHaveBeenCalledWith(suite.result);
expect(reportDispatcher.suiteStarted).toHaveBeenCalledWith(
'suite started event'
);
suite.getResult.and.returnValue({ my: 'result' });
suite.doneEvent.and.returnValue({ my: 'result' });
runQueue.calls.mostRecent().args[0].onComplete();
expect(reportDispatcher.suiteDone).toHaveBeenCalledWith({ my: 'result' });
@@ -236,14 +238,19 @@ describe('Runner', function() {
queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
expect(queueableFns.length).toBe(2);
parent.startedEvent.and.returnValue('parent suite started event');
queueableFns[0].fn();
expect(reportDispatcher.suiteStarted).toHaveBeenCalledWith(parent.result);
expect(reportDispatcher.suiteStarted).toHaveBeenCalledWith(
'parent suite started event'
);
verifyAndFinishSpec(spec, queueableFns[1], true);
parent.getResult.and.returnValue(parent.result);
parent.doneEvent.and.returnValue('parent suite done event');
runQueue.calls.argsFor(1)[0].onComplete();
expect(reportDispatcher.suiteDone).toHaveBeenCalledWith(parent.result);
expect(reportDispatcher.suiteDone).toHaveBeenCalledWith(
'parent suite done event'
);
await expectAsync(promise).toBePending();
});

View File

@@ -85,7 +85,7 @@ describe('Spec', function() {
spec.setSpecProperty('a', 4);
expect(spec.result.properties).toEqual({ a: 4 });
expect(spec.doneEvent().properties).toEqual({ a: 4 });
});
it('replace the property result when it was previously set', function() {
@@ -97,32 +97,20 @@ describe('Spec', function() {
spec.setSpecProperty('b', 'original-value');
spec.setSpecProperty('a', 'new-value');
expect(spec.result.properties).toEqual({
expect(spec.doneEvent().properties).toEqual({
a: 'new-value',
b: 'original-value'
});
});
it('throws if the key is not structured-cloneable', function() {
it('throws if the key is not a string', function() {
const spec = new privateUnderTest.Spec({
queueableFn: { fn: () => {} }
});
expect(function() {
spec.setSpecProperty(new Promise(() => {}), '');
}).toThrowError("Key can't be cloned");
});
it('throws if the key is not JSON-serializable', function() {
const spec = new privateUnderTest.Spec({
queueableFn: { fn: () => {} }
});
expect(function() {
const k = {};
k.self = k;
spec.setSpecProperty(k, '');
}).toThrowError("Key can't be cloned");
spec.setSpecProperty({}, '');
}).toThrowError('Key must be a string');
});
it('throws if the value is not structured-cloneable', function() {
@@ -149,11 +137,16 @@ describe('Spec', function() {
});
describe('status', function() {
it('is "passed" by default', function() {
it('returns "passed" by default', function() {
const spec = new privateUnderTest.Spec({
queueableFn: { fn: () => {} }
});
expect(spec.getResult().status).toBe('passed');
expect(spec.status())
.withContext('status()')
.toBe('passed');
expect(spec.doneEvent().status)
.withContext('doneEvent().status')
.toBe('passed');
});
it('is "passed" if all expectations passed', function() {
@@ -163,7 +156,12 @@ describe('Spec', function() {
spec.addExpectationResult(true, {});
expect(spec.getResult().status).toBe('passed');
expect(spec.status())
.withContext('status()')
.toBe('passed');
expect(spec.doneEvent().status)
.withContext('doneEvent().status')
.toBe('passed');
});
it('is "failed" if any expectation failed', function() {
@@ -174,7 +172,12 @@ describe('Spec', function() {
spec.addExpectationResult(true, {});
spec.addExpectationResult(false, {});
expect(spec.getResult().status).toBe('failed');
expect(spec.status())
.withContext('status()')
.toBe('failed');
expect(spec.doneEvent().status)
.withContext('doneEvent().status')
.toBe('failed');
});
it('is "pending" if created without a function body', function() {
@@ -186,7 +189,65 @@ describe('Spec', function() {
resultCallback: resultCallback
});
expect(spec.getResult().status).toBe('pending');
expect(spec.status())
.withContext('status()')
.toBe('pending');
expect(spec.doneEvent().status)
.withContext('doneEvent().status')
.toBe('pending');
});
describe('after a call to executionFinished()', function() {
describe('with excluded true', function() {
it("is 'excluded'", function() {
const spec = new privateUnderTest.Spec({
queueableFn: { fn: () => {} }
});
spec.executionFinished(true, false);
expect(spec.status())
.withContext('status()')
.toBe('excluded');
expect(spec.doneEvent().status)
.withContext('doneEvent().status')
.toBe('excluded');
});
});
describe('with failSpecWithNoExp true', function() {
it("is 'failed' if there were no expectations", function() {
const spec = new privateUnderTest.Spec({
queueableFn: { fn: () => {} }
});
spec.executionFinished(false, true);
expect(spec.status())
.withContext('status()')
.toBe('failed');
expect(spec.doneEvent().status)
.withContext('doneEvent().status')
.toBe('failed');
});
});
});
describe('after a call to hadBeforeAllFailure()', function() {
it("is 'failed'", function() {
const spec = new privateUnderTest.Spec({
queueableFn: { fn: () => {} }
});
spec.hadBeforeAllFailure();
expect(spec.status())
.withContext('status()')
.toBe('failed');
expect(spec.doneEvent().status)
.withContext('doneEvent().status')
.toBe('failed');
});
});
});
@@ -199,10 +260,10 @@ describe('Spec', function() {
spec.addExpectationResult(true, { message: 'expectation1' });
spec.addExpectationResult(false, { message: 'expectation2' });
expect(spec.result.passedExpectations).toEqual([
expect(spec.doneEvent().passedExpectations).toEqual([
jasmine.objectContaining({ message: 'expectation1' })
]);
expect(spec.result.failedExpectations).toEqual([
expect(spec.doneEvent().failedExpectations).toEqual([
jasmine.objectContaining({ message: 'expectation2' })
]);
});
@@ -219,7 +280,7 @@ describe('Spec', function() {
spec.addExpectationResult(false, { message: 'failed' });
}).toThrowError(jasmineUnderTest.private.errors.ExpectationFailed);
expect(spec.result.failedExpectations).toEqual([
expect(spec.doneEvent().failedExpectations).toEqual([
jasmine.objectContaining({ message: 'failed' })
]);
});
@@ -233,7 +294,7 @@ describe('Spec', function() {
spec.addExpectationResult(false, { message: 'failed' });
expect(spec.result.failedExpectations).toEqual([
expect(spec.doneEvent().failedExpectations).toEqual([
jasmine.objectContaining({ message: 'failed' })
]);
});
@@ -262,7 +323,7 @@ describe('Spec', function() {
message: jasmine.stringMatching(/^Error: nope/)
})
);
expect(spec.result.failedExpectations).toEqual([]);
expect(spec.doneEvent().failedExpectations).toEqual([]);
});
it('does not forward non-late expectation failures to onLateError', function() {
@@ -299,7 +360,7 @@ describe('Spec', function() {
message: jasmine.stringMatching(/^Error: oops/)
})
);
expect(spec.result.failedExpectations).toEqual([]);
expect(spec.doneEvent().failedExpectations).toEqual([]);
});
it('does not forward non-late handleException calls to onLateError', function() {
@@ -313,7 +374,7 @@ describe('Spec', function() {
spec.handleException(error);
expect(onLateError).not.toHaveBeenCalled();
expect(spec.result.failedExpectations.length).toEqual(1);
expect(spec.doneEvent().failedExpectations.length).toEqual(1);
});
it('clears the reportedDone flag when reset', function() {
@@ -376,7 +437,7 @@ describe('Spec', function() {
spec.handleException('foo');
expect(spec.result.failedExpectations).toEqual([
expect(spec.doneEvent().failedExpectations).toEqual([
{
message: 'foo thrown',
matcherName: '',
@@ -396,7 +457,7 @@ describe('Spec', function() {
new jasmineUnderTest.private.errors.ExpectationFailed()
);
expect(spec.result.failedExpectations).toEqual([]);
expect(spec.doneEvent().failedExpectations).toEqual([]);
});
});
@@ -410,15 +471,15 @@ describe('Spec', function() {
const t1 = 123;
const t2 = 456;
expect(spec.result.debugLogs).toBeNull();
expect(spec.doneEvent().debugLogs).toBeNull();
timer.elapsed.and.returnValue(t1);
spec.debugLog('msg 1');
expect(spec.result.debugLogs).toEqual([
expect(spec.doneEvent().debugLogs).toEqual([
{ message: 'msg 1', timestamp: t1 }
]);
timer.elapsed.and.returnValue(t2);
spec.debugLog('msg 2');
expect(spec.result.debugLogs).toEqual([
expect(spec.doneEvent().debugLogs).toEqual([
{ message: 'msg 1', timestamp: t1 },
{ message: 'msg 2', timestamp: t2 }
]);
@@ -433,7 +494,7 @@ describe('Spec', function() {
spec.debugLog('msg');
spec.executionFinished();
expect(spec.result.debugLogs).toBeNull();
expect(spec.doneEvent().debugLogs).toBeNull();
});
});
@@ -452,7 +513,7 @@ describe('Spec', function() {
spec.handleException(new Error('nope'));
spec.executionFinished();
expect(spec.result.debugLogs).toEqual([
expect(spec.doneEvent().debugLogs).toEqual([
{ message: 'msg', timestamp: timestamp }
]);
});

View File

@@ -1,6 +1,6 @@
describe('ClearStack', function() {
describe('StackClearer', function() {
it('works in an integrationy way', function(done) {
const clearStack = privateUnderTest.getClearStack(
const { clearStack } = privateUnderTest.getStackClearer(
jasmineUnderTest.getGlobal()
);
@@ -36,7 +36,7 @@ describe('ClearStack', function() {
queueMicrotask
};
const clearStack = privateUnderTest.getClearStack(global);
const { clearStack } = privateUnderTest.getStackClearer(global);
for (let i = 0; i < 9; i++) {
clearStack(function() {});
@@ -73,17 +73,6 @@ describe('ClearStack', function() {
}
};
});
describe('when MessageChannel is unavailable', function() {
usesQueueMicrotaskWithSetTimeout(function() {
return {
navigator: {
userAgent: 'CERN-LineMode/2.15 libwww/2.17b3',
MessageChannel: undefined
}
};
});
});
});
describe('in Node', function() {
@@ -104,7 +93,7 @@ describe('ClearStack', function() {
...makeGlobal(),
MessageChannel: fakeMessageChannel
};
const clearStack = privateUnderTest.getClearStack(global);
const { clearStack } = privateUnderTest.getStackClearer(global);
let called = false;
clearStack(function() {
@@ -125,7 +114,7 @@ describe('ClearStack', function() {
return fakeChannel;
}
};
const clearStack = privateUnderTest.getClearStack(global);
const { clearStack } = privateUnderTest.getStackClearer(global);
for (let i = 0; i < 9; i++) {
clearStack(function() {});
@@ -150,7 +139,7 @@ describe('ClearStack', function() {
setTimeout,
MessageChannel: fakeMessageChannel
};
const clearStack = privateUnderTest.getClearStack(global);
const { clearStack } = privateUnderTest.getStackClearer(global);
const fn = jasmine.createSpy('second clearStack function');
clearStack(function() {
@@ -170,7 +159,7 @@ describe('ClearStack', function() {
fn();
}
};
const clearStack = privateUnderTest.getClearStack(global);
const { clearStack } = privateUnderTest.getStackClearer(global);
let called = false;
clearStack(function() {
@@ -180,30 +169,82 @@ describe('ClearStack', function() {
expect(called).toBe(true);
});
it('uses setTimeout instead of queueMicrotask every 10 calls to make sure we release the CPU', function() {
const queueMicrotask = jasmine.createSpy('queueMicrotask');
const setTimeout = jasmine.createSpy('setTimeout');
const global = {
...makeGlobal(),
queueMicrotask,
setTimeout
};
const clearStack = privateUnderTest.getClearStack(global);
function hasSetTimeoutBehavior(configure) {
it('uses setTimeout instead of queueMicrotask every 10 calls', function() {
const queueMicrotask = jasmine.createSpy('queueMicrotask');
const setTimeout = jasmine.createSpy('setTimeout');
const global = {
...makeGlobal(),
queueMicrotask,
setTimeout
};
const stackClearer = privateUnderTest.getStackClearer(global);
for (let i = 0; i < 9; i++) {
clearStack(function() {});
}
if (configure) {
configure(stackClearer);
}
expect(queueMicrotask).toHaveBeenCalled();
expect(setTimeout).not.toHaveBeenCalled();
for (let i = 0; i < 9; i++) {
stackClearer.clearStack(function() {});
}
clearStack(function() {});
expect(queueMicrotask).toHaveBeenCalledTimes(9);
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(queueMicrotask).toHaveBeenCalled();
expect(setTimeout).not.toHaveBeenCalled();
clearStack(function() {});
expect(queueMicrotask).toHaveBeenCalledTimes(10);
expect(setTimeout).toHaveBeenCalledTimes(1);
stackClearer.clearStack(function() {});
expect(queueMicrotask).toHaveBeenCalledTimes(9);
expect(setTimeout).toHaveBeenCalledTimes(1);
stackClearer.clearStack(function() {});
expect(queueMicrotask).toHaveBeenCalledTimes(10);
expect(setTimeout).toHaveBeenCalledTimes(1);
});
}
hasSetTimeoutBehavior();
describe('With yield strategy explicitly set to count', function() {
hasSetTimeoutBehavior(function(stackClearer) {
stackClearer.setSafariYieldStrategy('count');
});
});
describe('With yield strategy set to time', function() {
beforeEach(function() {
jasmine.clock().install();
jasmine.clock().mockDate();
});
afterEach(function() {
jasmine.clock().uninstall();
});
it('uses setTimeout instead of queueMicrotask every 25 milliseconds', function() {
const queueMicrotask = jasmine.createSpy('queueMicrotask');
const setTimeout = jasmine.createSpy('setTimeout');
const global = {
...makeGlobal(),
queueMicrotask,
setTimeout
};
const stackClearer = privateUnderTest.getStackClearer(global);
stackClearer.setSafariYieldStrategy('time');
// 10+ counts should not trigger a setTimeout if they happen fast enough
jasmine.clock().tick(24);
for (let i = 0; i < 11; i++) {
stackClearer.clearStack(function() {});
}
expect(queueMicrotask).toHaveBeenCalled();
expect(setTimeout).not.toHaveBeenCalled();
queueMicrotask.calls.reset();
jasmine.clock().tick(1);
stackClearer.clearStack(function() {});
expect(queueMicrotask).not.toHaveBeenCalled();
expect(setTimeout).toHaveBeenCalledTimes(1);
});
});
}
@@ -215,7 +256,7 @@ describe('ClearStack', function() {
fn();
}
};
const clearStack = privateUnderTest.getClearStack(global);
const { clearStack } = privateUnderTest.getStackClearer(global);
let called = false;
clearStack(function() {
@@ -233,7 +274,7 @@ describe('ClearStack', function() {
queueMicrotask,
setTimeout
};
const clearStack = privateUnderTest.getClearStack(global);
const { clearStack } = privateUnderTest.getStackClearer(global);
clearStack(function() {});
clearStack(function() {});

View File

@@ -311,10 +311,11 @@ describe('SuiteBuilder', function() {
suiteBuilder.topSuite.handleException(new Error('nope'));
suiteBuilder.parallelReset();
expect(suiteBuilder.topSuite.result).toEqual({
expect(suiteBuilder.topSuite.doneEvent()).toEqual({
id: suiteBuilder.topSuite.id,
description: 'Jasmine__TopLevel__Suite',
fullName: '',
status: 'passed',
failedExpectations: [],
deprecationWarnings: [],
duration: null,

View File

@@ -115,20 +115,20 @@ describe('Suite', function() {
const suite = new privateUnderTest.Suite({});
suite.addExpectationResult(false, {});
expect(suite.getResult().status).toBe('failed');
expect(suite.doneEvent().status).toBe('failed');
});
it('retrieves a result with updated status', function() {
const suite = new privateUnderTest.Suite({});
expect(suite.getResult().status).toBe('passed');
expect(suite.doneEvent().status).toBe('passed');
});
it('retrieves a result with pending status', function() {
const suite = new privateUnderTest.Suite({});
suite.pend();
expect(suite.getResult().status).toBe('pending');
expect(suite.doneEvent().status).toBe('pending');
});
it('throws an ExpectationFailed when receiving a failed expectation when throwOnExpectationFailure is set', function() {
@@ -140,8 +140,8 @@ describe('Suite', function() {
suite.addExpectationResult(false, { message: 'failed' });
}).toThrowError(jasmineUnderTest.private.errors.ExpectationFailed);
expect(suite.getResult().status).toBe('failed');
expect(suite.result.failedExpectations).toEqual([
expect(suite.doneEvent().status).toBe('failed');
expect(suite.doneEvent().failedExpectations).toEqual([
jasmine.objectContaining({ message: 'failed' })
]);
});
@@ -153,7 +153,7 @@ describe('Suite', function() {
new jasmineUnderTest.private.errors.ExpectationFailed()
);
expect(suite.getResult().failedExpectations).toEqual([]);
expect(suite.doneEvent().failedExpectations).toEqual([]);
});
it('forwards late expectation failures to onLateError', function() {
@@ -175,7 +175,7 @@ describe('Suite', function() {
message: jasmine.stringMatching(/^Error: nope/)
})
);
expect(suite.result.failedExpectations).toEqual([]);
expect(suite.doneEvent().failedExpectations).toEqual([]);
});
it('does not forward non-late expectation failures to onLateError', function() {
@@ -194,7 +194,7 @@ describe('Suite', function() {
suite.addExpectationResult(false, data, true);
expect(onLateError).not.toHaveBeenCalled();
expect(suite.result.failedExpectations.length).toEqual(1);
expect(suite.doneEvent().failedExpectations.length).toEqual(1);
});
it('forwards late handleException calls to onLateError', function() {
@@ -212,7 +212,7 @@ describe('Suite', function() {
message: jasmine.stringMatching(/^Error: oops/)
})
);
expect(suite.result.failedExpectations).toEqual([]);
expect(suite.doneEvent().failedExpectations).toEqual([]);
});
it('does not forward non-late handleException calls to onLateError', function() {
@@ -225,7 +225,7 @@ describe('Suite', function() {
suite.handleException(error);
expect(onLateError).not.toHaveBeenCalled();
expect(suite.result.failedExpectations.length).toEqual(1);
expect(suite.doneEvent().failedExpectations.length).toEqual(1);
});
it('clears the reportedDone flag when reset', function() {
@@ -248,7 +248,7 @@ describe('Suite', function() {
});
suite.startTimer();
suite.endTimer();
expect(suite.getResult().duration).toEqual(77000);
expect(suite.doneEvent().duration).toEqual(77000);
});
describe('#sharedUserContext', function() {
@@ -306,14 +306,14 @@ describe('Suite', function() {
const suite = new privateUnderTest.Suite({});
suite.pend();
suite.reset();
expect(suite.getResult().status).toBe('passed');
expect(suite.doneEvent().status).toBe('passed');
});
it('should not reset the "pending" status when the suite was excluded', function() {
const suite = new privateUnderTest.Suite({});
suite.exclude();
suite.reset();
expect(suite.getResult().status).toBe('pending');
expect(suite.doneEvent().status).toBe('pending');
});
it('should also reset the children', function() {
@@ -335,7 +335,7 @@ describe('Suite', function() {
suite.reset();
const result = suite.getResult();
const result = suite.doneEvent();
expect(result.status).toBe('passed');
expect(result.failedExpectations).toHaveSize(0);
});
@@ -409,12 +409,12 @@ describe('Suite', function() {
});
describe('#setSuiteProperty', function() {
it('throws if the key is not structured-cloneable', function() {
it('throws if the key is not a string', function() {
const suite = new privateUnderTest.Suite({});
expect(function() {
suite.setSuiteProperty(new Promise(() => {}), '');
}).toThrowError("Key can't be cloned");
suite.setSuiteProperty({}, '');
}).toThrowError('Key must be a string');
});
it('throws if the value is not structured-cloneable', function() {
@@ -424,5 +424,45 @@ describe('Suite', function() {
suite.setSuiteProperty('k', new Promise(() => {}));
}).toThrowError("Value can't be cloned");
});
it('throws if the value is not JSON-serializable', function() {
const suite = new privateUnderTest.Suite({});
expect(function() {
const v = {};
v.self = v;
suite.setSuiteProperty('k', v);
}).toThrowError("Value can't be cloned");
});
});
describe('#startedEvent', function() {
it('includes only properties that are known before execution', function() {
const topSuite = new privateUnderTest.Suite({});
const parentSuite = new privateUnderTest.Suite({
id: 'suite1',
parentSuite: topSuite,
description: 'a parent suite'
});
const suite = new privateUnderTest.Suite({
id: 'suite2',
parentSuite,
reportedParentSuiteId: parentSuite.id,
description: 'a suite',
filename: 'somefile.js',
getPath() {
return ['a parent suite', 'a spec'];
},
queueableFn: { fn: () => {} }
});
expect(suite.startedEvent()).toEqual({
id: 'suite2',
parentSuiteId: 'suite1',
description: 'a suite',
fullName: 'a parent suite a suite',
filename: 'somefile.js'
});
});
});
});

View File

@@ -6,9 +6,6 @@ describe('TreeProcessor', function() {
attrs = attrs || {};
this.id = 'node' + nodeNumber++;
this.children = attrs.children || [];
this.canBeReentered = function() {
return !attrs.noReenter;
};
this.markedPending = attrs.markedPending || false;
this.sharedUserContext = function() {
return attrs.userContext || {};
@@ -195,47 +192,6 @@ describe('TreeProcessor', function() {
expect(result.isExcluded(childOfPending)).toEqual(false);
});
it('throws if the specified order would re-enter a node', function() {
const leaf1 = new Leaf(),
leaf2 = new Leaf(),
leaf3 = new Leaf(),
reentered = new Node({ noReenter: true, children: [leaf1, leaf2] }),
root = new Node({ children: [reentered, leaf3] }),
processor = new privateUnderTest.TreeProcessor({
tree: root,
runnableIds: [leaf1.id, leaf3.id, leaf2.id]
});
expect(function() {
processor.processTree();
}).toThrowError('Invalid order: would split up a suite');
});
it("does not throw if a node which can't be re-entered is only entered once", function() {
const leaf1 = new Leaf(),
leaf2 = new Leaf(),
leaf3 = new Leaf(),
noReentry = new Node({ noReenter: true }),
root = new Node({ children: [noReentry] }),
processor = new privateUnderTest.TreeProcessor({
tree: root,
runnableIds: [leaf2.id, leaf1.id, leaf3.id]
});
processor.processTree();
});
it("does not throw if a node which can't be re-entered is run directly", function() {
const noReentry = new Node({ noReenter: true }),
root = new Node({ children: [noReentry] }),
processor = new privateUnderTest.TreeProcessor({
tree: root,
runnableIds: [root.id]
});
processor.processTree();
});
it('orders children according to orderChildren when specified', function() {
const leaf1 = new Leaf();
const leaf2 = new Leaf();
@@ -290,4 +246,53 @@ describe('TreeProcessor', function() {
{ spec: leaf1 }
]);
});
describe("The returned ExecutionTree's numExcludedSpecs method", function() {
it('counts filtered-out specs', function() {
const included = new Leaf();
const excluded1 = new Leaf();
const excluded2 = new Leaf();
const excluded3 = new Leaf();
const topSuite = new Node({
children: [
excluded1,
new Node({
children: [included, excluded2, new Node({ children: [excluded3] })]
})
]
});
const processor = new privateUnderTest.TreeProcessor({
tree: topSuite,
runnableIds: [topSuite.id],
excludeNode(node) {
return node.id !== included.id;
}
});
const executionTree = processor.processTree();
expect(executionTree.numExcludedSpecs()).toEqual(3);
});
it("counts specs that aren't in or descendants of runnableIds", function() {
const includedSuite = new Node({
children: [new Node({ children: [new Leaf()] }), new Leaf()]
});
const directlyIncludedSpec = new Leaf();
const topSuite = new Node({
children: [
includedSuite,
new Node({
children: [new Leaf(), directlyIncludedSpec]
})
]
});
const processor = new privateUnderTest.TreeProcessor({
tree: topSuite,
runnableIds: [includedSuite.id, directlyIncludedSpec.id]
});
const executionTree = processor.processTree();
expect(executionTree.numExcludedSpecs()).toEqual(1);
});
});
});

View File

@@ -64,7 +64,7 @@ describe('TreeRunner', function() {
expect(currentRunableTracker.currentSpec()).toBeFalsy();
expect(runableResources.clearForRunable).toHaveBeenCalledWith(spec.id);
expect(reportDispatcher.specDone).toHaveBeenCalledWith(spec.doneEvent());
expect(spec.result.duration).toEqual('the elapsed time');
expect(spec.doneEvent().duration).toEqual('the elapsed time');
expect(spec.reportedDone).toEqual(true);
await Promise.resolve();
await Promise.resolve();
@@ -116,8 +116,8 @@ describe('TreeRunner', function() {
expect(specRunQueueArgs.queueableFns[1]).toEqual(queueableFn);
queueableFn.fn();
expect(spec.getResult().status).toEqual('pending');
expect(spec.getResult().pendingReason).toEqual('');
expect(spec.doneEvent().status).toEqual('pending');
expect(spec.doneEvent().pendingReason).toEqual('');
});
it('marks specs pending at runtime with a message', function() {
@@ -137,8 +137,8 @@ describe('TreeRunner', function() {
expect(specRunQueueArgs.queueableFns[1]).toEqual(queueableFn);
queueableFn.fn();
expect(spec.getResult().status).toEqual('pending');
expect(spec.getResult().pendingReason).toEqual('some reason');
expect(spec.doneEvent().status).toEqual('pending');
expect(spec.doneEvent().pendingReason).toEqual('some reason');
});
it('passes failSpecWithNoExp to Spec#executionFinished', async function() {
@@ -218,7 +218,7 @@ describe('TreeRunner', function() {
timer.elapsed.and.returnValue('the duration');
suiteRunQueueOpts.onComplete();
expect(timer.elapsed).toHaveBeenCalled();
const result = suite.getResult();
const result = suite.doneEvent();
expect(result.duration).toEqual('the duration');
expect(reportDispatcher.suiteDone).toHaveBeenCalledWith(result);

View File

@@ -1,20 +1,4 @@
describe('util', function() {
describe('isArray', function() {
it('should return true if the argument is an array', function() {
expect(privateUnderTest.isArray([])).toBe(true);
expect(privateUnderTest.isArray(['a'])).toBe(true);
});
it('should return false if the argument is not an array', function() {
expect(privateUnderTest.isArray(undefined)).toBe(false);
expect(privateUnderTest.isArray({})).toBe(false);
expect(privateUnderTest.isArray(function() {})).toBe(false);
expect(privateUnderTest.isArray('foo')).toBe(false);
expect(privateUnderTest.isArray(5)).toBe(false);
expect(privateUnderTest.isArray(null)).toBe(false);
});
});
describe('isObject', function() {
it('should return true if the argument is an object', function() {
expect(privateUnderTest.isObject({})).toBe(true);

View File

@@ -0,0 +1,63 @@
describe('AllOf', function() {
it('matches a single value', function() {
const matchersUtil = new privateUnderTest.MatchersUtil();
const allOf = new privateUnderTest.AllOf('foo');
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeTrue();
});
it('matches a single matcher', function() {
const matchersUtil = new privateUnderTest.MatchersUtil();
const allOf = new privateUnderTest.AllOf(
new privateUnderTest.StringContaining('oo')
);
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeTrue();
});
it('matches multiple matchers', function() {
const matchersUtil = new privateUnderTest.MatchersUtil();
const allOf = new privateUnderTest.AllOf(
new privateUnderTest.StringContaining('o'),
new privateUnderTest.StringContaining('f')
);
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeTrue();
});
it('does not match when value does not match', function() {
const matchersUtil = new privateUnderTest.MatchersUtil();
const allOf = new privateUnderTest.AllOf('bar');
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeFalse();
});
it('does not match when any matchers fail', function() {
const matchersUtil = new privateUnderTest.MatchersUtil();
const allOf = new privateUnderTest.AllOf(
new privateUnderTest.StringContaining('o'),
new privateUnderTest.StringContaining('x')
);
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeFalse();
});
it('jasmineToStrings itself', function() {
const matcher = new privateUnderTest.AllOf('o');
const pp = jasmine.createSpy('pp').and.returnValue('sample');
expect(matcher.jasmineToString(pp)).toEqual('<jasmine.allOf(sample)>');
expect(pp).toHaveBeenCalledWith(['o']);
});
describe('when called without an argument', function() {
it('tells the user to pass a constructor argument', function() {
expect(function() {
new privateUnderTest.AllOf();
}).toThrowError(
TypeError,
'jasmine.allOf() expects at least one argument to be passed.'
);
});
});
});

View File

@@ -1,34 +1,5 @@
describe('base helpers', function() {
describe('isError', function() {
it('correctly handles WebSocket events', function(done) {
if (typeof jasmine.getGlobal().WebSocket === 'undefined') {
pending('Environment does not provide WebSocket');
}
const obj = (function() {
const sock = new WebSocket('ws://localhost');
let event;
sock.onerror = function(e) {
event = e;
};
return function() {
return event;
};
})();
let left = 20;
const int = setInterval(function() {
if (obj() || left === 0) {
const result = privateUnderTest.isError(obj());
expect(result).toBe(false);
clearInterval(int);
done();
} else {
left--;
}
}, 100);
});
it('returns true for an Error subclass', function() {
function MyError() {}
MyError.prototype = new Error();
@@ -199,8 +170,15 @@ describe('base helpers', function() {
});
describe('debugLog', function() {
beforeEach(function() {
privateUnderTest.currentEnv_ = jasmine.createSpyObj('env', ['debugLog']);
});
afterEach(function() {
privateUnderTest.currentEnv_ = null;
});
it("forwards to the current env's debugLog function", function() {
spyOn(jasmineUnderTest.getEnv(), 'debugLog');
jasmineUnderTest.debugLog('a message');
expect(jasmineUnderTest.getEnv().debugLog).toHaveBeenCalledWith(
'a message'

View File

@@ -1114,8 +1114,17 @@ describe('Env integration', function() {
global: {
setTimeout: globalSetTimeout,
clearTimeout: clearTimeout,
addEventListener() {},
removeEventListener() {},
queueMicrotask: function(fn) {
queueMicrotask(fn);
},
// Enough Node globals to make getStackClearer() return the microtask
// implementation, which is the easiest to mock
process: {
versions: {
node: ''
}
}
}
});
@@ -1179,7 +1188,7 @@ describe('Env integration', function() {
global: {
setTimeout: function(cb, t) {
const stack = new Error().stack;
if (stack.indexOf('ClearStack') >= 0) {
if (stack.indexOf('clearStack') >= 0) {
return realSetTimeout(cb, t);
} else {
return setTimeout(cb, t);
@@ -1188,8 +1197,17 @@ describe('Env integration', function() {
clearTimeout: clearTimeout,
setInterval: setInterval,
clearInterval: clearInterval,
addEventListener() {},
removeEventListener() {},
queueMicrotask: function(fn) {
queueMicrotask(fn);
},
// Enough Node globals to make getStackClearer() return the microtask
// implementation, which is the easiest to mock
process: {
versions: {
node: ''
}
}
}
});
@@ -1539,6 +1557,7 @@ describe('Env integration', function() {
expect(reporter.jasmineStarted).toHaveBeenCalledWith({
totalSpecsDefined: 1,
numExcludedSpecs: 0,
order: { random: true, seed: jasmine.any(String) },
parallel: false
});
@@ -1574,6 +1593,7 @@ describe('Env integration', function() {
expect(reporter.jasmineStarted).toHaveBeenCalledWith({
totalSpecsDefined: 1,
numExcludedSpecs: 0,
order: { random: true, seed: jasmine.any(String) },
parallel: false
});
@@ -1630,6 +1650,7 @@ describe('Env integration', function() {
expect(reporter.jasmineStarted).toHaveBeenCalledWith({
totalSpecsDefined: 6,
numExcludedSpecs: 3,
order: { random: false },
parallel: false
});
@@ -1765,10 +1786,12 @@ describe('Env integration', function() {
const baseSuiteEvent = {
id: jasmine.any(String),
filename: jasmine.any(String),
filename: jasmine.any(String)
};
const baseSuiteDoneEvent = {
...baseSuiteEvent,
failedExpectations: [],
deprecationWarnings: [],
duration: null,
properties: null
};
@@ -1779,7 +1802,7 @@ describe('Env integration', function() {
parentSuiteId: null
});
expect(reporter.suiteDone.calls.argsFor(2)[0]).toEqual({
...baseSuiteEvent,
...baseSuiteDoneEvent,
description: 'A Suite',
fullName: 'A Suite',
status: 'passed',
@@ -1794,7 +1817,7 @@ describe('Env integration', function() {
parentSuiteId: suiteFullNameToId['A Suite']
});
expect(reporter.suiteDone.calls.argsFor(0)[0]).toEqual({
...baseSuiteEvent,
...baseSuiteDoneEvent,
description: 'with a nested suite',
status: 'passed',
fullName: 'A Suite with a nested suite',
@@ -1809,7 +1832,7 @@ describe('Env integration', function() {
parentSuiteId: suiteFullNameToId['A Suite']
});
expect(reporter.suiteDone.calls.argsFor(1)[0]).toEqual({
...baseSuiteEvent,
...baseSuiteDoneEvent,
description: 'with only non-executable specs',
status: 'passed',
fullName: 'A Suite with only non-executable specs',
@@ -2041,6 +2064,7 @@ describe('Env integration', function() {
expect(reporter.jasmineStarted).toHaveBeenCalledWith({
totalSpecsDefined: 1,
numExcludedSpecs: 1,
order: { random: true, seed: jasmine.any(String) },
parallel: false
});
@@ -2272,6 +2296,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;
@@ -2773,12 +2825,18 @@ describe('Env integration', function() {
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
},
// Enough Node globals to make getStackClearer() return the microtask
// implementation, which is the easiest to mock
process: {
versions: {
node: ''
}
}
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
const reporter = jasmine.createSpyObj('reporter', [
'jasmineDone',
'suiteDone',
@@ -3226,13 +3284,11 @@ describe('Env integration', function() {
});
it('is resolved after the stack is cleared', function(done) {
const realClearStack = privateUnderTest.getClearStack(
jasmineUnderTest.getGlobal()
),
clearStackSpy = jasmine
.createSpy('clearStack')
.and.callFake(realClearStack);
spyOn(privateUnderTest, 'getClearStack').and.returnValue(clearStackSpy);
const stackClearer = privateUnderTest.getStackClearer(
jasmineUnderTest.getGlobal()
);
spyOn(stackClearer, 'clearStack').and.callThrough();
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(stackClearer);
// Create a new env that has the clearStack defined above
env.cleanup_();
@@ -3243,10 +3299,10 @@ describe('Env integration', function() {
});
env.execute(null).then(function() {
expect(clearStackSpy).toHaveBeenCalled(); // (many times)
clearStackSpy.calls.reset();
expect(stackClearer.clearStack).toHaveBeenCalled(); // (many times)
stackClearer.clearStack.calls.reset();
setTimeout(function() {
expect(clearStackSpy).not.toHaveBeenCalled();
expect(stackClearer.clearStack).not.toHaveBeenCalled();
done();
});
});
@@ -3335,13 +3391,11 @@ describe('Env integration', function() {
});
it('is called after the stack is cleared', async function() {
const realClearStack = privateUnderTest.getClearStack(
jasmineUnderTest.getGlobal()
),
clearStackSpy = jasmine
.createSpy('clearStack')
.and.callFake(realClearStack);
spyOn(privateUnderTest, 'getClearStack').and.returnValue(clearStackSpy);
const stackClearer = privateUnderTest.getStackClearer(
jasmineUnderTest.getGlobal()
);
spyOn(stackClearer, 'clearStack').and.callThrough();
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(stackClearer);
// Create a new env that has the clearStack defined above
env.cleanup_();
@@ -3353,12 +3407,12 @@ describe('Env integration', function() {
await env.execute();
expect(clearStackSpy).toHaveBeenCalled(); // (many times)
clearStackSpy.calls.reset();
expect(stackClearer.clearStack).toHaveBeenCalled(); // (many times)
stackClearer.clearStack.calls.reset();
await new Promise(resolve => setTimeout(resolve));
expect(clearStackSpy).not.toHaveBeenCalled();
expect(stackClearer.clearStack).not.toHaveBeenCalled();
});
it('is called after QueueRunner timeouts are cleared', async function() {
@@ -3792,6 +3846,34 @@ describe('Env integration', function() {
});
});
describe('pp', function() {
it("pretty-prints using the current runable's custom object formatters", async function() {
env.it('a spec', function() {
env.addCustomObjectFormatter(function(x) {
if (x === 1) {
return 'hi!';
}
});
env.expect(env.pp(1)).toEqual('hi!');
});
const reporter = jasmine.createSpyObj('reporter', ['specDone']);
env.addReporter(reporter);
await env.execute();
expect(reporter.specDone).toHaveBeenCalledWith(
jasmine.objectContaining({
failedExpectations: []
})
);
});
it('works when there is no current runable', function() {
expect(env.pp({ some: 'thing' })).toEqual("Object({ some: 'thing' })");
});
});
it('forbids duplicates when forbidDuplicateNames is true', function() {
env.configure({ forbidDuplicateNames: true });
env.it('a spec');

View File

@@ -11,8 +11,8 @@ describe('Global error handling (integration)', function() {
env.cleanup_();
});
it('reports errors that occur during loading', async function() {
const global = {
function mockGlobal() {
return {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
@@ -23,12 +23,21 @@ describe('Global error handling (integration)', function() {
queueMicrotask: function(fn) {
queueMicrotask(fn);
},
onerror: function() {}
onerror: function() {},
// Enough Node globals to make getStackClearer() return the microtask
// implementation, which is the easiest to mock
process: {
versions: {
node: ''
}
}
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
}
it('reports errors that occur during loading', async function() {
const global = mockGlobal();
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
const reporter = jasmine.createSpyObj('reporter', [
'jasmineDone',
'suiteDone',
@@ -53,38 +62,26 @@ describe('Global error handling (integration)', function() {
passed: false,
globalErrorType: 'load',
message: 'Uncaught SyntaxError: Unexpected end of input',
stack: undefined,
filename: 'borkenSpec.js',
lineno: 42
lineno: 42,
matcherName: undefined,
stack: jasmine.any(String)
},
{
passed: false,
globalErrorType: 'load',
message: 'ENOCHEESE',
stack: error.stack,
filename: undefined,
lineno: undefined
matcherName: undefined,
stack: jasmine.any(String)
}
]);
});
describe('If suppressLoadErrors: true was passed', function() {
it('does not install a global error handler during loading', async function() {
const global = mockGlobal();
const originalOnerror = jasmine.createSpy('original onerror');
const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
},
clearTimeout: function(fn, delay) {
clearTimeout(fn, delay);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
},
onerror: originalOnerror
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
global.onerror = originalOnerror;
const globalErrors = new privateUnderTest.GlobalErrors(global);
const onerror = jasmine.createSpy('onerror');
globalErrors.pushListener(onerror);
@@ -92,6 +89,7 @@ describe('Global error handling (integration)', function() {
env.cleanup_();
env = new privateUnderTest.Env({
suppressLoadErrors: true,
global,
GlobalErrors: function() {
return globalErrors;
}
@@ -115,21 +113,9 @@ describe('Global error handling (integration)', function() {
describe('Handling unhandled exceptions', function() {
it('routes unhandled exceptions to the running spec', 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);
const global = mockGlobal();
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
const reporter = jasmine.createSpyObj('fakeReporter', [
'specDone',
'suiteDone'
@@ -156,24 +142,12 @@ describe('Global error handling (integration)', function() {
describe('When the most recently running spec has reported specDone', function() {
it('routes unhandled exceptions to an ancestor suite', async function() {
const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
},
clearTimeout: function(fn) {
clearTimeout(fn);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
}
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
const realClearStack = privateUnderTest.getClearStack(global);
const global = mockGlobal();
const stackClearer = privateUnderTest.getStackClearer(global);
const realClearStack = stackClearer.clearStack;
const clearStackCallbacks = {};
let clearStackCallCount = 0;
spyOn(privateUnderTest, 'getClearStack').and.returnValue(function(fn) {
spyOn(stackClearer, 'clearStack').and.callFake(function(fn) {
clearStackCallCount++;
if (clearStackCallbacks[clearStackCallCount]) {
@@ -182,9 +156,12 @@ describe('Global error handling (integration)', function() {
realClearStack(fn);
});
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(
stackClearer
);
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
let suiteErrors = [];
env.addReporter({
@@ -220,21 +197,9 @@ describe('Global error handling (integration)', function() {
});
it('routes unhandled exceptions to the running suite', 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);
const global = mockGlobal();
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
const reporter = jasmine.createSpyObj('fakeReporter', [
'specDone',
'suiteDone'
@@ -273,24 +238,12 @@ describe('Global error handling (integration)', function() {
describe('When the most recently suite has reported suiteDone', function() {
it('routes unhandled exceptions to an ancestor suite', 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);
const realClearStack = privateUnderTest.getClearStack(global);
const global = mockGlobal();
const stackClearer = privateUnderTest.getStackClearer(global);
const realClearStack = stackClearer.clearStack;
const clearStackCallbacks = {};
let clearStackCallCount = 0;
spyOn(privateUnderTest, 'getClearStack').and.returnValue(function(fn) {
spyOn(stackClearer, 'clearStack').and.callFake(function(fn) {
clearStackCallCount++;
if (clearStackCallbacks[clearStackCallCount]) {
@@ -299,9 +252,12 @@ describe('Global error handling (integration)', function() {
realClearStack(fn);
});
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(
stackClearer
);
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
let suiteErrors = [];
env.addReporter({
@@ -341,21 +297,9 @@ describe('Global error handling (integration)', function() {
describe('When the env has started reporting jasmineDone', function() {
it('logs the error to the console', 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);
const global = mockGlobal();
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
spyOn(console, 'error');
@@ -384,26 +328,14 @@ describe('Global error handling (integration)', function() {
});
it('routes all errors that occur during stack clearing somewhere', async function() {
const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
},
clearTimeout: function(fn) {
clearTimeout(fn);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
}
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
const realClearStack = privateUnderTest.getClearStack(global);
const global = mockGlobal();
const stackClearer = privateUnderTest.getStackClearer(global);
const realClearStack = stackClearer.clearStack;
let clearStackCallCount = 0;
let jasmineDone = false;
const expectedErrors = [];
const expectedErrorsAfterJasmineDone = [];
spyOn(privateUnderTest, 'getClearStack').and.returnValue(function(fn) {
spyOn(stackClearer, 'clearStack').and.callFake(function(fn) {
clearStackCallCount++;
const msg = `Error in clearStack #${clearStackCallCount}`;
@@ -416,10 +348,11 @@ describe('Global error handling (integration)', function() {
dispatchErrorEvent(global, 'error', { error: msg });
realClearStack(fn);
});
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(stackClearer);
spyOn(console, 'error');
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
const receivedErrors = [];
function logErrors(event) {
@@ -459,21 +392,9 @@ describe('Global error handling (integration)', function() {
describe('Handling unhandled promise rejections', function() {
it('routes unhandled promise rejections to the running spec', 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);
const global = mockGlobal();
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
const reporter = jasmine.createSpyObj('fakeReporter', [
'specDone',
'suiteDone'
@@ -502,24 +423,12 @@ describe('Global error handling (integration)', function() {
describe('When the most recently running spec has reported specDone', function() {
it('routes unhandled promise rejections to an ancestor suite', async function() {
const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
},
clearTimeout: function(fn) {
clearTimeout(fn);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
}
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
const realClearStack = privateUnderTest.getClearStack(global);
const global = mockGlobal();
const stackClearer = privateUnderTest.getStackClearer(global);
const realClearStack = stackClearer.clearStack;
const clearStackCallbacks = {};
let clearStackCallCount = 0;
spyOn(privateUnderTest, 'getClearStack').and.returnValue(function(fn) {
spyOn(stackClearer, 'clearStack').and.callFake(function(fn) {
clearStackCallCount++;
if (clearStackCallbacks[clearStackCallCount]) {
@@ -528,9 +437,12 @@ describe('Global error handling (integration)', function() {
realClearStack(fn);
});
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(
stackClearer
);
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
let suiteErrors = [];
env.addReporter({
@@ -566,21 +478,9 @@ describe('Global error handling (integration)', function() {
});
it('routes unhandled promise rejections to the running suite', 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);
const global = mockGlobal();
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
const reporter = jasmine.createSpyObj('fakeReporter', [
'specDone',
'suiteDone'
@@ -621,24 +521,12 @@ describe('Global error handling (integration)', function() {
describe('When the most recently suite has reported suiteDone', function() {
it('routes unhandled promise rejections to an ancestor suite', 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);
const realClearStack = privateUnderTest.getClearStack(global);
const global = mockGlobal();
const stackClearer = privateUnderTest.getStackClearer(global);
const realClearStack = stackClearer.clearStack;
const clearStackCallbacks = {};
let clearStackCallCount = 0;
spyOn(privateUnderTest, 'getClearStack').and.returnValue(function(fn) {
spyOn(stackClearer, 'clearStack').and.callFake(function(fn) {
clearStackCallCount++;
if (clearStackCallbacks[clearStackCallCount]) {
@@ -647,9 +535,12 @@ describe('Global error handling (integration)', function() {
realClearStack(fn);
});
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(
stackClearer
);
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
let suiteErrors = [];
env.addReporter({
@@ -689,21 +580,9 @@ describe('Global error handling (integration)', function() {
describe('When the env has started reporting jasmineDone', function() {
it('logs the rejection to the console', 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);
const global = mockGlobal();
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
spyOn(console, 'error');
@@ -734,26 +613,14 @@ describe('Global error handling (integration)', function() {
});
it('routes all unhandled promise rejections that occur during stack clearing somewhere', async function() {
const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
},
clearTimeout: function(fn) {
clearTimeout(fn);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
}
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
const realClearStack = privateUnderTest.getClearStack(global);
const global = mockGlobal();
const stackClearer = privateUnderTest.getStackClearer(global);
const realClearStack = stackClearer.clearStack;
let clearStackCallCount = 0;
let jasmineDone = false;
const expectedErrors = [];
const expectedErrorsAfterJasmineDone = [];
spyOn(privateUnderTest, 'getClearStack').and.returnValue(function(fn) {
spyOn(stackClearer, 'clearStack').and.callFake(function(fn) {
clearStackCallCount++;
const reason = `Error in clearStack #${clearStackCallCount}`;
const expectedMsg = `Unhandled promise rejection: ${reason} thrown`;
@@ -767,10 +634,11 @@ describe('Global error handling (integration)', function() {
dispatchErrorEvent(global, 'unhandledrejection', { reason });
realClearStack(fn);
});
spyOn(privateUnderTest, 'getStackClearer').and.returnValue(stackClearer);
spyOn(console, 'error');
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
const receivedErrors = [];
function logErrors(event) {
@@ -819,21 +687,9 @@ describe('Global error handling (integration)', function() {
let global, reporter;
beforeEach(function() {
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);
global = mockGlobal();
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
env.configure({ detectLateRejectionHandling: true });
reporter = jasmine.createSpyObj('fakeReporter', [
@@ -966,21 +822,8 @@ describe('Global error handling (integration)', function() {
describe("When the unhandled rejection event doesn't have a promise", function() {
it('reports the rejection', 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 privateUnderTest.Env();
const global = mockGlobal();
env = new privateUnderTest.Env({ global });
env.configure({ detectLateRejectionHandling: true });
const reporter = jasmine.createSpyObj('fakeReporter', [
'specDone',
@@ -1012,21 +855,9 @@ 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);
const global = mockGlobal();
env.cleanup_();
env = new privateUnderTest.Env();
env = new privateUnderTest.Env({ global });
env.configure({ autoCleanClosures: false });
const reporter = jasmine.createSpyObj('fakeReporter', ['specDone']);

View File

@@ -13,7 +13,72 @@ describe('The jasmine namespace', function() {
expect(setDifference(actualKeys, expectedKeys())).toEqual(new Set());
});
function expectedKeys() {
describe('Warning about monkey patching', function() {
beforeEach(function() {
spyOn(console, 'error');
});
for (const key of expectedKeys(false)) {
if (!key.startsWith('MAX_') && key !== 'private' && key !== 'getEnv') {
describe(`jasmine.${key}`, function() {
let orig;
beforeEach(function() {
orig = jasmineUnderTest[key];
});
afterEach(function() {
jasmineUnderTest[key] = orig;
});
it('warns if monkey patched', function() {
const patch = {};
jasmineUnderTest[key] = patch;
verifyDeprecation();
expect(jasmineUnderTest[key]).toBe(patch);
});
});
}
}
// These specs rely on jasmineRequire being exposed. That only happens
// in browsers.
if (typeof document !== 'undefined') {
const statics = ['addMatchers', 'clock', 'createSpyObj'];
for (const name of statics) {
describe(`jasmine.${name}`, function() {
let bootedCore, env, orig;
beforeEach(function() {
bootedCore = jasmineRequire.core(jasmineRequire);
env = bootedCore.getEnv();
jasmineRequire.interface(bootedCore, env);
orig = bootedCore[name];
});
afterEach(function() {
bootedCore[name] = orig;
env.cleanup_();
});
it(`warns if jasmine.${name} is monkey patched`, function() {
const patch = {};
bootedCore[name] = patch;
verifyDeprecation();
expect(bootedCore[name]).toBe(patch);
});
});
}
}
});
function expectedKeys(includeHtml) {
if (includeHtml === undefined) {
includeHtml = typeof window !== 'undefined';
}
// Does not include properties added by requireInterface(), since that isn't
// called by defineJasmineUnderTest.js/nodeDefineJasmineUnderTest.js.
const result = new Set([
@@ -30,6 +95,7 @@ describe('The jasmine namespace', function() {
'version',
// Asymmetric equality testers
'allOf',
'any',
'anything',
'arrayContaining',
@@ -50,11 +116,12 @@ describe('The jasmine namespace', function() {
'getGlobal'
]);
if (typeof window !== 'undefined') {
if (includeHtml) {
// jasmine-html.js
result.add('HtmlReporter');
result.add('HtmlReporterV2');
result.add('HtmlReporterV2Urls');
result.add('HtmlSpecFilter');
result.add('HtmlExactSpecFilter');
result.add('QueryString');
}
@@ -74,4 +141,15 @@ describe('The jasmine namespace', function() {
return result;
}
function verifyDeprecation() {
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalledOnceWith(
jasmine.stringContaining('DEPRECATION: Monkey patching detected.')
);
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalledOnceWith(
jasmine.stringContaining('jasmineNamespaceSpec.js')
);
}
});

View File

@@ -768,7 +768,16 @@ describe('matchersUtil', function() {
a2[0] = 1;
const diffBuilder = new privateUnderTest.DiffBuilder();
expect(matchersUtil.equals(a1, a2, diffBuilder)).toBe(false);
jasmine.debugLog('Diff: ' + diffBuilder.getMessage());
jasmine.debugLog(
'a1 keys: ' + jasmine.pp(privateUnderTest.MatchersUtil.keys(a1))
);
jasmine.debugLog(
'a2 keys: ' + jasmine.pp(privateUnderTest.MatchersUtil.keys(a2))
);
jasmine.debugLog('a1 length:' + a1.length);
jasmine.debugLog('a2 length:' + a2.length);
jasmine.debugLog('a1[0]: ' + a1[0]);
jasmine.debugLog('a2[0]: ' + a2[0]);
}
);

View File

@@ -0,0 +1,5 @@
(function() {
specHelpers.callerFilenameShim = function(fn) {
return fn();
};
})();

View File

@@ -1,63 +0,0 @@
describe('HtmlExactSpecFilter', function() {
it('matches everything when no string is provided', function() {
const specFilter = new jasmineUnderTest.HtmlExactSpecFilter({
queryString: {
getParam(name) {
return '';
}
}
});
expect(specFilter.matches({})).toBeTrue();
});
it('matches a spec with the exact same path', function() {
const specFilter = new jasmineUnderTest.HtmlExactSpecFilter({
queryString: {
getParam(name) {
if (name === 'spec') {
return '["a","b","c"]';
}
}
}
});
expect(specFilter.matches(stubSpec(['a', 'b', 'c']))).toBeTrue();
});
it('matches a spec whose path has the filter path as a prefix', function() {
const specFilter = new jasmineUnderTest.HtmlExactSpecFilter({
queryString: {
getParam(name) {
if (name === 'spec') {
return '["a","b"]';
}
}
}
});
expect(specFilter.matches(stubSpec(['a', 'b', 'c']))).toBeTrue();
});
it('does not match a spec with a different path', function() {
const specFilter = new jasmineUnderTest.HtmlExactSpecFilter({
queryString: {
getParam(name) {
if (name === 'spec') {
return '["a","b","c"]';
}
}
}
});
expect(specFilter.matches(stubSpec(['a', 'd', 'c']))).toBeFalse();
});
function stubSpec(path) {
return {
getPath() {
return path;
}
};
}
});

View File

@@ -1,14 +1,36 @@
describe('HtmlReporter', function() {
let env;
let env, deprecator;
beforeEach(function() {
env = new privateUnderTest.Env();
deprecator = jasmine.createSpyObj('deprecator', [
'verboseDeprecations',
'addDeprecationWarning'
]);
env = new privateUnderTest.Env({ deprecator });
});
afterEach(function() {
env.cleanup_();
});
it('emits a deprecation warning', function() {
const container = document.createElement('div'),
getContainer = function() {
return container;
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer
});
reporter.initialize();
expect(deprecator.addDeprecationWarning).toHaveBeenCalledWith(
jasmine.anything(),
'HtmlReporter and HtmlSpecFilter are deprecated. Use HtmlReporterV2 instead.',
undefined
);
});
it('builds the initial DOM elements, including the title banner', function() {
const container = document.createElement('div'),
getContainer = function() {
@@ -16,13 +38,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -52,13 +68,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
reporter.initialize();
@@ -79,12 +89,6 @@ describe('HtmlReporter', function() {
env: env,
getContainer: function() {
return container;
},
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
});
@@ -132,13 +136,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
reporter.specDone({
@@ -164,13 +162,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -193,13 +185,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -223,13 +209,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -255,13 +235,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -310,13 +284,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -360,13 +328,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -393,13 +355,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -434,13 +390,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
spyOn(console, 'error');
@@ -478,13 +428,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -507,12 +451,6 @@ describe('HtmlReporter', function() {
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
},
addToExistingQueryString: function(key, value) {
return '?foo=bar&' + key + '=' + value;
}
@@ -528,7 +466,6 @@ describe('HtmlReporter', function() {
let specResult = {
id: 123,
parentSuiteId: 1,
description: 'with a spec',
fullName: 'A Suite with a spec',
status: 'passed',
@@ -606,9 +543,7 @@ describe('HtmlReporter', function() {
const suiteDetail = outerSuite.childNodes[0];
const suiteLink = suiteDetail.childNodes[0];
expect(suiteLink.innerHTML).toEqual('A Suite');
expect(suiteLink.getAttribute('href')).toEqual(
'/?foo=bar&spec=["A Suite"]'
);
expect(suiteLink.getAttribute('href')).toEqual('/?foo=bar&spec=A Suite');
const specs = outerSuite.childNodes[1];
const spec = specs.childNodes[0];
@@ -618,7 +553,7 @@ describe('HtmlReporter', function() {
const specLink = spec.childNodes[0];
expect(specLink.innerHTML).toEqual('with a spec');
expect(specLink.getAttribute('href')).toEqual(
'/?foo=bar&spec=["A Suite","with a spec"]'
'/?foo=bar&spec=A Suite with a spec'
);
const specDuration = spec.childNodes[1];
@@ -632,13 +567,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -670,13 +599,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -716,13 +639,7 @@ describe('HtmlReporter', function() {
};
const reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -760,13 +677,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -802,13 +713,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -825,13 +730,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
env.configure({ stopOnSpecFailure: true });
@@ -852,13 +751,7 @@ describe('HtmlReporter', function() {
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
navigateWithNewParam: navigationHandler,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -882,13 +775,7 @@ describe('HtmlReporter', function() {
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
navigateWithNewParam: navigationHandler,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
env.configure({ stopOnSpecFailure: true });
@@ -914,13 +801,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -939,13 +820,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
env.configure({ stopSpecOnExpectationFailure: true });
@@ -968,13 +843,7 @@ describe('HtmlReporter', function() {
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
navigateWithNewParam: navigateHandler,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
navigateWithNewParam: navigateHandler
});
reporter.initialize();
@@ -1000,13 +869,7 @@ describe('HtmlReporter', function() {
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
navigateWithNewParam: navigateHandler,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
navigateWithNewParam: navigateHandler
});
env.configure({ stopSpecOnExpectationFailure: true });
@@ -1025,22 +888,19 @@ describe('HtmlReporter', function() {
);
});
});
describe('UI for hiding disabled specs', function() {
it('should be unchecked if not hiding disabled specs', function() {
const container = document.createElement('div'),
getContainer = function() {
return container;
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
});
const container = document.createElement('div');
const getContainer = function() {
return container;
};
const navigateHandler = jasmine.createSpy('navigate');
const reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
navigateWithNewParam: navigateHandler
});
env.configure({ hideDisabled: false });
reporter.initialize();
@@ -1048,23 +908,22 @@ describe('HtmlReporter', function() {
const disabledUI = container.querySelector('.jasmine-disabled');
expect(disabledUI.checked).toBe(false);
disabledUI.click();
expect(navigateHandler).toHaveBeenCalledWith('hideDisabled', true);
});
it('should be checked if hiding disabled', function() {
const container = document.createElement('div'),
getContainer = function() {
return container;
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
});
const container = document.createElement('div');
const getContainer = function() {
return container;
};
const navigateHandler = jasmine.createSpy('navigate');
const reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
navigateWithNewParam: navigateHandler
});
env.configure({ hideDisabled: true });
reporter.initialize();
@@ -1072,6 +931,9 @@ describe('HtmlReporter', function() {
const disabledUI = container.querySelector('.jasmine-disabled');
expect(disabledUI.checked).toBe(true);
disabledUI.click();
expect(navigateHandler).toHaveBeenCalledWith('hideDisabled', false);
});
it('should not display specs that have been disabled', function() {
@@ -1081,13 +943,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
env.configure({ hideDisabled: true });
@@ -1106,6 +962,7 @@ describe('HtmlReporter', function() {
);
});
});
describe('UI for running tests in random order', function() {
it('should be unchecked if not randomizing', function() {
const container = document.createElement('div'),
@@ -1114,13 +971,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
env.configure({ random: false });
@@ -1138,13 +989,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
env.configure({ random: true });
@@ -1164,13 +1009,7 @@ describe('HtmlReporter', function() {
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
navigateWithNewParam: navigateHandler,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
navigateWithNewParam: navigateHandler
});
env.configure({ random: false });
@@ -1192,13 +1031,7 @@ describe('HtmlReporter', function() {
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
navigateWithNewParam: navigateHandler,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
navigateWithNewParam: navigateHandler
});
env.configure({ random: true });
@@ -1218,13 +1051,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -1248,13 +1075,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -1264,6 +1085,34 @@ describe('HtmlReporter', function() {
expect(seedBar).toBeNull();
});
it('includes the number of specs in the text of the jasmine-skipped link', function() {
const container = document.createElement('div');
const reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: function() {
return container;
},
addToExistingQueryString: function(key, value) {
return '?foo=bar&' + key + '=' + value;
}
});
reporter.initialize();
const minimalSpecDone = {
failedExpectations: [],
passedExpectations: []
};
reporter.jasmineStarted({ totalSpecsDefined: 3 });
reporter.specDone({ ...minimalSpecDone });
reporter.specDone({ ...minimalSpecDone });
reporter.specDone({ ...minimalSpecDone, status: 'excluded' });
reporter.jasmineDone({});
const skippedLink = container.querySelector('.jasmine-skipped a');
expect(skippedLink.textContent).toEqual('Ran 2 of 3 specs - run all');
});
it('should include non-spec query params in the jasmine-skipped link when present', function() {
const container = document.createElement('div'),
reporter = new jasmineUnderTest.HtmlReporter({
@@ -1271,12 +1120,6 @@ describe('HtmlReporter', function() {
getContainer: function() {
return container;
},
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
},
addToExistingQueryString: function(key, value) {
return '?foo=bar&' + key + '=' + value;
}
@@ -1301,13 +1144,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -1362,17 +1199,6 @@ describe('HtmlReporter', function() {
env: env,
getContainer: function() {
return container;
},
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
},
queryString: {
getParam(name) {
return '';
}
}
};
specStatus = {
@@ -1387,12 +1213,7 @@ describe('HtmlReporter', function() {
describe('when the specs are not filtered', function() {
beforeEach(function() {
reporterConfig.queryString.getParam = function(name) {
if (name !== 'spec') {
throw new Error('Unexpected query param ' + name);
}
return '';
};
reporterConfig.filterSpecs = false;
reporter = new jasmineUnderTest.HtmlReporter(reporterConfig);
reporter.initialize();
reporter.jasmineStarted({ totalSpecsDefined: 1 });
@@ -1410,12 +1231,7 @@ describe('HtmlReporter', function() {
describe('when the specs are filtered', function() {
beforeEach(function() {
reporterConfig.queryString.getParam = function(name) {
if (name !== 'spec') {
throw new Error('Unexpected query param ' + name);
}
return 'not the empty string';
};
reporterConfig.filterSpecs = true;
reporter = new jasmineUnderTest.HtmlReporter(reporterConfig);
reporter.initialize();
reporter.jasmineStarted({ totalSpecsDefined: 1 });
@@ -1459,13 +1275,7 @@ describe('HtmlReporter', function() {
};
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -1526,12 +1336,6 @@ describe('HtmlReporter', function() {
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
},
addToExistingQueryString: function(key, value) {
return '?foo=bar&' + key + '=' + value;
}
@@ -1559,7 +1363,6 @@ describe('HtmlReporter', function() {
const failingSpecResult = {
id: 124,
parentSuiteId: 2,
status: 'failed',
description: 'a failing spec',
fullName: 'a suite inner suite a failing spec',
@@ -1683,18 +1486,16 @@ describe('HtmlReporter', function() {
expect(links.length).toEqual(3);
expect(links[0].textContent).toEqual('A suite');
expect(links[0].getAttribute('href')).toEqual(
'/?foo=bar&spec=["A suite"]'
);
expect(links[0].getAttribute('href')).toMatch(/\?foo=bar&spec=A suite/);
expect(links[1].textContent).toEqual('inner suite');
expect(links[1].getAttribute('href')).toEqual(
'/?foo=bar&spec=["A suite","inner suite"]'
expect(links[1].getAttribute('href')).toMatch(
/\?foo=bar&spec=A suite inner suite/
);
expect(links[2].textContent).toEqual('a failing spec');
expect(links[2].getAttribute('href')).toEqual(
'/?foo=bar&spec=["A suite","inner suite","a failing spec"]'
expect(links[2].getAttribute('href')).toMatch(
/\?foo=bar&spec=a suite inner suite a failing spec/
);
});
@@ -1724,12 +1525,6 @@ describe('HtmlReporter', function() {
const reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
},
addToExistingQueryString: function(key, value) {
return '?' + key + '=' + value;
}
@@ -1781,13 +1576,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -1811,13 +1600,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();
@@ -1841,13 +1624,7 @@ describe('HtmlReporter', function() {
},
reporter = new jasmineUnderTest.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
getContainer: getContainer
});
reporter.initialize();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
describe('HtmlReporterV2Urls', function() {
describe('#configFromCurrentUrl', function() {
passesThroughQueryParam('stopOnSpecFailure');
passesThroughQueryParam('stopSpecOnExpectationFailure');
passesThroughQueryParam('random');
ignoresEmpty('random');
passesThroughQueryParam('seed');
ignoresEmpty('seed');
it('configures a matching spec filter', function() {
const queryString = mockQueryString();
queryString.getParam.withArgs('path').and.returnValue('["foo","bar"]');
const subject = new jasmineUnderTest.HtmlReporterV2Urls({ queryString });
const config = subject.configFromCurrentUrl();
const matching = {
getPath() {
return ['foo', 'bar'];
}
};
const nonMatching = {
getPath() {
return ['foobar'];
}
};
expect(config.specFilter(matching)).toEqual(true);
expect(config.specFilter(nonMatching)).toEqual(false);
});
function passesThroughQueryParam(k) {
it(`sets config.${k} to undefined when ${k} is not in the query string`, function() {
const queryString = mockQueryString();
queryString.getParam.withArgs(k).and.returnValue(undefined);
const subject = new jasmineUnderTest.HtmlReporterV2Urls({
queryString
});
expect(subject.configFromCurrentUrl()[k]).toBeUndefined();
});
it(`sets config.${k} to the ${k} query param`, function() {
const queryString = mockQueryString();
queryString.getParam.withArgs(k).and.returnValue('someval');
const subject = new jasmineUnderTest.HtmlReporterV2Urls({
queryString
});
expect(subject.configFromCurrentUrl()[k]).toEqual('someval');
});
}
function ignoresEmpty(k) {
it(`sets config.${k} to undefined when the ${k} query param is empty`, function() {
const queryString = mockQueryString();
queryString.getParam.withArgs(k).and.returnValue(undefined);
const subject = new jasmineUnderTest.HtmlReporterV2Urls({
queryString
});
expect(subject.configFromCurrentUrl()[k]).toBeUndefined();
});
}
function mockQueryString() {
const qs = jasmine.createSpyObj('queryString', ['getParam']);
qs.getParam.and.returnValue('NOT STUBBED');
return qs;
}
});
});

View File

@@ -1,6 +1,29 @@
describe('HtmlSpecFilter', function() {
let env, deprecator;
beforeEach(function() {
deprecator = jasmine.createSpyObj('deprecator', [
'verboseDeprecations',
'addDeprecationWarning'
]);
env = new privateUnderTest.Env({ deprecator });
});
afterEach(function() {
env.cleanup_();
});
it('emits a deprecation warning', function() {
new jasmineUnderTest.HtmlSpecFilter({ env });
expect(deprecator.addDeprecationWarning).toHaveBeenCalledWith(
jasmine.anything(),
'HtmlReporter and HtmlSpecFilter are deprecated. Use HtmlReporterV2 instead.',
undefined
);
});
it('should match when no string is provided', function() {
const specFilter = new jasmineUnderTest.HtmlSpecFilter();
const specFilter = new jasmineUnderTest.HtmlSpecFilter({ env });
expect(specFilter.matches('foo')).toBe(true);
expect(specFilter.matches('*bar')).toBe(true);
@@ -8,6 +31,7 @@ describe('HtmlSpecFilter', function() {
it('should only match the provided string', function() {
const specFilter = new jasmineUnderTest.HtmlSpecFilter({
env,
filterString: function() {
return 'foo';
}
@@ -16,17 +40,4 @@ describe('HtmlSpecFilter', function() {
expect(specFilter.matches('foo')).toBe(true);
expect(specFilter.matches('bar')).toBe(false);
});
it('copes with HtmlExactSpecFilterV2 filter strings', function() {
const specFilter = new jasmineUnderTest.HtmlSpecFilter({
filterString: function() {
return '["foo","bar"]';
}
});
expect(specFilter.matches('foo bar')).toBe(true);
expect(specFilter.matches('baz foo bar qux')).toBe(true);
expect(specFilter.matches('foo')).toBe(false);
expect(specFilter.matches('bar')).toBe(false);
});
});

View File

@@ -0,0 +1,70 @@
describe('HtmlSpecFilterV2', function() {
describe('When both query parameters are falsy', function() {
it('matches everything', function() {
const specFilter = new privateUnderTest.HtmlSpecFilterV2({
filterParams() {
return { path: '', spec: '' };
}
});
expect(specFilter.matches({})).toBeTrue();
});
});
describe('When the path parameter is truthy', function() {
it('matches a spec with the exact same path', function() {
const specFilter = new privateUnderTest.HtmlSpecFilterV2({
filterParams() {
return { path: '["a","b","c"]', spec: '' };
}
});
expect(specFilter.matches(stubSpec(['a', 'b', 'c']))).toBeTrue();
});
it('matches a spec whose path has the filter path as a prefix', function() {
const specFilter = new privateUnderTest.HtmlSpecFilterV2({
filterParams() {
return { path: '["a","b"]', spec: '' };
}
});
expect(specFilter.matches(stubSpec(['a', 'b', 'c']))).toBeTrue();
});
it('does not match a spec with a different path', function() {
const specFilter = new privateUnderTest.HtmlSpecFilterV2({
filterParams() {
return { path: '["a","b","c"]', spec: '' };
}
});
expect(specFilter.matches(stubSpec(['a', 'd', 'c']))).toBeFalse();
});
});
describe('When the path parameter is falsy and the spec parameter is truthy', function() {
it('matches specs with full names containing the parameter value', function() {
const specFilter = new privateUnderTest.HtmlSpecFilterV2({
filterParams() {
return { path: '', spec: 'bar' };
}
});
expect(specFilter.matches(stubSpec('', 'foo bar baz'))).toBeTrue();
expect(specFilter.matches(stubSpec('', 'foo baz'))).toBeFalse();
expect(specFilter.matches(stubSpec('', 'sandbars'))).toBeTrue();
});
});
function stubSpec(path, fullName) {
return {
getPath() {
return path;
},
getFullName() {
return fullName;
}
};
}
});

View File

@@ -0,0 +1,107 @@
'use strict';
describe('PerformanceView', function() {
it('shows specs ordered by execution time', function() {
const stateBuilder = new privateUnderTest.ResultsStateBuilder();
stateBuilder.suiteStarted({});
stateBuilder.specDone({
fullName: 'spec A',
duration: 2
});
stateBuilder.suiteDone({});
stateBuilder.specDone({
fullName: 'spec B',
duration: 1
});
stateBuilder.specDone({
fullName: 'spec C',
duration: 3
});
const subject = new privateUnderTest.PerformanceView();
subject.addResults(stateBuilder.topResults);
const rows = Array.from(subject.rootEl.querySelectorAll('tbody tr'));
const durations = rows.map(r => r.querySelectorAll('td')[0].textContent);
const names = rows.map(r => r.querySelectorAll('td')[1].textContent);
expect(names).toEqual(['spec C', 'spec A', 'spec B']);
expect(durations).toEqual(['3ms', '2ms', '1ms']);
});
it('shows at most 20 specs', function() {
const stateBuilder = new privateUnderTest.ResultsStateBuilder();
const subject = new privateUnderTest.PerformanceView();
for (let i = 0; i < 21; i++) {
stateBuilder.specDone({
fullName: `spec ${i}`,
duration: i
});
}
subject.addResults(stateBuilder.topResults);
expect(subject.rootEl.querySelectorAll('tbody tr').length).toEqual(20);
expect(subject.textContent).not.toContain('spec 0');
});
it('shows mean and median run times for an odd number of specs', function() {
const stateBuilder = new privateUnderTest.ResultsStateBuilder();
const subject = new privateUnderTest.PerformanceView();
stateBuilder.specDone({ duration: 1 });
stateBuilder.specDone({ duration: 2 });
stateBuilder.specDone({ duration: 5 });
subject.addResults(stateBuilder.topResults);
expect(subject.rootEl.textContent).toContain('Mean spec run time: 3ms');
expect(subject.rootEl.textContent).toContain('Median spec run time: 2ms');
});
it('shows mean and median run times for an even number of specs', function() {
const stateBuilder = new privateUnderTest.ResultsStateBuilder();
const subject = new privateUnderTest.PerformanceView();
stateBuilder.specDone({ duration: 1 });
stateBuilder.specDone({ duration: 3 });
stateBuilder.specDone({ duration: 10 });
stateBuilder.specDone({ duration: 2 });
subject.addResults(stateBuilder.topResults);
expect(subject.rootEl.textContent).toContain('Mean spec run time: 4ms');
expect(subject.rootEl.textContent).toContain('Median spec run time: 2ms');
});
it('copes with 0 specs', function() {
const stateBuilder = new privateUnderTest.ResultsStateBuilder();
const subject = new privateUnderTest.PerformanceView();
expect(function() {
subject.addResults(stateBuilder.topResults);
}).not.toThrow();
});
it('filters out excluded specs', function() {
const stateBuilder = new privateUnderTest.ResultsStateBuilder();
stateBuilder.specDone({
fullName: 'spec A',
duration: 2
});
stateBuilder.specDone({
fullName: 'spec B',
duration: 1,
status: 'excluded'
});
stateBuilder.specDone({
fullName: 'spec C',
duration: 3
});
const subject = new privateUnderTest.PerformanceView();
subject.addResults(stateBuilder.topResults);
const rows = Array.from(subject.rootEl.querySelectorAll('tbody tr'));
const names = rows.map(r => r.querySelectorAll('td')[1].textContent);
expect(names).toEqual(['spec C', 'spec A']);
expect(subject.rootEl.textContent).toContain('Mean spec run time: 3ms');
expect(subject.rootEl.textContent).toContain('Median spec run time: 2ms');
});
});

97
spec/html/TabBarSpec.js Normal file
View File

@@ -0,0 +1,97 @@
describe('TabBar', function() {
it('initially renders but hides the tabs', function() {
const subject = new privateUnderTest.TabBar([
{ id: 'tab1', label: 'tab 1' }
]);
const tabs = subject.rootEl.querySelectorAll('.jasmine-tab');
expect(tabs.length).toEqual(1);
expect(tabs[0].id).toEqual('tab1');
expect(tabs[0]).toHaveClass('jasmine-hidden');
const link = tabs[0].querySelector('a');
expect(link).toBeTruthy();
expect(link.textContent).toEqual('tab 1');
});
it('does not initially call the onSelect callback', function() {
const onSelect = jasmine.createSpy('onSelect');
new privateUnderTest.TabBar([{ id: 'tab1', label: '' }], onSelect);
expect(onSelect).not.toHaveBeenCalled();
});
describe('#showTab', function() {
it('shows the specified tab', function() {
const subject = new privateUnderTest.TabBar([
{ id: 'tab1' },
{ id: 'tab2' }
]);
subject.showTab('tab2');
const tabs = subject.rootEl.querySelectorAll('.jasmine-tab');
expect(tabs[0]).toHaveClass('jasmine-hidden');
expect(tabs[1]).not.toHaveClass('jasmine-hidden');
});
it('does not hide previously shown tabs', function() {
const subject = new privateUnderTest.TabBar([
{ id: 'tab1' },
{ id: 'tab2' }
]);
subject.showTab('tab1');
subject.showTab('tab2');
const tabs = subject.rootEl.querySelectorAll('.jasmine-tab');
expect(tabs[0]).not.toHaveClass('jasmine-hidden');
});
});
describe("When a tab's link is clicked", function() {
it("calls the onSelect callback with the tab's id", function() {
const onSelect = jasmine.createSpy('onSelect');
const subject = new privateUnderTest.TabBar(
[{ id: 'tab1', label: '' }],
onSelect
);
subject.rootEl.querySelector('.jasmine-tab a').click();
expect(onSelect).toHaveBeenCalledWith('tab1');
});
it('shows links on all non-selected tabs only', function() {
const subject = new privateUnderTest.TabBar(
[
{ id: 'tab1', label: 'tab 1' },
{ id: 'tab2', label: 'tab 2' },
{ id: 'tab3', label: 'tab 3' }
],
() => {}
);
subject.rootEl.querySelectorAll('.jasmine-tab a')[1].click();
let tabs = subject.rootEl.querySelectorAll('.jasmine-tab');
expect(tabs[0].querySelector('a'))
.withContext('tab 1')
.toBeTruthy();
expect(tabs[1].querySelector('a'))
.withContext('tab 1')
.toBeFalsy();
expect(tabs[2].querySelector('a'))
.withContext('tab 1')
.toBeTruthy();
subject.rootEl.querySelectorAll('.jasmine-tab a')[0].click();
tabs = subject.rootEl.querySelectorAll('.jasmine-tab');
expect(tabs[0].querySelector('a'))
.withContext('tab 1')
.toBeFalsy();
expect(tabs[1].querySelector('a'))
.withContext('tab 1')
.toBeTruthy();
expect(tabs[2].querySelector('a'))
.withContext('tab 1')
.toBeTruthy();
});
});
});

View File

@@ -1,4 +1,3 @@
/* eslint-env node, es6 */
const path = require('path'),
jasmineBrowser = require('jasmine-browser-runner'),
jasmineCore = require('../../lib/jasmine-core');

View File

@@ -1,4 +1,3 @@
/* eslint-env node, es6 */
module.exports = {
srcDir: 'src',
srcFiles: [
@@ -23,11 +22,13 @@ module.exports = {
'helpers/BrowserFlags.js',
'helpers/domHelpers.js',
'helpers/integrationMatchers.js',
'helpers/callerFilenameShim.js',
'helpers/defineJasmineUnderTest.js',
'helpers/resetEnv.js'
],
env: {
forbidDuplicateNames: true
forbidDuplicateNames: true,
safariYieldStrategy: 'time'
},
random: true,
browser: {

View File

@@ -8,6 +8,7 @@
"helpers/init.js",
"helpers/domHelpers.js",
"helpers/integrationMatchers.js",
"helpers/callerFilenameShim.js",
"helpers/overrideConsoleLogForCircleCi.js",
"helpers/nodeDefineJasmineUnderTest.js",
"helpers/resetEnv.js"

View File

@@ -1,3 +1,5 @@
'use strict';
/**
This file starts the process of "booting" Jasmine. It initializes Jasmine,
makes its globals available, and creates the env. This file should be loaded

View File

@@ -1,3 +1,5 @@
'use strict';
/**
This file finishes 'booting' Jasmine, performing all of the necessary
initialization before executing the loaded environment and all of a project's
@@ -13,85 +15,26 @@
(function() {
const env = jasmine.getEnv();
const queryString = new jasmine.QueryString({
getWindowLocation: function() {
return window.location;
}
});
const config = {
stopOnSpecFailure: queryString.getParam('stopOnSpecFailure'),
stopSpecOnExpectationFailure: queryString.getParam(
'stopSpecOnExpectationFailure'
),
hideDisabled: queryString.getParam('hideDisabled')
};
const random = queryString.getParam('random');
if (random !== undefined && random !== '') {
config.random = random;
}
const seed = queryString.getParam('seed');
if (seed) {
config.seed = seed;
}
const urls = new jasmine.HtmlReporterV2Urls();
/**
* ## Reporters
* The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
* Configures Jasmine based on the current set of query parameters. This
* supports all parameters set by the HTML reporter as well as
* spec=partialPath, which filters out specs whose paths don't contain the
* parameter.
*/
const htmlReporter = new jasmine.HtmlReporter({
env: env,
navigateWithNewParam: function(key, value) {
return queryString.navigateWithNewParam(key, value);
},
addToExistingQueryString: function(key, value) {
return queryString.fullStringWithNewParam(key, value);
},
getContainer: function() {
return document.body;
},
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
},
timer: new jasmine.Timer(),
queryString
});
env.configure(urls.configFromCurrentUrl());
/**
* The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
*/
env.addReporter(jsApiReporter);
env.addReporter(htmlReporter);
/**
* Filter which specs will be run by matching the start of the full name against the `spec` query param.
*/
const specFilter = new jasmine.HtmlExactSpecFilter({ queryString });
config.specFilter = function(spec) {
return specFilter.matches(spec);
};
env.configure(config);
/**
* ## Execution
*
* Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
*/
const currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
htmlReporter.initialize();
// The HTML reporter needs to be set up here so it can access the DOM. Other
// reporters can be added at any time before env.execute() is called.
const htmlReporter = new jasmine.HtmlReporterV2({ env, urls });
env.addReporter(htmlReporter);
env.execute();
};
})();

View File

@@ -1,3 +1,5 @@
'use strict';
/**
* Note: Only available on Node.
* @module jasmine-core

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().CallTracker = function(j$) {
'use strict';
/**
* @namespace Spy#calls
* @since 2.0.0

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().Clock = function() {
getJasmineRequireObj().Clock = function(j$) {
'use strict';
/* global process */
const NODE_JS =
typeof process !== 'undefined' &&
@@ -189,6 +191,9 @@ callbacks to execute _before_ running the next one.
clearTimeout[IsMockClockTimingFn] = true;
setInterval[IsMockClockTimingFn] = true;
clearInterval[IsMockClockTimingFn] = true;
j$.private.deprecateMonkeyPatching(this);
return this;
// Advances the Clock's time until the mode changes.

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().CompleteOnFirstErrorSkipPolicy = function(j$) {
'use strict';
function CompleteOnFirstErrorSkipPolicy(queueableFns) {
this.queueableFns_ = queueableFns;
this.erroredFnIx_ = null;

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().Configuration = function(j$) {
'use strict';
/**
* This represents the available options to configure Jasmine.
* Options that are not provided will use their default values.
@@ -71,6 +73,8 @@ getJasmineRequireObj().Configuration = function(j$) {
specFilter: function() {
return true;
},
// TODO: remove hideDisabled when HtmlReporter is removed
/**
* Whether reporters should hide disabled specs from their output.
* Currently only supported by Jasmine's HTMLReporter
@@ -78,6 +82,7 @@ getJasmineRequireObj().Configuration = function(j$) {
* @since 3.3.0
* @type Boolean
* @default false
* @deprecated
*/
hideDisabled: false,
/**
@@ -123,7 +128,45 @@ getJasmineRequireObj().Configuration = function(j$) {
* @type Boolean
* @default false
*/
detectLateRejectionHandling: false
detectLateRejectionHandling: false,
/**
* The number of extra stack frames inserted by a wrapper around {@link it}
* or by some other local modification. Jasmine uses this to determine the
* filename for {@link SpecStartedEvent} and {@link SpecDoneEvent}.
* @name Configuration#extraItStackFrames
* @since 5.13.0
* @type number
* @default 0
*/
extraItStackFrames: 0,
/**
* The number of extra stack frames inserted by a wrapper around
* {@link describe} or by some other local modification. Jasmine uses this
* to determine the filename for {@link SpecStartedEvent} and
* {@link SpecDoneEvent}.
* @name Configuration#extraDescribeStackFrames
* @since 5.13.0
* @type number
* @default 0
*/
extraDescribeStackFrames: 0,
/**
* The strategy to use in Safari and similar browsers to determine how often
* to yield control by calling setTimeout. If set to "count", the default,
* the frequency of setTimeout calls is based on the number of relevant
* function calls. If set to "time", the frequency of setTimeout calls is
* based on elapsed time. Using "time" may provide a significant performance
* improvement, but as of 6.0 it hasn't been tested with a wide variety of
* workloads and should be considered experimental.
* @name Configuration#safariYieldStrategy
* @since 6.0.0
* @type 'count' | 'time'
* @default 'count'
*/
safariYieldStrategy: 'count'
};
Object.freeze(defaultConfig);
@@ -174,6 +217,28 @@ getJasmineRequireObj().Configuration = function(j$) {
if (typeof changes.seed !== 'undefined') {
this.#values.seed = changes.seed;
}
// 0 is a valid value for both of these, so a truthiness check wouldn't work
if (typeof changes.extraItStackFrames !== 'undefined') {
this.#values.extraItStackFrames = changes.extraItStackFrames;
}
if (typeof changes.extraDescribeStackFrames !== 'undefined') {
this.#values.extraDescribeStackFrames =
changes.extraDescribeStackFrames;
}
if (typeof changes.safariYieldStrategy !== 'undefined') {
const v = changes.safariYieldStrategy;
if (v === 'count' || v === 'time') {
this.#values.safariYieldStrategy = v;
} else {
throw new Error(
"Invalid safariYieldStrategy value. Valid values are 'count' and 'time'."
);
}
}
}
}

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().CurrentRunableTracker = function() {
'use strict';
class CurrentRunableTracker {
#currentSpec;
#currentlyExecutingSuites;

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().Deprecator = function(j$) {
'use strict';
function Deprecator(topSuite) {
this.topSuite_ = topSuite;
this.verbose_ = false;

View File

@@ -1,4 +1,8 @@
getJasmineRequireObj().Env = function(j$) {
'use strict';
const DEFAULT_IT_DESCRIBE_STACK_DEPTH = 3;
/**
* @class Env
* @since 2.0.0
@@ -16,7 +20,7 @@ getJasmineRequireObj().Env = function(j$) {
const realSetTimeout = global.setTimeout;
const realClearTimeout = global.clearTimeout;
const clearStack = j$.private.getClearStack(global);
const stackClearer = j$.private.getStackClearer(global);
this.clock = new j$.private.Clock(
global,
function() {
@@ -26,7 +30,7 @@ getJasmineRequireObj().Env = function(j$) {
);
const globalErrors = new GlobalErrors(
undefined,
global,
// Configuration is late-bound because GlobalErrors needs to be constructed
// before it's set to detect load-time errors in browsers
() => this.configuration()
@@ -67,14 +71,14 @@ getJasmineRequireObj().Env = function(j$) {
if (!envOptions.suppressLoadErrors) {
installGlobalErrors();
globalErrors.pushListener(function loadtimeErrorHandler(error, event) {
topSuite.result.failedExpectations.push({
globalErrors.pushListener(function loadtimeErrorHandler(error) {
topSuite.addExpectationResult(false, {
passed: false,
globalErrorType: 'load',
message: error ? error.message : event.message,
stack: error && error.stack,
filename: event && event.filename,
lineno: event && event.lineno
message: error.message,
stack: error.stack,
filename: error.filename,
lineno: error.lineno
});
});
}
@@ -95,6 +99,7 @@ getJasmineRequireObj().Env = function(j$) {
config.update(changes);
deprecator.verboseDeprecations(config.verboseDeprecations);
stackClearer.setSafariYieldStrategy(config.safariYieldStrategy);
};
/**
@@ -240,7 +245,7 @@ getJasmineRequireObj().Env = function(j$) {
expectationResult.globalErrorType = 'lateError';
}
r.result.failedExpectations.push(expectationResult);
r.addExpectationResult(false, expectationResult);
return;
}
}
@@ -291,13 +296,16 @@ getJasmineRequireObj().Env = function(j$) {
* @param {String|Error} deprecation The deprecation message
* @param {Object} [options] Optional extra options, as described above
*/
this.deprecated = function(deprecation, options) {
const runable = runner.currentRunable() || topSuite;
deprecator.addDeprecationWarning(runable, deprecation, options);
};
Object.defineProperty(this, 'deprecated', {
enumerable: true,
value: function(deprecation, options) {
const runable = runner.currentRunable() || topSuite;
deprecator.addDeprecationWarning(runable, deprecation, options);
}
});
function runQueue(options) {
options.clearStack = options.clearStack || clearStack;
options.clearStack = options.clearStack || stackClearer;
options.timeout = {
setTimeout: realSetTimeout,
clearTimeout: realClearTimeout
@@ -321,7 +329,8 @@ getJasmineRequireObj().Env = function(j$) {
runQueue
});
topSuite = suiteBuilder.topSuite;
const deprecator = new j$.private.Deprecator(topSuite);
const deprecator =
envOptions?.deprecator ?? new j$.private.Deprecator(topSuite);
/**
* Provides the root suite, through which all suites and specs can be
@@ -400,6 +409,14 @@ getJasmineRequireObj().Env = function(j$) {
this.execute = async function(runablesToRun) {
installGlobalErrors();
// Karma incorrectly loads jasmine-core as an ES module. It isn't one,
// and we don't test that configuration. Warn about it.
if (j$.private.loadedAsBrowserEsm) {
this.deprecated(
"jasmine-core isn't an ES module but it was loaded as one. This is not a supported configuration."
);
}
if (parallelLoadingState) {
validateConfigForParallel();
}
@@ -581,14 +598,14 @@ getJasmineRequireObj().Env = function(j$) {
this.describe = function(description, definitionFn) {
ensureIsNotNested('describe');
const filename = callerCallerFilename();
const filename = indirectCallerFilename(describeStackDepth());
return suiteBuilder.describe(description, definitionFn, filename)
.metadata;
};
this.xdescribe = function(description, definitionFn) {
ensureIsNotNested('xdescribe');
const filename = callerCallerFilename();
const filename = indirectCallerFilename(describeStackDepth());
return suiteBuilder.xdescribe(description, definitionFn, filename)
.metadata;
};
@@ -596,30 +613,38 @@ getJasmineRequireObj().Env = function(j$) {
this.fdescribe = function(description, definitionFn) {
ensureIsNotNested('fdescribe');
ensureNonParallel('fdescribe');
const filename = callerCallerFilename();
const filename = indirectCallerFilename(describeStackDepth());
return suiteBuilder.fdescribe(description, definitionFn, filename)
.metadata;
};
this.it = function(description, fn, timeout) {
ensureIsNotNested('it');
const filename = callerCallerFilename();
const filename = indirectCallerFilename(itStackDepth());
return suiteBuilder.it(description, fn, timeout, filename).metadata;
};
this.xit = function(description, fn, timeout) {
ensureIsNotNested('xit');
const filename = callerCallerFilename();
const filename = indirectCallerFilename(itStackDepth());
return suiteBuilder.xit(description, fn, timeout, filename).metadata;
};
this.fit = function(description, fn, timeout) {
ensureIsNotNested('fit');
ensureNonParallel('fit');
const filename = callerCallerFilename();
const filename = indirectCallerFilename(itStackDepth());
return suiteBuilder.fit(description, fn, timeout, filename).metadata;
};
function itStackDepth() {
return DEFAULT_IT_DESCRIBE_STACK_DEPTH + config.extraItStackFrames;
}
function describeStackDepth() {
return DEFAULT_IT_DESCRIBE_STACK_DEPTH + config.extraDescribeStackFrames;
}
/**
* Get a user-defined property as part of the properties field of {@link SpecDoneEvent}
* @name Env#getSpecProperty
@@ -800,16 +825,26 @@ getJasmineRequireObj().Env = function(j$) {
}
};
this.pp = function(value) {
const pp = runner.currentRunable()
? runableResources.makePrettyPrinter()
: j$.private.basicPrettyPrinter;
return pp(value);
};
this.cleanup_ = function() {
uninstallGlobalErrors();
};
j$.private.deprecateMonkeyPatching(this, ['deprecated']);
}
function callerCallerFilename() {
function indirectCallerFilename(depth) {
const frames = new j$.private.StackTrace(new Error()).frames;
// frames[3] should always exist except in Jasmine's own tests, which bypass
// the global it/describe layer, but don't crash if it doesn't.
return frames[3] && frames[3].file;
// The specified frame should always exist except in Jasmine's own tests,
// which bypass the global it/describe layer, but could be absent in case
// of misconfiguration. Don't crash if it's absent.
return frames[depth] && frames[depth].file;
}
return Env;

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().ExceptionFormatter = function(j$) {
'use strict';
const ignoredProperties = [
'name',
'message',
@@ -9,7 +11,8 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) {
'lineNumber',
'column',
'description',
'jasmineMessage'
'jasmineMessage',
'errors'
];
function ExceptionFormatter(options) {
@@ -75,6 +78,19 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) {
lines = lines.concat(substack);
}
if (Array.isArray(error.errors)) {
for (let i = 0; i < error.errors.length; i++) {
if (error.errors[i] instanceof Error) {
lines.push('');
const substack = this.stack_(error.errors[i], {
messageHandling: 'require'
});
substack[0] = 'Error ' + (i + 1) + ': ' + substack[0];
lines = lines.concat(substack.map(x => ' ' + x));
}
}
}
return lines;
};

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().Expectation = function(j$) {
'use strict';
/**
* Matchers that come with Jasmine out of the box.
* @namespace matchers

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().ExpectationFilterChain = function() {
'use strict';
function ExpectationFilterChain(maybeFilter, prev) {
this.filter_ = maybeFilter;
this.prev_ = prev;

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().Expector = function(j$) {
'use strict';
function Expector(options) {
this.matchersUtil = options.matchersUtil || {
buildFailureMessage: function() {}

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().GlobalErrors = function(j$) {
'use strict';
class GlobalErrors {
#getConfig;
#adapter;
@@ -40,13 +42,6 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
this.#adapter.uninstall();
}
// The listener at the top of the stack will be called with two arguments:
// the error and the event. Either of them may be falsy.
// The error will normally be provided, but will be falsy in the case of
// some browser load-time errors. The event will normally be provided in
// browsers but will be falsy in Node.
// Listeners that are pushed after spec files have been loaded should be
// able to just use the error parameter.
pushListener(listener) {
this.#handlers.push(listener);
}
@@ -78,29 +73,23 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
}
reportUnhandledRejections() {
for (const {
reason,
event
} of this.#pendingUnhandledRejections.values()) {
this.#dispatchError(reason, event);
for (const { reason } of this.#pendingUnhandledRejections.values()) {
this.#dispatchError(reason);
}
this.#pendingUnhandledRejections.clear();
}
// Either error or event may be undefined
#onUncaughtException(error, event) {
this.#dispatchError(error, event);
#onUncaughtException(error) {
this.#dispatchError(error);
}
// event or promise may be undefined
// event is passed through for backwards compatibility reasons. It's probably
// unnecessary, but user code could depend on it.
#onUnhandledRejection(reason, promise, event) {
// promise may be undefined
#onUnhandledRejection(reason, promise) {
if (this.#detectLateRejectionHandling() && promise) {
this.#pendingUnhandledRejections.set(promise, { reason, event });
this.#pendingUnhandledRejections.set(promise, { reason });
} else {
this.#dispatchError(reason, event);
this.#dispatchError(reason);
}
}
@@ -112,8 +101,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
this.#pendingUnhandledRejections.delete(promise);
}
// Either error or event may be undefined
#dispatchError(error, event) {
#dispatchError(error) {
if (this.#overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
this.#overrideHandler(error);
@@ -123,7 +111,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
const handler = this.#handlers[this.#handlers.length - 1];
if (handler) {
handler(error, event);
handler(error);
} else {
throw error;
}
@@ -140,7 +128,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
constructor(global, dispatch) {
this.#global = global;
this.#dispatch = dispatch;
this.#onError = event => dispatch.onUncaughtException(event.error, event);
this.#onError = this.#errorHandler.bind(this);
this.#onUnhandledRejection = this.#unhandledRejectionHandler.bind(this);
this.#onRejectionHandled = this.#rejectionHandledHandler.bind(this);
}
@@ -169,6 +157,28 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
);
}
#errorHandler(event) {
let error = event.error;
// event.error isn't guaranteed to be present in all browser load-time
// error events.
if (!error) {
error = {
message: event.message,
stack: `@${event.filename}:${event.lineno}`
};
}
if (event.filename) {
// filename and lineno can be more convenient than stack when reporting
// things like syntax errors. Pass them along.
error.filename = event.filename;
error.lineno = event.lineno;
}
this.#dispatch.onUncaughtException(error);
}
#unhandledRejectionHandler(event) {
const jasmineMessage = 'Unhandled promise rejection: ' + event.reason;
let reason;
@@ -180,7 +190,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
reason = jasmineMessage;
}
this.#dispatch.onUnhandledRejection(reason, event.promise, event);
this.#dispatch.onUnhandledRejection(reason, event.promise);
}
#rejectionHandledHandler(event) {

View File

@@ -1,9 +1,13 @@
getJasmineRequireObj().JsApiReporter = function(j$) {
'use strict';
// TODO: remove in 7.0.
/**
* @name jsApiReporter
* @classdesc {@link Reporter} added by default in `boot.js` to record results for retrieval in javascript code. An instance is made available as `jsApiReporter` on the global object.
* @class
* @hideconstructor
* @deprecated In most cases jsApiReporter can simply be removed. If necessary, it can be replaced with a {@link Reporter|custom reporter}.
*/
function JsApiReporter(options) {
const timer = options.timer || new j$.Timer();

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().MockDate = function(j$) {
'use strict';
function MockDate(global) {
let currentTime = 0;

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().NeverSkipPolicy = function(j$) {
'use strict';
function NeverSkipPolicy(queueableFns) {}
NeverSkipPolicy.prototype.skipTo = function(lastRanFnIx) {

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().Order = function() {
'use strict';
function Order(options) {
this.random = 'random' in options ? options.random : true;
const seed = (this.seed = options.seed || generateSeed());

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().makePrettyPrinter = function(j$) {
'use strict';
class SinglePrettyPrintRun {
constructor(customObjectFormatters, pp) {
this.customObjectFormatters_ = customObjectFormatters;
@@ -57,7 +59,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
} else if (
value.toString &&
typeof value === 'object' &&
!j$.private.isArray(value) &&
!Array.isArray(value) &&
hasCustomToString(value)
) {
try {
@@ -69,15 +71,12 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
} else if (this.seen.includes(value)) {
this.emitScalar(
'<circular reference: ' +
(j$.private.isArray(value) ? 'Array' : 'Object') +
(Array.isArray(value) ? 'Array' : 'Object') +
'>'
);
} else if (
j$.private.isArray(value) ||
j$.private.isA('Object', value)
) {
} else if (Array.isArray(value) || j$.private.isA('Object', value)) {
this.seen.push(value);
if (j$.private.isArray(value)) {
if (Array.isArray(value)) {
this.emitArray(value);
} else {
this.emitObject(value);
@@ -100,10 +99,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
}
iterateObject(obj, fn) {
const objKeys = j$.private.MatchersUtil.keys(
obj,
j$.private.isArray(obj)
);
const objKeys = j$.private.MatchersUtil.keys(obj, Array.isArray(obj));
const length = Math.min(objKeys.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH);
for (let i = 0; i < length; i++) {

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().QueueRunner = function(j$) {
'use strict';
let nextid = 1;
function StopExecutionError() {}
@@ -49,11 +51,11 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}
this.onComplete = attrs.onComplete || emptyFn;
this.clearStack =
attrs.clearStack ||
function(fn) {
this.clearStack = attrs.clearStack || {
clearStack(fn) {
fn();
};
}
};
this.onException = attrs.onException || emptyFn;
this.onMultipleDone = attrs.onMultipleDone || fallbackOnMultipleDone;
this.userContext = attrs.userContext || new j$.private.UserContext();
@@ -77,8 +79,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}
QueueRunner.prototype.execute = function() {
this.handleFinalError = (error, event) => {
this.onException(errorOrMsgForGlobalError(error, event));
this.handleFinalError = error => {
this.onException(error);
};
this.globalErrors.pushListener(this.handleFinalError);
this.run(0);
@@ -108,8 +110,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
this.recordError_(iterativeIndex);
};
function handleError(error, event) {
onException(errorOrMsgForGlobalError(error, event));
function handleError(error) {
onException(error);
}
const cleanup = once(() => {
if (timeoutId !== void 0) {
@@ -233,7 +235,7 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}
}
this.clearStack(() => {
this.clearStack.clearStack(() => {
this.globalErrors.popListener(this.handleFinalError);
if (this.errored_) {
@@ -296,16 +298,5 @@ getJasmineRequireObj().QueueRunner = function(j$) {
};
}
function errorOrMsgForGlobalError(error, event) {
// TODO: In cases where error is a string or undefined, the error message
// that gets sent to reporters will be `${message} thrown`, which could
// be improved to not say "thrown" when the cause wasn't necessarily
// an exception or to provide hints about throwing Errors rather than
// strings.
return (
error || (event && event.message) || 'Global error event with no message'
);
}
return QueueRunner;
};

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().RunableResources = function(j$) {
'use strict';
class RunableResources {
constructor(options) {
this.byRunableId_ = {};

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().Runner = function(j$) {
'use strict';
class Runner {
#topSuite;
#getTotalSpecsDefined;
@@ -94,7 +96,8 @@ getJasmineRequireObj().Runner = function(j$) {
/**
* Information passed to the {@link Reporter#jasmineStarted} event.
* @typedef JasmineStartedInfo
* @property {Int} totalSpecsDefined - The total number of specs defined in this suite. Note that this property is not present when Jasmine is run in parallel mode.
* @property {int} totalSpecsDefined - The total number of specs defined in this suite. Note that this property is not present when Jasmine is run in parallel mode.
* @property {int} numExcludedSpecs - The number of specs that will be excluded from execution. Note that this property is not present when Jasmine is run in parallel mode.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite. Note that this property is not present when Jasmine is run in parallel mode.
* @property {Boolean} parallel - Whether Jasmine is being run in parallel mode.
* @since 2.0.0
@@ -103,6 +106,7 @@ 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,
numExcludedSpecs: this.#executionTree.numExcludedSpecs(),
order: orderForReporting(order),
parallel: false
});
@@ -123,7 +127,7 @@ getJasmineRequireObj().Runner = function(j$) {
this.#currentRunableTracker.popSuite();
let overallStatus, incompleteReason, incompleteCode;
if (hasFailures || this.#topSuite.result.failedExpectations.length > 0) {
if (hasFailures || this.#topSuite.hasOwnFailedExpectations()) {
overallStatus = 'failed';
} else if (this.#getFocusedRunables().length > 0) {
overallStatus = 'incomplete';
@@ -137,6 +141,7 @@ getJasmineRequireObj().Runner = function(j$) {
overallStatus = 'passed';
}
const topSuiteResult = this.#topSuite.doneEvent();
/**
* Information passed to the {@link Reporter#jasmineDone} event.
* @typedef JasmineDoneInfo
@@ -156,8 +161,8 @@ getJasmineRequireObj().Runner = function(j$) {
incompleteReason: incompleteReason,
incompleteCode: incompleteCode,
order: orderForReporting(order),
failedExpectations: this.#topSuite.result.failedExpectations,
deprecationWarnings: this.#topSuite.result.deprecationWarnings
failedExpectations: topSuiteResult.failedExpectations,
deprecationWarnings: topSuiteResult.deprecationWarnings
};
this.#topSuite.reportedDone = true;
await this.#reportDispatcher.jasmineDone(jasmineDoneInfo);

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().SkipAfterBeforeAllErrorPolicy = function(j$) {
'use strict';
function SkipAfterBeforeAllErrorPolicy(queueableFns) {
this.queueableFns_ = queueableFns;
this.skipping_ = false;

View File

@@ -1,9 +1,12 @@
getJasmineRequireObj().Spec = function(j$) {
'use strict';
class Spec {
#autoCleanClosures;
#throwOnExpectationFailure;
#timer;
#metadata;
#executionState;
constructor(attrs) {
this.expectationFactory = attrs.expectationFactory;
@@ -35,28 +38,23 @@ getJasmineRequireObj().Spec = function(j$) {
this.#throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
this.#timer = attrs.timer || new j$.Timer();
this.reset();
if (!this.queueableFn.fn) {
this.exclude();
}
this.reset();
}
addExpectationResult(passed, data, isError) {
const expectationResult = j$.private.buildExpectationResult(data);
if (passed) {
this.result.passedExpectations.push(expectationResult);
this.#executionState.passedExpectations.push(expectationResult);
} else {
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';
}
this.#executionState.failedExpectations.push(expectationResult);
}
if (this.#throwOnExpectationFailure && !isError) {
@@ -66,18 +64,21 @@ getJasmineRequireObj().Spec = function(j$) {
}
getSpecProperty(key) {
this.result.properties = this.result.properties || {};
return this.result.properties[key];
this.#executionState.properties = this.#executionState.properties || {};
return this.#executionState.properties[key];
}
setSpecProperty(key, value) {
// Key and value will eventually be cloned during reporting. The error
// thrown at that point if they aren't cloneable isn't very helpful.
// Throw a better one now.
j$.private.util.assertReporterCloneable(key, 'Key');
if (!j$.private.isString(key)) {
throw new Error('Key must be a string');
}
j$.private.util.assertReporterCloneable(value, 'Value');
this.result.properties = this.result.properties || {};
this.result.properties[key] = value;
this.#executionState.properties = this.#executionState.properties || {};
this.#executionState.properties[key] = value;
}
executionStarted() {
@@ -85,34 +86,48 @@ getJasmineRequireObj().Spec = function(j$) {
}
executionFinished(excluded, failSpecWithNoExp) {
this.#executionState.dynamicallyExcluded = excluded;
this.#executionState.requireExpectations = failSpecWithNoExp;
if (this.#autoCleanClosures) {
this.queueableFn.fn = null;
}
this.result.status = this.#status(excluded, failSpecWithNoExp);
this.result.duration = this.#timer.elapsed();
this.#executionState.duration = this.#timer.elapsed();
if (this.result.status !== 'failed') {
this.result.debugLogs = null;
if (this.status() !== 'failed') {
this.#executionState.debugLogs = null;
}
}
hadBeforeAllFailure() {
this.addExpectationResult(
false,
{
passed: false,
message:
'Not run because a beforeAll function failed. The ' +
'beforeAll failure will be reported on the suite that ' +
'caused it.'
},
true
);
}
reset() {
this.result = {
id: this.id,
description: this.description,
fullName: this.getFullName(),
parentSuiteId: this.parentSuiteId,
filename: this.filename,
this.#executionState = {
failedExpectations: [],
passedExpectations: [],
deprecationWarnings: [],
pendingReason: this.excludeMessage || '',
duration: null,
properties: null,
debugLogs: null
debugLogs: null,
// TODO: better naming. Don't make 'excluded' mean two things.
dynamicallyExcluded: false,
requireExpectations: false,
markedPending: this.markedExcluding
};
this.markedPending = this.markedExcluding;
this.reportedDone = false;
}
@@ -129,7 +144,7 @@ getJasmineRequireObj().Spec = function(j$) {
* 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}.
* @since 6.0.0
* @since 2.0.0
*/
return this.#commonEventFields();
}
@@ -141,38 +156,37 @@ getJasmineRequireObj().Spec = function(j$) {
* @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.
* @property {String} filename - 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}.
* same call stack height as the originals. You can fix that by setting
* {@link Configuration#extraItStackFrames}.
* @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 {String} status - The result of this spec. May be 'passed', 'failed', 'pending', or 'excluded'.
* @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 6.0.0
* @since 2.0.0
*/
const event = {
...this.#commonEventFields()
...this.#commonEventFields(),
status: this.status()
};
const toCopy = [
'failedExpectations',
'passedExpectations',
'deprecationWarnings',
'pendingReason',
'status',
'duration',
'properties',
'debugLogs'
];
for (const k of toCopy) {
event[k] = this.result[k];
event[k] = this.#executionState[k];
}
return event;
@@ -210,12 +224,16 @@ getJasmineRequireObj().Spec = function(j$) {
}
pend(message) {
this.markedPending = true;
this.#executionState.markedPending = true;
if (message) {
this.result.pendingReason = message;
this.#executionState.pendingReason = message;
}
}
get markedPending() {
return this.#executionState.markedPending;
}
// Like pend(), but pending state will survive reset().
// Useful for fit, xit, where pending state remains.
exclude(message) {
@@ -226,15 +244,8 @@ getJasmineRequireObj().Spec = function(j$) {
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) {
status() {
if (this.#executionState.dynamicallyExcluded) {
return 'excluded';
}
@@ -243,10 +254,10 @@ getJasmineRequireObj().Spec = function(j$) {
}
if (
this.result.failedExpectations.length > 0 ||
(failSpecWithNoExpectations &&
this.result.failedExpectations.length +
this.result.passedExpectations.length ===
this.#executionState.failedExpectations.length > 0 ||
(this.#executionState.requireExpectations &&
this.#executionState.failedExpectations.length +
this.#executionState.passedExpectations.length ===
0)
) {
return 'failed';
@@ -263,14 +274,14 @@ getJasmineRequireObj().Spec = function(j$) {
if (typeof deprecation === 'string') {
deprecation = { message: deprecation };
}
this.result.deprecationWarnings.push(
this.#executionState.deprecationWarnings.push(
j$.private.buildExpectationResult(deprecation)
);
}
debugLog(msg) {
if (!this.result.debugLogs) {
this.result.debugLogs = [];
if (!this.#executionState.debugLogs) {
this.#executionState.debugLogs = [];
}
/**
@@ -279,7 +290,7 @@ getJasmineRequireObj().Spec = function(j$) {
* @property {number} timestamp - The time when the entry was added, in
* milliseconds from the spec's start time
*/
this.result.debugLogs.push({
this.#executionState.debugLogs.push({
message: msg,
timestamp: this.#timer.elapsed()
});
@@ -331,7 +342,20 @@ getJasmineRequireObj().Spec = function(j$) {
* @returns {Array.<string>}
* @since 5.7.0
*/
getPath: this.getPath.bind(this)
getPath: this.getPath.bind(this),
/**
* 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. You can fix that by setting
* {@link Configuration#extraItStackFrames}.
* @name Spec#filename
* @readonly
* @type {string}
* @since 5.13.0
*/
filename: this.filename
};
}

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().Spy = function(j$) {
'use strict';
const nextOrder = (function() {
let order = 0;

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().SpyFactory = function(j$) {
'use strict';
function SpyFactory(
getCustomStrategies,
getDefaultStrategyFn,
@@ -19,7 +21,7 @@ getJasmineRequireObj().SpyFactory = function(j$) {
this.createSpyObj = function(baseName, methodNames, propertyNames) {
const baseNameIsCollection =
j$.private.isObject(baseName) || j$.private.isArray(baseName);
j$.private.isObject(baseName) || Array.isArray(baseName);
if (baseNameIsCollection) {
propertyNames = methodNames;
@@ -65,7 +67,7 @@ getJasmineRequireObj().SpyFactory = function(j$) {
function normalizeKeyValues(object) {
const result = [];
if (j$.private.isArray(object)) {
if (Array.isArray(object)) {
for (let i = 0; i < object.length; i++) {
result.push([object[i]]);
}

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().SpyRegistry = function(j$) {
'use strict';
const spyOnMsg = j$.private.formatErrorMsg(
'<spyOn>',
'spyOn(<object>, <methodName>)'
@@ -81,7 +83,10 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
};
} else {
restoreStrategy = function() {
if (!delete obj[methodName]) {
try {
delete obj[methodName];
// eslint-disable-next-line no-unused-vars
} catch (e) {
obj[methodName] = originalMethod;
}
};

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().SpyStrategy = function(j$) {
'use strict';
/**
* @interface SpyStrategy
*/

View File

@@ -1,18 +1,48 @@
getJasmineRequireObj().clearStack = function(j$) {
getJasmineRequireObj().StackClearer = function(j$) {
'use strict';
const maxInlineCallCount = 10;
// 25ms gives a good balance of speed and UI responsiveness when running
// jasmine-core's own tests in Safari 18. The exact value isn't critical.
const safariYieldIntervalMs = 25;
function browserQueueMicrotaskImpl(global) {
const unclampedSetTimeout = getUnclampedSetTimeout(global);
const { queueMicrotask } = global;
let currentCallCount = 0;
return function clearStack(fn) {
currentCallCount++;
let yieldStrategy = 'count';
let currentCallCount = 0; // for count strategy
let nextSetTimeoutTime; // for time strategy
if (currentCallCount < maxInlineCallCount) {
queueMicrotask(fn);
} else {
currentCallCount = 0;
unclampedSetTimeout(fn);
return {
clearStack(fn) {
currentCallCount++;
let shouldSetTimeout;
if (yieldStrategy === 'time') {
const now = new Date().getTime();
shouldSetTimeout = now >= nextSetTimeoutTime;
if (shouldSetTimeout) {
nextSetTimeoutTime = now + safariYieldIntervalMs;
}
} else {
shouldSetTimeout = currentCallCount >= maxInlineCallCount;
if (shouldSetTimeout) {
currentCallCount = 0;
}
}
if (shouldSetTimeout) {
unclampedSetTimeout(fn);
} else {
queueMicrotask(fn);
}
},
setSafariYieldStrategy(strategy) {
yieldStrategy = strategy;
if (yieldStrategy === 'time') {
nextSetTimeoutTime = new Date().getTime() + safariYieldIntervalMs;
}
}
};
}
@@ -20,8 +50,11 @@ getJasmineRequireObj().clearStack = function(j$) {
function nodeQueueMicrotaskImpl(global) {
const { queueMicrotask } = global;
return function(fn) {
queueMicrotask(fn);
return {
clearStack(fn) {
queueMicrotask(fn);
},
setSafariYieldStrategy() {}
};
}
@@ -30,15 +63,19 @@ getJasmineRequireObj().clearStack = function(j$) {
const postMessage = getPostMessage(global);
let currentCallCount = 0;
return function clearStack(fn) {
currentCallCount++;
if (currentCallCount < maxInlineCallCount) {
postMessage(fn);
} else {
currentCallCount = 0;
setTimeout(fn);
}
return {
clearStack(fn) {
currentCallCount++;
if (currentCallCount < maxInlineCallCount) {
postMessage(fn);
} else {
currentCallCount = 0;
setTimeout(fn);
}
},
setSafariYieldStrategy() {}
};
}
@@ -86,7 +123,7 @@ getJasmineRequireObj().clearStack = function(j$) {
};
}
function getClearStack(global) {
function getStackClearer(global) {
const NODE_JS =
global.process &&
global.process.versions &&
@@ -104,12 +141,10 @@ getJasmineRequireObj().clearStack = function(j$) {
// Unlike browsers, Node doesn't require us to do a periodic setTimeout
// so we avoid the overhead.
return nodeQueueMicrotaskImpl(global);
} else if (SAFARI_OR_WIN_WEBKIT || !global.MessageChannel /* tests */) {
} else if (SAFARI_OR_WIN_WEBKIT) {
// queueMicrotask is dramatically faster than MessageChannel in Safari
// and other WebKit-based browsers, such as the one distributed by Playwright
// to test Safari-like behavior on Windows.
// Some of our own integration tests provide a mock queueMicrotask in all
// environments because it's simpler to mock than MessageChannel.
return browserQueueMicrotaskImpl(global);
} else {
// MessageChannel is faster than queueMicrotask in supported browsers
@@ -118,5 +153,5 @@ getJasmineRequireObj().clearStack = function(j$) {
}
}
return getClearStack;
return getStackClearer;
};

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().StackTrace = function(j$) {
'use strict';
function StackTrace(error) {
let lines = error.stack.split('\n');

View File

@@ -1,9 +1,12 @@
getJasmineRequireObj().Suite = function(j$) {
'use strict';
class Suite {
#reportedParentSuiteId;
#throwOnExpectationFailure;
#autoCleanClosures;
#timer;
#result;
constructor(attrs) {
this.id = attrs.id;
@@ -34,10 +37,13 @@ getJasmineRequireObj().Suite = function(j$) {
// Key and value will eventually be cloned during reporting. The error
// thrown at that point if they aren't cloneable isn't very helpful.
// Throw a better one now.
j$.private.util.assertReporterCloneable(key, 'Key');
if (!j$.private.isString(key)) {
throw new Error('Key must be a string');
}
j$.private.util.assertReporterCloneable(value, 'Value');
this.result.properties = this.result.properties || {};
this.result.properties[key] = value;
this.#result.properties = this.#result.properties || {};
this.#result.properties[key] = value;
}
getFullName() {
@@ -87,7 +93,7 @@ getJasmineRequireObj().Suite = function(j$) {
}
endTimer() {
this.result.duration = this.#timer.elapsed();
this.#result.duration = this.#timer.elapsed();
}
cleanupBeforeAfter() {
@@ -100,26 +106,7 @@ getJasmineRequireObj().Suite = function(j$) {
}
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 = {
this.#result = {
id: this.id,
description: this.description,
fullName: this.getFullName(),
@@ -137,6 +124,71 @@ getJasmineRequireObj().Suite = function(j$) {
this.reportedDone = false;
}
startedEvent() {
/**
* @typedef SuiteStartedEvent
* @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}.
* @since 6.0.0
*/
return this.#commonEventFields();
}
doneEvent() {
/**
* @typedef SuiteDoneEvent
* @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 - 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. You can fix
* that by setting {@link Configuration#extraDescribeStackFrames}.
* @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
*/
const event = {
...this.#commonEventFields(),
status: this.#status()
};
const toCopy = [
'failedExpectations',
'deprecationWarnings',
'duration',
'properties'
];
for (const k of toCopy) {
event[k] = this.#result[k];
}
return event;
}
#commonEventFields() {
return {
id: this.id,
description: this.description,
fullName: this.getFullName(),
parentSuiteId: this.#reportedParentSuiteId,
filename: this.filename
};
}
removeChildren() {
this.children = [];
}
@@ -150,20 +202,15 @@ getJasmineRequireObj().Suite = function(j$) {
return 'pending';
}
if (this.result.failedExpectations.length > 0) {
if (this.#result.failedExpectations.length > 0) {
return 'failed';
} else {
return 'passed';
}
}
getResult() {
this.result.status = this.#status();
return this.result;
}
canBeReentered() {
return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
hasOwnFailedExpectations() {
return this.#result.failedExpectations.length > 0;
}
sharedUserContext() {
@@ -199,7 +246,7 @@ getJasmineRequireObj().Suite = function(j$) {
if (this.reportedDone) {
this.onLateError(failedExpectation);
} else {
this.result.failedExpectations.push(failedExpectation);
this.#result.failedExpectations.push(failedExpectation);
}
}
@@ -237,12 +284,7 @@ getJasmineRequireObj().Suite = function(j$) {
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';
}
this.#result.failedExpectations.push(expectationResult);
}
if (this.#throwOnExpectationFailure) {
@@ -254,7 +296,7 @@ getJasmineRequireObj().Suite = function(j$) {
if (typeof deprecation === 'string') {
deprecation = { message: deprecation };
}
this.result.deprecationWarnings.push(
this.#result.deprecationWarnings.push(
j$.private.buildExpectationResult(deprecation)
);
}
@@ -319,6 +361,19 @@ getJasmineRequireObj().Suite = function(j$) {
* @since 2.0.0
*/
this.description = suite.description;
/**
* 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. You
* can fix that by setting {@link Configuration#extraItStackFrames}.
* @name Suite#filename
* @readonly
* @type {string}
* @since 5.13.0
*/
this.filename = suite.filename;
}
/**

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().SuiteBuilder = function(j$) {
'use strict';
class SuiteBuilder {
constructor(options) {
this.env_ = options.env;

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().Timer = function() {
'use strict';
const defaultNow = (function(Date) {
return function() {
return new Date().getTime();

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().TreeProcessor = function(j$) {
'use strict';
const defaultMin = Infinity;
const defaultMax = 1 - Infinity;
@@ -121,6 +123,23 @@ getJasmineRequireObj().TreeProcessor = function(j$) {
const nodeStats = this.#stats[node.id];
return node.children ? !nodeStats.willExecute : nodeStats.excluded;
}
numExcludedSpecs(node) {
if (!node) {
return this.numExcludedSpecs(this.topSuite);
} else if (node.children) {
let result = 0;
for (const child of node.children) {
result += this.numExcludedSpecs(child);
}
return result;
} else {
const nodeStats = this.#stats[node.id];
return nodeStats.willExecute ? 0 : 1;
}
}
}
function segmentChildren(node, orderedChildren, stats, executableIndex) {

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().TreeRunner = function(j$) {
'use strict';
class TreeRunner {
#executionTree;
#setTimeout;
@@ -47,7 +49,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.startedEvent()).then(next);
};
const resultCallback = (result, next) => {
@@ -77,7 +82,7 @@ getJasmineRequireObj().TreeRunner = function(j$) {
);
},
onComplete: () => {
if (spec.result.status === 'failed') {
if (spec.status() === 'failed') {
specOverallDone(new j$.private.StopExecutionError('spec failed'));
} else {
specOverallDone();
@@ -109,7 +114,7 @@ getJasmineRequireObj().TreeRunner = function(j$) {
const complete = {
fn(done) {
spec.executionFinished(excluded, config.failSpecWithNoExpectations);
resultCallback(spec.result, done);
resultCallback(spec.doneEvent(), done);
},
type: 'specCleanup'
};
@@ -163,7 +168,7 @@ getJasmineRequireObj().TreeRunner = function(j$) {
this.#runQueue({
onComplete: maybeError => {
this.#suiteSegmentComplete(suite, suite.getResult(), () => {
this.#suiteSegmentComplete(suite, () => {
done(maybeError);
});
},
@@ -200,11 +205,12 @@ getJasmineRequireObj().TreeRunner = function(j$) {
#suiteSegmentStart(suite, next) {
this.#currentRunableTracker.pushSuite(suite);
this.#runableResources.initForRunable(suite.id, suite.parentSuite.id);
this.#reportDispatcher.suiteStarted(suite.result).then(next);
this.#reportDispatcher.suiteStarted(suite.startedEvent()).then(next);
suite.startTimer();
}
#suiteSegmentComplete(suite, result, next) {
#suiteSegmentComplete(suite, next) {
suite.endTimer();
const isTopSuite = suite === this.#executionTree.topSuite;
if (!isTopSuite) {
@@ -220,15 +226,14 @@ getJasmineRequireObj().TreeRunner = function(j$) {
this.#runableResources.clearForRunable(suite.id);
this.#currentRunableTracker.popSuite();
if (result.status === 'failed') {
if (suite.doneEvent().status === 'failed') {
this.#hasFailures = true;
}
suite.endTimer();
}
const finish = isTopSuite
? next
: () => this.#reportSuiteDone(suite, result, next);
: () => this.#reportSuiteDone(suite, next);
if (suite.hadBeforeAllFailure) {
this.#reportChildrenOfBeforeAllFailure(suite).then(finish);
@@ -237,16 +242,16 @@ getJasmineRequireObj().TreeRunner = function(j$) {
}
}
#reportSuiteDone(suite, result, next) {
#reportSuiteDone(suite, next) {
suite.reportedDone = true;
this.#reportDispatcher.suiteDone(result).then(next);
this.#reportDispatcher.suiteDone(suite.doneEvent()).then(next);
}
async #specComplete(spec) {
this.#runableResources.clearForRunable(spec.id);
this.#currentRunableTracker.setCurrentSpec(null);
if (spec.result.status === 'failed') {
if (spec.status() === 'failed') {
this.#hasFailures = true;
}
@@ -261,30 +266,13 @@ getJasmineRequireObj().TreeRunner = function(j$) {
async #reportChildrenOfBeforeAllFailure(suite) {
for (const child of suite.children) {
if (child instanceof j$.private.Suite) {
await this.#reportDispatcher.suiteStarted(child.result);
await this.#reportDispatcher.suiteStarted(child.startedEvent());
await this.#reportChildrenOfBeforeAllFailure(child);
// Marking the suite passed is consistent with how suites that
// contain failed specs but no suite-level failures are reported.
child.result.status = 'passed';
await this.#reportDispatcher.suiteDone(child.result);
await this.#reportDispatcher.suiteDone(child.doneEvent());
} else {
/* a spec */
await this.#reportDispatcher.specStarted(child.startedEvent());
child.addExpectationResult(
false,
{
passed: false,
message:
'Not run because a beforeAll function failed. The ' +
'beforeAll failure will be reported on the suite that ' +
'caused it.'
},
true
);
child.result.status = 'failed';
child.hadBeforeAllFailure();
await this.#reportSpecDone(child);
}
}

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().UserContext = function(j$) {
'use strict';
function UserContext() {}
UserContext.fromExisting = function(oldContext) {

View File

@@ -0,0 +1,29 @@
getJasmineRequireObj().AllOf = function(j$) {
'use strict';
function AllOf() {
const expectedValues = Array.from(arguments);
if (expectedValues.length === 0) {
throw new TypeError(
'jasmine.allOf() expects at least one argument to be passed.'
);
}
this.expectedValues = expectedValues;
}
AllOf.prototype.asymmetricMatch = function(other, matchersUtil) {
for (const expectedValue of this.expectedValues) {
if (!matchersUtil.equals(other, expectedValue)) {
return false;
}
}
return true;
};
AllOf.prototype.jasmineToString = function(pp) {
return '<jasmine.allOf(' + pp(this.expectedValues) + ')>';
};
return AllOf;
};

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().Any = function(j$) {
'use strict';
function Any(expectedObject) {
if (typeof expectedObject === 'undefined') {
throw new TypeError(

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().Anything = function(j$) {
'use strict';
function Anything() {}
Anything.prototype.asymmetricMatch = function(other) {

View File

@@ -1,10 +1,12 @@
getJasmineRequireObj().ArrayContaining = function(j$) {
'use strict';
function ArrayContaining(sample) {
this.sample = sample;
}
ArrayContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
if (!j$.private.isArray(this.sample)) {
if (!Array.isArray(this.sample)) {
throw new Error(
'You must provide an array to arrayContaining, not ' +
j$.private.basicPrettyPrinter(this.sample) +
@@ -15,7 +17,7 @@ getJasmineRequireObj().ArrayContaining = function(j$) {
// If the actual parameter is not an array, we can fail immediately, since it couldn't
// possibly be an "array containing" anything. However, we also want an empty sample
// array to match anything, so we need to double-check we aren't in that case
if (!j$.private.isArray(other) && this.sample.length > 0) {
if (!Array.isArray(other) && this.sample.length > 0) {
return false;
}

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().ArrayWithExactContents = function(j$) {
'use strict';
function ArrayWithExactContents(sample) {
this.sample = sample;
}
@@ -7,7 +9,7 @@ getJasmineRequireObj().ArrayWithExactContents = function(j$) {
other,
matchersUtil
) {
if (!j$.private.isArray(this.sample)) {
if (!Array.isArray(this.sample)) {
throw new Error(
'You must provide an array to arrayWithExactContents, not ' +
j$.private.basicPrettyPrinter(this.sample) +

View File

@@ -1,10 +1,12 @@
getJasmineRequireObj().Empty = function(j$) {
'use strict';
function Empty() {}
Empty.prototype.asymmetricMatch = function(other) {
if (
j$.private.isString(other) ||
j$.private.isArray(other) ||
Array.isArray(other) ||
j$.private.isTypedArray(other)
) {
return other.length === 0;

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().Falsy = function(j$) {
'use strict';
function Falsy() {}
Falsy.prototype.asymmetricMatch = function(other) {

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().Is = function(j$) {
'use strict';
class Is {
constructor(expected) {
this.expected_ = expected;

Some files were not shown because too many files have changed in this diff Show More