Compare commits

..

66 Commits

Author SHA1 Message Date
Steve Gravrock
bb777e93e5 Bump version to 5.9.0 2025-07-19 08:27:17 -07:00
Steve Gravrock
9d3fb167a2 Document that the filename property of suite and spec results is deprecated
See <https://github.com/jasmine/jasmine/issues/2065>.
2025-07-19 06:54:54 -07:00
Steve Gravrock
3176eaf1d8 Merge branch 'idConflict' of https://github.com/atscott/jasmine
* Avoid generating timers with IDs that conflict with native
* Fixes #2068
* Merges #2069 from @atscott
2025-07-15 16:50:57 -07:00
Andrew Scott
d31a431d1f fix(clock): Avoid generating timers with IDs that conflict with native
This commit attempts to ensure that the timers created by jasmine mock
clock do not conflict with the native timers. This also retains
pre-existing behavior whereby a native scheduled function cannot be
cleared if it was created prior to the mock clock being installed
(unless the mock clock is uninstalled first).

Prior to this commit, attempting to clear a native timer would result in
clearing a mocked scheduled function instead, in some scenarios where
the IDs conflicted.

fixes #2068
2025-07-14 16:55:05 -07:00
Steve Gravrock
84f78c1435 Split GlobalErrors into portable and platform-specific parts 2025-07-12 13:59:19 -07:00
Steve Gravrock
ff476b1982 Unify error dispatching between browser and node 2025-07-12 13:56:58 -07:00
Steve Gravrock
d53d2ff3eb Convert GlobalErrors to an ES6 class 2025-07-12 13:56:50 -07:00
Steve Gravrock
adfbd00c75 Refactor mocking in GlobalErrorsSpec 2025-07-12 13:56:48 -07:00
Steve Gravrock
495e5fcd50 Backfill integration tests for unhandled promise rejections 2025-07-11 21:36:30 -07:00
Steve Gravrock
bc2aa7be25 Start breaking up integration/EnvSpec.js 2025-07-11 07:39:39 -07:00
Steve Gravrock
af04599bb5 Relaxed timeout on flaky test 2025-07-09 06:56:08 -07:00
Steve Gravrock
21db6ec0e3 Removed unnecessary errorWithStack helper 2025-06-22 12:49:26 -07:00
Steve Gravrock
2d07b3e6d7 Removed protections against user code redefining undefined
Jasmine hasn't even run on platforms that allowed redefining undefined
since 2.x.
2025-06-22 12:23:18 -07:00
Steve Gravrock
6891789ed2 Don't test on Node versions before 18.20.5
18.20.5 is the oldest version supported by current selenium-webdriver.
Also, many dev dependencies require at least 18.18.0.
2025-06-14 10:22:03 -07:00
Steve Gravrock
7a3d3c9360 Removed shelljs dev dependency 2025-06-14 09:05:12 -07:00
Steve Gravrock
1b2922e008 Don't hardcode temp dir in buildStandaloneDist 2025-06-14 09:05:12 -07:00
Steve Gravrock
bd8d23f2a7 Removed rimraf dev dependency 2025-06-14 09:05:06 -07:00
Steve Gravrock
de26763868 CI: remove special case for Chrome 2025-06-09 15:05:50 -07:00
Steve Gravrock
f4be08b657 Bump version to 5.8.0 2025-06-06 17:34:09 -07:00
Steve Gravrock
50ef882a1a Merge branch 'gh1886-spy-args-deep-clone' of https://github.com/evanwalsh/jasmine
Merges #2062 from @evanwaslh
Fixes #1886
2025-06-05 06:54:37 -07:00
Steve Gravrock
c1cd5c6291 Use custom object formatters in spy strategy mismatch errors 2025-06-05 05:46:29 -07:00
Steve Gravrock
63ed2b3948 Include function names in pretty printer output
This helps make matcher errors and spy strategy mismatch errors easier
to understand in cases where the difference involves expecting one
function but getting a different one.
2025-06-04 18:37:44 -07:00
Steve Gravrock
0183acc682 Fix diff building when only one side has a custom object formatter
Fixes #2061
2025-06-04 18:04:40 -07:00
Steve Gravrock
e15819c0dd Test aginast Node 24 2025-05-27 17:32:39 -07:00
Evan Walsh
f694194b2b Allow passing a function to saveArgumentsByValue to customize how arguments are saved
For instance, pass `structuredClone` to do a deep clone.

Fixes https://github.com/jasmine/jasmine/issues/1886
2025-05-27 15:43:21 -04:00
Steve Gravrock
94c00886a6 Merge branch 'setimmedate' of https://github.com/atscott/jasmine
Merges #2058 from @atscott
2025-05-03 10:00:41 -07:00
Steve Gravrock
f5915d7963 Bump version to 5.7.1 2025-05-01 19:31:30 -07:00
Steve Gravrock
15587f3ce3 Merge branch 'autotickuninstall' of https://github.com/atscott/jasmine
Merges #2057 from @atscott
2025-05-01 16:46:30 -07:00
Andrew Scott
3ecddc2555 fixup! fix(Clock): Ensure that uninstalling the clock also stops auto tick 2025-05-01 10:25:24 -07:00
Andrew Scott
6a7c0e6368 perf(clock): use setImmediate for autoTick macrotask in Node
When called within an I/O cycle, `setImmediate` is generally faster because it
is designed to execute immediately after the current I/O event completes,
whereas `setTimeout(0)` gets placed in the timers queue and might be subject to delays.

> The main advantage to using setImmediate() over setTimeout() is setImmediate()
> will always be executed before any timers if scheduled within an I/O cycle,
> independently of how many timers are present.

* https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#setimmediate-vs-settimeout
* https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#poll
* https://nodejs.org/en/learn/asynchronous-work/understanding-setimmediate
2025-04-30 14:10:39 -07:00
Andrew Scott
84daa0f5dc fix(Clock): Ensure that uninstalling the clock also stops auto tick
The autotick feature mistakenly does not account for the clock being a
singleton and the re-installation of the clock causes the auto ticking
exit conditions to become true again, before it has a chance to break.
2025-04-30 13:38:10 -07:00
Steve Gravrock
c6b3e947e9 Fixed errors in 5.7.0 release notes 2025-04-29 07:28:36 -07:00
Steve Gravrock
0e604de0db Bump version to 5.7.0 2025-04-26 09:29:07 -07:00
Steve Gravrock
e7ca9c5765 Built distribution 2025-04-26 09:28:21 -07:00
Steve Gravrock
cbff6f95cb Fixed autoTick jsdoc 2025-04-26 08:27:12 -07:00
Steve Gravrock
361640f52e Document that SpecResult#filename and SuiteResult#filename are wrong in some common scenarios
See:

* https://github.com/jasmine/jasmine/issues/2016
* https://github.com/jasmine/jasmine/issues/1884
2025-04-26 07:38:28 -07:00
Steve Gravrock
e5d46e8624 Expose spec path as an array of names
This is in addition to the existing concatenated name. It's meant to
support tools like IDE integrations that want to be able to filter a
run to an exact set of suites/specs.
2025-04-12 09:49:35 -07:00
Steve Gravrock
8f6b3c49cc Removed flaky test
It doesn't look like there's a reliable way to test setTimeout throttling
prevention. The underlying behavior is too nondeterministic. This test
failed at a significant rate in browsers where throttling prevention worked,
simply due to setTimeout taking longer than expected (e.g. 130ms for the
entire test vs an expected <= 5oms). When run in Safari, where setTimeout
throttling prevention doesn't work, it would incorrectly pass if run early
enough in the test order. This is presumably because setTimeout throttling
is influenced by the setTimeout calls made by Jasmine itself prior to
running the test.
2025-04-12 08:28:15 -07:00
Steve Gravrock
df6ab05280 Merge pull request #2056 from jasmine/fix-readme-image
Update README header image
2025-04-11 12:11:06 -07:00
Steve Gravrock
9ea027dbff Update README header image
* Work around GitHub's broken rendering of rawgithub.com images
* Add alt text
* Remove unnecessary docs link
2025-04-11 12:10:25 -07:00
Steve Gravrock
7cc7da4abc Merge branch 'degrunt' 2025-04-10 18:36:21 -07:00
Steve Gravrock
8be98e73ca Use published css-url-embed 2025-04-10 18:27:55 -07:00
Steve Gravrock
52aaf63d22 Create the dist dir if it doesn't already exist 2025-04-10 18:27:55 -07:00
Steve Gravrock
a09fdd3284 Removed remaining use of Grunt 2025-04-09 09:11:19 -07:00
Steve Gravrock
89f3e9449d Use ejs to build SpecRunner.html 2025-04-08 21:53:05 -07:00
Steve Gravrock
ba033c520d Use sass directly rather than grunt-sass 2025-04-08 21:27:43 -07:00
Steve Gravrock
fc935e89c6 Removed grunt-contrib-concat 2025-04-08 21:08:45 -07:00
Steve Gravrock
4bd2feda7d Use archiver directly rather than grunt-contrib-compress
Removes two old copies of glob and is another step towards removing Grunt.
2025-04-08 18:48:58 -07:00
Steve Gravrock
bcf699f145 Switch from grunt-css-url-embed to css-url-embed
This eliminates some rickety indirect dependencies and is a big step
towards removing Grunt.
2025-04-08 18:00:55 -07:00
Steve Gravrock
d4f29491c9 Removed old eslint config file 2025-04-08 07:46:50 -07:00
Steve Gravrock
5b1c932f89 Built distribution 2025-04-07 22:07:52 -07:00
Steve Gravrock
7a8d6e44e3 Fixed sass deprecation warning 2025-04-07 21:59:56 -07:00
Steve Gravrock
dac349397e Updated grunt-sass 2025-04-07 21:59:14 -07:00
Steve Gravrock
5ff7e7f9a1 Updated to eslint 9
This isn't officially compatible with the oldest version of Node that
Jasmine supports, but it works. If it stops working, we can always disable
linting in CI builds on older Node versions.
2025-04-07 21:39:58 -07:00
Steve Gravrock
7b2ab822c6 Removed mostly-unmaintained dev dependency 'temp'
temp has seen some recent maintainer activity but there haven't been
any commits since 2021 and PRs have gone un-addressed for years. It's
one of the dev dependencies that depends on very old versions of rimraf.
2025-04-07 21:35:36 -07:00
Steve Gravrock
033260300a Upgrade shelljs 2025-04-07 21:35:36 -07:00
Steve Gravrock
cb66b54f8b Upgraded jsdom 2025-04-07 21:35:36 -07:00
Steve Gravrock
f4a8102a80 Merge pull request #2055 from atscott/throttleTest
Fix throttling test unit conversion bug and disable test in Node
2025-04-07 21:31:54 -07:00
Andrew Scott
8f539f17b2 refactor(Clock): Fix throttling test unit conversion bug
* fixes the throttling timeout test which incorrectly converted
from milliseconds to seconds before making an assertion expecting
milliseconds.
* reduces number of timeouts from 2000 to 100, which is still more than enough
to observe throttling (or lackthereof).
* Omits Node from the test since it does not throttle timeouts
2025-04-07 08:37:26 -07:00
Steve Gravrock
e5c543a0a1 Updated bug report template
* Don't separately ask for code and steps to reproduce. They're usually
  the same.
* Require steps to reproduce, don't just recommend them
2025-03-21 10:46:45 -07:00
Steve Gravrock
7d697faf95 Merge branch 'atscott-autoTick'
* Merges #2042 from @atscott
* Fixes #1932
* Fixes #1725
2025-03-21 09:21:14 -07:00
Steve Gravrock
6f23151a5e Hardened stop-sauce-connect 2025-03-17 17:33:34 -07:00
Steve Gravrock
e53c7ed8d1 Update to Sauce Connect 5 2025-03-16 14:34:36 -07:00
Andrew Scott
dcd44a0edf feat(Clock): Add ability to automatically tick the clock asynchronously
Testing with mock clocks can often turn into a real struggle when
dealing with situations where some work in the test is truly async and
other work is captured by the mock clock. This can happen for many
reasons, but as one example:

An asynchonrous change from a task in the mocked clock may change DOM where
a resize observer then gets triggered. This browser API is truly asynchronous
and would require the user to wait real time for it to fire. If there is
follow-up work after the resize observer fires, it may be captured by the mock
clock again. This would require the tester to write something like the
following:

```
// flush the timer
jasmine.clock().tick();
// wait for resize observer
await new Promise(resolve => setTimeout(resolve));
// flush follow-up work from the resize observer callback
jasmine.clock().tick();
```

When using mock clocks, testers are always forced to write tests with intimate
knowledge of when the mock clock needs to be ticked. Oftentimes, the
purpose of using a mock clock is to speed up the execution time of the
test when there are timeouts involved. It is not often a goal to test
the exact timeout values. This can cause tests to be riddled with
`tick`. It ideal for test code to be written in a way
that is independent of whether a mock clock is installed. For example:

```
document.getElementById('submit');
// https://testing-library.com/docs/dom-testing-library/api-async/#waitfor
await waitFor(() => expect(mockAPI).toHaveBeenCalledTimes(1))
```

When mock clocks are involved, the above may not be possible if there is
some delay involved between the click and the request to the API.
Instead, developers would need to manually tick the clock beyond the
delay to trigger the API call.

This commit attempts to resolve these issues by adding a feature to the
clock which allows it to advance on its own with the passage of time,
just as clocks do without mocks installed. It also allows for some
breathing time so any unmocked micro and macrotasks are given space to
execute as well.

This feature would also address both #1725 and #1932. `asyncTick` can be
accomplished by enabling the auto tick feature and then waiting for a
promise with a timout to be resolved
(`await new Promise(resolve => setTimeout(resolve, 20))`) where
`setTimeout` is captured by the mock clock and flushed while the code is
waiting for the promise to resolve.

resolves #1725
resolves #1932

All credit goes to @stephenfarrar for this.
2025-03-10 15:31:32 -07:00
Steve Gravrock
f0a5ea9d0f Updated docs for expected and actual properties of expectation results 2025-02-17 12:07:32 -08:00
Steve Gravrock
491b513aa3 Debug logs for rare TypedArray comparison failures in Firefox 2025-02-16 20:30:47 -08:00
80 changed files with 3340 additions and 2124 deletions

View File

@@ -4,6 +4,10 @@
version: 2.1
executors:
node24:
docker:
- image: cimg/node:24.0.0
working_directory: ~/workspace
node22:
docker:
- image: cimg/node:22.0.0
@@ -14,7 +18,7 @@ executors:
working_directory: ~/workspace
node18:
docker:
- image: cimg/node:18.0.0
- image: cimg/node:18.20.5
working_directory: ~/workspace
jobs:
@@ -61,7 +65,7 @@ jobs:
at: .
- run:
name: Run tests in parallel
command: npx grunt execSpecsInParallel
command: npm run test:parallel
test_browsers: &test_browsers
executor: node18
@@ -71,12 +75,14 @@ jobs:
- run:
name: Install Sauce Connect
command: |
cd /tmp
curl https://saucelabs.com/downloads/sc-4.7.1-linux.tar.gz | tar zxf -
chmod +x sc-4.7.1-linux/bin/sc
tmpdir=$(mktemp -d)
cd "$tmpdir"
curl https://saucelabs.com/downloads/sauce-connect/5.2.2/sauce-connect-5.2.2_linux.x86_64.tar.gz | tar zxf -
chmod +x sc
mkdir ~/workspace/bin
cp sc-4.7.1-linux/bin/sc ~/workspace/bin
~/workspace/bin/sc --version
cp sc ~/workspace/bin
echo "Sauce Connect version info:"
~/workspace/bin/sc version
- run:
name: Run tests
command: |
@@ -84,13 +90,13 @@ jobs:
# cleanly if we kill it from a different step than it started in.
export PATH=$PATH:$HOME/workspace/bin
export SAUCE_TUNNEL_IDENTIFIER=$CIRCLE_WORKFLOW_JOB_ID
scripts/start-sauce-connect sauce-pidfile
export SAUCE_TUNNEL_NAME=$CIRCLE_WORKFLOW_JOB_ID
scripts/start-sauce-connect
set +o errexit
scripts/run-all-browsers
exitcode=$?
set -o errexit
scripts/stop-sauce-connect $(cat sauce-pidfile)
scripts/stop-sauce-connect
exit $exitcode
workflows:
@@ -98,6 +104,9 @@ workflows:
push:
jobs:
- build:
executor: node24
name: build_node_24
- build:
executor: node22
name: build_node_22
@@ -123,10 +132,10 @@ workflows:
requires:
- build_node_18
- test_parallel:
executor: node18
name: test_parallel_node_18
executor: node24
name: test_parallel_node_24
requires:
- build_node_18
- build_node_24
- test_parallel:
executor: node22
name: test_parallel_node_22
@@ -137,6 +146,11 @@ workflows:
name: test_parallel_node_20
requires:
- build_node_20
- test_parallel:
executor: node18
name: test_parallel_node_18
requires:
- build_node_18
- test_browsers:
requires:
- build_node_18

View File

@@ -3,6 +3,6 @@ charset = utf-8
end_of_line = lf
insert_final_newline = true
[*.{js, json, sh, yml}]
[*.{js,mjs,json,sh,yml}]
indent_style = space
indent_size = 2

View File

@@ -1,47 +0,0 @@
{
"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"
}
}

View File

@@ -13,7 +13,7 @@ body:
Check the [FAQ](https://jasmine.github.io/pages/faq.html) and any other relevant [documentation](https://jasmine.github.io/pages/docs_home.html) to see if your issue has already been addressed.
## Special troubleshooting steps for asynchronous scenarios
If the issue has to do with testing asynchronous code, please read the [async tutorial](https://jasmine.github.io/tutorials/async) and the async section of the FAQ. In particular, check for the following common errors:
If the issue has to do with testing asynchronous code, please read the [async tutorial](https://jasmine.github.io/tutorials/async) and the [async section of the FAQ](https://jasmine.github.io/pages/faq.html#async). In particular, check for the following common errors:
* Are you trying to write a synchronous test for asynchronous code?
* Does the test signal completion before the code under test finishes?
@@ -22,19 +22,22 @@ body:
## Try the latest version of Jasmine
If at all possible, upgrade to the latest versions of `jasmine-core` and any other relevant packages (e.g. `jasmine`, `jasmine-browser-runner`). If you can't do that, please check the [release notes](https://github.com/jasmine/jasmine/tree/main/release_notes) for all newer versions to make sure that the bug hasn't already been fixed.
## Put together a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example)
Please help us help you by creating a minimal but complete setup that demonstrates the problem. Remove any code and libraries that aren't absolutely necessary, but make sure it doesn't depend on any code you haven't included. In many cases a simple code snippet is enough. In cases involving external libraries, *especially* Karma or Angular, we're likely to need a runable Git repository or jsbin/stackblitz/etc.
**If we can't reproduce it, we can't fix it. Bug reports without a minimal, reproducible example are very likely to be closed.**
## Explain how to reproduce the bug
**Working steps to reproduce are required for all bug reports.** Please help us help you by creating complete but minimal instructions for reproducing the bug.
The steps to reproduce could be:
* A code snippet that reproduces the problem when run by itself in a newly generated empty `jasmine` or `jasmine-browser-runner` project, or in the standalone distribution.
* A set of steps that reproduce the problem when followed exactly as they're written in an empty directory.
* A link to a Git repository or zip/tar file containing a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). This option is required for all bugs that can only be reproduced with third-party libraries, including Angular and Karma.
Please **test your steps** by starting with an empty directory and following them exactly as they're written. Bug reports with steps to reproduce that are unclear, don't work, or include an unreasonable amount of extraneous code will likely be closed.
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to Reproduce
placeholder: |
Example steps:
1. Paste the example code below into `mySpec.js`.
2. Run `npx jasmine@<some version> mySpec.js`
validations:
required: true
- type: textarea
@@ -51,14 +54,6 @@ body:
description: What happened instead?
validations:
required: true
- type: textarea
id: code-sample
attributes:
label: Example code that reproduces the problem
description: Please include either a code snippet that reproduces the problem or a link to a repository or jsbin/stackblitz/etc containing a minimal, reproducible example as described above.
render: JavaScript
validations:
required: true
- type: textarea
id: possible-solution
attributes:
@@ -82,10 +77,11 @@ body:
placeholder: |
jasmine-browser-runner 1.2.0
fancy-reporter 132.4.8
- type: input
id: browser-or-node-version
attributes:
label: Node.js or browser version
label: Node.js and/or browser version
placeholder: E.g. "node 16.2.0" or "Safari 15"
validations:
required: true
@@ -96,3 +92,4 @@ body:
placeholder: E.g. "Windows 10", "MacOS 12.5", "MCC Interim Linux 0.99.p8"
validations:
required: true

View File

@@ -1,104 +0,0 @@
module.exports = function(grunt) {
var pkg = require("./package.json");
global.jasmineVersion = pkg.version;
grunt.initConfig({
pkg: pkg,
concat: require('./grunt/config/concat.js'),
sass: require('./grunt/config/sass.js'),
compress: require('./grunt/config/compress.js'),
cssUrlEmbed: require('./grunt/config/cssUrlEmbed.js')
});
require('load-grunt-tasks')(grunt);
grunt.loadTasks('grunt/tasks');
grunt.registerTask('default', ['sass:dist', "cssUrlEmbed"]);
grunt.registerTask('buildDistribution',
'Builds and lints jasmine.js, jasmine-html.js, jasmine.css',
[
'sass:dist',
"cssUrlEmbed",
'concat'
]
);
grunt.registerTask("execSpecsInNode",
"Run Jasmine core specs in Node.js",
function() {
verifyNoGlobals(() => require('./lib/jasmine-core.js').noGlobals());
const done = this.async(),
Jasmine = require('jasmine'),
jasmineCore = require('./lib/jasmine-core.js'),
jasmine = new Jasmine({jasmineCore: jasmineCore});
jasmine.loadConfigFile('./spec/support/jasmine.json');
jasmine.exitOnCompletion = false;
jasmine.execute().then(
result => done(result.overallStatus === 'passed'),
err => {
console.error(err);
done(false);
}
);
}
);
grunt.registerTask("execSpecsInParallel",
"Run Jasmine core specs in parallel in Node.js",
function() {
// Need to require this here rather than at the top of the file
// so that we don't break verifyNoGlobals above by loading jasmine-core
// too early
const ParallelRunner = require('jasmine/parallel');
let numWorkers = require('os').cpus().length;
if (process.env['CIRCLECI']) {
// On Circle CI, the above gives the number of CPU cores on the host
// computer, which is unrelated to the resources actually available
// to the container. 2 workers gives peak performance with our current
// configuration, but 4 might increase the odds of discovering any
// parallel-specific bugs.
numWorkers = 4;
}
const done = this.async();
const runner = new ParallelRunner({
jasmineCore: require('./lib/jasmine-core.js'),
numWorkers
});
runner.loadConfigFile('./spec/support/jasmine.json')
.then(() => {
runner.exitOnCompletion = false;
return runner.execute();
}).then(
jasmineDoneInfo => done(jasmineDoneInfo.overallStatus === 'passed'),
err => {
console.error(err);
done(false);
}
);
}
);
grunt.registerTask("execSpecsInNode:performance",
"Run Jasmine performance specs in Node.js",
function() {
require("shelljs").exec("node_modules/.bin/jasmine JASMINE_CONFIG_PATH=spec/support/jasmine-performance.json");
}
);
};
function verifyNoGlobals(fn) {
const initialGlobals = Object.keys(global);
fn();
const extras = Object.keys(global).filter(k => !initialGlobals.includes(k));
if (extras.length !== 0) {
throw new Error('Globals were unexpectedly created: ' + extras.join(', '));
}
}

View File

@@ -1,4 +1,4 @@
<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://raw.githubusercontent.com/jasmine/jasmine/main/images/jasmine-horizontal.svg" width="400px" alt="Jasmine"></a>
# A JavaScript Testing Framework
@@ -29,7 +29,7 @@ Microsoft Edge) as well as Node.
| Environment | Supported versions |
|-------------------|----------------------------|
| Node | 18, 20, 22 |
| Node | 18.20.5+*, 20, 22, 24 |
| Safari | 15*, 16*, 17* |
| Chrome | Evergreen |
| Firefox | Evergreen, 102*, 115*, 128 |

View File

@@ -41,7 +41,7 @@ When ready to release - specs are all green and the stories are done:
### Build standalone distribution
1. Build the standalone distribution with `grunt buildStandaloneDist`
1. Build the standalone distribution with `npm run buildStandaloneDist`
1. This will generate `dist/jasmine-standalone-<version>.zip`, which you will upload later (see "Finally" below).
### Release the core NPM module

56
eslint.config.mjs Normal file
View File

@@ -0,0 +1,56 @@
import { defineConfig } from "eslint/config";
import globals from "globals";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default defineConfig([{
extends: compat.extends("plugin:compat/recommended"),
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
// 2022 isn't exactly right, but it's the earliest version that allows
// private properties.
ecmaVersion: 2022,
sourceType: "commonjs",
},
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",
"no-console": "error",
},
}]);

View File

@@ -1,57 +0,0 @@
var standaloneLibDir = "lib/jasmine-" + jasmineVersion;
function root(path) { return "./" + path; }
function libJasmineCore(path) { return root("lib/jasmine-core/" + path); }
function dist(path) { return root("dist/" + path); }
module.exports = {
standalone: {
options: {
archive: root("dist/jasmine-standalone-" + global.jasmineVersion + ".zip")
},
files: [
{ src: [ root("LICENSE") ] },
{
src: [ "jasmine_favicon.png"],
dest: standaloneLibDir,
expand: true,
cwd: root("images")
},
{
src: [
"jasmine.js",
"jasmine-html.js",
"jasmine.css"
],
dest: standaloneLibDir,
expand: true,
cwd: libJasmineCore("")
},
{
src: [ "boot0.js", "boot1.js" ],
dest: standaloneLibDir,
expand: true,
cwd: libJasmineCore("")
},
{
src: [ "SpecRunner.html" ],
dest: root(""),
expand: true,
cwd: dist("tmp")
},
{
src: [ "*.js" ],
dest: "src",
expand: true,
cwd: libJasmineCore("example/src/")
},
{
src: [ "*.js" ],
dest: "spec",
expand: true,
cwd: libJasmineCore("example/spec/")
}
]
}
};

View File

@@ -1,56 +0,0 @@
var grunt = require('grunt');
function license() {
var currentYear = "" + new Date(Date.now()).getFullYear();
return grunt.template.process(
grunt.file.read("grunt/templates/licenseBanner.js.jst"),
{ data: { currentYear: currentYear}});
}
module.exports = {
'jasmine-html': {
src: [
'src/html/requireHtml.js',
'src/html/HtmlReporter.js',
'src/html/HtmlSpecFilter.js',
'src/html/ResultsNode.js',
'src/html/QueryString.js',
'src/html/**/*.js'
],
dest: 'lib/jasmine-core/jasmine-html.js'
},
jasmine: {
src: [
'src/core/requireCore.js',
'src/core/matchers/requireMatchers.js',
'src/core/base.js',
'src/core/util.js',
'src/core/Spec.js',
'src/core/Order.js',
'src/core/Env.js',
'src/core/JsApiReporter.js',
'src/core/PrettyPrinter',
'src/core/Suite',
'src/core/**/*.js',
'src/version.js'
],
dest: 'lib/jasmine-core/jasmine.js'
},
boot0: {
src: ['src/boot/boot0.js'],
dest: 'lib/jasmine-core/boot0.js'
},
boot1: {
src: ['src/boot/boot1.js'],
dest: 'lib/jasmine-core/boot1.js'
},
options: {
banner: license(),
process: {
data: {
version: global.jasmineVersion
}
}
}
};

View File

@@ -1,7 +0,0 @@
module.exports = {
encodeWithBaseDir: {
files: {
"lib/jasmine-core/jasmine.css": ["lib/jasmine-core/jasmine.css"]
}
}
};

View File

@@ -1,13 +0,0 @@
const sass = require('sass');
module.exports = {
options: {
implementation: sass,
sourceComments: false
},
dist: {
files: {
"lib/jasmine-core/jasmine.css": "src/html/jasmine.scss"
}
}
};

View File

@@ -1,31 +0,0 @@
var grunt = require("grunt");
function standaloneTmpDir(path) { return "dist/tmp/" + path; }
grunt.registerTask("build:compileSpecRunner",
"Processes the spec runner template and writes to a tmp file",
function() {
var runnerHtml = grunt.template.process(
grunt.file.read("grunt/templates/SpecRunner.html.jst"),
{ data: { jasmineVersion: global.jasmineVersion }});
grunt.file.write(standaloneTmpDir("SpecRunner.html"), runnerHtml);
}
);
grunt.registerTask("build:cleanSpecRunner",
"Deletes the tmp spec runner file",
function() {
grunt.file.delete(standaloneTmpDir(""));
}
);
grunt.registerTask("buildStandaloneDist",
"Builds a standalone distribution",
[
"buildDistribution",
"build:compileSpecRunner",
"compress:standalone",
"build:cleanSpecRunner"
]
);

View File

@@ -21,6 +21,7 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
This file starts the process of "booting" Jasmine. It initializes Jasmine,
makes its globals available, and creates the env. This file should be loaded

View File

@@ -21,6 +21,7 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
This file finishes 'booting' Jasmine, performing all of the necessary
initialization before executing the loaded environment and all of a project's

View File

@@ -21,6 +21,7 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// eslint-disable-next-line no-var
var jasmineRequire = window.jasmineRequire || require('./jasmine.js');
@@ -155,8 +156,10 @@ jasmineRequire.HtmlReporter = function(j$) {
if (noExpectations(result)) {
const noSpecMsg = "Spec '" + result.fullName + "' has no expectations.";
if (result.status === 'failed') {
// eslint-disable-next-line no-console
console.error(noSpecMsg);
} else {
// eslint-disable-next-line no-console
console.warn(noSpecMsg);
}
}

View File

@@ -21,6 +21,7 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// eslint-disable-next-line no-unused-vars,no-var
var getJasmineRequireObj = (function(jasmineGlobal) {
let jasmineRequire;
@@ -252,9 +253,7 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
j$.isObject_ = function(value) {
return (
!j$.util.isUndefined(value) && value !== null && j$.isA_('Object', value)
);
return value !== undefined && value !== null && j$.isA_('Object', value);
};
j$.isString_ = function(value) {
@@ -675,10 +674,6 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
getJasmineRequireObj().util = function(j$) {
const util = {};
util.isUndefined = function(obj) {
return obj === void 0;
};
util.clone = function(obj) {
if (Object.prototype.toString.apply(obj) === '[object Array]') {
return obj.slice();
@@ -726,16 +721,9 @@ getJasmineRequireObj().util = function(j$) {
return Object.prototype.hasOwnProperty.call(obj, key);
};
util.errorWithStack = function errorWithStack() {
// Don't throw and catch. That makes it harder for users to debug their
// code with exception breakpoints, and it's unnecessary since all
// supported environments populate new Error().stack
return new Error();
};
function callerFile() {
const trace = new j$.StackTrace(util.errorWithStack());
return trace.frames[2].file;
const trace = new j$.StackTrace(new Error());
return trace.frames[1].file;
}
util.jasmineFile = (function() {
@@ -791,11 +779,11 @@ getJasmineRequireObj().Spec = function(j$) {
this.onStart = attrs.onStart || function() {};
this.autoCleanClosures =
attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures;
this.getSpecName =
attrs.getSpecName ||
function() {
return '';
};
this.getPath = function() {
return attrs.getPath ? attrs.getPath(this) : [];
};
this.onLateError = attrs.onLateError || function() {};
this.catchingExceptions =
attrs.catchingExceptions ||
@@ -917,7 +905,12 @@ getJasmineRequireObj().Spec = function(j$) {
* @property {String} description - The description passed to the {@link it} that created this spec.
* @property {String} fullName - The full description including all ancestors of this spec.
* @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe().
* @property {String} filename - The name of the file the spec was defined in.
* @property {String} filename - Deprecated. The name of the file the spec was defined in.
* Note: The value may be incorrect if zone.js is installed or
* `it`/`fit`/`xit` have been replaced with versions that don't maintain the
* same call stack height as the originals. This property may be removed in
* a future version unless there is enough user interest in keeping it.
* See {@link https://github.com/jasmine/jasmine/issues/2065}.
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec.
* @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec.
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec.
@@ -1021,7 +1014,7 @@ getJasmineRequireObj().Spec = function(j$) {
};
Spec.prototype.getFullName = function() {
return this.getSpecName(this);
return this.getPath().join(' ');
};
Spec.prototype.addDeprecationWarning = function(deprecation) {
@@ -1075,6 +1068,10 @@ getJasmineRequireObj().Spec = function(j$) {
* @since 2.0.0
*/
Object.defineProperty(Spec.prototype, 'metadata', {
// NOTE: Although most of jasmine-core only exposes these metadata objects,
// actual Spec instances are still passed to Configuration#specFilter. Until
// that is fixed, it's important to make sure that all metadata properties
// also exist in compatible form on the underlying Spec.
get: function() {
if (!this.metadata_) {
this.metadata_ = {
@@ -1103,7 +1100,16 @@ getJasmineRequireObj().Spec = function(j$) {
* @returns {string}
* @since 2.0.0
*/
getFullName: this.getFullName.bind(this)
getFullName: this.getFullName.bind(this),
/**
* The full path of the spec, as an array of names.
* @name Spec#getPath
* @function
* @returns {Array.<string>}
* @since 5.7.0
*/
getPath: this.getPath.bind(this)
};
}
@@ -1545,7 +1551,9 @@ getJasmineRequireObj().Env = function(j$) {
// If we get here, all results have been reported and there's nothing we
// can do except log the result and hope the user sees it.
// eslint-disable-next-line no-console
console.error('Jasmine received a result after the suite finished:');
// eslint-disable-next-line no-console
console.error(expectationResult);
}
@@ -2296,7 +2304,7 @@ getJasmineRequireObj().Anything = function(j$) {
function Anything() {}
Anything.prototype.asymmetricMatch = function(other) {
return !j$.util.isUndefined(other) && other !== null;
return other !== undefined && other !== null;
};
Anything.prototype.jasmineToString = function() {
@@ -2693,7 +2701,8 @@ getJasmineRequireObj().buildExpectationResult = function(j$) {
* in a future release. In many Jasmine configurations they are passed
* through JSON serialization and deserialization, which is inherently
* lossy. In such cases, the expected and actual values may be placeholders
* or approximations of the original objects.
* or approximations of the original objects. jasmine-browser-runner 3.0 and
* later omits them entirely.
*
* @typedef ExpectationResult
* @property {String} matcherName - The name of the matcher that was executed for this expectation.
@@ -2783,7 +2792,7 @@ getJasmineRequireObj().CallTracker = function(j$) {
this.track = function(context) {
if (opts.cloneArgs) {
context.args = j$.util.cloneArgs(context.args);
context.args = opts.argsCloner(context.args);
}
calls.push(context);
};
@@ -2891,13 +2900,15 @@ getJasmineRequireObj().CallTracker = function(j$) {
};
/**
* Set this spy to do a shallow clone of arguments passed to each invocation.
* Set this spy to do a clone of arguments passed to each invocation.
* @name Spy#calls#saveArgumentsByValue
* @since 2.5.0
* @param {Function} [argsCloner] A function to use to clone the arguments. Defaults to a shallow cloning function.
* @function
*/
this.saveArgumentsByValue = function() {
this.saveArgumentsByValue = function(argsCloner = j$.util.cloneArgs) {
opts.cloneArgs = true;
opts.argsCloner = argsCloner;
};
this.unverifiedCount = function() {
@@ -2954,7 +2965,7 @@ getJasmineRequireObj().clearStack = function(j$) {
function getUnclampedSetTimeout(global) {
const { setTimeout } = global;
if (j$.util.isUndefined(global.MessageChannel)) {
if (!global.MessageChannel) {
return setTimeout;
}
@@ -3014,10 +3025,7 @@ getJasmineRequireObj().clearStack = function(j$) {
// Unlike browsers, Node doesn't require us to do a periodic setTimeout
// so we avoid the overhead.
return nodeQueueMicrotaskImpl(global);
} else if (
SAFARI_OR_WIN_WEBKIT ||
j$.util.isUndefined(global.MessageChannel) /* tests */
) {
} else if (SAFARI_OR_WIN_WEBKIT || !global.MessageChannel /* tests */) {
// queueMicrotask is dramatically faster than MessageChannel in Safari
// and other WebKit-based browsers, such as the one distributed by Playwright
// to test Safari-like behavior on Windows.
@@ -3065,6 +3073,15 @@ getJasmineRequireObj().Clock = function() {
let installed = false;
let delayedFunctionScheduler;
let timer;
// Tracks how the clock ticking behaves.
// By default, the clock only ticks when the user manually calls a tick method.
// There is also an 'auto' mode which will advance the clock automatically to
// to the next task. Once enabled, there is currently no mechanism for users
// to disable the auto ticking.
let tickMode = {
mode: 'manual',
counter: 0
};
this.FakeTimeout = FakeTimeout;
@@ -3096,6 +3113,10 @@ getJasmineRequireObj().Clock = function() {
* @function
*/
this.uninstall = function() {
// Ensure auto ticking loop is aborted when clock is uninstalled
if (tickMode.mode === 'auto') {
tickMode = { mode: 'manual', counter: tickMode.counter + 1 };
}
delayedFunctionScheduler = null;
mockDate.uninstall();
replace(global, realTimingFunctions);
@@ -3174,8 +3195,104 @@ getJasmineRequireObj().Clock = function() {
}
};
/**
* Updates the clock to automatically advance time.
*
* With this mode, the clock advances to the first scheduled timer and fires it, in a loop.
* Between each timer, it will also break the event loop, allowing any scheduled promise
callbacks to execute _before_ running the next one.
*
* This mode allows tests to be authored in a way that does not need to be aware of the
* mock clock. Consequently, tests which have been authored without a mock clock installed
* can one with auto tick enabled without any other updates to the test logic.
*
* In many cases, this can greatly improve test execution speed because asynchronous tasks
* will execute as quickly as possible rather than waiting real time to complete.
*
* Furthermore, tests can be authored in a consistent manner. They can always be written in an asynchronous style
* rather than having `tick` sprinkled throughout the tests with mock time in order to manually
* advance the clock.
*
* When auto tick is enabled, `tick` can still be used to synchronously advance the clock if necessary.
* @name Clock#autoTick
* @function
* @since 5.7.0
*/
this.autoTick = function() {
if (tickMode.mode === 'auto') {
return;
}
tickMode = { mode: 'auto', counter: tickMode.counter + 1 };
advanceUntilModeChanges();
};
return this;
// Advances the Clock's time until the mode changes.
//
// The time is advanced asynchronously, giving microtasks and events a chance
// to run before each timer runs.
//
// @function
// @return {!Promise<undefined>}
async function advanceUntilModeChanges() {
if (!installed) {
throw new Error(
'Mock clock is not installed, use jasmine.clock().install()'
);
}
const { counter } = tickMode;
while (true) {
await newMacrotask();
if (
tickMode.counter !== counter ||
!installed ||
delayedFunctionScheduler === null
) {
return;
}
if (!delayedFunctionScheduler.isEmpty()) {
delayedFunctionScheduler.runNextQueuedFunction(function(millis) {
mockDate.tick(millis);
});
}
}
}
// Waits until a new macro task.
//
// Used with autoTick(), which is meant to act when the test is waiting, we need
// to insert ourselves in the macro task queue.
//
// @return {!Promise<undefined>}
async function newMacrotask() {
if (NODE_JS) {
// setImmediate is generally faster than setTimeout in Node
// https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#setimmediate-vs-settimeout
return new Promise(resolve => void setImmediate(resolve));
}
// MessageChannel ensures that setTimeout is not throttled to 4ms.
// https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#reasons_for_delays_longer_than_specified
// https://stackblitz.com/edit/stackblitz-starters-qtlpcc
// Note: This trick does not work in Safari, which will still throttle the setTimeout
const channel = new MessageChannel();
await new Promise(resolve => {
channel.port1.onmessage = resolve;
channel.port2.postMessage(undefined);
});
channel.port1.close();
channel.port2.close();
// setTimeout ensures that we interleave with other setTimeouts.
await new Promise(resolve => {
realTimingFunctions.setTimeout.call(global, resolve);
});
}
function originalTimingFunctionsIntact() {
return (
global.setTimeout === realTimingFunctions.setTimeout &&
@@ -3325,7 +3442,7 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
this.scheduledLookup_ = [];
this.scheduledFunctions_ = {};
this.currentTime_ = 0;
this.delayedFnCount_ = 0;
this.delayedFnStartCount_ = 1e12; // arbitrarily large number to avoid collisions with native timer IDs;
this.deletedKeys_ = [];
this.tick = function(millis, tickDate) {
@@ -3357,7 +3474,7 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
}
millis = millis || 0;
timeoutKey = timeoutKey || ++this.delayedFnCount_;
timeoutKey = timeoutKey || ++this.delayedFnStartCount_;
runAtMillis = runAtMillis || this.currentTime_ + millis;
const funcToSchedule = {
@@ -3406,6 +3523,37 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
}
};
// Returns whether there are any scheduled functions.
// Returns true if there are any scheduled functions, otherwise false.
this.isEmpty = function() {
return this.scheduledFunctions_.length === 0;
};
// Runs the next timeout in the queue, advancing the clock.
this.runNextQueuedFunction = function(tickDate) {
if (this.scheduledLookup_.length === 0) {
return;
}
const newCurrentTime = this.scheduledLookup_[0];
if (newCurrentTime >= this.currentTime_) {
tickDate(newCurrentTime - this.currentTime_);
this.currentTime_ = newCurrentTime;
}
const funcsAtTime = this.scheduledFunctions_[this.currentTime_];
const fn = funcsAtTime.shift();
if (funcsAtTime.length === 0) {
delete this.scheduledFunctions_[this.currentTime_];
this.scheduledLookup_.splice(0, 1);
}
if (fn.recurring) {
this.reschedule_(fn);
}
fn.funcToCall.apply(null, fn.params || []);
};
return this;
}
@@ -3542,6 +3690,7 @@ getJasmineRequireObj().Deprecator = function(j$) {
Deprecator.prototype.log_ = function(runnable, deprecation, options) {
if (j$.isError_(deprecation)) {
// eslint-disable-next-line no-console
console.error(deprecation);
return;
}
@@ -3564,12 +3713,13 @@ getJasmineRequireObj().Deprecator = function(j$) {
context += '\n' + verboseNote;
}
// eslint-disable-next-line no-console
console.error('DEPRECATION: ' + deprecation + context);
};
Deprecator.prototype.stackTrace_ = function() {
const formatter = new j$.ExceptionFormatter();
return formatter.stack(j$.util.errorWithStack()).replace(/^Error\n/m, '');
return formatter.stack(new Error()).replace(/^Error\n/m, '');
};
Deprecator.prototype.report_ = function(runnable, deprecation, options) {
@@ -3879,7 +4029,7 @@ getJasmineRequireObj().Expectation = function(j$) {
return function() {
// Capture the call stack here, before we go async, so that it will contain
// frames that are relevant to the user instead of just parts of Jasmine.
const errorForStack = j$.util.errorWithStack();
const errorForStack = new Error();
return this.expector
.compare(name, matcherFactory, arguments)
@@ -4154,135 +4304,38 @@ getJasmineRequireObj().formatErrorMsg = function() {
};
getJasmineRequireObj().GlobalErrors = function(j$) {
function GlobalErrors(global) {
global = global || j$.getGlobal();
class GlobalErrors {
#adapter;
#handlers;
#overrideHandler;
#onRemoveOverrideHandler;
const handlers = [];
let overrideHandler = null,
onRemoveOverrideHandler = null;
constructor(global) {
global = global || j$.getGlobal();
const dispatchError = this.#dispatchError.bind(this);
function onBrowserError(event) {
dispatchBrowserError(event.error, event);
}
function dispatchBrowserError(error, event) {
if (overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
overrideHandler(error);
return;
}
const handler = handlers[handlers.length - 1];
if (handler) {
handler(error, event);
} else {
throw error;
}
}
this.originalHandlers = {};
this.jasmineHandlers = {};
this.installOne_ = function installOne_(errorType, jasmineMessage) {
function taggedOnError(error) {
if (j$.isError_(error)) {
error.jasmineMessage = jasmineMessage + ': ' + error;
} else {
let substituteMsg;
if (error) {
substituteMsg = jasmineMessage + ': ' + error;
} else {
substituteMsg = jasmineMessage + ' with no error or message';
}
if (errorType === 'unhandledRejection') {
substituteMsg +=
'\n' +
'(Tip: to get a useful stack trace, use ' +
'Promise.reject(new Error(...)) instead of Promise.reject(' +
(error ? '...' : '') +
').)';
}
error = new Error(substituteMsg);
}
const handler = handlers[handlers.length - 1];
if (overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
overrideHandler(error);
return;
}
if (handler) {
handler(error);
} else {
throw error;
}
}
this.originalHandlers[errorType] = global.process.listeners(errorType);
this.jasmineHandlers[errorType] = taggedOnError;
global.process.removeAllListeners(errorType);
global.process.on(errorType, taggedOnError);
this.uninstall = function uninstall() {
const errorTypes = Object.keys(this.originalHandlers);
for (const errorType of errorTypes) {
global.process.removeListener(
errorType,
this.jasmineHandlers[errorType]
);
for (let i = 0; i < this.originalHandlers[errorType].length; i++) {
global.process.on(errorType, this.originalHandlers[errorType][i]);
}
delete this.originalHandlers[errorType];
delete this.jasmineHandlers[errorType];
}
};
};
this.install = function install() {
if (
global.process &&
global.process.listeners &&
j$.isFunction_(global.process.on)
) {
this.installOne_('uncaughtException', 'Uncaught exception');
this.installOne_('unhandledRejection', 'Unhandled promise rejection');
this.#adapter = new NodeAdapter(global, dispatchError);
} else {
global.addEventListener('error', onBrowserError);
const browserRejectionHandler = function browserRejectionHandler(
event
) {
if (j$.isError_(event.reason)) {
event.reason.jasmineMessage =
'Unhandled promise rejection: ' + event.reason;
dispatchBrowserError(event.reason, event);
} else {
dispatchBrowserError(
'Unhandled promise rejection: ' + event.reason,
event
);
}
};
global.addEventListener('unhandledrejection', browserRejectionHandler);
this.uninstall = function uninstall() {
global.removeEventListener('error', onBrowserError);
global.removeEventListener(
'unhandledrejection',
browserRejectionHandler
);
};
this.#adapter = new BrowserAdapter(global, dispatchError);
}
};
this.#handlers = [];
this.#overrideHandler = null;
this.#onRemoveOverrideHandler = null;
}
install() {
this.#adapter.install();
}
uninstall() {
this.#adapter.uninstall();
}
// The listener at the top of the stack will be called with two arguments:
// the error and the event. Either of them may be falsy.
@@ -4291,35 +4344,183 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
// browsers but will be falsy in Node.
// Listeners that are pushed after spec files have been loaded should be
// able to just use the error parameter.
this.pushListener = function pushListener(listener) {
handlers.push(listener);
};
pushListener(listener) {
this.#handlers.push(listener);
}
this.popListener = function popListener(listener) {
popListener(listener) {
if (!listener) {
throw new Error('popListener expects a listener');
}
handlers.pop();
};
this.#handlers.pop();
}
this.setOverrideListener = function(listener, onRemove) {
if (overrideHandler) {
setOverrideListener(listener, onRemove) {
if (this.#overrideHandler) {
throw new Error("Can't set more than one override listener at a time");
}
overrideHandler = listener;
onRemoveOverrideHandler = onRemove;
};
this.#overrideHandler = listener;
this.#onRemoveOverrideHandler = onRemove;
}
this.removeOverrideListener = function() {
if (onRemoveOverrideHandler) {
onRemoveOverrideHandler();
removeOverrideListener() {
if (this.#onRemoveOverrideHandler) {
this.#onRemoveOverrideHandler();
}
overrideHandler = null;
onRemoveOverrideHandler = null;
};
this.#overrideHandler = null;
this.#onRemoveOverrideHandler = null;
}
// Either error or event may be undefined
#dispatchError(error, event) {
if (this.#overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
this.#overrideHandler(error);
return;
}
const handler = this.#handlers[this.#handlers.length - 1];
if (handler) {
handler(error, event);
} else {
throw error;
}
}
}
class BrowserAdapter {
#global;
#dispatchError;
#onError;
#onUnhandledRejection;
constructor(global, dispatchError) {
this.#global = global;
this.#dispatchError = dispatchError;
this.#onError = event => this.#dispatchError(event.error, event);
this.#onUnhandledRejection = this.#unhandledRejectionHandler.bind(this);
}
install() {
this.#global.addEventListener('error', this.#onError);
this.#global.addEventListener(
'unhandledrejection',
this.#onUnhandledRejection
);
}
uninstall() {
this.#global.removeEventListener('error', this.#onError);
this.#global.removeEventListener(
'unhandledrejection',
this.#onUnhandledRejection
);
}
#unhandledRejectionHandler(event) {
if (j$.isError_(event.reason)) {
event.reason.jasmineMessage =
'Unhandled promise rejection: ' + event.reason;
this.#dispatchError(event.reason, event);
} else {
this.#dispatchError(
'Unhandled promise rejection: ' + event.reason,
event
);
}
}
}
class NodeAdapter {
#global;
#dispatchError;
#originalHandlers;
#jasmineHandlers;
#onError;
#onUnhandledRejection;
constructor(global, dispatchError) {
this.#global = global;
this.#dispatchError = dispatchError;
this.#jasmineHandlers = {};
this.#originalHandlers = {};
this.#onError = error =>
this.#eventHandler(error, 'uncaughtException', 'Uncaught exception');
this.#onUnhandledRejection = error =>
this.#eventHandler(
error,
'unhandledRejection',
'Unhandled promise rejection'
);
}
install() {
this.#installHandler('uncaughtException', this.#onError);
this.#installHandler('unhandledRejection', this.#onUnhandledRejection);
}
uninstall() {
const errorTypes = Object.keys(this.#originalHandlers);
for (const errorType of errorTypes) {
this.#global.process.removeListener(
errorType,
this.#jasmineHandlers[errorType]
);
for (let i = 0; i < this.#originalHandlers[errorType].length; i++) {
this.#global.process.on(
errorType,
this.#originalHandlers[errorType][i]
);
}
delete this.#originalHandlers[errorType];
delete this.#jasmineHandlers[errorType];
}
}
#installHandler(errorType, handler) {
this.#originalHandlers[errorType] = this.#global.process.listeners(
errorType
);
this.#jasmineHandlers[errorType] = handler;
this.#global.process.removeAllListeners(errorType);
this.#global.process.on(errorType, handler);
}
#eventHandler(error, errorType, jasmineMessage) {
if (j$.isError_(error)) {
error.jasmineMessage = jasmineMessage + ': ' + error;
} else {
let substituteMsg;
if (error) {
substituteMsg = jasmineMessage + ': ' + error;
} else {
substituteMsg = jasmineMessage + ' with no error or message';
}
if (errorType === 'unhandledRejection') {
substituteMsg +=
'\n' +
'(Tip: to get a useful stack trace, use ' +
'Promise.reject(new Error(...)) instead of Promise.reject(' +
(error ? '...' : '') +
').)';
}
error = new Error(substituteMsg);
}
this.#dispatchError(error);
}
}
return GlobalErrors;
@@ -4713,13 +4914,14 @@ getJasmineRequireObj().DiffBuilder = function(j$) {
const actualCustom = this.prettyPrinter_.customFormat_(actual);
const expectedCustom = this.prettyPrinter_.customFormat_(expected);
const useCustom = !(
j$.util.isUndefined(actualCustom) &&
j$.util.isUndefined(expectedCustom)
);
const useCustom =
actualCustom !== undefined || expectedCustom !== undefined;
if (useCustom) {
messages.push(wrapPrettyPrinted(actualCustom, expectedCustom, path));
const prettyActual = actualCustom || this.prettyPrinter_(actual);
const prettyExpected =
expectedCustom || this.prettyPrinter_(expected);
messages.push(wrapPrettyPrinted(prettyActual, prettyExpected, path));
return false; // don't recurse further
}
@@ -4972,13 +5174,13 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
bStack,
diffBuilder
);
if (!j$.util.isUndefined(asymmetricResult)) {
if (asymmetricResult !== undefined) {
return asymmetricResult;
}
for (const tester of this.customTesters_) {
const customTesterResult = tester(a, b);
if (!j$.util.isUndefined(customTesterResult)) {
if (customTesterResult !== undefined) {
if (!customTesterResult) {
diffBuilder.recordMismatch();
}
@@ -5861,6 +6063,7 @@ getJasmineRequireObj().toBeInstanceOf = function(j$) {
try {
expectedMatcher = new j$.Any(expected);
pass = expectedMatcher.asymmetricMatch(actual);
// eslint-disable-next-line no-unused-vars
} catch (error) {
throw new Error(
usageError('Expected value is not a constructor function')
@@ -7359,7 +7562,7 @@ getJasmineRequireObj().MockDate = function(j$) {
if (mockDate instanceof GlobalDate) {
currentTime = mockDate.getTime();
} else {
if (!j$.util.isUndefined(mockDate)) {
if (mockDate !== undefined) {
throw new Error(
'The argument to jasmine.clock().mockDate(), if specified, ' +
'should be a Date instance.'
@@ -7576,7 +7779,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
if (customFormatResult) {
this.emitScalar(customFormatResult);
} else if (j$.util.isUndefined(value)) {
} else if (value === undefined) {
this.emitScalar('undefined');
} else if (value === null) {
this.emitScalar('null');
@@ -7595,7 +7798,11 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
} else if (value instanceof RegExp) {
this.emitScalar(value.toString());
} else if (typeof value === 'function') {
this.emitScalar('Function');
if (value.name) {
this.emitScalar(`Function '${value.name}'`);
} else {
this.emitScalar('Function');
}
} else if (j$.isDomNode(value)) {
if (value.tagName) {
this.emitDomElement(value);
@@ -7618,6 +7825,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
) {
try {
this.emitScalar(value.toString());
// eslint-disable-next-line no-unused-vars
} catch (e) {
this.emitScalar('has-invalid-toString-method');
}
@@ -7864,6 +8072,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
value.toString !== Object.prototype.toString &&
value.toString() !== Object.prototype.toString.call(value)
);
// eslint-disable-next-line no-unused-vars
} catch (e) {
// The custom toString() threw.
return true;
@@ -7942,6 +8151,7 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}
function fallbackOnMultipleDone() {
// eslint-disable-next-line no-console
console.error(
new Error(
"An asynchronous function called its 'done' " +
@@ -8055,6 +8265,7 @@ getJasmineRequireObj().QueueRunner = function(j$) {
// Any error we catch here is probably due to a bug in Jasmine,
// and it's not likely to end up anywhere useful if we let it
// propagate. Log it so it can at least show up when debugging.
// eslint-disable-next-line no-console
console.error(error);
}
}
@@ -9507,7 +9718,7 @@ getJasmineRequireObj().Spy = function(j$) {
"Spy '" +
strategyArgs.name +
"' received a call with arguments " +
j$.basicPrettyPrinter_(Array.prototype.slice.call(args)) +
matchersUtil.pp(Array.prototype.slice.call(args)) +
' but all configured strategies specify other arguments.'
);
} else {
@@ -9667,7 +9878,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
this.spyOn = function(obj, methodName) {
const getErrorMsg = spyOnMsg;
if (j$.util.isUndefined(obj) || obj === null) {
if (obj === undefined || obj === null) {
throw new Error(
getErrorMsg(
'could not find an object to spy upon for ' + methodName + '()'
@@ -9675,11 +9886,11 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
);
}
if (j$.util.isUndefined(methodName) || methodName === null) {
if (methodName === undefined || methodName === null) {
throw new Error(getErrorMsg('No method name supplied'));
}
if (j$.util.isUndefined(obj[methodName])) {
if (obj[methodName] === undefined) {
throw new Error(getErrorMsg(methodName + '() method does not exist'));
}
@@ -9744,7 +9955,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
accessType = accessType || 'get';
if (j$.util.isUndefined(obj)) {
if (!obj) {
throw new Error(
getErrorMsg(
'spyOn could not find an object to spy upon for ' +
@@ -9754,7 +9965,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
);
}
if (j$.util.isUndefined(propertyName)) {
if (propertyName === undefined) {
throw new Error(getErrorMsg('No property name supplied'));
}
@@ -9819,7 +10030,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
};
this.spyOnAllFunctions = function(obj, includeNonEnumerable) {
if (j$.util.isUndefined(obj)) {
if (!obj) {
throw new Error(
'spyOnAllFunctions could not find an object to spy upon'
);
@@ -9894,6 +10105,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
let value;
try {
value = obj[prop];
// eslint-disable-next-line no-unused-vars
} catch (e) {
return false;
}
@@ -10325,7 +10537,12 @@ getJasmineRequireObj().Suite = function(j$) {
* @property {String} description - The description text passed to the {@link describe} that made this suite.
* @property {String} fullName - The full description including all ancestors of this suite.
* @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe().
* @property {String} filename - The name of the file the suite was defined in.
* @property {String} filename - Deprecated. The name of the file the suite was defined in.
* Note: The value may be incorrect if zone.js is installed or
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions that
* don't maintain the same call stack height as the originals. This property
* may be removed in a future version unless there is enough user interest
* in keeping it. See {@link https://github.com/jasmine/jasmine/issues/2065}.
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite.
* @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite.
@@ -10808,9 +11025,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
resultCallback: (result, next) => {
this.specResultCallback_(spec, result, next);
},
getSpecName: function(spec) {
return getSpecName(spec, suite);
},
getPath: spec => this.getSpecPath_(spec, suite),
onStart: (spec, next) => this.specStarted_(spec, suite, next),
description: description,
userContext: function() {
@@ -10827,6 +11042,17 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
return spec;
}
getSpecPath_(spec, suite) {
const path = [spec.description];
while (suite && suite !== this.topSuite) {
path.unshift(suite.description);
suite = suite.parentSuite;
}
return path;
}
unfocusAncestor_() {
const focusedAncestor = findFocusedAncestor(
this.currentDeclarationSuite_
@@ -10890,16 +11116,6 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
};
}
function getSpecName(spec, suite) {
const fullName = [spec.description],
suiteFullName = suite.getFullName();
if (suiteFullName !== '') {
fullName.unshift(suiteFullName);
}
return fullName.join(' ');
}
return SuiteBuilder;
};
@@ -11231,5 +11447,5 @@ getJasmineRequireObj().UserContext = function(j$) {
};
getJasmineRequireObj().version = function() {
return '5.6.0';
return '5.9.0';
};

View File

@@ -1,7 +1,7 @@
{
"name": "jasmine-core",
"license": "MIT",
"version": "5.6.0",
"version": "5.9.0",
"repository": {
"type": "git",
"url": "https://github.com/jasmine/jasmine.git"
@@ -15,9 +15,11 @@
],
"scripts": {
"posttest": "eslint \"src/**/*.js\" \"spec/**/*.js\" && prettier --check \"src/**/*.js\" \"spec/**/*.js\"",
"test": "grunt --stack execSpecsInNode",
"test": "node scripts/runSpecsInNode.js",
"test:parallel": "node scripts/runSpecsInParallel.js",
"cleanup": "prettier --write \"src/**/*.js\" \"spec/**/*.js\"",
"build": "grunt buildDistribution",
"build": "node scripts/buildDistribution.js",
"buildStandaloneDist": "node scripts/buildStandaloneDist.js",
"serve": "node spec/support/localJasmineBrowser.js",
"serve:performance": "node spec/support/localJasmineBrowser.js jasmine-browser-performance.json",
"ci": "node spec/support/ci.js",
@@ -34,23 +36,20 @@
"package.json"
],
"devDependencies": {
"eslint": "^8.36.0",
"eslint-plugin-compat": "^4.0.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.24.0",
"archiver": "^7.0.1",
"css-url-embed": "^0.1.0",
"ejs": "^3.1.10",
"eslint": "^9.24.0",
"eslint-plugin-compat": "^6.0.2",
"glob": "^10.2.3",
"grunt": "^1.0.4",
"grunt-cli": "^1.3.2",
"grunt-contrib-compress": "^2.0.0",
"grunt-contrib-concat": "^2.0.0",
"grunt-css-url-embed": "^1.11.1",
"grunt-sass": "^3.0.2",
"globals": "^16.0.0",
"jasmine": "^5.0.0",
"jasmine-browser-runner": "github:jasmine/jasmine-browser-runner",
"jsdom": "^22.0.0",
"load-grunt-tasks": "^5.1.0",
"jsdom": "^26.0.0",
"prettier": "1.17.1",
"sass": "^1.58.3",
"shelljs": "^0.8.3",
"temp": "^0.9.0"
"sass": "^1.58.3"
},
"browserslist": [
"Safari >= 15",

62
release_notes/5.7.0.md Normal file
View File

@@ -0,0 +1,62 @@
# Jasmine Core 5.7.0 Release Notes
## New features
* Added [Clock#autoTick](https://jasmine.github.io/api/5.7/Clock.html#autoTick)
to automatically tick the clock asynchronously
* Merges #2042 from @atscott and @stephenfarrar
* Fixes #1725
* Expose [spec path](https://jasmine.github.io/api/5.7/Spec.html#getPath) as an
array of names in addition to the existing concatenated name
This is meant to support tools like IDE integrations that need to filter a run
to an exact set of suites/specs.
## Documentation improvements
* Documented that [SpecResult#filename](https://jasmine.github.io/api/5.7/global.html#SpecResult)
and [SuiteResult#filename](https://jasmine.github.io/api/5.7/global.html#SuiteResult)
are wrong when zone.js is present and in some cases where it/describe/etc are
replaced
* Updated docs for expected and actual properties of
[expectation results](https://jasmine.github.io/api/5.7/global.html#ExpectationResult)
## Internal improvements
* Rewrote the build system to not use Grunt
Although Grunt has served Jasmine well over the years, it was keeping us tied
to an aging and increasingly questionable set of dev dependencies.
* Updated to eslint 9
* Removed mostly-unmaintained dev dependency 'temp'
* Updated most other dev dependencies to latest versions
* Fixed sass deprecation warning
* Updated to Sauce Connect 5
* Made stop-sauce-connect script more robust
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|-------------------------|
| Node | 18**, 20, 22 |
| Safari | 15**, 16**, 17** |
| Chrome | 135* |
| Firefox | 102**, 115**, 128, 137* |
| Edge | 135* |
\* Evergreen browser. Each version of Jasmine is tested against the latest
version available at release time.<br>
\** Supported on a best-effort basis. Support for these versions may be dropped
if it becomes impractical, and bugs affecting only these versions may not be
treated as release blockers.
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

28
release_notes/5.7.1.md Normal file
View File

@@ -0,0 +1,28 @@
# Jasmine Core 5.7.1 Release Notes
## Bug fixes
* Ensure that uninstalling the clock also stops auto tick
* Merges #2057 from @atscott
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|-------------------------|
| Node | 18**, 20, 22 |
| Safari | 15**, 16**, 17** |
| Chrome | 136* |
| Firefox | 102**, 115**, 128, 138* |
| Edge | 135* |
\* Evergreen browser. Each version of Jasmine is tested against the latest
version available at release time.<br>
\** Supported on a best-effort basis. Support for these versions may be dropped
if it becomes impractical, and bugs affecting only these versions may not be
treated as release blockers.
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

44
release_notes/5.8.0.md Normal file
View File

@@ -0,0 +1,44 @@
# Jasmine Core 5.8.0 Release Notes
## New Features
* Allow passing a function to `saveArgumentsByValue` to customize how arguments
are saved
* Merges [#2062](https://github.com/jasmine/jasmine/pull/2062) from @evanwaslh
* Fixes [#1886](https://github.com/jasmine/jasmine/issues/1886)
* Use custom object formatters in spy strategy mismatch errors
* Include function names in pretty printer output
* Improve performance of autoTick in Node
* Merges [#2058](https://github.com/jasmine/jasmine/pull/2058) from @atscott
## Bug Fixes
* Fix diff building when only one side has a custom object formatter
* Fixes [#2061](https://github.com/jasmine/jasmine/issues/2061)
## Documentation improvements
* Added Node 24 to supported environments
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|-------------------------|
| Node | 18**, 20, 22, 24 |
| Safari | 15**, 16**, 17** |
| Chrome | 137* |
| Firefox | 102**, 115**, 128, 139* |
| Edge | 137* |
\* Evergreen browser. Each version of Jasmine is tested against the latest
version available at release time.<br>
\** Supported on a best-effort basis. Support for these versions may be dropped
if it becomes impractical, and bugs affecting only these versions may not be
treated as release blockers.
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

56
release_notes/5.9.0.md Normal file
View File

@@ -0,0 +1,56 @@
# Jasmine Core 5.9.0 Release Notes
## Bug fixes
* Avoid generating mock clock timer IDs that conflict with native ones
* Fixes [#2068](https://github.com/jasmine/jasmine/issues/2068)
* Merges [#2069](https://github.com/jasmine/jasmine/pull/2069) from @atscott
## Deprecations and changes to platform support
* Node versions before 18.20.5 are no longer supported
Older 18.x versions might still work, but Jasmine is no longer tested in them
prior to release.
* Document that the filename properties of suite and spec results are deprecated
These properties are incorrect in many configurations. They'll be removed in
the next major release unless there is enough user interest in fixing them.
See <https://github.com/jasmine/jasmine/issues/2065>.
## Internal improvements
* Extensive GlobalErrors refactoring
* Removed many of the error dispatching differences between browsers and Node
* Split into portable and platform-specific parts
* Converted to ES6 classes
* Removed unnecessary errorWithStack helper
Jasmine no longer runs on platforms that create errors without stack traces.
* Removed protections against user code redefining undefined
Jasmine no longer runs on platforms that allow redefining undefined.
* Removed rimraf and shelljs dev dependencies
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|-------------------------|
| Node | 18.20.5**, 20, 22, 24 |
| Safari | 15**, 16**, 17** |
| Chrome | 138* |
| Firefox | 102**, 115**, 128, 140* |
| Edge | 138* |
\* Evergreen browser. Each version of Jasmine is tested against the latest
version available at release time.<br>
\** Supported on a best-effort basis. Support for these versions may be dropped
if it becomes impractical, and bugs affecting only these versions may not be
treated as release blockers.
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

View File

@@ -0,0 +1,3 @@
const buildDistribution = require('./lib/buildDistribution');
buildDistribution();

View File

@@ -0,0 +1,86 @@
const fs = require('fs');
const os = require('os');
const path = require('path');
const glob = require('glob');
const ejs = require('ejs');
const archiver = require('archiver');
const buildDistribution = require('./lib/buildDistribution');
const prefix = path.join(os.tmpdir(), 'jasmine-build-standalone');
const tmpDir = fs.mkdtempSync(prefix);
buildStandaloneDist().finally(function() {
fs.rmSync(tmpDir, { recursive: true });
});
async function buildStandaloneDist() {
buildDistribution();
const pkg = JSON.parse(fs.readFileSync('package.json'));
compileSpecRunner(pkg.version);
await zipStandaloneDist(pkg.version);
}
function compileSpecRunner(jasmineVersion) {
const template = fs.readFileSync('src/SpecRunner.html.ejs',
{encoding: 'utf8'});
const runnerHtml = ejs.render(template, { jasmineVersion });
fs.writeFileSync(path.join(tmpDir, 'SpecRunner.html'), runnerHtml,
{encoding: 'utf8'});
}
async function zipStandaloneDist(jasmineVersion) {
const fileGroups = [
{
src: [
'LICENSE',
path.join(tmpDir, 'SpecRunner.html'),
]
},
{
src: [
'images/jasmine_favicon.png',
'lib/jasmine-core/jasmine.js',
'lib/jasmine-core/jasmine-html.js',
'lib/jasmine-core/jasmine.css',
'lib/jasmine-core/boot0.js',
'lib/jasmine-core/boot1.js',
],
destDir: 'lib/jasmine-' + jasmineVersion
},
{
src: glob.sync('lib/jasmine-core/example/src/*.js'),
destDir: 'src'
},
{
src: glob.sync('lib/jasmine-core/example/spec/*.js'),
destDir: 'spec'
}
];
const destPath = `./dist/jasmine-standalone-${jasmineVersion}.zip`;
const output = fs.createWriteStream(destPath);
const archive = archiver('zip');
const done = new Promise(function(resolve, reject) {
output.on('close', resolve);
archive.on('warning', reject);
archive.on('error', reject);
});
archive.pipe(output);
for (const group of fileGroups) {
for (const srcPath of group.src) {
let destPath = path.basename(srcPath);
if (group.destDir) {
destPath = `${group.destDir}/${destPath}`;
}
archive.file(srcPath, {name: destPath});
}
}
archive.finalize();
await done;
}

View File

@@ -0,0 +1,129 @@
const fs = require('fs');
const sass = require('sass');
const glob = require('glob');
const ejs = require('ejs');
const cssUrlEmbed = require('css-url-embed');
function buildDistribution() {
compileSass();
embedCssAssets();
concatFiles();
}
function embedCssAssets() {
const cssPath = 'lib/jasmine-core/jasmine.css';
cssUrlEmbed.processFile(cssPath, cssPath, function(filePath) {
if (filePath.endsWith('.png')) {
return 'image/png';
} else if (filePath.endsWith('.svg')) {
return 'image/svg+xml';
} else {
throw new Error(`Don't know MIME type for file: ${filePath}`);
}
});
}
function compileSass() {
const output = sass.compile('src/html/jasmine.scss');
fs.writeFileSync('lib/jasmine-core/jasmine.css', output.css,
{encoding: 'utf8'});
}
function concatFiles() {
const pkg = JSON.parse(fs.readFileSync('package.json'));
const configs = [
{
src: [
'src/html/requireHtml.js',
'src/html/HtmlReporter.js',
'src/html/HtmlSpecFilter.js',
'src/html/ResultsNode.js',
'src/html/QueryString.js',
'src/html/**/*.js'
],
dest: 'lib/jasmine-core/jasmine-html.js',
},
{
dest: 'lib/jasmine-core/jasmine.js',
src: [
'src/core/requireCore.js',
'src/core/matchers/requireMatchers.js',
'src/core/base.js',
'src/core/util.js',
'src/core/Spec.js',
'src/core/Order.js',
'src/core/Env.js',
'src/core/JsApiReporter.js',
'src/core/PrettyPrinter',
'src/core/Suite',
'src/core/**/*.js',
{
template: 'src/version.js',
data: {version: pkg.version}
},
],
},
{
dest: 'lib/jasmine-core/boot0.js',
src: ['src/boot/boot0.js'],
},
{
dest: 'lib/jasmine-core/boot1.js',
src: ['src/boot/boot1.js'],
}
];
const licenseBanner = {
template: 'src/licenseBanner.js.ejs',
data: {currentYear: new Date(Date.now()).getFullYear()}
};
for (const {src, dest} of configs) {
src.unshift(licenseBanner);
function expand(srcListEntry) {
if (typeof srcListEntry === 'object') {
return srcListEntry;
}
return glob.sync(srcListEntry)
.sort(function (a, b) {
// Match the sort order of previous build tools, so that the
// output is the same.
a = a.toLowerCase();
b = b.toLowerCase();
if (a < b) {
return -1;
} else if (a === b) {
return 0;
} else {
return 1;
}
});
}
const srcs = src.flatMap(expand);
const seen = new Set();
const chunks = [];
for (const s of srcs) {
let content;
if (!seen.has(s)) {
if (s.template) {
const template = fs.readFileSync(s.template, {encoding: 'utf8'});
content = ejs.render(template, s.data);
} else {
content = fs.readFileSync(s, {encoding: 'utf8'});
}
chunks.push(content);
seen.add(s);
}
}
fs.writeFileSync(dest, chunks.join('\n'), {encoding: 'utf8'});
}
}
module.exports = buildDistribution;

View File

@@ -25,15 +25,7 @@ run_browser() {
passfile=`mktemp -t jasmine-results.XXXXXX` || exit 1
failfile=`mktemp -t jasmine-results.XXXXXX` || exit 1
# 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 chrome latest
run_browser firefox latest
run_browser firefox 128

28
scripts/runSpecsInNode.js Normal file
View File

@@ -0,0 +1,28 @@
verifyNoGlobals(() => require('../lib/jasmine-core.js').noGlobals());
const Jasmine = require('jasmine');
const jasmineCore = require('../lib/jasmine-core.js');
const runner = new Jasmine({jasmineCore: jasmineCore});
runner.loadConfigFile('./spec/support/jasmine.json');
runner.exitOnCompletion = false;
runner.execute()
.then(
result => result.overallStatus === 'passed',
err => {
console.error(err);
return false;
}
)
.then(ok => process.exit(ok ? 0 : 1));
function verifyNoGlobals(fn) {
const initialGlobals = Object.keys(global);
fn();
const extras = Object.keys(global).filter(k => !initialGlobals.includes(k));
if (extras.length !== 0) {
throw new Error('Globals were unexpectedly created: ' + extras.join(', '));
}
}

View File

@@ -0,0 +1,28 @@
const ParallelRunner = require('jasmine/parallel');
const jasmineCore = require('../lib/jasmine-core.js');
let numWorkers = require('os').cpus().length;
if (process.env['CIRCLECI']) {
// On Circle CI, the above gives the number of CPU cores on the host
// computer, which is unrelated to the resources actually available
// to the container. 2 workers gives peak performance with our current
// configuration, but 4 might increase the odds of discovering any
// parallel-specific bugs.
numWorkers = 4;
}
const runner = new ParallelRunner({jasmineCore, numWorkers});
runner.loadConfigFile('./spec/support/jasmine.json')
.then(() => {
runner.exitOnCompletion = false;
return runner.execute();
})
.then(
jasmineDoneInfo => jasmineDoneInfo.overallStatus === 'passed',
err => {
console.error(err);
return false;
}
)
.then(ok => process.exit(ok ? 0 : 1));

View File

@@ -2,32 +2,19 @@
set -o errexit
set -o pipefail
if [ $# -gt 1 -o "$1" = "--help" ]; then
echo "Usage: $0 [pidfile]" 1>&2
exit
fi
if [ -z "$1" ]; then
pidfile=`mktemp`
else
pidfile="$1"
if [ -z "$SAUCE_TUNNEL_NAME" ]; then
echo "SAUCE_TUNNEL_NAME must be set" 1>&2
exit 1
fi
outfile=`mktemp`
echo "Starting Sauce Connect"
if [ -z "$SAUCE_TUNNEL_IDENTIFIER" ]; then
sc -u "$SAUCE_USERNAME" -k "$SAUCE_ACCESS_KEY" -X 4445 --pidfile "$pidfile" 2>&1 | tee "$outfile" &
else
sc -u "$SAUCE_USERNAME" -k "$SAUCE_ACCESS_KEY" -X 4445 --pidfile "$pidfile" -i "$SAUCE_TUNNEL_IDENTIFIER" 2>&1 | tee "$outfile" &
fi
sc legacy --proxy-localhost --tunnel-domains localhost --region us-west \
-u "$SAUCE_USERNAME" -k "$SAUCE_ACCESS_KEY" \
-X 4445 -i "$SAUCE_TUNNEL_NAME" 2>&1 | tee "$outfile" &
while ! fgrep "Sauce Connect is up, you may start your tests." "$outfile" > /dev/null; do
while ! fgrep "Sauce Connect is up, you may start your tests" "$outfile" > /dev/null; do
sleep 1
if ! ps -p $(cat "$pidfile") > /dev/null; then
echo "Sauce Connect exited"
exit 1
fi
done
if ! nc -z localhost 4445; then

View File

@@ -2,32 +2,38 @@
set -o errexit
set -o pipefail
if [ -z "$1" ]; then
echo "Usage: $0 sauce-connect-pid" 1>&2
exit
fi
pid="$1"
echo "PID: $pid"
echo "Stopping Sauce Connect"
# Sauce Connect docs say that we can just kill -9 it if we don't care about
# failing any ongoing sessions. In practice, that sometimes works but usually
# leaks a tunnel so badly that you can't even stop it from the web UI.
# Instead of doing that, we give Sauce Connect some time to shut down
# gracefully and then give up.
kill -INT $pid
# Sauce Connect 4 docs said that we can just kill -9 it if we don't care about
# failing any ongoing sessions. In practice, that sometimes worked but usually
# leaked a tunnel so badly that you couldn't even stop it from the web UI.
#
# Sauce Connect 5 appears to be *much* more prone to hung jobs. Hung jobs have
# a shutdown deadline of *three hours*, however, they appear to usually exit
# within a minute or so. Unfortunately the only thing the Sauce Connect 5 docs
# say about shutdown is that "you can stop your tunnel from the terminal where
# Sauce Connect is running by entering Ctrl+C". Nothing is said about what to
# do if Sauce Connect doesn't exit on it own or about non-interactive usage.
#
# So we do our best to be well-behaved without assuming that Sauce Connect
# always is: send it the same signal that it would get if an interactive user
# hit ctrl-c, wait a while for it to exit, then give up so that the CI task
# doesn't keep running indefinitely.
if ! pkill -INT '^sc$'; then
echo "sc does not appear to be running" 1>&2
exit 1
fi
# Wait up to 2 minutes, then give up if it's still running
n=0
while [ $n -lt 120 ] && ps -p $pid > /dev/null; do
while [ $n -lt 120 ] && pgrep '^sc$' > /dev/null; do
sleep 1
kill -INT $pid 2> /dev/null || true
pkill -INT '^sc$' || true
n=$(($n + 1))
done
if ps -p $pid > /dev/null; then
if pgrep '^sc$' > /dev/null; then
echo "Could not shut down Sauce Connect"
exit 1
fi
exit $exitcode

View File

@@ -277,23 +277,18 @@ describe('AsyncExpectation', function() {
it('reports a passing result to the spec when the comparison passes', function() {
const matchers = {
toFoo: function() {
return {
compare: function() {
return Promise.resolve({ pass: true });
}
};
}
},
matchersUtil = {
buildFailureMessage: jasmine.createSpy('buildFailureMessage')
},
addExpectationResult = jasmine.createSpy('addExpectationResult'),
errorWithStack = new Error('errorWithStack');
spyOn(jasmineUnderTest.util, 'errorWithStack').and.returnValue(
errorWithStack
);
toFoo: function() {
return {
compare: function() {
return Promise.resolve({ pass: true });
}
};
}
};
const matchersUtil = {
buildFailureMessage: jasmine.createSpy('buildFailureMessage')
};
const addExpectationResult = jasmine.createSpy('addExpectationResult');
const expectation = jasmineUnderTest.Expectation.asyncFactory({
customAsyncMatchers: matchers,
@@ -310,7 +305,7 @@ describe('AsyncExpectation', function() {
error: undefined,
expected: 'hello',
actual: 'an actual',
errorForStack: errorWithStack
errorForStack: jasmine.any(Error)
});
});
});
@@ -329,13 +324,8 @@ describe('AsyncExpectation', function() {
buildFailureMessage: function() {
return '';
}
},
addExpectationResult = jasmine.createSpy('addExpectationResult'),
errorWithStack = new Error('errorWithStack');
spyOn(jasmineUnderTest.util, 'errorWithStack').and.returnValue(
errorWithStack
);
};
const addExpectationResult = jasmine.createSpy('addExpectationResult');
const expectation = jasmineUnderTest.Expectation.asyncFactory({
customAsyncMatchers: matchers,
@@ -352,30 +342,25 @@ describe('AsyncExpectation', function() {
actual: 'an actual',
message: '',
error: undefined,
errorForStack: errorWithStack
errorForStack: jasmine.any(Error)
});
});
});
it('reports a failing result and a custom fail message to the spec when the comparison fails', function() {
const matchers = {
toFoo: function() {
return {
compare: function() {
return Promise.resolve({
pass: false,
message: 'I am a custom message'
});
}
};
}
},
addExpectationResult = jasmine.createSpy('addExpectationResult'),
errorWithStack = new Error('errorWithStack');
spyOn(jasmineUnderTest.util, 'errorWithStack').and.returnValue(
errorWithStack
);
toFoo: function() {
return {
compare: function() {
return Promise.resolve({
pass: false,
message: 'I am a custom message'
});
}
};
}
};
const addExpectationResult = jasmine.createSpy('addExpectationResult');
const expectation = jasmineUnderTest.Expectation.asyncFactory({
actual: 'an actual',
@@ -391,32 +376,27 @@ describe('AsyncExpectation', function() {
actual: 'an actual',
message: 'I am a custom message',
error: undefined,
errorForStack: errorWithStack
errorForStack: jasmine.any(Error)
});
});
});
it('reports a failing result with a custom fail message function to the spec when the comparison fails', function() {
const matchers = {
toFoo: function() {
return {
compare: function() {
return Promise.resolve({
pass: false,
message: function() {
return 'I am a custom message';
}
});
}
};
}
},
addExpectationResult = jasmine.createSpy('addExpectationResult'),
errorWithStack = new Error('errorWithStack');
spyOn(jasmineUnderTest.util, 'errorWithStack').and.returnValue(
errorWithStack
);
toFoo: function() {
return {
compare: function() {
return Promise.resolve({
pass: false,
message: function() {
return 'I am a custom message';
}
});
}
};
}
};
const addExpectationResult = jasmine.createSpy('addExpectationResult');
const expectation = jasmineUnderTest.Expectation.asyncFactory({
customAsyncMatchers: matchers,
@@ -432,28 +412,23 @@ describe('AsyncExpectation', function() {
actual: 'an actual',
message: 'I am a custom message',
error: undefined,
errorForStack: errorWithStack
errorForStack: jasmine.any(Error)
});
});
});
it('reports a passing result to the spec when the comparison fails for a negative expectation', function() {
const matchers = {
toFoo: function() {
return {
compare: function() {
return Promise.resolve({ pass: false });
}
};
}
},
addExpectationResult = jasmine.createSpy('addExpectationResult'),
actual = 'an actual',
errorWithStack = new Error('errorWithStack');
spyOn(jasmineUnderTest.util, 'errorWithStack').and.returnValue(
errorWithStack
);
toFoo: function() {
return {
compare: function() {
return Promise.resolve({ pass: false });
}
};
}
};
const addExpectationResult = jasmine.createSpy('addExpectationResult');
const actual = 'an actual';
const expectation = jasmineUnderTest.Expectation.asyncFactory({
customAsyncMatchers: matchers,
@@ -469,7 +444,7 @@ describe('AsyncExpectation', function() {
error: undefined,
expected: 'hello',
actual: actual,
errorForStack: errorWithStack
errorForStack: jasmine.any(Error)
});
});
});
@@ -488,14 +463,9 @@ describe('AsyncExpectation', function() {
buildFailureMessage: function() {
return 'default message';
}
},
addExpectationResult = jasmine.createSpy('addExpectationResult'),
actual = 'an actual',
errorWithStack = new Error('errorWithStack');
spyOn(jasmineUnderTest.util, 'errorWithStack').and.returnValue(
errorWithStack
);
};
const addExpectationResult = jasmine.createSpy('addExpectationResult');
const actual = 'an actual';
const expectation = jasmineUnderTest.Expectation.asyncFactory({
customAsyncMatchers: matchers,
@@ -512,31 +482,26 @@ describe('AsyncExpectation', function() {
actual: actual,
message: 'default message',
error: undefined,
errorForStack: errorWithStack
errorForStack: jasmine.any(Error)
});
});
});
it('reports a failing result and a custom fail message to the spec when the comparison passes for a negative expectation', function() {
const matchers = {
toFoo: function() {
return {
compare: function() {
return Promise.resolve({
pass: true,
message: 'I am a custom message'
});
}
};
}
},
addExpectationResult = jasmine.createSpy('addExpectationResult'),
actual = 'an actual',
errorWithStack = new Error('errorWithStack');
spyOn(jasmineUnderTest.util, 'errorWithStack').and.returnValue(
errorWithStack
);
toFoo: function() {
return {
compare: function() {
return Promise.resolve({
pass: true,
message: 'I am a custom message'
});
}
};
}
};
const addExpectationResult = jasmine.createSpy('addExpectationResult');
const actual = 'an actual';
const expectation = jasmineUnderTest.Expectation.asyncFactory({
customAsyncMatchers: matchers,
@@ -552,31 +517,26 @@ describe('AsyncExpectation', function() {
actual: actual,
message: 'I am a custom message',
error: undefined,
errorForStack: errorWithStack
errorForStack: jasmine.any(Error)
});
});
});
it("reports a passing result to the spec when the 'not' comparison passes, given a negativeCompare", function() {
const matchers = {
toFoo: function() {
return {
compare: function() {
return Promise.resolve({ pass: true });
},
negativeCompare: function() {
return Promise.resolve({ pass: true });
}
};
}
},
addExpectationResult = jasmine.createSpy('addExpectationResult'),
actual = 'an actual',
errorWithStack = new Error('errorWithStack');
spyOn(jasmineUnderTest.util, 'errorWithStack').and.returnValue(
errorWithStack
);
toFoo: function() {
return {
compare: function() {
return Promise.resolve({ pass: true });
},
negativeCompare: function() {
return Promise.resolve({ pass: true });
}
};
}
};
const addExpectationResult = jasmine.createSpy('addExpectationResult');
const actual = 'an actual';
const expectation = jasmineUnderTest.Expectation.asyncFactory({
customAsyncMatchers: matchers,
@@ -592,34 +552,29 @@ describe('AsyncExpectation', function() {
actual: actual,
message: '',
error: undefined,
errorForStack: errorWithStack
errorForStack: jasmine.any(Error)
});
});
});
it("reports a failing result and a custom fail message to the spec when the 'not' comparison fails, given a negativeCompare", function() {
const matchers = {
toFoo: function() {
return {
compare: function() {
return Promise.resolve({ pass: true });
},
negativeCompare: function() {
return Promise.resolve({
pass: false,
message: "I'm a custom message"
});
}
};
}
},
addExpectationResult = jasmine.createSpy('addExpectationResult'),
actual = 'an actual',
errorWithStack = new Error('errorWithStack');
spyOn(jasmineUnderTest.util, 'errorWithStack').and.returnValue(
errorWithStack
);
toFoo: function() {
return {
compare: function() {
return Promise.resolve({ pass: true });
},
negativeCompare: function() {
return Promise.resolve({
pass: false,
message: "I'm a custom message"
});
}
};
}
};
const addExpectationResult = jasmine.createSpy('addExpectationResult');
const actual = 'an actual';
const expectation = jasmineUnderTest.Expectation.asyncFactory({
customAsyncMatchers: matchers,
@@ -635,7 +590,7 @@ describe('AsyncExpectation', function() {
actual: actual,
message: "I'm a custom message",
error: undefined,
errorForStack: errorWithStack
errorForStack: jasmine.any(Error)
});
});
});
@@ -643,24 +598,19 @@ describe('AsyncExpectation', function() {
it('reports errorWithStack when a custom error message is returned', function() {
const customError = new Error('I am a custom error');
const matchers = {
toFoo: function() {
return {
compare: function() {
return Promise.resolve({
pass: false,
message: 'I am a custom message',
error: customError
});
}
};
}
},
addExpectationResult = jasmine.createSpy('addExpectationResult'),
errorWithStack = new Error('errorWithStack');
spyOn(jasmineUnderTest.util, 'errorWithStack').and.returnValue(
errorWithStack
);
toFoo: function() {
return {
compare: function() {
return Promise.resolve({
pass: false,
message: 'I am a custom message',
error: customError
});
}
};
}
};
const addExpectationResult = jasmine.createSpy('addExpectationResult');
const expectation = jasmineUnderTest.Expectation.asyncFactory({
actual: 'an actual',
@@ -676,30 +626,25 @@ describe('AsyncExpectation', function() {
actual: 'an actual',
message: 'I am a custom message',
error: undefined,
errorForStack: errorWithStack
errorForStack: jasmine.any(Error)
});
});
});
it("reports a custom message to the spec when a 'not' comparison fails", function() {
const matchers = {
toFoo: function() {
return {
compare: function() {
return Promise.resolve({
pass: true,
message: 'I am a custom message'
});
}
};
}
},
addExpectationResult = jasmine.createSpy('addExpectationResult'),
errorWithStack = new Error('errorWithStack');
spyOn(jasmineUnderTest.util, 'errorWithStack').and.returnValue(
errorWithStack
);
toFoo: function() {
return {
compare: function() {
return Promise.resolve({
pass: true,
message: 'I am a custom message'
});
}
};
}
};
const addExpectationResult = jasmine.createSpy('addExpectationResult');
const expectation = jasmineUnderTest.Expectation.asyncFactory({
actual: 'an actual',
@@ -715,32 +660,27 @@ describe('AsyncExpectation', function() {
actual: 'an actual',
message: 'I am a custom message',
error: undefined,
errorForStack: errorWithStack
errorForStack: jasmine.any(Error)
});
});
});
it("reports a custom message func to the spec when a 'not' comparison fails", function() {
const matchers = {
toFoo: function() {
return {
compare: function() {
return Promise.resolve({
pass: true,
message: function() {
return 'I am a custom message';
}
});
}
};
}
},
addExpectationResult = jasmine.createSpy('addExpectationResult'),
errorWithStack = new Error('errorWithStack');
spyOn(jasmineUnderTest.util, 'errorWithStack').and.returnValue(
errorWithStack
);
toFoo: function() {
return {
compare: function() {
return Promise.resolve({
pass: true,
message: function() {
return 'I am a custom message';
}
});
}
};
}
};
const addExpectationResult = jasmine.createSpy('addExpectationResult');
let expectation = jasmineUnderTest.Expectation.asyncFactory({
actual: 'an actual',
@@ -756,7 +696,7 @@ describe('AsyncExpectation', function() {
actual: 'an actual',
message: 'I am a custom message',
error: undefined,
errorForStack: errorWithStack
errorForStack: jasmine.any(Error)
});
});
});

View File

@@ -134,6 +134,42 @@ describe('CallTracker', function() {
expect(callTracker.mostRecent().args[1]).toEqual(arrayArg);
});
it('allows object arguments to be deep cloned', function() {
const callTracker = new jasmineUnderTest.CallTracker();
callTracker.saveArgumentsByValue(args => JSON.parse(JSON.stringify(args)));
const objectArg = { foo: { bar: { baz: ['qux'] } } },
arrayArg = ['foo', 'bar'];
callTracker.track({
object: {},
args: [objectArg, arrayArg, false, undefined, null, NaN, '', 0, 1.0]
});
objectArg.foo.bar.baz.push('quux');
expect(callTracker.mostRecent().args[0]).not.toBe(objectArg);
expect(callTracker.mostRecent().args[0]).not.toEqual(objectArg);
expect(callTracker.mostRecent().args[0]).toEqual({
foo: { bar: { baz: ['qux'] } }
});
expect(callTracker.mostRecent().args[1]).not.toBe(arrayArg);
expect(callTracker.mostRecent().args[1]).toEqual(arrayArg);
});
it('can take any function to transform arguments when saving by value', function() {
const callTracker = new jasmineUnderTest.CallTracker();
callTracker.saveArgumentsByValue(JSON.stringify);
const objectArg = { foo: { bar: { baz: ['qux'] } } },
arrayArg = ['foo', 'bar'],
args = [objectArg, arrayArg, false, undefined, null, NaN, '', 0, 1.0];
callTracker.track({ object: {}, args });
expect(callTracker.mostRecent().args).toEqual(JSON.stringify(args));
});
it('saves primitive arguments by value', function() {
const callTracker = new jasmineUnderTest.CallTracker(),
args = [undefined, null, false, '', /\s/, 0, 1.2, NaN];

View File

@@ -687,6 +687,159 @@ describe('Clock (acceptance)', function() {
expect(recurring1.calls.count()).toBe(4);
});
describe('auto tick mode', () => {
let delayedFunctionScheduler;
let mockDate;
let clock;
beforeEach(() => {
delayedFunctionScheduler = new jasmineUnderTest.DelayedFunctionScheduler();
mockDate = {
install: function() {},
tick: function() {},
uninstall: function() {}
};
// window setTimeout to window to make firefox happy
const _setTimeout =
typeof window !== 'undefined' ? setTimeout.bind(window) : setTimeout;
// passing a fake global allows us to preserve the real timing functions for use in tests
const _global = { setTimeout: _setTimeout, setInterval: setInterval };
clock = new jasmineUnderTest.Clock(
_global,
function() {
return delayedFunctionScheduler;
},
mockDate
);
clock.install().autoTick();
});
afterEach(() => {
clock.uninstall();
});
it('flushes microtask queue between macrotasks', async () => {
const log = [];
await new Promise(r => clock.setTimeout(r, 10)).then(() => {
log.push(1);
Promise.resolve().then(() => log.push(2));
Promise.resolve().then(() => log.push(3));
});
await new Promise(r => clock.setTimeout(r, 10)).then(() => {
log.push(4);
Promise.resolve().then(() => log.push(5));
});
expect(log).toEqual([1, 2, 3, 4, 5]);
});
it('can run setTimeouts/setIntervals asynchronously', function() {
const recurring = jasmine.createSpy('recurring'),
fn1 = jasmine.createSpy('fn1'),
fn2 = jasmine.createSpy('fn2'),
fn3 = jasmine.createSpy('fn3');
const intervalId = clock.setInterval(recurring, 50);
// In a microtask, add some timeouts.
Promise.resolve()
.then(function() {
return new Promise(function(resolve) {
clock.setTimeout(resolve, 25);
});
})
.then(function() {
fn1();
return new Promise(function(resolve) {
clock.setTimeout(resolve, 200);
});
})
.then(function() {
fn2();
return new Promise(function(resolve) {
clock.setTimeout(resolve, 100);
});
})
.then(function() {
fn3();
});
expect(recurring).not.toHaveBeenCalled();
expect(fn1).not.toHaveBeenCalled();
expect(fn2).not.toHaveBeenCalled();
expect(fn3).not.toHaveBeenCalled();
return new Promise(resolve => clock.setTimeout(resolve, 50))
.then(function() {
expect(recurring).toHaveBeenCalledTimes(1);
expect(fn1).toHaveBeenCalled();
expect(fn2).not.toHaveBeenCalled();
expect(fn3).not.toHaveBeenCalled();
return new Promise(resolve => clock.setTimeout(resolve, 175));
})
.then(function() {
expect(recurring).toHaveBeenCalledTimes(4);
expect(fn1).toHaveBeenCalled();
expect(fn2).toHaveBeenCalled();
expect(fn3).not.toHaveBeenCalled();
clock.clearInterval(intervalId);
return new Promise(resolve => clock.setTimeout(resolve, 100));
})
.then(function() {
expect(recurring).toHaveBeenCalledTimes(4);
expect(fn1).toHaveBeenCalled();
expect(fn2).toHaveBeenCalled();
expect(fn3).toHaveBeenCalled();
});
});
it('aborts auto ticking when uninstalled, even if installed again synchonrously', async () => {
clock.uninstall();
clock.install();
let resolved = false;
const promise = new Promise(resolve => {
clock.setTimeout(resolve, 1);
}).then(() => {
resolved = true;
});
// wait some real time and verify that the clock did not flush the timer above automatically
await new Promise(resolve => setTimeout(resolve, 2));
expect(resolved).toBe(false);
// enabling auto tick again will flush the timer
clock.autoTick();
await expectAsync(promise).toBeResolved();
});
it('speeds up the execution of the timers in all browsers', async () => {
const startTimeMs = performance.now() / 1000;
await new Promise(resolve => clock.setTimeout(resolve, 5000));
await new Promise(resolve => clock.setTimeout(resolve, 5000));
await new Promise(resolve => clock.setTimeout(resolve, 5000));
await new Promise(resolve => clock.setTimeout(resolve, 5000));
const endTimeMs = performance.now() / 1000;
// Ensure we didn't take 20s to complete the awaits above and, in fact, can do it in a fraction of a second
expect(endTimeMs - startTimeMs).toBeLessThan(100);
});
it('is easy to test async functions with interleaved timers and microtasks', async () => {
async function blackBoxWithLotsOfAsyncStuff() {
await new Promise(r => clock.setTimeout(r, 10));
await Promise.resolve();
await Promise.resolve();
await new Promise(r => clock.setTimeout(r, 20));
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
return 'done';
}
const result = await blackBoxWithLotsOfAsyncStuff();
expect(result).toBe('done');
});
});
it('can clear a previously set timeout', function() {
const clearedFn = jasmine.createSpy('clearedFn'),
delayedFunctionScheduler = new jasmineUnderTest.DelayedFunctionScheduler(),

View File

@@ -86,12 +86,13 @@ describe('DelayedFunctionScheduler', function() {
it('increments scheduled fns ids unless one is passed', function() {
const scheduler = new jasmineUnderTest.DelayedFunctionScheduler();
expect(scheduler.scheduleFunction(function() {}, 0)).toBe(1);
expect(scheduler.scheduleFunction(function() {}, 0)).toBe(2);
const initial = scheduler.scheduleFunction(function() {}, 0);
expect(scheduler.scheduleFunction(function() {}, 0)).toBe(initial + 1);
expect(scheduler.scheduleFunction(function() {}, 0)).toBe(initial + 2);
expect(scheduler.scheduleFunction(function() {}, 0, [], false, 123)).toBe(
123
);
expect(scheduler.scheduleFunction(function() {}, 0)).toBe(3);
expect(scheduler.scheduleFunction(function() {}, 0)).toBe(initial + 3);
});
it('#removeFunctionWithId removes a previously scheduled function with a given id', function() {
@@ -264,6 +265,42 @@ describe('DelayedFunctionScheduler', function() {
expect(fn).toHaveBeenCalled();
});
it('runs the next scheduled funtion', function() {
const scheduler = new jasmineUnderTest.DelayedFunctionScheduler();
const fn = jasmine.createSpy('fn');
const tickSpy = jasmine.createSpy('tick');
scheduler.scheduleFunction(fn, 10, [], false, 'foo');
expect(fn).not.toHaveBeenCalled();
scheduler.runNextQueuedFunction(tickSpy);
expect(fn).toHaveBeenCalled();
expect(tickSpy).toHaveBeenCalledWith(10);
});
it('runs the only a single scheduled funtion in a time slot', function() {
const scheduler = new jasmineUnderTest.DelayedFunctionScheduler();
const fn1 = jasmine.createSpy('fn');
const fn2 = jasmine.createSpy('fn2');
const tickSpy = jasmine.createSpy('tick');
scheduler.scheduleFunction(fn1, 10, [], false, 'foo1');
scheduler.scheduleFunction(fn2, 10, [], false, 'foo2');
scheduler.runNextQueuedFunction(tickSpy);
expect(fn1).toHaveBeenCalled();
expect(fn2).not.toHaveBeenCalled();
expect(tickSpy).toHaveBeenCalledWith(10);
tickSpy.calls.reset();
scheduler.runNextQueuedFunction(tickSpy);
expect(fn2).toHaveBeenCalled();
expect(tickSpy).toHaveBeenCalledWith(0);
});
it('updates the mockDate per scheduled time', function() {
const scheduler = new jasmineUnderTest.DelayedFunctionScheduler(),
tickDate = jasmine.createSpy('tickDate');
@@ -277,6 +314,28 @@ describe('DelayedFunctionScheduler', function() {
expect(tickDate).toHaveBeenCalledWith(1);
});
it('does not conflict with native timer IDs', function() {
const NODE_JS =
typeof process !== 'undefined' &&
process.versions &&
typeof process.versions.node === 'string';
if (NODE_JS) {
pending('numeric timer ID conflicts only relevant for browsers.');
}
const nativeTimeoutId = setTimeout(function() {}, 100);
const scheduler = new jasmineUnderTest.DelayedFunctionScheduler();
const fn = jasmine.createSpy('fn');
for (let i = 0; i < nativeTimeoutId; i++) {
scheduler.scheduleFunction(fn, 0, [], false);
}
scheduler.removeFunctionWithId(nativeTimeoutId);
scheduler.tick(1);
expect(fn).toHaveBeenCalledTimes(nativeTimeoutId);
});
describe('ticking inside a scheduled function', function() {
let clock;

View File

@@ -205,7 +205,6 @@ describe('Env', function() {
it('throws an error when given arguments', function() {
expect(function() {
// eslint-disable-next-line no-unused-vars
env.describe('done method', function(done) {});
}).toThrowError('describe does not expect any arguments');
});

View File

@@ -1,14 +1,14 @@
describe('GlobalErrors', function() {
it('calls the added handler on error', function() {
const fakeGlobal = browserGlobal();
const globals = browserGlobals();
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
errors.install();
errors.pushListener(handler);
const error = new Error('nope');
dispatchErrorEvent(fakeGlobal, { error });
dispatchEvent(globals.listeners, 'error', { error });
expect(handler).toHaveBeenCalledWith(
jasmine.is(error),
@@ -17,17 +17,17 @@ describe('GlobalErrors', function() {
});
it('is not affected by overriding global.onerror', function() {
const fakeGlobal = browserGlobal();
const globals = browserGlobals();
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
errors.install();
errors.pushListener(handler);
fakeGlobal.onerror = () => {};
globals.global.onerror = () => {};
const error = new Error('nope');
dispatchErrorEvent(fakeGlobal, { error });
dispatchEvent(globals.listeners, 'error', { error });
expect(handler).toHaveBeenCalledWith(
jasmine.is(error),
@@ -36,17 +36,17 @@ describe('GlobalErrors', function() {
});
it('only calls the most recent handler', function() {
const fakeGlobal = browserGlobal();
const globals = browserGlobals();
const handler1 = jasmine.createSpy('errorHandler1');
const handler2 = jasmine.createSpy('errorHandler2');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
errors.install();
errors.pushListener(handler1);
errors.pushListener(handler2);
const error = new Error('nope');
dispatchErrorEvent(fakeGlobal, { error });
dispatchEvent(globals.listeners, 'error', { error });
expect(handler1).not.toHaveBeenCalled();
expect(handler2).toHaveBeenCalledWith(
@@ -56,10 +56,10 @@ describe('GlobalErrors', function() {
});
it('calls previous handlers when one is removed', function() {
const fakeGlobal = browserGlobal();
const globals = browserGlobals();
const handler1 = jasmine.createSpy('errorHandler1');
const handler2 = jasmine.createSpy('errorHandler2');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
errors.install();
errors.pushListener(handler1);
@@ -68,7 +68,7 @@ describe('GlobalErrors', function() {
errors.popListener(handler2);
const error = new Error('nope');
dispatchErrorEvent(fakeGlobal, { error });
dispatchEvent(globals.listeners, 'error', { error });
expect(handler1).toHaveBeenCalledWith(
jasmine.is(error),
@@ -85,26 +85,26 @@ describe('GlobalErrors', function() {
});
it('uninstalls itself', function() {
const fakeGlobal = browserGlobal();
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const globals = browserGlobals();
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
function unrelatedListener() {}
errors.install();
fakeGlobal.addEventListener('error', unrelatedListener);
globals.global.addEventListener('error', unrelatedListener);
errors.uninstall();
expect(fakeGlobal.listeners_.error).toEqual([unrelatedListener]);
expect(globals.listeners.error).toEqual([unrelatedListener]);
});
it('rethrows the original error when there is no handler', function() {
const fakeGlobal = browserGlobal();
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const globals = browserGlobals();
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
const originalError = new Error('nope');
errors.install();
try {
dispatchErrorEvent(fakeGlobal, { error: originalError });
dispatchEvent(globals.listeners, 'error', { error: originalError });
} catch (e) {
expect(e).toBe(originalError);
}
@@ -113,191 +113,129 @@ describe('GlobalErrors', function() {
});
it('reports uncaught exceptions in node.js', function() {
const fakeGlobal = {
process: {
on: jasmine.createSpy('process.on'),
removeListener: jasmine.createSpy('process.removeListener'),
listeners: jasmine
.createSpy('process.listeners')
.and.returnValue(['foo']),
removeAllListeners: jasmine.createSpy('process.removeAllListeners')
}
},
handler = jasmine.createSpy('errorHandler'),
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const globals = nodeGlobals();
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
const handler = jasmine.createSpy('errorHandler');
function originalHandler() {}
globals.listeners.uncaughtException = [originalHandler];
errors.install();
expect(fakeGlobal.process.on).toHaveBeenCalledWith(
'uncaughtException',
expect(globals.listeners.uncaughtException).toEqual([
jasmine.any(Function)
);
expect(fakeGlobal.process.listeners).toHaveBeenCalledWith(
'uncaughtException'
);
expect(fakeGlobal.process.removeAllListeners).toHaveBeenCalledWith(
'uncaughtException'
);
]);
expect(globals.listeners.uncaughtException).not.toEqual([
originalHandler()
]);
errors.pushListener(handler);
const addedListener = fakeGlobal.process.on.calls.argsFor(0)[1];
addedListener(new Error('bar'));
dispatchEvent(globals.listeners, 'uncaughtException', new Error('bar'));
expect(handler).toHaveBeenCalledWith(new Error('bar'));
expect(handler).toHaveBeenCalledWith(new Error('bar'), undefined);
expect(handler.calls.argsFor(0)[0].jasmineMessage).toBe(
'Uncaught exception: Error: bar'
);
errors.uninstall();
expect(fakeGlobal.process.removeListener).toHaveBeenCalledWith(
'uncaughtException',
addedListener
);
expect(fakeGlobal.process.on).toHaveBeenCalledWith(
'uncaughtException',
'foo'
);
expect(globals.listeners.uncaughtException).toEqual([originalHandler]);
});
describe('Reporting unhandled promise rejections in node.js', function() {
it('reports rejections with `Error` reasons', function() {
const fakeGlobal = {
process: {
on: jasmine.createSpy('process.on'),
removeListener: jasmine.createSpy('process.removeListener'),
listeners: jasmine
.createSpy('process.listeners')
.and.returnValue(['foo']),
removeAllListeners: jasmine.createSpy('process.removeAllListeners')
}
},
handler = jasmine.createSpy('errorHandler'),
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const globals = nodeGlobals();
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
const handler = jasmine.createSpy('errorHandler');
function originalHandler() {}
globals.listeners.unhandledRejection = [originalHandler];
errors.install();
expect(fakeGlobal.process.on).toHaveBeenCalledWith(
'unhandledRejection',
expect(globals.listeners.unhandledRejection).toEqual([
jasmine.any(Function)
);
expect(fakeGlobal.process.listeners).toHaveBeenCalledWith(
'unhandledRejection'
);
expect(fakeGlobal.process.removeAllListeners).toHaveBeenCalledWith(
'unhandledRejection'
);
]);
expect(globals.listeners.unhandledRejection).not.toEqual([
originalHandler()
]);
errors.pushListener(handler);
const addedListener = fakeGlobal.process.on.calls.argsFor(1)[1];
addedListener(new Error('bar'));
dispatchEvent(globals.listeners, 'unhandledRejection', new Error('bar'));
expect(handler).toHaveBeenCalledWith(new Error('bar'));
expect(handler).toHaveBeenCalledWith(new Error('bar'), undefined);
expect(handler.calls.argsFor(0)[0].jasmineMessage).toBe(
'Unhandled promise rejection: Error: bar'
);
errors.uninstall();
expect(fakeGlobal.process.removeListener).toHaveBeenCalledWith(
'unhandledRejection',
addedListener
);
expect(fakeGlobal.process.on).toHaveBeenCalledWith(
'unhandledRejection',
'foo'
);
expect(globals.listeners.unhandledRejection).toEqual([originalHandler]);
});
it('reports rejections with non-`Error` reasons', function() {
const fakeGlobal = {
process: {
on: jasmine.createSpy('process.on'),
removeListener: function() {},
listeners: function() {
return [];
},
removeAllListeners: function() {}
}
},
handler = jasmine.createSpy('errorHandler'),
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const globals = nodeGlobals();
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
const handler = jasmine.createSpy('errorHandler');
errors.install();
errors.pushListener(handler);
expect(fakeGlobal.process.on.calls.argsFor(1)[0]).toEqual(
'unhandledRejection'
);
const addedListener = fakeGlobal.process.on.calls.argsFor(1)[1];
addedListener(17);
dispatchEvent(globals.listeners, 'unhandledRejection', 17);
expect(handler).toHaveBeenCalledWith(
new Error(
'Unhandled promise rejection: 17\n' +
'(Tip: to get a useful stack trace, use ' +
'Promise.reject(new Error(...)) instead of Promise.reject(...).)'
)
),
undefined
);
});
it('reports rejections with no reason provided', function() {
const fakeGlobal = {
process: {
on: jasmine.createSpy('process.on'),
removeListener: function() {},
listeners: function() {
return [];
},
removeAllListeners: function() {}
}
},
handler = jasmine.createSpy('errorHandler'),
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const globals = nodeGlobals();
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
const handler = jasmine.createSpy('errorHandler');
errors.install();
errors.pushListener(handler);
expect(fakeGlobal.process.on.calls.argsFor(1)[0]).toEqual(
'unhandledRejection'
);
const addedListener = fakeGlobal.process.on.calls.argsFor(1)[1];
addedListener(undefined);
dispatchEvent(globals.listeners, 'unhandledRejection', undefined);
expect(handler).toHaveBeenCalledWith(
new Error(
'Unhandled promise rejection with no error or message\n' +
'(Tip: to get a useful stack trace, use ' +
'Promise.reject(new Error(...)) instead of Promise.reject().)'
)
),
undefined
);
});
});
describe('Reporting unhandled promise rejections in the browser', function() {
it('subscribes and unsubscribes from the unhandledrejection event', function() {
const fakeGlobal = browserGlobal();
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const globals = browserGlobals();
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
errors.install();
expect(fakeGlobal.listeners_.unhandledrejection).toEqual([
expect(globals.listeners.unhandledrejection).toEqual([
jasmine.any(Function)
]);
errors.uninstall();
expect(fakeGlobal.listeners_.unhandledrejection).toEqual([]);
expect(globals.listeners.unhandledrejection).toEqual([]);
});
it('reports rejections whose reason is a string', function() {
const fakeGlobal = browserGlobal();
const globals = browserGlobals();
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
errors.install();
errors.pushListener(handler);
const event = { reason: 'nope' };
dispatchUnhandledRejectionEvent(fakeGlobal, event);
dispatchEvent(globals.listeners, 'unhandledrejection', event);
expect(handler).toHaveBeenCalledWith(
'Unhandled promise rejection: nope',
@@ -306,16 +244,16 @@ describe('GlobalErrors', function() {
});
it('reports rejections whose reason is an Error', function() {
const fakeGlobal = browserGlobal();
const globals = browserGlobals();
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
errors.install();
errors.pushListener(handler);
const reason = new Error('bar');
const event = { reason };
dispatchUnhandledRejectionEvent(fakeGlobal, event);
dispatchEvent(globals.listeners, 'unhandledrejection', event);
expect(handler).toHaveBeenCalledWith(
jasmine.objectContaining({
@@ -330,73 +268,51 @@ 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 globals = nodeGlobals();
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
uncaughtExceptionListener(fakeGlobal)(17);
dispatchEvent(globals.listeners, 'uncaughtException', 17);
expect(handler).toHaveBeenCalledWith(new Error('Uncaught exception: 17'));
expect(handler).toHaveBeenCalledWith(
new Error('Uncaught exception: 17'),
undefined
);
});
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 globals = nodeGlobals();
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
uncaughtExceptionListener(fakeGlobal)();
dispatchEvent(globals.listeners, 'uncaughtException', undefined);
expect(handler).toHaveBeenCalledWith(
new Error('Uncaught exception with no error or message')
new Error('Uncaught exception with no error or message'),
undefined
);
});
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() {
it('overrides the existing handlers in browsers until removed', function() {
const fakeGlobal = browserGlobal();
const globals = browserGlobals();
const handler0 = jasmine.createSpy('handler0');
const handler1 = jasmine.createSpy('handler1');
const overrideHandler = jasmine.createSpy('overrideHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
errors.install();
errors.pushListener(handler0);
errors.setOverrideListener(overrideHandler, () => {});
errors.pushListener(handler1);
dispatchErrorEvent(fakeGlobal, { error: 'foo' });
dispatchEvent(globals.listeners, 'error', { error: 'foo' });
expect(overrideHandler).toHaveBeenCalledWith('foo');
expect(handler0).not.toHaveBeenCalled();
@@ -405,55 +321,42 @@ describe('GlobalErrors', function() {
errors.removeOverrideListener();
const event = { error: 'baz' };
dispatchErrorEvent(fakeGlobal, event);
dispatchEvent(globals.listeners, 'error', event);
expect(overrideHandler).not.toHaveBeenCalledWith('baz');
expect(handler1).toHaveBeenCalledWith('baz', event);
});
it('overrides the existing handlers in Node until removed', function() {
const globalEventListeners = {};
const fakeGlobal = {
process: {
on: (name, listener) => (globalEventListeners[name] = listener),
removeListener: () => {},
listeners: name => globalEventListeners[name],
removeAllListeners: name => (globalEventListeners[name] = [])
}
};
const globals = nodeGlobals();
const handler0 = jasmine.createSpy('handler0');
const handler1 = jasmine.createSpy('handler1');
const overrideHandler = jasmine.createSpy('overrideHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
errors.install();
errors.pushListener(handler0);
errors.setOverrideListener(overrideHandler);
errors.pushListener(handler1);
globalEventListeners['uncaughtException'](new Error('foo'));
dispatchEvent(globals.listeners, 'uncaughtException', new Error('foo'));
expect(overrideHandler).toHaveBeenCalledWith(new Error('foo'));
expect(handler0).not.toHaveBeenCalled();
expect(handler1).not.toHaveBeenCalled();
overrideHandler.calls.reset();
errors.removeOverrideListener();
globalEventListeners['uncaughtException'](new Error('bar'));
expect(overrideHandler).not.toHaveBeenCalledWith(new Error('bar'));
expect(handler1).toHaveBeenCalledWith(new Error('bar'));
dispatchEvent(globals.listeners, 'uncaughtException', new Error('bar'));
expect(overrideHandler).not.toHaveBeenCalled();
expect(handler1).toHaveBeenCalledWith(new Error('bar'), undefined);
});
it('handles unhandled promise rejections in browsers', function() {
const globalEventListeners = {};
const fakeGlobal = {
addEventListener(name, listener) {
globalEventListeners[name] = listener;
},
removeEventListener() {}
};
const globals = browserGlobals();
const handler = jasmine.createSpy('handler');
const overrideHandler = jasmine.createSpy('overrideHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
errors.install();
errors.pushListener(handler);
@@ -461,7 +364,7 @@ describe('GlobalErrors', function() {
const reason = new Error('bar');
globalEventListeners['unhandledrejection']({ reason: reason });
dispatchEvent(globals.listeners, 'unhandledrejection', { reason });
expect(overrideHandler).toHaveBeenCalledWith(
jasmine.objectContaining({
@@ -474,32 +377,18 @@ describe('GlobalErrors', function() {
});
it('handles unhandled promise rejections in Node', function() {
const globalEventListeners = {};
const fakeGlobal = {
process: {
on(name, listener) {
globalEventListeners[name] = listener;
},
removeListener() {},
listeners(name) {
return globalEventListeners[name];
},
removeAllListeners(name) {
globalEventListeners[name] = null;
}
}
};
const globals = nodeGlobals();
const handler0 = jasmine.createSpy('handler0');
const handler1 = jasmine.createSpy('handler1');
const overrideHandler = jasmine.createSpy('overrideHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
errors.install();
errors.pushListener(handler0);
errors.setOverrideListener(overrideHandler, () => {});
errors.pushListener(handler1);
globalEventListeners['unhandledRejection'](new Error('nope'));
dispatchEvent(globals.listeners, 'unhandledRejection', new Error('nope'));
expect(overrideHandler).toHaveBeenCalledWith(new Error('nope'));
expect(handler0).not.toHaveBeenCalled();
@@ -507,7 +396,7 @@ describe('GlobalErrors', function() {
});
it('throws if there is already an override handler', function() {
const errors = new jasmineUnderTest.GlobalErrors(browserGlobal());
const errors = new jasmineUnderTest.GlobalErrors(browserGlobals().global);
errors.setOverrideListener(() => {}, () => {});
expect(function() {
@@ -519,7 +408,7 @@ describe('GlobalErrors', function() {
describe('#removeOverrideListener', function() {
it("calls the handler's onRemove callback", function() {
const onRemove = jasmine.createSpy('onRemove');
const errors = new jasmineUnderTest.GlobalErrors(browserGlobal());
const errors = new jasmineUnderTest.GlobalErrors(browserGlobals().global);
errors.setOverrideListener(() => {}, onRemove);
errors.removeOverrideListener();
@@ -528,42 +417,60 @@ describe('GlobalErrors', function() {
});
it('does not throw if there is no handler', function() {
const errors = new jasmineUnderTest.GlobalErrors(browserGlobal());
const errors = new jasmineUnderTest.GlobalErrors(browserGlobals().global);
expect(() => errors.removeOverrideListener()).not.toThrow();
});
});
function browserGlobal() {
function browserGlobals() {
const listeners = { error: [], unhandledrejection: [] };
return {
listeners_: { error: [], unhandledrejection: [] },
addEventListener(eventName, listener) {
this.listeners_[eventName].push(listener);
},
removeEventListener(eventName, listener) {
this.listeners_[eventName] = this.listeners_[eventName].filter(
l => l !== listener
);
listeners,
global: {
addEventListener(eventName, listener) {
listeners[eventName].push(listener);
},
removeEventListener(eventName, listener) {
listeners[eventName] = listeners[eventName].filter(
l => l !== listener
);
}
}
};
}
function dispatchErrorEvent(global, event) {
expect(global.listeners_.error.length)
.withContext('number of error listeners')
.toBeGreaterThan(0);
for (const l of global.listeners_.error) {
l(event);
}
function nodeGlobals() {
const listeners = { uncaughtException: [], unhandledRejection: [] };
return {
listeners,
global: {
process: {
on(eventName, listener) {
listeners[eventName].push(listener);
},
removeListener(eventName, listener) {
listeners[eventName] = listeners[eventName].filter(
l => l !== listener
);
},
removeAllListeners(eventName) {
listeners[eventName] = [];
},
listeners(eventName) {
return listeners[eventName];
}
}
}
};
}
function dispatchUnhandledRejectionEvent(global, event) {
expect(global.listeners_.unhandledrejection.length)
.withContext('number of unhandledrejection listeners')
function dispatchEvent(listeners, eventName, event) {
expect(listeners[eventName].length)
.withContext(`number of ${eventName} listeners`)
.toBeGreaterThan(0);
for (const l of global.listeners_.unhandledrejection) {
for (const l of listeners[eventName]) {
l(event);
}
}

View File

@@ -164,7 +164,7 @@ describe('PrettyPrinter', function() {
"Object({ foo: 'bar', baz: 3, nullValue: null, undefinedValue: undefined })"
);
expect(pp({ foo: function() {}, bar: [1, 2, 3] })).toEqual(
'Object({ foo: Function, bar: [ 1, 2, 3 ] })'
"Object({ foo: Function 'foo', bar: [ 1, 2, 3 ] })"
);
});
@@ -450,7 +450,7 @@ describe('PrettyPrinter', function() {
};
expect(pp(objFromOtherContext)).toEqual(
"Object({ foo: 'bar', toString: Function })"
"Object({ foo: 'bar', toString: Function 'toString' })"
);
});
@@ -477,6 +477,17 @@ describe('PrettyPrinter', function() {
expect(pp(a)).toEqual('<anonymous>({ })');
});
it('stringifies functions with names', function() {
const pp = jasmineUnderTest.makePrettyPrinter();
expect(pp(foo)).toEqual("Function 'foo'");
function foo() {}
});
it('stringifies functions without names', function() {
const pp = jasmineUnderTest.makePrettyPrinter();
expect(pp(function() {})).toEqual('Function');
});
it('should handle objects with null prototype', function() {
const pp = jasmineUnderTest.makePrettyPrinter();
const obj = Object.create(null);

View File

@@ -239,7 +239,6 @@ describe('QueueRunner', function() {
it("sets a timeout if requested for asynchronous functions so they don't go on forever", function() {
const timeout = 3,
// eslint-disable-next-line no-unused-vars
beforeFn = { fn: function(done) {}, type: 'before', timeout: timeout },
queueableFn = { fn: jasmine.createSpy('fn'), type: 'queueable' },
onComplete = jasmine.createSpy('onComplete'),
@@ -287,7 +286,6 @@ describe('QueueRunner', function() {
});
it('by default does not set a timeout for asynchronous functions', function() {
// eslint-disable-next-line no-unused-vars
const beforeFn = { fn: function(done) {} },
queueableFn = { fn: jasmine.createSpy('fn') },
onComplete = jasmine.createSpy('onComplete'),
@@ -310,7 +308,6 @@ describe('QueueRunner', function() {
it('clears the timeout when an async function throws an exception, to prevent additional exception reporting', function() {
const queueableFn = {
// eslint-disable-next-line no-unused-vars
fn: function(done) {
throw new Error('error!');
}
@@ -409,7 +406,6 @@ describe('QueueRunner', function() {
it('continues running functions when an exception is thrown in async code without timing out', function() {
const queueableFn = {
// eslint-disable-next-line no-unused-vars
fn: function(done) {
throwAsync();
},
@@ -461,7 +457,6 @@ describe('QueueRunner', function() {
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];
@@ -641,7 +636,6 @@ describe('QueueRunner', function() {
it('issues an error if the function also takes a parameter', function() {
const queueableFn = {
// eslint-disable-next-line no-unused-vars
fn: function(done) {
return new StubPromise();
}
@@ -666,7 +660,6 @@ describe('QueueRunner', function() {
});
it('issues a more specific error if the function is `async`', function() {
// eslint-disable-next-line no-unused-vars
async function fn(done) {}
const onException = jasmine.createSpy('onException'),
queueRunner = new jasmineUnderTest.QueueRunner({
@@ -720,7 +713,6 @@ describe('QueueRunner', function() {
it('continues running the functions even after an exception is thrown in an async spec', function() {
const queueableFn = {
// eslint-disable-next-line no-unused-vars
fn: function(done) {
throw new Error('error');
}

View File

@@ -197,8 +197,8 @@ describe('Spec', function() {
description: 'with a spec',
parentSuiteId: 'suite1',
filename: 'someSpecFile.js',
getSpecName: function() {
return 'a suite with a spec';
getPath: function() {
return ['a suite', 'with a spec'];
},
queueableFn: { fn: null }
});
@@ -485,17 +485,33 @@ describe('Spec', function() {
});
it('can return its full name', function() {
const specNameSpy = jasmine
.createSpy('specNameSpy')
.and.returnValue('expected val');
const getPath = jasmine
.createSpy('getPath')
.and.returnValue(['expected', 'val']);
const spec = new jasmineUnderTest.Spec({
getSpecName: specNameSpy,
getPath,
queueableFn: { fn: null }
});
expect(spec.getFullName()).toBe('expected val');
expect(specNameSpy.calls.mostRecent().args[0].id).toEqual(spec.id);
expect(getPath.calls.mostRecent().args[0]).toBe(spec);
});
it('can return its full path', function() {
const getPath = jasmine
.createSpy('getPath')
.and.returnValue(['expected val']);
const spec = new jasmineUnderTest.Spec({
getPath,
queueableFn: { fn: null }
});
expect(spec.getPath()).toEqual(['expected val']);
expect(getPath.calls.mostRecent().args[0]).toBe(spec);
expect(spec.metadata.getPath()).toEqual(['expected val']);
});
describe('when a spec is marked pending during execution', function() {
@@ -586,8 +602,8 @@ describe('Spec', function() {
spec = new jasmineUnderTest.Spec({
onLateError: onLateError,
queueableFn: { fn: function() {} },
getSpecName: function() {
return 'a spec';
getPath: function() {
return ['a spec'];
}
});

View File

@@ -97,17 +97,11 @@ describe('Spies', function() {
it('preserves arity of original function', function() {
const functions = [
function nullary() {},
// eslint-disable-next-line no-unused-vars
function unary(arg) {},
// eslint-disable-next-line no-unused-vars
function binary(arg1, arg2) {},
// eslint-disable-next-line no-unused-vars
function ternary(arg1, arg2, arg3) {},
// eslint-disable-next-line no-unused-vars
function quaternary(arg1, arg2, arg3, arg4) {},
// eslint-disable-next-line no-unused-vars
function quinary(arg1, arg2, arg3, arg4, arg5) {},
// eslint-disable-next-line no-unused-vars
function senary(arg1, arg2, arg3, arg4, arg5, arg6) {}
];

View File

@@ -163,6 +163,20 @@ describe('SuiteBuilder', function() {
expect(spec2.id).toMatch(/^spec[0-9]+$/);
expect(spec1.id).not.toEqual(spec2.id);
});
it('gives each spec a full path', function() {
const env = { configuration: () => ({}) };
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
let spec;
suiteBuilder.describe('a suite', function() {
suiteBuilder.describe('a nested suite', function() {
spec = suiteBuilder[fnName]('a spec', function() {});
});
});
expect(spec.getPath()).toEqual(['a suite', 'a nested suite', 'a spec']);
});
}
function sameInstanceAs(expected) {

View File

@@ -128,17 +128,6 @@ describe('util', function() {
});
});
describe('isUndefined', function() {
it('reports if a variable is defined', function() {
let a;
expect(jasmineUnderTest.util.isUndefined(a)).toBe(true);
expect(jasmineUnderTest.util.isUndefined(undefined)).toBe(true);
const defined = 'diz be undefined yo';
expect(jasmineUnderTest.util.isUndefined(defined)).toBe(false);
});
});
describe('cloneArgs', function() {
it('clones primitives as-is', function() {
expect(jasmineUnderTest.util.cloneArgs([true, false])).toEqual([

View File

@@ -429,350 +429,6 @@ describe('Env integration', function() {
]);
});
describe('Handling async errors', function() {
it('routes async errors to a running spec', async function() {
const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
},
clearTimeout: function(fn, delay) {
clearTimeout(fn, delay);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
}
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
env.cleanup_();
env = new jasmineUnderTest.Env();
const reporter = jasmine.createSpyObj('fakeReporter', [
'specDone',
'suiteDone'
]);
env.addReporter(reporter);
env.describe('A suite', function() {
env.it('fails', function(specDone) {
setTimeout(function() {
dispatchErrorEvent(global, { error: 'fail' });
specDone();
});
});
});
await env.execute();
expect(reporter.specDone).toHaveFailedExpectationsForRunnable(
'A suite fails',
['fail thrown']
);
});
describe('When the running spec has reported specDone', function() {
it('routes async errors to an ancestor suite', async function() {
const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
},
clearTimeout: function(fn) {
clearTimeout(fn);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
}
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
const realClearStack = jasmineUnderTest.getClearStack(global);
const clearStackCallbacks = {};
let clearStackCallCount = 0;
spyOn(jasmineUnderTest, 'getClearStack').and.returnValue(function(fn) {
clearStackCallCount++;
if (clearStackCallbacks[clearStackCallCount]) {
clearStackCallbacks[clearStackCallCount]();
}
realClearStack(fn);
});
env.cleanup_();
env = new jasmineUnderTest.Env();
let suiteErrors = [];
env.addReporter({
suiteDone: function(result) {
const messages = result.failedExpectations.map(e => e.message);
suiteErrors = suiteErrors.concat(messages);
},
specDone: function() {
clearStackCallbacks[clearStackCallCount + 1] = function() {
dispatchErrorEvent(global, {
error: 'fail at the end of the reporter queue'
});
};
clearStackCallbacks[clearStackCallCount + 2] = function() {
dispatchErrorEvent(global, {
error: 'fail at the end of the spec queue'
});
};
}
});
env.describe('A suite', function() {
env.it('is finishing when the failure occurs', function() {});
});
await env.execute();
expect(suiteErrors).toEqual([
'fail at the end of the reporter queue thrown',
'fail at the end of the spec queue thrown'
]);
});
});
it('routes async errors to a running suite', async function() {
const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
},
clearTimeout: function(fn, delay) {
clearTimeout(fn, delay);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
}
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
env.cleanup_();
env = new jasmineUnderTest.Env();
const reporter = jasmine.createSpyObj('fakeReporter', [
'specDone',
'suiteDone'
]);
env.addReporter(reporter);
env.fdescribe('A suite', function() {
env.it('fails', function(specDone) {
setTimeout(function() {
specDone();
queueMicrotask(function() {
queueMicrotask(function() {
dispatchErrorEvent(global, { error: 'fail' });
});
});
});
});
});
env.describe('Ignored', function() {
env.it('is not run', function() {});
});
await env.execute();
expect(reporter.specDone).not.toHaveFailedExpectationsForRunnable(
'A suite fails',
['fail thrown']
);
expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable(
'A suite',
['fail thrown']
);
});
describe('When the running suite has reported suiteDone', function() {
it('routes async errors to an ancestor suite', async function() {
const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
},
clearTimeout: function(fn, delay) {
clearTimeout(fn, delay);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
}
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
const realClearStack = jasmineUnderTest.getClearStack(global);
const clearStackCallbacks = {};
let clearStackCallCount = 0;
spyOn(jasmineUnderTest, 'getClearStack').and.returnValue(function(fn) {
clearStackCallCount++;
if (clearStackCallbacks[clearStackCallCount]) {
clearStackCallbacks[clearStackCallCount]();
}
realClearStack(fn);
});
env.cleanup_();
env = new jasmineUnderTest.Env();
let suiteErrors = [];
env.addReporter({
suiteDone: function(result) {
const messages = result.failedExpectations.map(e => e.message);
suiteErrors = suiteErrors.concat(messages);
if (result.description === 'A nested suite') {
clearStackCallbacks[clearStackCallCount + 1] = function() {
dispatchErrorEvent(global, {
error: 'fail at the end of the reporter queue'
});
};
clearStackCallbacks[clearStackCallCount + 2] = function() {
dispatchErrorEvent(global, {
error: 'fail at the end of the suite queue'
});
};
}
}
});
env.describe('A suite', function() {
env.describe('A nested suite', function() {
env.it('a spec', function() {});
});
});
await env.execute();
expect(suiteErrors).toEqual([
'fail at the end of the reporter queue thrown',
'fail at the end of the suite queue thrown'
]);
});
});
describe('When the env has started reporting jasmineDone', function() {
it('logs the error to the console', async function() {
const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
},
clearTimeout: function(fn, delay) {
clearTimeout(fn, delay);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
}
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
env.cleanup_();
env = new jasmineUnderTest.Env();
spyOn(console, 'error');
env.addReporter({
jasmineDone: function() {
dispatchErrorEvent(global, { error: 'a very late error' });
}
});
env.it('a spec', function() {});
await env.execute();
/* eslint-disable-next-line no-console */
expect(console.error).toHaveBeenCalledWith(
'Jasmine received a result after the suite finished:'
);
/* eslint-disable-next-line no-console */
expect(console.error).toHaveBeenCalledWith(
jasmine.objectContaining({
message: 'a very late error thrown',
globalErrorType: 'afterAll'
})
);
});
});
it('routes all errors that occur during stack clearing somewhere', async function() {
const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
},
clearTimeout: function(fn) {
clearTimeout(fn);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
}
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
const realClearStack = jasmineUnderTest.getClearStack(global);
let clearStackCallCount = 0;
let jasmineDone = false;
const expectedErrors = [];
const expectedErrorsAfterJasmineDone = [];
spyOn(jasmineUnderTest, 'getClearStack').and.returnValue(function(fn) {
clearStackCallCount++;
const msg = `Error in clearStack #${clearStackCallCount}`;
if (jasmineDone) {
expectedErrorsAfterJasmineDone.push(`${msg} thrown`);
} else {
expectedErrors.push(`${msg} thrown`);
}
dispatchErrorEvent(global, { error: msg });
realClearStack(fn);
});
spyOn(console, 'error');
env.cleanup_();
env = new jasmineUnderTest.Env();
const receivedErrors = [];
function logErrors(event) {
for (const failure of event.failedExpectations) {
receivedErrors.push(failure.message);
}
}
env.addReporter({
specDone: logErrors,
suiteDone: logErrors,
jasmineDone: function(event) {
jasmineDone = true;
logErrors(event);
}
});
env.describe('A suite', function() {
env.it('is finishing when the failure occurs', function() {});
});
await env.execute();
expect(receivedErrors.length).toEqual(expectedErrors.length);
for (const e of expectedErrors) {
expect(receivedErrors).toContain(e);
}
for (const message of expectedErrorsAfterJasmineDone) {
/* eslint-disable-next-line no-console */
expect(console.error).toHaveBeenCalledWith(
jasmine.objectContaining({ message })
);
}
});
});
it('reports multiple calls to done in the top suite as errors', async function() {
const reporter = jasmine.createSpyObj('fakeReporter', ['jasmineDone']);
const message =
@@ -1071,7 +727,6 @@ describe('Env integration', function() {
env.describe('my suite', function() {
env.it('my spec', function() {});
// eslint-disable-next-line no-unused-vars
env.afterAll(function(afterAllDone) {
throw error;
});
@@ -1525,7 +1180,7 @@ describe('Env integration', function() {
env = new jasmineUnderTest.Env({
global: {
setTimeout: function(cb, t) {
const stack = jasmine.util.errorWithStack().stack;
const stack = new Error().stack;
if (stack.indexOf('ClearStack') >= 0) {
return realSetTimeout(cb, t);
} else {
@@ -1569,7 +1224,6 @@ describe('Env integration', function() {
env.addReporter(reporter);
jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = 8414;
// eslint-disable-next-line no-unused-vars
env.it("async spec that doesn't call done", function(underTestCallback) {
env.expect(true).toBeTruthy();
jasmine.clock().tick(8416);
@@ -1642,13 +1296,13 @@ describe('Env integration', function() {
realSetTimeout(function() {
try {
jasmine.clock().tick(10);
// eslint-disable-next-line no-unused-vars
} catch (e) {
// don't worry if the clock is already uninstalled
}
}, 100);
});
env.describe('beforeAll', function() {
// eslint-disable-next-line no-unused-vars
env.beforeAll(function(innerDone) {
realSetTimeout(function() {
jasmine.clock().tick(5001);
@@ -1664,7 +1318,6 @@ describe('Env integration', function() {
});
env.describe('afterAll', function() {
// eslint-disable-next-line no-unused-vars
env.afterAll(function(innerDone) {
realSetTimeout(function() {
jasmine.clock().tick(2001);
@@ -1680,7 +1333,6 @@ describe('Env integration', function() {
});
env.describe('beforeEach', function() {
// eslint-disable-next-line no-unused-vars
env.beforeEach(function(innerDone) {
realSetTimeout(function() {
jasmine.clock().tick(1001);
@@ -1696,7 +1348,6 @@ describe('Env integration', function() {
});
env.describe('afterEach', function() {
// eslint-disable-next-line no-unused-vars
env.afterEach(function(innerDone) {
realSetTimeout(function() {
jasmine.clock().tick(4001);
@@ -1713,7 +1364,6 @@ describe('Env integration', function() {
env.it(
'it times out',
// eslint-disable-next-line no-unused-vars
function(innerDone) {
realSetTimeout(function() {
jasmine.clock().tick(6001);
@@ -2699,12 +2349,11 @@ describe('Env integration', function() {
env.addReporter(reporter);
env.describe('async suite', function() {
// eslint-disable-next-line no-unused-vars
env.afterAll(function(innerDone) {
setTimeout(function() {
throw new Error('suite');
}, 1);
}, 10);
}, 50);
env.it('spec', function() {});
});
@@ -2712,13 +2361,12 @@ describe('Env integration', function() {
env.describe('suite', function() {
env.it(
'async spec',
// eslint-disable-next-line no-unused-vars
function(innerDone) {
setTimeout(function() {
throw new Error('spec');
}, 1);
},
10
50
);
});
@@ -2848,104 +2496,6 @@ describe('Env integration', function() {
);
});
it('reports errors that occur during loading', async function() {
const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
},
clearTimeout: function(fn, delay) {
clearTimeout(fn, delay);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
},
onerror: function() {}
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
env.cleanup_();
env = new jasmineUnderTest.Env();
const reporter = jasmine.createSpyObj('reporter', [
'jasmineDone',
'suiteDone',
'specDone'
]);
env.addReporter(reporter);
dispatchErrorEvent(global, {
message: 'Uncaught SyntaxError: Unexpected end of input',
error: undefined,
filename: 'borkenSpec.js',
lineno: 42
});
const error = new Error('ENOCHEESE');
dispatchErrorEvent(global, { error });
await env.execute();
const e = reporter.jasmineDone.calls.argsFor(0)[0];
expect(e.failedExpectations).toEqual([
{
passed: false,
globalErrorType: 'load',
message: 'Uncaught SyntaxError: Unexpected end of input',
stack: undefined,
filename: 'borkenSpec.js',
lineno: 42
},
{
passed: false,
globalErrorType: 'load',
message: 'ENOCHEESE',
stack: error.stack,
filename: undefined,
lineno: undefined
}
]);
});
describe('If suppressLoadErrors: true was passed', function() {
it('does not install a global error handler during loading', async function() {
const originalOnerror = jasmine.createSpy('original onerror');
const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) {
return setTimeout(fn, delay);
},
clearTimeout: function(fn, delay) {
clearTimeout(fn, delay);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
},
onerror: originalOnerror
};
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
const globalErrors = new jasmineUnderTest.GlobalErrors(global);
const onerror = jasmine.createSpy('onerror');
globalErrors.pushListener(onerror);
spyOn(jasmineUnderTest, 'GlobalErrors').and.returnValue(globalErrors);
env.cleanup_();
env = new jasmineUnderTest.Env({ suppressLoadErrors: true });
const reporter = jasmine.createSpyObj('reporter', [
'jasmineDone',
'suiteDone',
'specDone'
]);
env.addReporter(reporter);
global.onerror('Uncaught Error: ENOCHEESE');
await env.execute();
const e = reporter.jasmineDone.calls.argsFor(0)[0];
expect(e.failedExpectations).toEqual([]);
expect(originalOnerror).toHaveBeenCalledWith('Uncaught Error: ENOCHEESE');
});
});
describe('Overall status in the jasmineDone event', function() {
describe('When everything passes', function() {
it('is "passed"', async function() {
@@ -3863,345 +3413,33 @@ describe('Env integration', function() {
expect(failedExpectations).toEqual([]);
});
describe('#spyOnGlobalErrorsAsync', function() {
const leftInstalledMessage =
'Global error spy was not uninstalled. ' +
'(Did you forget to await the return value of spyOnGlobalErrorsAsync?)';
function resultForRunable(reporterSpy, fullName) {
const match = reporterSpy.calls.all().find(function(call) {
return call.args[0].fullName === fullName;
});
if (!match) {
throw new Error(`No result for runable "${fullName}"`);
}
return match.args[0];
}
it('allows global errors to be suppressed and spied on', async function() {
env.it('a passing spec', async function() {
await env.spyOnGlobalErrorsAsync(async spy => {
setTimeout(() => {
throw new Error('nope');
});
await new Promise(resolve => setTimeout(resolve));
env.expect(spy).toHaveBeenCalledWith(new Error('nope'));
});
});
env.it('a failing spec', async function() {
await env.spyOnGlobalErrorsAsync(async spy => {
setTimeout(() => {
throw new Error('yep');
});
await new Promise(resolve => setTimeout(resolve));
env.expect(spy).toHaveBeenCalledWith(new Error('nope'));
});
});
const reporter = jasmine.createSpyObj('reporter', ['specDone']);
env.addReporter(reporter);
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
await env.execute();
if (isBrowser) {
// Verify that there were no unexpected errors
expect(globalErrorSpy).toHaveBeenCalledTimes(2);
expect(globalErrorSpy).toHaveBeenCalledWith(new Error('nope'));
expect(globalErrorSpy).toHaveBeenCalledWith(new Error('yep'));
it('uses custom object formatters in spy strategy argument mismatch errors', async function() {
env.it('a spec', function() {
env.addCustomObjectFormatter(function(value) {
if (typeof value === 'string') {
return 'custom:' + value;
}
});
const passingResult = resultForRunable(
reporter.specDone,
'a passing spec'
);
expect(passingResult.status).toEqual('passed');
expect(passingResult.failedExpectations).toEqual([]);
const failingResult = resultForRunable(
reporter.specDone,
'a failing spec'
);
expect(failingResult.status).toEqual('failed');
expect(failingResult.failedExpectations[0].message).toMatch(
/Expected \$\[0] = Error: yep to equal Error: nope\./
);
const spy = env
.createSpy('foo')
.withArgs('x')
.and.returnValue('');
spy('y');
});
it('cleans up if the global error spy is left installed in a beforeAll', async function() {
env.configure({ random: false });
env.describe('Suite 1', function() {
env.beforeAll(async function() {
env.spyOnGlobalErrorsAsync(function() {
// Never resolves
return new Promise(() => {});
});
});
env.it('a spec', function() {});
});
env.describe('Suite 2', function() {
env.it('a spec', async function() {
setTimeout(function() {
throw new Error('should fail the spec');
});
await new Promise(resolve => setTimeout(resolve));
});
});
const reporter = jasmine.createSpyObj('reporter', [
'specDone',
'suiteDone'
]);
env.addReporter(reporter);
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
await env.execute();
if (isBrowser) {
// Verify that there were no unexpected errors
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
expect(globalErrorSpy).toHaveBeenCalledWith(
new Error('should fail the spec')
);
}
});
const suiteResult = resultForRunable(reporter.suiteDone, 'Suite 1');
expect(suiteResult.status).toEqual('failed');
expect(suiteResult.failedExpectations.length).toEqual(1);
expect(suiteResult.failedExpectations[0].message).toEqual(
leftInstalledMessage
);
const specResult = resultForRunable(reporter.specDone, 'Suite 2 a spec');
expect(specResult.status).toEqual('failed');
expect(specResult.failedExpectations.length).toEqual(1);
expect(specResult.failedExpectations[0].message).toMatch(
/Error: should fail the spec/
);
let failedExpectations;
env.addReporter({
specDone: r => (failedExpectations = r.failedExpectations)
});
it('cleans up if the global error spy is left installed in an afterAll', async function() {
env.configure({ random: false });
env.describe('Suite 1', function() {
env.afterAll(async function() {
env.spyOnGlobalErrorsAsync(function() {
// Never resolves
return new Promise(() => {});
});
});
env.it('a spec', function() {});
});
env.describe('Suite 2', function() {
env.it('a spec', async function() {
setTimeout(function() {
throw new Error('should fail the spec');
});
await new Promise(resolve => setTimeout(resolve));
});
});
const reporter = jasmine.createSpyObj('reporter', [
'specDone',
'suiteDone'
]);
env.addReporter(reporter);
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
await env.execute();
if (isBrowser) {
// Verify that there were no unexpected errors
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
expect(globalErrorSpy).toHaveBeenCalledWith(
new Error('should fail the spec')
);
}
});
expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable(
'Suite 1',
[leftInstalledMessage]
);
const suiteResult = resultForRunable(reporter.suiteDone, 'Suite 1');
expect(suiteResult.status).toEqual('failed');
expect(suiteResult.failedExpectations.length).toEqual(1);
expect(suiteResult.failedExpectations[0].message).toEqual(
leftInstalledMessage
);
const specResult = resultForRunable(reporter.specDone, 'Suite 2 a spec');
expect(specResult.status).toEqual('failed');
expect(specResult.failedExpectations.length).toEqual(1);
expect(specResult.failedExpectations[0].message).toMatch(
/Error: should fail the spec/
);
});
it('cleans up if the global error spy is left installed in a beforeEach', async function() {
env.configure({ random: false });
env.describe('Suite 1', function() {
env.beforeEach(async function() {
env.spyOnGlobalErrorsAsync(function() {
// Never resolves
return new Promise(() => {});
});
});
env.it('a spec', function() {});
});
env.describe('Suite 2', function() {
env.it('a spec', async function() {
setTimeout(function() {
throw new Error('should fail the spec');
});
await new Promise(resolve => setTimeout(resolve));
});
});
const reporter = jasmine.createSpyObj('reporter', [
'specDone',
'suiteDone'
]);
env.addReporter(reporter);
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
await env.execute();
if (isBrowser) {
// Verify that there were no unexpected errors
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
expect(globalErrorSpy).toHaveBeenCalledWith(
new Error('should fail the spec')
);
}
});
const spec1Result = resultForRunable(reporter.specDone, 'Suite 1 a spec');
expect(spec1Result.status).toEqual('failed');
expect(spec1Result.failedExpectations.length).toEqual(1);
expect(spec1Result.failedExpectations[0].message).toEqual(
leftInstalledMessage
);
const spec2Result = resultForRunable(reporter.specDone, 'Suite 2 a spec');
expect(spec2Result.status).toEqual('failed');
expect(spec2Result.failedExpectations.length).toEqual(1);
expect(spec2Result.failedExpectations[0].message).toMatch(
/Error: should fail the spec/
);
});
it('cleans up if the global error spy is left installed in an it', async function() {
env.configure({ random: false });
env.it('spec 1', async function() {
env.spyOnGlobalErrorsAsync(function() {
// Never resolves
return new Promise(() => {});
});
});
env.it('spec 2', async function() {
setTimeout(function() {
throw new Error('should fail the spec');
});
await new Promise(resolve => setTimeout(resolve));
});
const reporter = jasmine.createSpyObj('reporter', ['specDone']);
env.addReporter(reporter);
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
await env.execute();
if (isBrowser) {
// Verify that there were no unexpected errors
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
expect(globalErrorSpy).toHaveBeenCalledWith(
new Error('should fail the spec')
);
}
});
const spec1Result = resultForRunable(reporter.specDone, 'spec 1');
expect(spec1Result.status).toEqual('failed');
expect(spec1Result.failedExpectations.length).toEqual(1);
expect(spec1Result.failedExpectations[0].message).toEqual(
leftInstalledMessage
);
const spec2Result = resultForRunable(reporter.specDone, 'spec 2');
expect(spec2Result.status).toEqual('failed');
expect(spec2Result.failedExpectations.length).toEqual(1);
expect(spec2Result.failedExpectations[0].message).toMatch(
/Error: should fail the spec/
);
});
it('cleans up if the global error spy is left installed in an afterEach', async function() {
env.configure({ random: false });
env.describe('Suite 1', function() {
env.afterEach(async function() {
env.spyOnGlobalErrorsAsync(function() {
// Never resolves
return new Promise(() => {});
});
});
env.it('a spec', function() {});
});
env.describe('Suite 2', function() {
env.it('a spec', async function() {
setTimeout(function() {
throw new Error('should fail the spec');
});
await new Promise(resolve => setTimeout(resolve));
});
});
const reporter = jasmine.createSpyObj('reporter', [
'specDone',
'suiteDone'
]);
env.addReporter(reporter);
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
await env.execute();
if (isBrowser) {
// Verify that there were no unexpected errors
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
expect(globalErrorSpy).toHaveBeenCalledWith(
new Error('should fail the spec')
);
}
});
const spec1Result = resultForRunable(reporter.specDone, 'Suite 1 a spec');
expect(spec1Result.status).toEqual('failed');
expect(spec1Result.failedExpectations.length).toEqual(1);
expect(spec1Result.failedExpectations[0].message).toEqual(
leftInstalledMessage
);
const spec2Result = resultForRunable(reporter.specDone, 'Suite 2 a spec');
expect(spec2Result.status).toEqual('failed');
expect(spec2Result.failedExpectations.length).toEqual(1);
expect(spec2Result.failedExpectations[0].message).toMatch(
/Error: should fail the spec/
);
});
await env.execute();
expect(failedExpectations).toEqual([
jasmine.objectContaining({
message: jasmine.stringContaining(
'received a call with arguments [ custom:y ]'
)
})
]);
});
it('reports a suite level error when a describe fn throws', async function() {
@@ -4362,6 +3600,7 @@ describe('Env integration', function() {
env.it('a spec', function() {
try {
env.throwUnless(1).toEqual(1);
// eslint-disable-next-line no-unused-vars
} catch (e) {
threw = true;
}
@@ -4375,6 +3614,7 @@ describe('Env integration', function() {
env.it('a spec', function() {
try {
env.throwUnless(1).toEqual(2);
// eslint-disable-next-line no-unused-vars
} catch (e) {}
});
@@ -4418,6 +3658,7 @@ describe('Env integration', function() {
env.it('a spec', async function() {
try {
await env.throwUnlessAsync(Promise.resolve()).toBeResolved();
// eslint-disable-next-line no-unused-vars
} catch (e) {
threw = true;
}
@@ -4431,6 +3672,7 @@ describe('Env integration', function() {
env.it('a spec', async function() {
try {
await env.throwUnlessAsync(Promise.resolve()).toBeRejected();
// eslint-disable-next-line no-unused-vars
} catch (e) {}
});

File diff suppressed because it is too large Load Diff

View File

@@ -866,7 +866,6 @@ describe('spec running', function() {
const actions = [];
env.describe('Something', function() {
// eslint-disable-next-line no-unused-vars
env.beforeEach(function(innerDone) {
actions.push('beforeEach');
}, 1);

View File

@@ -161,6 +161,63 @@ describe('DiffBuilder', function() {
expect(diffBuilder.getMessage()).toEqual(expectedMsg);
});
it('handles cases where only the expected has a custom object formatter', function() {
const formatter = function(x) {
if (typeof x === 'number') {
return '[number:' + x + ']';
}
};
const prettyPrinter = jasmineUnderTest.makePrettyPrinter([formatter]);
const diffBuilder = new jasmineUnderTest.DiffBuilder({
prettyPrinter: prettyPrinter
});
diffBuilder.setRoots('five', 4);
diffBuilder.recordMismatch();
expect(diffBuilder.getMessage()).toEqual(
"Expected 'five' to equal [number:4]."
);
});
it('handles cases where only the actual has a custom object formatter', function() {
const formatter = function(x) {
if (typeof x === 'number') {
return '[number:' + x + ']';
}
};
const prettyPrinter = jasmineUnderTest.makePrettyPrinter([formatter]);
const diffBuilder = new jasmineUnderTest.DiffBuilder({
prettyPrinter: prettyPrinter
});
diffBuilder.setRoots(5, 'four');
diffBuilder.recordMismatch();
expect(diffBuilder.getMessage()).toEqual(
"Expected [number:5] to equal 'four'."
);
});
it('handles complex cases where only one side has a custom object formatter', function() {
const formatter = function(x) {
if (typeof x === 'number') {
return '[number:' + x + ']';
}
};
const prettyPrinter = jasmineUnderTest.makePrettyPrinter([formatter]);
const diffBuilder = new jasmineUnderTest.DiffBuilder({
prettyPrinter: prettyPrinter
});
diffBuilder.setRoots(5, { foo: 'bar', fnord: { graults: ['wombat'] } });
diffBuilder.recordMismatch();
expect(diffBuilder.getMessage()).toEqual(
"Expected [number:5] to equal Object({ foo: 'bar', fnord: Object({ graults: [ 'wombat' ] }) })."
);
});
it('builds diffs involving asymmetric equality testers that implement valuesForDiff_ at the root', function() {
const prettyPrinter = jasmineUnderTest.makePrettyPrinter([]),
diffBuilder = new jasmineUnderTest.DiffBuilder({

View File

@@ -768,7 +768,9 @@ describe('matchersUtil', function() {
const a1 = new TypedArrayCtor(2);
const a2 = new TypedArrayCtor(1);
a1[0] = a1[1] = a2[0] = 0;
expect(matchersUtil.equals(a1, a2)).toBe(false);
const diffBuilder = new jasmineUnderTest.DiffBuilder();
expect(matchersUtil.equals(a1, a2, diffBuilder)).toBe(false);
jasmine.debugLog('Diff: ' + diffBuilder.getMessage());
});
it(
@@ -779,7 +781,9 @@ describe('matchersUtil', function() {
const a2 = new TypedArrayCtor(1);
a1[0] = 0;
a2[0] = 1;
expect(matchersUtil.equals(a1, a2)).toBe(false);
const diffBuilder = new jasmineUnderTest.DiffBuilder();
expect(matchersUtil.equals(a1, a2, diffBuilder)).toBe(false);
jasmine.debugLog('Diff: ' + diffBuilder.getMessage());
}
);
@@ -826,9 +830,7 @@ describe('matchersUtil', function() {
const matchersUtil = new jasmineUnderTest.MatchersUtil();
const a1 = new TypedArrayCtor(2);
const a2 = new TypedArrayCtor(2);
// eslint-disable-next-line compat/compat
a1[0] = a2[0] = BigInt(0);
// eslint-disable-next-line compat/compat
a1[1] = a2[1] = BigInt(1);
expect(matchersUtil.equals(a1, a2)).toBe(true);
}
@@ -839,7 +841,6 @@ describe('matchersUtil', function() {
const matchersUtil = new jasmineUnderTest.MatchersUtil();
const a1 = new TypedArrayCtor(2);
const a2 = new TypedArrayCtor(1);
// eslint-disable-next-line compat/compat
a1[0] = a1[1] = a2[0] = BigInt(0);
expect(matchersUtil.equals(a1, a2)).toBe(false);
});
@@ -851,9 +852,7 @@ describe('matchersUtil', function() {
const matchersUtil = new jasmineUnderTest.MatchersUtil();
const a1 = new TypedArrayCtor(2);
const a2 = new TypedArrayCtor(2);
// eslint-disable-next-line compat/compat
a1[0] = a1[1] = a2[0] = BigInt(0);
// eslint-disable-next-line compat/compat
a2[1] = BigInt(1);
expect(matchersUtil.equals(a1, a2)).toBe(false);
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable compat/compat */
describe('toEqual', function() {
'use strict';
@@ -459,9 +458,9 @@ describe('toEqual', function() {
});
it('reports mismatches between Functions', function() {
const actual = { x: function() {} },
expected = { x: function() {} },
message = 'Expected $.x = Function to equal Function.';
const actual = { x: function() {} };
const expected = { x: function() {} };
const message = "Expected $.x = Function 'x' to equal Function 'x'.";
expect(compareEquals(actual, expected).message).toEqual(message);
});

View File

@@ -85,7 +85,7 @@ describe('toHaveNoOtherSpyInteractions', function() {
);
});
it(`throws an error if a non-object is passed`, function() {
it('throws an error if a non-object is passed', function() {
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
expect(function() {

View File

@@ -70,7 +70,7 @@ describe('toHaveSpyInteractions', function() {
);
});
it(`throws an error if a non-object is passed`, function() {
it('throws an error if a non-object is passed', function() {
let matcher = jasmineUnderTest.matchers.toHaveSpyInteractions();
expect(function() {

View File

@@ -1,22 +1,21 @@
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');
const child_process = require('node:child_process');
describe('npm package', function() {
const path = require('path'),
temp = require('temp').track(),
fs = require('fs');
beforeAll(function() {
const shell = require('shelljs'),
pack = shell.exec('npm pack', { silent: true });
const packOutput = child_process.execSync('npm pack', {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe']
});
this.tarball = packOutput.split('\n')[0];
const prefix = path.join(os.tmpdir(), 'jasmine-npm-package');
this.tmpDir = fs.mkdtempSync(prefix);
this.tarball = pack.stdout.split('\n')[0];
this.tmpDir = temp.mkdirSync(); // automatically deleted on exit
const untar = shell.exec(
'tar -xzf ' + this.tarball + ' -C ' + this.tmpDir,
{
silent: true
}
);
expect(untar.code).toBe(0);
child_process.execSync(`tar -xzf ${this.tarball} -C ${this.tmpDir}`, {
encoding: 'utf8'
});
this.packagedCore = require(path.join(
this.tmpDir,
@@ -41,6 +40,7 @@ describe('npm package', function() {
afterAll(function() {
fs.unlinkSync(this.tarball);
fs.rmSync(this.tmpDir, { recursive: true });
});
it('has a root path', function() {

View File

@@ -8,6 +8,7 @@ config.clearReporters = true;
config.jasmineCore = jasmineCore;
jasmineBrowser.runSpecs(config).catch(function(error) {
// eslint-disable-next-line no-console
console.error(error);
process.exit(1);
});

View File

@@ -38,7 +38,7 @@ module.exports = {
name: `jasmine-core ${new Date().toISOString()}`,
build: `Core ${process.env.CIRCLE_BUILD_NUM || 'Ran locally'}`,
tags: ['Jasmine-Core'],
tunnelIdentifier: process.env.SAUCE_TUNNEL_IDENTIFIER,
tunnelName: process.env.SAUCE_TUNNEL_NAME,
username: process.env.SAUCE_USERNAME,
accessKey: process.env.SAUCE_ACCESS_KEY
}

View File

@@ -9,7 +9,7 @@ getJasmineRequireObj().CallTracker = function(j$) {
this.track = function(context) {
if (opts.cloneArgs) {
context.args = j$.util.cloneArgs(context.args);
context.args = opts.argsCloner(context.args);
}
calls.push(context);
};
@@ -117,13 +117,15 @@ getJasmineRequireObj().CallTracker = function(j$) {
};
/**
* Set this spy to do a shallow clone of arguments passed to each invocation.
* Set this spy to do a clone of arguments passed to each invocation.
* @name Spy#calls#saveArgumentsByValue
* @since 2.5.0
* @param {Function} [argsCloner] A function to use to clone the arguments. Defaults to a shallow cloning function.
* @function
*/
this.saveArgumentsByValue = function() {
this.saveArgumentsByValue = function(argsCloner = j$.util.cloneArgs) {
opts.cloneArgs = true;
opts.argsCloner = argsCloner;
};
this.unverifiedCount = function() {

View File

@@ -44,7 +44,7 @@ getJasmineRequireObj().clearStack = function(j$) {
function getUnclampedSetTimeout(global) {
const { setTimeout } = global;
if (j$.util.isUndefined(global.MessageChannel)) {
if (!global.MessageChannel) {
return setTimeout;
}
@@ -104,10 +104,7 @@ getJasmineRequireObj().clearStack = function(j$) {
// Unlike browsers, Node doesn't require us to do a periodic setTimeout
// so we avoid the overhead.
return nodeQueueMicrotaskImpl(global);
} else if (
SAFARI_OR_WIN_WEBKIT ||
j$.util.isUndefined(global.MessageChannel) /* tests */
) {
} else if (SAFARI_OR_WIN_WEBKIT || !global.MessageChannel /* tests */) {
// queueMicrotask is dramatically faster than MessageChannel in Safari
// and other WebKit-based browsers, such as the one distributed by Playwright
// to test Safari-like behavior on Windows.

View File

@@ -29,6 +29,15 @@ getJasmineRequireObj().Clock = function() {
let installed = false;
let delayedFunctionScheduler;
let timer;
// Tracks how the clock ticking behaves.
// By default, the clock only ticks when the user manually calls a tick method.
// There is also an 'auto' mode which will advance the clock automatically to
// to the next task. Once enabled, there is currently no mechanism for users
// to disable the auto ticking.
let tickMode = {
mode: 'manual',
counter: 0
};
this.FakeTimeout = FakeTimeout;
@@ -60,6 +69,10 @@ getJasmineRequireObj().Clock = function() {
* @function
*/
this.uninstall = function() {
// Ensure auto ticking loop is aborted when clock is uninstalled
if (tickMode.mode === 'auto') {
tickMode = { mode: 'manual', counter: tickMode.counter + 1 };
}
delayedFunctionScheduler = null;
mockDate.uninstall();
replace(global, realTimingFunctions);
@@ -138,8 +151,104 @@ getJasmineRequireObj().Clock = function() {
}
};
/**
* Updates the clock to automatically advance time.
*
* With this mode, the clock advances to the first scheduled timer and fires it, in a loop.
* Between each timer, it will also break the event loop, allowing any scheduled promise
callbacks to execute _before_ running the next one.
*
* This mode allows tests to be authored in a way that does not need to be aware of the
* mock clock. Consequently, tests which have been authored without a mock clock installed
* can one with auto tick enabled without any other updates to the test logic.
*
* In many cases, this can greatly improve test execution speed because asynchronous tasks
* will execute as quickly as possible rather than waiting real time to complete.
*
* Furthermore, tests can be authored in a consistent manner. They can always be written in an asynchronous style
* rather than having `tick` sprinkled throughout the tests with mock time in order to manually
* advance the clock.
*
* When auto tick is enabled, `tick` can still be used to synchronously advance the clock if necessary.
* @name Clock#autoTick
* @function
* @since 5.7.0
*/
this.autoTick = function() {
if (tickMode.mode === 'auto') {
return;
}
tickMode = { mode: 'auto', counter: tickMode.counter + 1 };
advanceUntilModeChanges();
};
return this;
// Advances the Clock's time until the mode changes.
//
// The time is advanced asynchronously, giving microtasks and events a chance
// to run before each timer runs.
//
// @function
// @return {!Promise<undefined>}
async function advanceUntilModeChanges() {
if (!installed) {
throw new Error(
'Mock clock is not installed, use jasmine.clock().install()'
);
}
const { counter } = tickMode;
while (true) {
await newMacrotask();
if (
tickMode.counter !== counter ||
!installed ||
delayedFunctionScheduler === null
) {
return;
}
if (!delayedFunctionScheduler.isEmpty()) {
delayedFunctionScheduler.runNextQueuedFunction(function(millis) {
mockDate.tick(millis);
});
}
}
}
// Waits until a new macro task.
//
// Used with autoTick(), which is meant to act when the test is waiting, we need
// to insert ourselves in the macro task queue.
//
// @return {!Promise<undefined>}
async function newMacrotask() {
if (NODE_JS) {
// setImmediate is generally faster than setTimeout in Node
// https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#setimmediate-vs-settimeout
return new Promise(resolve => void setImmediate(resolve));
}
// MessageChannel ensures that setTimeout is not throttled to 4ms.
// https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#reasons_for_delays_longer_than_specified
// https://stackblitz.com/edit/stackblitz-starters-qtlpcc
// Note: This trick does not work in Safari, which will still throttle the setTimeout
const channel = new MessageChannel();
await new Promise(resolve => {
channel.port1.onmessage = resolve;
channel.port2.postMessage(undefined);
});
channel.port1.close();
channel.port2.close();
// setTimeout ensures that we interleave with other setTimeouts.
await new Promise(resolve => {
realTimingFunctions.setTimeout.call(global, resolve);
});
}
function originalTimingFunctionsIntact() {
return (
global.setTimeout === realTimingFunctions.setTimeout &&

View File

@@ -6,7 +6,7 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
this.scheduledLookup_ = [];
this.scheduledFunctions_ = {};
this.currentTime_ = 0;
this.delayedFnCount_ = 0;
this.delayedFnStartCount_ = 1e12; // arbitrarily large number to avoid collisions with native timer IDs;
this.deletedKeys_ = [];
this.tick = function(millis, tickDate) {
@@ -38,7 +38,7 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
}
millis = millis || 0;
timeoutKey = timeoutKey || ++this.delayedFnCount_;
timeoutKey = timeoutKey || ++this.delayedFnStartCount_;
runAtMillis = runAtMillis || this.currentTime_ + millis;
const funcToSchedule = {
@@ -87,6 +87,37 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
}
};
// Returns whether there are any scheduled functions.
// Returns true if there are any scheduled functions, otherwise false.
this.isEmpty = function() {
return this.scheduledFunctions_.length === 0;
};
// Runs the next timeout in the queue, advancing the clock.
this.runNextQueuedFunction = function(tickDate) {
if (this.scheduledLookup_.length === 0) {
return;
}
const newCurrentTime = this.scheduledLookup_[0];
if (newCurrentTime >= this.currentTime_) {
tickDate(newCurrentTime - this.currentTime_);
this.currentTime_ = newCurrentTime;
}
const funcsAtTime = this.scheduledFunctions_[this.currentTime_];
const fn = funcsAtTime.shift();
if (funcsAtTime.length === 0) {
delete this.scheduledFunctions_[this.currentTime_];
this.scheduledLookup_.splice(0, 1);
}
if (fn.recurring) {
this.reschedule_(fn);
}
fn.funcToCall.apply(null, fn.params || []);
};
return this;
}

View File

@@ -36,6 +36,7 @@ getJasmineRequireObj().Deprecator = function(j$) {
Deprecator.prototype.log_ = function(runnable, deprecation, options) {
if (j$.isError_(deprecation)) {
// eslint-disable-next-line no-console
console.error(deprecation);
return;
}
@@ -58,12 +59,13 @@ getJasmineRequireObj().Deprecator = function(j$) {
context += '\n' + verboseNote;
}
// eslint-disable-next-line no-console
console.error('DEPRECATION: ' + deprecation + context);
};
Deprecator.prototype.stackTrace_ = function() {
const formatter = new j$.ExceptionFormatter();
return formatter.stack(j$.util.errorWithStack()).replace(/^Error\n/m, '');
return formatter.stack(new Error()).replace(/^Error\n/m, '');
};
Deprecator.prototype.report_ = function(runnable, deprecation, options) {

View File

@@ -385,7 +385,9 @@ getJasmineRequireObj().Env = function(j$) {
// If we get here, all results have been reported and there's nothing we
// can do except log the result and hope the user sees it.
// eslint-disable-next-line no-console
console.error('Jasmine received a result after the suite finished:');
// eslint-disable-next-line no-console
console.error(expectationResult);
}

View File

@@ -148,7 +148,7 @@ getJasmineRequireObj().Expectation = function(j$) {
return function() {
// Capture the call stack here, before we go async, so that it will contain
// frames that are relevant to the user instead of just parts of Jasmine.
const errorForStack = j$.util.errorWithStack();
const errorForStack = new Error();
return this.expector
.compare(name, matcherFactory, arguments)

View File

@@ -1,133 +1,36 @@
getJasmineRequireObj().GlobalErrors = function(j$) {
function GlobalErrors(global) {
global = global || j$.getGlobal();
class GlobalErrors {
#adapter;
#handlers;
#overrideHandler;
#onRemoveOverrideHandler;
const handlers = [];
let overrideHandler = null,
onRemoveOverrideHandler = null;
constructor(global) {
global = global || j$.getGlobal();
const dispatchError = this.#dispatchError.bind(this);
function onBrowserError(event) {
dispatchBrowserError(event.error, event);
}
function dispatchBrowserError(error, event) {
if (overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
overrideHandler(error);
return;
}
const handler = handlers[handlers.length - 1];
if (handler) {
handler(error, event);
} else {
throw error;
}
}
this.originalHandlers = {};
this.jasmineHandlers = {};
this.installOne_ = function installOne_(errorType, jasmineMessage) {
function taggedOnError(error) {
if (j$.isError_(error)) {
error.jasmineMessage = jasmineMessage + ': ' + error;
} else {
let substituteMsg;
if (error) {
substituteMsg = jasmineMessage + ': ' + error;
} else {
substituteMsg = jasmineMessage + ' with no error or message';
}
if (errorType === 'unhandledRejection') {
substituteMsg +=
'\n' +
'(Tip: to get a useful stack trace, use ' +
'Promise.reject(new Error(...)) instead of Promise.reject(' +
(error ? '...' : '') +
').)';
}
error = new Error(substituteMsg);
}
const handler = handlers[handlers.length - 1];
if (overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
overrideHandler(error);
return;
}
if (handler) {
handler(error);
} else {
throw error;
}
}
this.originalHandlers[errorType] = global.process.listeners(errorType);
this.jasmineHandlers[errorType] = taggedOnError;
global.process.removeAllListeners(errorType);
global.process.on(errorType, taggedOnError);
this.uninstall = function uninstall() {
const errorTypes = Object.keys(this.originalHandlers);
for (const errorType of errorTypes) {
global.process.removeListener(
errorType,
this.jasmineHandlers[errorType]
);
for (let i = 0; i < this.originalHandlers[errorType].length; i++) {
global.process.on(errorType, this.originalHandlers[errorType][i]);
}
delete this.originalHandlers[errorType];
delete this.jasmineHandlers[errorType];
}
};
};
this.install = function install() {
if (
global.process &&
global.process.listeners &&
j$.isFunction_(global.process.on)
) {
this.installOne_('uncaughtException', 'Uncaught exception');
this.installOne_('unhandledRejection', 'Unhandled promise rejection');
this.#adapter = new NodeAdapter(global, dispatchError);
} else {
global.addEventListener('error', onBrowserError);
const browserRejectionHandler = function browserRejectionHandler(
event
) {
if (j$.isError_(event.reason)) {
event.reason.jasmineMessage =
'Unhandled promise rejection: ' + event.reason;
dispatchBrowserError(event.reason, event);
} else {
dispatchBrowserError(
'Unhandled promise rejection: ' + event.reason,
event
);
}
};
global.addEventListener('unhandledrejection', browserRejectionHandler);
this.uninstall = function uninstall() {
global.removeEventListener('error', onBrowserError);
global.removeEventListener(
'unhandledrejection',
browserRejectionHandler
);
};
this.#adapter = new BrowserAdapter(global, dispatchError);
}
};
this.#handlers = [];
this.#overrideHandler = null;
this.#onRemoveOverrideHandler = null;
}
install() {
this.#adapter.install();
}
uninstall() {
this.#adapter.uninstall();
}
// The listener at the top of the stack will be called with two arguments:
// the error and the event. Either of them may be falsy.
@@ -136,35 +39,183 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
// browsers but will be falsy in Node.
// Listeners that are pushed after spec files have been loaded should be
// able to just use the error parameter.
this.pushListener = function pushListener(listener) {
handlers.push(listener);
};
pushListener(listener) {
this.#handlers.push(listener);
}
this.popListener = function popListener(listener) {
popListener(listener) {
if (!listener) {
throw new Error('popListener expects a listener');
}
handlers.pop();
};
this.#handlers.pop();
}
this.setOverrideListener = function(listener, onRemove) {
if (overrideHandler) {
setOverrideListener(listener, onRemove) {
if (this.#overrideHandler) {
throw new Error("Can't set more than one override listener at a time");
}
overrideHandler = listener;
onRemoveOverrideHandler = onRemove;
};
this.#overrideHandler = listener;
this.#onRemoveOverrideHandler = onRemove;
}
this.removeOverrideListener = function() {
if (onRemoveOverrideHandler) {
onRemoveOverrideHandler();
removeOverrideListener() {
if (this.#onRemoveOverrideHandler) {
this.#onRemoveOverrideHandler();
}
overrideHandler = null;
onRemoveOverrideHandler = null;
};
this.#overrideHandler = null;
this.#onRemoveOverrideHandler = null;
}
// Either error or event may be undefined
#dispatchError(error, event) {
if (this.#overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
this.#overrideHandler(error);
return;
}
const handler = this.#handlers[this.#handlers.length - 1];
if (handler) {
handler(error, event);
} else {
throw error;
}
}
}
class BrowserAdapter {
#global;
#dispatchError;
#onError;
#onUnhandledRejection;
constructor(global, dispatchError) {
this.#global = global;
this.#dispatchError = dispatchError;
this.#onError = event => this.#dispatchError(event.error, event);
this.#onUnhandledRejection = this.#unhandledRejectionHandler.bind(this);
}
install() {
this.#global.addEventListener('error', this.#onError);
this.#global.addEventListener(
'unhandledrejection',
this.#onUnhandledRejection
);
}
uninstall() {
this.#global.removeEventListener('error', this.#onError);
this.#global.removeEventListener(
'unhandledrejection',
this.#onUnhandledRejection
);
}
#unhandledRejectionHandler(event) {
if (j$.isError_(event.reason)) {
event.reason.jasmineMessage =
'Unhandled promise rejection: ' + event.reason;
this.#dispatchError(event.reason, event);
} else {
this.#dispatchError(
'Unhandled promise rejection: ' + event.reason,
event
);
}
}
}
class NodeAdapter {
#global;
#dispatchError;
#originalHandlers;
#jasmineHandlers;
#onError;
#onUnhandledRejection;
constructor(global, dispatchError) {
this.#global = global;
this.#dispatchError = dispatchError;
this.#jasmineHandlers = {};
this.#originalHandlers = {};
this.#onError = error =>
this.#eventHandler(error, 'uncaughtException', 'Uncaught exception');
this.#onUnhandledRejection = error =>
this.#eventHandler(
error,
'unhandledRejection',
'Unhandled promise rejection'
);
}
install() {
this.#installHandler('uncaughtException', this.#onError);
this.#installHandler('unhandledRejection', this.#onUnhandledRejection);
}
uninstall() {
const errorTypes = Object.keys(this.#originalHandlers);
for (const errorType of errorTypes) {
this.#global.process.removeListener(
errorType,
this.#jasmineHandlers[errorType]
);
for (let i = 0; i < this.#originalHandlers[errorType].length; i++) {
this.#global.process.on(
errorType,
this.#originalHandlers[errorType][i]
);
}
delete this.#originalHandlers[errorType];
delete this.#jasmineHandlers[errorType];
}
}
#installHandler(errorType, handler) {
this.#originalHandlers[errorType] = this.#global.process.listeners(
errorType
);
this.#jasmineHandlers[errorType] = handler;
this.#global.process.removeAllListeners(errorType);
this.#global.process.on(errorType, handler);
}
#eventHandler(error, errorType, jasmineMessage) {
if (j$.isError_(error)) {
error.jasmineMessage = jasmineMessage + ': ' + error;
} else {
let substituteMsg;
if (error) {
substituteMsg = jasmineMessage + ': ' + error;
} else {
substituteMsg = jasmineMessage + ' with no error or message';
}
if (errorType === 'unhandledRejection') {
substituteMsg +=
'\n' +
'(Tip: to get a useful stack trace, use ' +
'Promise.reject(new Error(...)) instead of Promise.reject(' +
(error ? '...' : '') +
').)';
}
error = new Error(substituteMsg);
}
this.#dispatchError(error);
}
}
return GlobalErrors;

View File

@@ -15,7 +15,7 @@ getJasmineRequireObj().MockDate = function(j$) {
if (mockDate instanceof GlobalDate) {
currentTime = mockDate.getTime();
} else {
if (!j$.util.isUndefined(mockDate)) {
if (mockDate !== undefined) {
throw new Error(
'The argument to jasmine.clock().mockDate(), if specified, ' +
'should be a Date instance.'

View File

@@ -16,7 +16,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
if (customFormatResult) {
this.emitScalar(customFormatResult);
} else if (j$.util.isUndefined(value)) {
} else if (value === undefined) {
this.emitScalar('undefined');
} else if (value === null) {
this.emitScalar('null');
@@ -35,7 +35,11 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
} else if (value instanceof RegExp) {
this.emitScalar(value.toString());
} else if (typeof value === 'function') {
this.emitScalar('Function');
if (value.name) {
this.emitScalar(`Function '${value.name}'`);
} else {
this.emitScalar('Function');
}
} else if (j$.isDomNode(value)) {
if (value.tagName) {
this.emitDomElement(value);
@@ -58,6 +62,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
) {
try {
this.emitScalar(value.toString());
// eslint-disable-next-line no-unused-vars
} catch (e) {
this.emitScalar('has-invalid-toString-method');
}
@@ -304,6 +309,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
value.toString !== Object.prototype.toString &&
value.toString() !== Object.prototype.toString.call(value)
);
// eslint-disable-next-line no-unused-vars
} catch (e) {
// The custom toString() threw.
return true;

View File

@@ -22,6 +22,7 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}
function fallbackOnMultipleDone() {
// eslint-disable-next-line no-console
console.error(
new Error(
"An asynchronous function called its 'done' " +
@@ -135,6 +136,7 @@ getJasmineRequireObj().QueueRunner = function(j$) {
// Any error we catch here is probably due to a bug in Jasmine,
// and it's not likely to end up anywhere useful if we let it
// propagate. Log it so it can at least show up when debugging.
// eslint-disable-next-line no-console
console.error(error);
}
}

View File

@@ -21,11 +21,11 @@ getJasmineRequireObj().Spec = function(j$) {
this.onStart = attrs.onStart || function() {};
this.autoCleanClosures =
attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures;
this.getSpecName =
attrs.getSpecName ||
function() {
return '';
};
this.getPath = function() {
return attrs.getPath ? attrs.getPath(this) : [];
};
this.onLateError = attrs.onLateError || function() {};
this.catchingExceptions =
attrs.catchingExceptions ||
@@ -147,7 +147,12 @@ getJasmineRequireObj().Spec = function(j$) {
* @property {String} description - The description passed to the {@link it} that created this spec.
* @property {String} fullName - The full description including all ancestors of this spec.
* @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe().
* @property {String} filename - The name of the file the spec was defined in.
* @property {String} filename - Deprecated. The name of the file the spec was defined in.
* Note: The value may be incorrect if zone.js is installed or
* `it`/`fit`/`xit` have been replaced with versions that don't maintain the
* same call stack height as the originals. This property may be removed in
* a future version unless there is enough user interest in keeping it.
* See {@link https://github.com/jasmine/jasmine/issues/2065}.
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec.
* @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec.
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec.
@@ -251,7 +256,7 @@ getJasmineRequireObj().Spec = function(j$) {
};
Spec.prototype.getFullName = function() {
return this.getSpecName(this);
return this.getPath().join(' ');
};
Spec.prototype.addDeprecationWarning = function(deprecation) {
@@ -305,6 +310,10 @@ getJasmineRequireObj().Spec = function(j$) {
* @since 2.0.0
*/
Object.defineProperty(Spec.prototype, 'metadata', {
// NOTE: Although most of jasmine-core only exposes these metadata objects,
// actual Spec instances are still passed to Configuration#specFilter. Until
// that is fixed, it's important to make sure that all metadata properties
// also exist in compatible form on the underlying Spec.
get: function() {
if (!this.metadata_) {
this.metadata_ = {
@@ -333,7 +342,16 @@ getJasmineRequireObj().Spec = function(j$) {
* @returns {string}
* @since 2.0.0
*/
getFullName: this.getFullName.bind(this)
getFullName: this.getFullName.bind(this),
/**
* The full path of the spec, as an array of names.
* @name Spec#getPath
* @function
* @returns {Array.<string>}
* @since 5.7.0
*/
getPath: this.getPath.bind(this)
};
}

View File

@@ -161,7 +161,7 @@ getJasmineRequireObj().Spy = function(j$) {
"Spy '" +
strategyArgs.name +
"' received a call with arguments " +
j$.basicPrettyPrinter_(Array.prototype.slice.call(args)) +
matchersUtil.pp(Array.prototype.slice.call(args)) +
' but all configured strategies specify other arguments.'
);
} else {

View File

@@ -25,7 +25,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
this.spyOn = function(obj, methodName) {
const getErrorMsg = spyOnMsg;
if (j$.util.isUndefined(obj) || obj === null) {
if (obj === undefined || obj === null) {
throw new Error(
getErrorMsg(
'could not find an object to spy upon for ' + methodName + '()'
@@ -33,11 +33,11 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
);
}
if (j$.util.isUndefined(methodName) || methodName === null) {
if (methodName === undefined || methodName === null) {
throw new Error(getErrorMsg('No method name supplied'));
}
if (j$.util.isUndefined(obj[methodName])) {
if (obj[methodName] === undefined) {
throw new Error(getErrorMsg(methodName + '() method does not exist'));
}
@@ -102,7 +102,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
accessType = accessType || 'get';
if (j$.util.isUndefined(obj)) {
if (!obj) {
throw new Error(
getErrorMsg(
'spyOn could not find an object to spy upon for ' +
@@ -112,7 +112,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
);
}
if (j$.util.isUndefined(propertyName)) {
if (propertyName === undefined) {
throw new Error(getErrorMsg('No property name supplied'));
}
@@ -177,7 +177,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
};
this.spyOnAllFunctions = function(obj, includeNonEnumerable) {
if (j$.util.isUndefined(obj)) {
if (!obj) {
throw new Error(
'spyOnAllFunctions could not find an object to spy upon'
);
@@ -252,6 +252,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
let value;
try {
value = obj[prop];
// eslint-disable-next-line no-unused-vars
} catch (e) {
return false;
}

View File

@@ -104,7 +104,12 @@ getJasmineRequireObj().Suite = function(j$) {
* @property {String} description - The description text passed to the {@link describe} that made this suite.
* @property {String} fullName - The full description including all ancestors of this suite.
* @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe().
* @property {String} filename - The name of the file the suite was defined in.
* @property {String} filename - Deprecated. The name of the file the suite was defined in.
* Note: The value may be incorrect if zone.js is installed or
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions that
* don't maintain the same call stack height as the originals. This property
* may be removed in a future version unless there is enough user interest
* in keeping it. See {@link https://github.com/jasmine/jasmine/issues/2065}.
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite.
* @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite.

View File

@@ -250,9 +250,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
resultCallback: (result, next) => {
this.specResultCallback_(spec, result, next);
},
getSpecName: function(spec) {
return getSpecName(spec, suite);
},
getPath: spec => this.getSpecPath_(spec, suite),
onStart: (spec, next) => this.specStarted_(spec, suite, next),
description: description,
userContext: function() {
@@ -269,6 +267,17 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
return spec;
}
getSpecPath_(spec, suite) {
const path = [spec.description];
while (suite && suite !== this.topSuite) {
path.unshift(suite.description);
suite = suite.parentSuite;
}
return path;
}
unfocusAncestor_() {
const focusedAncestor = findFocusedAncestor(
this.currentDeclarationSuite_
@@ -332,15 +341,5 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
};
}
function getSpecName(spec, suite) {
const fullName = [spec.description],
suiteFullName = suite.getFullName();
if (suiteFullName !== '') {
fullName.unshift(suiteFullName);
}
return fullName.join(' ');
}
return SuiteBuilder;
};

View File

@@ -2,7 +2,7 @@ getJasmineRequireObj().Anything = function(j$) {
function Anything() {}
Anything.prototype.asymmetricMatch = function(other) {
return !j$.util.isUndefined(other) && other !== null;
return other !== undefined && other !== null;
};
Anything.prototype.jasmineToString = function() {

View File

@@ -73,9 +73,7 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
j$.isObject_ = function(value) {
return (
!j$.util.isUndefined(value) && value !== null && j$.isA_('Object', value)
);
return value !== undefined && value !== null && j$.isA_('Object', value);
};
j$.isString_ = function(value) {

View File

@@ -10,7 +10,8 @@ getJasmineRequireObj().buildExpectationResult = function(j$) {
* in a future release. In many Jasmine configurations they are passed
* through JSON serialization and deserialization, which is inherently
* lossy. In such cases, the expected and actual values may be placeholders
* or approximations of the original objects.
* or approximations of the original objects. jasmine-browser-runner 3.0 and
* later omits them entirely.
*
* @typedef ExpectationResult
* @property {String} matcherName - The name of the matcher that was executed for this expectation.

View File

@@ -31,13 +31,14 @@ getJasmineRequireObj().DiffBuilder = function(j$) {
const actualCustom = this.prettyPrinter_.customFormat_(actual);
const expectedCustom = this.prettyPrinter_.customFormat_(expected);
const useCustom = !(
j$.util.isUndefined(actualCustom) &&
j$.util.isUndefined(expectedCustom)
);
const useCustom =
actualCustom !== undefined || expectedCustom !== undefined;
if (useCustom) {
messages.push(wrapPrettyPrinted(actualCustom, expectedCustom, path));
const prettyActual = actualCustom || this.prettyPrinter_(actual);
const prettyExpected =
expectedCustom || this.prettyPrinter_(expected);
messages.push(wrapPrettyPrinted(prettyActual, prettyExpected, path));
return false; // don't recurse further
}

View File

@@ -177,13 +177,13 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
bStack,
diffBuilder
);
if (!j$.util.isUndefined(asymmetricResult)) {
if (asymmetricResult !== undefined) {
return asymmetricResult;
}
for (const tester of this.customTesters_) {
const customTesterResult = tester(a, b);
if (!j$.util.isUndefined(customTesterResult)) {
if (customTesterResult !== undefined) {
if (!customTesterResult) {
diffBuilder.recordMismatch();
}

View File

@@ -31,6 +31,7 @@ getJasmineRequireObj().toBeInstanceOf = function(j$) {
try {
expectedMatcher = new j$.Any(expected);
pass = expectedMatcher.asymmetricMatch(actual);
// eslint-disable-next-line no-unused-vars
} catch (error) {
throw new Error(
usageError('Expected value is not a constructor function')

View File

@@ -1,10 +1,6 @@
getJasmineRequireObj().util = function(j$) {
const util = {};
util.isUndefined = function(obj) {
return obj === void 0;
};
util.clone = function(obj) {
if (Object.prototype.toString.apply(obj) === '[object Array]') {
return obj.slice();
@@ -52,16 +48,9 @@ getJasmineRequireObj().util = function(j$) {
return Object.prototype.hasOwnProperty.call(obj, key);
};
util.errorWithStack = function errorWithStack() {
// Don't throw and catch. That makes it harder for users to debug their
// code with exception breakpoints, and it's unnecessary since all
// supported environments populate new Error().stack
return new Error();
};
function callerFile() {
const trace = new j$.StackTrace(util.errorWithStack());
return trace.frames[2].file;
const trace = new j$.StackTrace(new Error());
return trace.frames[1].file;
}
util.jasmineFile = (function() {

View File

@@ -122,8 +122,10 @@ jasmineRequire.HtmlReporter = function(j$) {
if (noExpectations(result)) {
const noSpecMsg = "Spec '" + result.fullName + "' has no expectations.";
if (result.status === 'failed') {
// eslint-disable-next-line no-console
console.error(noSpecMsg);
} else {
// eslint-disable-next-line no-console
console.warn(noSpecMsg);
}
}

View File

@@ -1 +1 @@
@import "HTMLReporter";
@use "HTMLReporter";