Compare commits
149 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cf9b856b0 | ||
|
|
db6c142afd | ||
|
|
1e691b2470 | ||
|
|
c5555dd8cc | ||
|
|
78c14f81a8 | ||
|
|
56e2832ebe | ||
|
|
9a9d3994da | ||
|
|
ff9feb29d3 | ||
|
|
fee7e6e64e | ||
|
|
18d4d38655 | ||
|
|
53e9bc68d2 | ||
|
|
2be50e1b87 | ||
|
|
27a1257b6d | ||
|
|
75658e0566 | ||
|
|
9a67c4e24d | ||
|
|
7ba53b25f7 | ||
|
|
dbc1f9244e | ||
|
|
489b83c61b | ||
|
|
c590095662 | ||
|
|
0688db88e9 | ||
|
|
124effe04b | ||
|
|
418e9a7728 | ||
|
|
6715f24fd0 | ||
|
|
fa481b2bd1 | ||
|
|
8309416cb2 | ||
|
|
4ccc7bf3ac | ||
|
|
cca6b2aa07 | ||
|
|
78940aa0fb | ||
|
|
3d8396da0a | ||
|
|
0bf9aff195 | ||
|
|
55b2e8846f | ||
|
|
3493519c9f | ||
|
|
62b5698a99 | ||
|
|
98849882a2 | ||
|
|
6665c4e123 | ||
|
|
3698f6fb5d | ||
|
|
60f34ec087 | ||
|
|
91bd3f8201 | ||
|
|
ca4fbcbccb | ||
|
|
e1532be726 | ||
|
|
54465f6f6a | ||
|
|
fa9939ae94 | ||
|
|
7978ad9889 | ||
|
|
af4662ad31 | ||
|
|
15c38c7728 | ||
|
|
b597975c7e | ||
|
|
09ce3a30b6 | ||
|
|
3bcbc2e3af | ||
|
|
fbaba902dc | ||
|
|
bf2e8e759e | ||
|
|
50e566bd67 | ||
|
|
4b7d5e3623 | ||
|
|
6449832e7e | ||
|
|
c6266b24b7 | ||
|
|
f16b81d4ef | ||
|
|
7feec406d9 | ||
|
|
f822ffea21 | ||
|
|
db65c3b131 | ||
|
|
fd37a7eac0 | ||
|
|
12219e80c1 | ||
|
|
a980ae6bf2 | ||
|
|
56ac8f5505 | ||
|
|
3780fe0b35 | ||
|
|
164a393932 | ||
|
|
759a867094 | ||
|
|
f94d0ceda9 | ||
|
|
8d99f27be8 | ||
|
|
63774597f0 | ||
|
|
a3e1abfa12 | ||
|
|
b89a870a59 | ||
|
|
ea3fc88803 | ||
|
|
d5884e33c6 | ||
|
|
138bf9be4b | ||
|
|
98d5284c19 | ||
|
|
2299c85751 | ||
|
|
8e3ec25f6d | ||
|
|
b009cd2922 | ||
|
|
8eee6ebb91 | ||
|
|
c15a1aaa6d | ||
|
|
5b06531cac | ||
|
|
42cca93926 | ||
|
|
395ef85954 | ||
|
|
5e88fde655 | ||
|
|
bb777e93e5 | ||
|
|
9d3fb167a2 | ||
|
|
3176eaf1d8 | ||
|
|
d31a431d1f | ||
|
|
84f78c1435 | ||
|
|
ff476b1982 | ||
|
|
d53d2ff3eb | ||
|
|
adfbd00c75 | ||
|
|
495e5fcd50 | ||
|
|
bc2aa7be25 | ||
|
|
af04599bb5 | ||
|
|
21db6ec0e3 | ||
|
|
2d07b3e6d7 | ||
|
|
6891789ed2 | ||
|
|
7a3d3c9360 | ||
|
|
1b2922e008 | ||
|
|
bd8d23f2a7 | ||
|
|
de26763868 | ||
|
|
f4be08b657 | ||
|
|
50ef882a1a | ||
|
|
c1cd5c6291 | ||
|
|
63ed2b3948 | ||
|
|
0183acc682 | ||
|
|
e15819c0dd | ||
|
|
f694194b2b | ||
|
|
94c00886a6 | ||
|
|
f5915d7963 | ||
|
|
15587f3ce3 | ||
|
|
3ecddc2555 | ||
|
|
6a7c0e6368 | ||
|
|
84daa0f5dc | ||
|
|
c6b3e947e9 | ||
|
|
0e604de0db | ||
|
|
e7ca9c5765 | ||
|
|
cbff6f95cb | ||
|
|
361640f52e | ||
|
|
e5d46e8624 | ||
|
|
8f6b3c49cc | ||
|
|
df6ab05280 | ||
|
|
9ea027dbff | ||
|
|
7cc7da4abc | ||
|
|
8be98e73ca | ||
|
|
52aaf63d22 | ||
|
|
a09fdd3284 | ||
|
|
89f3e9449d | ||
|
|
ba033c520d | ||
|
|
fc935e89c6 | ||
|
|
4bd2feda7d | ||
|
|
bcf699f145 | ||
|
|
d4f29491c9 | ||
|
|
5b1c932f89 | ||
|
|
7a8d6e44e3 | ||
|
|
dac349397e | ||
|
|
5ff7e7f9a1 | ||
|
|
7b2ab822c6 | ||
|
|
033260300a | ||
|
|
cb66b54f8b | ||
|
|
f4a8102a80 | ||
|
|
8f539f17b2 | ||
|
|
e5c543a0a1 | ||
|
|
7d697faf95 | ||
|
|
6f23151a5e | ||
|
|
e53c7ed8d1 | ||
|
|
dcd44a0edf | ||
|
|
f0a5ea9d0f | ||
|
|
491b513aa3 |
@@ -1,9 +1,13 @@
|
||||
# Run tests against supported Node versions, and (except for pull requests)
|
||||
# against supported browsers.
|
||||
# against supported browsers that are available on Saucelabs.
|
||||
|
||||
version: 2.1
|
||||
|
||||
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
|
||||
scripts/run-sauce-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
|
||||
|
||||
@@ -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
|
||||
|
||||
47
.eslintrc
47
.eslintrc
@@ -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"
|
||||
}
|
||||
}
|
||||
33
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
33
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -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
|
||||
|
||||
|
||||
23
.github/workflows/safari.yml
vendored
Normal file
23
.github/workflows/safari.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Test in latest available Safari
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Report Safari version
|
||||
run: osascript -e 'get version of application "Safari"'
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22.x
|
||||
- run: npm install
|
||||
- run: npm run build
|
||||
- run: JASMINE_BROWSER=safari npm run ci
|
||||
104
Gruntfile.js
104
Gruntfile.js
@@ -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(', '));
|
||||
}
|
||||
}
|
||||
16
README.md
16
README.md
@@ -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
|
||||
|
||||
@@ -27,13 +27,13 @@ for information on writing specs, and [the FAQ](https://jasmine.github.io/pages/
|
||||
Jasmine tests itself across popular browsers (Safari, Chrome, Firefox, and
|
||||
Microsoft Edge) as well as Node.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------------|----------------------------|
|
||||
| Node | 18, 20, 22 |
|
||||
| Safari | 15*, 16*, 17* |
|
||||
| Chrome | Evergreen |
|
||||
| Firefox | Evergreen, 102*, 115*, 128 |
|
||||
| Edge | Evergreen |
|
||||
| Environment | Supported versions |
|
||||
|-------------------|----------------------------------|
|
||||
| Node | 18.20.5+*, 20, 22, 24 |
|
||||
| Safari | 16*, 17*, 26* |
|
||||
| Chrome | Evergreen |
|
||||
| Firefox | Evergreen, 102*, 115*, 128*, 140 |
|
||||
| Edge | Evergreen |
|
||||
|
||||
For evergreen browsers, each version of Jasmine is tested against the version of the browser that is available to us
|
||||
at the time of release. Other browsers, as well as older & newer versions of some supported browsers, are likely to work.
|
||||
|
||||
17
RELEASE.md
17
RELEASE.md
@@ -28,9 +28,18 @@ should also rev to that version.
|
||||
|
||||
When ready to release - specs are all green and the stories are done:
|
||||
|
||||
1. Update the release notes in `release_notes` - use the Anchorman gem to generate the markdown file and edit accordingly. Include a list of supported environments.
|
||||
1. Update the version in `package.json`
|
||||
1. Run `npm run build`.
|
||||
1. Update the release notes in `release_notes` - use the Anchorman gem to
|
||||
generate the Markdown file and edit accordingly. Include a list of supported
|
||||
environments. Get that information from these places:
|
||||
* For Node, see .circleci/config.yml or the README.
|
||||
* For Firefox ESR and Safari <=17, see scripts/run-sauce-browsers or the README.
|
||||
* For evergreen browsers, trigger a Circle CI run and check the
|
||||
[Saucelabs dashboard](https://app.saucelabs.com/dashboard/tests?ownerId=90a771d55857492da3bd5251a2d92457&ownerType=user&ownerName=jasmine-js&start=last7days)
|
||||
once it's finished.
|
||||
* For Safari >17, trigger the [Safari action](https://github.com/jasmine/jasmine/actions/workflows/safari.yml)
|
||||
and get the version from the output.
|
||||
2. Update the version in `package.json`
|
||||
3. Run `npm run build`.
|
||||
|
||||
### Commit and push core changes
|
||||
|
||||
@@ -41,7 +50,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
56
eslint.config.mjs
Normal 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",
|
||||
},
|
||||
}]);
|
||||
@@ -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/")
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
encodeWithBaseDir: {
|
||||
files: {
|
||||
"lib/jasmine-core/jasmine.css": ["lib/jasmine-core/jasmine.css"]
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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"
|
||||
]
|
||||
);
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -80,6 +81,13 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @class HtmlReporter
|
||||
* @classdesc Displays results and allows re-running individual specs and suites.
|
||||
* @implements {Reporter}
|
||||
* @param options Options object. See lib/jasmine-core/boot1.js for details.
|
||||
* @since 1.2.0
|
||||
*/
|
||||
function HtmlReporter(options) {
|
||||
function config() {
|
||||
return (options.env && options.env.configuration()) || {};
|
||||
@@ -97,6 +105,11 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
const deprecationWarnings = [];
|
||||
const failures = [];
|
||||
|
||||
/**
|
||||
* Initializes the reporter. Should be called before {@link Env#execute}.
|
||||
* @function
|
||||
* @name HtmlReporter#initialize
|
||||
*/
|
||||
this.initialize = function() {
|
||||
clearPrior();
|
||||
htmlReporterMain = createDom(
|
||||
@@ -155,8 +168,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);
|
||||
}
|
||||
}
|
||||
@@ -554,6 +569,11 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
'a',
|
||||
{ href: specHref(resultNode.result) },
|
||||
specDescription
|
||||
),
|
||||
createDom(
|
||||
'span',
|
||||
{ className: 'jasmine-spec-duration' },
|
||||
'(' + resultNode.result.duration + 'ms)'
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -873,6 +893,8 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
};
|
||||
|
||||
jasmineRequire.HtmlSpecFilter = function() {
|
||||
// Legacy HTML spec filter, preserved for backward compatibility with
|
||||
// boot files that predate HtmlExactSpecFilterV2
|
||||
function HtmlSpecFilter(options) {
|
||||
const filterString =
|
||||
options &&
|
||||
@@ -880,6 +902,13 @@ jasmineRequire.HtmlSpecFilter = function() {
|
||||
options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
||||
const filterPattern = new RegExp(filterString);
|
||||
|
||||
/**
|
||||
* Determines whether the spec with the specified name should be executed.
|
||||
* @name HtmlSpecFilter#matches
|
||||
* @function
|
||||
* @param {string} specName The full name of the spec
|
||||
* @returns {boolean}
|
||||
*/
|
||||
this.matches = function(specName) {
|
||||
return filterPattern.test(specName);
|
||||
};
|
||||
@@ -913,38 +942,57 @@ jasmineRequire.ResultsNode = function() {
|
||||
};
|
||||
|
||||
jasmineRequire.QueryString = function() {
|
||||
function QueryString(options) {
|
||||
this.navigateWithNewParam = function(key, value) {
|
||||
options.getWindowLocation().search = this.fullStringWithNewParam(
|
||||
/**
|
||||
* Reads and manipulates the query string.
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class QueryString {
|
||||
#getWindowLocation;
|
||||
|
||||
/**
|
||||
* @param options Object with a getWindowLocation property, which should be
|
||||
* a function returning the current value of window.location.
|
||||
*/
|
||||
constructor(options) {
|
||||
this.#getWindowLocation = options.getWindowLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the specified query parameter and navigates to the resulting URL.
|
||||
* @param {string} key
|
||||
* @param {string} value
|
||||
*/
|
||||
navigateWithNewParam(key, value) {
|
||||
this.#getWindowLocation().search = this.fullStringWithNewParam(
|
||||
key,
|
||||
value
|
||||
);
|
||||
};
|
||||
|
||||
this.fullStringWithNewParam = function(key, value) {
|
||||
const paramMap = queryStringToParamMap();
|
||||
paramMap[key] = value;
|
||||
return toQueryString(paramMap);
|
||||
};
|
||||
|
||||
this.getParam = function(key) {
|
||||
return queryStringToParamMap()[key];
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
function toQueryString(paramMap) {
|
||||
const qStrPairs = [];
|
||||
for (const prop in paramMap) {
|
||||
qStrPairs.push(
|
||||
encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])
|
||||
);
|
||||
}
|
||||
return '?' + qStrPairs.join('&');
|
||||
}
|
||||
|
||||
function queryStringToParamMap() {
|
||||
const paramStr = options.getWindowLocation().search.substring(1);
|
||||
/**
|
||||
* Returns a new URL based on the current location, with the specified
|
||||
* query parameter set.
|
||||
* @param {string} key
|
||||
* @param {string} value
|
||||
* @return {string}
|
||||
*/
|
||||
fullStringWithNewParam(key, value) {
|
||||
const paramMap = this.#queryStringToParamMap();
|
||||
paramMap[key] = value;
|
||||
return toQueryString(paramMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the specified query parameter.
|
||||
* @param {string} key
|
||||
* @return {string}
|
||||
*/
|
||||
getParam(key) {
|
||||
return this.#queryStringToParamMap()[key];
|
||||
}
|
||||
|
||||
#queryStringToParamMap() {
|
||||
const paramStr = this.#getWindowLocation().search.substring(1);
|
||||
let params = [];
|
||||
const paramMap = {};
|
||||
|
||||
@@ -964,5 +1012,15 @@ jasmineRequire.QueryString = function() {
|
||||
}
|
||||
}
|
||||
|
||||
function toQueryString(paramMap) {
|
||||
const qStrPairs = [];
|
||||
for (const prop in paramMap) {
|
||||
qStrPairs.push(
|
||||
encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])
|
||||
);
|
||||
}
|
||||
return '?' + qStrPairs.join('&');
|
||||
}
|
||||
|
||||
return QueryString;
|
||||
};
|
||||
|
||||
@@ -229,6 +229,9 @@ body {
|
||||
.jasmine_html-reporter .jasmine-specs li.jasmine-excluded a:before {
|
||||
content: "• ";
|
||||
}
|
||||
.jasmine_html-reporter .jasmine-specs li .jasmine-spec-duration {
|
||||
margin-left: 1em;
|
||||
}
|
||||
.jasmine_html-reporter .jasmine-description + .jasmine-suite {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
33
package.json
33
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jasmine-core",
|
||||
"license": "MIT",
|
||||
"version": "5.6.0",
|
||||
"version": "5.13.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,26 +36,23 @@
|
||||
"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",
|
||||
"Safari >= 16",
|
||||
"Firefox >= 102",
|
||||
"last 2 Chrome versions",
|
||||
"last 2 Edge versions"
|
||||
|
||||
54
release_notes/5.10.0.md
Normal file
54
release_notes/5.10.0.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Jasmine Core 5.10.0 Release Notes
|
||||
|
||||
## New Features
|
||||
|
||||
* Optionally detect promise rejections that are handled after an initial
|
||||
unhandled promise rejection event and don't report them as errors.
|
||||
This is off by default because it comes with a performance cost. It can be
|
||||
enabled by setting the `detectLateRejectionHandling` config property to true.
|
||||
* Add `getSpecProperty` to retrieve data that was set with `setSpecProperty`.
|
||||
* Merges [#2072](https://github.com/jasmine/jasmine/pull/2072) from @bonkevin
|
||||
* Show spec duration in the HTML reporter.
|
||||
* Merges [#2073](https://github.com/jasmine/jasmine/pull/2073) from @bonkevin
|
||||
* Protect `GlobalErrors` against monkey patching.
|
||||
|
||||
All currently shipped versions of zone.js contain a monkey patch that fails
|
||||
to pass constructor arguments on to `GlobalErrors`. This patch normally has
|
||||
no effect because zone.js is normally installed after `GlobalErrors` is
|
||||
instantiated, but it would crash Jasmine if it was applied early enough.
|
||||
|
||||
## Deprecations
|
||||
|
||||
* Issue a deprecation warning if the suite/spec order passed as a parameter to
|
||||
`Env#execute` causes a suite to be re-entered.
|
||||
|
||||
## Changes to supported environments
|
||||
|
||||
* Added Firefox 140 (current ESR) to supported environments
|
||||
* Demoted Firefox 128 (previous ESR) to best-effort support
|
||||
|
||||
## Internal improvements
|
||||
|
||||
* Core suite/spec execution flow has been significantly simplified.
|
||||
|
||||
## 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 | 139* |
|
||||
| Firefox | 102**, 115**, 128**, 140, 142* |
|
||||
| Edge | 139* |
|
||||
|
||||
\* 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)_
|
||||
66
release_notes/5.11.0.md
Normal file
66
release_notes/5.11.0.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Jasmine Core 5.11.0 Release Notes
|
||||
|
||||
## New features
|
||||
|
||||
* `detectLateRejectionHandling` works in `beforeAll` and `afterAll` as well as
|
||||
in specs.
|
||||
* Clicking a link in the HTML reporter does exact filtering rather than a
|
||||
substring match.
|
||||
|
||||
If you use jasmine-browser-runner or load boot1.js directly from jasmine-core,
|
||||
you'll get the new filtering behavior automatically. Otherwise, it requires
|
||||
several changes to boot1.js:
|
||||
|
||||
1. Add `queryString` to the options object passed to `HtmlReporter`, and
|
||||
remove `filterSpecs`.
|
||||
2. Instantiate a `jasmine.HtmlExactSpecFilter` instead of a
|
||||
`jasmine.HtmlSpecFilter`.
|
||||
2. Change the body of `config.specFilter` from
|
||||
`return specFilter.matches(spec.getFullName());` to
|
||||
`return specFilter.matches(spec)`
|
||||
|
||||
For a working example, see lib/jasmine-core/boot1.js in this package.
|
||||
Old boot1.js files will still work, but you'll get the old filtering behavior.
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fixed global error handling when the env is executed repeatedly
|
||||
|
||||
## Changes to supported environments
|
||||
|
||||
* Safari 15 is no longer supported.
|
||||
|
||||
## Documentation improvements
|
||||
|
||||
* Added API reference docs for classes used in browser boot files
|
||||
* Documented the order properties of `jasmineStarted` and `jasmineDone` events
|
||||
|
||||
## Internal improvements
|
||||
|
||||
* Unified top suite and regular suite execution
|
||||
* Converted `Spec`, `Suite`, and `QueryString` to ES6 classes
|
||||
* Extracted configuration out of `Env`
|
||||
* Updated tests to characterize suite/spec reporting more completely
|
||||
* Adopted `forbidDuplicateNames: true` in jasmine-core's own tests
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------------|--------------------------------|
|
||||
| Node | 18.20.5**, 20, 22, 24 |
|
||||
| Safari | 16**, 17** |
|
||||
| Chrome | 140* |
|
||||
| Firefox | 102**, 115**, 128**, 140, 143* |
|
||||
| Edge | 140* |
|
||||
|
||||
\* Evergreen browser. Each version of Jasmine is tested against the latest
|
||||
version available at release time.<br>
|
||||
\** Supported on a best-effort basis. Support for these versions may be dropped
|
||||
if it becomes impractical, and bugs affecting only these versions may not be
|
||||
treated as release blockers.
|
||||
|
||||
------
|
||||
|
||||
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
|
||||
27
release_notes/5.12.0.md
Normal file
27
release_notes/5.12.0.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Jasmine Core 5.12.0 Release Notes
|
||||
|
||||
This release reverts the exact spec filtering feature introduced in 5.11.0,
|
||||
which broke spec filtering in Karma.
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------|--------------------------------|
|
||||
| Node | 18.20.5**, 20, 22, 24 |
|
||||
| Safari | 16**, 17** |
|
||||
| Chrome | 141* |
|
||||
| Firefox | 102**, 115**, 128**, 140, 143* |
|
||||
| Edge | 140* |
|
||||
|
||||
\* Evergreen browser. Each version of Jasmine is tested against the latest
|
||||
version available at release time.<br>
|
||||
\** Supported on a best-effort basis. Support for these versions may be dropped
|
||||
if it becomes impractical, and bugs affecting only these versions may not be
|
||||
treated as release blockers.
|
||||
|
||||
|
||||
------
|
||||
|
||||
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
|
||||
29
release_notes/5.12.1.md
Normal file
29
release_notes/5.12.1.md
Normal file
@@ -0,0 +1,29 @@
|
||||
Jasmine Core 5.12.1 Release Notes
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fix custom matchers in top-level specs
|
||||
* Merges [#2088](https://github.com/jasmine/jasmine/pull/2088) from @bonkevin
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------|--------------------------------|
|
||||
| Node | 18.20.5**, 20, 22, 24 |
|
||||
| Safari | 16**, 17** |
|
||||
| Chrome | 141* |
|
||||
| Firefox | 102**, 115**, 128**, 140, 144* |
|
||||
| Edge | 141* |
|
||||
|
||||
\* Evergreen browser. Each version of Jasmine is tested against the latest
|
||||
version available at release time.<br>
|
||||
\** Supported on a best-effort basis. Support for these versions may be dropped
|
||||
if it becomes impractical, and bugs affecting only these versions may not be
|
||||
treated as release blockers.
|
||||
|
||||
|
||||
------
|
||||
|
||||
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
|
||||
44
release_notes/5.13.0.md
Normal file
44
release_notes/5.13.0.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Jasmine Core 5.13.0 Release Notes
|
||||
|
||||
## Changes to supported environments
|
||||
|
||||
Safari 26 is now supported on a best-effort basis.
|
||||
|
||||
Due to the limited availability of Safari 18 and later on free CI services,
|
||||
Safari support in future jasmine-core versions will be limited to:
|
||||
|
||||
* Best-effort support for the latest Safari version available on GitHub Actions,
|
||||
which may change at any time.
|
||||
* Best-effort support for Safari 16 and 17 for as long as it remains practical.
|
||||
|
||||
## New Features
|
||||
|
||||
* New `extraItStackFrames` and `extraDescribeStackFrames` config options to fix
|
||||
the filename properties of reporter events in configurations that wrap
|
||||
`it`/`describe`, such as zone.js. The `filename` properties of reporter events
|
||||
are no longer deprecated.
|
||||
* `jasmine.allOf` asymmetric equality tester
|
||||
* Merges [#2087](https://github.com/jasmine/jasmine/issues/2083) from @jonahd-g
|
||||
* Fixes [#2083](https://github.com/jasmine/jasmine/pull/2087)
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------|--------------------------------|
|
||||
| Node | 18.20.5**, 20, 22, 24 |
|
||||
| Safari** | 16, 17, 26.1 |
|
||||
| Chrome | 142* |
|
||||
| Firefox | 102**, 115**, 128**, 140, 145* |
|
||||
| Edge | 142* |
|
||||
|
||||
\* Evergreen browser. Each version of Jasmine is tested against the latest
|
||||
version available at release time.<br>
|
||||
\** Supported on a best-effort basis. Support for these versions may be dropped
|
||||
if it becomes impractical, and bugs affecting only these versions may not be
|
||||
treated as release blockers.
|
||||
|
||||
------
|
||||
|
||||
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
|
||||
62
release_notes/5.7.0.md
Normal file
62
release_notes/5.7.0.md
Normal 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
28
release_notes/5.7.1.md
Normal 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
44
release_notes/5.8.0.md
Normal 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
56
release_notes/5.9.0.md
Normal 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)_
|
||||
100
release_notes/6.0.0-alpha.0.md
Normal file
100
release_notes/6.0.0-alpha.0.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Jasmine Core 6.0.0-alpha.0 Release Notes
|
||||
|
||||
This is a pre-release, intended to offer a preview of breaking changes and to
|
||||
solicit feedback.
|
||||
|
||||
## A Note About Pre-Release Compatibility
|
||||
|
||||
There may be additional breaking changes in future 6.0 pre-releases or in the
|
||||
final 6.0 release. That's allowed by the semver specification, but users are
|
||||
sometimes unpleasantly surprised by it.
|
||||
|
||||
NPM's implementation of carat version ranges assumes that subsequent
|
||||
pre-releases and final releases are fully compatible with earlier pre-releases.
|
||||
If your package.json contains `"jasmine-core": "^6.0.0-alpha.0`,
|
||||
NPM might install any later 6.x version even though there is no guarantee of
|
||||
compatibility. If that isn't ok, you should specify an exact pre-release version:
|
||||
`"jasmine-core": "6.0.0-alpha.0`.
|
||||
|
||||
## Changes to supported environments
|
||||
|
||||
* Node 18 is no longer supported.
|
||||
|
||||
## Breaking changes
|
||||
|
||||
### General
|
||||
|
||||
* Private APIs have been removed from the `jasmine` namespace.
|
||||
|
||||
The purpose of this change is to reduce the risk of users inadvertently
|
||||
depending on private APIs. Anything that's not covered by
|
||||
[the documentation](https://jasmine.github.io/pages/docs_home.html) remains
|
||||
private regardless of namespacing. Private APIs may be changed or removed in
|
||||
any release. This change is being made in a major release as a courtesy to users of
|
||||
libraries that depend on private APIs.
|
||||
|
||||
* Arguments to `setSpecProperty`/`setSuiteProperty` must be both
|
||||
structured-cloneable and JSON-serializable.
|
||||
* Mock clock timing functions cannot be spied on. Previously this "worked" but
|
||||
prevented the mock clock from uninstalling itself.
|
||||
* The default value of the `forbidDuplicateNames` config option has been
|
||||
changed to true.
|
||||
* The mock clock no longer supports the eval forms of `setTimeout` and
|
||||
`setInterval`.
|
||||
* If an execution order is passed to `Env#execute`, it must not enter any suite
|
||||
more than once.
|
||||
* The argument passed to spec filters is a
|
||||
[spec metadata](https://jasmine.github.io/api/6.0.0-alpha.0/Spec.html)
|
||||
instance, not the internal spec object.
|
||||
|
||||
### Changes that affect reporters
|
||||
|
||||
This release includes changes that are intended to streamline and clarify the
|
||||
reporter interface, prevent sharing of mutable state, and prevent bugs involving
|
||||
non-serializable objects. These changes should be compatible with most existing
|
||||
reporters but could break reporters that manage their internal state in unusual
|
||||
ways. Please [open an issue](https://github.com/jasmine/jasmine/issues/new?template=bug_report.yml)
|
||||
if you find a published reporter package that works with jasmine-core 5.x but
|
||||
not with this release.
|
||||
|
||||
* Irrelevant properties such as `status` and `failedExpectations` are omitted
|
||||
from [the event passed to specStarted](https://jasmine.github.io/api/6.0.0-alpha.0/global.html#SpecStartedEvent).
|
||||
* Reporter events are deep-cloned before being passed to each reporter. This
|
||||
protects reporters against later mutation by jasmine-core or other reporters.
|
||||
* The `expected` and `actual` properties of
|
||||
[passed and failed expectations](https://jasmine.github.io/api/6.0.0-alpha.0/global.html#ExpectationResult)
|
||||
have been removed.
|
||||
* The [order](https://jasmine.github.io/api/6.0.0-alpha.0/global.html#Order)
|
||||
property of the`jasmineStarted` and `jasmineDone` reporter events no longer
|
||||
includes undocumented properties.
|
||||
|
||||
### Changes to Node boot functions
|
||||
|
||||
* [boot](https://jasmine.github.io/api/6.0.0-alpha.0/module-jasmine-core.html#.boot)
|
||||
defaults to creating a new core instance each time it's called. This restores
|
||||
the pre-5.0 default behavior.
|
||||
* [noGlobals](https://jasmine.github.io/api/6.0.0-alpha.0/module-jasmine-core.html#.noGlobals)
|
||||
no longer takes a parameter. It always returns the same object when called
|
||||
repeatedly.
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------------|--------------------------------|
|
||||
| Node | 20, 22, 24 |
|
||||
| Safari | 16**, 17** |
|
||||
| Chrome | 140* |
|
||||
| Firefox | 102**, 115**, 128**, 140, 143* |
|
||||
| Edge | 140* |
|
||||
|
||||
\* Evergreen browser. Each version of Jasmine is tested against the latest
|
||||
version available at release time.<br>
|
||||
\** Supported on a best-effort basis. Support for these versions may be dropped
|
||||
if it becomes impractical, and bugs affecting only these versions may not be
|
||||
treated as release blockers.
|
||||
|
||||
------
|
||||
|
||||
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
|
||||
116
release_notes/6.0.0-alpha.1.md
Normal file
116
release_notes/6.0.0-alpha.1.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Jasmine Core 6.0.0-alpha.1 Release Notes
|
||||
|
||||
This is a pre-release, intended to offer a preview of breaking changes and to
|
||||
solicit feedback.
|
||||
|
||||
## A Note About Pre-Release Compatibility
|
||||
|
||||
There may be additional breaking changes in future 6.0 pre-releases or in the
|
||||
final 6.0 release. That's allowed by the semver specification, but users are
|
||||
sometimes unpleasantly surprised by it.
|
||||
|
||||
NPM's implementation of carat version ranges assumes that subsequent
|
||||
pre-releases and final releases are fully compatible with earlier pre-releases.
|
||||
If your package.json contains `"jasmine-core": "^6.0.0-alpha.1`,
|
||||
NPM might install any later 6.x version even though there is no guarantee of
|
||||
compatibility. If that isn't ok, you should specify an exact pre-release version:
|
||||
`"jasmine-core": "6.0.0-alpha.1`.
|
||||
|
||||
|
||||
## Breaking changes
|
||||
|
||||
### Changes that affect reporters
|
||||
|
||||
* Irrelevant properties such as `status` and `failedExpectations` are omitted
|
||||
from [the event passed to suiteStarted](https://jasmine.github.io/api/6.0.0-alpha.1/global.html#SuiteStartedEvent).
|
||||
|
||||
This change should be compatible with most existing reporters but could break
|
||||
reporters that manage their internal state in unusual ways. Please
|
||||
[open an issue](https://github.com/jasmine/jasmine/issues/new?template=bug_report.yml)
|
||||
if you find a published reporter package that works with jasmine-core 5.x but
|
||||
not with this release.
|
||||
|
||||
### Changes that affect browser boot files
|
||||
|
||||
* The `createElement` and `createTextNode` options of `HtmlReporter` are ignored.
|
||||
`HtmlReporter` now unconditionally uses `document.createElement` and
|
||||
`document.createTextNode`.
|
||||
|
||||
### Changes that affect spec writing
|
||||
|
||||
* HTML reporters cache configuration throughout each run. Configuration changes
|
||||
made while specs are running will not affect reporter behavior.
|
||||
* Global error spies always receive a single argument. Previously, the browser
|
||||
error event was passed as the second argument.
|
||||
|
||||
## New features
|
||||
|
||||
* A new `HtmlReporterV2` with several improvements over the old `HtmlReporter`:
|
||||
* Clicking a spec/suite link does exact filtering rather than a substring
|
||||
match.
|
||||
* The old dots are replaced with a progress bar. This improves usability with
|
||||
large suites and fixes an accessibility problem.
|
||||
* Details of failed specs are displayed as soon as each spec finishes.
|
||||
* Initialization and wire-up in boot files are much simpler.
|
||||
|
||||
If you're using jasmine-browser-runner or copying boot1.js from the standalone
|
||||
distribution, you'll automatically get the new reporter. If you maintain your
|
||||
own boot files, you'll get the old reporter unless you update your boot1.js
|
||||
to match the one that's in this package.
|
||||
|
||||
The new reporter produces `spec` query string parameters that are different
|
||||
from those created by the old reporter. If you use non-Jasmine software that
|
||||
interprets the `spec` parameter, such as karma-jasmine, you may not be able to
|
||||
adopt `HtmlReporterV2` unlesss it's updated.
|
||||
* Use `globalThis` to determine the global object during initialization
|
||||
This makes jasmine-core more tolerant of buggy bundlers or loaders that
|
||||
cause `this` to be undefined in the global context.
|
||||
|
||||
|
||||
## Deprecations
|
||||
|
||||
* Warn if jasmine-core is loaded as an ES module in a browser.
|
||||
This is an untested and unsupported configuration that has been known to cause
|
||||
problems in the past.
|
||||
* Deprecated `HtmlReporter` and `HtmlSpecFilter` in favor of `HtmlReporterV2`.
|
||||
|
||||
|
||||
## Documentation improvements
|
||||
|
||||
* Improved API reference documentation for APIs that are used from browser boot
|
||||
files.
|
||||
|
||||
|
||||
## Internal improvements
|
||||
|
||||
* Removed remaining code that supported suite re-entry.
|
||||
* Encapsulated suite and spec result and status management.
|
||||
* Adopted strict mode throughout the codebase.
|
||||
* Decomposed `HtmlReporter` into components and converted to ES6 classes.
|
||||
* Made global error handling more uniform between browsers and Node.
|
||||
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------------|--------------------------------|
|
||||
| Node | 20, 22, 24 |
|
||||
| Safari | 16**, 17** |
|
||||
| Chrome | 141* |
|
||||
| Firefox | 102**, 115**, 128**, 140, 143* |
|
||||
| Edge | 141* |
|
||||
|
||||
\* Evergreen browser. Each version of Jasmine is tested against the latest
|
||||
version available at release time.<br>
|
||||
\** Supported on a best-effort basis. Support for these versions may be dropped
|
||||
if it becomes impractical, and bugs affecting only these versions may not be
|
||||
treated as release blockers.
|
||||
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
|
||||
90
release_notes/6.0.0-alpha.2.md
Normal file
90
release_notes/6.0.0-alpha.2.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# Jasmine Core 6.0.0-alpha.2 Release Notes
|
||||
|
||||
This is a pre-release, intended to offer a preview of upcoming changes and to
|
||||
solicit feedback.
|
||||
|
||||
## A Note About Pre-Release Compatibility
|
||||
|
||||
There may be additional breaking changes in future 6.0 pre-releases or in the
|
||||
final 6.0 release. That's allowed by the semver specification, but users are
|
||||
sometimes unpleasantly surprised by it.
|
||||
|
||||
NPM's implementation of carat version ranges assumes that subsequent
|
||||
pre-releases and final releases are fully compatible with earlier pre-releases.
|
||||
If your package.json contains `"jasmine-core": "^6.0.0-alpha.2`,
|
||||
NPM might install any later 6.x version even though there is no guarantee of
|
||||
compatibility. If that isn't ok, you should specify an exact pre-release version:
|
||||
`"jasmine-core": "6.0.0-alpha.2`.
|
||||
|
||||
|
||||
## Changes to supported environments
|
||||
|
||||
Safari 26 is now supported on a best-effort basis.†
|
||||
|
||||
Due to the limited availability of Safari 18 and later on free CI services,
|
||||
Safari support in future jasmine-core versions will be limited to:
|
||||
|
||||
* Best-effort support for the latest Safari version available on GitHub Actions,
|
||||
which may change at any time.
|
||||
* Best-effort support for Safari 16 and 17 for as long as it remains practical.
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* Fix custom matchers in top-level specs††
|
||||
* Merges [#2088](https://github.com/jasmine/jasmine/pull/2088) from @bonkevin
|
||||
|
||||
## New features
|
||||
|
||||
* Larger body font size in HTML reporters
|
||||
* New Performance tab in HtmlReporterV2 shows metrics and a list of the slowest
|
||||
specs.
|
||||
* Experimental `safariYieldStrategy: "time"` config option, which may make
|
||||
Jasmine run significantly faster in Safari and similar browsers. So far, this
|
||||
option has not been tested on a wide variety of workloads. Feedback is
|
||||
appreciated.
|
||||
* New `extraItStackFrames` and `extraDescribeStackFrames` config options to fix
|
||||
the filename properties of reporter events in configurations that wrap
|
||||
`it`/`describe`, such as zone.js.†
|
||||
* `jasmine.allOf asymmetric` equality tester†
|
||||
* Merges [#2087](https://github.com/jasmine/jasmine/pull/2087) from @jonahd-g
|
||||
* Fixes [#2083](https://github.com/jasmine/jasmine/issues/2083)
|
||||
* Re-add support for partial spec name filtering via `spec` query parameter
|
||||
* Fixes [#2085](https://github.com/jasmine/jasmine/issues/2085).
|
||||
* Require spec/suite property keys to be strings, not just anything that's
|
||||
cloneable and serializable. This matches the existing API reference
|
||||
documentation.
|
||||
|
||||
## Documentation improvements
|
||||
|
||||
* Fix HtmlReporterV2 ctor example
|
||||
|
||||
## Internal Improvements
|
||||
|
||||
* Remove code to support browsers that don't have MessageChannel. Jasmine hasn't
|
||||
run in any such browsers since 2.x.
|
||||
|
||||
† Also likely to be included in a future 5.x release.<br>
|
||||
†† Also released in 5.12.1.
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------------|--------------------------------|
|
||||
| Node | 20, 22, 24 |
|
||||
| Safari | 16**, 17**, 26** |
|
||||
| Chrome | 142* |
|
||||
| Firefox | 102**, 115**, 128**, 140, 145* |
|
||||
| Edge | 142* |
|
||||
|
||||
\* Evergreen browser. Each version of Jasmine is tested against the latest
|
||||
version available at release time.<br>
|
||||
\** Supported on a best-effort basis. Support for these versions may be dropped
|
||||
if it becomes impractical, and bugs affecting only these versions may not be
|
||||
treated as release blockers.
|
||||
|
||||
|
||||
------
|
||||
|
||||
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
|
||||
74
release_notes/6.0.0-beta.0.md
Normal file
74
release_notes/6.0.0-beta.0.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Jasmine Core 6.0.0-beta.0 Release Notes
|
||||
|
||||
This is a pre-release, intended to offer a preview of upcoming changes and to
|
||||
solicit feedback.
|
||||
|
||||
## A Note About Pre-Release Compatibility
|
||||
|
||||
There may be additional breaking changes in future 6.0 pre-releases or in the
|
||||
final 6.0 release. That's allowed by the semver specification, but users are
|
||||
sometimes unpleasantly surprised by it.
|
||||
|
||||
NPM's implementation of carat version ranges assumes that subsequent
|
||||
pre-releases and final releases are fully compatible with earlier pre-releases.
|
||||
If your package.json contains `"jasmine-core": "^6.0.0-beta.0`,
|
||||
NPM might install any later 6.x version even though there is no guarantee of
|
||||
compatibility. If that isn't ok, you should specify an exact pre-release version:
|
||||
`"jasmine-core": "6.0.0-beta.0`.
|
||||
|
||||
|
||||
## Breaking changes
|
||||
|
||||
* boot1.js no longer adds jsApiReporter to the env.
|
||||
* HtmlReporterV2 initialization and boot1.js have been simplified. If you
|
||||
maintain your own boot file, update it to match the boot1.js included in this
|
||||
package.
|
||||
|
||||
|
||||
## New features
|
||||
|
||||
* Statically exposed pretty printer as jasmine.pp().
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fixed HtmlReporterV2 progress bar when running a subset of specs.
|
||||
|
||||
|
||||
## Deprecations
|
||||
|
||||
* jsApiReporter is deprecated.
|
||||
* Detect monkey patching and emit a deprecation warning.
|
||||
|
||||
## Documentation improvements
|
||||
|
||||
* Documented the set of possible spec statuses.
|
||||
|
||||
|
||||
## Internal improvements
|
||||
|
||||
* Replaced isArray helper with native Array.isArray
|
||||
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------------|--------------------------------|
|
||||
| Node | 20, 22, 24 |
|
||||
| Safari | 16**, 17**, 26.1** |
|
||||
| Chrome | 142* |
|
||||
| Firefox | 102**, 115**, 128**, 140, 145* |
|
||||
| Edge | 142* |
|
||||
|
||||
\* Evergreen browser. Each version of Jasmine is tested against the latest
|
||||
version available at release time.<br>
|
||||
\** Supported on a best-effort basis. Support for these versions may be dropped
|
||||
if it becomes impractical, and bugs affecting only these versions may not be
|
||||
treated as release blockers.
|
||||
|
||||
|
||||
|
||||
------
|
||||
|
||||
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
|
||||
3
scripts/buildDistribution.js
Normal file
3
scripts/buildDistribution.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const buildDistribution = require('./lib/buildDistribution');
|
||||
|
||||
buildDistribution();
|
||||
86
scripts/buildStandaloneDist.js
Normal file
86
scripts/buildStandaloneDist.js
Normal 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;
|
||||
}
|
||||
129
scripts/lib/buildDistribution.js
Normal file
129
scripts/lib/buildDistribution.js
Normal 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;
|
||||
@@ -1,5 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Run tests in supported browsers that are available on Saucelabs.
|
||||
# Note: The latest Safari version is tested via GitHub Actions because Saucelabs
|
||||
# only makes it available to paid enterprise accounts. See
|
||||
# .github/workflows/safari.yml.
|
||||
|
||||
run_browser() {
|
||||
browser=$1
|
||||
version=$2
|
||||
@@ -25,23 +30,23 @@ 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
|
||||
run_browser firefox 115
|
||||
if [ "$1" = "--not-actually-all" ]; then
|
||||
echo "SKIPPED: firefox 140" >> "$passfile"
|
||||
echo "SKIPPED: firefox 128" >> "$passfile"
|
||||
echo "SKIPPED: firefox 115" >> "$passfile"
|
||||
else
|
||||
run_browser firefox 140
|
||||
run_browser firefox 128
|
||||
run_browser firefox 115
|
||||
fi
|
||||
run_browser firefox 102
|
||||
|
||||
run_browser safari 17
|
||||
run_browser safari 16
|
||||
run_browser safari 15
|
||||
|
||||
run_browser MicrosoftEdge latest
|
||||
|
||||
echo
|
||||
28
scripts/runSpecsInNode.js
Normal file
28
scripts/runSpecsInNode.js
Normal 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(', '));
|
||||
}
|
||||
}
|
||||
28
scripts/runSpecsInParallel.js
Normal file
28
scripts/runSpecsInParallel.js
Normal 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));
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -129,33 +129,33 @@ describe('AsyncExpectation', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('prepends the context to a custom failure message from a function', function() {
|
||||
pending('should actually work, but no custom matchers for async yet');
|
||||
|
||||
it('prepends the context to a custom failure message from a matcher', async function() {
|
||||
const matchersUtil = {
|
||||
buildFailureMessage: function() {
|
||||
return 'failure message';
|
||||
}
|
||||
buildFailureMessage() {
|
||||
return 'failure message';
|
||||
},
|
||||
addExpectationResult = jasmine.createSpy('addExpectationResult'),
|
||||
actual = Promise.reject(new Error('nope')),
|
||||
expectation = jasmineUnderTest.Expectation.asyncFactory({
|
||||
actual: actual,
|
||||
addExpectationResult: addExpectationResult,
|
||||
matchersUtil: matchersUtil
|
||||
});
|
||||
pp(v) {
|
||||
return v.toString();
|
||||
}
|
||||
};
|
||||
const addExpectationResult = jasmine.createSpy('addExpectationResult');
|
||||
const actual = Promise.reject(new Error('nope'));
|
||||
const expectation = jasmineUnderTest.Expectation.asyncFactory({
|
||||
actual: actual,
|
||||
addExpectationResult: addExpectationResult,
|
||||
matchersUtil: matchersUtil
|
||||
});
|
||||
|
||||
return expectation
|
||||
.withContext('Some context')
|
||||
.toBeResolved()
|
||||
.then(function() {
|
||||
expect(addExpectationResult).toHaveBeenCalledWith(
|
||||
false,
|
||||
jasmine.objectContaining({
|
||||
message: 'Some context: msg'
|
||||
})
|
||||
);
|
||||
});
|
||||
await expectation.withContext('Some context').toBeResolved();
|
||||
|
||||
expect(addExpectationResult).toHaveBeenCalledWith(
|
||||
false,
|
||||
jasmine.objectContaining({
|
||||
message:
|
||||
'Some context: Expected a promise to be resolved but it ' +
|
||||
'was rejected with Error: nope.'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('works with #not', function() {
|
||||
@@ -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)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -242,7 +242,7 @@ describe('Clock', function() {
|
||||
expect(fakeGlobal.clearInterval).toBe(replacedClearInterval);
|
||||
});
|
||||
|
||||
it('replaces the global timer functions on uninstall', function() {
|
||||
it('restores the global timer functions on uninstall', function() {
|
||||
const fakeSetTimeout = jasmine.createSpy('global setTimeout'),
|
||||
fakeClearTimeout = jasmine.createSpy('global clearTimeout'),
|
||||
fakeSetInterval = jasmine.createSpy('global setInterval'),
|
||||
@@ -408,211 +408,219 @@ describe('Clock', function() {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('schedules the delayed function (via setTimeout) with the fake timer', function() {
|
||||
const fakeSetTimeout = jasmine.createSpy('setTimeout'),
|
||||
scheduleFunction = jasmine.createSpy('scheduleFunction'),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setTimeout: fakeSetTimeout },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
describe('setTimeout', function() {
|
||||
it('schedules the delayed function with the fake timer', function() {
|
||||
const fakeSetTimeout = jasmine.createSpy('setTimeout'),
|
||||
scheduleFunction = jasmine.createSpy('scheduleFunction'),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setTimeout: fakeSetTimeout },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
mockDate
|
||||
),
|
||||
timeout = new clock.FakeTimeout();
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
),
|
||||
timeout = new clock.FakeTimeout();
|
||||
|
||||
clock.install();
|
||||
clock.setTimeout(delayedFn, 0, 'a', 'b');
|
||||
clock.install();
|
||||
clock.setTimeout(delayedFn, 0, 'a', 'b');
|
||||
|
||||
expect(fakeSetTimeout).not.toHaveBeenCalled();
|
||||
expect(fakeSetTimeout).not.toHaveBeenCalled();
|
||||
|
||||
if (!NODE_JS) {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b']
|
||||
);
|
||||
} else {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b'],
|
||||
false,
|
||||
timeout
|
||||
);
|
||||
}
|
||||
if (!NODE_JS) {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b']
|
||||
);
|
||||
} else {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b'],
|
||||
false,
|
||||
timeout
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an id for the delayed function', function() {
|
||||
const fakeSetTimeout = jasmine.createSpy('setTimeout'),
|
||||
scheduleId = 123,
|
||||
scheduleFunction = jasmine
|
||||
.createSpy('scheduleFunction')
|
||||
.and.returnValue(scheduleId),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setTimeout: fakeSetTimeout },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
|
||||
clock.install();
|
||||
const timeout = clock.setTimeout(delayedFn, 0);
|
||||
|
||||
if (!NODE_JS) {
|
||||
expect(timeout).toEqual(123);
|
||||
} else {
|
||||
expect(timeout.constructor.name).toEqual('FakeTimeout');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an id for the delayed function', function() {
|
||||
const fakeSetTimeout = jasmine.createSpy('setTimeout'),
|
||||
scheduleId = 123,
|
||||
scheduleFunction = jasmine
|
||||
.createSpy('scheduleFunction')
|
||||
.and.returnValue(scheduleId),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setTimeout: fakeSetTimeout },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
describe('clearTimeout', function() {
|
||||
it('clears the scheduled function with the scheduler', function() {
|
||||
const fakeClearTimeout = jasmine.createSpy('clearTimeout'),
|
||||
delayedFunctionScheduler = jasmine.createSpyObj(
|
||||
'delayedFunctionScheduler',
|
||||
['removeFunctionWithId']
|
||||
),
|
||||
fakeGlobal = { setTimeout: fakeClearTimeout },
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
|
||||
clock.install();
|
||||
const timeout = clock.setTimeout(delayedFn, 0);
|
||||
clock.install();
|
||||
clock.clearTimeout(123);
|
||||
|
||||
if (!NODE_JS) {
|
||||
expect(timeout).toEqual(123);
|
||||
} else {
|
||||
expect(timeout.constructor.name).toEqual('FakeTimeout');
|
||||
}
|
||||
expect(fakeClearTimeout).not.toHaveBeenCalled();
|
||||
expect(
|
||||
delayedFunctionScheduler.removeFunctionWithId
|
||||
).toHaveBeenCalledWith(123);
|
||||
});
|
||||
});
|
||||
|
||||
it('clears the scheduled function with the scheduler', function() {
|
||||
const fakeClearTimeout = jasmine.createSpy('clearTimeout'),
|
||||
delayedFunctionScheduler = jasmine.createSpyObj(
|
||||
'delayedFunctionScheduler',
|
||||
['removeFunctionWithId']
|
||||
),
|
||||
fakeGlobal = { setTimeout: fakeClearTimeout },
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
describe('setInterval', function() {
|
||||
it('schedules the delayed function with the fake timer', function() {
|
||||
const fakeSetInterval = jasmine.createSpy('setInterval'),
|
||||
scheduleFunction = jasmine.createSpy('scheduleFunction'),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setInterval: fakeSetInterval },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
),
|
||||
timeout = new clock.FakeTimeout();
|
||||
|
||||
clock.install();
|
||||
clock.clearTimeout(123);
|
||||
clock.install();
|
||||
clock.setInterval(delayedFn, 0, 'a', 'b');
|
||||
|
||||
expect(fakeClearTimeout).not.toHaveBeenCalled();
|
||||
expect(delayedFunctionScheduler.removeFunctionWithId).toHaveBeenCalledWith(
|
||||
123
|
||||
);
|
||||
expect(fakeSetInterval).not.toHaveBeenCalled();
|
||||
|
||||
if (!NODE_JS) {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b'],
|
||||
true
|
||||
);
|
||||
} else {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b'],
|
||||
true,
|
||||
timeout
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an id for the delayed function', function() {
|
||||
const fakeSetInterval = jasmine.createSpy('setInterval'),
|
||||
scheduleId = 123,
|
||||
scheduleFunction = jasmine
|
||||
.createSpy('scheduleFunction')
|
||||
.and.returnValue(scheduleId),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setInterval: fakeSetInterval },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
|
||||
clock.install();
|
||||
const interval = clock.setInterval(delayedFn, 0);
|
||||
|
||||
if (!NODE_JS) {
|
||||
expect(interval).toEqual(123);
|
||||
} else {
|
||||
expect(interval.constructor.name).toEqual('FakeTimeout');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('schedules the delayed function with the fake timer', function() {
|
||||
const fakeSetInterval = jasmine.createSpy('setInterval'),
|
||||
scheduleFunction = jasmine.createSpy('scheduleFunction'),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setInterval: fakeSetInterval },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
describe('clearInterval', function() {
|
||||
it('clears the scheduled function with the scheduler', function() {
|
||||
const clearInterval = jasmine.createSpy('clearInterval'),
|
||||
delayedFunctionScheduler = jasmine.createSpyObj(
|
||||
'delayedFunctionScheduler',
|
||||
['removeFunctionWithId']
|
||||
),
|
||||
fakeGlobal = { setInterval: clearInterval },
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
mockDate
|
||||
),
|
||||
timeout = new clock.FakeTimeout();
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
|
||||
clock.install();
|
||||
clock.setInterval(delayedFn, 0, 'a', 'b');
|
||||
clock.install();
|
||||
clock.clearInterval(123);
|
||||
|
||||
expect(fakeSetInterval).not.toHaveBeenCalled();
|
||||
|
||||
if (!NODE_JS) {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b'],
|
||||
true
|
||||
);
|
||||
} else {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b'],
|
||||
true,
|
||||
timeout
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an id for the delayed function', function() {
|
||||
const fakeSetInterval = jasmine.createSpy('setInterval'),
|
||||
scheduleId = 123,
|
||||
scheduleFunction = jasmine
|
||||
.createSpy('scheduleFunction')
|
||||
.and.returnValue(scheduleId),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setInterval: fakeSetInterval },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
|
||||
clock.install();
|
||||
const interval = clock.setInterval(delayedFn, 0);
|
||||
|
||||
if (!NODE_JS) {
|
||||
expect(interval).toEqual(123);
|
||||
} else {
|
||||
expect(interval.constructor.name).toEqual('FakeTimeout');
|
||||
}
|
||||
});
|
||||
|
||||
it('clears the scheduled function with the scheduler', function() {
|
||||
const clearInterval = jasmine.createSpy('clearInterval'),
|
||||
delayedFunctionScheduler = jasmine.createSpyObj(
|
||||
'delayedFunctionScheduler',
|
||||
['removeFunctionWithId']
|
||||
),
|
||||
fakeGlobal = { setInterval: clearInterval },
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
|
||||
clock.install();
|
||||
clock.clearInterval(123);
|
||||
|
||||
expect(clearInterval).not.toHaveBeenCalled();
|
||||
expect(delayedFunctionScheduler.removeFunctionWithId).toHaveBeenCalledWith(
|
||||
123
|
||||
);
|
||||
expect(clearInterval).not.toHaveBeenCalled();
|
||||
expect(
|
||||
delayedFunctionScheduler.removeFunctionWithId
|
||||
).toHaveBeenCalledWith(123);
|
||||
});
|
||||
});
|
||||
|
||||
it('gives you a friendly reminder if the Clock is not installed and you tick', function() {
|
||||
@@ -687,6 +695,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(),
|
||||
|
||||
158
spec/core/ConfigurationSpec.js
Normal file
158
spec/core/ConfigurationSpec.js
Normal file
@@ -0,0 +1,158 @@
|
||||
describe('Configuration', function() {
|
||||
const standardBooleanKeys = [
|
||||
'random',
|
||||
'stopOnSpecFailure',
|
||||
'stopSpecOnExpectationFailure',
|
||||
'failSpecWithNoExpectations',
|
||||
'hideDisabled',
|
||||
'autoCleanClosures',
|
||||
'forbidDuplicateNames',
|
||||
'detectLateRejectionHandling'
|
||||
];
|
||||
const allKeys = [
|
||||
...standardBooleanKeys,
|
||||
'seed',
|
||||
'specFilter',
|
||||
'verboseDeprecations',
|
||||
'extraItStackFrames',
|
||||
'extraDescribeStackFrames'
|
||||
];
|
||||
Object.freeze(standardBooleanKeys);
|
||||
Object.freeze(allKeys);
|
||||
|
||||
it('provides defaults', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
expect(subject.random).toEqual(true);
|
||||
expect(subject.seed).toBeNull();
|
||||
expect(subject.stopOnSpecFailure).toEqual(false);
|
||||
expect(subject.stopSpecOnExpectationFailure).toEqual(false);
|
||||
expect(subject.failSpecWithNoExpectations).toEqual(false);
|
||||
expect(subject.specFilter).toEqual(jasmine.any(Function));
|
||||
expect(subject.specFilter()).toEqual(true);
|
||||
expect(subject.hideDisabled).toEqual(false);
|
||||
expect(subject.autoCleanClosures).toEqual(true);
|
||||
expect(subject.forbidDuplicateNames).toEqual(false);
|
||||
expect(subject.verboseDeprecations).toEqual(false);
|
||||
expect(subject.detectLateRejectionHandling).toEqual(false);
|
||||
expect(subject.extraItStackFrames).toEqual(0);
|
||||
expect(subject.extraDescribeStackFrames).toEqual(0);
|
||||
});
|
||||
|
||||
describe('copy()', function() {
|
||||
it('returns a copy of the configuration as a plain old JS object', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
|
||||
const copy = subject.copy();
|
||||
|
||||
expect(copy.constructor.name).toEqual('Object');
|
||||
|
||||
expect(new Set(Object.keys(copy))).toEqual(new Set(allKeys));
|
||||
for (const k of allKeys) {
|
||||
expect(copy[k]).toEqual(subject[k]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('update()', function() {
|
||||
it('does not update properties that are absent from the parameter', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
const originalValues = subject.copy();
|
||||
|
||||
subject.update({});
|
||||
expect(subject.copy()).toEqual(originalValues);
|
||||
});
|
||||
|
||||
function booleanPropertyBehavior(key) {
|
||||
it('does not update the property if the specified value is undefined', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
const orig = subject[key];
|
||||
|
||||
subject.update({ [key]: undefined });
|
||||
|
||||
expect(subject[key]).toEqual(orig);
|
||||
});
|
||||
|
||||
it('updates the property if the specified value is not undefined', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
const orig = subject[key];
|
||||
|
||||
subject.update({ [key]: !orig });
|
||||
expect(subject[key]).toEqual(!orig);
|
||||
|
||||
subject.update({ [key]: orig });
|
||||
expect(subject[key]).toEqual(orig);
|
||||
});
|
||||
}
|
||||
|
||||
for (const k of standardBooleanKeys) {
|
||||
describe(k, function() {
|
||||
booleanPropertyBehavior(k);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: in the next major release, treat verboseDeprecations like other booleans
|
||||
it('sets verboseDeprecations when present', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
const orig = subject.verboseDeprecations;
|
||||
|
||||
subject.update({ verboseDeprecations: !orig });
|
||||
expect(subject.verboseDeprecations).toEqual(!orig);
|
||||
|
||||
subject.update({ verboseDeprecations: orig });
|
||||
expect(subject.verboseDeprecations).toEqual(orig);
|
||||
|
||||
// For backwards compatibility, explicitly setting to undefined should
|
||||
// work. Undefined isn't officially valid but gets treated like false.
|
||||
subject.update({ verboseDeprecations: undefined });
|
||||
expect(subject.verboseDeprecations).toBeUndefined();
|
||||
});
|
||||
|
||||
it('sets specFilter when truthy', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
const orig = subject.specFilter;
|
||||
|
||||
subject.update({ specFilter: undefined });
|
||||
expect(subject.specFilter).toBe(orig);
|
||||
|
||||
subject.update({ specFilter: false });
|
||||
expect(subject.specFilter).toBe(orig);
|
||||
|
||||
function newSpecFilter() {}
|
||||
subject.update({ specFilter: newSpecFilter });
|
||||
expect(subject.specFilter).toBe(newSpecFilter);
|
||||
});
|
||||
|
||||
it('sets seed when not undefined', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
|
||||
subject.update({ seed: undefined });
|
||||
expect(subject.seed).toBeNull();
|
||||
|
||||
subject.update({ seed: 1234 });
|
||||
expect(subject.seed).toEqual(1234);
|
||||
|
||||
subject.update({ seed: null });
|
||||
expect(subject.seed).toBeNull();
|
||||
});
|
||||
|
||||
it('sets extraItStackFrames when not undefined', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
|
||||
subject.update({ extraItStackFrames: undefined });
|
||||
expect(subject.extraItStackFrames).toEqual(0);
|
||||
|
||||
subject.update({ extraItStackFrames: 100000 });
|
||||
expect(subject.extraItStackFrames).toEqual(100000);
|
||||
});
|
||||
|
||||
it('sets extraDescribeStackFrames when not undefined', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
|
||||
subject.update({ extraDescribeStackFrames: undefined });
|
||||
expect(subject.extraDescribeStackFrames).toEqual(0);
|
||||
|
||||
subject.update({ extraDescribeStackFrames: 100000 });
|
||||
expect(subject.extraDescribeStackFrames).toEqual(100000);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// TODO: Fix these unit tests!
|
||||
describe('Env', function() {
|
||||
let env;
|
||||
beforeEach(function() {
|
||||
@@ -95,7 +94,7 @@ describe('Env', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts its own current configureation', function() {
|
||||
it('accepts its own current configuration', function() {
|
||||
env.configure(env.configuration());
|
||||
});
|
||||
|
||||
@@ -198,6 +197,29 @@ describe('Env', function() {
|
||||
expect(innerSuite.parentSuite).toBe(suite);
|
||||
expect(spec.getFullName()).toEqual('outer suite inner suite a spec');
|
||||
});
|
||||
|
||||
it('sets the caller filename correctly when extraDescribeStackFrames is not set', function() {
|
||||
// IIFE is used to match the stack depth when global describe() is called
|
||||
const suite = (function() {
|
||||
return env[methodName]('a suite', function() {
|
||||
env.it('a spec');
|
||||
});
|
||||
})();
|
||||
expect(suite.filename).toMatch(/EnvSpec\.js$/);
|
||||
});
|
||||
|
||||
it('sets the caller filename correctly when extraDescribeStackFrames is set', function() {
|
||||
env.configure({ extraDescribeStackFrames: 2 });
|
||||
// IIFE is used to match the stack depth when global describe() is called
|
||||
const suite = (function() {
|
||||
return specHelpers.callerFilenameShim(function() {
|
||||
return env[methodName]('a suite', function() {
|
||||
env.it('a spec');
|
||||
});
|
||||
});
|
||||
})();
|
||||
expect(suite.filename).toMatch(/EnvSpec\.js$/);
|
||||
});
|
||||
}
|
||||
|
||||
describe('#describe', function() {
|
||||
@@ -205,7 +227,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');
|
||||
});
|
||||
@@ -301,6 +322,25 @@ describe('Env', function() {
|
||||
.not.toEqual('');
|
||||
expect(spec.pend).toBeFalsy();
|
||||
});
|
||||
|
||||
it('sets the caller filename correctly when extraItStackFrames is not set', function() {
|
||||
// IIFE is used to match the stack depth when global it() is called
|
||||
const spec = (function() {
|
||||
return env[methodName]('a spec', function() {});
|
||||
})();
|
||||
expect(spec.filename).toMatch(/EnvSpec\.js$/);
|
||||
});
|
||||
|
||||
it('sets the caller filename correctly when extraItStackFrames is set', function() {
|
||||
env.configure({ extraItStackFrames: 2 });
|
||||
// IIFE is used to match the stack depth when global it() is called
|
||||
const spec = (function() {
|
||||
return specHelpers.callerFilenameShim(function() {
|
||||
return env[methodName]('a spec', function() {});
|
||||
});
|
||||
})();
|
||||
expect(spec.filename).toMatch(/EnvSpec\.js$/);
|
||||
});
|
||||
}
|
||||
|
||||
describe('#it', function() {
|
||||
@@ -626,9 +666,12 @@ describe('Env', function() {
|
||||
'popListener',
|
||||
'removeOverrideListener'
|
||||
]);
|
||||
spyOn(jasmineUnderTest, 'GlobalErrors').and.returnValue(globalErrors);
|
||||
env.cleanup_();
|
||||
env = new jasmineUnderTest.Env();
|
||||
env = new jasmineUnderTest.Env({
|
||||
GlobalErrors: function() {
|
||||
return globalErrors;
|
||||
}
|
||||
});
|
||||
expect(globalErrors.install).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -642,9 +685,13 @@ describe('Env', function() {
|
||||
'popListener',
|
||||
'removeOverrideListener'
|
||||
]);
|
||||
spyOn(jasmineUnderTest, 'GlobalErrors').and.returnValue(globalErrors);
|
||||
env.cleanup_();
|
||||
env = new jasmineUnderTest.Env({ suppressLoadErrors: true });
|
||||
env = new jasmineUnderTest.Env({
|
||||
suppressLoadErrors: true,
|
||||
GlobalErrors: function() {
|
||||
return globalErrors;
|
||||
}
|
||||
});
|
||||
expect(globalErrors.install).not.toHaveBeenCalled();
|
||||
env.execute();
|
||||
expect(globalErrors.install).toHaveBeenCalled();
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
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 +20,20 @@ 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 +42,20 @@ 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 +65,13 @@ 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 +80,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 +97,32 @@ 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 +131,281 @@ 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('When detectLateRejectionHandling is true', function() {
|
||||
let globals, errors;
|
||||
|
||||
beforeEach(function() {
|
||||
globals = nodeGlobals();
|
||||
errors = new jasmineUnderTest.GlobalErrors(globals.global, () => ({
|
||||
detectLateRejectionHandling: true
|
||||
}));
|
||||
});
|
||||
|
||||
it('subscribes and unsubscribes from the rejectionHandled event', function() {
|
||||
function originalHandler() {}
|
||||
globals.global.process.on('rejectionHandled', originalHandler);
|
||||
errors.install();
|
||||
|
||||
expect(globals.listeners.rejectionHandled).toEqual([
|
||||
jasmine.any(Function)
|
||||
]);
|
||||
expect(globals.listeners.rejectionHandled).not.toEqual([
|
||||
originalHandler
|
||||
]);
|
||||
|
||||
errors.uninstall();
|
||||
expect(globals.listeners.rejectionHandled).toEqual([originalHandler]);
|
||||
});
|
||||
|
||||
describe("When the unhandledRejection event doesn't have a promise", function() {
|
||||
it('immediately reports the rejection', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
dispatchEvent(
|
||||
globals.listeners,
|
||||
'unhandledRejection',
|
||||
new Error('nope'),
|
||||
undefined
|
||||
);
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(new Error('nope'), undefined);
|
||||
expect(handler.calls.argsFor(0)[0].jasmineMessage).toBe(
|
||||
'Unhandled promise rejection: Error: nope'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the unhandledRejection event has a promise property', function() {
|
||||
it('does not immediately report the rejection', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const promise = Promise.reject('nope');
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(
|
||||
globals.listeners,
|
||||
'unhandledRejection',
|
||||
'nope',
|
||||
promise
|
||||
);
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('When reportUnhandledRejections is called', function() {
|
||||
it('reports rejections that have not been handled', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const reason = new Error('nope');
|
||||
const promise = Promise.reject(reason);
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(
|
||||
globals.listeners,
|
||||
'unhandledRejection',
|
||||
reason,
|
||||
promise
|
||||
);
|
||||
errors.reportUnhandledRejections();
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(new Error('nope'), undefined);
|
||||
expect(handler.calls.argsFor(0)[0].jasmineMessage).toBe(
|
||||
'Unhandled promise rejection: Error: nope'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not report rejections that have been handled', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const reason = new Error('nope');
|
||||
const promise = Promise.reject(reason);
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(
|
||||
globals.listeners,
|
||||
'unhandledRejection',
|
||||
reason,
|
||||
promise
|
||||
);
|
||||
dispatchEvent(globals.listeners, 'rejectionHandled', promise);
|
||||
errors.reportUnhandledRejections();
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not report the same rejection on subsequent calls', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const promise = Promise.reject('nope');
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(
|
||||
globals.listeners,
|
||||
'unhandledRejection',
|
||||
'nope',
|
||||
promise
|
||||
);
|
||||
errors.reportUnhandledRejections();
|
||||
expect(handler).toHaveBeenCalled();
|
||||
handler.calls.reset();
|
||||
|
||||
errors.reportUnhandledRejections();
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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 +414,19 @@ 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({
|
||||
@@ -326,77 +437,178 @@ describe('GlobalErrors', function() {
|
||||
event
|
||||
);
|
||||
});
|
||||
|
||||
describe('When detectLateRejectionHandling is true', function() {
|
||||
let globals, errors;
|
||||
|
||||
beforeEach(function() {
|
||||
globals = browserGlobals();
|
||||
errors = new jasmineUnderTest.GlobalErrors(globals.global, () => ({
|
||||
detectLateRejectionHandling: true
|
||||
}));
|
||||
});
|
||||
|
||||
it('subscribes and unsubscribes from the rejectionhandled event', function() {
|
||||
errors.install();
|
||||
expect(globals.listeners.rejectionhandled).toEqual([
|
||||
jasmine.any(Function)
|
||||
]);
|
||||
|
||||
errors.uninstall();
|
||||
expect(globals.listeners.rejectionhandled).toEqual([]);
|
||||
});
|
||||
|
||||
describe("When the unhandledrejection event doesn't have a promise property", function() {
|
||||
it('immediately reports the rejection', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const event = { reason: 'nope' };
|
||||
dispatchEvent(globals.listeners, 'unhandledrejection', event);
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(
|
||||
'Unhandled promise rejection: nope',
|
||||
event
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the unhandledrejection event has a promise property', function() {
|
||||
it('does not immediately report the rejection', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const promise = Promise.reject('nope');
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(globals.listeners, 'unhandledrejection', {
|
||||
reason: 'nope',
|
||||
promise
|
||||
});
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('When reportUnhandledRejections is called', function() {
|
||||
it('reports rejections that have not been handled', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const promise = Promise.reject('nope');
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(globals.listeners, 'unhandledrejection', {
|
||||
reason: 'nope',
|
||||
promise
|
||||
});
|
||||
errors.reportUnhandledRejections();
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(
|
||||
'Unhandled promise rejection: nope',
|
||||
{ reason: 'nope', promise }
|
||||
);
|
||||
});
|
||||
|
||||
it('does not report rejections that have been handled', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const promise = Promise.reject('nope');
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(globals.listeners, 'unhandledrejection', {
|
||||
reason: 'nope',
|
||||
promise
|
||||
});
|
||||
dispatchEvent(globals.listeners, 'rejectionhandled', { promise });
|
||||
errors.reportUnhandledRejections();
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not report the same rejection on subsequent calls', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const promise = Promise.reject('nope');
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(globals.listeners, 'unhandledrejection', {
|
||||
reason: 'nope',
|
||||
promise
|
||||
});
|
||||
errors.reportUnhandledRejections();
|
||||
expect(handler).toHaveBeenCalled();
|
||||
handler.calls.reset();
|
||||
|
||||
errors.reportUnhandledRejections();
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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 +617,48 @@ 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 +666,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 +679,21 @@ 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 +701,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 +713,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,43 +722,69 @@ 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: [],
|
||||
rejectionhandled: []
|
||||
};
|
||||
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: [],
|
||||
rejectionHandled: []
|
||||
};
|
||||
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, ...args) {
|
||||
expect(listeners[eventName].length)
|
||||
.withContext(`number of ${eventName} listeners`)
|
||||
.toBeGreaterThan(0);
|
||||
|
||||
for (const l of global.listeners_.unhandledrejection) {
|
||||
l(event);
|
||||
for (const l of listeners[eventName]) {
|
||||
l.apply(null, args);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
describe('QueueRunner', function() {
|
||||
it('validates that queueableFns are truthy', function() {
|
||||
expect(function() {
|
||||
new jasmineUnderTest.QueueRunner({
|
||||
queueableFns: [undefined]
|
||||
});
|
||||
}).toThrowError('Received a falsy queueableFn');
|
||||
});
|
||||
|
||||
it('validates that queueableFns have fn properties', function() {
|
||||
expect(function() {
|
||||
new jasmineUnderTest.QueueRunner({
|
||||
queueableFns: [{ fn: undefined }]
|
||||
});
|
||||
}).toThrowError('Received a queueableFn with no fn');
|
||||
});
|
||||
|
||||
it("runs all the functions it's passed", function() {
|
||||
const calls = [],
|
||||
queueableFn1 = { fn: jasmine.createSpy('fn1') },
|
||||
@@ -239,7 +255,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 +302,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 +324,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 +422,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 +473,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 +652,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 +676,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 +729,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');
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ describe('ReportDispatcher', function() {
|
||||
});
|
||||
|
||||
it('dispatches requested methods to added reporters', function() {
|
||||
const queueRunnerFactory = jasmine.createSpy('queueRunner'),
|
||||
const runQueue = jasmine.createSpy('runQueue'),
|
||||
dispatcher = new jasmineUnderTest.ReportDispatcher(
|
||||
['foo', 'bar'],
|
||||
queueRunnerFactory
|
||||
runQueue
|
||||
),
|
||||
reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']),
|
||||
anotherReporter = jasmine.createSpyObj('reporter', ['foo', 'bar']);
|
||||
@@ -25,7 +25,7 @@ describe('ReportDispatcher', function() {
|
||||
|
||||
dispatcher.foo(123, 456);
|
||||
|
||||
expect(queueRunnerFactory).toHaveBeenCalledWith(
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
queueableFns: [
|
||||
{ fn: jasmine.any(Function) },
|
||||
@@ -35,7 +35,7 @@ describe('ReportDispatcher', function() {
|
||||
})
|
||||
);
|
||||
|
||||
let fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
|
||||
let fns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
fns[0].fn();
|
||||
expect(reporter.foo).toHaveBeenCalledWith(123, 456);
|
||||
expect(reporter.foo.calls.mostRecent().object).toBe(reporter);
|
||||
@@ -44,11 +44,11 @@ describe('ReportDispatcher', function() {
|
||||
expect(anotherReporter.foo).toHaveBeenCalledWith(123, 456);
|
||||
expect(anotherReporter.foo.calls.mostRecent().object).toBe(anotherReporter);
|
||||
|
||||
queueRunnerFactory.calls.reset();
|
||||
runQueue.calls.reset();
|
||||
|
||||
dispatcher.bar('a', 'b');
|
||||
|
||||
expect(queueRunnerFactory).toHaveBeenCalledWith(
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
queueableFns: [
|
||||
{ fn: jasmine.any(Function) },
|
||||
@@ -58,7 +58,7 @@ describe('ReportDispatcher', function() {
|
||||
})
|
||||
);
|
||||
|
||||
fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
|
||||
fns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
fns[0].fn();
|
||||
expect(reporter.bar).toHaveBeenCalledWith('a', 'b');
|
||||
|
||||
@@ -67,17 +67,14 @@ describe('ReportDispatcher', function() {
|
||||
});
|
||||
|
||||
it("does not dispatch to a reporter if the reporter doesn't accept the method", function() {
|
||||
const queueRunnerFactory = jasmine.createSpy('queueRunner'),
|
||||
dispatcher = new jasmineUnderTest.ReportDispatcher(
|
||||
['foo'],
|
||||
queueRunnerFactory
|
||||
),
|
||||
const runQueue = jasmine.createSpy('runQueue'),
|
||||
dispatcher = new jasmineUnderTest.ReportDispatcher(['foo'], runQueue),
|
||||
reporter = jasmine.createSpyObj('reporter', ['baz']);
|
||||
|
||||
dispatcher.addReporter(reporter);
|
||||
|
||||
dispatcher.foo(123, 456);
|
||||
expect(queueRunnerFactory).toHaveBeenCalledWith(
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
queueableFns: []
|
||||
})
|
||||
@@ -85,33 +82,33 @@ describe('ReportDispatcher', function() {
|
||||
});
|
||||
|
||||
it("allows providing a fallback reporter in case there's no other reporter", function() {
|
||||
const queueRunnerFactory = jasmine.createSpy('queueRunner'),
|
||||
const runQueue = jasmine.createSpy('runQueue'),
|
||||
dispatcher = new jasmineUnderTest.ReportDispatcher(
|
||||
['foo', 'bar'],
|
||||
queueRunnerFactory
|
||||
runQueue
|
||||
),
|
||||
reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']);
|
||||
|
||||
dispatcher.provideFallbackReporter(reporter);
|
||||
dispatcher.foo(123, 456);
|
||||
|
||||
expect(queueRunnerFactory).toHaveBeenCalledWith(
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
queueableFns: [{ fn: jasmine.any(Function) }],
|
||||
isReporter: true
|
||||
})
|
||||
);
|
||||
|
||||
const fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
|
||||
const fns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
fns[0].fn();
|
||||
expect(reporter.foo).toHaveBeenCalledWith(123, 456);
|
||||
});
|
||||
|
||||
it('does not call fallback reporting methods when another reporter is provided', function() {
|
||||
const queueRunnerFactory = jasmine.createSpy('queueRunner'),
|
||||
const runQueue = jasmine.createSpy('runQueue'),
|
||||
dispatcher = new jasmineUnderTest.ReportDispatcher(
|
||||
['foo', 'bar'],
|
||||
queueRunnerFactory
|
||||
runQueue
|
||||
),
|
||||
reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']),
|
||||
fallbackReporter = jasmine.createSpyObj('otherReporter', ['foo', 'bar']);
|
||||
@@ -120,38 +117,38 @@ describe('ReportDispatcher', function() {
|
||||
dispatcher.addReporter(reporter);
|
||||
dispatcher.foo(123, 456);
|
||||
|
||||
expect(queueRunnerFactory).toHaveBeenCalledWith(
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
queueableFns: [{ fn: jasmine.any(Function) }],
|
||||
isReporter: true
|
||||
})
|
||||
);
|
||||
|
||||
const fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
|
||||
const fns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
fns[0].fn();
|
||||
expect(reporter.foo).toHaveBeenCalledWith(123, 456);
|
||||
expect(fallbackReporter.foo).not.toHaveBeenCalledWith(123, 456);
|
||||
});
|
||||
|
||||
it('allows registered reporters to be cleared', function() {
|
||||
const queueRunnerFactory = jasmine.createSpy('queueRunner'),
|
||||
const runQueue = jasmine.createSpy('runQueue'),
|
||||
dispatcher = new jasmineUnderTest.ReportDispatcher(
|
||||
['foo', 'bar'],
|
||||
queueRunnerFactory
|
||||
runQueue
|
||||
),
|
||||
reporter1 = jasmine.createSpyObj('reporter1', ['foo', 'bar']),
|
||||
reporter2 = jasmine.createSpyObj('reporter2', ['foo', 'bar']);
|
||||
|
||||
dispatcher.addReporter(reporter1);
|
||||
dispatcher.foo(123);
|
||||
expect(queueRunnerFactory).toHaveBeenCalledWith(
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
queueableFns: [{ fn: jasmine.any(Function) }],
|
||||
isReporter: true
|
||||
})
|
||||
);
|
||||
|
||||
let fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
|
||||
let fns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
fns[0].fn();
|
||||
expect(reporter1.foo).toHaveBeenCalledWith(123);
|
||||
|
||||
@@ -159,14 +156,14 @@ describe('ReportDispatcher', function() {
|
||||
dispatcher.addReporter(reporter2);
|
||||
dispatcher.bar(456);
|
||||
|
||||
expect(queueRunnerFactory).toHaveBeenCalledWith(
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
queueableFns: [{ fn: jasmine.any(Function) }],
|
||||
isReporter: true
|
||||
})
|
||||
);
|
||||
|
||||
fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
|
||||
fns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
fns[0].fn();
|
||||
expect(reporter1.bar).not.toHaveBeenCalled();
|
||||
expect(reporter2.bar).toHaveBeenCalledWith(456);
|
||||
|
||||
630
spec/core/RunnerSpec.js
Normal file
630
spec/core/RunnerSpec.js
Normal file
@@ -0,0 +1,630 @@
|
||||
describe('Runner', function() {
|
||||
describe('Integration with TreeProcessor and TreeRunner', function() {
|
||||
let suiteNumber,
|
||||
specNumber,
|
||||
runQueue,
|
||||
globalErrors,
|
||||
reportDispatcher,
|
||||
failSpecWithNoExpectations,
|
||||
detectLateRejectionHandling;
|
||||
|
||||
beforeEach(function() {
|
||||
suiteNumber = 0;
|
||||
specNumber = 0;
|
||||
runQueue = jasmine.createSpy('runQueue');
|
||||
globalErrors = 'the global errors instance';
|
||||
reportDispatcher = jasmine.createSpyObj(
|
||||
'reportDispatcher',
|
||||
jasmineUnderTest.reporterEvents
|
||||
);
|
||||
|
||||
for (const k of jasmineUnderTest.reporterEvents) {
|
||||
reportDispatcher[k].and.returnValue(Promise.resolve());
|
||||
}
|
||||
|
||||
// Reasonable defaults, may be overridden in some cases
|
||||
failSpecWithNoExpectations = false;
|
||||
detectLateRejectionHandling = false;
|
||||
|
||||
spyOn(jasmineUnderTest.TreeRunner.prototype, '_executeSpec');
|
||||
});
|
||||
|
||||
function StubSuite(attrs) {
|
||||
attrs = attrs || {};
|
||||
this.id = 'suite' + suiteNumber++;
|
||||
this.children = attrs.children || [];
|
||||
this.canBeReentered = function() {
|
||||
return !attrs.noReenter;
|
||||
};
|
||||
this.markedPending = attrs.markedPending || false;
|
||||
this.sharedUserContext = function() {
|
||||
return attrs.userContext || {};
|
||||
};
|
||||
this.result = {
|
||||
id: this.id,
|
||||
failedExpectations: []
|
||||
};
|
||||
this.getResult = jasmine.createSpy('getResult');
|
||||
this.beforeAllFns = attrs.beforeAllFns || [];
|
||||
this.afterAllFns = attrs.afterAllFns || [];
|
||||
this.cleanupBeforeAfter = function() {};
|
||||
this.startTimer = function() {};
|
||||
this.endTimer = function() {};
|
||||
}
|
||||
|
||||
function StubSpec(attrs) {
|
||||
attrs = attrs || {};
|
||||
this.id = 'spec' + specNumber++;
|
||||
this.markedPending = attrs.markedPending || false;
|
||||
this.execute = jasmine.createSpy(this.id + '#execute');
|
||||
this.beforeAndAfterFns = () => ({ befores: [], afters: [] });
|
||||
this.userContext = () => ({});
|
||||
this.getFullName = () => '';
|
||||
this.queueableFn = () => {};
|
||||
}
|
||||
|
||||
function makeRunner(topSuite) {
|
||||
const defaultOptions = {
|
||||
getConfig: () => ({
|
||||
specFilter: () => true,
|
||||
failSpecWithNoExpectations,
|
||||
detectLateRejectionHandling
|
||||
}),
|
||||
focusedRunables: () => [],
|
||||
totalSpecsDefined: () => 1,
|
||||
TreeProcessor: jasmineUnderTest.TreeProcessor,
|
||||
runableResources: {
|
||||
initForRunable: () => {},
|
||||
clearForRunable: () => {}
|
||||
},
|
||||
reportDispatcher,
|
||||
globalErrors,
|
||||
runQueue
|
||||
};
|
||||
return new jasmineUnderTest.Runner({
|
||||
...defaultOptions,
|
||||
topSuite
|
||||
});
|
||||
}
|
||||
|
||||
function arrayNotContaining(item) {
|
||||
return {
|
||||
asymmetricMatch(other, matchersUtil) {
|
||||
if (!jasmine.isArray_(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const x of other) {
|
||||
if (matchersUtil.equals(x, item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Precondition: jasmineUnderTest.TreeRunner.prototype._executeSpec is a spy
|
||||
function verifyAndFinishSpec(spec, queueableFn, shouldBeExcluded) {
|
||||
const ex = jasmineUnderTest.TreeRunner.prototype._executeSpec;
|
||||
ex.withArgs(spec, 'onComplete').and.callThrough();
|
||||
|
||||
queueableFn.fn('onComplete');
|
||||
expect(ex).toHaveBeenCalledWith(spec, 'onComplete');
|
||||
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
isLeaf: true,
|
||||
SkipPolicy: jasmineUnderTest.CompleteOnFirstErrorSkipPolicy,
|
||||
queueableFns: shouldBeExcluded
|
||||
? arrayNotContaining(spec.queueableFn)
|
||||
: jasmine.arrayContaining([spec.queueableFn])
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
it('runs a single spec', async function() {
|
||||
const spec = new StubSpec();
|
||||
const topSuite = new StubSuite({
|
||||
children: [spec],
|
||||
userContext: { root: 'context' }
|
||||
});
|
||||
detectLateRejectionHandling = true;
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
|
||||
expect(runQueue).toHaveBeenCalledWith({
|
||||
onComplete: jasmine.any(Function),
|
||||
onException: jasmine.any(Function),
|
||||
userContext: { root: 'context' },
|
||||
queueableFns: [{ fn: jasmine.any(Function) }],
|
||||
onMultipleDone: null,
|
||||
SkipPolicy: jasmineUnderTest.SkipAfterBeforeAllErrorPolicy
|
||||
});
|
||||
|
||||
const runQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
verifyAndFinishSpec(spec, runQueueArgs.queueableFns[0], false);
|
||||
runQueueArgs.onComplete();
|
||||
await promise;
|
||||
});
|
||||
|
||||
it('runs an empty suite', async function() {
|
||||
const suite = new StubSuite({ userContext: { for: 'suite' } });
|
||||
const topSuite = new StubSuite({
|
||||
children: [suite],
|
||||
userContext: { for: 'topSuite' }
|
||||
});
|
||||
suite.parentSuite = topSuite;
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
|
||||
expect(runQueue).toHaveBeenCalledWith({
|
||||
onComplete: jasmine.any(Function),
|
||||
onException: jasmine.any(Function),
|
||||
userContext: { for: 'topSuite' },
|
||||
queueableFns: [{ fn: jasmine.any(Function) }],
|
||||
onMultipleDone: null,
|
||||
SkipPolicy: jasmineUnderTest.SkipAfterBeforeAllErrorPolicy
|
||||
});
|
||||
|
||||
const runQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
const nodeDone = jasmine.createSpy('nodeDone');
|
||||
runQueueArgs.queueableFns[0].fn(nodeDone);
|
||||
expect(runQueue).toHaveBeenCalledWith({
|
||||
onComplete: jasmine.any(Function),
|
||||
onMultipleDone: null,
|
||||
queueableFns: [{ fn: jasmine.any(Function) }],
|
||||
userContext: { for: 'suite' },
|
||||
onException: jasmine.any(Function),
|
||||
onMultipleDone: null,
|
||||
SkipPolicy: jasmineUnderTest.SkipAfterBeforeAllErrorPolicy
|
||||
});
|
||||
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[0].fn('foo');
|
||||
expect(reportDispatcher.suiteStarted).toHaveBeenCalledWith(suite.result);
|
||||
|
||||
suite.getResult.and.returnValue({ my: 'result' });
|
||||
|
||||
runQueue.calls.mostRecent().args[0].onComplete();
|
||||
expect(reportDispatcher.suiteDone).toHaveBeenCalledWith({ my: 'result' });
|
||||
|
||||
runQueueArgs.onComplete();
|
||||
await promise;
|
||||
});
|
||||
|
||||
it('runs a non-empty suite', async function() {
|
||||
const spec1 = new StubSpec();
|
||||
const spec2 = new StubSpec();
|
||||
const suite = new StubSuite({ children: [spec1, spec2] });
|
||||
const topSuite = new StubSuite({ children: [suite] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
let queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(2);
|
||||
queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
expect(queueableFns.length).toBe(3);
|
||||
|
||||
verifyAndFinishSpec(spec1, queueableFns[1], false);
|
||||
verifyAndFinishSpec(spec2, queueableFns[2], false);
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('"runs" an excluded suite', async function() {
|
||||
const spec = new StubSpec();
|
||||
const parent = new StubSuite({ children: [spec] });
|
||||
const topSuite = new StubSuite({ children: [parent] });
|
||||
parent.parentSuite = topSuite;
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
// Empty list of runable IDs excludes everything
|
||||
const promise = subject.execute([]);
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
let queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn(function() {});
|
||||
|
||||
queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
expect(queueableFns.length).toBe(2);
|
||||
|
||||
queueableFns[0].fn();
|
||||
expect(reportDispatcher.suiteStarted).toHaveBeenCalledWith(parent.result);
|
||||
|
||||
verifyAndFinishSpec(spec, queueableFns[1], true);
|
||||
|
||||
parent.getResult.and.returnValue(parent.result);
|
||||
runQueue.calls.argsFor(1)[0].onComplete();
|
||||
expect(reportDispatcher.suiteDone).toHaveBeenCalledWith(parent.result);
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('handles the failSpecWithNoExpectations option', async function() {
|
||||
failSpecWithNoExpectations = true;
|
||||
const spec = new StubSpec();
|
||||
const parent = new StubSuite({ children: [spec] });
|
||||
const topSuite = new StubSuite({ children: [parent] });
|
||||
parent.parentSuite = topSuite;
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
let queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn(function() {});
|
||||
|
||||
queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
expect(queueableFns.length).toBe(2);
|
||||
|
||||
queueableFns[1].fn('foo');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec, 'foo');
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs beforeAlls and afterAlls for a suite with children', async function() {
|
||||
const spec = new StubSpec();
|
||||
const target = new StubSuite({
|
||||
children: [spec],
|
||||
beforeAllFns: [
|
||||
{ fn: 'beforeAll1', timeout: 1 },
|
||||
{ fn: 'beforeAll2', timeout: 2 }
|
||||
],
|
||||
afterAllFns: [
|
||||
{ fn: 'afterAll1', timeout: 3 },
|
||||
{ fn: 'afterAll2', timeout: 4 }
|
||||
]
|
||||
});
|
||||
const topSuite = new StubSuite({ children: [target] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns).toEqual([
|
||||
{ fn: jasmine.any(Function) },
|
||||
{ fn: 'beforeAll1', timeout: 1 },
|
||||
{ fn: 'beforeAll2', timeout: 2 },
|
||||
{ fn: jasmine.any(Function) },
|
||||
{ fn: 'afterAll1', timeout: 3 },
|
||||
{ fn: 'afterAll2', timeout: 4 }
|
||||
]);
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('does not run beforeAlls or afterAlls for a suite with no children', async function() {
|
||||
const target = new StubSuite({
|
||||
beforeAllFns: [{ fn: 'before' }],
|
||||
afterAllFns: [{ fn: 'after' }]
|
||||
});
|
||||
const topSuite = new StubSuite({ children: [target] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toEqual(
|
||||
1
|
||||
);
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('does not run beforeAlls or afterAlls for a suite with only pending children', async function() {
|
||||
const spec = new StubSpec({ markedPending: true });
|
||||
const target = new StubSuite({
|
||||
children: [spec],
|
||||
beforeAllFns: [{ fn: 'before' }],
|
||||
afterAllFns: [{ fn: 'after' }]
|
||||
});
|
||||
const topSuite = new StubSuite({ children: [target] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toEqual(
|
||||
2
|
||||
);
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs specs in the order specified', async function() {
|
||||
const specs = [new StubSpec(), new StubSpec()];
|
||||
const topSuite = new StubSuite({ children: specs });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute([specs[1].id, specs[0].id]);
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn('done');
|
||||
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).not.toHaveBeenCalledWith(specs[0], jasmine.anything());
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(specs[1], 'done');
|
||||
|
||||
queueableFns[1].fn('done');
|
||||
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(specs[0], 'done');
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs specified specs before non-specified specs within a suite', async function() {
|
||||
const specified = new StubSpec();
|
||||
const nonSpecified = new StubSpec();
|
||||
const topSuite = new StubSuite({ children: [nonSpecified, specified] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute([specified.id]);
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn('done');
|
||||
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).not.toHaveBeenCalledWith(nonSpecified, jasmine.anything());
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(specified, 'done');
|
||||
|
||||
queueableFns[1].fn('done');
|
||||
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(nonSpecified, 'done');
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs suites and specs with a specified order', async function() {
|
||||
const specifiedSpec = new StubSpec();
|
||||
const nonSpecifiedSpec = new StubSpec();
|
||||
const specifiedSuite = new StubSuite({ children: [nonSpecifiedSpec] });
|
||||
const topSuite = new StubSuite({
|
||||
children: [specifiedSpec, specifiedSuite]
|
||||
});
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute([specifiedSuite.id, specifiedSpec.id]);
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn();
|
||||
|
||||
expect(specifiedSpec.execute).not.toHaveBeenCalled();
|
||||
const nodeQueueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
nodeQueueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(nonSpecifiedSpec, 'done');
|
||||
|
||||
queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(specifiedSpec, 'done');
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs suites and specs in the order they were declared', async function() {
|
||||
const spec1 = new StubSpec();
|
||||
const spec2 = new StubSpec();
|
||||
const spec3 = new StubSpec();
|
||||
const parent = new StubSuite({ children: [spec2, spec3] });
|
||||
const topSuite = new StubSuite({ children: [spec1, parent] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
expect(queueableFns.length).toBe(2);
|
||||
|
||||
queueableFns[0].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec1, 'done');
|
||||
|
||||
queueableFns[1].fn();
|
||||
const childFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
expect(childFns.length).toBe(3);
|
||||
childFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec2, 'done');
|
||||
|
||||
childFns[2].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec3, 'done');
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs a suite multiple times if the order specified leaves and re-enters it', async function() {
|
||||
const spec1 = new StubSpec();
|
||||
const spec2 = new StubSpec();
|
||||
const spec3 = new StubSpec();
|
||||
const spec4 = new StubSpec();
|
||||
const spec5 = new StubSpec();
|
||||
const reentered = new StubSuite({ children: [spec1, spec2, spec3] });
|
||||
const topSuite = new StubSuite({ children: [reentered, spec4, spec5] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
spyOn(jasmineUnderTest.getEnv(), 'deprecated');
|
||||
const promise = subject.execute([
|
||||
spec1.id,
|
||||
spec4.id,
|
||||
spec2.id,
|
||||
spec5.id,
|
||||
spec3.id
|
||||
]);
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
|
||||
queueableFns[0].fn();
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec1, 'done');
|
||||
|
||||
queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec4, 'done');
|
||||
|
||||
queueableFns[2].fn();
|
||||
expect(runQueue.calls.count()).toBe(3);
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec2, 'done');
|
||||
|
||||
queueableFns[3].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec5, 'done');
|
||||
|
||||
queueableFns[4].fn();
|
||||
expect(runQueue.calls.count()).toBe(4);
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec3, 'done');
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs a parent of a suite with multiple segments correctly', async function() {
|
||||
const spec1 = new StubSpec();
|
||||
const spec2 = new StubSpec();
|
||||
const spec3 = new StubSpec();
|
||||
const spec4 = new StubSpec();
|
||||
const spec5 = new StubSpec();
|
||||
const parent = new StubSuite({ children: [spec1, spec2, spec3] });
|
||||
const grandparent = new StubSuite({ children: [parent] });
|
||||
const topSuite = new StubSuite({ children: [grandparent, spec4, spec5] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
spyOn(jasmineUnderTest.getEnv(), 'deprecated');
|
||||
const promise = subject.execute([
|
||||
spec1.id,
|
||||
spec4.id,
|
||||
spec2.id,
|
||||
spec5.id,
|
||||
spec3.id
|
||||
]);
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
expect(queueableFns.length).toBe(5);
|
||||
|
||||
queueableFns[0].fn();
|
||||
expect(runQueue.calls.count()).toBe(2);
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
|
||||
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn();
|
||||
expect(runQueue.calls.count()).toBe(3);
|
||||
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec1, 'done');
|
||||
|
||||
queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec4, 'done');
|
||||
|
||||
queueableFns[2].fn();
|
||||
expect(runQueue.calls.count()).toBe(4);
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
|
||||
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn();
|
||||
expect(runQueue.calls.count()).toBe(5);
|
||||
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec2, 'done');
|
||||
|
||||
queueableFns[3].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec5, 'done');
|
||||
|
||||
queueableFns[4].fn();
|
||||
expect(runQueue.calls.count()).toBe(6);
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
|
||||
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn();
|
||||
expect(runQueue.calls.count()).toBe(7);
|
||||
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec3, 'done');
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs large segments of nodes in the order they were declared', async function() {
|
||||
const specs = [];
|
||||
|
||||
for (let i = 0; i < 11; i++) {
|
||||
specs.push(new StubSpec());
|
||||
}
|
||||
|
||||
const topSuite = new StubSuite({ children: specs });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
expect(queueableFns.length).toBe(11);
|
||||
|
||||
for (let i = 0; i < 11; i++) {
|
||||
queueableFns[i].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(specs[i], 'done');
|
||||
}
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -33,357 +33,167 @@ describe('Spec', function() {
|
||||
expect(jasmineUnderTest.Spec.isPendingSpecException(void 0)).toBe(false);
|
||||
});
|
||||
|
||||
it('delegates execution to a QueueRunner', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
description: 'my test',
|
||||
id: 'some-id',
|
||||
queueableFn: { fn: function() {} }
|
||||
describe('#executionFinished', function() {
|
||||
it('removes the fn if autoCleanClosures is true', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} },
|
||||
autoCleanClosures: true
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
expect(fakeQueueRunner).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call the start callback on execution', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
|
||||
startCallback = jasmine.createSpy('startCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
id: 123,
|
||||
description: 'foo bar',
|
||||
queueableFn: { fn: function() {} },
|
||||
onStart: startCallback
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
fakeQueueRunner.calls.mostRecent().args[0].queueableFns[0].fn();
|
||||
expect(startCallback).toHaveBeenCalled();
|
||||
expect(startCallback.calls.first().object).toEqual(spec);
|
||||
});
|
||||
|
||||
it('should call the start callback on execution but before any befores are called', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('fakeQueueRunner');
|
||||
let beforesWereCalled = false;
|
||||
const startCallback = jasmine
|
||||
.createSpy('start-callback')
|
||||
.and.callFake(function() {
|
||||
expect(beforesWereCalled).toBe(false);
|
||||
});
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: function() {} },
|
||||
beforeFns: function() {
|
||||
return [
|
||||
function() {
|
||||
beforesWereCalled = true;
|
||||
}
|
||||
];
|
||||
},
|
||||
onStart: startCallback
|
||||
spec.executionFinished();
|
||||
expect(spec.queueableFn.fn).toBeFalsy();
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
fakeQueueRunner.calls.mostRecent().args[0].queueableFns[0].fn();
|
||||
expect(startCallback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('provides all before fns and after fns to be run', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
|
||||
before = jasmine.createSpy('before'),
|
||||
after = jasmine.createSpy('after'),
|
||||
queueableFn = {
|
||||
fn: jasmine.createSpy('test body').and.callFake(function() {
|
||||
expect(before).toHaveBeenCalled();
|
||||
expect(after).not.toHaveBeenCalled();
|
||||
})
|
||||
},
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: queueableFn,
|
||||
beforeAndAfterFns: function() {
|
||||
return { befores: [before], afters: [after] };
|
||||
}
|
||||
it('removes the fn after execution if autoCleanClosures is undefined', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} },
|
||||
autoCleanClosures: undefined
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
const options = fakeQueueRunner.calls.mostRecent().args[0];
|
||||
expect(options.queueableFns).toEqual([
|
||||
{ fn: jasmine.any(Function) },
|
||||
before,
|
||||
queueableFn,
|
||||
after,
|
||||
{
|
||||
fn: jasmine.any(Function),
|
||||
type: 'specCleanup'
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it("tells the queue runner that it's a leaf node", function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: function() {} },
|
||||
beforeAndAfterFns: function() {
|
||||
return { befores: [], afters: [] };
|
||||
}
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
expect(fakeQueueRunner).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
isLeaf: true
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('is marked pending if created without a function body', function() {
|
||||
const startCallback = jasmine.createSpy('startCallback'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
onStart: startCallback,
|
||||
queueableFn: { fn: null },
|
||||
resultCallback: resultCallback
|
||||
});
|
||||
|
||||
expect(spec.status()).toBe('pending');
|
||||
});
|
||||
|
||||
it('can be excluded at execution time by a parent', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
|
||||
startCallback = jasmine.createSpy('startCallback'),
|
||||
specBody = jasmine.createSpy('specBody'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
onStart: startCallback,
|
||||
queueableFn: { fn: specBody },
|
||||
resultCallback: resultCallback
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner, 'cally-back', true);
|
||||
|
||||
expect(fakeQueueRunner).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
onComplete: jasmine.any(Function),
|
||||
queueableFns: [
|
||||
{ fn: jasmine.any(Function) },
|
||||
{
|
||||
fn: jasmine.any(Function),
|
||||
type: 'specCleanup'
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
expect(specBody).not.toHaveBeenCalled();
|
||||
|
||||
const args = fakeQueueRunner.calls.mostRecent().args[0];
|
||||
args.queueableFns[0].fn();
|
||||
expect(startCallback).toHaveBeenCalled();
|
||||
args.queueableFns[args.queueableFns.length - 1].fn();
|
||||
expect(resultCallback).toHaveBeenCalled();
|
||||
|
||||
expect(spec.result.status).toBe('excluded');
|
||||
});
|
||||
|
||||
it('can be marked pending, but still calls callbacks when executed', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
|
||||
startCallback = jasmine.createSpy('startCallback'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
onStart: startCallback,
|
||||
resultCallback: resultCallback,
|
||||
description: 'with a spec',
|
||||
parentSuiteId: 'suite1',
|
||||
filename: 'someSpecFile.js',
|
||||
getSpecName: function() {
|
||||
return 'a suite with a spec';
|
||||
},
|
||||
queueableFn: { fn: null }
|
||||
});
|
||||
|
||||
spec.pend();
|
||||
|
||||
expect(spec.status()).toBe('pending');
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
expect(fakeQueueRunner).toHaveBeenCalled();
|
||||
|
||||
const args = fakeQueueRunner.calls.mostRecent().args[0];
|
||||
args.queueableFns[0].fn();
|
||||
expect(startCallback).toHaveBeenCalled();
|
||||
args.queueableFns[1].fn('things');
|
||||
expect(resultCallback).toHaveBeenCalledWith(
|
||||
{
|
||||
id: spec.id,
|
||||
status: 'pending',
|
||||
description: 'with a spec',
|
||||
fullName: 'a suite with a spec',
|
||||
parentSuiteId: 'suite1',
|
||||
filename: 'someSpecFile.js',
|
||||
failedExpectations: [],
|
||||
passedExpectations: [],
|
||||
deprecationWarnings: [],
|
||||
pendingReason: '',
|
||||
duration: jasmine.any(Number),
|
||||
properties: null,
|
||||
debugLogs: null
|
||||
},
|
||||
'things'
|
||||
);
|
||||
});
|
||||
|
||||
it('should call the done callback on execution complete', function() {
|
||||
const done = jasmine.createSpy('done callback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: function() {} },
|
||||
catchExceptions: function() {
|
||||
return false;
|
||||
},
|
||||
resultCallback: function() {}
|
||||
});
|
||||
|
||||
spec.execute(attrs => attrs.onComplete(), done);
|
||||
|
||||
expect(done).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call the done callback with an error if the spec is failed', function() {
|
||||
const done = jasmine.createSpy('done callback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: function() {} },
|
||||
catchExceptions: function() {
|
||||
return false;
|
||||
},
|
||||
resultCallback: function() {}
|
||||
});
|
||||
|
||||
function queueRunnerFactory(attrs) {
|
||||
spec.result.status = 'failed';
|
||||
attrs.onComplete();
|
||||
}
|
||||
spec.execute(queueRunnerFactory, done);
|
||||
|
||||
expect(done).toHaveBeenCalledWith(
|
||||
jasmine.any(jasmineUnderTest.StopExecutionError)
|
||||
);
|
||||
});
|
||||
|
||||
it('should report the duration of the test', function() {
|
||||
const timer = jasmine.createSpyObj('timer', {
|
||||
start: null,
|
||||
elapsed: 77000
|
||||
});
|
||||
let duration = undefined;
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: jasmine.createSpy('spec body') },
|
||||
catchExceptions: function() {
|
||||
return false;
|
||||
},
|
||||
resultCallback: function(result) {
|
||||
duration = result.duration;
|
||||
},
|
||||
timer: timer
|
||||
spec.executionFinished();
|
||||
expect(spec.queueableFn.fn).toBeFalsy();
|
||||
});
|
||||
|
||||
function queueRunnerFactory(config) {
|
||||
config.queueableFns.forEach(function(qf) {
|
||||
qf.fn();
|
||||
it('does not remove the fn after execution if autoCleanClosures is false', function() {
|
||||
function originalFn() {}
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: originalFn },
|
||||
autoCleanClosures: false
|
||||
});
|
||||
config.onComplete();
|
||||
}
|
||||
|
||||
spec.execute(queueRunnerFactory, function() {});
|
||||
expect(duration).toBe(77000);
|
||||
});
|
||||
|
||||
it('should report properties set during the test', function() {
|
||||
const done = jasmine.createSpy('done callback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: jasmine.createSpy('spec body') },
|
||||
catchExceptions: function() {
|
||||
return false;
|
||||
},
|
||||
resultCallback: function() {}
|
||||
});
|
||||
spec.setSpecProperty('a', 4);
|
||||
spec.execute(attrs => attrs.onComplete(), done);
|
||||
expect(spec.result.properties).toEqual({ a: 4 });
|
||||
});
|
||||
|
||||
it('#status returns passing by default', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: jasmine.createSpy('spec body') }
|
||||
spec.executionFinished();
|
||||
expect(spec.queueableFn.fn).toBe(originalFn);
|
||||
});
|
||||
expect(spec.status()).toBe('passed');
|
||||
});
|
||||
|
||||
it('#status returns passed if all expectations in the spec have passed', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: jasmine.createSpy('spec body') }
|
||||
});
|
||||
spec.addExpectationResult(true, {});
|
||||
expect(spec.status()).toBe('passed');
|
||||
});
|
||||
|
||||
it('#status returns failed if any expectations in the spec have failed', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: jasmine.createSpy('spec body') }
|
||||
});
|
||||
spec.addExpectationResult(true, {});
|
||||
spec.addExpectationResult(false, {});
|
||||
expect(spec.status()).toBe('failed');
|
||||
});
|
||||
|
||||
it('keeps track of passed and failed expectations', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('queueRunner'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: jasmine.createSpy('spec body') },
|
||||
resultCallback: resultCallback
|
||||
});
|
||||
spec.addExpectationResult(true, { message: 'expectation1' });
|
||||
spec.addExpectationResult(false, { message: 'expectation2' });
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
const fns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns;
|
||||
fns[fns.length - 1].fn();
|
||||
|
||||
expect(resultCallback.calls.first().args[0].passedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'expectation1' })
|
||||
]);
|
||||
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'expectation2' })
|
||||
]);
|
||||
});
|
||||
|
||||
it("throws an ExpectationFailed error upon receiving a failed expectation when 'throwOnExpectationFailure' is set", function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('queueRunner'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: function() {} },
|
||||
resultCallback: resultCallback,
|
||||
throwOnExpectationFailure: true
|
||||
describe('#getSpecProperty', function() {
|
||||
it('get the property value', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
spec.addExpectationResult(true, { message: 'passed' });
|
||||
expect(function() {
|
||||
spec.addExpectationResult(false, { message: 'failed' });
|
||||
}).toThrowError(jasmineUnderTest.errors.ExpectationFailed);
|
||||
spec.setSpecProperty('a', 4);
|
||||
expect(spec.getSpecProperty('a')).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
describe('#setSpecProperty', function() {
|
||||
it('adds the property to the result', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
const fns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns;
|
||||
fns[fns.length - 1].fn();
|
||||
expect(resultCallback.calls.first().args[0].passedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'passed' })
|
||||
]);
|
||||
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'failed' })
|
||||
]);
|
||||
spec.setSpecProperty('a', 4);
|
||||
|
||||
expect(spec.result.properties).toEqual({ a: 4 });
|
||||
});
|
||||
|
||||
it('replace the property result when it was previously set', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
spec.setSpecProperty('a', 'original-value');
|
||||
spec.setSpecProperty('b', 'original-value');
|
||||
spec.setSpecProperty('a', 'new-value');
|
||||
|
||||
expect(spec.result.properties).toEqual({
|
||||
a: 'new-value',
|
||||
b: 'original-value'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('status', function() {
|
||||
it('is "passed" by default', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
expect(spec.getResult().status).toBe('passed');
|
||||
});
|
||||
|
||||
it('is "passed" if all expectations passed', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
spec.addExpectationResult(true, {});
|
||||
|
||||
expect(spec.getResult().status).toBe('passed');
|
||||
});
|
||||
|
||||
it('is "failed" if any expectation failed', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
spec.addExpectationResult(true, {});
|
||||
spec.addExpectationResult(false, {});
|
||||
|
||||
expect(spec.getResult().status).toBe('failed');
|
||||
});
|
||||
|
||||
it('is "pending" if created without a function body', function() {
|
||||
const startCallback = jasmine.createSpy('startCallback'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
onStart: startCallback,
|
||||
queueableFn: { fn: null },
|
||||
resultCallback: resultCallback
|
||||
});
|
||||
|
||||
expect(spec.getResult().status).toBe('pending');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#addExpectationResult', function() {
|
||||
it('keeps track of passed and failed expectations', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
spec.addExpectationResult(true, { message: 'expectation1' });
|
||||
spec.addExpectationResult(false, { message: 'expectation2' });
|
||||
|
||||
expect(spec.result.passedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'expectation1' })
|
||||
]);
|
||||
expect(spec.result.failedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'expectation2' })
|
||||
]);
|
||||
});
|
||||
|
||||
describe("when 'throwOnExpectationFailure' is set", function() {
|
||||
it('throws an ExpectationFailed error', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} },
|
||||
throwOnExpectationFailure: true
|
||||
});
|
||||
|
||||
spec.addExpectationResult(true, { message: 'passed' });
|
||||
expect(function() {
|
||||
spec.addExpectationResult(false, { message: 'failed' });
|
||||
}).toThrowError(jasmineUnderTest.errors.ExpectationFailed);
|
||||
|
||||
expect(spec.result.failedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'failed' })
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when 'throwOnExpectationFailure' is not set", function() {
|
||||
it('does not throw', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
spec.addExpectationResult(false, { message: 'failed' });
|
||||
|
||||
expect(spec.result.failedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'failed' })
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('forwards late expectation failures to onLateError', function() {
|
||||
@@ -485,138 +295,76 @@ 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);
|
||||
});
|
||||
|
||||
describe('when a spec is marked pending during execution', function() {
|
||||
it('should mark the spec as pending', function() {
|
||||
const fakeQueueRunner = function(opts) {
|
||||
opts.onException(
|
||||
new Error(jasmineUnderTest.Spec.pendingSpecExceptionMessage)
|
||||
);
|
||||
},
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
description: 'my test',
|
||||
id: 'some-id',
|
||||
queueableFn: { fn: function() {} }
|
||||
});
|
||||
it('can return its full path', function() {
|
||||
const getPath = jasmine
|
||||
.createSpy('getPath')
|
||||
.and.returnValue(['expected val']);
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
expect(spec.status()).toEqual('pending');
|
||||
expect(spec.result.pendingReason).toEqual('');
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
getPath,
|
||||
queueableFn: { fn: null }
|
||||
});
|
||||
|
||||
it('should set the pendingReason', function() {
|
||||
const fakeQueueRunner = function(opts) {
|
||||
opts.onException(
|
||||
new Error(
|
||||
jasmineUnderTest.Spec.pendingSpecExceptionMessage +
|
||||
'custom message'
|
||||
)
|
||||
);
|
||||
},
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
description: 'my test',
|
||||
id: 'some-id',
|
||||
queueableFn: { fn: function() {} }
|
||||
});
|
||||
expect(spec.getPath()).toEqual(['expected val']);
|
||||
expect(getPath.calls.mostRecent().args[0]).toBe(spec);
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
expect(spec.status()).toEqual('pending');
|
||||
expect(spec.result.pendingReason).toEqual('custom message');
|
||||
});
|
||||
expect(spec.metadata.getPath()).toEqual(['expected val']);
|
||||
});
|
||||
|
||||
it('should log a failure when handling an exception', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('queueRunner'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: function() {} },
|
||||
resultCallback: resultCallback
|
||||
describe('#handleException', function() {
|
||||
it('records a failure', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: {}
|
||||
});
|
||||
|
||||
spec.handleException('foo');
|
||||
spec.execute(fakeQueueRunner);
|
||||
spec.handleException('foo');
|
||||
|
||||
const args = fakeQueueRunner.calls.mostRecent().args[0];
|
||||
args.queueableFns[args.queueableFns.length - 1].fn();
|
||||
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([
|
||||
{
|
||||
message: 'foo thrown',
|
||||
matcherName: '',
|
||||
passed: false,
|
||||
expected: '',
|
||||
actual: '',
|
||||
stack: null
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not log an additional failure when handling an ExpectationFailed error', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('queueRunner'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: function() {} },
|
||||
resultCallback: resultCallback
|
||||
});
|
||||
|
||||
spec.handleException(new jasmineUnderTest.errors.ExpectationFailed());
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
const args = fakeQueueRunner.calls.mostRecent().args[0];
|
||||
args.queueableFns[args.queueableFns.length - 1].fn();
|
||||
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([]);
|
||||
});
|
||||
|
||||
it('treats multiple done calls as late errors', function() {
|
||||
const queueRunnerFactory = jasmine.createSpy('queueRunnerFactory'),
|
||||
onLateError = jasmine.createSpy('onLateError'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
onLateError: onLateError,
|
||||
queueableFn: { fn: function() {} },
|
||||
getSpecName: function() {
|
||||
return 'a spec';
|
||||
expect(spec.result.failedExpectations).toEqual([
|
||||
{
|
||||
message: 'foo thrown',
|
||||
matcherName: '',
|
||||
passed: false,
|
||||
expected: '',
|
||||
actual: '',
|
||||
stack: null
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not record an additional failure when the error is ExpectationFailed', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: {}
|
||||
});
|
||||
|
||||
spec.execute(queueRunnerFactory);
|
||||
spec.handleException(new jasmineUnderTest.errors.ExpectationFailed());
|
||||
|
||||
expect(queueRunnerFactory).toHaveBeenCalled();
|
||||
queueRunnerFactory.calls.argsFor(0)[0].onMultipleDone();
|
||||
|
||||
expect(onLateError).toHaveBeenCalledTimes(1);
|
||||
expect(onLateError.calls.argsFor(0)[0]).toBeInstanceOf(Error);
|
||||
expect(onLateError.calls.argsFor(0)[0].message).toEqual(
|
||||
'An asynchronous spec, beforeEach, or afterEach function called its ' +
|
||||
"'done' callback more than once.\n(in spec: a spec)"
|
||||
);
|
||||
expect(spec.result.failedExpectations).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#trace', function() {
|
||||
describe('#debugLog', function() {
|
||||
it('adds the messages to the result', function() {
|
||||
const timer = jasmine.createSpyObj('timer', ['start', 'elapsed']),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: {
|
||||
fn: function() {}
|
||||
},
|
||||
timer: timer
|
||||
}),
|
||||
t1 = 123,
|
||||
t2 = 456;
|
||||
const timer = jasmine.createSpyObj('timer', ['start', 'elapsed']);
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} },
|
||||
timer: timer
|
||||
});
|
||||
const t1 = 123;
|
||||
const t2 = 456;
|
||||
|
||||
spec.execute(() => {});
|
||||
expect(spec.result.debugLogs).toBeNull();
|
||||
timer.elapsed.and.returnValue(t1);
|
||||
spec.debugLog('msg 1');
|
||||
@@ -632,84 +380,36 @@ describe('Spec', function() {
|
||||
});
|
||||
|
||||
describe('When the spec passes', function() {
|
||||
it('omits the messages from the reported result', function() {
|
||||
const resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: {
|
||||
fn: function() {}
|
||||
},
|
||||
resultCallback: resultCallback
|
||||
});
|
||||
it('removes the logs from the result', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
function queueRunnerFactory(config) {
|
||||
spec.debugLog('msg');
|
||||
for (const fn of config.queueableFns) {
|
||||
fn.fn();
|
||||
}
|
||||
config.onComplete(false);
|
||||
}
|
||||
spec.debugLog('msg');
|
||||
spec.executionFinished();
|
||||
|
||||
spec.execute(queueRunnerFactory, function() {});
|
||||
expect(resultCallback).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({ debugLogs: null }),
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
it('removes the messages to save memory', function() {
|
||||
const resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: {
|
||||
fn: function() {}
|
||||
},
|
||||
resultCallback: resultCallback
|
||||
});
|
||||
|
||||
function queueRunnerFactory(config) {
|
||||
spec.debugLog('msg');
|
||||
for (const fn of config.queueableFns) {
|
||||
fn.fn();
|
||||
}
|
||||
config.onComplete(false);
|
||||
}
|
||||
|
||||
spec.execute(queueRunnerFactory, function() {});
|
||||
expect(resultCallback).toHaveBeenCalled();
|
||||
expect(spec.result.debugLogs).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the spec fails', function() {
|
||||
it('includes the messages in the reported result', function() {
|
||||
const resultCallback = jasmine.createSpy('resultCallback'),
|
||||
timer = jasmine.createSpyObj('timer', ['start', 'elapsed']),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: {
|
||||
fn: function() {}
|
||||
},
|
||||
resultCallback: resultCallback,
|
||||
timer: timer
|
||||
}),
|
||||
timestamp = 12345;
|
||||
it('includes the messages in the result', function() {
|
||||
const timer = jasmine.createSpyObj('timer', ['start', 'elapsed']);
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} },
|
||||
timer: timer
|
||||
});
|
||||
const timestamp = 12345;
|
||||
|
||||
timer.elapsed.and.returnValue(timestamp);
|
||||
|
||||
function queueRunnerFactory(config) {
|
||||
spec.debugLog('msg');
|
||||
spec.handleException(new Error('nope'));
|
||||
for (const fn of config.queueableFns) {
|
||||
fn.fn();
|
||||
}
|
||||
config.onComplete(true);
|
||||
}
|
||||
spec.debugLog('msg');
|
||||
spec.handleException(new Error('nope'));
|
||||
spec.executionFinished();
|
||||
|
||||
spec.execute(queueRunnerFactory, function() {});
|
||||
expect(resultCallback).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
debugLogs: [{ message: 'msg', timestamp: timestamp }]
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
expect(spec.result.debugLogs).toEqual([
|
||||
{ message: 'msg', timestamp: timestamp }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) {}
|
||||
];
|
||||
|
||||
@@ -159,7 +153,7 @@ describe('Spies', function() {
|
||||
it('should throw if you do not pass an array or object argument', function() {
|
||||
expect(function() {
|
||||
env.createSpyObj('BaseName');
|
||||
}).toThrow(
|
||||
}).toThrowError(
|
||||
'createSpyObj requires a non-empty array or object of method names to create spies for'
|
||||
);
|
||||
});
|
||||
@@ -167,7 +161,7 @@ describe('Spies', function() {
|
||||
it('should throw if you pass an empty array argument', function() {
|
||||
expect(function() {
|
||||
env.createSpyObj('BaseName', []);
|
||||
}).toThrow(
|
||||
}).toThrowError(
|
||||
'createSpyObj requires a non-empty array or object of method names to create spies for'
|
||||
);
|
||||
});
|
||||
@@ -175,7 +169,7 @@ describe('Spies', function() {
|
||||
it('should throw if you pass an empty object argument', function() {
|
||||
expect(function() {
|
||||
env.createSpyObj('BaseName', {});
|
||||
}).toThrow(
|
||||
}).toThrowError(
|
||||
'createSpyObj requires a non-empty array or object of method names to create spies for'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -115,7 +115,7 @@ describe('Suite', function() {
|
||||
const suite = new jasmineUnderTest.Suite({});
|
||||
|
||||
suite.addExpectationResult(false, {});
|
||||
expect(suite.status()).toBe('failed');
|
||||
expect(suite.getResult().status).toBe('failed');
|
||||
});
|
||||
|
||||
it('retrieves a result with updated status', function() {
|
||||
@@ -140,7 +140,7 @@ describe('Suite', function() {
|
||||
suite.addExpectationResult(false, { message: 'failed' });
|
||||
}).toThrowError(jasmineUnderTest.errors.ExpectationFailed);
|
||||
|
||||
expect(suite.status()).toBe('failed');
|
||||
expect(suite.getResult().status).toBe('failed');
|
||||
expect(suite.result.failedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'failed' })
|
||||
]);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
717
spec/core/TreeRunnerSpec.js
Normal file
717
spec/core/TreeRunnerSpec.js
Normal file
@@ -0,0 +1,717 @@
|
||||
describe('TreeRunner', function() {
|
||||
describe('spec execution', function() {
|
||||
it('starts the timer, reports the spec started, and updates run state at the start of the queue', async function() {
|
||||
const timer = jasmine.createSpyObj('timer', ['start']);
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
id: 'spec1',
|
||||
queueableFn: {},
|
||||
timer
|
||||
});
|
||||
const {
|
||||
runQueue,
|
||||
currentRunableTracker,
|
||||
runableResources,
|
||||
reportDispatcher,
|
||||
suiteRunQueueArgs,
|
||||
executePromise
|
||||
} = runSingleSpecSuite(spec);
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
const next = jasmine.createSpy('next');
|
||||
specRunQueueArgs.queueableFns[0].fn(next);
|
||||
|
||||
expect(timer.start).toHaveBeenCalled();
|
||||
expect(currentRunableTracker.currentRunable()).toBe(spec);
|
||||
expect(runableResources.initForRunable).toHaveBeenCalledWith(
|
||||
spec.id,
|
||||
spec.parentSuiteId
|
||||
);
|
||||
expect(reportDispatcher.specStarted).toHaveBeenCalledWith(spec.result);
|
||||
await Promise.resolve();
|
||||
expect(reportDispatcher.specStarted).toHaveBeenCalledBefore(next);
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
|
||||
it('stops the timer, updates run state, and reports the spec done at the end of the queue', async function() {
|
||||
const timer = jasmine.createSpyObj('timer', ['start', 'elapsed']);
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
id: 'spec1',
|
||||
queueableFn: {},
|
||||
timer
|
||||
});
|
||||
const {
|
||||
runQueue,
|
||||
currentRunableTracker,
|
||||
runableResources,
|
||||
reportDispatcher,
|
||||
suiteRunQueueArgs,
|
||||
executePromise
|
||||
} = runSingleSpecSuite(spec);
|
||||
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
const next = jasmine.createSpy('next');
|
||||
timer.elapsed.and.returnValue('the elapsed time');
|
||||
currentRunableTracker.setCurrentSpec(spec);
|
||||
specRunQueueArgs.queueableFns[1].fn(next);
|
||||
|
||||
expect(currentRunableTracker.currentSpec()).toBeFalsy();
|
||||
expect(runableResources.clearForRunable).toHaveBeenCalledWith(spec.id);
|
||||
expect(reportDispatcher.specDone).toHaveBeenCalledWith(spec.result);
|
||||
expect(spec.result.duration).toEqual('the elapsed time');
|
||||
expect(spec.reportedDone).toEqual(true);
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
expect(reportDispatcher.specDone).toHaveBeenCalledBefore(next);
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
|
||||
it('runs before and after fns', function() {
|
||||
const before = { fn: jasmine.createSpy('before') };
|
||||
const after = { fn: jasmine.createSpy('after') };
|
||||
const queueableFn = {
|
||||
fn: jasmine.createSpy('test body').and.callFake(function() {
|
||||
expect(before).toHaveBeenCalled();
|
||||
expect(after).not.toHaveBeenCalled();
|
||||
})
|
||||
};
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: queueableFn,
|
||||
beforeAndAfterFns: function() {
|
||||
return { befores: [before], afters: [after] };
|
||||
}
|
||||
});
|
||||
|
||||
const { runQueue, suiteRunQueueArgs } = runSingleSpecSuite(spec);
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
|
||||
expect(specRunQueueArgs.queueableFns[1]).toEqual(before);
|
||||
expect(specRunQueueArgs.queueableFns[2]).toEqual(queueableFn);
|
||||
expect(specRunQueueArgs.queueableFns[3]).toEqual(after);
|
||||
});
|
||||
|
||||
it('marks specs pending at runtime', function() {
|
||||
let spec;
|
||||
const queueableFn = {
|
||||
fn() {
|
||||
spec.pend();
|
||||
}
|
||||
};
|
||||
spec = new jasmineUnderTest.Spec({ queueableFn });
|
||||
|
||||
const { runQueue, suiteRunQueueArgs } = runSingleSpecSuite(spec);
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
|
||||
expect(specRunQueueArgs.queueableFns[1]).toEqual(queueableFn);
|
||||
queueableFn.fn();
|
||||
|
||||
expect(spec.getResult().status).toEqual('pending');
|
||||
expect(spec.getResult().pendingReason).toEqual('');
|
||||
});
|
||||
|
||||
it('marks specs pending at runtime with a message', function() {
|
||||
let spec;
|
||||
const queueableFn = {
|
||||
fn() {
|
||||
spec.pend('some reason');
|
||||
}
|
||||
};
|
||||
spec = new jasmineUnderTest.Spec({ queueableFn });
|
||||
|
||||
const { runQueue, suiteRunQueueArgs } = runSingleSpecSuite(spec);
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
|
||||
expect(specRunQueueArgs.queueableFns[1]).toEqual(queueableFn);
|
||||
queueableFn.fn();
|
||||
|
||||
expect(spec.getResult().status).toEqual('pending');
|
||||
expect(spec.getResult().pendingReason).toEqual('some reason');
|
||||
});
|
||||
|
||||
it('passes failSpecWithNoExp to Spec#executionFinished', async function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
id: 'spec1',
|
||||
queueableFn: {}
|
||||
});
|
||||
spyOn(spec, 'executionFinished');
|
||||
const {
|
||||
runQueue,
|
||||
suiteRunQueueArgs,
|
||||
executePromise
|
||||
} = runSingleSpecSuite(spec, { failSpecWithNoExpectations: true });
|
||||
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
expect(specRunQueueArgs.queueableFns[1].type).toEqual('specCleanup');
|
||||
specRunQueueArgs.queueableFns[1].fn();
|
||||
|
||||
expect(spec.executionFinished).toHaveBeenCalledWith(false, true);
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Suite execution', function() {
|
||||
it('reports the duration of the suite', async function() {
|
||||
const timer = jasmine.createSpyObj('timer', ['start', 'elapsed']);
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
|
||||
const suite = new jasmineUnderTest.Suite({
|
||||
id: 'suite1',
|
||||
parentSuite: topSuite,
|
||||
timer
|
||||
});
|
||||
topSuite.addChild(suite);
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ suite }];
|
||||
},
|
||||
childrenOfSuiteSegment() {
|
||||
return [];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors: mockGlobalErrors(),
|
||||
runableResources: mockRunableResources(),
|
||||
reportDispatcher,
|
||||
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
|
||||
getConfig() {
|
||||
return {};
|
||||
},
|
||||
reportChildrenOfBeforeAllFailure() {}
|
||||
});
|
||||
|
||||
const executePromise = subject.execute();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
topSuiteRunQueueOpts.queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
expect(timer.start).not.toHaveBeenCalled();
|
||||
const suiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
suiteRunQueueOpts.queueableFns[0].fn();
|
||||
expect(timer.start).toHaveBeenCalled();
|
||||
expect(timer.elapsed).not.toHaveBeenCalled();
|
||||
|
||||
timer.elapsed.and.returnValue('the duration');
|
||||
suiteRunQueueOpts.onComplete();
|
||||
expect(timer.elapsed).toHaveBeenCalled();
|
||||
const result = suite.getResult();
|
||||
expect(result.duration).toEqual('the duration');
|
||||
expect(reportDispatcher.suiteDone).toHaveBeenCalledWith(result);
|
||||
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
|
||||
it('returns false if a suite failed', async function() {
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
|
||||
const failingSuite = new jasmineUnderTest.Suite({
|
||||
id: 'failingSuite',
|
||||
parentSuite: topSuite
|
||||
});
|
||||
const passingSuite = new jasmineUnderTest.Suite({
|
||||
id: 'passingSuite',
|
||||
parentSuite: topSuite
|
||||
});
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ suite: failingSuite }, { suite: passingSuite }];
|
||||
},
|
||||
childrenOfSuiteSegment() {
|
||||
return [];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors: mockGlobalErrors(),
|
||||
runableResources: mockRunableResources(),
|
||||
reportDispatcher,
|
||||
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
|
||||
getConfig() {
|
||||
return {};
|
||||
},
|
||||
reportChildrenOfBeforeAllFailure() {}
|
||||
});
|
||||
|
||||
const executePromise = subject.execute();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
topSuiteRunQueueOpts.queueableFns[0].fn(function() {});
|
||||
|
||||
// Fail the first suite.
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const failingSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
failingSuiteRunQueueOpts.queueableFns[0].fn();
|
||||
failingSuite.addExpectationResult(false, {});
|
||||
failingSuiteRunQueueOpts.onComplete();
|
||||
|
||||
// Passing the second suite should not reset the overall result.
|
||||
topSuiteRunQueueOpts.queueableFns[1].fn(function() {});
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const passingSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
passingSuiteRunQueueOpts.queueableFns[0].fn();
|
||||
passingSuiteRunQueueOpts.onComplete();
|
||||
|
||||
topSuiteRunQueueOpts.onComplete();
|
||||
|
||||
const result = await executePromise;
|
||||
expect(result.hasFailures).toEqual(true);
|
||||
});
|
||||
|
||||
it('reports children when there is a beforeAll failure', async function() {
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
|
||||
const suite = new jasmineUnderTest.Suite({
|
||||
id: 'suite',
|
||||
parentSuite: topSuite
|
||||
});
|
||||
suite.beforeAll({ fn() {} });
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
id: 'spec',
|
||||
parentSuite: suite,
|
||||
queueableFn: { fn() {} }
|
||||
});
|
||||
suite.addChild(spec);
|
||||
topSuite.addChild(suite);
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ suite }];
|
||||
},
|
||||
childrenOfSuiteSegment() {
|
||||
return [{ spec }];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const reportChildrenOfBeforeAllFailure = jasmine
|
||||
.createSpy('reportChildrenOfBeforeAllFailure')
|
||||
.and.returnValue(Promise.resolve());
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors: mockGlobalErrors(),
|
||||
runableResources: mockRunableResources(),
|
||||
reportDispatcher,
|
||||
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
|
||||
reportChildrenOfBeforeAllFailure,
|
||||
getConfig() {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
const executePromise = subject.execute();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
topSuiteRunQueueOpts.queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const suiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
suiteRunQueueOpts.queueableFns[0].fn();
|
||||
suite.hadBeforeAllFailure = true;
|
||||
suiteRunQueueOpts.onComplete();
|
||||
|
||||
while (reportDispatcher.suiteDone.calls.count() === 0) {
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
expect(reportDispatcher.specDone).toHaveBeenCalledBefore(
|
||||
reportDispatcher.suiteDone
|
||||
);
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
|
||||
it('throws if the wrong suite is completed', async function() {
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
|
||||
const suite = new jasmineUnderTest.Suite({
|
||||
id: 'suite',
|
||||
parentSuite: topSuite
|
||||
});
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
id: 'spec',
|
||||
parentSuite: suite,
|
||||
queueableFn: { fn() {} }
|
||||
});
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ suite }];
|
||||
},
|
||||
childrenOfSuiteSegment() {
|
||||
return [{ spec }];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors: mockGlobalErrors(),
|
||||
runableResources: mockRunableResources(),
|
||||
reportDispatcher,
|
||||
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
|
||||
getConfig() {
|
||||
return {};
|
||||
},
|
||||
reportChildrenOfBeforeAllFailure() {}
|
||||
});
|
||||
|
||||
const executePromise = subject.execute();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
topSuiteRunQueueOpts.queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const suiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
|
||||
// Complete the suite without starting it
|
||||
expect(function() {
|
||||
suiteRunQueueOpts.onComplete();
|
||||
}).toThrowError('Tried to complete the wrong suite');
|
||||
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not remove before and after fns from the top suite', async function() {
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
|
||||
spyOn(topSuite, 'cleanupBeforeAfter');
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors: mockGlobalErrors(),
|
||||
runableResources: mockRunableResources(),
|
||||
reportDispatcher: mockReportDispatcher(),
|
||||
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
|
||||
getConfig() {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
const executePromise = subject.execute();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
|
||||
for (const qfn of topSuiteRunQueueOpts.queueableFns) {
|
||||
qfn.fn();
|
||||
}
|
||||
topSuiteRunQueueOpts.onComplete();
|
||||
|
||||
await expectAsync(executePromise).toBeResolved();
|
||||
expect(topSuite.cleanupBeforeAfter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('Late promise rejection handling', function() {
|
||||
it('works for specs when the detectLateRejectionHandling param is true', function() {
|
||||
const before = jasmine.createSpy('before');
|
||||
const after = jasmine.createSpy('after');
|
||||
const queueableFn = {
|
||||
fn: jasmine.createSpy('test body').and.callFake(function() {
|
||||
expect(before).toHaveBeenCalled();
|
||||
expect(after).not.toHaveBeenCalled();
|
||||
})
|
||||
};
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn,
|
||||
beforeAndAfterFns: function() {
|
||||
return { befores: [before], afters: [after] };
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
runQueue,
|
||||
setTimeout,
|
||||
suiteRunQueueArgs,
|
||||
globalErrors
|
||||
} = runSingleSpecSuite(spec, { detectLateRejectionHandling: true });
|
||||
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
|
||||
expect(specRunQueueOpts.queueableFns).toEqual([
|
||||
{ fn: jasmine.any(Function) },
|
||||
before,
|
||||
queueableFn,
|
||||
after,
|
||||
{ fn: jasmine.any(Function) },
|
||||
{
|
||||
fn: jasmine.any(Function),
|
||||
type: 'specCleanup'
|
||||
}
|
||||
]);
|
||||
|
||||
const done = jasmine.createSpy('done');
|
||||
specRunQueueOpts.queueableFns[4].fn(done);
|
||||
expect(globalErrors.reportUnhandledRejections).not.toHaveBeenCalled();
|
||||
expect(done).not.toHaveBeenCalled();
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledOnceWith(jasmine.any(Function));
|
||||
setTimeout.calls.argsFor(0)[0]();
|
||||
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalled();
|
||||
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalledBefore(
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('works for beforeAll when the detectLateRejectionHandling param is true', async function() {
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
|
||||
const suite = new jasmineUnderTest.Suite({
|
||||
id: 'suite',
|
||||
parentSuite: topSuite
|
||||
});
|
||||
suite.beforeAll(function() {});
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn() {} },
|
||||
parentSuite: suite
|
||||
});
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ suite }];
|
||||
},
|
||||
childrenOfSuiteSegment() {
|
||||
return [{ spec }];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const globalErrors = mockGlobalErrors();
|
||||
const setTimeout = jasmine.createSpy('setTimeout');
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors,
|
||||
runableResources: mockRunableResources(),
|
||||
reportDispatcher,
|
||||
setTimeout,
|
||||
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
|
||||
getConfig() {
|
||||
return { detectLateRejectionHandling: true };
|
||||
},
|
||||
reportChildrenOfBeforeAllFailure() {}
|
||||
});
|
||||
|
||||
const executePromise = subject.execute();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
topSuiteRunQueueOpts.queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const suiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
expect(suiteRunQueueOpts.queueableFns).toEqual([
|
||||
{ fn: jasmine.any(Function) }, // onStart
|
||||
jasmine.objectContaining({ type: 'beforeAll' }),
|
||||
{ fn: jasmine.any(Function) }, // detect late rejection handling
|
||||
{ fn: jasmine.any(Function) } // spec
|
||||
]);
|
||||
suiteRunQueueOpts.queueableFns[0].fn();
|
||||
const done = jasmine.createSpy('done');
|
||||
suiteRunQueueOpts.queueableFns[2].fn(done);
|
||||
expect(globalErrors.reportUnhandledRejections).not.toHaveBeenCalled();
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledOnceWith(jasmine.any(Function));
|
||||
setTimeout.calls.argsFor(0)[0]();
|
||||
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalledBefore(
|
||||
done
|
||||
);
|
||||
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
|
||||
it('works for afterAll when the detectLateRejectionHandling param is true', async function() {
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
|
||||
const suite = new jasmineUnderTest.Suite({
|
||||
id: 'suite',
|
||||
parentSuite: topSuite
|
||||
});
|
||||
suite.afterAll(function() {});
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn() {} },
|
||||
parentSuite: suite
|
||||
});
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ suite }];
|
||||
},
|
||||
childrenOfSuiteSegment() {
|
||||
return [{ spec }];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const globalErrors = mockGlobalErrors();
|
||||
const setTimeout = jasmine.createSpy('setTimeout');
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors,
|
||||
runableResources: mockRunableResources(),
|
||||
reportDispatcher,
|
||||
setTimeout,
|
||||
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
|
||||
getConfig() {
|
||||
return { detectLateRejectionHandling: true };
|
||||
},
|
||||
reportChildrenOfBeforeAllFailure() {}
|
||||
});
|
||||
|
||||
const executePromise = subject.execute();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
topSuiteRunQueueOpts.queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const suiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
expect(suiteRunQueueOpts.queueableFns).toEqual([
|
||||
{ fn: jasmine.any(Function) }, // onStart
|
||||
{ fn: jasmine.any(Function) }, // spec
|
||||
jasmine.objectContaining({ type: 'afterAll' }),
|
||||
{ fn: jasmine.any(Function) } // detect late rejection handling
|
||||
]);
|
||||
suiteRunQueueOpts.queueableFns[0].fn();
|
||||
const done = jasmine.createSpy('done');
|
||||
suiteRunQueueOpts.queueableFns[3].fn(done);
|
||||
expect(globalErrors.reportUnhandledRejections).not.toHaveBeenCalled();
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledOnceWith(jasmine.any(Function));
|
||||
setTimeout.calls.argsFor(0)[0]();
|
||||
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalledBefore(
|
||||
done
|
||||
);
|
||||
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
});
|
||||
|
||||
function runSingleSpecSuite(spec, optionalConfig) {
|
||||
const topSuiteId = 'suite1';
|
||||
spec.parentSuiteId = topSuiteId;
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: topSuiteId });
|
||||
topSuite.addChild(spec);
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ spec }];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const runableResources = mockRunableResources();
|
||||
const globalErrors = mockGlobalErrors();
|
||||
const setTimeout = jasmine.createSpy('setTimeout');
|
||||
const currentRunableTracker = new jasmineUnderTest.CurrentRunableTracker();
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors,
|
||||
setTimeout,
|
||||
runableResources,
|
||||
reportDispatcher,
|
||||
currentRunableTracker,
|
||||
getConfig() {
|
||||
return optionalConfig || {};
|
||||
},
|
||||
reportChildrenOfBeforeAllFailure() {}
|
||||
});
|
||||
|
||||
const executePromise = subject.execute();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const suiteRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
|
||||
return {
|
||||
runQueue,
|
||||
globalErrors,
|
||||
setTimeout,
|
||||
currentRunableTracker,
|
||||
runableResources,
|
||||
reportDispatcher,
|
||||
suiteRunQueueArgs,
|
||||
executePromise
|
||||
};
|
||||
}
|
||||
|
||||
function mockReportDispatcher() {
|
||||
const reportDispatcher = jasmine.createSpyObj(
|
||||
'reportDispatcher',
|
||||
jasmineUnderTest.reporterEvents
|
||||
);
|
||||
|
||||
for (const k of jasmineUnderTest.reporterEvents) {
|
||||
reportDispatcher[k].and.returnValue(Promise.resolve());
|
||||
}
|
||||
|
||||
return reportDispatcher;
|
||||
}
|
||||
|
||||
function mockRunableResources() {
|
||||
return jasmine.createSpyObj('runableResources', [
|
||||
'initForRunable',
|
||||
'clearForRunable'
|
||||
]);
|
||||
}
|
||||
|
||||
function mockGlobalErrors() {
|
||||
return jasmine.createSpyObj('globalErrors', ['reportUnhandledRejections']);
|
||||
}
|
||||
});
|
||||
@@ -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([
|
||||
|
||||
63
spec/core/asymmetric_equality/AllOfSpec.js
Normal file
63
spec/core/asymmetric_equality/AllOfSpec.js
Normal file
@@ -0,0 +1,63 @@
|
||||
describe('AllOf', function() {
|
||||
it('matches a single value', function() {
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
const allOf = new jasmineUnderTest.AllOf('foo');
|
||||
|
||||
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeTrue();
|
||||
});
|
||||
|
||||
it('matches a single matcher', function() {
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
const allOf = new jasmineUnderTest.AllOf(
|
||||
new jasmineUnderTest.StringContaining('oo')
|
||||
);
|
||||
|
||||
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeTrue();
|
||||
});
|
||||
|
||||
it('matches multiple matchers', function() {
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
const allOf = new jasmineUnderTest.AllOf(
|
||||
new jasmineUnderTest.StringContaining('o'),
|
||||
new jasmineUnderTest.StringContaining('f')
|
||||
);
|
||||
|
||||
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeTrue();
|
||||
});
|
||||
|
||||
it('does not match when value does not match', function() {
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
const allOf = new jasmineUnderTest.AllOf('bar');
|
||||
|
||||
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeFalse();
|
||||
});
|
||||
|
||||
it('does not match when any matchers fail', function() {
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
const allOf = new jasmineUnderTest.AllOf(
|
||||
new jasmineUnderTest.StringContaining('o'),
|
||||
new jasmineUnderTest.StringContaining('x')
|
||||
);
|
||||
|
||||
expect(allOf.asymmetricMatch('foo', matchersUtil)).toBeFalse();
|
||||
});
|
||||
|
||||
it('jasmineToStrings itself', function() {
|
||||
const matcher = new jasmineUnderTest.AllOf('o');
|
||||
const pp = jasmine.createSpy('pp').and.returnValue('sample');
|
||||
|
||||
expect(matcher.jasmineToString(pp)).toEqual('<jasmine.allOf(sample)>');
|
||||
expect(pp).toHaveBeenCalledWith(['o']);
|
||||
});
|
||||
|
||||
describe('when called without an argument', function() {
|
||||
it('tells the user to pass a constructor argument', function() {
|
||||
expect(function() {
|
||||
new jasmineUnderTest.AllOf();
|
||||
}).toThrowError(
|
||||
TypeError,
|
||||
'jasmine.allOf() expects at least one argument to be passed.'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,8 +2,7 @@ describe('base helpers', function() {
|
||||
describe('isError_', function() {
|
||||
it('correctly handles WebSocket events', function(done) {
|
||||
if (typeof jasmine.getGlobal().WebSocket === 'undefined') {
|
||||
done();
|
||||
return;
|
||||
pending('Environment does not provide WebSocket');
|
||||
}
|
||||
|
||||
const obj = (function() {
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('buildExpectationResult', function() {
|
||||
|
||||
it('handles nodejs assertions', function() {
|
||||
if (typeof require === 'undefined') {
|
||||
return;
|
||||
pending('This test only runs in Node');
|
||||
}
|
||||
const assert = require('assert');
|
||||
const value = 8421;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1415
spec/core/integration/GlobalErrorHandlingSpec.js
Normal file
1415
spec/core/integration/GlobalErrorHandlingSpec.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -470,20 +470,28 @@ describe('Matchers (Integration)', function() {
|
||||
});
|
||||
|
||||
describe('toBeNullish', function() {
|
||||
verifyPasses(function(env) {
|
||||
env.expect(undefined).toBeNullish();
|
||||
describe('with undefined', function() {
|
||||
verifyPasses(function(env) {
|
||||
env.expect(undefined).toBeNullish();
|
||||
});
|
||||
});
|
||||
|
||||
verifyPasses(function(env) {
|
||||
env.expect(null).toBeNullish();
|
||||
describe('with null', function() {
|
||||
verifyPasses(function(env) {
|
||||
env.expect(null).toBeNullish();
|
||||
});
|
||||
});
|
||||
|
||||
verifyFails(function(env) {
|
||||
env.expect(1).toBeNullish();
|
||||
describe('with a truthy value', function() {
|
||||
verifyFails(function(env) {
|
||||
env.expect(1).toBeNullish();
|
||||
});
|
||||
});
|
||||
|
||||
verifyFails(function(env) {
|
||||
env.expect('').toBeNullish();
|
||||
describe('with a non-null falsy value', function() {
|
||||
verifyFails(function(env) {
|
||||
env.expect('').toBeNullish();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -665,13 +673,17 @@ describe('Matchers (Integration)', function() {
|
||||
env.expect(spyObj).toHaveSpyInteractions();
|
||||
});
|
||||
|
||||
verifyFails(function(env) {
|
||||
env.expect(spyObj).toHaveSpyInteractions();
|
||||
describe('with no methods called', function() {
|
||||
verifyFails(function(env) {
|
||||
env.expect(spyObj).toHaveSpyInteractions();
|
||||
});
|
||||
});
|
||||
|
||||
verifyFails(function(env) {
|
||||
spyObj.otherMethod();
|
||||
env.expect(spyObj).toHaveSpyInteractions();
|
||||
describe('with only non-spy methods called', function() {
|
||||
verifyFails(function(env) {
|
||||
spyObj.otherMethod();
|
||||
env.expect(spyObj).toHaveSpyInteractions();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -621,9 +621,14 @@ describe('spec running', function() {
|
||||
actions.push('spec3');
|
||||
});
|
||||
|
||||
spyOn(jasmineUnderTest.getEnv(), 'deprecated');
|
||||
|
||||
await env.execute([spec2.id, spec3.id, spec1.id]);
|
||||
|
||||
expect(actions).toEqual(['spec2', 'spec3', 'spec1']);
|
||||
expect(jasmineUnderTest.getEnv().deprecated).toHaveBeenCalledWith(
|
||||
'The specified spec/suite order splits up a suite, running unrelated specs in the middle of it. This will become an error in a future release.'
|
||||
);
|
||||
});
|
||||
|
||||
it('refuses to re-enter suites with a beforeAll', async function() {
|
||||
@@ -866,7 +871,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);
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -250,10 +250,6 @@ describe('matchersUtil', function() {
|
||||
});
|
||||
|
||||
it('passes for equivalent Promises (GitHub issue #1314)', function() {
|
||||
if (typeof Promise === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const p1 = new Promise(function() {}),
|
||||
p2 = new Promise(function() {}),
|
||||
matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
@@ -263,14 +259,13 @@ describe('matchersUtil', function() {
|
||||
});
|
||||
|
||||
describe('when running in a browser', function() {
|
||||
function isNotRunningInBrowser() {
|
||||
return typeof document === 'undefined';
|
||||
}
|
||||
beforeEach(function() {
|
||||
if (typeof document === 'undefined') {
|
||||
pending('This test only runs in browsers');
|
||||
}
|
||||
});
|
||||
|
||||
it('passes for equivalent DOM nodes', function() {
|
||||
if (isNotRunningInBrowser()) {
|
||||
return;
|
||||
}
|
||||
const a = document.createElement('div');
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
|
||||
@@ -285,9 +280,6 @@ describe('matchersUtil', function() {
|
||||
});
|
||||
|
||||
it('passes for equivalent objects from different frames', function() {
|
||||
if (isNotRunningInBrowser()) {
|
||||
return;
|
||||
}
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
@@ -299,9 +291,6 @@ describe('matchersUtil', function() {
|
||||
});
|
||||
|
||||
it('fails for DOM nodes with different attributes or child nodes', function() {
|
||||
if (isNotRunningInBrowser()) {
|
||||
return;
|
||||
}
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
const a = document.createElement('div');
|
||||
a.setAttribute('test-attr', 'attr-value');
|
||||
@@ -325,14 +314,13 @@ describe('matchersUtil', function() {
|
||||
});
|
||||
|
||||
describe('when running in Node', function() {
|
||||
function isNotRunningInNode() {
|
||||
return typeof require !== 'function';
|
||||
}
|
||||
beforeEach(function() {
|
||||
if (typeof require !== 'function') {
|
||||
pending('This test only runs in Node');
|
||||
}
|
||||
});
|
||||
|
||||
it('passes for equivalent objects from different vm contexts', function() {
|
||||
if (isNotRunningInNode()) {
|
||||
return;
|
||||
}
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
const vm = require('vm');
|
||||
const sandbox = {
|
||||
@@ -344,9 +332,6 @@ describe('matchersUtil', function() {
|
||||
});
|
||||
|
||||
it('passes for equivalent arrays from different vm contexts', function() {
|
||||
if (isNotRunningInNode()) {
|
||||
return;
|
||||
}
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
const vm = require('vm');
|
||||
const sandbox = {
|
||||
@@ -768,7 +753,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 +766,24 @@ 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(
|
||||
'a1 keys: ' +
|
||||
jasmine.basicPrettyPrinter_(
|
||||
jasmineUnderTest.MatchersUtil.keys(a1)
|
||||
)
|
||||
);
|
||||
jasmine.debugLog(
|
||||
'a2 keys: ' +
|
||||
jasmine.basicPrettyPrinter_(
|
||||
jasmineUnderTest.MatchersUtil.keys(a2)
|
||||
)
|
||||
);
|
||||
jasmine.debugLog('a1 length:' + a1.length);
|
||||
jasmine.debugLog('a2 length:' + a2.length);
|
||||
jasmine.debugLog('a1[0]: ' + a1[0]);
|
||||
jasmine.debugLog('a2[0]: ' + a2[0]);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -82,7 +82,7 @@ describe('toThrowError', function() {
|
||||
|
||||
it('passes if thrown is an instanceof Error regardless of global that contains its constructor', function() {
|
||||
if (isNotRunningInBrowser()) {
|
||||
return;
|
||||
pending('This test only runs in browsers.');
|
||||
}
|
||||
|
||||
const matcher = jasmineUnderTest.matchers.toThrowError();
|
||||
@@ -281,22 +281,44 @@ describe('toThrowError', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if thrown is a type of Error and the expected is a different Error', function() {
|
||||
const matchersUtil = {
|
||||
equals: jasmine.createSpy('delegated-equal').and.returnValue(false),
|
||||
pp: jasmineUnderTest.makePrettyPrinter()
|
||||
},
|
||||
matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil),
|
||||
fn = function() {
|
||||
throw new TypeError('foo');
|
||||
};
|
||||
describe('with a string', function() {
|
||||
it('fails if thrown is a type of Error and the expected is a different Error', function() {
|
||||
const matchersUtil = {
|
||||
equals: jasmine.createSpy('delegated-equal').and.returnValue(false),
|
||||
pp: jasmineUnderTest.makePrettyPrinter()
|
||||
},
|
||||
matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil),
|
||||
fn = function() {
|
||||
throw new TypeError('foo');
|
||||
};
|
||||
|
||||
const result = matcher.compare(fn, TypeError, 'bar');
|
||||
const result = matcher.compare(fn, TypeError, 'bar');
|
||||
|
||||
expect(result.pass).toBe(false);
|
||||
expect(result.message()).toEqual(
|
||||
"Expected function to throw TypeError with message 'bar', but it threw TypeError with message 'foo'."
|
||||
);
|
||||
expect(result.pass).toBe(false);
|
||||
expect(result.message()).toEqual(
|
||||
"Expected function to throw TypeError with message 'bar', but it threw TypeError with message 'foo'."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a regex', function() {
|
||||
it('fails if thrown is a type of Error and the expected is a different Error', function() {
|
||||
const matchersUtil = {
|
||||
equals: jasmine.createSpy('delegated-equal').and.returnValue(false),
|
||||
pp: jasmineUnderTest.makePrettyPrinter()
|
||||
},
|
||||
matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil),
|
||||
fn = function() {
|
||||
throw new TypeError('foo');
|
||||
};
|
||||
|
||||
const result = matcher.compare(fn, TypeError, /bar/);
|
||||
|
||||
expect(result.pass).toBe(false);
|
||||
expect(result.message()).toEqual(
|
||||
"Expected function to throw TypeError with a message matching /bar/, but it threw TypeError with message 'foo'."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('passes if thrown is a type of Error and has the same type as the expected Error and the message matches the expected message', function() {
|
||||
@@ -316,22 +338,4 @@ describe('toThrowError', function() {
|
||||
'Expected function not to throw TypeError with a message matching /foo/.'
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if thrown is a type of Error and the expected is a different Error', function() {
|
||||
const matchersUtil = {
|
||||
equals: jasmine.createSpy('delegated-equal').and.returnValue(false),
|
||||
pp: jasmineUnderTest.makePrettyPrinter()
|
||||
},
|
||||
matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil),
|
||||
fn = function() {
|
||||
throw new TypeError('foo');
|
||||
};
|
||||
|
||||
const result = matcher.compare(fn, TypeError, /bar/);
|
||||
|
||||
expect(result.pass).toBe(false);
|
||||
expect(result.message()).toEqual(
|
||||
"Expected function to throw TypeError with a message matching /bar/, but it threw TypeError with message 'foo'."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
5
spec/helpers/callerFilenameShim.js
Normal file
5
spec/helpers/callerFilenameShim.js
Normal file
@@ -0,0 +1,5 @@
|
||||
(function() {
|
||||
specHelpers.callerFilenameShim = function(fn) {
|
||||
return fn();
|
||||
};
|
||||
})();
|
||||
@@ -499,7 +499,7 @@ describe('HtmlReporter', function() {
|
||||
expect(duration.innerHTML).toMatch(/finished in 0.1s/);
|
||||
});
|
||||
|
||||
it('reports the suite and spec names with status', function() {
|
||||
it('reports the suite names with status, and spec names with status and duration', function() {
|
||||
const container = document.createElement('div'),
|
||||
getContainer = function() {
|
||||
return container;
|
||||
@@ -532,7 +532,8 @@ describe('HtmlReporter', function() {
|
||||
fullName: 'A Suite with a spec',
|
||||
status: 'passed',
|
||||
failedExpectations: [],
|
||||
passedExpectations: [{ passed: true }]
|
||||
passedExpectations: [{ passed: true }],
|
||||
duration: 1230
|
||||
};
|
||||
reporter.specStarted(specResult);
|
||||
reporter.specDone(specResult);
|
||||
@@ -549,7 +550,8 @@ describe('HtmlReporter', function() {
|
||||
fullName: 'A Suite inner suite with another spec',
|
||||
status: 'passed',
|
||||
failedExpectations: [],
|
||||
passedExpectations: [{ passed: true }]
|
||||
passedExpectations: [{ passed: true }],
|
||||
duration: 1240
|
||||
};
|
||||
reporter.specStarted(specResult);
|
||||
reporter.specDone(specResult);
|
||||
@@ -567,7 +569,8 @@ describe('HtmlReporter', function() {
|
||||
fullName: 'A Suite inner with a failing spec',
|
||||
status: 'failed',
|
||||
failedExpectations: [{}],
|
||||
passedExpectations: []
|
||||
passedExpectations: [],
|
||||
duration: 2090
|
||||
};
|
||||
reporter.specStarted(specResult);
|
||||
reporter.specDone(specResult);
|
||||
@@ -614,6 +617,9 @@ describe('HtmlReporter', function() {
|
||||
expect(specLink.getAttribute('href')).toEqual(
|
||||
'/?foo=bar&spec=A Suite with a spec'
|
||||
);
|
||||
|
||||
const specDuration = spec.childNodes[1];
|
||||
expect(specDuration.innerHTML).toEqual('(1230ms)');
|
||||
});
|
||||
|
||||
it('has an options menu', function() {
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
describe('MatchersSpec - HTML Dependent', function() {
|
||||
let env, spec;
|
||||
|
||||
beforeEach(function() {
|
||||
env = new jasmineUnderTest.Env();
|
||||
|
||||
env.describe('suite', function() {
|
||||
spec = env.it('spec', function() {});
|
||||
});
|
||||
spyOn(spec, 'addExpectationResult');
|
||||
|
||||
addMatchers({
|
||||
toPass: function() {
|
||||
return lastResult().passed;
|
||||
},
|
||||
toFail: function() {
|
||||
return !lastResult().passed;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
env.cleanup_();
|
||||
});
|
||||
|
||||
function match(value) {
|
||||
return spec.expect(value);
|
||||
}
|
||||
|
||||
function lastResult() {
|
||||
return spec.addExpectationResult.mostRecentCall.args[1];
|
||||
}
|
||||
|
||||
xit('toEqual with DOM nodes', function() {
|
||||
const nodeA = document.createElement('div');
|
||||
const nodeB = document.createElement('div');
|
||||
expect(match(nodeA).toEqual(nodeA)).toPass();
|
||||
expect(match(nodeA).toEqual(nodeB)).toFail();
|
||||
});
|
||||
});
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -23,9 +23,13 @@ module.exports = {
|
||||
'helpers/BrowserFlags.js',
|
||||
'helpers/domHelpers.js',
|
||||
'helpers/integrationMatchers.js',
|
||||
'helpers/callerFilenameShim.js',
|
||||
'helpers/defineJasmineUnderTest.js',
|
||||
'helpers/resetEnv.js'
|
||||
],
|
||||
env: {
|
||||
forbidDuplicateNames: true
|
||||
},
|
||||
random: true,
|
||||
browser: {
|
||||
name: process.env.JASMINE_BROWSER || 'firefox',
|
||||
@@ -38,7 +42,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
|
||||
}
|
||||
|
||||
@@ -8,9 +8,13 @@
|
||||
"helpers/init.js",
|
||||
"helpers/domHelpers.js",
|
||||
"helpers/integrationMatchers.js",
|
||||
"helpers/callerFilenameShim.js",
|
||||
"helpers/overrideConsoleLogForCircleCi.js",
|
||||
"helpers/nodeDefineJasmineUnderTest.js",
|
||||
"helpers/resetEnv.js"
|
||||
],
|
||||
"env": {
|
||||
"forbidDuplicateNames": true
|
||||
},
|
||||
"random": true
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
219
src/core/Configuration.js
Normal file
219
src/core/Configuration.js
Normal file
@@ -0,0 +1,219 @@
|
||||
getJasmineRequireObj().Configuration = function(j$) {
|
||||
/**
|
||||
* This represents the available options to configure Jasmine.
|
||||
* Options that are not provided will use their default values.
|
||||
* @see Env#configure
|
||||
* @interface Configuration
|
||||
* @since 3.3.0
|
||||
*/
|
||||
const defaultConfig = {
|
||||
/**
|
||||
* Whether to randomize spec execution order
|
||||
* @name Configuration#random
|
||||
* @since 3.3.0
|
||||
* @type Boolean
|
||||
* @default true
|
||||
*/
|
||||
random: true,
|
||||
/**
|
||||
* Seed to use as the basis of randomization.
|
||||
* Null causes the seed to be determined randomly at the start of execution.
|
||||
* @name Configuration#seed
|
||||
* @since 3.3.0
|
||||
* @type (number|string)
|
||||
* @default null
|
||||
*/
|
||||
seed: null,
|
||||
/**
|
||||
* Whether to stop execution of the suite after the first spec failure
|
||||
*
|
||||
* <p>In parallel mode, `stopOnSpecFailure` works on a "best effort"
|
||||
* basis. Jasmine will stop execution as soon as practical after a failure
|
||||
* but it might not be immediate.</p>
|
||||
* @name Configuration#stopOnSpecFailure
|
||||
* @since 3.9.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
stopOnSpecFailure: false,
|
||||
/**
|
||||
* Whether to fail the spec if it ran no expectations. By default
|
||||
* a spec that ran no expectations is reported as passed. Setting this
|
||||
* to true will report such spec as a failure.
|
||||
* @name Configuration#failSpecWithNoExpectations
|
||||
* @since 3.5.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
failSpecWithNoExpectations: false,
|
||||
/**
|
||||
* Whether to cause specs to only have one expectation failure.
|
||||
* @name Configuration#stopSpecOnExpectationFailure
|
||||
* @since 3.3.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
stopSpecOnExpectationFailure: false,
|
||||
/**
|
||||
* A function that takes a spec and returns true if it should be executed
|
||||
* or false if it should be skipped.
|
||||
* @callback SpecFilter
|
||||
* @param {Spec} spec - The spec that the filter is being applied to.
|
||||
* @return boolean
|
||||
*/
|
||||
/**
|
||||
* Function to use to filter specs
|
||||
* @name Configuration#specFilter
|
||||
* @since 3.3.0
|
||||
* @type SpecFilter
|
||||
* @default A function that always returns true.
|
||||
*/
|
||||
specFilter: function() {
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
* Whether reporters should hide disabled specs from their output.
|
||||
* Currently only supported by Jasmine's HTMLReporter
|
||||
* @name Configuration#hideDisabled
|
||||
* @since 3.3.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
hideDisabled: false,
|
||||
/**
|
||||
* Clean closures when a suite is done running (done by clearing the stored function reference).
|
||||
* This prevents memory leaks, but you won't be able to run jasmine multiple times.
|
||||
* @name Configuration#autoCleanClosures
|
||||
* @since 3.10.0
|
||||
* @type boolean
|
||||
* @default true
|
||||
*/
|
||||
autoCleanClosures: true,
|
||||
/**
|
||||
* Whether to forbid duplicate spec or suite names. If set to true, using
|
||||
* the same name multiple times in the same immediate parent suite is an
|
||||
* error.
|
||||
* @name Configuration#forbidDuplicateNames
|
||||
* @type boolean
|
||||
* @default false
|
||||
*/
|
||||
forbidDuplicateNames: false,
|
||||
/**
|
||||
* Whether to issue warnings for certain deprecated functionality
|
||||
* every time it's used. If not set or set to false, deprecation warnings
|
||||
* for methods that tend to be called frequently will be issued only once
|
||||
* or otherwise throttled to prevent the suite output from being flooded
|
||||
* with warnings.
|
||||
* @name Configuration#verboseDeprecations
|
||||
* @since 3.6.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
verboseDeprecations: false,
|
||||
|
||||
/**
|
||||
* Whether to detect late promise rejection handling during spec
|
||||
* execution. If this option is enabled, a promise rejection that triggers
|
||||
* the JavaScript runtime's unhandled rejection event will not be treated
|
||||
* as an error as long as it's handled before the spec finishes.
|
||||
*
|
||||
* This option is off by default because it imposes a performance penalty.
|
||||
* @name Configuration#detectLateRejectionHandling
|
||||
* @since 5.10.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
detectLateRejectionHandling: false,
|
||||
|
||||
/**
|
||||
* The number of extra stack frames inserted by a wrapper around {@link it}
|
||||
* or by some other local modification. Jasmine uses this to determine the
|
||||
* filename for {@link SpecStartedEvent} and {@link SpecDoneEvent}.
|
||||
* @name Configuration#extraItStackFrames
|
||||
* @since 5.13.0
|
||||
* @type number
|
||||
* @default 0
|
||||
*/
|
||||
extraItStackFrames: 0,
|
||||
|
||||
/**
|
||||
* The number of extra stack frames inserted by a wrapper around
|
||||
* {@link describe} or by some other local modification. Jasmine uses this
|
||||
* to determine the filename for {@link SpecStartedEvent} and
|
||||
* {@link SpecDoneEvent}.
|
||||
* @name Configuration#extraDescribeStackFrames
|
||||
* @since 5.13.0
|
||||
* @type number
|
||||
* @default 0
|
||||
*/
|
||||
extraDescribeStackFrames: 0
|
||||
};
|
||||
Object.freeze(defaultConfig);
|
||||
|
||||
class Configuration {
|
||||
#values;
|
||||
|
||||
constructor() {
|
||||
this.#values = { ...defaultConfig };
|
||||
|
||||
for (const k of Object.keys(defaultConfig)) {
|
||||
Object.defineProperty(this, k, {
|
||||
enumerable: true,
|
||||
get() {
|
||||
return this.#values[k];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
copy() {
|
||||
return { ...this.#values };
|
||||
}
|
||||
|
||||
update(changes) {
|
||||
const booleanProps = [
|
||||
'random',
|
||||
'failSpecWithNoExpectations',
|
||||
'hideDisabled',
|
||||
'stopOnSpecFailure',
|
||||
'stopSpecOnExpectationFailure',
|
||||
'autoCleanClosures',
|
||||
'forbidDuplicateNames',
|
||||
'detectLateRejectionHandling'
|
||||
];
|
||||
|
||||
for (const k of booleanProps) {
|
||||
if (typeof changes[k] !== 'undefined') {
|
||||
this.#values[k] = changes[k];
|
||||
}
|
||||
}
|
||||
|
||||
if (changes.specFilter) {
|
||||
this.#values.specFilter = changes.specFilter;
|
||||
}
|
||||
|
||||
// 0 and null are valid values, so a truthiness check wouldn't work
|
||||
if (typeof changes.seed !== 'undefined') {
|
||||
this.#values.seed = changes.seed;
|
||||
}
|
||||
|
||||
// TODO: in the next major release, make verboseDeprecations work like
|
||||
// other boolean properties.
|
||||
if (changes.hasOwnProperty('verboseDeprecations')) {
|
||||
this.#values.verboseDeprecations = changes.verboseDeprecations;
|
||||
}
|
||||
|
||||
// 0 is a valid value for both of these, so a truthiness check wouldn't work
|
||||
if (typeof changes.extraItStackFrames !== 'undefined') {
|
||||
this.#values.extraItStackFrames = changes.extraItStackFrames;
|
||||
}
|
||||
|
||||
if (typeof changes.extraDescribeStackFrames !== 'undefined') {
|
||||
this.#values.extraDescribeStackFrames =
|
||||
changes.extraDescribeStackFrames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Configuration;
|
||||
};
|
||||
38
src/core/CurrentRunableTracker.js
Normal file
38
src/core/CurrentRunableTracker.js
Normal file
@@ -0,0 +1,38 @@
|
||||
getJasmineRequireObj().CurrentRunableTracker = function() {
|
||||
class CurrentRunableTracker {
|
||||
#currentSpec;
|
||||
#currentlyExecutingSuites;
|
||||
|
||||
constructor() {
|
||||
this.#currentlyExecutingSuites = [];
|
||||
}
|
||||
|
||||
currentRunable() {
|
||||
return this.currentSpec() || this.currentSuite();
|
||||
}
|
||||
|
||||
currentSpec() {
|
||||
return this.#currentSpec;
|
||||
}
|
||||
|
||||
setCurrentSpec(spec) {
|
||||
this.#currentSpec = spec;
|
||||
}
|
||||
|
||||
currentSuite() {
|
||||
return this.#currentlyExecutingSuites[
|
||||
this.#currentlyExecutingSuites.length - 1
|
||||
];
|
||||
}
|
||||
|
||||
pushSuite(suite) {
|
||||
this.#currentlyExecutingSuites.push(suite);
|
||||
}
|
||||
|
||||
popSuite() {
|
||||
this.#currentlyExecutingSuites.pop();
|
||||
}
|
||||
}
|
||||
|
||||
return CurrentRunableTracker;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
292
src/core/Env.js
292
src/core/Env.js
@@ -1,4 +1,6 @@
|
||||
getJasmineRequireObj().Env = function(j$) {
|
||||
const DEFAULT_IT_DESCRIBE_STACK_DEPTH = 3;
|
||||
|
||||
/**
|
||||
* @class Env
|
||||
* @since 2.0.0
|
||||
@@ -7,11 +9,12 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
* calling {@link jasmine.getEnv}.
|
||||
* @hideconstructor
|
||||
*/
|
||||
function Env(options) {
|
||||
options = options || {};
|
||||
function Env(envOptions) {
|
||||
envOptions = envOptions || {};
|
||||
|
||||
const self = this;
|
||||
const global = options.global || j$.getGlobal();
|
||||
const GlobalErrors = envOptions.GlobalErrors || j$.GlobalErrors;
|
||||
const global = envOptions.global || j$.getGlobal();
|
||||
|
||||
const realSetTimeout = global.setTimeout;
|
||||
const realClearTimeout = global.clearTimeout;
|
||||
@@ -24,13 +27,27 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
new j$.MockDate(global)
|
||||
);
|
||||
|
||||
const globalErrors = new j$.GlobalErrors();
|
||||
const installGlobalErrors = (function() {
|
||||
const globalErrors = new GlobalErrors(
|
||||
undefined,
|
||||
// Configuration is late-bound because GlobalErrors needs to be constructed
|
||||
// before it's set to detect load-time errors in browsers
|
||||
() => this.configuration()
|
||||
);
|
||||
const { installGlobalErrors, uninstallGlobalErrors } = (function() {
|
||||
let installed = false;
|
||||
return function() {
|
||||
if (!installed) {
|
||||
globalErrors.install();
|
||||
installed = true;
|
||||
|
||||
return {
|
||||
installGlobalErrors() {
|
||||
if (!installed) {
|
||||
globalErrors.install();
|
||||
installed = true;
|
||||
}
|
||||
},
|
||||
uninstallGlobalErrors() {
|
||||
if (installed) {
|
||||
globalErrors.uninstall();
|
||||
installed = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
@@ -43,125 +60,14 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
globalErrors
|
||||
});
|
||||
|
||||
let reporter;
|
||||
let reportDispatcher;
|
||||
let topSuite;
|
||||
let runner;
|
||||
let parallelLoadingState = null; // 'specs', 'helpers', or null for non-parallel
|
||||
|
||||
/**
|
||||
* This represents the available options to configure Jasmine.
|
||||
* Options that are not provided will use their default values.
|
||||
* @see Env#configure
|
||||
* @interface Configuration
|
||||
* @since 3.3.0
|
||||
*/
|
||||
const config = {
|
||||
/**
|
||||
* Whether to randomize spec execution order
|
||||
* @name Configuration#random
|
||||
* @since 3.3.0
|
||||
* @type Boolean
|
||||
* @default true
|
||||
*/
|
||||
random: true,
|
||||
/**
|
||||
* Seed to use as the basis of randomization.
|
||||
* Null causes the seed to be determined randomly at the start of execution.
|
||||
* @name Configuration#seed
|
||||
* @since 3.3.0
|
||||
* @type (number|string)
|
||||
* @default null
|
||||
*/
|
||||
seed: null,
|
||||
/**
|
||||
* Whether to stop execution of the suite after the first spec failure
|
||||
*
|
||||
* <p>In parallel mode, `stopOnSpecFailure` works on a "best effort"
|
||||
* basis. Jasmine will stop execution as soon as practical after a failure
|
||||
* but it might not be immediate.</p>
|
||||
* @name Configuration#stopOnSpecFailure
|
||||
* @since 3.9.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
stopOnSpecFailure: false,
|
||||
/**
|
||||
* Whether to fail the spec if it ran no expectations. By default
|
||||
* a spec that ran no expectations is reported as passed. Setting this
|
||||
* to true will report such spec as a failure.
|
||||
* @name Configuration#failSpecWithNoExpectations
|
||||
* @since 3.5.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
failSpecWithNoExpectations: false,
|
||||
/**
|
||||
* Whether to cause specs to only have one expectation failure.
|
||||
* @name Configuration#stopSpecOnExpectationFailure
|
||||
* @since 3.3.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
stopSpecOnExpectationFailure: false,
|
||||
/**
|
||||
* A function that takes a spec and returns true if it should be executed
|
||||
* or false if it should be skipped.
|
||||
* @callback SpecFilter
|
||||
* @param {Spec} spec - The spec that the filter is being applied to.
|
||||
* @return boolean
|
||||
*/
|
||||
/**
|
||||
* Function to use to filter specs
|
||||
* @name Configuration#specFilter
|
||||
* @since 3.3.0
|
||||
* @type SpecFilter
|
||||
* @default A function that always returns true.
|
||||
*/
|
||||
specFilter: function() {
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
* Whether or not reporters should hide disabled specs from their output.
|
||||
* Currently only supported by Jasmine's HTMLReporter
|
||||
* @name Configuration#hideDisabled
|
||||
* @since 3.3.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
hideDisabled: false,
|
||||
/**
|
||||
* Clean closures when a suite is done running (done by clearing the stored function reference).
|
||||
* This prevents memory leaks, but you won't be able to run jasmine multiple times.
|
||||
* @name Configuration#autoCleanClosures
|
||||
* @since 3.10.0
|
||||
* @type boolean
|
||||
* @default true
|
||||
*/
|
||||
autoCleanClosures: true,
|
||||
/**
|
||||
* Whether to forbid duplicate spec or suite names. If set to true, using
|
||||
* the same name multiple times in the same immediate parent suite is an
|
||||
* error.
|
||||
* @name Configuration#forbidDuplicateNames
|
||||
* @type boolean
|
||||
* @default false
|
||||
*/
|
||||
forbidDuplicateNames: false,
|
||||
/**
|
||||
* Whether or not to issue warnings for certain deprecated functionality
|
||||
* every time it's used. If not set or set to false, deprecation warnings
|
||||
* for methods that tend to be called frequently will be issued only once
|
||||
* or otherwise throttled to to prevent the suite output from being flooded
|
||||
* with warnings.
|
||||
* @name Configuration#verboseDeprecations
|
||||
* @since 3.6.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
verboseDeprecations: false
|
||||
};
|
||||
const config = new j$.Configuration();
|
||||
|
||||
if (!options.suppressLoadErrors) {
|
||||
if (!envOptions.suppressLoadErrors) {
|
||||
installGlobalErrors();
|
||||
globalErrors.pushListener(function loadtimeErrorHandler(error, event) {
|
||||
topSuite.result.failedExpectations.push({
|
||||
@@ -182,41 +88,15 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
* @argument {Configuration} configuration
|
||||
* @function
|
||||
*/
|
||||
this.configure = function(configuration) {
|
||||
this.configure = function(changes) {
|
||||
if (parallelLoadingState) {
|
||||
throw new Error(
|
||||
'Jasmine cannot be configured via Env in parallel mode'
|
||||
);
|
||||
}
|
||||
|
||||
const booleanProps = [
|
||||
'random',
|
||||
'failSpecWithNoExpectations',
|
||||
'hideDisabled',
|
||||
'stopOnSpecFailure',
|
||||
'stopSpecOnExpectationFailure',
|
||||
'autoCleanClosures',
|
||||
'forbidDuplicateNames'
|
||||
];
|
||||
|
||||
booleanProps.forEach(function(prop) {
|
||||
if (typeof configuration[prop] !== 'undefined') {
|
||||
config[prop] = !!configuration[prop];
|
||||
}
|
||||
});
|
||||
|
||||
if (configuration.specFilter) {
|
||||
config.specFilter = configuration.specFilter;
|
||||
}
|
||||
|
||||
if (typeof configuration.seed !== 'undefined') {
|
||||
config.seed = configuration.seed;
|
||||
}
|
||||
|
||||
if (configuration.hasOwnProperty('verboseDeprecations')) {
|
||||
config.verboseDeprecations = configuration.verboseDeprecations;
|
||||
deprecator.verboseDeprecations(config.verboseDeprecations);
|
||||
}
|
||||
config.update(changes);
|
||||
deprecator.verboseDeprecations(config.verboseDeprecations);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -227,11 +107,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
* @returns {Configuration}
|
||||
*/
|
||||
this.configuration = function() {
|
||||
const result = {};
|
||||
for (const property in config) {
|
||||
result[property] = config[property];
|
||||
}
|
||||
return result;
|
||||
return config.copy();
|
||||
};
|
||||
|
||||
this.setDefaultSpyStrategy = function(defaultStrategyFn) {
|
||||
@@ -385,7 +261,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);
|
||||
}
|
||||
|
||||
@@ -432,7 +310,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
deprecator.addDeprecationWarning(runable, deprecation, options);
|
||||
};
|
||||
|
||||
function queueRunnerFactory(options) {
|
||||
function runQueue(options) {
|
||||
options.clearStack = options.clearStack || clearStack;
|
||||
options.timeout = {
|
||||
setTimeout: realSetTimeout,
|
||||
@@ -454,9 +332,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
expectationFactory,
|
||||
asyncExpectationFactory,
|
||||
onLateError: recordLateError,
|
||||
specResultCallback,
|
||||
specStarted,
|
||||
queueRunnerFactory
|
||||
runQueue
|
||||
});
|
||||
topSuite = suiteBuilder.topSuite;
|
||||
const deprecator = new j$.Deprecator(topSuite);
|
||||
@@ -479,11 +355,11 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
* @interface Reporter
|
||||
* @see custom_reporter
|
||||
*/
|
||||
reporter = new j$.ReportDispatcher(
|
||||
reportDispatcher = new j$.ReportDispatcher(
|
||||
j$.reporterEvents,
|
||||
function(options) {
|
||||
options.SkipPolicy = j$.NeverSkipPolicy;
|
||||
return queueRunnerFactory(options);
|
||||
return runQueue(options);
|
||||
},
|
||||
recordLateError
|
||||
);
|
||||
@@ -493,10 +369,11 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
totalSpecsDefined: () => suiteBuilder.totalSpecsDefined,
|
||||
focusedRunables: () => suiteBuilder.focusedRunables,
|
||||
runableResources,
|
||||
reporter,
|
||||
queueRunnerFactory,
|
||||
getConfig: () => config,
|
||||
reportSpecDone
|
||||
reportDispatcher,
|
||||
runQueue,
|
||||
TreeProcessor: j$.TreeProcessor,
|
||||
globalErrors,
|
||||
getConfig: () => config
|
||||
});
|
||||
|
||||
this.setParallelLoadingState = function(state) {
|
||||
@@ -559,7 +436,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
throw new Error('Reporters cannot be added via Env in parallel mode');
|
||||
}
|
||||
|
||||
reporter.addReporter(reporterToAdd);
|
||||
reportDispatcher.addReporter(reporterToAdd);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -571,7 +448,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
* @see custom_reporter
|
||||
*/
|
||||
this.provideFallbackReporter = function(reporterToAdd) {
|
||||
reporter.provideFallbackReporter(reporterToAdd);
|
||||
reportDispatcher.provideFallbackReporter(reporterToAdd);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -585,7 +462,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
throw new Error('Reporters cannot be removed via Env in parallel mode');
|
||||
}
|
||||
|
||||
reporter.clearReporters();
|
||||
reportDispatcher.clearReporters();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -718,14 +595,14 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
|
||||
this.describe = function(description, definitionFn) {
|
||||
ensureIsNotNested('describe');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(describeStackDepth());
|
||||
return suiteBuilder.describe(description, definitionFn, filename)
|
||||
.metadata;
|
||||
};
|
||||
|
||||
this.xdescribe = function(description, definitionFn) {
|
||||
ensureIsNotNested('xdescribe');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(describeStackDepth());
|
||||
return suiteBuilder.xdescribe(description, definitionFn, filename)
|
||||
.metadata;
|
||||
};
|
||||
@@ -733,52 +610,58 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
this.fdescribe = function(description, definitionFn) {
|
||||
ensureIsNotNested('fdescribe');
|
||||
ensureNonParallel('fdescribe');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(describeStackDepth());
|
||||
return suiteBuilder.fdescribe(description, definitionFn, filename)
|
||||
.metadata;
|
||||
};
|
||||
|
||||
function specResultCallback(spec, result, next) {
|
||||
runableResources.clearForRunable(spec.id);
|
||||
runner.currentSpec = null;
|
||||
|
||||
if (result.status === 'failed') {
|
||||
runner.hasFailures = true;
|
||||
}
|
||||
|
||||
reportSpecDone(spec, result, next);
|
||||
}
|
||||
|
||||
function specStarted(spec, suite, next) {
|
||||
runner.currentSpec = spec;
|
||||
runableResources.initForRunable(spec.id, suite.id);
|
||||
reporter.specStarted(spec.result).then(next);
|
||||
}
|
||||
|
||||
function reportSpecDone(spec, result, next) {
|
||||
spec.reportedDone = true;
|
||||
reporter.specDone(result).then(next);
|
||||
}
|
||||
|
||||
this.it = function(description, fn, timeout) {
|
||||
ensureIsNotNested('it');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(itStackDepth());
|
||||
return suiteBuilder.it(description, fn, timeout, filename).metadata;
|
||||
};
|
||||
|
||||
this.xit = function(description, fn, timeout) {
|
||||
ensureIsNotNested('xit');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(itStackDepth());
|
||||
return suiteBuilder.xit(description, fn, timeout, filename).metadata;
|
||||
};
|
||||
|
||||
this.fit = function(description, fn, timeout) {
|
||||
ensureIsNotNested('fit');
|
||||
ensureNonParallel('fit');
|
||||
const filename = callerCallerFilename();
|
||||
const filename = indirectCallerFilename(itStackDepth());
|
||||
return suiteBuilder.fit(description, fn, timeout, filename).metadata;
|
||||
};
|
||||
|
||||
function itStackDepth() {
|
||||
return DEFAULT_IT_DESCRIBE_STACK_DEPTH + config.extraItStackFrames;
|
||||
}
|
||||
|
||||
function describeStackDepth() {
|
||||
return DEFAULT_IT_DESCRIBE_STACK_DEPTH + config.extraDescribeStackFrames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user-defined property as part of the properties field of {@link SpecResult}
|
||||
* @name Env#getSpecProperty
|
||||
* @since 5.10.0
|
||||
* @function
|
||||
* @param {String} key The name of the property
|
||||
* @returns {*} The value of the property
|
||||
*/
|
||||
this.getSpecProperty = function(key) {
|
||||
if (
|
||||
!runner.currentRunable() ||
|
||||
runner.currentRunable() == runner.currentSuite()
|
||||
) {
|
||||
throw new Error(
|
||||
"'getSpecProperty' was used when there was no current spec"
|
||||
);
|
||||
}
|
||||
return runner.currentRunable().getSpecProperty(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets a user-defined property that will be provided to reporters as part of the properties field of {@link SpecResult}
|
||||
* @name Env#setSpecProperty
|
||||
@@ -940,17 +823,16 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
};
|
||||
|
||||
this.cleanup_ = function() {
|
||||
if (globalErrors) {
|
||||
globalErrors.uninstall();
|
||||
}
|
||||
uninstallGlobalErrors();
|
||||
};
|
||||
}
|
||||
|
||||
function callerCallerFilename() {
|
||||
function indirectCallerFilename(depth) {
|
||||
const frames = new j$.StackTrace(new Error()).frames;
|
||||
// frames[3] should always exist except in Jasmine's own tests, which bypass
|
||||
// the global it/describe layer, but don't crash if it doesn't.
|
||||
return frames[3] && frames[3].file;
|
||||
// The specified frame should always exist except in Jasmine's own tests,
|
||||
// which bypass the global it/describe layer, but could be absent in case
|
||||
// of misconfiguration. Don't crash if it's absent.
|
||||
return frames[depth] && frames[depth].file;
|
||||
}
|
||||
|
||||
return Env;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,133 +1,44 @@
|
||||
getJasmineRequireObj().GlobalErrors = function(j$) {
|
||||
function GlobalErrors(global) {
|
||||
global = global || j$.getGlobal();
|
||||
class GlobalErrors {
|
||||
#getConfig;
|
||||
#adapter;
|
||||
#handlers;
|
||||
#overrideHandler;
|
||||
#onRemoveOverrideHandler;
|
||||
#pendingUnhandledRejections;
|
||||
|
||||
const handlers = [];
|
||||
let overrideHandler = null,
|
||||
onRemoveOverrideHandler = null;
|
||||
constructor(global, getConfig) {
|
||||
global = global || j$.getGlobal();
|
||||
this.#getConfig = getConfig;
|
||||
this.#pendingUnhandledRejections = new Map();
|
||||
this.#handlers = [];
|
||||
this.#overrideHandler = null;
|
||||
this.#onRemoveOverrideHandler = null;
|
||||
|
||||
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];
|
||||
}
|
||||
const dispatch = {
|
||||
onUncaughtException: this.#onUncaughtException.bind(this),
|
||||
onUnhandledRejection: this.#onUnhandledRejection.bind(this),
|
||||
onRejectionHandled: this.#onRejectionHandled.bind(this)
|
||||
};
|
||||
};
|
||||
|
||||
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, dispatch);
|
||||
} 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, dispatch);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 +47,246 @@ 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;
|
||||
}
|
||||
|
||||
reportUnhandledRejections() {
|
||||
for (const {
|
||||
reason,
|
||||
event
|
||||
} of this.#pendingUnhandledRejections.values()) {
|
||||
this.#dispatchError(reason, event);
|
||||
}
|
||||
|
||||
this.#pendingUnhandledRejections.clear();
|
||||
}
|
||||
|
||||
// Either error or event may be undefined
|
||||
#onUncaughtException(error, event) {
|
||||
this.#dispatchError(error, event);
|
||||
}
|
||||
|
||||
// event or promise may be undefined
|
||||
// event is passed through for backwards compatibility reasons. It's probably
|
||||
// unnecessary, but user code could depend on it.
|
||||
#onUnhandledRejection(reason, promise, event) {
|
||||
if (this.#detectLateRejectionHandling() && promise) {
|
||||
this.#pendingUnhandledRejections.set(promise, { reason, event });
|
||||
} else {
|
||||
this.#dispatchError(reason, event);
|
||||
}
|
||||
}
|
||||
|
||||
#detectLateRejectionHandling() {
|
||||
return this.#getConfig().detectLateRejectionHandling;
|
||||
}
|
||||
|
||||
#onRejectionHandled(promise) {
|
||||
this.#pendingUnhandledRejections.delete(promise);
|
||||
}
|
||||
|
||||
// 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;
|
||||
#dispatch;
|
||||
#onError;
|
||||
#onUnhandledRejection;
|
||||
#onRejectionHandled;
|
||||
|
||||
constructor(global, dispatch) {
|
||||
this.#global = global;
|
||||
this.#dispatch = dispatch;
|
||||
this.#onError = event => dispatch.onUncaughtException(event.error, event);
|
||||
this.#onUnhandledRejection = this.#unhandledRejectionHandler.bind(this);
|
||||
this.#onRejectionHandled = this.#rejectionHandledHandler.bind(this);
|
||||
}
|
||||
|
||||
install() {
|
||||
this.#global.addEventListener('error', this.#onError);
|
||||
this.#global.addEventListener(
|
||||
'unhandledrejection',
|
||||
this.#onUnhandledRejection
|
||||
);
|
||||
this.#global.addEventListener(
|
||||
'rejectionhandled',
|
||||
this.#onRejectionHandled
|
||||
);
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
this.#global.removeEventListener('error', this.#onError);
|
||||
this.#global.removeEventListener(
|
||||
'unhandledrejection',
|
||||
this.#onUnhandledRejection
|
||||
);
|
||||
this.#global.removeEventListener(
|
||||
'rejectionhandled',
|
||||
this.#onRejectionHandled
|
||||
);
|
||||
}
|
||||
|
||||
#unhandledRejectionHandler(event) {
|
||||
const jasmineMessage = 'Unhandled promise rejection: ' + event.reason;
|
||||
let reason;
|
||||
|
||||
if (j$.isError_(event.reason)) {
|
||||
reason = event.reason;
|
||||
reason.jasmineMessage = jasmineMessage;
|
||||
} else {
|
||||
reason = jasmineMessage;
|
||||
}
|
||||
|
||||
this.#dispatch.onUnhandledRejection(reason, event.promise, event);
|
||||
}
|
||||
|
||||
#rejectionHandledHandler(event) {
|
||||
this.#dispatch.onRejectionHandled(event.promise);
|
||||
}
|
||||
}
|
||||
|
||||
class NodeAdapter {
|
||||
#global;
|
||||
#dispatch;
|
||||
#originalHandlers;
|
||||
#jasmineHandlers;
|
||||
|
||||
constructor(global, dispatch) {
|
||||
this.#global = global;
|
||||
this.#dispatch = dispatch;
|
||||
|
||||
this.#jasmineHandlers = {};
|
||||
this.#originalHandlers = {};
|
||||
|
||||
this.onError = this.onError.bind(this);
|
||||
this.onUnhandledRejection = this.onUnhandledRejection.bind(this);
|
||||
}
|
||||
|
||||
install() {
|
||||
this.#installHandler('uncaughtException', this.onError);
|
||||
this.#installHandler('unhandledRejection', this.onUnhandledRejection);
|
||||
this.#installHandler(
|
||||
'rejectionHandled',
|
||||
this.#dispatch.onRejectionHandled
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#augmentError(error, isUnhandledRejection) {
|
||||
let jasmineMessagePrefix;
|
||||
|
||||
if (isUnhandledRejection) {
|
||||
jasmineMessagePrefix = 'Unhandled promise rejection';
|
||||
} else {
|
||||
jasmineMessagePrefix = 'Uncaught exception';
|
||||
}
|
||||
|
||||
if (j$.isError_(error)) {
|
||||
error.jasmineMessage = jasmineMessagePrefix + ': ' + error;
|
||||
return error;
|
||||
} else {
|
||||
let substituteMsg;
|
||||
|
||||
if (error) {
|
||||
substituteMsg = jasmineMessagePrefix + ': ' + error;
|
||||
} else {
|
||||
substituteMsg = jasmineMessagePrefix + ' with no error or message';
|
||||
}
|
||||
|
||||
if (isUnhandledRejection) {
|
||||
substituteMsg +=
|
||||
'\n' +
|
||||
'(Tip: to get a useful stack trace, use ' +
|
||||
'Promise.reject(n' +
|
||||
'ew Error(...)) instead of Promise.reject(' +
|
||||
(error ? '...' : '') +
|
||||
').)';
|
||||
}
|
||||
|
||||
return new Error(substituteMsg);
|
||||
}
|
||||
}
|
||||
|
||||
onError(error) {
|
||||
error = this.#augmentError(error, false);
|
||||
this.#dispatch.onUncaughtException(error);
|
||||
}
|
||||
|
||||
onUnhandledRejection(reason, promise) {
|
||||
reason = this.#augmentError(reason, true);
|
||||
this.#dispatch.onUnhandledRejection(reason, promise);
|
||||
}
|
||||
}
|
||||
|
||||
return GlobalErrors;
|
||||
|
||||
@@ -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.'
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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' " +
|
||||
@@ -36,6 +37,17 @@ getJasmineRequireObj().QueueRunner = function(j$) {
|
||||
function QueueRunner(attrs) {
|
||||
this.id_ = nextid++;
|
||||
this.queueableFns = attrs.queueableFns || [];
|
||||
|
||||
for (const f of this.queueableFns) {
|
||||
if (!f) {
|
||||
throw new Error('Received a falsy queueableFn');
|
||||
}
|
||||
|
||||
if (!f.fn) {
|
||||
throw new Error('Received a queueableFn with no fn');
|
||||
}
|
||||
}
|
||||
|
||||
this.onComplete = attrs.onComplete || emptyFn;
|
||||
this.clearStack =
|
||||
attrs.clearStack ||
|
||||
@@ -135,6 +147,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
getJasmineRequireObj().ReportDispatcher = function(j$) {
|
||||
'use strict';
|
||||
|
||||
function ReportDispatcher(methods, queueRunnerFactory, onLateError) {
|
||||
function ReportDispatcher(methods, runQueue, onLateError) {
|
||||
const dispatchedMethods = methods || [];
|
||||
|
||||
for (const method of dispatchedMethods) {
|
||||
@@ -39,7 +39,7 @@ getJasmineRequireObj().ReportDispatcher = function(j$) {
|
||||
}
|
||||
|
||||
return new Promise(function(resolve) {
|
||||
queueRunnerFactory({
|
||||
runQueue({
|
||||
queueableFns: fns,
|
||||
onComplete: resolve,
|
||||
isReporter: true,
|
||||
|
||||
@@ -1,51 +1,66 @@
|
||||
getJasmineRequireObj().Runner = function(j$) {
|
||||
class Runner {
|
||||
constructor(options) {
|
||||
this.topSuite_ = options.topSuite;
|
||||
// TODO use names that read like getters
|
||||
this.totalSpecsDefined_ = options.totalSpecsDefined;
|
||||
this.focusedRunables_ = options.focusedRunables;
|
||||
this.runableResources_ = options.runableResources;
|
||||
this.queueRunnerFactory_ = options.queueRunnerFactory;
|
||||
this.reporter_ = options.reporter;
|
||||
this.getConfig_ = options.getConfig;
|
||||
this.reportSpecDone_ = options.reportSpecDone;
|
||||
this.hasFailures = false;
|
||||
this.executedBefore_ = false;
|
||||
#topSuite;
|
||||
#getTotalSpecsDefined;
|
||||
#getFocusedRunables;
|
||||
#runableResources;
|
||||
#runQueue;
|
||||
#TreeProcessor;
|
||||
#executionTree;
|
||||
#globalErrors;
|
||||
#reportDispatcher;
|
||||
#getConfig;
|
||||
#executedBefore;
|
||||
#currentRunableTracker;
|
||||
|
||||
this.currentlyExecutingSuites_ = [];
|
||||
this.currentSpec = null;
|
||||
constructor(options) {
|
||||
this.#topSuite = options.topSuite;
|
||||
this.#getTotalSpecsDefined = options.totalSpecsDefined;
|
||||
this.#getFocusedRunables = options.focusedRunables;
|
||||
this.#runableResources = options.runableResources;
|
||||
this.#runQueue = options.runQueue;
|
||||
this.#TreeProcessor = options.TreeProcessor;
|
||||
this.#globalErrors = options.globalErrors;
|
||||
this.#reportDispatcher = options.reportDispatcher;
|
||||
this.#getConfig = options.getConfig;
|
||||
this.#executedBefore = false;
|
||||
this.#currentRunableTracker = new j$.CurrentRunableTracker();
|
||||
}
|
||||
|
||||
currentSpec() {
|
||||
return this.#currentRunableTracker.currentSpec();
|
||||
}
|
||||
|
||||
setCurrentSpec(spec) {
|
||||
this.#currentRunableTracker.setCurrentSpec(spec);
|
||||
}
|
||||
|
||||
currentRunable() {
|
||||
return this.currentSpec || this.currentSuite();
|
||||
return this.#currentRunableTracker.currentRunable();
|
||||
}
|
||||
|
||||
currentSuite() {
|
||||
return this.currentlyExecutingSuites_[
|
||||
this.currentlyExecutingSuites_.length - 1
|
||||
];
|
||||
return this.#currentRunableTracker.currentSuite();
|
||||
}
|
||||
|
||||
parallelReset() {
|
||||
this.executedBefore_ = false;
|
||||
this.#executedBefore = false;
|
||||
}
|
||||
|
||||
async execute(runablesToRun) {
|
||||
if (this.executedBefore_) {
|
||||
this.topSuite_.reset();
|
||||
if (this.#executedBefore) {
|
||||
this.#topSuite.reset();
|
||||
}
|
||||
this.executedBefore_ = true;
|
||||
this.#executedBefore = true;
|
||||
|
||||
this.hasFailures = false;
|
||||
const focusedRunables = this.focusedRunables_();
|
||||
const config = this.getConfig_();
|
||||
const focusedRunables = this.#getFocusedRunables();
|
||||
const config = this.#getConfig();
|
||||
|
||||
if (!runablesToRun) {
|
||||
if (focusedRunables.length) {
|
||||
runablesToRun = focusedRunables;
|
||||
} else {
|
||||
runablesToRun = [this.topSuite_.id];
|
||||
runablesToRun = [this.#topSuite.id];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,52 +69,9 @@ getJasmineRequireObj().Runner = function(j$) {
|
||||
seed: j$.isNumber_(config.seed) ? config.seed + '' : config.seed
|
||||
});
|
||||
|
||||
const processor = new j$.TreeProcessor({
|
||||
tree: this.topSuite_,
|
||||
const treeProcessor = new this.#TreeProcessor({
|
||||
tree: this.#topSuite,
|
||||
runnableIds: runablesToRun,
|
||||
queueRunnerFactory: options => {
|
||||
if (options.isLeaf) {
|
||||
// A spec
|
||||
options.SkipPolicy = j$.CompleteOnFirstErrorSkipPolicy;
|
||||
} else {
|
||||
// A suite
|
||||
if (config.stopOnSpecFailure) {
|
||||
options.SkipPolicy = j$.CompleteOnFirstErrorSkipPolicy;
|
||||
} else {
|
||||
options.SkipPolicy = j$.SkipAfterBeforeAllErrorPolicy;
|
||||
}
|
||||
}
|
||||
|
||||
return this.queueRunnerFactory_(options);
|
||||
},
|
||||
failSpecWithNoExpectations: config.failSpecWithNoExpectations,
|
||||
nodeStart: (suite, next) => {
|
||||
this.currentlyExecutingSuites_.push(suite);
|
||||
this.runableResources_.initForRunable(suite.id, suite.parentSuite.id);
|
||||
this.reporter_.suiteStarted(suite.result).then(next);
|
||||
suite.startTimer();
|
||||
},
|
||||
nodeComplete: (suite, result, next) => {
|
||||
if (suite !== this.currentSuite()) {
|
||||
throw new Error('Tried to complete the wrong suite');
|
||||
}
|
||||
|
||||
this.runableResources_.clearForRunable(suite.id);
|
||||
this.currentlyExecutingSuites_.pop();
|
||||
|
||||
if (result.status === 'failed') {
|
||||
this.hasFailures = true;
|
||||
}
|
||||
suite.endTimer();
|
||||
|
||||
if (suite.hadBeforeAllFailure) {
|
||||
this.reportChildrenOfBeforeAllFailure_(suite).then(() => {
|
||||
this.reportSuiteDone_(suite, result, next);
|
||||
});
|
||||
} else {
|
||||
this.reportSuiteDone_(suite, result, next);
|
||||
}
|
||||
},
|
||||
orderChildren: function(node) {
|
||||
return order.sort(node.children);
|
||||
},
|
||||
@@ -107,20 +79,15 @@ getJasmineRequireObj().Runner = function(j$) {
|
||||
return !config.specFilter(spec);
|
||||
}
|
||||
});
|
||||
this.#executionTree = treeProcessor.processTree();
|
||||
|
||||
if (!processor.processTree().valid) {
|
||||
throw new Error(
|
||||
'Invalid order: would cause a beforeAll or afterAll to be run multiple times'
|
||||
);
|
||||
}
|
||||
|
||||
return this.execute2_(runablesToRun, order, processor);
|
||||
return this.#execute2(runablesToRun, order);
|
||||
}
|
||||
|
||||
async execute2_(runablesToRun, order, processor) {
|
||||
const totalSpecsDefined = this.totalSpecsDefined_();
|
||||
async #execute2(runablesToRun, order) {
|
||||
const totalSpecsDefined = this.#getTotalSpecsDefined();
|
||||
|
||||
this.runableResources_.initForRunable(this.topSuite_.id);
|
||||
this.#runableResources.initForRunable(this.#topSuite.id);
|
||||
const jasmineTimer = new j$.Timer();
|
||||
jasmineTimer.start();
|
||||
|
||||
@@ -132,31 +99,39 @@ getJasmineRequireObj().Runner = function(j$) {
|
||||
* @property {Boolean} parallel - Whether Jasmine is being run in parallel mode.
|
||||
* @since 2.0.0
|
||||
*/
|
||||
await this.reporter_.jasmineStarted({
|
||||
await this.#reportDispatcher.jasmineStarted({
|
||||
// In parallel mode, the jasmineStarted event is separately dispatched
|
||||
// by jasmine-npm. This event only reaches reporters in non-parallel.
|
||||
totalSpecsDefined,
|
||||
/**
|
||||
* Information about the ordering (random or not) of this execution of the suite.
|
||||
* @typedef Order
|
||||
* @property {boolean} random - Whether the suite is running in random order
|
||||
* @property {string} seed - The random seed
|
||||
*/
|
||||
order: order,
|
||||
parallel: false
|
||||
});
|
||||
|
||||
this.currentlyExecutingSuites_.push(this.topSuite_);
|
||||
await processor.execute();
|
||||
this.#currentRunableTracker.pushSuite(this.#topSuite);
|
||||
const treeRunner = new j$.TreeRunner({
|
||||
executionTree: this.#executionTree,
|
||||
globalErrors: this.#globalErrors,
|
||||
runableResources: this.#runableResources,
|
||||
reportDispatcher: this.#reportDispatcher,
|
||||
runQueue: this.#runQueue,
|
||||
getConfig: this.#getConfig,
|
||||
currentRunableTracker: this.#currentRunableTracker
|
||||
});
|
||||
const { hasFailures } = await treeRunner.execute();
|
||||
|
||||
if (this.topSuite_.hadBeforeAllFailure) {
|
||||
await this.reportChildrenOfBeforeAllFailure_(this.topSuite_);
|
||||
}
|
||||
|
||||
this.runableResources_.clearForRunable(this.topSuite_.id);
|
||||
this.currentlyExecutingSuites_.pop();
|
||||
this.#runableResources.clearForRunable(this.#topSuite.id);
|
||||
this.#currentRunableTracker.popSuite();
|
||||
let overallStatus, incompleteReason, incompleteCode;
|
||||
|
||||
if (
|
||||
this.hasFailures ||
|
||||
this.topSuite_.result.failedExpectations.length > 0
|
||||
) {
|
||||
if (hasFailures || this.#topSuite.result.failedExpectations.length > 0) {
|
||||
overallStatus = 'failed';
|
||||
} else if (this.focusedRunables_().length > 0) {
|
||||
} else if (this.#getFocusedRunables().length > 0) {
|
||||
overallStatus = 'incomplete';
|
||||
incompleteReason = 'fit() or fdescribe() was found';
|
||||
incompleteCode = 'focused';
|
||||
@@ -187,53 +162,13 @@ getJasmineRequireObj().Runner = function(j$) {
|
||||
incompleteReason: incompleteReason,
|
||||
incompleteCode: incompleteCode,
|
||||
order: order,
|
||||
failedExpectations: this.topSuite_.result.failedExpectations,
|
||||
deprecationWarnings: this.topSuite_.result.deprecationWarnings
|
||||
failedExpectations: this.#topSuite.result.failedExpectations,
|
||||
deprecationWarnings: this.#topSuite.result.deprecationWarnings
|
||||
};
|
||||
this.topSuite_.reportedDone = true;
|
||||
await this.reporter_.jasmineDone(jasmineDoneInfo);
|
||||
this.#topSuite.reportedDone = true;
|
||||
await this.#reportDispatcher.jasmineDone(jasmineDoneInfo);
|
||||
return jasmineDoneInfo;
|
||||
}
|
||||
|
||||
reportSuiteDone_(suite, result, next) {
|
||||
suite.reportedDone = true;
|
||||
this.reporter_.suiteDone(result).then(next);
|
||||
}
|
||||
|
||||
async reportChildrenOfBeforeAllFailure_(suite) {
|
||||
for (const child of suite.children) {
|
||||
if (child instanceof j$.Suite) {
|
||||
await this.reporter_.suiteStarted(child.result);
|
||||
await this.reportChildrenOfBeforeAllFailure_(child);
|
||||
|
||||
// Marking the suite passed is consistent with how suites that
|
||||
// contain failed specs but no suite-level failures are reported.
|
||||
child.result.status = 'passed';
|
||||
|
||||
await this.reporter_.suiteDone(child.result);
|
||||
} else {
|
||||
/* a spec */
|
||||
await this.reporter_.specStarted(child.result);
|
||||
|
||||
child.addExpectationResult(
|
||||
false,
|
||||
{
|
||||
passed: false,
|
||||
message:
|
||||
'Not run because a beforeAll function failed. The ' +
|
||||
'beforeAll failure will be reported on the suite that ' +
|
||||
'caused it.'
|
||||
},
|
||||
true
|
||||
);
|
||||
child.result.status = 'failed';
|
||||
|
||||
await new Promise(resolve => {
|
||||
this.reportSpecDone_(child, child.result, resolve);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Runner;
|
||||
|
||||
572
src/core/Spec.js
572
src/core/Spec.js
@@ -1,313 +1,248 @@
|
||||
getJasmineRequireObj().Spec = function(j$) {
|
||||
function Spec(attrs) {
|
||||
this.expectationFactory = attrs.expectationFactory;
|
||||
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
|
||||
this.resultCallback = attrs.resultCallback || function() {};
|
||||
this.id = attrs.id;
|
||||
this.filename = attrs.filename;
|
||||
this.parentSuiteId = attrs.parentSuiteId;
|
||||
this.description = attrs.description || '';
|
||||
this.queueableFn = attrs.queueableFn;
|
||||
this.beforeAndAfterFns =
|
||||
attrs.beforeAndAfterFns ||
|
||||
function() {
|
||||
return { befores: [], afters: [] };
|
||||
};
|
||||
this.userContext =
|
||||
attrs.userContext ||
|
||||
function() {
|
||||
return {};
|
||||
};
|
||||
this.onStart = attrs.onStart || function() {};
|
||||
this.autoCleanClosures =
|
||||
attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures;
|
||||
this.getSpecName =
|
||||
attrs.getSpecName ||
|
||||
function() {
|
||||
return '';
|
||||
};
|
||||
this.onLateError = attrs.onLateError || function() {};
|
||||
this.catchingExceptions =
|
||||
attrs.catchingExceptions ||
|
||||
function() {
|
||||
return true;
|
||||
};
|
||||
this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
|
||||
this.timer = attrs.timer || new j$.Timer();
|
||||
class Spec {
|
||||
#autoCleanClosures;
|
||||
#throwOnExpectationFailure;
|
||||
#timer;
|
||||
#metadata;
|
||||
|
||||
if (!this.queueableFn.fn) {
|
||||
this.exclude();
|
||||
constructor(attrs) {
|
||||
this.expectationFactory = attrs.expectationFactory;
|
||||
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
|
||||
this.id = attrs.id;
|
||||
this.filename = attrs.filename;
|
||||
this.parentSuiteId = attrs.parentSuiteId;
|
||||
this.description = attrs.description || '';
|
||||
this.queueableFn = attrs.queueableFn;
|
||||
this.beforeAndAfterFns =
|
||||
attrs.beforeAndAfterFns ||
|
||||
function() {
|
||||
return { befores: [], afters: [] };
|
||||
};
|
||||
this.userContext =
|
||||
attrs.userContext ||
|
||||
function() {
|
||||
return {};
|
||||
};
|
||||
this.getPath = function() {
|
||||
return attrs.getPath ? attrs.getPath(this) : [];
|
||||
};
|
||||
|
||||
this.#autoCleanClosures =
|
||||
attrs.autoCleanClosures === undefined
|
||||
? true
|
||||
: !!attrs.autoCleanClosures;
|
||||
this.onLateError = attrs.onLateError || function() {};
|
||||
this.#throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
|
||||
this.#timer = attrs.timer || new j$.Timer();
|
||||
|
||||
if (!this.queueableFn.fn) {
|
||||
this.exclude();
|
||||
}
|
||||
|
||||
this.reset();
|
||||
}
|
||||
|
||||
this.reset();
|
||||
}
|
||||
addExpectationResult(passed, data, isError) {
|
||||
const expectationResult = j$.buildExpectationResult(data);
|
||||
|
||||
Spec.prototype.addExpectationResult = function(passed, data, isError) {
|
||||
const expectationResult = j$.buildExpectationResult(data);
|
||||
|
||||
if (passed) {
|
||||
this.result.passedExpectations.push(expectationResult);
|
||||
} else {
|
||||
if (this.reportedDone) {
|
||||
this.onLateError(expectationResult);
|
||||
if (passed) {
|
||||
this.result.passedExpectations.push(expectationResult);
|
||||
} else {
|
||||
this.result.failedExpectations.push(expectationResult);
|
||||
|
||||
// TODO: refactor so that we don't need to override cached status
|
||||
if (this.result.status) {
|
||||
this.result.status = 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
if (this.throwOnExpectationFailure && !isError) {
|
||||
throw new j$.errors.ExpectationFailed();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Spec.prototype.setSpecProperty = function(key, value) {
|
||||
this.result.properties = this.result.properties || {};
|
||||
this.result.properties[key] = value;
|
||||
};
|
||||
|
||||
Spec.prototype.execute = function(
|
||||
queueRunnerFactory,
|
||||
onComplete,
|
||||
excluded,
|
||||
failSpecWithNoExp
|
||||
) {
|
||||
const onStart = {
|
||||
fn: done => {
|
||||
this.timer.start();
|
||||
this.onStart(this, done);
|
||||
}
|
||||
};
|
||||
|
||||
const complete = {
|
||||
fn: done => {
|
||||
if (this.autoCleanClosures) {
|
||||
this.queueableFn.fn = null;
|
||||
}
|
||||
this.result.status = this.status(excluded, failSpecWithNoExp);
|
||||
this.result.duration = this.timer.elapsed();
|
||||
|
||||
if (this.result.status !== 'failed') {
|
||||
this.result.debugLogs = null;
|
||||
}
|
||||
|
||||
this.resultCallback(this.result, done);
|
||||
},
|
||||
type: 'specCleanup'
|
||||
};
|
||||
|
||||
const fns = this.beforeAndAfterFns();
|
||||
|
||||
const runnerConfig = {
|
||||
isLeaf: true,
|
||||
queueableFns: [...fns.befores, this.queueableFn, ...fns.afters],
|
||||
onException: e => this.handleException(e),
|
||||
onMultipleDone: () => {
|
||||
// Issue a deprecation. Include the context ourselves and pass
|
||||
// ignoreRunnable: true, since getting here always means that we've already
|
||||
// moved on and the current runnable isn't the one that caused the problem.
|
||||
this.onLateError(
|
||||
new Error(
|
||||
'An asynchronous spec, beforeEach, or afterEach function called its ' +
|
||||
"'done' callback more than once.\n(in spec: " +
|
||||
this.getFullName() +
|
||||
')'
|
||||
)
|
||||
);
|
||||
},
|
||||
onComplete: () => {
|
||||
if (this.result.status === 'failed') {
|
||||
onComplete(new j$.StopExecutionError('spec failed'));
|
||||
if (this.reportedDone) {
|
||||
this.onLateError(expectationResult);
|
||||
} else {
|
||||
onComplete();
|
||||
}
|
||||
},
|
||||
userContext: this.userContext(),
|
||||
runnableName: this.getFullName.bind(this)
|
||||
};
|
||||
this.result.failedExpectations.push(expectationResult);
|
||||
|
||||
if (this.markedPending || excluded === true) {
|
||||
runnerConfig.queueableFns = [];
|
||||
// TODO: refactor so that we don't need to override cached status
|
||||
if (this.result.status) {
|
||||
this.result.status = 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
if (this.#throwOnExpectationFailure && !isError) {
|
||||
throw new j$.errors.ExpectationFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runnerConfig.queueableFns.unshift(onStart);
|
||||
runnerConfig.queueableFns.push(complete);
|
||||
getSpecProperty(key) {
|
||||
this.result.properties = this.result.properties || {};
|
||||
return this.result.properties[key];
|
||||
}
|
||||
|
||||
queueRunnerFactory(runnerConfig);
|
||||
};
|
||||
setSpecProperty(key, value) {
|
||||
this.result.properties = this.result.properties || {};
|
||||
this.result.properties[key] = value;
|
||||
}
|
||||
|
||||
executionStarted() {
|
||||
this.#timer.start();
|
||||
}
|
||||
|
||||
executionFinished(excluded, failSpecWithNoExp) {
|
||||
if (this.#autoCleanClosures) {
|
||||
this.queueableFn.fn = null;
|
||||
}
|
||||
|
||||
this.result.status = this.#status(excluded, failSpecWithNoExp);
|
||||
this.result.duration = this.#timer.elapsed();
|
||||
|
||||
if (this.result.status !== 'failed') {
|
||||
this.result.debugLogs = null;
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
/**
|
||||
* @typedef SpecResult
|
||||
* @property {String} id - The unique id of this spec.
|
||||
* @property {String} description - The description passed to the {@link it} that created this spec.
|
||||
* @property {String} fullName - The full description including all ancestors of this spec.
|
||||
* @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe().
|
||||
* @property {String} filename - The name of the file the spec was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `it`/`fit`/`xit` have been replaced with versions that don't maintain the
|
||||
* same call stack height as the originals. You can fix that by setting
|
||||
* {@link Configuration#extraItStackFrames}.
|
||||
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec.
|
||||
* @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec.
|
||||
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec.
|
||||
* @property {String} pendingReason - If the spec is {@link pending}, this will be the reason.
|
||||
* @property {String} status - Once the spec has completed, this string represents the pass/fail status of this spec.
|
||||
* @property {number} duration - The time in ms used by the spec execution, including any before/afterEach.
|
||||
* @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSpecProperty}
|
||||
* @property {DebugLogEntry[]|null} debugLogs - Messages, if any, that were logged using {@link jasmine.debugLog} during a failing spec.
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.result = {
|
||||
id: this.id,
|
||||
description: this.description,
|
||||
fullName: this.getFullName(),
|
||||
parentSuiteId: this.parentSuiteId,
|
||||
filename: this.filename,
|
||||
failedExpectations: [],
|
||||
passedExpectations: [],
|
||||
deprecationWarnings: [],
|
||||
pendingReason: this.excludeMessage || '',
|
||||
duration: null,
|
||||
properties: null,
|
||||
debugLogs: null
|
||||
};
|
||||
this.markedPending = this.markedExcluding;
|
||||
this.reportedDone = false;
|
||||
}
|
||||
|
||||
handleException(e) {
|
||||
if (Spec.isPendingSpecException(e)) {
|
||||
this.pend(extractCustomPendingMessage(e));
|
||||
return;
|
||||
}
|
||||
|
||||
if (e instanceof j$.errors.ExpectationFailed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addExpectationResult(
|
||||
false,
|
||||
{
|
||||
matcherName: '',
|
||||
passed: false,
|
||||
expected: '',
|
||||
actual: '',
|
||||
error: e
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
pend(message) {
|
||||
this.markedPending = true;
|
||||
if (message) {
|
||||
this.result.pendingReason = message;
|
||||
}
|
||||
}
|
||||
|
||||
// Like pend(), but pending state will survive reset().
|
||||
// Useful for fit, xit, where pending state remains.
|
||||
exclude(message) {
|
||||
this.markedExcluding = true;
|
||||
if (this.message) {
|
||||
this.excludeMessage = message;
|
||||
}
|
||||
this.pend(message);
|
||||
}
|
||||
|
||||
// TODO: ensure that all access to result goes through .getResult()
|
||||
// so that the status is correct.
|
||||
getResult() {
|
||||
this.result.status = this.#status();
|
||||
return this.result;
|
||||
}
|
||||
|
||||
#status(excluded, failSpecWithNoExpectations) {
|
||||
if (excluded === true) {
|
||||
return 'excluded';
|
||||
}
|
||||
|
||||
if (this.markedPending) {
|
||||
return 'pending';
|
||||
}
|
||||
|
||||
if (
|
||||
this.result.failedExpectations.length > 0 ||
|
||||
(failSpecWithNoExpectations &&
|
||||
this.result.failedExpectations.length +
|
||||
this.result.passedExpectations.length ===
|
||||
0)
|
||||
) {
|
||||
return 'failed';
|
||||
}
|
||||
|
||||
return 'passed';
|
||||
}
|
||||
|
||||
getFullName() {
|
||||
return this.getPath().join(' ');
|
||||
}
|
||||
|
||||
addDeprecationWarning(deprecation) {
|
||||
if (typeof deprecation === 'string') {
|
||||
deprecation = { message: deprecation };
|
||||
}
|
||||
this.result.deprecationWarnings.push(
|
||||
j$.buildExpectationResult(deprecation)
|
||||
);
|
||||
}
|
||||
|
||||
debugLog(msg) {
|
||||
if (!this.result.debugLogs) {
|
||||
this.result.debugLogs = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef DebugLogEntry
|
||||
* @property {String} message - The message that was passed to {@link jasmine.debugLog}.
|
||||
* @property {number} timestamp - The time when the entry was added, in
|
||||
* milliseconds from the spec's start time
|
||||
*/
|
||||
this.result.debugLogs.push({
|
||||
message: msg,
|
||||
timestamp: this.#timer.elapsed()
|
||||
});
|
||||
}
|
||||
|
||||
Spec.prototype.reset = function() {
|
||||
/**
|
||||
* @typedef SpecResult
|
||||
* @property {String} id - The unique id of this spec.
|
||||
* @property {String} description - The description passed to the {@link it} that created this spec.
|
||||
* @property {String} fullName - The full description including all ancestors of this spec.
|
||||
* @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe().
|
||||
* @property {String} filename - The name of the file the spec was defined in.
|
||||
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec.
|
||||
* @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec.
|
||||
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec.
|
||||
* @property {String} pendingReason - If the spec is {@link pending}, this will be the reason.
|
||||
* @property {String} status - Once the spec has completed, this string represents the pass/fail status of this spec.
|
||||
* @property {number} duration - The time in ms used by the spec execution, including any before/afterEach.
|
||||
* @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSpecProperty}
|
||||
* @property {DebugLogEntry[]|null} debugLogs - Messages, if any, that were logged using {@link jasmine.debugLog} during a failing spec.
|
||||
* @interface Spec
|
||||
* @see Configuration#specFilter
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.result = {
|
||||
id: this.id,
|
||||
description: this.description,
|
||||
fullName: this.getFullName(),
|
||||
parentSuiteId: this.parentSuiteId,
|
||||
filename: this.filename,
|
||||
failedExpectations: [],
|
||||
passedExpectations: [],
|
||||
deprecationWarnings: [],
|
||||
pendingReason: this.excludeMessage || '',
|
||||
duration: null,
|
||||
properties: null,
|
||||
debugLogs: null
|
||||
};
|
||||
this.markedPending = this.markedExcluding;
|
||||
this.reportedDone = false;
|
||||
};
|
||||
|
||||
Spec.prototype.handleException = function handleException(e) {
|
||||
if (Spec.isPendingSpecException(e)) {
|
||||
this.pend(extractCustomPendingMessage(e));
|
||||
return;
|
||||
}
|
||||
|
||||
if (e instanceof j$.errors.ExpectationFailed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addExpectationResult(
|
||||
false,
|
||||
{
|
||||
matcherName: '',
|
||||
passed: false,
|
||||
expected: '',
|
||||
actual: '',
|
||||
error: e
|
||||
},
|
||||
true
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* Marks state as pending
|
||||
* @param {string} [message] An optional reason message
|
||||
*/
|
||||
Spec.prototype.pend = function(message) {
|
||||
this.markedPending = true;
|
||||
if (message) {
|
||||
this.result.pendingReason = message;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Like {@link Spec#pend}, but pending state will survive {@link Spec#reset}
|
||||
* Useful for fit, xit, where pending state remains.
|
||||
* @param {string} [message] An optional reason message
|
||||
*/
|
||||
Spec.prototype.exclude = function(message) {
|
||||
this.markedExcluding = true;
|
||||
if (this.message) {
|
||||
this.excludeMessage = message;
|
||||
}
|
||||
this.pend(message);
|
||||
};
|
||||
|
||||
Spec.prototype.getResult = function() {
|
||||
this.result.status = this.status();
|
||||
return this.result;
|
||||
};
|
||||
|
||||
Spec.prototype.status = function(excluded, failSpecWithNoExpectations) {
|
||||
if (excluded === true) {
|
||||
return 'excluded';
|
||||
}
|
||||
|
||||
if (this.markedPending) {
|
||||
return 'pending';
|
||||
}
|
||||
|
||||
if (
|
||||
this.result.failedExpectations.length > 0 ||
|
||||
(failSpecWithNoExpectations &&
|
||||
this.result.failedExpectations.length +
|
||||
this.result.passedExpectations.length ===
|
||||
0)
|
||||
) {
|
||||
return 'failed';
|
||||
}
|
||||
|
||||
return 'passed';
|
||||
};
|
||||
|
||||
Spec.prototype.getFullName = function() {
|
||||
return this.getSpecName(this);
|
||||
};
|
||||
|
||||
Spec.prototype.addDeprecationWarning = function(deprecation) {
|
||||
if (typeof deprecation === 'string') {
|
||||
deprecation = { message: deprecation };
|
||||
}
|
||||
this.result.deprecationWarnings.push(
|
||||
j$.buildExpectationResult(deprecation)
|
||||
);
|
||||
};
|
||||
|
||||
Spec.prototype.debugLog = function(msg) {
|
||||
if (!this.result.debugLogs) {
|
||||
this.result.debugLogs = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef DebugLogEntry
|
||||
* @property {String} message - The message that was passed to {@link jasmine.debugLog}.
|
||||
* @property {number} timestamp - The time when the entry was added, in
|
||||
* milliseconds from the spec's start time
|
||||
*/
|
||||
this.result.debugLogs.push({
|
||||
message: msg,
|
||||
timestamp: this.timer.elapsed()
|
||||
});
|
||||
};
|
||||
|
||||
const extractCustomPendingMessage = function(e) {
|
||||
const fullMessage = e.toString(),
|
||||
boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage),
|
||||
boilerplateEnd =
|
||||
boilerplateStart + Spec.pendingSpecExceptionMessage.length;
|
||||
|
||||
return fullMessage.slice(boilerplateEnd);
|
||||
};
|
||||
|
||||
Spec.pendingSpecExceptionMessage = '=> marked Pending';
|
||||
|
||||
Spec.isPendingSpecException = function(e) {
|
||||
return !!(
|
||||
e &&
|
||||
e.toString &&
|
||||
e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @interface Spec
|
||||
* @see Configuration#specFilter
|
||||
* @since 2.0.0
|
||||
*/
|
||||
Object.defineProperty(Spec.prototype, 'metadata', {
|
||||
get: function() {
|
||||
if (!this.metadata_) {
|
||||
this.metadata_ = {
|
||||
get metadata() {
|
||||
// NOTE: Although most of jasmine-core only exposes these metadata objects,
|
||||
// actual Spec instances are still passed to Configuration#specFilter. Until
|
||||
// that is fixed, it's important to make sure that all metadata properties
|
||||
// also exist in compatible form on the underlying Spec.
|
||||
if (!this.#metadata) {
|
||||
this.#metadata = {
|
||||
/**
|
||||
* The unique ID of this spec.
|
||||
* @name Spec#id
|
||||
@@ -333,13 +268,54 @@ 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),
|
||||
|
||||
/**
|
||||
* The name of the file the spec was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `it`/`fit`/`xit` have been replaced with versions that don't maintain the
|
||||
* same call stack height as the originals. You can fix that by setting
|
||||
* {@link Configuration#extraItStackFrames}.
|
||||
* @name Spec#filename
|
||||
* @readonly
|
||||
* @type {string}
|
||||
* @since 5.13.0
|
||||
*/
|
||||
filename: this.filename
|
||||
};
|
||||
}
|
||||
|
||||
return this.metadata_;
|
||||
return this.#metadata;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const extractCustomPendingMessage = function(e) {
|
||||
const fullMessage = e.toString(),
|
||||
boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage),
|
||||
boilerplateEnd =
|
||||
boilerplateStart + Spec.pendingSpecExceptionMessage.length;
|
||||
|
||||
return fullMessage.slice(boilerplateEnd);
|
||||
};
|
||||
|
||||
Spec.pendingSpecExceptionMessage = '=> marked Pending';
|
||||
|
||||
Spec.isPendingSpecException = function(e) {
|
||||
return !!(
|
||||
e &&
|
||||
e.toString &&
|
||||
e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1
|
||||
);
|
||||
};
|
||||
|
||||
return Spec;
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -54,7 +54,9 @@ getJasmineRequireObj().SpyFactory = function(j$) {
|
||||
}
|
||||
|
||||
if (methods.length === 0 && properties.length === 0) {
|
||||
throw 'createSpyObj requires a non-empty array or object of method names to create spies for';
|
||||
throw new Error(
|
||||
'createSpyObj requires a non-empty array or object of method names to create spies for'
|
||||
);
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,86 +1,275 @@
|
||||
getJasmineRequireObj().Suite = function(j$) {
|
||||
function Suite(attrs) {
|
||||
this.env = attrs.env;
|
||||
this.id = attrs.id;
|
||||
this.parentSuite = attrs.parentSuite;
|
||||
this.description = attrs.description;
|
||||
this.reportedParentSuiteId = attrs.reportedParentSuiteId;
|
||||
this.filename = attrs.filename;
|
||||
this.expectationFactory = attrs.expectationFactory;
|
||||
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
|
||||
this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
|
||||
this.autoCleanClosures =
|
||||
attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures;
|
||||
this.onLateError = attrs.onLateError || function() {};
|
||||
class Suite {
|
||||
#reportedParentSuiteId;
|
||||
#throwOnExpectationFailure;
|
||||
#autoCleanClosures;
|
||||
#timer;
|
||||
|
||||
this.beforeFns = [];
|
||||
this.afterFns = [];
|
||||
this.beforeAllFns = [];
|
||||
this.afterAllFns = [];
|
||||
this.timer = attrs.timer || new j$.Timer();
|
||||
this.children = [];
|
||||
constructor(attrs) {
|
||||
this.id = attrs.id;
|
||||
this.parentSuite = attrs.parentSuite;
|
||||
this.description = attrs.description;
|
||||
this.filename = attrs.filename;
|
||||
this.expectationFactory = attrs.expectationFactory;
|
||||
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
|
||||
this.onLateError = attrs.onLateError || function() {};
|
||||
this.#reportedParentSuiteId = attrs.reportedParentSuiteId;
|
||||
this.#throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
|
||||
this.#autoCleanClosures =
|
||||
attrs.autoCleanClosures === undefined
|
||||
? true
|
||||
: !!attrs.autoCleanClosures;
|
||||
this.#timer = attrs.timer || new j$.Timer();
|
||||
|
||||
this.reset();
|
||||
}
|
||||
this.beforeFns = [];
|
||||
this.afterFns = [];
|
||||
this.beforeAllFns = [];
|
||||
this.afterAllFns = [];
|
||||
this.children = [];
|
||||
|
||||
Suite.prototype.setSuiteProperty = function(key, value) {
|
||||
this.result.properties = this.result.properties || {};
|
||||
this.result.properties[key] = value;
|
||||
};
|
||||
this.reset();
|
||||
}
|
||||
|
||||
Suite.prototype.getFullName = function() {
|
||||
const fullName = [];
|
||||
for (
|
||||
let parentSuite = this;
|
||||
parentSuite;
|
||||
parentSuite = parentSuite.parentSuite
|
||||
) {
|
||||
if (parentSuite.parentSuite) {
|
||||
fullName.unshift(parentSuite.description);
|
||||
setSuiteProperty(key, value) {
|
||||
this.result.properties = this.result.properties || {};
|
||||
this.result.properties[key] = value;
|
||||
}
|
||||
|
||||
getFullName() {
|
||||
const fullName = [];
|
||||
for (
|
||||
let parentSuite = this;
|
||||
parentSuite;
|
||||
parentSuite = parentSuite.parentSuite
|
||||
) {
|
||||
if (parentSuite.parentSuite) {
|
||||
fullName.unshift(parentSuite.description);
|
||||
}
|
||||
}
|
||||
return fullName.join(' ');
|
||||
}
|
||||
|
||||
// Mark the suite with "pending" status
|
||||
pend() {
|
||||
this.markedPending = true;
|
||||
}
|
||||
|
||||
// Like pend(), but pending state will survive reset().
|
||||
// Useful for fdescribe, xdescribe, where pending state should remain.
|
||||
exclude() {
|
||||
this.pend();
|
||||
this.markedExcluding = true;
|
||||
}
|
||||
|
||||
beforeEach(fn) {
|
||||
this.beforeFns.unshift({ ...fn, suite: this });
|
||||
}
|
||||
|
||||
beforeAll(fn) {
|
||||
this.beforeAllFns.push({ ...fn, type: 'beforeAll', suite: this });
|
||||
}
|
||||
|
||||
afterEach(fn) {
|
||||
this.afterFns.unshift({ ...fn, suite: this, type: 'afterEach' });
|
||||
}
|
||||
|
||||
afterAll(fn) {
|
||||
this.afterAllFns.unshift({ ...fn, type: 'afterAll' });
|
||||
}
|
||||
|
||||
startTimer() {
|
||||
this.#timer.start();
|
||||
}
|
||||
|
||||
endTimer() {
|
||||
this.result.duration = this.#timer.elapsed();
|
||||
}
|
||||
|
||||
cleanupBeforeAfter() {
|
||||
if (this.#autoCleanClosures) {
|
||||
removeFns(this.beforeAllFns);
|
||||
removeFns(this.afterAllFns);
|
||||
removeFns(this.beforeFns);
|
||||
removeFns(this.afterFns);
|
||||
}
|
||||
}
|
||||
return fullName.join(' ');
|
||||
};
|
||||
|
||||
/*
|
||||
* Mark the suite with "pending" status
|
||||
*/
|
||||
Suite.prototype.pend = function() {
|
||||
this.markedPending = true;
|
||||
};
|
||||
reset() {
|
||||
/**
|
||||
* @typedef SuiteResult
|
||||
* @property {String} id - The unique id of this suite.
|
||||
* @property {String} description - The description text passed to the {@link describe} that made this suite.
|
||||
* @property {String} fullName - The full description including all ancestors of this suite.
|
||||
* @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe().
|
||||
* @property {String} filename - The name of the file the suite was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions that
|
||||
* don't maintain the same call stack height as the originals. You can fix
|
||||
* that by setting {@link Configuration#extraDescribeStackFrames}.
|
||||
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
|
||||
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite.
|
||||
* @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite.
|
||||
* @property {number} duration - The time in ms for Suite execution, including any before/afterAll, before/afterEach.
|
||||
* @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSuiteProperty}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.result = {
|
||||
id: this.id,
|
||||
description: this.description,
|
||||
fullName: this.getFullName(),
|
||||
parentSuiteId: this.#reportedParentSuiteId,
|
||||
filename: this.filename,
|
||||
failedExpectations: [],
|
||||
deprecationWarnings: [],
|
||||
duration: null,
|
||||
properties: null
|
||||
};
|
||||
this.markedPending = this.markedExcluding;
|
||||
this.children.forEach(function(child) {
|
||||
child.reset();
|
||||
});
|
||||
this.reportedDone = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Like {@link Suite#pend}, but pending state will survive {@link Spec#reset}
|
||||
* Useful for fdescribe, xdescribe, where pending state should remain.
|
||||
*/
|
||||
Suite.prototype.exclude = function() {
|
||||
this.pend();
|
||||
this.markedExcluding = true;
|
||||
};
|
||||
removeChildren() {
|
||||
this.children = [];
|
||||
}
|
||||
|
||||
Suite.prototype.beforeEach = function(fn) {
|
||||
this.beforeFns.unshift({ ...fn, suite: this });
|
||||
};
|
||||
addChild(child) {
|
||||
this.children.push(child);
|
||||
}
|
||||
|
||||
Suite.prototype.beforeAll = function(fn) {
|
||||
this.beforeAllFns.push({ ...fn, type: 'beforeAll', suite: this });
|
||||
};
|
||||
#status() {
|
||||
if (this.markedPending) {
|
||||
return 'pending';
|
||||
}
|
||||
|
||||
Suite.prototype.afterEach = function(fn) {
|
||||
this.afterFns.unshift({ ...fn, suite: this, type: 'afterEach' });
|
||||
};
|
||||
if (this.result.failedExpectations.length > 0) {
|
||||
return 'failed';
|
||||
} else {
|
||||
return 'passed';
|
||||
}
|
||||
}
|
||||
|
||||
Suite.prototype.afterAll = function(fn) {
|
||||
this.afterAllFns.unshift({ ...fn, type: 'afterAll' });
|
||||
};
|
||||
getResult() {
|
||||
this.result.status = this.#status();
|
||||
return this.result;
|
||||
}
|
||||
|
||||
Suite.prototype.startTimer = function() {
|
||||
this.timer.start();
|
||||
};
|
||||
canBeReentered() {
|
||||
return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
|
||||
}
|
||||
|
||||
Suite.prototype.endTimer = function() {
|
||||
this.result.duration = this.timer.elapsed();
|
||||
};
|
||||
sharedUserContext() {
|
||||
if (!this.sharedContext) {
|
||||
this.sharedContext = this.parentSuite
|
||||
? this.parentSuite.clonedSharedUserContext()
|
||||
: new j$.UserContext();
|
||||
}
|
||||
|
||||
return this.sharedContext;
|
||||
}
|
||||
|
||||
clonedSharedUserContext() {
|
||||
return j$.UserContext.fromExisting(this.sharedUserContext());
|
||||
}
|
||||
|
||||
handleException() {
|
||||
if (arguments[0] instanceof j$.errors.ExpectationFailed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
matcherName: '',
|
||||
passed: false,
|
||||
expected: '',
|
||||
actual: '',
|
||||
error: arguments[0]
|
||||
};
|
||||
const failedExpectation = j$.buildExpectationResult(data);
|
||||
|
||||
if (!this.parentSuite) {
|
||||
failedExpectation.globalErrorType = 'afterAll';
|
||||
}
|
||||
|
||||
if (this.reportedDone) {
|
||||
this.onLateError(failedExpectation);
|
||||
} else {
|
||||
this.result.failedExpectations.push(failedExpectation);
|
||||
}
|
||||
}
|
||||
|
||||
onMultipleDone() {
|
||||
let msg;
|
||||
|
||||
// Issue an error. Include the context ourselves and pass
|
||||
// ignoreRunnable: true, since getting here always means that we've already
|
||||
// moved on and the current runnable isn't the one that caused the problem.
|
||||
if (this.parentSuite) {
|
||||
msg =
|
||||
"An asynchronous beforeAll or afterAll function called its 'done' " +
|
||||
'callback more than once.\n' +
|
||||
'(in suite: ' +
|
||||
this.getFullName() +
|
||||
')';
|
||||
} else {
|
||||
msg =
|
||||
'A top-level beforeAll or afterAll function called its ' +
|
||||
"'done' callback more than once.";
|
||||
}
|
||||
|
||||
this.onLateError(new Error(msg));
|
||||
}
|
||||
|
||||
addExpectationResult() {
|
||||
if (isFailure(arguments)) {
|
||||
const data = arguments[1];
|
||||
const expectationResult = j$.buildExpectationResult(data);
|
||||
|
||||
if (this.reportedDone) {
|
||||
this.onLateError(expectationResult);
|
||||
} else {
|
||||
this.result.failedExpectations.push(expectationResult);
|
||||
|
||||
// TODO: refactor so that we don't need to override cached status
|
||||
if (this.result.status) {
|
||||
this.result.status = 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
if (this.#throwOnExpectationFailure) {
|
||||
throw new j$.errors.ExpectationFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addDeprecationWarning(deprecation) {
|
||||
if (typeof deprecation === 'string') {
|
||||
deprecation = { message: deprecation };
|
||||
}
|
||||
this.result.deprecationWarnings.push(
|
||||
j$.buildExpectationResult(deprecation)
|
||||
);
|
||||
}
|
||||
|
||||
hasChildWithDescription(description) {
|
||||
for (const child of this.children) {
|
||||
if (child.description === description) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get metadata() {
|
||||
if (!this.metadata_) {
|
||||
this.metadata_ = new SuiteMetadata(this);
|
||||
}
|
||||
|
||||
return this.metadata_;
|
||||
}
|
||||
}
|
||||
|
||||
function removeFns(queueableFns) {
|
||||
for (const qf of queueableFns) {
|
||||
@@ -88,245 +277,77 @@ getJasmineRequireObj().Suite = function(j$) {
|
||||
}
|
||||
}
|
||||
|
||||
Suite.prototype.cleanupBeforeAfter = function() {
|
||||
if (this.autoCleanClosures) {
|
||||
removeFns(this.beforeAllFns);
|
||||
removeFns(this.afterAllFns);
|
||||
removeFns(this.beforeFns);
|
||||
removeFns(this.afterFns);
|
||||
}
|
||||
};
|
||||
|
||||
Suite.prototype.reset = function() {
|
||||
/**
|
||||
* @typedef SuiteResult
|
||||
* @property {String} id - The unique id of this suite.
|
||||
* @property {String} description - The description text passed to the {@link describe} that made this suite.
|
||||
* @property {String} fullName - The full description including all ancestors of this suite.
|
||||
* @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe().
|
||||
* @property {String} filename - The name of the file the suite was defined in.
|
||||
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
|
||||
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite.
|
||||
* @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite.
|
||||
* @property {number} duration - The time in ms for Suite execution, including any before/afterAll, before/afterEach.
|
||||
* @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSuiteProperty}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.result = {
|
||||
id: this.id,
|
||||
description: this.description,
|
||||
fullName: this.getFullName(),
|
||||
parentSuiteId: this.reportedParentSuiteId,
|
||||
filename: this.filename,
|
||||
failedExpectations: [],
|
||||
deprecationWarnings: [],
|
||||
duration: null,
|
||||
properties: null
|
||||
};
|
||||
this.markedPending = this.markedExcluding;
|
||||
this.children.forEach(function(child) {
|
||||
child.reset();
|
||||
});
|
||||
this.reportedDone = false;
|
||||
};
|
||||
|
||||
Suite.prototype.removeChildren = function() {
|
||||
this.children = [];
|
||||
};
|
||||
|
||||
Suite.prototype.addChild = function(child) {
|
||||
this.children.push(child);
|
||||
};
|
||||
|
||||
Suite.prototype.status = function() {
|
||||
if (this.markedPending) {
|
||||
return 'pending';
|
||||
}
|
||||
|
||||
if (this.result.failedExpectations.length > 0) {
|
||||
return 'failed';
|
||||
} else {
|
||||
return 'passed';
|
||||
}
|
||||
};
|
||||
|
||||
Suite.prototype.canBeReentered = function() {
|
||||
return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
|
||||
};
|
||||
|
||||
Suite.prototype.getResult = function() {
|
||||
this.result.status = this.status();
|
||||
return this.result;
|
||||
};
|
||||
|
||||
Suite.prototype.sharedUserContext = function() {
|
||||
if (!this.sharedContext) {
|
||||
this.sharedContext = this.parentSuite
|
||||
? this.parentSuite.clonedSharedUserContext()
|
||||
: new j$.UserContext();
|
||||
}
|
||||
|
||||
return this.sharedContext;
|
||||
};
|
||||
|
||||
Suite.prototype.clonedSharedUserContext = function() {
|
||||
return j$.UserContext.fromExisting(this.sharedUserContext());
|
||||
};
|
||||
|
||||
Suite.prototype.handleException = function() {
|
||||
if (arguments[0] instanceof j$.errors.ExpectationFailed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
matcherName: '',
|
||||
passed: false,
|
||||
expected: '',
|
||||
actual: '',
|
||||
error: arguments[0]
|
||||
};
|
||||
const failedExpectation = j$.buildExpectationResult(data);
|
||||
|
||||
if (!this.parentSuite) {
|
||||
failedExpectation.globalErrorType = 'afterAll';
|
||||
}
|
||||
|
||||
if (this.reportedDone) {
|
||||
this.onLateError(failedExpectation);
|
||||
} else {
|
||||
this.result.failedExpectations.push(failedExpectation);
|
||||
}
|
||||
};
|
||||
|
||||
Suite.prototype.onMultipleDone = function() {
|
||||
let msg;
|
||||
|
||||
// Issue a deprecation. Include the context ourselves and pass
|
||||
// ignoreRunnable: true, since getting here always means that we've already
|
||||
// moved on and the current runnable isn't the one that caused the problem.
|
||||
if (this.parentSuite) {
|
||||
msg =
|
||||
"An asynchronous beforeAll or afterAll function called its 'done' " +
|
||||
'callback more than once.\n' +
|
||||
'(in suite: ' +
|
||||
this.getFullName() +
|
||||
')';
|
||||
} else {
|
||||
msg =
|
||||
'A top-level beforeAll or afterAll function called its ' +
|
||||
"'done' callback more than once.";
|
||||
}
|
||||
|
||||
this.onLateError(new Error(msg));
|
||||
};
|
||||
|
||||
Suite.prototype.addExpectationResult = function() {
|
||||
if (isFailure(arguments)) {
|
||||
const data = arguments[1];
|
||||
const expectationResult = j$.buildExpectationResult(data);
|
||||
|
||||
if (this.reportedDone) {
|
||||
this.onLateError(expectationResult);
|
||||
} else {
|
||||
this.result.failedExpectations.push(expectationResult);
|
||||
|
||||
// TODO: refactor so that we don't need to override cached status
|
||||
if (this.result.status) {
|
||||
this.result.status = 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
if (this.throwOnExpectationFailure) {
|
||||
throw new j$.errors.ExpectationFailed();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Suite.prototype.addDeprecationWarning = function(deprecation) {
|
||||
if (typeof deprecation === 'string') {
|
||||
deprecation = { message: deprecation };
|
||||
}
|
||||
this.result.deprecationWarnings.push(
|
||||
j$.buildExpectationResult(deprecation)
|
||||
);
|
||||
};
|
||||
|
||||
Suite.prototype.hasChildWithDescription = function(description) {
|
||||
for (const child of this.children) {
|
||||
if (child.description === description) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
Object.defineProperty(Suite.prototype, 'metadata', {
|
||||
get: function() {
|
||||
if (!this.metadata_) {
|
||||
this.metadata_ = new SuiteMetadata(this);
|
||||
}
|
||||
|
||||
return this.metadata_;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @interface Suite
|
||||
* @see Env#topSuite
|
||||
* @since 2.0.0
|
||||
*/
|
||||
function SuiteMetadata(suite) {
|
||||
this.suite_ = suite;
|
||||
/**
|
||||
* The unique ID of this suite.
|
||||
* @name Suite#id
|
||||
* @readonly
|
||||
* @type {string}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.id = suite.id;
|
||||
class SuiteMetadata {
|
||||
#suite;
|
||||
|
||||
/**
|
||||
* The parent of this suite, or null if this is the top suite.
|
||||
* @name Suite#parentSuite
|
||||
* @readonly
|
||||
* @type {Suite}
|
||||
*/
|
||||
this.parentSuite = suite.parentSuite ? suite.parentSuite.metadata : null;
|
||||
constructor(suite) {
|
||||
this.#suite = suite;
|
||||
/**
|
||||
* The unique ID of this suite.
|
||||
* @name Suite#id
|
||||
* @readonly
|
||||
* @type {string}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.id = suite.id;
|
||||
|
||||
/**
|
||||
* The description passed to the {@link describe} that created this suite.
|
||||
* @name Suite#description
|
||||
* @readonly
|
||||
* @type {string}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.description = suite.description;
|
||||
}
|
||||
/**
|
||||
* The parent of this suite, or null if this is the top suite.
|
||||
* @name Suite#parentSuite
|
||||
* @readonly
|
||||
* @type {Suite}
|
||||
*/
|
||||
this.parentSuite = suite.parentSuite ? suite.parentSuite.metadata : null;
|
||||
|
||||
/**
|
||||
* The full description including all ancestors of this suite.
|
||||
* @name Suite#getFullName
|
||||
* @function
|
||||
* @returns {string}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
SuiteMetadata.prototype.getFullName = function() {
|
||||
return this.suite_.getFullName();
|
||||
};
|
||||
/**
|
||||
* The description passed to the {@link describe} that created this suite.
|
||||
* @name Suite#description
|
||||
* @readonly
|
||||
* @type {string}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.description = suite.description;
|
||||
|
||||
/**
|
||||
* The suite's children.
|
||||
* @name Suite#children
|
||||
* @type {Array.<(Spec|Suite)>}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
Object.defineProperty(SuiteMetadata.prototype, 'children', {
|
||||
get: function() {
|
||||
return this.suite_.children.map(child => child.metadata);
|
||||
/**
|
||||
* The name of the file the suite was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions
|
||||
* that don't maintain the same call stack height as the originals. You
|
||||
* can fix that by setting {@link Configuration#extraItStackFrames}.
|
||||
* @name Suite#filename
|
||||
* @readonly
|
||||
* @type {string}
|
||||
* @since 5.13.0
|
||||
*/
|
||||
this.filename = suite.filename;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The full description including all ancestors of this suite.
|
||||
* @name Suite#getFullName
|
||||
* @function
|
||||
* @returns {string}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
getFullName() {
|
||||
return this.#suite.getFullName();
|
||||
}
|
||||
|
||||
/**
|
||||
* The suite's children.
|
||||
* @name Suite#children
|
||||
* @type {Array.<(Spec|Suite)>}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
get children() {
|
||||
return this.#suite.children.map(child => child.metadata);
|
||||
}
|
||||
}
|
||||
|
||||
function isFailure(args) {
|
||||
return !args[0];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user