Compare commits

..

79 Commits

Author SHA1 Message Date
Steve Gravrock 5cd7d47f72 Bump version to 5.3.0 2024-09-07 13:18:21 -07:00
Steve Gravrock d0fe5c4712 Require curly braces around loop and conditonal bodies 2024-09-02 11:30:36 -07:00
Steve Gravrock f602c4911c Merge branch 'dave-unclamp-safari' of https://github.com/dcsaszar/jasmine
* Significantly improves performance in Safari
* Merges #2040 from @dcsaszar
* Fixes #2008
2024-09-02 11:22:56 -07:00
Steve Gravrock 7aaf7eaf30 API reference for reporter capabilities 2024-09-02 10:53:56 -07:00
David Császár 35f16e8125 Add test for unclamping setTimeout in Safari 2024-09-01 10:22:16 +02:00
David Császár acc777c267 Unclamp setTimeout in Safari
Fixes #2008

wrapping setTimeout in postMessage is a trade-off between
* slowdown due to postMessage (slow on Safari)
* speedup due to no setTimeout clamping (can be severe on Safari)
2024-09-01 10:21:57 +02:00
David Császár 1c74356691 Add unclampedSetTimeout helper 2024-09-01 09:54:55 +02:00
David Császár bbebea0fa5 Refactor: extract postMessage 2024-09-01 09:31:52 +02:00
Steve Gravrock 66eb27b0af Throw if spying has no effect
This provides a useful diagnostic in cases where assigning to a property
is a no-op, like localStorage in Firefox and Safari 17.

See #2036 and #2007.
2024-08-17 09:07:18 -07:00
Steve Gravrock 7a63c06a65 Merge branch 'webkit-performance' of https://github.com/m-akinc/jasmine
* Merges #2034 from @m-akinc
2024-08-10 07:26:00 -07:00
Mert Akinc 554dfd4923 Update comments as requested 2024-08-05 12:27:11 -05:00
Mert Akinc 36a6e2aa1d Merge branch 'main' into webkit-performance 2024-08-05 12:19:53 -05:00
Steve Gravrock 3c4b73f136 Fixed globbing in own test suite when running on Windows outside of c:\Users
The previous code used path.join() to construct glob input. That should't
work, but it did as long as the working directory was under c:\Users.
2024-08-03 18:06:20 -07:00
Mert Akinc bc3ed74336 Formatting fix 2024-07-26 17:54:04 -05:00
Mert Akinc 97b6f33cc2 Add test, update rexex pattern and constant name 2024-07-26 17:45:23 -05:00
Mert Akinc a9889ddb31 Improve performance on Playwright Windows WebKit 2024-07-25 09:54:32 -05:00
Steve Gravrock cd1b7ce9c7 Bump version to 5.2.0 2024-07-20 08:44:53 -07:00
Steve Gravrock 3653d6e0ef Moved eslint and prettier configs out of package.json 2024-07-18 07:17:31 -07:00
Steve Gravrock c3387f8dbf Merge branch 'stephanreiter-better-toHaveSize'
* Merges #2033 from @stephanreiter
2024-07-13 14:02:15 -07:00
Steve Gravrock 3d54184c7f Docs: Improved discoverability of asymmetric equality testers 2024-07-03 10:17:57 -07:00
Stephan Ferlin-Reiter 6f23e706d7 Improve the error message of the toHaveSize matcher.
We include the size of the thing that didn't meet the size expectation.
2024-07-02 20:28:16 +00:00
Steve Gravrock cc69edf92c Fixed stack trace filtering in FF when the developer tools are open 2024-06-22 11:49:49 -07:00
Steve Gravrock ba7560f65e HTML reporter: show debug logs with white-space: pre 2024-06-22 11:44:11 -07:00
Steve Gravrock c3960c4a96 Test against Node 22 2024-06-18 18:13:51 -07:00
Steve Gravrock 5c21f94bb1 4.6.1 release notes 2024-05-25 10:24:11 -07:00
Steve Gravrock 8cd7c94490 Added a jsdoc example for withContext()
Fixes jasmine/jasmine.github.io#166
2024-04-16 16:22:25 -07:00
Steve Gravrock 6a6fa7b29a Tests for existing handling of non-Error global errors in Node 2024-03-22 09:15:22 -07:00
Steve Gravrock 4984548cab Clarify argument to spyOnGlobalErrorsAsync's spy 2024-03-22 08:53:19 -07:00
Steve Gravrock 6941bde7e2 Revert "Deprecate the suppressLoadErrors option"
jasmine-npm still needs this to enable the default behavior of crashing
with a stack trace on load errors.

This reverts commit 99e350ac85.
2024-03-21 09:34:26 -07:00
Steve Gravrock 1504f25ced Report the message when a browser error event with a message but no error occurs 2024-03-21 09:15:43 -07:00
Steve Gravrock 99e350ac85 Deprecate the suppressLoadErrors option
This was intended as a 3.0 migration aid for browser users who had
dependencies that triggered errors at load time. However, it was never
documented and never supported by jasmine-brower-runner, karma, or any
other commonly used tool for runing Jasmine in the browser. There is
no evidence of it actually being used. It is, however, starting to
show up in machine-generated "tutorials".
2024-03-04 19:35:31 -08:00
Steve Gravrock 1624b07589 Clarify spyOnGlobalErrorsAsync API docs 2024-03-04 19:35:24 -08:00
Steve Gravrock d06dce4614 Bump version to 5.1.2 2024-02-08 17:22:46 -08:00
Steve Gravrock 03098e81f8 Fixed throwUnlessAsync
Fixes #2026
2024-02-05 18:49:19 -08:00
Steve Gravrock 726c152f6e Added Safari 17 to supported browsers 2024-02-05 18:44:11 -08:00
Steve Gravrock 409d2e29e5 Fixed formatting of copyright notice in README 2023-10-14 17:38:30 -07:00
Steve Gravrock 01e2bd5050 Updated copyright notices
The Pivotal copyright notice needs to be retained. That's the right
thing to do and the MIT license requires it. However, using it by itself
becomes more obviously incorrect with each passing year since Pivotal
ceased to exist. Moreover, Pivotal hasn't actually been the sole
copyright owner since the first external contribution was merged.
Contributors were not asked to sign a copyright assignment, so they
retain copyright to their contributions.

"Copyright (c) 2008-2019 Pivotal Labs" complies with the terms of the
license and acknowledges Pivotal's outsized role in developing Jasmine.
"Copyright (c) 2008-$YEAR The Jasmine developers" acknowledges all
authors and will remain correct in the future.
2023-10-14 08:55:48 -07:00
Steve Gravrock 96033e38ea Merge branch 'main' of https://github.com/jd-apprentice/jasmine
* Merges #2017 from @jd-apprentice
2023-10-07 10:34:06 -07:00
Steve Gravrock ed75290ef7 Removed badges from README
The CI status badge mostly just shows whether Saucelabas was flaky
last night. Code Triage was a nice idea but it's attracted at most
one new contributor over 5.5 years.
2023-09-30 10:22:37 -07:00
Steve Gravrock a14dbf012a Fix Chrome on CI 2023-09-30 08:33:27 -07:00
Jonathan 17c11ba7b9 fix: updated remaining files 2023-09-21 11:48:52 -03:00
Jonathan 2a1daca1ca chore: rename license file 2023-09-19 14:41:21 -03:00
Steve Gravrock f0db5ce350 Added Firefox 115 (current ESR) to supported browsers 2023-09-07 21:43:24 -07:00
Steve Gravrock 39f9c2e1a0 Don't attach spec helpers to the env 2023-08-26 11:52:26 -07:00
Steve Gravrock bff612a169 Bump version to 5.1.1 2023-08-24 19:00:26 -07:00
Steve Gravrock 4ba42f3746 Fixed global variable leak when using ParallelReportDispatcher 2023-08-22 19:34:22 -07:00
Steve Gravrock 58bee05c36 Documented usage of eval in DelayedFunctionScheduler 2023-08-22 19:28:20 -07:00
Steve Gravrock c1871b0f0c Removed unnecessary throw when building stack trace
Since 4.0, all supported JS runtimes populate the stack property of
Error objects when the Error is instantiated, not when it's thrown.
2023-08-19 09:54:03 -07:00
Steve Gravrock c16974b091 Improved jsdocs for originalFn argument to createSpy
Fixes jasmine.github.io#137.
2023-08-14 18:32:51 -07:00
Steve Gravrock bfedda9764 Link to 5.0 upgrade guide in README, not 4.0 2023-08-12 17:26:39 -07:00
Steve Gravrock a67b7276be Fixed jsdocs for throwUnless and throwUnlessAsync 2023-07-22 09:36:16 -07:00
Steve Gravrock 47f3105ef0 Bump version to 5.1.0 2023-07-22 09:12:36 -07:00
Steve Gravrock aeb56539c9 Built distribution 2023-07-22 09:12:18 -07:00
Steve Gravrock 75d45efa16 Exclude inherited Error properties from stack trace
These are likely to be methods or other things that aren't meaningful in
Jasmine's output.
2023-07-19 19:13:24 -07:00
Steve Gravrock 59d1c5bebb Use "bug report" tag, not "unconfirmed bug" 2023-07-19 18:29:51 -07:00
Steve Gravrock 040983c979 Merge branch 'skip-non-error-cause' of https://github.com/angrycat9000/jasmine
* Merges #2013 from @angrycat9000
* Fixes #2011
2023-07-19 18:28:56 -07:00
angrycat9000 2ddb344bac Skip parsing cause if it is not an Error object 2023-07-19 10:00:50 -04:00
Steve Gravrock e56bd3918b Added throwUnless and throwUnlessAsync
These are similar to `expect` and `expectAsync` except that they throw
exceptions rather than recording matcher failures as spec/suite failures.
They're intended to support using Jasmine matchers in testing-library's
`waitFor`, and also provide a way to integration-test custom matchers.

These funtions are not equivalent to `expect` and `expectAsync` and should
not be used in situations where you want a matcher failure to reliably fail
the spec. Whether that happens depends on the structure of the surrounding
code. In general, you should only use `throwUnless` when you expect
something (which could be your own code or library code like `waitFor`) to
catch the resulting exception.

Fixes #2003.
Fixes #1980.
2023-07-15 12:08:11 -07:00
Steve Gravrock 59600a1c29 Removed expect/expectAsync indirection through spec/suite 2023-07-15 12:08:11 -07:00
Steve Gravrock f8e4ea868f CI: Use a globally-unique Sauce tunnel ID
CIRCLE_BUILD_NUM is only unique per-repo, and we have multiple repos
that can concurrently run Sauce builds.
2023-07-01 11:05:04 -07:00
Steve Gravrock 0ff56c53b1 Dogfood remote Selenium grid support 2023-07-01 10:17:05 -07:00
Steve Gravrock b617d983de Bump version to 5.0.1 2023-06-09 16:24:00 -07:00
Steve Gravrock 8e0f0e8e8c Optionally restore the pre-5.0 behavior of boot() always creating a new instance
This is needed by jasmine-npm (and likely other tools like it) that may
need to create and use multiple envs in sequence.
2023-06-05 19:44:06 -07:00
Steve Gravrock d745d6b5f0 Bump version to 5.0.0 2023-05-13 15:14:53 -07:00
Steve Gravrock fc2c2a477d Updated most dev dependencies
Not updating Prettier because newer versions impose significant formatting
changes. In particular, 2.0 changes every function definition from
`function() {` to `function () {` with no way to opt out. I'm not willing
to accept that kind of churn just becuse the Prettier devs changed their
mind about what color the bikeshed should be.

We'll most likely stay on Prettier 1.17 for as long as it remains viable,
then either switch to an autoformatter that offers stability or just
remove it.
2023-05-13 12:03:45 -07:00
Steve Gravrock ff93277c0f Accessibility: Always provide a non-color indication that a spec is pending 2023-04-29 11:45:21 -07:00
Steve Gravrock 90741b3cee Accessibility: Improved contrast of version number and inactive tab links 2023-04-29 11:45:16 -07:00
Steve Gravrock d1de59f0ed Updated jasmine dev dependency 2023-04-29 10:53:03 -07:00
Steve Gravrock 73f8e001ad Bump version to 5.0.0-beta-0 2023-04-29 10:10:05 -07:00
Steve Gravrock 390cc45af2 Dropped support for Node 16
Node 16 will reach EOL no later than a few months after Jasmine 5 is
released. Experience with Node 12 and Node 14 has shown that our
dependencies, especially dev dependencies, move on from past-EOL Node
versions fairly quickly. That can make it difficult to continue supporting
them. Since long term support for past EOL Node versions is a non-goal and
many users expect that Node versions will only be dropped in major
releases, it's better to drop it in 5.0.
2023-04-27 19:32:18 -07:00
Steve Gravrock 33118ac6e2 Merge branch 'main' into 5.0 2023-04-27 19:22:30 -07:00
Steve Gravrock 31ff9a300c Added Node 20 to supported environments 2023-04-22 08:12:49 -07:00
Steve Gravrock 5cc739d879 Bump version to 5.0.0-alpha.1 2023-04-08 13:02:21 -07:00
Steve Gravrock 1e7f07259e API docs for parallel support things that jasmine-npm uses 2023-04-08 12:37:40 -07:00
Steve Gravrock c36a5cfd96 Parallel: Cleaner interface for reporter dispatching
This gets jasmine-npm out of having to deal with QueueRunner, GlobalErrors,
and ReportDispatcher directly.
2023-04-08 11:41:15 -07:00
Steve Gravrock 299fd1f770 Removed unnecessary TODO 2023-03-25 11:21:04 -07:00
Steve Gravrock 656427d328 Parallel: Disallow calls to Env#config from spec and helper files
Such configuration changes only affect one worker, which is almost certainly
not the intent.
2023-03-25 11:16:54 -07:00
Steve Gravrock 621522fdd4 Updated Glob 2023-03-21 22:35:15 -07:00
Steve Gravrock 6e3589bf52 Updated most dev dependencies 2023-03-21 22:33:24 -07:00
74 changed files with 2368 additions and 650 deletions
+38 -23
View File
@@ -4,16 +4,18 @@
version: 2.1 version: 2.1
executors: executors:
node22:
docker:
- image: cimg/node:22.0.0
working_directory: ~/workspace
node20:
docker:
- image: cimg/node:20.0.0
working_directory: ~/workspace
node18: node18:
docker: docker:
- image: cimg/node:18.0.0 - image: cimg/node:18.0.0
working_directory: ~/workspace working_directory: ~/workspace
node16:
docker:
# Oldest version with reliable support for error cause property,
# which jasmine-npm uses.
- image: cimg/node:16.14.0
working_directory: ~/workspace
jobs: jobs:
build: build:
@@ -62,7 +64,7 @@ jobs:
command: npx grunt execSpecsInParallel command: npx grunt execSpecsInParallel
test_browsers: &test_browsers test_browsers: &test_browsers
executor: node16 executor: node18
steps: steps:
- attach_workspace: - attach_workspace:
at: . at: .
@@ -82,7 +84,7 @@ jobs:
# cleanly if we kill it from a different step than it started in. # cleanly if we kill it from a different step than it started in.
export PATH=$PATH:$HOME/workspace/bin export PATH=$PATH:$HOME/workspace/bin
export SAUCE_TUNNEL_IDENTIFIER=$CIRCLE_BUILD_NUM export SAUCE_TUNNEL_IDENTIFIER=$CIRCLE_WORKFLOW_JOB_ID
scripts/start-sauce-connect sauce-pidfile scripts/start-sauce-connect sauce-pidfile
set +o errexit set +o errexit
scripts/run-all-browsers scripts/run-all-browsers
@@ -96,35 +98,48 @@ workflows:
push: push:
jobs: jobs:
- build:
executor: node22
name: build_node_22
- build:
executor: node20
name: build_node_20
- build: - build:
executor: node18 executor: node18
name: build_node_18 name: build_node_18
- build: - test_node:
executor: node16 executor: node22
name: build_node_16 name: test_node_22
requires:
- build_node_22
- test_node:
executor: node20
name: test_node_20
requires:
- build_node_20
- test_node: - test_node:
executor: node18 executor: node18
name: test_node_18 name: test_node_18
requires: requires:
- build_node_18 - build_node_18
- test_node:
executor: node16
name: test_node_16
requires:
- build_node_16
- test_parallel:
executor: node16
name: test_parallel_node_16
requires:
- build_node_16
- test_parallel: - test_parallel:
executor: node18 executor: node18
name: test_parallel_node_18 name: test_parallel_node_18
requires: requires:
- build_node_18 - build_node_18
- test_parallel:
executor: node22
name: test_parallel_node_22
requires:
- build_node_22
- test_parallel:
executor: node20
name: test_parallel_node_20
requires:
- build_node_20
- test_browsers: - test_browsers:
requires: requires:
- build_node_16 - build_node_18
filters: filters:
branches: branches:
ignore: /pull\/.*/ # Don't run on pull requests. ignore: /pull\/.*/ # Don't run on pull requests.
+47
View File
@@ -0,0 +1,47 @@
{
"extends": [
"plugin:compat/recommended"
],
"env": {
"browser": true,
"node": true,
"es2017": true
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"curly": "error",
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"no-unused-vars": [
"error",
{
"args": "none"
}
],
"no-implicit-globals": "error",
"block-spacing": "error",
"func-call-spacing": [
"error",
"never"
],
"key-spacing": "error",
"no-tabs": "error",
"no-trailing-spaces": "error",
"no-whitespace-before-property": "error",
"semi": [
"error",
"always"
],
"space-before-blocks": "error",
"no-eval": "error",
"no-var": "error",
"no-debugger": "error"
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
name: Bug Report name: Bug Report
description: I think I've found a bug in Jasmine description: I think I've found a bug in Jasmine
labels: ["unconfirmed bug"] labels: ["bug report"]
body: body:
- type: markdown - type: markdown
attributes: attributes:
+3
View File
@@ -0,0 +1,3 @@
{
"singleQuote": true
}
+1
View File
@@ -1,4 +1,5 @@
Copyright (c) 2008-2019 Pivotal Labs Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-2023 The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the
+11 -12
View File
@@ -1,13 +1,10 @@
<a name="README">[<img src="https://rawgithub.com/jasmine/jasmine/main/images/jasmine-horizontal.svg" width="400px" />](http://jasmine.github.io)</a> <a name="README">[<img src="https://rawgithub.com/jasmine/jasmine/main/images/jasmine-horizontal.svg" width="400px" />](http://jasmine.github.io)</a>
[![Build Status](https://circleci.com/gh/jasmine/jasmine.svg?style=shield)](https://circleci.com/gh/jasmine/jasmine)
[![Open Source Helpers](https://www.codetriage.com/jasmine/jasmine/badges/users.svg)](https://www.codetriage.com/jasmine/jasmine)
# A JavaScript Testing Framework # A JavaScript Testing Framework
Jasmine is a Behavior Driven Development testing framework for JavaScript. It does not rely on browsers, DOM, or any JavaScript framework. Thus it's suited for websites, [Node.js](http://nodejs.org) projects, or anywhere that JavaScript can run. Jasmine is a Behavior Driven Development testing framework for JavaScript. It does not rely on browsers, DOM, or any JavaScript framework. Thus it's suited for websites, [Node.js](http://nodejs.org) projects, or anywhere that JavaScript can run.
Upgrading from Jasmine 3.x? Check out the [upgrade guide](https://jasmine.github.io/tutorials/upgrading_to_Jasmine_4.0). Upgrading from Jasmine 4.x? Check out the [upgrade guide](https://jasmine.github.io/tutorials/upgrading_to_Jasmine_5.0).
## Contributing ## Contributing
@@ -30,13 +27,13 @@ for information on writing specs, and [the FAQ](https://jasmine.github.io/pages/
Jasmine tests itself across popular browsers (Safari, Chrome, Firefox, and Jasmine tests itself across popular browsers (Safari, Chrome, Firefox, and
Microsoft Edge) as well as Node. Microsoft Edge) as well as Node.
| Environment | Supported versions | | Environment | Supported versions |
|-------------------|--------------------| |-------------------|---------------------|
| Node | 16.14-16.19, 18 | | Node | 18, 20, 22 |
| Safari | 15-16 | | Safari | 15-17 |
| Chrome | Evergreen | | Chrome | Evergreen |
| Firefox | Evergreen, 102 | | Firefox | Evergreen, 102, 115 |
| Edge | Evergreen | | Edge | Evergreen |
For evergreen browsers, each version of Jasmine is tested against the version of the browser that is available to us For evergreen browsers, each version of Jasmine is tested against the version of the browser that is available to us
at the time of release. Other browsers, as well as older & newer versions of some supported browsers, are likely to work. at the time of release. Other browsers, as well as older & newer versions of some supported browsers, are likely to work.
@@ -58,4 +55,6 @@ To find out what environments work with a particular Jasmine release, see the [r
* [Christian Williams](mailto:antixian666@gmail.com) * [Christian Williams](mailto:antixian666@gmail.com)
* Sheel Choksi * Sheel Choksi
Copyright (c) 2008-2022 Jasmine Maintainers. This software is licensed under the [MIT License](https://github.com/jasmine/jasmine/blob/main/MIT.LICENSE). Copyright (c) 2008-2019 Pivotal Labs<br>
Copyright (c) 2008-2023 The Jasmine developers<br>
This software is licensed under the [MIT License](https://github.com/jasmine/jasmine/blob/main/LICENSE).
+1 -1
View File
@@ -11,7 +11,7 @@ module.exports = {
}, },
files: [ files: [
{ src: [ root("MIT.LICENSE") ] }, { src: [ root("LICENSE") ] },
{ {
src: [ "jasmine_favicon.png"], src: [ "jasmine_favicon.png"],
dest: standaloneLibDir, dest: standaloneLibDir,
+2 -1
View File
@@ -1,5 +1,6 @@
/* /*
Copyright (c) 2008-<%= currentYear %> Pivotal Labs Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-<%= currentYear %> The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the
+12 -9
View File
@@ -6,11 +6,11 @@
const jasmineRequire = require('./jasmine-core/jasmine.js'); const jasmineRequire = require('./jasmine-core/jasmine.js');
module.exports = jasmineRequire; module.exports = jasmineRequire;
const bootOnce = (function() { const boot = (function() {
let jasmine, jasmineInterface; let jasmine, jasmineInterface;
return function bootWithoutGlobals() { return function bootWithoutGlobals(reinitialize) {
if (!jasmineInterface) { if (!jasmineInterface || reinitialize === true) {
jasmine = jasmineRequire.core(jasmineRequire); jasmine = jasmineRequire.core(jasmineRequire);
const env = jasmine.getEnv({ suppressLoadErrors: true }); const env = jasmine.getEnv({ suppressLoadErrors: true });
jasmineInterface = jasmineRequire.interface(jasmine, env); jasmineInterface = jasmineRequire.interface(jasmine, env);
@@ -22,12 +22,14 @@ const bootOnce = (function() {
/** /**
* Boots a copy of Jasmine and returns an object as described in {@link jasmine}. * Boots a copy of Jasmine and returns an object as described in {@link jasmine}.
* If boot is called multiple times, the same object is returned every time. * If boot is called multiple times, the same object is returned every time
* unless true is passed.
* @param {boolean} [reinitialize=false] Whether to create a new copy of Jasmine if one already exists
* @type {function} * @type {function}
* @return {jasmine} * @return {jasmine}
*/ */
module.exports.boot = function() { module.exports.boot = function(reinitialize) {
const {jasmine, jasmineInterface} = bootOnce(); const {jasmine, jasmineInterface} = boot(reinitialize);
for (const k in jasmineInterface) { for (const k in jasmineInterface) {
global[k] = jasmineInterface[k]; global[k] = jasmineInterface[k];
@@ -39,13 +41,14 @@ module.exports.boot = function() {
/** /**
* Boots a copy of Jasmine and returns an object containing the properties * Boots a copy of Jasmine and returns an object containing the properties
* that would normally be added to the global object. If noGlobals is called * that would normally be added to the global object. If noGlobals is called
* multiple times, the same object is returned every time. * multiple times, the same object is returned every time unless true is passed.
* *
* @param {boolean} [reinitialize=false] Whether to create a new copy of Jasmine if one already exists
* @example * @example
* const {describe, beforeEach, it, expect, jasmine} = require('jasmine-core').noGlobals(); * const {describe, beforeEach, it, expect, jasmine} = require('jasmine-core').noGlobals();
*/ */
module.exports.noGlobals = function() { module.exports.noGlobals = function(reinitialize) {
const {jasmineInterface} = bootOnce(); const {jasmineInterface} = boot(reinitialize);
return jasmineInterface; return jasmineInterface;
}; };
+2 -1
View File
@@ -1,5 +1,6 @@
/* /*
Copyright (c) 2008-2023 Pivotal Labs Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-2024 The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the
+2 -1
View File
@@ -1,5 +1,6 @@
/* /*
Copyright (c) 2008-2023 Pivotal Labs Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-2024 The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the
+14 -10
View File
@@ -1,5 +1,6 @@
/* /*
Copyright (c) 2008-2023 Pivotal Labs Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-2024 The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the
@@ -462,7 +463,11 @@ jasmineRequire.HtmlReporter = function(j$) {
'tr', 'tr',
{}, {},
createDom('td', {}, entry.timestamp.toString()), createDom('td', {}, entry.timestamp.toString()),
createDom('td', {}, entry.message) createDom(
'td',
{ className: 'jasmine-debug-log-msg' },
entry.message
)
) )
); );
}); });
@@ -530,14 +535,13 @@ jasmineRequire.HtmlReporter = function(j$) {
if (noExpectations(resultNode.result)) { if (noExpectations(resultNode.result)) {
specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription; specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
} }
if ( if (resultNode.result.status === 'pending') {
resultNode.result.status === 'pending' && if (resultNode.result.pendingReason !== '') {
resultNode.result.pendingReason !== '' specDescription +=
) { ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
specDescription = } else {
specDescription + specDescription += ' PENDING';
' PENDING WITH MESSAGE: ' + }
resultNode.result.pendingReason;
} }
specListNode.appendChild( specListNode.appendChild(
createDom( createDom(
+6 -5
View File
@@ -55,9 +55,6 @@ body {
position: fixed; position: fixed;
right: 100%; right: 100%;
} }
.jasmine_html-reporter .jasmine-version {
color: #aaa;
}
.jasmine_html-reporter .jasmine-banner { .jasmine_html-reporter .jasmine-banner {
margin-top: 14px; margin-top: 14px;
} }
@@ -169,10 +166,11 @@ body {
} }
.jasmine_html-reporter .jasmine-bar.jasmine-menu { .jasmine_html-reporter .jasmine-bar.jasmine-menu {
background-color: #fff; background-color: #fff;
color: #aaa; color: #000;
} }
.jasmine_html-reporter .jasmine-bar.jasmine-menu a { .jasmine_html-reporter .jasmine-bar.jasmine-menu a {
color: #333; color: blue;
text-decoration: underline;
} }
.jasmine_html-reporter .jasmine-bar a { .jasmine_html-reporter .jasmine-bar a {
color: white; color: white;
@@ -298,3 +296,6 @@ body {
.jasmine_html-reporter .jasmine-debug-log table, .jasmine_html-reporter .jasmine-debug-log th, .jasmine_html-reporter .jasmine-debug-log td { .jasmine_html-reporter .jasmine-debug-log table, .jasmine_html-reporter .jasmine-debug-log th, .jasmine_html-reporter .jasmine-debug-log td {
border: 1px solid #ddd; border: 1px solid #ddd;
} }
.jasmine_html-reporter .jasmine-debug-log .jasmine-debug-log-msg {
white-space: pre;
}
File diff suppressed because it is too large Load Diff
+10 -58
View File
@@ -1,7 +1,7 @@
{ {
"name": "jasmine-core", "name": "jasmine-core",
"license": "MIT", "license": "MIT",
"version": "5.0.0-alpha.0", "version": "5.3.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/jasmine/jasmine.git" "url": "https://github.com/jasmine/jasmine.git"
@@ -27,79 +27,31 @@
"homepage": "https://jasmine.github.io", "homepage": "https://jasmine.github.io",
"main": "./lib/jasmine-core.js", "main": "./lib/jasmine-core.js",
"files": [ "files": [
"MIT.LICENSE", "LICENSE",
"README.md", "README.md",
"images/*.{png,svg}", "images/*.{png,svg}",
"lib/**/*.{js,css}", "lib/**/*.{js,css}",
"package.json" "package.json"
], ],
"devDependencies": { "devDependencies": {
"eslint": "^7.32.0", "eslint": "^8.36.0",
"eslint-plugin-compat": ">=4.0.0 <4.1.0", "eslint-plugin-compat": "^4.0.0",
"glob": "^7.2.0", "glob": "^10.2.3",
"grunt": ">=1.0.4 <1.6.0", "grunt": "^1.0.4",
"grunt-cli": "^1.3.2", "grunt-cli": "^1.3.2",
"grunt-contrib-compress": "^2.0.0", "grunt-contrib-compress": "^2.0.0",
"grunt-contrib-concat": "^2.0.0", "grunt-contrib-concat": "^2.0.0",
"grunt-css-url-embed": "^1.11.1", "grunt-css-url-embed": "^1.11.1",
"grunt-sass": "^3.0.2", "grunt-sass": "^3.0.2",
"jasmine": "github:jasmine/jasmine-npm#5.0", "jasmine": "^5.0.0",
"jasmine-browser-runner": "^1.0.0", "jasmine-browser-runner": "github:jasmine/jasmine-browser-runner",
"jsdom": "^19.0.0", "jsdom": "^22.0.0",
"load-grunt-tasks": "^5.1.0", "load-grunt-tasks": "^5.1.0",
"prettier": "1.17.1", "prettier": "1.17.1",
"sass": "1.58.3", "sass": "^1.58.3",
"shelljs": "^0.8.3", "shelljs": "^0.8.3",
"temp": "^0.9.0" "temp": "^0.9.0"
}, },
"prettier": {
"singleQuote": true
},
"eslintConfig": {
"extends": [
"plugin:compat/recommended"
],
"env": {
"browser": true,
"node": true,
"es2017": true
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"no-unused-vars": [
"error",
{
"args": "none"
}
],
"no-implicit-globals": "error",
"block-spacing": "error",
"func-call-spacing": [
"error",
"never"
],
"key-spacing": "error",
"no-tabs": "error",
"no-trailing-spaces": "error",
"no-whitespace-before-property": "error",
"semi": [
"error",
"always"
],
"space-before-blocks": "error",
"no-eval": "error",
"no-var": "error"
}
},
"browserslist": [ "browserslist": [
"Safari >= 15", "Safari >= 15",
"Firefox >= 102", "Firefox >= 102",
+51
View File
@@ -0,0 +1,51 @@
# Jasmine Core 4.6.1 Release Notes
## Summary
This is a one-time backport of bug fixes from 5.x, for the benefit of Karma
users who may not be aware that they're still using 4.x.
No further 4.x releases are planned. If possible, you should upgrade to the
latest 5.x instead of 4.6.1. If you're using Karma, you can do this by
installing jasmine-core 5.x and adding an override to package.json:
```
{
// ...
"overrides": {
"karma-jasmine": {
"jasmine-core": "^5.0.0"
}
}
}
```
## Bug Fixes
* Removed unnecessary throw when building stack trace
* Fixed error when formatting Error object with non-Error cause property
Merges [#2013](https://github.com/jasmine/jasmine/pull/2013) from @angrycat9000.
Fixes [#2011](https://github.com/jasmine/jasmine/issues/2011).
* Accessibility: Always provide a non-color indication that a spec is pending
* Accessibility: Improved contrast of version number and inactive tab links
## Supported environments
jasmine-core 4.6.1 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 12.17+, 14, 16, 18 |
| Safari | 14-16 |
| Chrome | 125 |
| Firefox | 91, 102, 126 |
| Edge | 124 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
+39
View File
@@ -0,0 +1,39 @@
# Jasmine Core 5.0.0-alpha.1 Release Notes
## Summary
This release provides improved support for parallel execution via the `jasmine`
package. Please see its release notes and the
[parallel documentation](https://jasmine.github.io/tutorials/running_specs_in_parallel)
for more information.
## New features and bug fixes
* Parallel: Cleaner interface for reporter dispatching
* When Env#config is called from spec and helper files in parallel mode, throw
an error rather than behaving unpredictably.
## Documentation improvements
* API reference docs for parallel support APIs that jasmine-npm uses
## Internal improvements
* Updated dev dependencies
## Supported environments
jasmine-core 5.0.0-alpha.1 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 16.14+, 18 |
| Safari | 15-16 |
| Chrome | 112 |
| Firefox | 102, 111 |
| Edge | 111 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
+28
View File
@@ -0,0 +1,28 @@
# Jasmine Core 5.0.0-beta.0 Release Notes
This release supports the 5.0.0-beta-0 release of the `jasmine` package.
## Breaking changes
* Dropped support for Node 16
## New features
* Added support for Node 20
## Supported environments
jasmine-core 5.0.0-beta.0 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20 |
| Safari | 15-16 |
| Chrome | 112 |
| Firefox | 102, 112 |
| Edge | 112 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
+55
View File
@@ -0,0 +1,55 @@
# Jasmine Core 5.0.0 Release Notes
## Summary
This is a major release that includes breaking changes. It primarily adds
support for parallel execution in Node via the `jasmine` package. Most users
should be able to upgrade without changes, but please read the list of breaking
changes below and see the [migration guide](https://jasmine.github.io/tutorials/upgrading_to_Jasmine_5.0)
for more information.
## Breaking changes
* Dropped support for Node 12, 14, and 16
* Dropped support for Safari 14 and Firefox 91
* Made Env#execute async and removed the callback parameter
* Global errors are detected via addEventListener rather than setting window.onerror
* The `boot` function exported by the core module returns the same object
every time it's called.
* Removed node_boot.js. Use the exported `boot` function instead.
## New features
* Support for parallel execution in Node via the `jasmine` package
See the [parallel guide](https://jasmine.github.io/tutorials/running_specs_in_parallel)
for more information.
* Added Node 20 to supported environments
## Bug fixes
* Accessibility: Always provide a non-color indication that a spec is pending
* Accessibility: Improved contrast of version number and inactive tab links
* Uninstall the global error handler at the end of env execution
## Internal improvements
* Updated dev dependencies
* Dogfood parallel execution feature in CI
## Supported environments
jasmine-core 5.0.0 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20 |
| Safari | 15-16 |
| Chrome | 113 |
| Firefox | 102, 113 |
| Edge | 113 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
+25
View File
@@ -0,0 +1,25 @@
# Jasmine Core 5.0.1 Release Notes
## Changes
* Optionally restore the pre-5.0 behavior of boot() always creating a new instance
This is needed by jasmine-npm (and likely other tools like it) that may
need to create and use multiple envs in sequence.
## Supported environments
jasmine-core 5.0.1 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20 |
| Safari | 15-16 |
| Chrome | 114 |
| Firefox | 102, 113 |
| Edge | 113 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
+39
View File
@@ -0,0 +1,39 @@
# Jasmine Core 5.1.0 Release Notes
## Changes
* Exclude inherited Error properties from stack trace
* Fixed error when formatting Error object with non-Error cause property
Merges [#2013](https://github.com/jasmine/jasmine/pull/2013) from @angrycat9000.
Fixes [#2011](https://github.com/jasmine/jasmine/issues/2011).
* Added `throwUnless` and `throwUnlessAsync`
These are similar to `expect` and `expectAsync` except that they throw
exceptions rather than recording matcher failures as spec/suite failures.
They're intended to support using Jasmine matchers in [testing-library's](https://testing-library.com/)
`waitFor`, and also provide a way to integration-test custom matchers.
Fixes [#2003](https://github.com/jasmine/jasmine/issues/2003).
Fixes [#1980](https://github.com/jasmine/jasmine/issues/1980).
## Supported environments
jasmine-core 5.1.0 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20 |
| Safari | 15-16 |
| Chrome | 114 |
| Firefox | 102, 113 |
| Edge | 113 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
+28
View File
@@ -0,0 +1,28 @@
# Jasmine Core 5.1.1 Release Notes
## Bug Fixes
* Fixed global variable leak in the main process when running in parallel mode
* Removed unnecessary throw when building expectation results
## Documentation Improvements
* Improved jsdocs for originalFn argument to createSpy
* Link to 5.0 upgrade guide in README, not 4.0
## Supported environments
jasmine-core 5.1.1 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20 |
| Safari | 15-16 |
| Chrome | 116 |
| Firefox | 102, 116 |
| Edge | 115 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
+27
View File
@@ -0,0 +1,27 @@
# Jasmine Core 5.1.2 Release Notes
## Bug Fixes
* Fixed `throwUnlessAsync`
* Fixes [#2026](https://github.com/jasmine/jasmine/issues/2026)
# Documentation improvements
* Added Safari 17 to supported browsers
* Added Firefox 115 (current ESR) to supported browsers
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20 |
| Safari | 15-17 |
| Chrome | 121 |
| Firefox | 102, 115, 122 |
| Edge | 121 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
+35
View File
@@ -0,0 +1,35 @@
# Jasmine Core 5.2.0 Release Notes
## Bug Fixes
* Fixed stack trace filtering in FF when the developer tools are open
* Fixed handling of browser `error` events with message but no error
## New Features
* Improved the error message of the toHaveSize matcher.
* Merges [#2033](https://github.com/jasmine/jasmine/pull/2033) from @stephanreiter
* HTML reporter: show debug logs with white-space: pre
## Documentation improvements
* Improved discoverability of asymmetric equality testers
* Added an example for withContext()
* Clarified spyOnGlobalErrorsAsync API docs
* Added Node 22 to supported environments
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20, 22 |
| Safari | 15-17 |
| Chrome | 126 |
| Firefox | 102, 115, 128 |
| Edge | 126 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
+35
View File
@@ -0,0 +1,35 @@
# Jasmine Core 5.3.0 Release Notes
## Changes
* Improved performance in Safari
* Merges [#2040](https://github.com/jasmine/jasmine/pull/2040) from @dcsaszar
* Fixes [#2008](https://github.com/jasmine/jasmine/issues/2008)
* Improved performance in Playwright Webkit on Windows
* Merges [#2034](https://github.com/jasmine/jasmine/pull/2034) from @m-akinc
* Throw if spying has no effect, as when spying on localStorage methods in Firefox and Safari 17
* See [#2036](https://github.com/jasmine/jasmine/issues/2036) and [#2007](https://github.com/jasmine/jasmine/issues/2007)
## Documentation improvements
* Added API reference for reporter capabilities
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20, 22 |
| Safari | 15-17 |
| Chrome | 128 |
| Firefox | 102, 115, 130 |
| Edge | 128 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
+15 -2
View File
@@ -3,6 +3,7 @@
run_browser() { run_browser() {
browser=$1 browser=$1
version=$2 version=$2
os="$3"
description="$browser $version" description="$browser $version"
if [ $version = "latest" ]; then if [ $version = "latest" ]; then
version="" version=""
@@ -12,7 +13,7 @@ run_browser() {
echo echo
echo "Running $description" echo "Running $description"
echo echo
USE_SAUCE=true JASMINE_BROWSER=$browser SAUCE_BROWSER_VERSION=$version npm run ci USE_SAUCE=true JASMINE_BROWSER=$browser SAUCE_BROWSER_VERSION=$version SAUCE_OS="$os" npm run ci
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
echo "PASS: $description" >> "$passfile" echo "PASS: $description" >> "$passfile"
@@ -23,9 +24,21 @@ run_browser() {
passfile=`mktemp -t jasmine-results.XXXXXX` || exit 1 passfile=`mktemp -t jasmine-results.XXXXXX` || exit 1
failfile=`mktemp -t jasmine-results.XXXXXX` || exit 1 failfile=`mktemp -t jasmine-results.XXXXXX` || exit 1
run_browser chrome latest
# As of 2023-09-30, Sauce Connect doesn't work with the latest Chrome version
# on the default Linux. Run on Mac OS instead. The OS specification may need to
# be updated or removed when new Chrome versions stop being available on Mac OS
# 12, although historically this has taken several major OS versions.
# See <https://saucelabs.com/products/supported-browsers-devices>.
# On Saucelabs, the test suite frequently runs ~30s slower on Mac OS than it
# does on Linux, so it's probably worth removing the OS specification once Sauce
# Connect works with Chrome latest on Linux again.
run_browser chrome latest "macOS 12"
run_browser firefox latest run_browser firefox latest
run_browser firefox 115
run_browser firefox 102 run_browser firefox 102
run_browser safari 17
run_browser safari 16 run_browser safari 16
run_browser safari 15 run_browser safari 15
run_browser MicrosoftEdge latest run_browser MicrosoftEdge latest
+41
View File
@@ -20,6 +20,47 @@ describe('ClearStack', function() {
MessageChannel: fakeMessageChannel MessageChannel: fakeMessageChannel
}; };
}); });
it('uses MessageChannel to reduce setTimeout clamping', function() {
const fakeChannel = fakeMessageChannel();
spyOn(fakeChannel.port2, 'postMessage');
const queueMicrotask = jasmine.createSpy('queueMicrotask');
const global = {
navigator: {
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.0.8 (KHTML, like Gecko) Version/15.1 Safari/605.0.8'
},
MessageChannel: function() {
return fakeChannel;
},
queueMicrotask
};
const clearStack = jasmineUnderTest.getClearStack(global);
for (let i = 0; i < 9; i++) {
clearStack(function() {});
}
expect(fakeChannel.port2.postMessage).not.toHaveBeenCalled();
clearStack(function() {});
expect(fakeChannel.port2.postMessage).toHaveBeenCalledTimes(1);
});
});
describe("in WebKit (Playwright's build for Windows)", function() {
usesQueueMicrotaskWithSetTimeout(function() {
return {
navigator: {
userAgent:
'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/605.1.15 (KHTML, like Gecko)'
},
// queueMicrotask should be used even though MessageChannel is present
MessageChannel: fakeMessageChannel
};
});
}); });
describe('in browsers other than Safari', function() { describe('in browsers other than Safari', function() {
+16 -2
View File
@@ -758,16 +758,16 @@ describe('Env', function() {
describe('In parallel mode', function() { describe('In parallel mode', function() {
it('rejects if random is set to false', async function() { it('rejects if random is set to false', async function() {
env.setParallelLoadingState('specs');
env.configure({ random: false }); env.configure({ random: false });
env.setParallelLoadingState('specs');
await expectAsync(env.execute()).toBeRejectedWithError( await expectAsync(env.execute()).toBeRejectedWithError(
'Randomization cannot be disabled in parallel mode' 'Randomization cannot be disabled in parallel mode'
); );
}); });
it('rejects if seed is set', async function() { it('rejects if seed is set', async function() {
env.setParallelLoadingState('specs');
env.configure({ seed: 1234 }); env.configure({ seed: 1234 });
env.setParallelLoadingState('specs');
await expectAsync(env.execute()).toBeRejectedWithError( await expectAsync(env.execute()).toBeRejectedWithError(
'Random seed cannot be set in parallel mode' 'Random seed cannot be set in parallel mode'
); );
@@ -817,4 +817,18 @@ describe('Env', function() {
}).toThrowError('Reporters cannot be removed via Env in parallel mode'); }).toThrowError('Reporters cannot be removed via Env in parallel mode');
}); });
}); });
describe('#configure', function() {
it('throws when called in parallel mode', function() {
env.setParallelLoadingState('helpers');
expect(function() {
env.configure({});
}).toThrowError('Jasmine cannot be configured via Env in parallel mode');
env.setParallelLoadingState('specs');
expect(function() {
env.configure({});
}).toThrowError('Jasmine cannot be configured via Env in parallel mode');
});
});
}); });
+55 -1
View File
@@ -153,12 +153,33 @@ describe('ExceptionFormatter', function() {
); );
}); });
it('filters Jasmine stack frames with Firefox async annotations', function() {
const error = {
stack:
'http://localhost:8888/__spec__/core/UtilSpec.js:115:28\n' +
'promise callback*fn1@http://localhost:8888/__jasmine__/jasmine.js:4320:27\n' +
'setTimeout handler*fn2@http://localhost:8888/__jasmine__/jasmine.js:4320:27\n' +
'http://localhost:8888/__spec__/core/UtilSpec.js:115:28'
};
const subject = new jasmineUnderTest.ExceptionFormatter({
jasmineFile: 'http://localhost:8888/__jasmine__/jasmine.js'
});
const result = subject.stack(error);
expect(result).toEqual(
'http://localhost:8888/__spec__/core/UtilSpec.js:115:28\n' +
'<Jasmine>\n' +
'http://localhost:8888/__spec__/core/UtilSpec.js:115:28'
);
});
it('filters Jasmine stack frames in this environment', function() { it('filters Jasmine stack frames in this environment', function() {
const error = new Error('an error'); const error = new Error('an error');
const subject = new jasmineUnderTest.ExceptionFormatter({ const subject = new jasmineUnderTest.ExceptionFormatter({
jasmineFile: jasmine.util.jasmineFile() jasmineFile: jasmine.util.jasmineFile()
}); });
const result = subject.stack(error); const result = subject.stack(error);
jasmine.debugLog('Original stack trace: ' + error.stack);
jasmine.debugLog('Filtered stack trace: ' + result);
const lines = result.split('\n'); const lines = result.split('\n');
if (lines[0].match(/an error/)) { if (lines[0].match(/an error/)) {
@@ -197,7 +218,7 @@ describe('ExceptionFormatter', function() {
expect(new jasmineUnderTest.ExceptionFormatter().stack()).toBeNull(); expect(new jasmineUnderTest.ExceptionFormatter().stack()).toBeNull();
}); });
it('includes error properties in stack', function() { it("includes the error's own properties in stack", function() {
const error = new Error('an error'); const error = new Error('an error');
error.someProperty = 'hello there'; error.someProperty = 'hello there';
@@ -206,6 +227,19 @@ describe('ExceptionFormatter', function() {
expect(result).toMatch(/error properties:.*someProperty.*hello there/); expect(result).toMatch(/error properties:.*someProperty.*hello there/);
}); });
it('does not include inherited error properties', function() {
function CustomError(msg) {
Error.call(this, msg);
}
CustomError.prototype = new Error();
CustomError.prototype.anInheritedProp = 'something';
const error = new CustomError('nope');
const result = new jasmineUnderTest.ExceptionFormatter().stack(error);
expect(result).not.toContain('anInheritedProp');
});
describe('When omitMessage is true', function() { describe('When omitMessage is true', function() {
it('filters the message from V8-style stack traces', function() { it('filters the message from V8-style stack traces', function() {
const error = { const error = {
@@ -291,6 +325,26 @@ describe('ExceptionFormatter', function() {
.withContext('first root cause stack frame') .withContext('first root cause stack frame')
.toContain('ExceptionFormatterSpec.js'); .toContain('ExceptionFormatterSpec.js');
}); });
it('does not throw if cause is a non Error', function() {
const formatter = new jasmineUnderTest.ExceptionFormatter();
expect(function() {
formatter.stack(
new Error('error', {
cause: function() {}
})
);
}).not.toThrowError();
expect(function() {
formatter.stack(
new Error('error', {
cause: 'another error'
})
);
}).not.toThrowError();
});
}); });
}); });
}); });
+56
View File
@@ -328,6 +328,62 @@ describe('GlobalErrors', function() {
}); });
}); });
describe('Reporting uncaught exceptions in node.js', function() {
it('prepends a descriptive message when the error is not an `Error`', function() {
const fakeGlobal = {
process: {
on: jasmine.createSpy('process.on'),
removeListener: function() {},
listeners: function() {
return [];
},
removeAllListeners: function() {}
}
};
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
uncaughtExceptionListener(fakeGlobal)(17);
expect(handler).toHaveBeenCalledWith(new Error('Uncaught exception: 17'));
});
it('substitutes a descriptive message when the error is falsy', function() {
const fakeGlobal = {
process: {
on: jasmine.createSpy('process.on'),
removeListener: function() {},
listeners: function() {
return [];
},
removeAllListeners: function() {}
}
};
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
uncaughtExceptionListener(fakeGlobal)();
expect(handler).toHaveBeenCalledWith(
new Error('Uncaught exception with no error or message')
);
});
function uncaughtExceptionListener(global) {
// Grab the right listener
expect(global.process.on.calls.argsFor(0)[0]).toEqual(
'uncaughtException'
);
return global.process.on.calls.argsFor(0)[1];
}
});
describe('#setOverrideListener', function() { describe('#setOverrideListener', function() {
it('overrides the existing handlers in browsers until removed', function() { it('overrides the existing handlers in browsers until removed', function() {
const fakeGlobal = browserGlobal(); const fakeGlobal = browserGlobal();
+176
View File
@@ -0,0 +1,176 @@
describe('ParallelReportDispatcher', function() {
it('dispatches the standard reporter events', async function() {
const subject = new jasmineUnderTest.ParallelReportDispatcher(() => {}, {
globalErrors: mockGlobalErrors()
});
const events = [
'jasmineStarted',
'jasmineDone',
'suiteStarted',
'suiteDone',
'specStarted',
'specDone'
];
const reporter = jasmine.createSpyObj('reporter', events);
subject.addReporter(reporter);
for (const eventName of events) {
const payload = { payloadFor: eventName };
await subject[eventName](payload);
expect(reporter[eventName]).toHaveBeenCalledWith(payload);
}
});
it('installs and uninstalls the global error handler', function() {
const globalErrors = mockGlobalErrors();
const subject = new jasmineUnderTest.ParallelReportDispatcher(() => {}, {
globalErrors
});
subject.installGlobalErrors();
expect(globalErrors.install).toHaveBeenCalled();
subject.uninstallGlobalErrors();
expect(globalErrors.uninstall).toHaveBeenCalled();
});
it('handles global errors from async reporters', async function() {
const globalErrors = mockGlobalErrors();
const onError = jasmine.createSpy('onError');
const subject = new jasmineUnderTest.ParallelReportDispatcher(onError, {
globalErrors
});
const reporter = jasmine.createSpyObj('reporter', [
'jasmineStarted',
'jasmineDone'
]);
let resolveStarted;
reporter.jasmineStarted.and.callFake(function() {
return new Promise(function(res) {
resolveStarted = res;
});
});
subject.addReporter(reporter);
const promise = subject.jasmineStarted({});
expect(globalErrors.pushListener).toHaveBeenCalled();
expect(globalErrors.popListener).not.toHaveBeenCalled();
const error = new Error('nope');
globalErrors.pushListener.calls.argsFor(0)[0](error);
expect(onError).toHaveBeenCalledWith(error);
resolveStarted();
await promise;
expect(globalErrors.popListener).toHaveBeenCalled();
});
it('handles done(error) from callback-style async reporters', function() {
const globalErrors = mockGlobalErrors();
const onError = jasmine.createSpy('onError');
const subject = new jasmineUnderTest.ParallelReportDispatcher(onError, {
globalErrors
});
const reporter = jasmine.createSpyObj('reporter', [
'jasmineStarted',
'jasmineDone'
]);
let callback;
reporter.jasmineStarted = function(event, cb) {
callback = cb;
};
subject.addReporter(reporter);
subject.jasmineStarted({});
expect(callback).toBeInstanceOf(Function);
const error = new Error('nope');
callback(error);
expect(onError).toHaveBeenCalledWith(error);
});
it('handles done.fail() from callback-style async reporters', function() {
const globalErrors = mockGlobalErrors();
const onError = jasmine.createSpy('onError');
const subject = new jasmineUnderTest.ParallelReportDispatcher(onError, {
globalErrors
});
const reporter = jasmine.createSpyObj('reporter', [
'jasmineStarted',
'jasmineDone'
]);
let callback;
reporter.jasmineStarted = function(event, cb) {
callback = cb;
};
subject.addReporter(reporter);
subject.jasmineStarted({});
expect(callback).toBeInstanceOf(Function);
const error = new Error('nope');
callback.fail(error);
expect(onError).toHaveBeenCalledWith(error);
onError.calls.reset();
callback.fail();
expect(onError).toHaveBeenCalledWith(
new Error('A reporter called done.fail()')
);
});
it('handles errors due to mixed async style in reporters', async function() {
const globalErrors = mockGlobalErrors();
const onError = jasmine.createSpy('onError');
const subject = new jasmineUnderTest.ParallelReportDispatcher(onError, {
globalErrors
});
subject.addReporter({
async jasmineStarted(event, done) {
done();
}
});
await subject.jasmineStarted({});
expect(onError).toHaveBeenCalledWith(
new Error(
'An asynchronous before/it/after function took a done callback but also returned a promise. Either remove the done callback (recommended) or change the function to not return a promise.'
)
);
});
it('handles errors due to multiple done calls in reporters', async function() {
const globalErrors = mockGlobalErrors();
const onError = jasmine.createSpy('onError');
const subject = new jasmineUnderTest.ParallelReportDispatcher(onError, {
globalErrors
});
subject.addReporter({
jasmineStarted(event, done) {
done();
done();
}
});
await subject.jasmineStarted({});
expect(onError).toHaveBeenCalledWith(
new Error(
"An asynchronous reporter callback called its 'done' callback more than once."
)
);
});
function mockGlobalErrors() {
const globalErrors = jasmine.createSpyObj('globalErrors', [
'install',
'pushListener',
'popListener'
]);
globalErrors.install.and.callFake(function() {
globalErrors.uninstall = jasmine.createSpy('globalErrors.uninstall');
});
return globalErrors;
}
});
+73 -9
View File
@@ -459,6 +459,32 @@ describe('QueueRunner', function() {
expect(nextQueueableFn.fn).toHaveBeenCalled(); expect(nextQueueableFn.fn).toHaveBeenCalled();
}); });
it('handles a global error event with a message but no error', function() {
const queueableFn = {
// eslint-disable-next-line no-unused-vars
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 jasmineUnderTest.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() { it('handles exceptions thrown while waiting for the stack to clear', function() {
const queueableFn = { const queueableFn = {
fn: function(done) { fn: function(done) {
@@ -492,6 +518,40 @@ describe('QueueRunner', function() {
clearStack.calls.argsFor(0)[0](); clearStack.calls.argsFor(0)[0]();
expect(onException).toHaveBeenCalledWith(error); 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 jasmineUnderTest.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() { describe('with a function that returns a promise', function() {
@@ -595,11 +655,13 @@ describe('QueueRunner', function() {
queueRunner.execute(); queueRunner.execute();
expect(onException).toHaveBeenCalledWith( expect(onException).toHaveBeenCalledWith(
'An asynchronous ' + new Error(
'before/it/after function took a done callback but also returned a ' + 'An asynchronous ' +
'promise. ' + 'before/it/after function took a done callback but also returned a ' +
'Either remove the done callback (recommended) or change the function ' + 'promise. ' +
'to not return a promise.' 'Either remove the done callback (recommended) or change the function ' +
'to not return a promise.'
)
); );
}); });
@@ -615,10 +677,12 @@ describe('QueueRunner', function() {
queueRunner.execute(); queueRunner.execute();
expect(onException).toHaveBeenCalledWith( expect(onException).toHaveBeenCalledWith(
'An asynchronous ' + new Error(
'before/it/after function was defined with the async keyword but ' + 'An asynchronous ' +
'also took a done callback. Either remove the done callback ' + 'before/it/after function was defined with the async keyword but ' +
'(recommended) or remove the async keyword.' 'also took a done callback. Either remove the done callback ' +
'(recommended) or remove the async keyword.'
)
); );
}); });
}); });
+24
View File
@@ -94,6 +94,30 @@ describe('SpyRegistry', function() {
}).not.toThrowError(/is not declared writable or has no setter/); }).not.toThrowError(/is not declared writable or has no setter/);
}); });
it('throws if assigning to the property is a no-op', function() {
const scope = {};
function original() {
return 1;
}
Object.defineProperty(scope, 'myFunc', {
get() {
return original;
},
set() {}
});
const spyRegistry = new jasmineUnderTest.SpyRegistry({
createSpy: createSpy
});
expect(function() {
spyRegistry.spyOn(scope, 'myFunc');
}).toThrowError(
"<spyOn> : Can't spy on myFunc because assigning to it had no effect"
);
});
it('overrides the method on the object and returns the spy', function() { it('overrides the method on the object and returns the spy', function() {
const originalFunctionWasCalled = false, const originalFunctionWasCalled = false,
spyRegistry = new jasmineUnderTest.SpyRegistry({ spyRegistry = new jasmineUnderTest.SpyRegistry({
+4 -1
View File
@@ -179,7 +179,10 @@ describe('base helpers', function() {
f2 = jasmine.createSpy('setTimeout callback for ' + (max + 1)); f2 = jasmine.createSpy('setTimeout callback for ' + (max + 1));
// Suppress printing of TimeoutOverflowWarning in node // Suppress printing of TimeoutOverflowWarning in node
spyOn(console, 'error'); if (typeof process !== 'undefined' && process.emitWarning) {
spyOn(process, 'emitWarning'); // Node 22
}
spyOn(console, 'error'); // Node <22
let id = setTimeout(f1, max); let id = setTimeout(f1, max);
setTimeout(function() { setTimeout(function() {
+112 -2
View File
@@ -3,7 +3,7 @@ describe('Env integration', function() {
const isBrowser = typeof window !== 'undefined'; const isBrowser = typeof window !== 'undefined';
beforeEach(function() { beforeEach(function() {
jasmine.getEnv().registerIntegrationMatchers(); specHelpers.registerIntegrationMatchers();
env = new jasmineUnderTest.Env(); env = new jasmineUnderTest.Env();
}); });
@@ -1315,8 +1315,9 @@ describe('Env integration', function() {
'works with constructors when using callThrough spy strategy', 'works with constructors when using callThrough spy strategy',
function() { function() {
function MyClass(foo) { function MyClass(foo) {
if (!(this instanceof MyClass)) if (!(this instanceof MyClass)) {
throw new Error('You must use the new keyword.'); throw new Error('You must use the new keyword.');
}
this.foo = foo; this.foo = foo;
} }
const subject = { MyClass: MyClass }; const subject = { MyClass: MyClass };
@@ -4334,6 +4335,115 @@ describe('Env integration', function() {
} }
}); });
describe('throwUnless', function() {
it('throws when the matcher fails', async function() {
let thrown;
env.it('a spec', function() {
try {
env.throwUnless(1).toEqual(2);
} catch (e) {
thrown = e;
}
});
await env.execute();
expect(thrown).toBeInstanceOf(Error);
expect(thrown.passed).toEqual(false);
expect(thrown.matcherName).toEqual('toEqual');
expect(thrown.message).toEqual('Expected 1 to equal 2.');
expect(thrown.actual).toEqual(1);
expect(thrown.expected).toEqual(2);
});
it('does not throw when the matcher passes', async function() {
let threw = false;
env.it('a spec', function() {
try {
env.throwUnless(1).toEqual(1);
} catch (e) {
threw = true;
}
});
await env.execute();
expect(threw).toBe(false);
});
it('does not cause a failure if the error does not propagate back to jasmine', async function() {
env.it('a spec', function() {
try {
env.throwUnless(1).toEqual(2);
} catch (e) {}
});
const reporter = jasmine.createSpyObj('reporter', ['specDone']);
env.addReporter(reporter);
await env.execute();
expect(reporter.specDone).toHaveBeenCalledWith(
jasmine.objectContaining({ status: 'passed' })
);
});
});
describe('throwUnlessAsync', function() {
it('throws when the matcher fails', async function() {
const promise = Promise.resolve('a');
let thrown;
env.it('a spec', async function() {
try {
await env.throwUnlessAsync(promise).toBeResolvedTo('b');
} catch (e) {
thrown = e;
}
});
await env.execute();
expect(thrown).toBeInstanceOf(Error);
expect(thrown.passed).toEqual(false);
expect(thrown.matcherName).toEqual('toBeResolvedTo');
expect(thrown.message).toEqual(
"Expected a promise to be resolved to 'b' but it was resolved to 'a'."
);
expect(thrown.actual).toBe(promise);
expect(thrown.expected).toEqual('b');
});
it('does not throw when the matcher passes', async function() {
let threw = false;
env.it('a spec', async function() {
try {
await env.throwUnlessAsync(Promise.resolve()).toBeResolved();
} catch (e) {
threw = true;
}
});
await env.execute();
expect(threw).toBe(false);
});
it('does not cause a failure if the error does not propagate back to jasmine', async function() {
env.it('a spec', async function() {
try {
await env.throwUnlessAsync(Promise.resolve()).toBeRejected();
} catch (e) {}
});
const reporter = jasmine.createSpyObj('reporter', ['specDone']);
env.addReporter(reporter);
await env.execute();
expect(reporter.specDone).toHaveBeenCalledWith(
jasmine.objectContaining({ status: 'passed' })
);
});
});
function browserEventMethods() { function browserEventMethods() {
return { return {
listeners_: { error: [], unhandledrejection: [] }, listeners_: { error: [], unhandledrejection: [] },
+2 -8
View File
@@ -610,19 +610,13 @@ describe('Matchers (Integration)', function() {
}); });
describe('toHaveClass', function() { describe('toHaveClass', function() {
beforeEach(function() {
this.domHelpers = jasmine.getEnv().domHelpers();
});
verifyPasses(function(env) { verifyPasses(function(env) {
const domHelpers = jasmine.getEnv().domHelpers(); const el = specHelpers.domHelpers.createElementWithClassName('foo');
const el = domHelpers.createElementWithClassName('foo');
env.expect(el).toHaveClass('foo'); env.expect(el).toHaveClass('foo');
}); });
verifyFails(function(env) { verifyFails(function(env) {
const domHelpers = jasmine.getEnv().domHelpers(); const el = specHelpers.domHelpers.createElementWithClassName('foo');
const el = domHelpers.createElementWithClassName('foo');
env.expect(el).toHaveClass('bar'); env.expect(el).toHaveClass('bar');
}); });
}); });
+1 -1
View File
@@ -2,7 +2,7 @@ describe('spec running', function() {
let env; let env;
beforeEach(function() { beforeEach(function() {
jasmine.getEnv().registerIntegrationMatchers(); specHelpers.registerIntegrationMatchers();
env = new jasmineUnderTest.Env(); env = new jasmineUnderTest.Env();
env.configure({ random: false }); env.configure({ random: false });
}); });
+4 -8
View File
@@ -1,12 +1,8 @@
describe('toHaveClass', function() { describe('toHaveClass', function() {
beforeEach(function() {
this.domHelpers = jasmine.getEnv().domHelpers();
});
it('fails for a DOM element that lacks the expected class', function() { it('fails for a DOM element that lacks the expected class', function() {
const matcher = jasmineUnderTest.matchers.toHaveClass(), const matcher = jasmineUnderTest.matchers.toHaveClass(),
result = matcher.compare( result = matcher.compare(
this.domHelpers.createElementWithClassName(''), specHelpers.domHelpers.createElementWithClassName(''),
'foo' 'foo'
); );
@@ -15,7 +11,7 @@ describe('toHaveClass', function() {
it('passes for a DOM element that has the expected class', function() { it('passes for a DOM element that has the expected class', function() {
const matcher = jasmineUnderTest.matchers.toHaveClass(), const matcher = jasmineUnderTest.matchers.toHaveClass(),
el = this.domHelpers.createElementWithClassName('foo bar baz'); el = specHelpers.domHelpers.createElementWithClassName('foo bar baz');
expect(matcher.compare(el, 'foo').pass).toBe(true); expect(matcher.compare(el, 'foo').pass).toBe(true);
expect(matcher.compare(el, 'bar').pass).toBe(true); expect(matcher.compare(el, 'bar').pass).toBe(true);
@@ -24,7 +20,7 @@ describe('toHaveClass', function() {
it('fails for a DOM element that only has other classes', function() { it('fails for a DOM element that only has other classes', function() {
const matcher = jasmineUnderTest.matchers.toHaveClass(), const matcher = jasmineUnderTest.matchers.toHaveClass(),
el = this.domHelpers.createElementWithClassName('foo bar'); el = specHelpers.domHelpers.createElementWithClassName('foo bar');
expect(matcher.compare(el, 'fo').pass).toBe(false); expect(matcher.compare(el, 'fo').pass).toBe(false);
}); });
@@ -42,7 +38,7 @@ describe('toHaveClass', function() {
matcher.compare(undefined, 'foo'); matcher.compare(undefined, 'foo');
}).toThrowError('undefined is not a DOM element'); }).toThrowError('undefined is not a DOM element');
const textNode = this.domHelpers.document.createTextNode(''); const textNode = specHelpers.domHelpers.document.createTextNode('');
expect(function() { expect(function() {
matcher.compare(textNode, 'foo'); matcher.compare(textNode, 'foo');
}).toThrowError('HTMLNode is not a DOM element'); }).toThrowError('HTMLNode is not a DOM element');
+11
View File
@@ -15,6 +15,17 @@ describe('toHaveSize', function() {
expect(result.pass).toBe(false); expect(result.pass).toBe(false);
}); });
it('informs about the size of an array whose length does not match', function() {
const matcher = jasmineUnderTest.matchers.toHaveSize({
pp: jasmineUnderTest.makePrettyPrinter()
}),
result = matcher.compare([1, 2, 3], 2);
expect(result.message()).toEqual(
'Expected [ 1, 2, 3 ] with size 3 to have size 2.'
);
});
it('passes for an object with the proper number of keys', function() { it('passes for an object with the proper number of keys', function() {
const matcher = jasmineUnderTest.matchers.toHaveSize(), const matcher = jasmineUnderTest.matchers.toHaveSize(),
result = matcher.compare({ a: 1, b: 2 }, 2); result = matcher.compare({ a: 1, b: 2 }, 2);
+3 -3
View File
@@ -1,4 +1,4 @@
(function(env) { (function() {
function browserVersion(matchFn) { function browserVersion(matchFn) {
const userAgent = jasmine.getGlobal().navigator.userAgent; const userAgent = jasmine.getGlobal().navigator.userAgent;
if (!userAgent) { if (!userAgent) {
@@ -10,7 +10,7 @@
return match ? parseFloat(match[1]) : void 0; return match ? parseFloat(match[1]) : void 0;
} }
env.firefoxVersion = browserVersion(function(userAgent) { specHelpers.firefoxVersion = browserVersion(function(userAgent) {
return /Firefox\/([0-9]{0,})/.exec(userAgent); return /Firefox\/([0-9]{0,})/.exec(userAgent);
}); });
})(jasmine.getEnv()); })();
+17 -21
View File
@@ -1,24 +1,20 @@
(function(env) { (function() {
function domHelpers() { let doc;
let doc;
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
doc = document; doc = document;
} else { } else {
const JSDOM = require('jsdom').JSDOM; const JSDOM = require('jsdom').JSDOM;
const dom = new JSDOM(); const dom = new JSDOM();
doc = dom.window.document; doc = dom.window.document;
}
return {
document: doc,
createElementWithClassName: function(className) {
const el = this.document.createElement('div');
el.className = className;
return el;
}
};
} }
env.domHelpers = domHelpers; specHelpers.domHelpers = {
})(jasmine.getEnv()); document: doc,
createElementWithClassName(className) {
const el = this.document.createElement('div');
el.className = className;
return el;
}
};
})();
+1
View File
@@ -0,0 +1 @@
globalThis.specHelpers = {};
+3 -3
View File
@@ -1,5 +1,5 @@
(function(env) { (function() {
env.registerIntegrationMatchers = function() { specHelpers.registerIntegrationMatchers = function() {
jasmine.addMatchers({ jasmine.addMatchers({
toHaveFailedExpectationsForRunnable: function() { toHaveFailedExpectationsForRunnable: function() {
return { return {
@@ -51,4 +51,4 @@
} }
}); });
}; };
})(jasmine.getEnv()); })();
+5 -7
View File
@@ -12,14 +12,12 @@
}; };
function getSourceFiles() { function getSourceFiles() {
const src_files = ['core/**/*.js', 'version.js'].map(function(file) { const globs = ['../../src/core/**/*.js', '../../src/version.js'];
return path.join(__dirname, '../../', 'src/', file); const srcFiles = globs.flatMap(g => glob.sync(g, { cwd: __dirname }));
});
const files = src_files.flatMap(g => glob.sync(g)); for (const file of srcFiles) {
files.forEach(function(resolvedFile) { require(file);
require(resolvedFile); }
});
} }
getSourceFiles(); getSourceFiles();
+38 -13
View File
@@ -1411,6 +1411,23 @@ describe('HtmlReporter', function() {
describe('and there are pending specs', function() { describe('and there are pending specs', function() {
let container, reporter; let container, reporter;
function pendingSpecStatus() {
return {
id: 123,
description: 'with a spec',
fullName: 'A Suite with a spec',
status: 'pending',
passedExpectations: [],
failedExpectations: []
};
}
function reportWithSpecStatus(specStatus) {
reporter.specStarted(specStatus);
reporter.specDone(specStatus);
reporter.jasmineDone({});
}
beforeEach(function() { beforeEach(function() {
container = document.createElement('div'); container = document.createElement('div');
const getContainer = function() { const getContainer = function() {
@@ -1429,21 +1446,10 @@ describe('HtmlReporter', function() {
reporter.initialize(); reporter.initialize();
reporter.jasmineStarted({ totalSpecsDefined: 1 }); reporter.jasmineStarted({ totalSpecsDefined: 1 });
const specStatus = {
id: 123,
description: 'with a spec',
fullName: 'A Suite with a spec',
status: 'pending',
passedExpectations: [],
failedExpectations: [],
pendingReason: 'my custom pending reason'
};
reporter.specStarted(specStatus);
reporter.specDone(specStatus);
reporter.jasmineDone({});
}); });
it('reports the pending specs count', function() { it('reports the pending specs count', function() {
reportWithSpecStatus(pendingSpecStatus());
const alertBar = container.querySelector('.jasmine-alert .jasmine-bar'); const alertBar = container.querySelector('.jasmine-alert .jasmine-bar');
expect(alertBar.innerHTML).toMatch( expect(alertBar.innerHTML).toMatch(
@@ -1452,17 +1458,36 @@ describe('HtmlReporter', function() {
}); });
it('reports no failure details', function() { it('reports no failure details', function() {
reportWithSpecStatus(pendingSpecStatus());
const specFailure = container.querySelector('.jasmine-failures'); const specFailure = container.querySelector('.jasmine-failures');
expect(specFailure.childNodes.length).toEqual(0); expect(specFailure.childNodes.length).toEqual(0);
}); });
it('displays the custom pending reason', function() { it('displays the custom pending reason', function() {
reportWithSpecStatus({
...pendingSpecStatus(),
pendingReason: 'my custom pending reason'
});
const pendingDetails = container.querySelector( const pendingDetails = container.querySelector(
'.jasmine-summary .jasmine-pending' '.jasmine-summary .jasmine-pending'
); );
expect(pendingDetails.innerHTML).toContain('my custom pending reason'); expect(pendingDetails.innerHTML).toContain(
'PENDING WITH MESSAGE: my custom pending reason'
);
});
it('indicates that the spec is pending even if there is no reason', function() {
reportWithSpecStatus({
...pendingSpecStatus(),
pendingReason: ''
});
const pendingDetails = container.querySelector(
'.jasmine-summary .jasmine-pending'
);
expect(pendingDetails.innerHTML).toContain('PENDING');
}); });
}); });
+1 -1
View File
@@ -22,7 +22,7 @@ describe('PrettyPrinter (HTML Dependent)', function() {
}); });
it("should print Firefox's wrapped native objects correctly", function() { it("should print Firefox's wrapped native objects correctly", function() {
if (jasmine.getEnv().firefoxVersion) { if (specHelpers.firefoxVersion) {
const pp = jasmineUnderTest.makePrettyPrinter(); const pp = jasmineUnderTest.makePrettyPrinter();
let err; let err;
try { try {
+1 -1
View File
@@ -113,7 +113,7 @@ describe('npm package', function() {
const files = fs.readdirSync(path.resolve(this.tmpDir, 'package')); const files = fs.readdirSync(path.resolve(this.tmpDir, 'package'));
files.sort(); files.sort();
expect(files).toEqual([ expect(files).toEqual([
'MIT.LICENSE', 'LICENSE',
'README.md', 'README.md',
'images', 'images',
'lib', 'lib',
+13 -9
View File
@@ -18,6 +18,7 @@ module.exports = {
specDir: 'spec', specDir: 'spec',
specFiles: ['**/*[Ss]pec.js', '!npmPackage/**/*'], specFiles: ['**/*[Ss]pec.js', '!npmPackage/**/*'],
helpers: [ helpers: [
'helpers/init.js',
'helpers/generator.js', 'helpers/generator.js',
'helpers/BrowserFlags.js', 'helpers/BrowserFlags.js',
'helpers/domHelpers.js', 'helpers/domHelpers.js',
@@ -28,16 +29,19 @@ module.exports = {
random: true, random: true,
browser: { browser: {
name: process.env.JASMINE_BROWSER || 'firefox', name: process.env.JASMINE_BROWSER || 'firefox',
useSauce: process.env.USE_SAUCE === 'true', useRemoteSeleniumGrid: process.env.USE_SAUCE === 'true',
sauce: { remoteSeleniumGrid: {
name: `jasmine-core ${new Date().toISOString()}`, url: 'https://ondemand.saucelabs.com/wd/hub',
os: process.env.SAUCE_OS,
browserVersion: process.env.SAUCE_BROWSER_VERSION, browserVersion: process.env.SAUCE_BROWSER_VERSION,
build: `Core ${process.env.TRAVIS_BUILD_NUMBER || 'Ran locally'}`, platformName: process.env.SAUCE_OS,
tags: ['Jasmine-Core'], 'sauce:options': {
tunnelIdentifier: process.env.SAUCE_TUNNEL_IDENTIFIER, name: `jasmine-core ${new Date().toISOString()}`,
username: process.env.SAUCE_USERNAME, build: `Core ${process.env.CIRCLE_BUILD_NUM || 'Ran locally'}`,
accessKey: process.env.SAUCE_ACCESS_KEY tags: ['Jasmine-Core'],
tunnelIdentifier: process.env.SAUCE_TUNNEL_IDENTIFIER,
username: process.env.SAUCE_USERNAME,
accessKey: process.env.SAUCE_ACCESS_KEY
}
} }
} }
}; };
+1
View File
@@ -5,6 +5,7 @@
"npmPackage/**/*[Ss]pec.js" "npmPackage/**/*[Ss]pec.js"
], ],
"helpers": [ "helpers": [
"helpers/init.js",
"helpers/domHelpers.js", "helpers/domHelpers.js",
"helpers/integrationMatchers.js", "helpers/integrationMatchers.js",
"helpers/overrideConsoleLogForCircleCi.js", "helpers/overrideConsoleLogForCircleCi.js",
+47 -18
View File
@@ -2,7 +2,8 @@ getJasmineRequireObj().clearStack = function(j$) {
const maxInlineCallCount = 10; const maxInlineCallCount = 10;
function browserQueueMicrotaskImpl(global) { function browserQueueMicrotaskImpl(global) {
const { setTimeout, queueMicrotask } = global; const unclampedSetTimeout = getUnclampedSetTimeout(global);
const { queueMicrotask } = global;
let currentCallCount = 0; let currentCallCount = 0;
return function clearStack(fn) { return function clearStack(fn) {
currentCallCount++; currentCallCount++;
@@ -11,7 +12,7 @@ getJasmineRequireObj().clearStack = function(j$) {
queueMicrotask(fn); queueMicrotask(fn);
} else { } else {
currentCallCount = 0; currentCallCount = 0;
setTimeout(fn); unclampedSetTimeout(fn);
} }
}; };
} }
@@ -25,6 +26,37 @@ getJasmineRequireObj().clearStack = function(j$) {
} }
function messageChannelImpl(global) { function messageChannelImpl(global) {
const { setTimeout } = global;
const postMessage = getPostMessage(global);
let currentCallCount = 0;
return function clearStack(fn) {
currentCallCount++;
if (currentCallCount < maxInlineCallCount) {
postMessage(fn);
} else {
currentCallCount = 0;
setTimeout(fn);
}
};
}
function getUnclampedSetTimeout(global) {
const { setTimeout } = global;
if (j$.util.isUndefined(global.MessageChannel)) {
return setTimeout;
}
const postMessage = getPostMessage(global);
return function unclampedSetTimeout(fn) {
postMessage(function() {
setTimeout(fn);
});
};
}
function getPostMessage(global) {
const { MessageChannel, setTimeout } = global; const { MessageChannel, setTimeout } = global;
const channel = new MessageChannel(); const channel = new MessageChannel();
let head = {}; let head = {};
@@ -48,17 +80,9 @@ getJasmineRequireObj().clearStack = function(j$) {
} }
}; };
let currentCallCount = 0; return function postMessage(fn) {
return function clearStack(fn) { tail = tail.next = { task: fn };
currentCallCount++; channel.port2.postMessage(0);
if (currentCallCount < maxInlineCallCount) {
tail = tail.next = { task: fn };
channel.port2.postMessage(0);
} else {
currentCallCount = 0;
setTimeout(fn);
}
}; };
} }
@@ -68,20 +92,25 @@ getJasmineRequireObj().clearStack = function(j$) {
global.process.versions && global.process.versions &&
typeof global.process.versions.node === 'string'; typeof global.process.versions.node === 'string';
const SAFARI = // Windows builds of WebKit have a fairly generic user agent string when no application name is provided:
// e.g. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/605.1.15 (KHTML, like Gecko)"
const SAFARI_OR_WIN_WEBKIT =
global.navigator && global.navigator &&
/^((?!chrome|android).)*safari/i.test(global.navigator.userAgent); /(^((?!chrome|android).)*safari)|(Win64; x64\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)$)/i.test(
global.navigator.userAgent
);
if (NODE_JS) { if (NODE_JS) {
// Unlike browsers, Node doesn't require us to do a periodic setTimeout // Unlike browsers, Node doesn't require us to do a periodic setTimeout
// so we avoid the overhead. // so we avoid the overhead.
return nodeQueueMicrotaskImpl(global); return nodeQueueMicrotaskImpl(global);
} else if ( } else if (
SAFARI || SAFARI_OR_WIN_WEBKIT ||
j$.util.isUndefined(global.MessageChannel) /* tests */ j$.util.isUndefined(global.MessageChannel) /* tests */
) { ) {
// queueMicrotask is dramatically faster than MessageChannel in Safari, // queueMicrotask is dramatically faster than MessageChannel in Safari
// at least through version 16. // 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 // Some of our own integration tests provide a mock queueMicrotask in all
// environments because it's simpler to mock than MessageChannel. // environments because it's simpler to mock than MessageChannel.
return browserQueueMicrotaskImpl(global); return browserQueueMicrotaskImpl(global);
+6
View File
@@ -1,3 +1,6 @@
// Warning: don't add "use strict" to this file. Doing so potentially changes
// the behavior of user code that does things like setTimeout("var x = 1;")
// while the mock clock is installed.
getJasmineRequireObj().DelayedFunctionScheduler = function(j$) { getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
function DelayedFunctionScheduler() { function DelayedFunctionScheduler() {
this.scheduledLookup_ = []; this.scheduledLookup_ = [];
@@ -23,6 +26,9 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
) { ) {
let f; let f;
if (typeof funcToCall === 'string') { if (typeof funcToCall === 'string') {
// setTimeout("some code") and setInterval("some code") are legal, if
// not recommended. We don't do that ourselves, but user code might.
// This allows such code to work when the mock clock is installed.
f = function() { f = function() {
// eslint-disable-next-line no-eval // eslint-disable-next-line no-eval
return eval(funcToCall); return eval(funcToCall);
+68 -71
View File
@@ -174,6 +174,12 @@ getJasmineRequireObj().Env = function(j$) {
* @function * @function
*/ */
this.configure = function(configuration) { this.configure = function(configuration) {
if (parallelLoadingState) {
throw new Error(
'Jasmine cannot be configured via Env in parallel mode'
);
}
const booleanProps = [ const booleanProps = [
'random', 'random',
'failSpecWithNoExpectations', 'failSpecWithNoExpectations',
@@ -258,6 +264,49 @@ getJasmineRequireObj().Env = function(j$) {
} }
}; };
const handleThrowUnlessFailure = function(passed, result) {
if (!passed) {
/**
* @interface
* @name ThrowUnlessFailure
* @extends Error
* @description Represents a failure of an expectation evaluated with
* {@link throwUnless}. Properties of this error are a subset of the
* properties of {@link Expectation} and have the same values.
* @property {String} matcherName - The name of the matcher that was executed for this expectation.
* @property {String} message - The failure message for the expectation.
* @property {Boolean} passed - Whether the expectation passed or failed.
* @property {Object} expected - If the expectation failed, what was the expected value.
* @property {Object} actual - If the expectation failed, what actual value was produced.
*/
const error = new Error(result.message);
error.passed = result.passed;
error.message = result.message;
error.expected = result.expected;
error.actual = result.actual;
error.matcherName = result.matcherName;
throw error;
}
};
const throwUnlessFactory = function(actual, spec) {
return j$.Expectation.factory({
matchersUtil: runableResources.makeMatchersUtil(),
customMatchers: runableResources.customMatchers(),
actual: actual,
addExpectationResult: handleThrowUnlessFailure
});
};
const throwUnlessAsyncFactory = function(actual, spec) {
return j$.Expectation.asyncFactory({
matchersUtil: runableResources.makeMatchersUtil(),
customAsyncMatchers: runableResources.customAsyncMatchers(),
actual: actual,
addExpectationResult: handleThrowUnlessFailure
});
};
// TODO: Unify recordLateError with recordLateExpectation? The extra // TODO: Unify recordLateError with recordLateExpectation? The extra
// diagnostic info added by the latter is probably useful in most cases. // diagnostic info added by the latter is probably useful in most cases.
function recordLateError(error) { function recordLateError(error) {
@@ -414,72 +463,7 @@ getJasmineRequireObj().Env = function(j$) {
* @see custom_reporter * @see custom_reporter
*/ */
reporter = new j$.ReportDispatcher( reporter = new j$.ReportDispatcher(
[ j$.reporterEvents,
/**
* `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts.
* @function
* @name Reporter#jasmineStarted
* @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'jasmineStarted',
/**
* When the entire suite has finished execution `jasmineDone` is called
* @function
* @name Reporter#jasmineDone
* @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running.
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'jasmineDone',
/**
* `suiteStarted` is invoked when a `describe` starts to run
* @function
* @name Reporter#suiteStarted
* @param {SuiteResult} result Information about the individual {@link describe} being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'suiteStarted',
/**
* `suiteDone` is invoked when all of the child specs and suites for a given suite have been run
*
* While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`.
* @function
* @name Reporter#suiteDone
* @param {SuiteResult} result
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'suiteDone',
/**
* `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions)
* @function
* @name Reporter#specStarted
* @param {SpecResult} result Information about the individual {@link it} being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'specStarted',
/**
* `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run.
*
* While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed.
* @function
* @name Reporter#specDone
* @param {SpecResult} result
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'specDone'
],
function(options) { function(options) {
options.SkipPolicy = j$.NeverSkipPolicy; options.SkipPolicy = j$.NeverSkipPolicy;
return queueRunnerFactory(options); return queueRunnerFactory(options);
@@ -503,7 +487,6 @@ getJasmineRequireObj().Env = function(j$) {
}; };
this.parallelReset = function() { this.parallelReset = function() {
// TODO: ensure that autoCleanClosures was false
suiteBuilder.parallelReset(); suiteBuilder.parallelReset();
runner.parallelReset(); runner.parallelReset();
}; };
@@ -827,23 +810,37 @@ getJasmineRequireObj().Env = function(j$) {
}; };
this.expect = function(actual) { this.expect = function(actual) {
if (!runner.currentRunable()) { const runable = runner.currentRunable();
if (!runable) {
throw new Error( throw new Error(
"'expect' was used when there was no current spec, this could be because an asynchronous test timed out" "'expect' was used when there was no current spec, this could be because an asynchronous test timed out"
); );
} }
return runner.currentRunable().expect(actual); return runable.expectationFactory(actual, runable);
}; };
this.expectAsync = function(actual) { this.expectAsync = function(actual) {
if (!runner.currentRunable()) { const runable = runner.currentRunable();
if (!runable) {
throw new Error( throw new Error(
"'expectAsync' was used when there was no current spec, this could be because an asynchronous test timed out" "'expectAsync' was used when there was no current spec, this could be because an asynchronous test timed out"
); );
} }
return runner.currentRunable().expectAsync(actual); return runable.asyncExpectationFactory(actual, runable);
};
this.throwUnless = function(actual) {
const runable = runner.currentRunable();
return throwUnlessFactory(actual, runable);
};
this.throwUnlessAsync = function(actual) {
const runable = runner.currentRunable();
return throwUnlessAsyncFactory(actual, runable);
}; };
this.beforeEach = function(beforeEachFunction, timeout) { this.beforeEach = function(beforeEachFunction, timeout) {
+2 -2
View File
@@ -67,7 +67,7 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) {
lines.unshift(stackTrace.message); lines.unshift(stackTrace.message);
} }
if (error.cause) { if (error.cause && error.cause instanceof Error) {
const substack = this.stack_(error.cause, { const substack = this.stack_(error.cause, {
messageHandling: 'require' messageHandling: 'require'
}); });
@@ -102,7 +102,7 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) {
const result = {}; const result = {};
let empty = true; let empty = true;
for (const prop in error) { for (const prop of Object.keys(error)) {
if (ignoredProperties.includes(prop)) { if (ignoredProperties.includes(prop)) {
continue; continue;
} }
+7 -2
View File
@@ -16,19 +16,24 @@ getJasmineRequireObj().Expectation = function(j$) {
} }
/** /**
* Add some context for an {@link expect} * Add some context to be included in matcher failures for an
* {@link expect|expectation}, so that it can be more easily distinguished
* from similar expectations.
* @function * @function
* @name matchers#withContext * @name matchers#withContext
* @since 3.3.0 * @since 3.3.0
* @param {String} message - Additional context to show when the matcher fails * @param {String} message - Additional context to show when the matcher fails
* @return {matchers} * @return {matchers}
* @example
* expect(things[0]).withContext('thing 0').toEqual('a');
* expect(things[1]).withContext('thing 1').toEqual('b');
*/ */
Expectation.prototype.withContext = function withContext(message) { Expectation.prototype.withContext = function withContext(message) {
return addFilter(this, new ContextAddingFilter(message)); return addFilter(this, new ContextAddingFilter(message));
}; };
/** /**
* Invert the matcher following this {@link expect} * Invert the matcher following this {@link expect|expectation}
* @member * @member
* @name matchers#not * @name matchers#not
* @since 1.3.0 * @since 1.3.0
+2
View File
@@ -12,6 +12,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
function dispatchBrowserError(error, event) { function dispatchBrowserError(error, event) {
if (overrideHandler) { if (overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
overrideHandler(error); overrideHandler(error);
return; return;
} }
@@ -55,6 +56,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
const handler = handlers[handlers.length - 1]; const handler = handlers[handlers.length - 1];
if (overrideHandler) { if (overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
overrideHandler(error); overrideHandler(error);
return; return;
} }
+95
View File
@@ -0,0 +1,95 @@
getJasmineRequireObj().ParallelReportDispatcher = function(j$) {
'use strict';
/**
* @class ParallelReportDispatcher
* @implements Reporter
* @classdesc A report dispatcher packaged for convenient use from outside jasmine-core.
*
* This is intended to help packages like `jasmine` (the Jasmine runner for
* Node.js) do their own report dispatching in order to support parallel
* execution. If you aren't implementing a runner package that supports
* parallel execution, this class probably isn't what you're looking for.
*
* Warning: Do not use ParallelReportDispatcher in the same process that
* Jasmine specs run in. Doing so will break Jasmine's error handling.
* @param onError {function} Function called when an unhandled exception, unhandled promise rejection, or explicit reporter failure occurs
*/
function ParallelReportDispatcher(onError, deps = {}) {
const ReportDispatcher = deps.ReportDispatcher || j$.ReportDispatcher;
const QueueRunner = deps.QueueRunner || j$.QueueRunner;
const globalErrors = deps.globalErrors || new j$.GlobalErrors();
const dispatcher = new ReportDispatcher(
j$.reporterEvents,
function(queueRunnerOptions) {
queueRunnerOptions = {
...queueRunnerOptions,
globalErrors,
timeout: { setTimeout, clearTimeout },
fail: function(error) {
// A callback-style async reporter called either done.fail()
// or done(anError).
if (!error) {
error = new Error('A reporter called done.fail()');
}
onError(error);
},
onException: function(error) {
// A reporter method threw an exception or returned a rejected
// promise, or there was an unhandled exception or unhandled promise
// rejection while an asynchronous reporter method was running.
onError(error);
}
};
new QueueRunner(queueRunnerOptions).execute();
},
function(error) {
// A reporter called done() more than once.
onError(error);
}
);
const self = {
/**
* Adds a reporter to the list of reporters that events will be dispatched to.
* @function
* @name ParallelReportDispatcher#addReporter
* @param {Reporter} reporterToAdd The reporter to be added.
* @see custom_reporter
*/
addReporter: dispatcher.addReporter.bind(dispatcher),
/**
* Clears all registered reporters.
* @function
* @name ParallelReportDispatcher#clearReporters
*/
clearReporters: dispatcher.clearReporters.bind(dispatcher),
/**
* Installs a global error handler. After this method is called, any
* unhandled exceptions or unhandled promise rejections will be passed to
* the onError callback that was passed to the constructor.
* @function
* @name ParallelReportDispatcher#installGlobalErrors
*/
installGlobalErrors: globalErrors.install.bind(globalErrors),
/**
* Uninstalls the global error handler.
* @function
* @name ParallelReportDispatcher#uninstallGlobalErrors
*/
uninstallGlobalErrors: function() {
// late-bind uninstall because it doesn't exist until install is called
globalErrors.uninstall(globalErrors);
}
};
for (const eventName of j$.reporterEvents) {
self[eventName] = dispatcher[eventName].bind(dispatcher);
}
return self;
}
return ParallelReportDispatcher;
};
+27 -23
View File
@@ -1,13 +1,4 @@
getJasmineRequireObj().QueueRunner = function(j$) { getJasmineRequireObj().QueueRunner = function(j$) {
/*
QueueRunner isn't part of the public interface, but it is used by
jasmine-npm. -core and -npm version in lockstep at the major and minor
levels but version independently at the patch level. Any changes that
would break -npm should be done in a major or minor release, never a
patch release, and accompanied by a change to -npm that's released in
the same version.
*/
let nextid = 1; let nextid = 1;
function StopExecutionError() {} function StopExecutionError() {}
@@ -74,8 +65,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
} }
QueueRunner.prototype.execute = function() { QueueRunner.prototype.execute = function() {
this.handleFinalError = error => { this.handleFinalError = (error, event) => {
this.onException(error); this.onException(errorOrMsgForGlobalError(error, event));
}; };
this.globalErrors.pushListener(this.handleFinalError); this.globalErrors.pushListener(this.handleFinalError);
this.run(0); this.run(0);
@@ -105,10 +96,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
this.recordError_(iterativeIndex); this.recordError_(iterativeIndex);
}; };
function handleError(error) { function handleError(error, event) {
// TODO probably shouldn't next() right away here. onException(errorOrMsgForGlobalError(error, event));
// That makes debugging async failures much more confusing.
onException(error);
} }
const cleanup = once(() => { const cleanup = once(() => {
if (timeoutId !== void 0) { if (timeoutId !== void 0) {
@@ -264,17 +253,21 @@ getJasmineRequireObj().QueueRunner = function(j$) {
// on the stack at this point. // on the stack at this point.
if (j$.isAsyncFunction_(fn)) { if (j$.isAsyncFunction_(fn)) {
this.onException( this.onException(
'An asynchronous before/it/after ' + new Error(
'function was defined with the async keyword but also took a ' + 'An asynchronous before/it/after ' +
'done callback. Either remove the done callback (recommended) or ' + 'function was defined with the async keyword but also took a ' +
'remove the async keyword.' 'done callback. Either remove the done callback (recommended) or ' +
'remove the async keyword.'
)
); );
} else { } else {
this.onException( this.onException(
'An asynchronous before/it/after ' + new Error(
'function took a done callback but also returned a promise. ' + 'An asynchronous before/it/after ' +
'Either remove the done callback (recommended) or change the ' + 'function took a done callback but also returned a promise. ' +
'function to not return a promise.' 'Either remove the done callback (recommended) or change the ' +
'function to not return a promise.'
)
); );
} }
} }
@@ -290,5 +283,16 @@ 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; return QueueRunner;
}; };
+1 -8
View File
@@ -1,12 +1,5 @@
getJasmineRequireObj().ReportDispatcher = function(j$) { getJasmineRequireObj().ReportDispatcher = function(j$) {
/* 'use strict';
ReportDispatcher isn't part of the public interface, but it is used by
jasmine-npm. -core and -npm version in lockstep at the major and minor
levels but version independently at the patch level. Any changes that
would break -npm should be done in a major or minor release, never a
patch release, and accompanied by a change to -npm that's released in
the same version.
*/
function ReportDispatcher(methods, queueRunnerFactory, onLateError) { function ReportDispatcher(methods, queueRunnerFactory, onLateError) {
const dispatchedMethods = methods || []; const dispatchedMethods = methods || [];
-8
View File
@@ -70,14 +70,6 @@ getJasmineRequireObj().Spec = function(j$) {
this.result.properties[key] = value; this.result.properties[key] = value;
}; };
Spec.prototype.expect = function(actual) {
return this.expectationFactory(actual, this);
};
Spec.prototype.expectAsync = function(actual) {
return this.asyncExpectationFactory(actual, this);
};
Spec.prototype.execute = function( Spec.prototype.execute = function(
queueRunnerFactory, queueRunnerFactory,
onComplete, onComplete,
+10
View File
@@ -84,6 +84,16 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
obj[methodName] = spiedMethod; obj[methodName] = spiedMethod;
// Check if setting the property actually worked. Some objects, such as
// localStorage in Firefox and later Safari versions, have no-op setters.
if (obj[methodName] !== spiedMethod) {
throw new Error(
j$.formatErrorMsg('<spyOn>')(
`Can't spy on ${methodName} because assigning to it had no effect`
)
);
}
return spiedMethod; return spiedMethod;
}; };
+10 -1
View File
@@ -32,7 +32,7 @@ getJasmineRequireObj().StackTrace = function(j$) {
// e.g. " at /some/path:4320:20 // e.g. " at /some/path:4320:20
{ re: /\s*at (.+)$/, fileLineColIx: 1, style: 'v8' }, { re: /\s*at (.+)$/, fileLineColIx: 1, style: 'v8' },
// PhantomJS on OS X, Safari, Firefox // Safari, most Firefox stack frames
// e.g. "run@http://localhost:8888/__jasmine__/jasmine.js:4320:27" // e.g. "run@http://localhost:8888/__jasmine__/jasmine.js:4320:27"
// or "http://localhost:8888/__jasmine__/jasmine.js:4320:27" // or "http://localhost:8888/__jasmine__/jasmine.js:4320:27"
{ {
@@ -40,6 +40,15 @@ getJasmineRequireObj().StackTrace = function(j$) {
fnIx: 2, fnIx: 2,
fileLineColIx: 3, fileLineColIx: 3,
style: 'webkit' style: 'webkit'
},
// Some Firefox stack frames when the developer tools are open
// e.g. "promise callback*specStarted@http://localhost:8888/__jasmine__/jasmine.js:1880:41"
{
re: /^^(?:((?:promise callback|[^\s]+ handler)\*([^@\s]+)@)|@)?([^\s]+)$/,
fnIx: 2,
fileLineColIx: 3,
style: 'webkit'
} }
]; ];
-8
View File
@@ -28,14 +28,6 @@ getJasmineRequireObj().Suite = function(j$) {
this.result.properties[key] = value; this.result.properties[key] = value;
}; };
Suite.prototype.expect = function(actual) {
return this.expectationFactory(actual, this);
};
Suite.prototype.expectAsync = function(actual) {
return this.asyncExpectationFactory(actual, this);
};
Suite.prototype.getFullName = function() { Suite.prototype.getFullName = function() {
const fullName = []; const fullName = [];
for ( for (
+19 -9
View File
@@ -1,29 +1,39 @@
getJasmineRequireObj().Timer = function() { getJasmineRequireObj().Timer = function() {
/*
Timer isn't part of the public interface, but it is used by
jasmine-npm. -core and -npm version in lockstep at the major and minor
levels but version independently at the patch level. Any changes that
would break -npm should be done in a major or minor release, never a
patch release, and accompanied by a change to -npm that's released in
the same version.
*/
const defaultNow = (function(Date) { const defaultNow = (function(Date) {
return function() { return function() {
return new Date().getTime(); return new Date().getTime();
}; };
})(Date); })(Date);
/**
* @class Timer
* @classdesc Tracks elapsed time
* @example
* const timer = new jasmine.Timer();
* timer.start();
* const elapsed = timer.elapsed()
*/
function Timer(options) { function Timer(options) {
options = options || {}; options = options || {};
const now = options.now || defaultNow; const now = options.now || defaultNow;
let startTime; let startTime;
/**
* Starts the timer.
* @function
* @name Timer#start
*/
this.start = function() { this.start = function() {
startTime = now(); startTime = now();
}; };
/**
* Determines the time since the timer was started.
* @function
* @name Timer#elapsed
* @returns {number} Elapsed time in milliseconds, or NaN if the timer has not been started
*/
this.elapsed = function() { this.elapsed = function() {
return now() - startTime; return now() - startTime;
}; };
@@ -11,7 +11,9 @@ getJasmineRequireObj().MapContaining = function(j$) {
} }
MapContaining.prototype.asymmetricMatch = function(other, matchersUtil) { MapContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
if (!j$.isMap(other)) return false; if (!j$.isMap(other)) {
return false;
}
for (const [key, value] of this.sample) { for (const [key, value] of this.sample) {
// for each key/value pair in `sample` // for each key/value pair in `sample`
@@ -11,7 +11,9 @@ getJasmineRequireObj().SetContaining = function(j$) {
} }
SetContaining.prototype.asymmetricMatch = function(other, matchersUtil) { SetContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
if (!j$.isSet(other)) return false; if (!j$.isSet(other)) {
return false;
}
for (const item of this.sample) { for (const item of this.sample) {
// for each item in `sample` there should be at least one matching item in `other` // for each item in `sample` there should be at least one matching item in `other`
+73 -46
View File
@@ -224,9 +224,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is an instance of the specified class/constructor. * value being compared is an instance of the specified class/constructor.
* @name jasmine.any * @name asymmetricEqualityTesters.any
* @emittedName jasmine.any
* @since 1.3.0 * @since 1.3.0
* @function * @function
* @param {Constructor} clazz - The constructor to check against. * @param {Constructor} clazz - The constructor to check against.
@@ -236,9 +237,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is not `null` and not `undefined`. * value being compared is not `null` and not `undefined`.
* @name jasmine.anything * @name asymmetricEqualityTesters.anything
* @emittedName jasmine.anything
* @since 2.2.0 * @since 2.2.0
* @function * @function
*/ */
@@ -247,9 +249,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is `true` or anything truthy. * value being compared is `true` or anything truthy.
* @name jasmine.truthy * @name asymmetricEqualityTesters.truthy
* @emittedName jasmine.truthy
* @since 3.1.0 * @since 3.1.0
* @function * @function
*/ */
@@ -258,9 +261,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is `null`, `undefined`, `0`, `false` or anything falsey. * value being compared is `null`, `undefined`, `0`, `false` or anything
* @name jasmine.falsy * falsy.
* @name asymmetricEqualityTesters.falsy
* @emittedName jasmine.falsy
* @since 3.1.0 * @since 3.1.0
* @function * @function
*/ */
@@ -269,9 +274,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is empty. * value being compared is empty.
* @name jasmine.empty * @name asymmetricEqualityTesters.empty
* @emittedName jasmine.empty
* @since 3.1.0 * @since 3.1.0
* @function * @function
*/ */
@@ -280,10 +286,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} * Get an {@link AsymmetricEqualityTester} that passes if the actual value is
* that passes if the actual value is the same as the sample as determined * the same as the sample as determined by the `===` operator.
* by the `===` operator. * @name asymmetricEqualityTesters.is
* @name jasmine.is * @emittedName jasmine.is
* @function * @function
* @param {Object} sample - The value to compare the actual to. * @param {Object} sample - The value to compare the actual to.
*/ */
@@ -292,9 +298,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is not empty. * value being compared is not empty.
* @name jasmine.notEmpty * @name asymmetricEqualityTesters.notEmpty
* @emittedName jasmine.notEmpty
* @since 3.1.0 * @since 3.1.0
* @function * @function
*/ */
@@ -303,9 +310,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared contains at least the keys and values. * value being compared contains at least the specified keys and values.
* @name jasmine.objectContaining * @name asymmetricEqualityTesters.objectContaining
* @emittedName jasmine.objectContaining
* @since 1.3.0 * @since 1.3.0
* @function * @function
* @param {Object} sample - The subset of properties that _must_ be in the actual. * @param {Object} sample - The subset of properties that _must_ be in the actual.
@@ -315,9 +323,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value is a `String` that matches the `RegExp` or `String`. * value is a `String` that matches the `RegExp` or `String`.
* @name jasmine.stringMatching * @name asymmetricEqualityTesters.stringMatching
* @emittedName jasmine.stringMatching
* @since 2.2.0 * @since 2.2.0
* @function * @function
* @param {RegExp|String} expected * @param {RegExp|String} expected
@@ -327,9 +336,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value is a `String` that contains the specified `String`. * value is a `String` that contains the specified `String`.
* @name jasmine.stringContaining * @name asymmetricEqualityTesters.stringContaining
* @emittedName jasmine.stringContaining
* @since 3.10.0 * @since 3.10.0
* @function * @function
* @param {String} expected * @param {String} expected
@@ -339,9 +349,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value is an `Array` that contains at least the elements in the sample. * value is an `Array` that contains at least the elements in the sample.
* @name jasmine.arrayContaining * @name asymmetricEqualityTesters.arrayContaining
* @emittedName jasmine.arrayContaining
* @since 2.2.0 * @since 2.2.0
* @function * @function
* @param {Array} sample * @param {Array} sample
@@ -351,9 +362,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value is an `Array` that contains all of the elements in the sample in any order. * value is an `Array` that contains all of the elements in the sample in
* @name jasmine.arrayWithExactContents * any order.
* @name asymmetricEqualityTesters.arrayWithExactContents
* @emittedName jasmine.arrayWithExactContents
* @since 2.8.0 * @since 2.8.0
* @function * @function
* @param {Array} sample * @param {Array} sample
@@ -363,10 +376,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if every
* that will succeed if every key/value pair in the sample passes the deep equality comparison * key/value pair in the sample passes the deep equality comparison
* with at least one key/value pair in the actual value being compared * with at least one key/value pair in the actual value being compared
* @name jasmine.mapContaining * @name asymmetricEqualityTesters.mapContaining
* @emittedName jasmine.mapContaining
* @since 3.5.0 * @since 3.5.0
* @function * @function
* @param {Map} sample - The subset of items that _must_ be in the actual. * @param {Map} sample - The subset of items that _must_ be in the actual.
@@ -376,10 +390,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if every item
* that will succeed if every item in the sample passes the deep equality comparison * in the sample passes the deep equality comparison
* with at least one item in the actual value being compared * with at least one item in the actual value being compared
* @name jasmine.setContaining * @name asymmetricEqualityTesters.setContaining
* @emittedName jasmine.setContaining
* @since 3.5.0 * @since 3.5.0
* @function * @function
* @param {Set} sample - The subset of items that _must_ be in the actual. * @param {Set} sample - The subset of items that _must_ be in the actual.
@@ -435,14 +450,26 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
* handling will be restored when the promise returned from the callback is * handling will be restored when the promise returned from the callback is
* settled. * settled.
* *
* When the JavaScript runtime reports an uncaught error or unhandled rejection,
* the spy will be called with a single parameter representing Jasmine's best
* effort at describing the error. This parameter may be of any type, because
* JavaScript allows anything to be thrown or used as the reason for a
* rejected promise, but Error instances and strings are most common.
*
* Note: The JavaScript runtime may deliver uncaught error events and unhandled * Note: The JavaScript runtime may deliver uncaught error events and unhandled
* rejection events asynchronously, especially in browsers. If the event * rejection events asynchronously, especially in browsers. If the event
* occurs after the promise returned from the callback is settled, it won't * occurs after the promise returned from the callback is settled, it won't
* be routed to the spy even if the underlying error occurred previously. * be routed to the spy even if the underlying error occurred previously.
* It's up to you to ensure that the returned promise isn't resolved until * It's up to you to ensure that all of the error/rejection events that you
* all of the error/rejection events that you want to handle have occurred. * want to handle have occurred before you resolve the promise returned from
* the callback.
* *
* You must await the return value of spyOnGlobalErrorsAsync. * You must ensure that the `it`/`beforeEach`/etc fn that called
* `spyOnGlobalErrorsAsync` does not signal completion until after the
* promise returned by `spyOnGlobalErrorsAsync` is resolved. Normally this is
* done by `await`ing the returned promise. Leaving the global error spy
* installed after the `it`/`beforeEach`/etc fn that installed it signals
* completion is likely to cause problems and is not supported.
* @name jasmine.spyOnGlobalErrorsAsync * @name jasmine.spyOnGlobalErrorsAsync
* @function * @function
* @async * @async
+1 -5
View File
@@ -69,11 +69,7 @@ getJasmineRequireObj().buildExpectationResult = function(j$) {
} else if (options.stack) { } else if (options.stack) {
error = options; error = options;
} else { } else {
try { error = new Error(message());
throw new Error(message());
} catch (e) {
error = e;
}
} }
} }
// Omit the message from the stack trace because it will be // Omit the message from the stack trace because it will be
+21 -4
View File
@@ -9,7 +9,7 @@ getJasmineRequireObj().toHaveSize = function(j$) {
* array = [1,2]; * array = [1,2];
* expect(array).toHaveSize(2); * expect(array).toHaveSize(2);
*/ */
function toHaveSize() { function toHaveSize(matchersUtil) {
return { return {
compare: function(actual, expected) { compare: function(actual, expected) {
const result = { const result = {
@@ -24,12 +24,29 @@ getJasmineRequireObj().toHaveSize = function(j$) {
throw new Error('Cannot get size of ' + actual + '.'); throw new Error('Cannot get size of ' + actual + '.');
} }
let actualSize;
if (j$.isSet(actual) || j$.isMap(actual)) { if (j$.isSet(actual) || j$.isMap(actual)) {
result.pass = actual.size === expected; actualSize = actual.size;
} else if (isLength(actual.length)) { } else if (isLength(actual.length)) {
result.pass = actual.length === expected; actualSize = actual.length;
} else { } else {
result.pass = Object.keys(actual).length === expected; actualSize = Object.keys(actual).length;
}
result.pass = actualSize === expected;
if (!result.pass) {
result.message = function() {
return (
'Expected ' +
matchersUtil.pp(actual) +
' with size ' +
actualSize +
' to have size ' +
expected +
'.'
);
};
} }
return result; return result;
+4 -1
View File
@@ -32,7 +32,10 @@ getJasmineRequireObj().toHaveSpyInteractions = function(j$) {
let hasSpy = false; let hasSpy = false;
const calledSpies = []; const calledSpies = [];
for (const spy of Object.values(actual)) { for (const spy of Object.values(actual)) {
if (!j$.isSpy(spy)) continue; if (!j$.isSpy(spy)) {
continue;
}
hasSpy = true; hasSpy = true;
if (spy.calls.any()) { if (spy.calls.any()) {
+96
View File
@@ -0,0 +1,96 @@
getJasmineRequireObj().reporterEvents = function() {
/**
* Used to tell Jasmine what optional or uncommonly implemented features
* the reporter supports. If not specified, the defaults described in
* {@link ReporterCapabilities} will apply.
* @name Reporter#reporterCapabilities
* @type ReporterCapabilities | undefined
* @since 5.0
*/
/**
* Used to tell Jasmine what optional or uncommonly implemented features
* the reporter supports.
* @interface ReporterCapabilities
* @see Reporter#reporterCapabilities
* @since 5.0
*/
/**
* Indicates whether the reporter supports parallel execution. Jasmine will
* not allow parallel execution unless all reporters that are in use set this
* capability to true.
* @name ReporterCapabilities#parallel
* @type boolean | undefined
* @default false
* @see running_specs_in_parallel
* @since 5.0
*/
const events = [
/**
* `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts.
* @function
* @name Reporter#jasmineStarted
* @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'jasmineStarted',
/**
* When the entire suite has finished execution `jasmineDone` is called
* @function
* @name Reporter#jasmineDone
* @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running.
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'jasmineDone',
/**
* `suiteStarted` is invoked when a `describe` starts to run
* @function
* @name Reporter#suiteStarted
* @param {SuiteResult} result Information about the individual {@link describe} being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'suiteStarted',
/**
* `suiteDone` is invoked when all of the child specs and suites for a given suite have been run
*
* While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`.
* @function
* @name Reporter#suiteDone
* @param {SuiteResult} result
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'suiteDone',
/**
* `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions)
* @function
* @name Reporter#specStarted
* @param {SpecResult} result Information about the individual {@link it} being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'specStarted',
/**
* `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run.
*
* While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed.
* @function
* @name Reporter#specDone
* @param {SpecResult} result
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'specDone'
];
Object.freeze(events);
return events;
};
+2
View File
@@ -67,7 +67,9 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
j$.CompleteOnFirstErrorSkipPolicy = jRequire.CompleteOnFirstErrorSkipPolicy( j$.CompleteOnFirstErrorSkipPolicy = jRequire.CompleteOnFirstErrorSkipPolicy(
j$ j$
); );
j$.reporterEvents = jRequire.reporterEvents(j$);
j$.ReportDispatcher = jRequire.ReportDispatcher(j$); j$.ReportDispatcher = jRequire.ReportDispatcher(j$);
j$.ParallelReportDispatcher = jRequire.ParallelReportDispatcher(j$);
j$.RunableResources = jRequire.RunableResources(j$); j$.RunableResources = jRequire.RunableResources(j$);
j$.Runner = jRequire.Runner(j$); j$.Runner = jRequire.Runner(j$);
j$.Spec = jRequire.Spec(j$); j$.Spec = jRequire.Spec(j$);
+81 -1
View File
@@ -225,6 +225,54 @@ getJasmineRequireObj().interface = function(jasmine, env) {
return env.expectAsync(actual); return env.expectAsync(actual);
}, },
/**
* Create an asynchronous expectation for a spec and throw an error if it fails.
*
* This is intended to allow Jasmine matchers to be used with tools like
* testing-library's `waitFor`, which expect matcher failures to throw
* exceptions and not trigger a spec failure if the exception is caught.
* It can also be used to integration-test custom matchers.
*
* If the resulting expectation fails, a {@link ThrowUnlessFailure} will be
* thrown. A failed expectation will not result in a spec failure unless the
* exception propagates back to Jasmine, either via the call stack or via
* the global unhandled exception/unhandled promise rejection events.
* @name throwUnlessAsync
* @since 5.1.0
* @function
* @param actual
* @global
* @param {Object} actual - Actual computed value to test expectations against.
* @return {matchers}
*/
throwUnlessAsync: function(actual) {
return env.throwUnlessAsync(actual);
},
/**
* Create an expectation for a spec and throw an error if it fails.
*
* This is intended to allow Jasmine matchers to be used with tools like
* testing-library's `waitFor`, which expect matcher failures to throw
* exceptions and not trigger a spec failure if the exception is caught.
* It can also be used to integration-test custom matchers.
*
* If the resulting expectation fails, a {@link ThrowUnlessFailure} will be
* thrown. A failed expectation will not result in a spec failure unless the
* exception propagates back to Jasmine, either via the call stack or via
* the global unhandled exception/unhandled promise rejection events.
* @name throwUnless
* @since 5.1.0
* @function
* @param actual
* @global
* @param {Object} actual - Actual computed value to test expectations against.
* @return {matchers}
*/
throwUnless: function(actual) {
return env.throwUnless(actual);
},
/** /**
* Mark a spec as pending, expectation results will be ignored. * Mark a spec as pending, expectation results will be ignored.
* @name pending * @name pending
@@ -297,6 +345,12 @@ getJasmineRequireObj().interface = function(jasmine, env) {
}), }),
/** /**
* <p>Members of the jasmine global.</p>
* <p>Note: The members of the
* {@link asymmetricEqualityTesters|asymmetricEqualityTesters namespace}
* are also accessed via the jasmine global, but due to jsdoc limitations
* they are not listed here.</p>
*
* @namespace jasmine * @namespace jasmine
*/ */
jasmine: jasmine jasmine: jasmine
@@ -375,7 +429,11 @@ getJasmineRequireObj().interface = function(jasmine, env) {
* @since 1.3.0 * @since 1.3.0
* @function * @function
* @param {String} [name] - Name to give the spy. This will be displayed in failure messages. * @param {String} [name] - Name to give the spy. This will be displayed in failure messages.
* @param {Function} [originalFn] - Function to act as the real implementation. * @param {Function} [originalFn] - The "real" function. This will
* be used for subsequent calls to the spy after you call
* `mySpy.and.callThrough()`. In most cases you should omit this parameter.
* The usual way to supply an original function is to call {@link spyOn}
* instead of createSpy.
* @return {Spy} * @return {Spy}
*/ */
jasmine.createSpy = function(name, originalFn) { jasmine.createSpy = function(name, originalFn) {
@@ -426,5 +484,27 @@ getJasmineRequireObj().interface = function(jasmine, env) {
return env.setDefaultSpyStrategy(defaultStrategyFn); return env.setDefaultSpyStrategy(defaultStrategyFn);
}; };
/**
* {@link AsymmetricEqualityTester|Asymmetric equality testers} allow for
* non-exact matching in matchers that use Jasmine's deep value equality
* semantics, such as {@link matchers#toEqual|toEqual},
* {@link matchers#toContain|toContain}, and
* {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}.
*
* @example
* const someComplexObject = {
* foo: 'bar',
* baz: 'a string that contains "something"',
* qux: 'whatever'
* };
* // Passes.
* expect(someComplexObject).toEqual(jasmine.objectContaining({
* foo: 'bar',
* baz: jasmine.stringContaining('something')
* });
*
* @namespace asymmetricEqualityTesters
*/
return jasmineInterface; return jasmineInterface;
}; };
+12 -9
View File
@@ -430,7 +430,11 @@ jasmineRequire.HtmlReporter = function(j$) {
'tr', 'tr',
{}, {},
createDom('td', {}, entry.timestamp.toString()), createDom('td', {}, entry.timestamp.toString()),
createDom('td', {}, entry.message) createDom(
'td',
{ className: 'jasmine-debug-log-msg' },
entry.message
)
) )
); );
}); });
@@ -498,14 +502,13 @@ jasmineRequire.HtmlReporter = function(j$) {
if (noExpectations(resultNode.result)) { if (noExpectations(resultNode.result)) {
specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription; specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
} }
if ( if (resultNode.result.status === 'pending') {
resultNode.result.status === 'pending' && if (resultNode.result.pendingReason !== '') {
resultNode.result.pendingReason !== '' specDescription +=
) { ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
specDescription = } else {
specDescription + specDescription += ' PENDING';
' PENDING WITH MESSAGE: ' + }
resultNode.result.pendingReason;
} }
specListNode.appendChild( specListNode.appendChild(
createDom( createDom(
+10 -7
View File
@@ -3,11 +3,13 @@
$line-height: 14px; $line-height: 14px;
$margin-unit: 14px; $margin-unit: 14px;
$faint-text-color: #aaa;
$light-text-color: #666; $light-text-color: #666;
$text-color: #333; $text-color: #333;
$inactive-tab-text-color: blue;
$active-tab-text-color: #000;
$page-background-color: #eee; $page-background-color: #eee;
$menu-background-color: #aaa;
$passing-color: #007069; $passing-color: #007069;
$failing-color: #ca3a11; $failing-color: #ca3a11;
@@ -91,10 +93,6 @@ body {
right: 100%; right: 100%;
} }
.jasmine-version {
color: $faint-text-color;
}
//--- Banner ---// //--- Banner ---//
.jasmine-banner { .jasmine-banner {
@@ -238,10 +236,11 @@ body {
&.jasmine-menu { &.jasmine-menu {
background-color: #fff; background-color: #fff;
color: $faint-text-color; color: $active-tab-text-color;
a { a {
color: $text-color; color: $inactive-tab-text-color;
text-decoration: underline;
} }
} }
@@ -425,5 +424,9 @@ body {
table, th, td { table, th, td {
border: 1px solid #ddd; border: 1px solid #ddd;
} }
.jasmine-debug-log-msg {
white-space: pre;
}
} }
} }