Compare commits

...

86 Commits

Author SHA1 Message Date
Steve Gravrock
d5872bba66 Fixed Safari footnotes in release notes 2025-02-08 11:13:28 -08:00
Steve Gravrock
c4f4edda1b Bump version to 5.6.0 2025-02-08 11:10:06 -08:00
Steve Gravrock
cf057b6631 Fixed parse error from jsdoc
"arguments" isn't a legal argument name in strict mode JS. The JS
runtimes that Jasmine runs in allow it, but jsdoc doesn't.
2025-02-08 10:25:41 -08:00
Steve Gravrock
2a7a157713 toHaveNoOtherSpyInteractions message tweaks 2025-01-20 11:31:47 -08:00
Steve Gravrock
7463fe511b Match messages exactly in toHaveNoOtherSpyInteractions specs 2025-01-20 11:31:18 -08:00
Steve Gravrock
1b724daa10 Merge branch 'Eradev-issue-1991'
* Merges #2051 from @Eradev
* Fixes #1991
2025-01-20 11:31:06 -08:00
Eradev
888d3b6250 Modified error message 2025-01-18 17:55:00 -05:00
Eradev
289afbf0a6 Remove jsdoc from unverifiedCount 2025-01-18 17:25:07 -05:00
Steve Gravrock
9b89bee4f5 Demote Safari to best-effort support 2025-01-18 11:31:17 -08:00
Eradev
3f8f488a58 Fix broken tests 2025-01-11 12:41:28 -05:00
Steve Gravrock
592d47e971 Update copyright date 2025-01-11 08:33:37 -08:00
Eradev
4732012f1c toHaveNoOtherSpyInteractions implementation 2025-01-10 21:05:12 -05:00
Steve Gravrock
7683325d68 Improved specs for async matcher error messages 2024-12-31 10:10:40 -08:00
Steve Gravrock
03d665e243 Merge branch 'improve_toBeRejectedWithError' of https://github.com/andiz2/jasmine 2024-12-31 10:07:53 -08:00
Andrei D
a1591da25d improved error msg on toBeRehectedWithError and all other built-in async matchers 2024-12-22 17:43:47 +02:00
Steve Gravrock
1f1e1209d2 Merge branch 'add_toBeNullish' of https://github.com/MattMcCherry/jasmine
* Merges #2045 from @MattMcCherry
2024-12-12 17:30:55 -08:00
Steve Gravrock
a389905a38 Merge branch 'feature/matcher-toHaveClasses' of https://github.com/aYorky/jasmine
* Merges #2046 from @aYorky
2024-12-11 19:19:58 -08:00
Matt McCherry
36dd6b07d1 add integration test for a falsy value 2024-12-10 11:39:44 +00:00
Matt McCherry
2f3689713b add tests for falsy values 2024-12-10 11:31:16 +00:00
Alex Yorkovich
819fab7b58 simplified match condition 2024-12-07 13:14:25 -06:00
Steve Gravrock
e5ca1f37f1 Use discussions for support requests, not issues 2024-12-07 09:32:57 -08:00
Alex Yorkovich
c3650ea7c7 updated release number 2024-12-04 12:34:36 -06:00
Alex Yorkovich
1805337424 Added new toHaveClasses matcher; tests included 2024-12-04 12:20:15 -06:00
Matt McCherry
27bb6ebac1 reset jasmine.js 2024-12-02 10:34:55 +00:00
Matt McCherry
580323c221 run prettier and fix tests 2024-12-02 10:34:55 +00:00
Matt McCherry
d9286c549f add integration test for toBeNullish 2024-12-02 10:34:55 +00:00
Matt McCherry
26dfa6d257 Add .toBeNullish matcher 2024-12-02 10:34:49 +00:00
Steve Gravrock
483d4ab3c3 Bump version to 5.5.0 2024-11-23 10:58:56 -08:00
Steve Gravrock
663dfe5932 Updated release instructions 2024-11-23 10:42:20 -08:00
Steve Gravrock
ce9c752899 Added debug logging to flaky test 2024-11-12 20:25:20 -08:00
Steve Gravrock
d5e7bc9fd6 Optionally enforce uniqueness of spec and suite names
This is off by default for backwards compatibility but can be enabled
by setting the forbidDuplicateNames env config property to true.

Fixes #1633.
2024-11-10 09:54:51 -08:00
Steve Gravrock
744e765d6f Update copyright date 2024-11-09 13:37:46 -08:00
Steve Gravrock
29551ba4f3 Prettier 2024-11-09 13:17:32 -08:00
Steve Gravrock
bd9a3b2305 Include property value mismatches in diffs even when there are missing or extra properties 2024-11-09 11:22:27 -08:00
Steve Gravrock
c8c3325b56 Bump version to 5.4.0 2024-10-12 10:31:35 -07:00
Steve Gravrock
84c7e2b21b Fixed de-duplication of exception messages containing blank lines on Node and Chrome
This is particularly helpful when reporting testing-library errors, which
have messages that contain blank lines and can be hundreds or even thousands
of lines long.
2024-10-07 20:04:07 -07:00
Steve Gravrock
dda25bb29e Removed references to PhantomJS from StackTraceSpec.js 2024-10-07 19:55:34 -07:00
Steve Gravrock
9ccf2ef96b Also deprecate the expected and actual properties of ThrowUnlessFailure 2024-10-05 13:55:49 -07:00
Steve Gravrock
c6fa55bfc8 Clarify support status of old Firefox ESRs 2024-10-05 13:46:49 -07:00
Steve Gravrock
06bcf1c2e1 Fixed broken docs link 2024-10-05 13:38:52 -07:00
Steve Gravrock
40f402d117 Deprecate the expected and actual properties of expectation results 2024-10-04 07:54:20 -07:00
Steve Gravrock
71f6a95ce5 Added Firefox 128 (current ESR) to supported browsers 2024-09-24 06:54:36 -07:00
Steve Gravrock
5cd7d47f72 Bump version to 5.3.0 2024-09-07 13:18:21 -07:00
Steve Gravrock
d0fe5c4712 Require curly braces around loop and conditonal bodies 2024-09-02 11:30:36 -07:00
Steve Gravrock
f602c4911c Merge branch 'dave-unclamp-safari' of https://github.com/dcsaszar/jasmine
* Significantly improves performance in Safari
* Merges #2040 from @dcsaszar
* Fixes #2008
2024-09-02 11:22:56 -07:00
Steve Gravrock
7aaf7eaf30 API reference for reporter capabilities 2024-09-02 10:53:56 -07:00
David Császár
35f16e8125 Add test for unclamping setTimeout in Safari 2024-09-01 10:22:16 +02:00
David Császár
acc777c267 Unclamp setTimeout in Safari
Fixes #2008

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

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

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

"Copyright (c) 2008-2019 Pivotal Labs" complies with the terms of the
license and acknowledges Pivotal's outsized role in developing Jasmine.
"Copyright (c) 2008-$YEAR The Jasmine developers" acknowledges all
authors and will remain correct in the future.
2023-10-14 08:55:48 -07:00
Steve Gravrock
96033e38ea Merge branch 'main' of https://github.com/jd-apprentice/jasmine
* Merges #2017 from @jd-apprentice
2023-10-07 10:34:06 -07:00
Steve Gravrock
ed75290ef7 Removed badges from README
The CI status badge mostly just shows whether Saucelabas was flaky
last night. Code Triage was a nice idea but it's attracted at most
one new contributor over 5.5 years.
2023-09-30 10:22:37 -07:00
Steve Gravrock
a14dbf012a Fix Chrome on CI 2023-09-30 08:33:27 -07:00
Jonathan
17c11ba7b9 fix: updated remaining files 2023-09-21 11:48:52 -03:00
Jonathan
2a1daca1ca chore: rename license file 2023-09-19 14:41:21 -03:00
Steve Gravrock
f0db5ce350 Added Firefox 115 (current ESR) to supported browsers 2023-09-07 21:43:24 -07:00
Steve Gravrock
39f9c2e1a0 Don't attach spec helpers to the env 2023-08-26 11:52:26 -07:00
102 changed files with 2310 additions and 483 deletions

View File

@@ -4,6 +4,10 @@
version: 2.1
executors:
node22:
docker:
- image: cimg/node:22.0.0
working_directory: ~/workspace
node20:
docker:
- image: cimg/node:20.0.0
@@ -94,12 +98,20 @@ workflows:
push:
jobs:
- build:
executor: node22
name: build_node_22
- build:
executor: node20
name: build_node_20
- build:
executor: node18
name: build_node_18
- test_node:
executor: node22
name: test_node_22
requires:
- build_node_22
- test_node:
executor: node20
name: test_node_20
@@ -115,6 +127,11 @@ workflows:
name: test_parallel_node_18
requires:
- build_node_18
- test_parallel:
executor: node22
name: test_parallel_node_22
requires:
- build_node_22
- test_parallel:
executor: node20
name: test_parallel_node_20

47
.eslintrc Normal file
View File

@@ -0,0 +1,47 @@
{
"extends": [
"plugin:compat/recommended"
],
"env": {
"browser": true,
"node": true,
"es2017": true
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"curly": "error",
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"no-unused-vars": [
"error",
{
"args": "none"
}
],
"no-implicit-globals": "error",
"block-spacing": "error",
"func-call-spacing": [
"error",
"never"
],
"key-spacing": "error",
"no-tabs": "error",
"no-trailing-spaces": "error",
"no-whitespace-before-property": "error",
"semi": [
"error",
"always"
],
"space-before-blocks": "error",
"no-eval": "error",
"no-var": "error",
"no-debugger": "error"
}
}

View File

@@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Questions, requests for help, etc
url: https://github.com/jasmine/jasmine/discussions/new/choose
about: Please start a discussion.
- name: Issues with the `jasmine` CLI
url: https://github.com/jasmine/jasmine-npm/issues
about: Please create issues related to the `jasmine` package in its repository.

View File

@@ -1,73 +0,0 @@
name: Question or Support Request
description: I need help using Jasmine
labels: ["question"]
body:
- type: markdown
attributes:
value: |
Jasmine is supported by volunteers working in their free time. Although we're generally willing to help, we're going to ask you to put in some effort first to help us help you.
## Troubleshooting
Please take the time to rule out problems with your code or third party libraries before opening an issue. If you're running into an error, try to determine whether the error is coming from Jasmine, another library, or your own code.
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 question has already been answered. Consider searching [Stack Overflow](https://stackoverflow.com/questions/tagged/jasmine) and past issues in this repository for related questions as well.
## 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:
* Are you trying to write a synchronous test for asynchronous code?
* Does the test signal completion before the code under test finishes?
* Do expectations run before the code that they're trying to verify?
## Consider asking Angular questions in an Angular forum
Questions like "how do I test this Angular service" are mostly about Angular, not Jasmine. You'll likely get better responses in an Angular forum. Here's a rule of thumb: If you can't demonstrate the problem without Angular, you probably have an Angular question.
## 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`).
## 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.
- type: textarea
id: question
attributes:
label: Your question
description: Clearly describe what you'd like help with.
validations:
required: true
- type: textarea
id: code-sample
attributes:
label: Example code that demonstrates the problem
description: Please include either a code snippet that demonstrates 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: input
id: jasmine-core-version
attributes:
label: jasmine-core version
validations:
required: true
- type: textarea
id: other-versions
attributes:
label: Versions of other relevant packages
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
placeholder: E.g. "node 16.2.0" or "Safari 15"
validations:
required: true
- type: input
id: os
attributes:
label: Operating System
placeholder: E.g. "Windows 10", "MacOS 12.5", "MCC Interim Linux 0.99.p8"
validations:
required: true

3
.prettierrc Normal file
View File

@@ -0,0 +1,3 @@
{
"singleQuote": true
}

View File

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

View File

@@ -1,8 +1,5 @@
<a name="README">[<img src="https://rawgithub.com/jasmine/jasmine/main/images/jasmine-horizontal.svg" width="400px" />](http://jasmine.github.io)</a>
[![Build Status](https://circleci.com/gh/jasmine/jasmine.svg?style=shield)](https://circleci.com/gh/jasmine/jasmine)
[![Open Source Helpers](https://www.codetriage.com/jasmine/jasmine/badges/users.svg)](https://www.codetriage.com/jasmine/jasmine)
# A JavaScript Testing Framework
Jasmine is a Behavior Driven Development testing framework for JavaScript. It does not rely on browsers, DOM, or any JavaScript framework. Thus it's suited for websites, [Node.js](http://nodejs.org) projects, or anywhere that JavaScript can run.
@@ -30,18 +27,22 @@ 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 |
| Safari | 15-16 |
| Chrome | Evergreen |
| Firefox | Evergreen, 102 |
| Edge | Evergreen |
| Environment | Supported versions |
|-------------------|----------------------------|
| Node | 18, 20, 22 |
| Safari | 15*, 16*, 17* |
| Chrome | Evergreen |
| Firefox | Evergreen, 102*, 115*, 128 |
| 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.
However, Jasmine isn't tested against them and they aren't actively supported.
\* 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.
To find out what environments work with a particular Jasmine release, see the [release notes](https://github.com/jasmine/jasmine/tree/main/release_notes).
## Maintainers
@@ -58,4 +59,6 @@ To find out what environments work with a particular Jasmine release, see the [r
* [Christian Williams](mailto:antixian666@gmail.com)
* Sheel Choksi
Copyright (c) 2008-2022 Jasmine Maintainers. This software is licensed under the [MIT License](https://github.com/jasmine/jasmine/blob/main/MIT.LICENSE).
Copyright (c) 2008-2019 Pivotal Labs<br>
Copyright (c) 2008-2025 The Jasmine developers<br>
This software is licensed under the [MIT License](https://github.com/jasmine/jasmine/blob/main/LICENSE).

View File

@@ -46,8 +46,8 @@ When ready to release - specs are all green and the stories are done:
### Release the core NPM module
1. `npm adduser` to save your credentials locally
1. `npm publish .` to publish what's in `package.json`
1. `npm login` to save your credentials locally
2. `npm publish .` to publish what's in `package.json`
### Release the docs
@@ -55,11 +55,6 @@ Probably only need to do this when releasing a minor version, and not a patch
version. See [the README file in the docs repo](https://github.com/jasmine/jasmine.github.io/blob/master/README.md)
for instructions.
1. `rake update_edge_jasmine`
1. `npm run jsdoc`
1. `rake release[${version}]` to copy the current edge docs to the new version
1. Commit and push.
### Release the `jasmine` NPM package
See <https://github.com/jasmine/jasmine-npm/blob/main/RELEASE.md>.

View File

@@ -11,7 +11,7 @@ module.exports = {
},
files: [
{ src: [ root("MIT.LICENSE") ] },
{ src: [ root("LICENSE") ] },
{
src: [ "jasmine_favicon.png"],
dest: standaloneLibDir,

View File

@@ -1,5 +1,6 @@
/*
Copyright (c) 2008-<%= currentYear %> Pivotal Labs
Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-<%= currentYear %> The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
/*
Copyright (c) 2008-2023 Pivotal Labs
Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-2025 The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -462,7 +463,11 @@ jasmineRequire.HtmlReporter = function(j$) {
'tr',
{},
createDom('td', {}, entry.timestamp.toString()),
createDom('td', {}, entry.message)
createDom(
'td',
{ className: 'jasmine-debug-log-msg' },
entry.message
)
)
);
});

View File

@@ -295,4 +295,7 @@ body {
}
.jasmine_html-reporter .jasmine-debug-log table, .jasmine_html-reporter .jasmine-debug-log th, .jasmine_html-reporter .jasmine-debug-log td {
border: 1px solid #ddd;
}
.jasmine_html-reporter .jasmine-debug-log .jasmine-debug-log-msg {
white-space: pre;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "jasmine-core",
"license": "MIT",
"version": "5.1.1",
"version": "5.6.0",
"repository": {
"type": "git",
"url": "https://github.com/jasmine/jasmine.git"
@@ -27,7 +27,7 @@
"homepage": "https://jasmine.github.io",
"main": "./lib/jasmine-core.js",
"files": [
"MIT.LICENSE",
"LICENSE",
"README.md",
"images/*.{png,svg}",
"lib/**/*.{js,css}",
@@ -52,55 +52,6 @@
"shelljs": "^0.8.3",
"temp": "^0.9.0"
},
"prettier": {
"singleQuote": true
},
"eslintConfig": {
"extends": [
"plugin:compat/recommended"
],
"env": {
"browser": true,
"node": true,
"es2017": true
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"no-unused-vars": [
"error",
{
"args": "none"
}
],
"no-implicit-globals": "error",
"block-spacing": "error",
"func-call-spacing": [
"error",
"never"
],
"key-spacing": "error",
"no-tabs": "error",
"no-trailing-spaces": "error",
"no-whitespace-before-property": "error",
"semi": [
"error",
"always"
],
"space-before-blocks": "error",
"no-eval": "error",
"no-var": "error",
"no-debugger": "error"
}
},
"browserslist": [
"Safari >= 15",
"Firefox >= 102",

51
release_notes/4.6.1.md Normal file
View File

@@ -0,0 +1,51 @@
# Jasmine Core 4.6.1 Release Notes
## Summary
This is a one-time backport of bug fixes from 5.x, for the benefit of Karma
users who may not be aware that they're still using 4.x.
No further 4.x releases are planned. If possible, you should upgrade to the
latest 5.x instead of 4.6.1. If you're using Karma, you can do this by
installing jasmine-core 5.x and adding an override to package.json:
```
{
// ...
"overrides": {
"karma-jasmine": {
"jasmine-core": "^5.0.0"
}
}
}
```
## Bug Fixes
* Removed unnecessary throw when building stack trace
* Fixed error when formatting Error object with non-Error cause property
Merges [#2013](https://github.com/jasmine/jasmine/pull/2013) from @angrycat9000.
Fixes [#2011](https://github.com/jasmine/jasmine/issues/2011).
* Accessibility: Always provide a non-color indication that a spec is pending
* Accessibility: Improved contrast of version number and inactive tab links
## Supported environments
jasmine-core 4.6.1 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 12.17+, 14, 16, 18 |
| Safari | 14-16 |
| Chrome | 125 |
| Firefox | 91, 102, 126 |
| Edge | 124 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

27
release_notes/5.1.2.md Normal file
View File

@@ -0,0 +1,27 @@
# Jasmine Core 5.1.2 Release Notes
## Bug Fixes
* Fixed `throwUnlessAsync`
* Fixes [#2026](https://github.com/jasmine/jasmine/issues/2026)
# Documentation improvements
* Added Safari 17 to supported browsers
* Added Firefox 115 (current ESR) to supported browsers
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20 |
| Safari | 15-17 |
| Chrome | 121 |
| Firefox | 102, 115, 122 |
| Edge | 121 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

35
release_notes/5.2.0.md Normal file
View File

@@ -0,0 +1,35 @@
# Jasmine Core 5.2.0 Release Notes
## Bug Fixes
* Fixed stack trace filtering in FF when the developer tools are open
* Fixed handling of browser `error` events with message but no error
## New Features
* Improved the error message of the toHaveSize matcher.
* Merges [#2033](https://github.com/jasmine/jasmine/pull/2033) from @stephanreiter
* HTML reporter: show debug logs with white-space: pre
## Documentation improvements
* Improved discoverability of asymmetric equality testers
* Added an example for withContext()
* Clarified spyOnGlobalErrorsAsync API docs
* Added Node 22 to supported environments
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20, 22 |
| Safari | 15-17 |
| Chrome | 126 |
| Firefox | 102, 115, 128 |
| Edge | 126 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

35
release_notes/5.3.0.md Normal file
View File

@@ -0,0 +1,35 @@
# Jasmine Core 5.3.0 Release Notes
## Changes
* Improved performance in Safari
* Merges [#2040](https://github.com/jasmine/jasmine/pull/2040) from @dcsaszar
* Fixes [#2008](https://github.com/jasmine/jasmine/issues/2008)
* Improved performance in Playwright Webkit on Windows
* Merges [#2034](https://github.com/jasmine/jasmine/pull/2034) from @m-akinc
* Throw if spying has no effect, as when spying on localStorage methods in Firefox and Safari 17
* See [#2036](https://github.com/jasmine/jasmine/issues/2036) and [#2007](https://github.com/jasmine/jasmine/issues/2007)
## Documentation improvements
* Added API reference for reporter capabilities
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20, 22 |
| Safari | 15-17 |
| Chrome | 128 |
| Firefox | 102, 115, 130 |
| Edge | 128 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

39
release_notes/5.4.0.md Normal file
View File

@@ -0,0 +1,39 @@
# Jasmine Core 5.4.0 Release Notes
## Changes
* Fixed de-duplication of exception messages containing blank lines on Node and Chrome
This is particularly helpful when reporting testing-library errors, which
have messages that contain blank lines and can be hundreds or even thousands
of lines long.
* Document that the expected and actual properties of expectation results are deprecated
The values of these properties are not reliable in configurations where
reporter messages are JSON serialized. They appear to have been seldom if ever
used. They will be removed in the next major release.
* Added Firefox 128 (current ESR) to supported browsers
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|-------------------------|
| Node | 18, 20, 22 |
| Safari | 15-17 |
| Chrome | 129* |
| Firefox | 102**, 115**, 128, 131* |
| Edge | 129* |
\* Evergreen browser. Each version of Jasmine is tested against the latest
version available at release time.<br>
\** Environments that are past end of life are supported on a best-effort basis.
They may be dropped in a future minor release of Jasmine if continued support
becomes impractical.
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

34
release_notes/5.5.0.md Normal file
View File

@@ -0,0 +1,34 @@
# Jasmine Core 5.5.0 Release Notes
## Changes
* Optionally enforce uniqueness of spec and suite names
This is off by default for backwards compatibility but can be enabled
by setting the `forbidDuplicateNames` env config property to true.
Fixes [#1633](https://github.com/jasmine/jasmine/issues/1633).
* Include property value mismatches in diffs even when there are missing or
extra properties
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|-------------------------|
| Node | 18, 20, 22 |
| Safari | 15-17 |
| Chrome | 131* |
| Firefox | 102**, 115**, 128, 132* |
| Edge | 131* |
\* Evergreen browser. Each version of Jasmine is tested against the latest
version available at release time.<br>
\** Environments that are past end of life are supported on a best-effort basis.
They may be dropped in a future minor release of Jasmine if continued support
becomes impractical.
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

51
release_notes/5.6.0.md Normal file
View File

@@ -0,0 +1,51 @@
# Jasmine Core 5.6.0 Release Notes
## Changes
* Added [toHaveNoOtherSpyInteractions](https://jasmine.github.io/api/5.6/matchers.html#toHaveNoOtherSpyInteractions) matcher
* Merges [#2051](https://github.com/jasmine/jasmine/pull/2051) from @Eradev
* Fixes [#1991](https://github.com/jasmine/jasmine/issues/1991)
* Added [toBeNullish](https://jasmine.github.io/api/5.6/matchers.html#toBeNullish) matcher
* Merges [#2045](https://github.com/jasmine/jasmine/pull/2045) from @MattMcCherry
* Improved error messages when non-promises are passed to built-in async matchers
* Merges [#2049](https://github.com/jasmine/jasmine/pull/2049) from @andiz2
* Fixes [#2037](https://github.com/jasmine/jasmine/issues/2037)
* Added [toHaveClasses](https://jasmine.github.io/api/5.6/matchers.html#toHaveClasses) matcher
* Merges [#2046](https://github.com/jasmine/jasmine/pull/2046) from @aYorky
## Documentation updates
* Demoted Safari to best-effort support
Due to limited availability of Safari versions for contributors and maintainers
as well as in CI, Safari will be supported on the same best-effort basis as
environments that are past end of life, such as previous Firefox ESR versions.
See [this discussion](https://github.com/jasmine/jasmine/discussions/2050) for
more information about why this change was made and what to expect.
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|-------------------------|
| Node | 18, 20, 22 |
| Safari | 15**, 16**, 17** |
| Chrome | 133* |
| Firefox | 102**, 115**, 128, 135* |
| Edge | 132* |
\* Evergreen browser. Each version of Jasmine is tested against the latest
version available at release time.<br>
\** Supported on a best-effort basis. Support for these versions may be dropped
if it becomes impractical, and bugs affecting only these versions may not be
treated as release blockers.
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

View File

@@ -3,6 +3,7 @@
run_browser() {
browser=$1
version=$2
os="$3"
description="$browser $version"
if [ $version = "latest" ]; then
version=""
@@ -12,7 +13,7 @@ run_browser() {
echo
echo "Running $description"
echo
USE_SAUCE=true JASMINE_BROWSER=$browser SAUCE_BROWSER_VERSION=$version npm run ci
USE_SAUCE=true JASMINE_BROWSER=$browser SAUCE_BROWSER_VERSION=$version SAUCE_OS="$os" npm run ci
if [ $? -eq 0 ]; then
echo "PASS: $description" >> "$passfile"
@@ -23,9 +24,22 @@ run_browser() {
passfile=`mktemp -t jasmine-results.XXXXXX` || exit 1
failfile=`mktemp -t jasmine-results.XXXXXX` || exit 1
run_browser chrome latest
# As of 2023-09-30, Sauce Connect doesn't work with the latest Chrome version
# on the default Linux. Run on Mac OS instead. The OS specification may need to
# be updated or removed when new Chrome versions stop being available on Mac OS
# 12, although historically this has taken several major OS versions.
# See <https://saucelabs.com/products/supported-browsers-devices>.
# On Saucelabs, the test suite frequently runs ~30s slower on Mac OS than it
# does on Linux, so it's probably worth removing the OS specification once Sauce
# Connect works with Chrome latest on Linux again.
run_browser chrome latest "macOS 12"
run_browser firefox latest
run_browser firefox 128
run_browser firefox 115
run_browser firefox 102
run_browser safari 17
run_browser safari 16
run_browser safari 15
run_browser MicrosoftEdge latest

View File

@@ -20,6 +20,47 @@ describe('ClearStack', function() {
MessageChannel: fakeMessageChannel
};
});
it('uses MessageChannel to reduce setTimeout clamping', function() {
const fakeChannel = fakeMessageChannel();
spyOn(fakeChannel.port2, 'postMessage');
const queueMicrotask = jasmine.createSpy('queueMicrotask');
const global = {
navigator: {
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.0.8 (KHTML, like Gecko) Version/15.1 Safari/605.0.8'
},
MessageChannel: function() {
return fakeChannel;
},
queueMicrotask
};
const clearStack = jasmineUnderTest.getClearStack(global);
for (let i = 0; i < 9; i++) {
clearStack(function() {});
}
expect(fakeChannel.port2.postMessage).not.toHaveBeenCalled();
clearStack(function() {});
expect(fakeChannel.port2.postMessage).toHaveBeenCalledTimes(1);
});
});
describe("in WebKit (Playwright's build for Windows)", function() {
usesQueueMicrotaskWithSetTimeout(function() {
return {
navigator: {
userAgent:
'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/605.1.15 (KHTML, like Gecko)'
},
// queueMicrotask should be used even though MessageChannel is present
MessageChannel: fakeMessageChannel
};
});
});
describe('in browsers other than Safari', function() {

View File

@@ -153,12 +153,33 @@ describe('ExceptionFormatter', function() {
);
});
it('filters Jasmine stack frames with Firefox async annotations', function() {
const error = {
stack:
'http://localhost:8888/__spec__/core/UtilSpec.js:115:28\n' +
'promise callback*fn1@http://localhost:8888/__jasmine__/jasmine.js:4320:27\n' +
'setTimeout handler*fn2@http://localhost:8888/__jasmine__/jasmine.js:4320:27\n' +
'http://localhost:8888/__spec__/core/UtilSpec.js:115:28'
};
const subject = new jasmineUnderTest.ExceptionFormatter({
jasmineFile: 'http://localhost:8888/__jasmine__/jasmine.js'
});
const result = subject.stack(error);
expect(result).toEqual(
'http://localhost:8888/__spec__/core/UtilSpec.js:115:28\n' +
'<Jasmine>\n' +
'http://localhost:8888/__spec__/core/UtilSpec.js:115:28'
);
});
it('filters Jasmine stack frames in this environment', function() {
const error = new Error('an error');
const subject = new jasmineUnderTest.ExceptionFormatter({
jasmineFile: jasmine.util.jasmineFile()
});
const result = subject.stack(error);
jasmine.debugLog('Original stack trace: ' + error.stack);
jasmine.debugLog('Filtered stack trace: ' + result);
const lines = result.split('\n');
if (lines[0].match(/an error/)) {

View File

@@ -328,6 +328,62 @@ describe('GlobalErrors', function() {
});
});
describe('Reporting uncaught exceptions in node.js', function() {
it('prepends a descriptive message when the error is not an `Error`', function() {
const fakeGlobal = {
process: {
on: jasmine.createSpy('process.on'),
removeListener: function() {},
listeners: function() {
return [];
},
removeAllListeners: function() {}
}
};
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
uncaughtExceptionListener(fakeGlobal)(17);
expect(handler).toHaveBeenCalledWith(new Error('Uncaught exception: 17'));
});
it('substitutes a descriptive message when the error is falsy', function() {
const fakeGlobal = {
process: {
on: jasmine.createSpy('process.on'),
removeListener: function() {},
listeners: function() {
return [];
},
removeAllListeners: function() {}
}
};
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
uncaughtExceptionListener(fakeGlobal)();
expect(handler).toHaveBeenCalledWith(
new Error('Uncaught exception with no error or message')
);
});
function uncaughtExceptionListener(global) {
// Grab the right listener
expect(global.process.on.calls.argsFor(0)[0]).toEqual(
'uncaughtException'
);
return global.process.on.calls.argsFor(0)[1];
}
});
describe('#setOverrideListener', function() {
it('overrides the existing handlers in browsers until removed', function() {
const fakeGlobal = browserGlobal();

View File

@@ -459,6 +459,32 @@ describe('QueueRunner', function() {
expect(nextQueueableFn.fn).toHaveBeenCalled();
});
it('handles a global error event with a message but no error', function() {
const queueableFn = {
// eslint-disable-next-line no-unused-vars
fn: function(done) {
const currentHandler = globalErrors.pushListener.calls.mostRecent()
.args[0];
currentHandler(undefined, { message: 'nope' });
},
timeout: 1
};
const onException = jasmine.createSpy('onException');
const globalErrors = {
pushListener: jasmine.createSpy('pushListener'),
popListener: jasmine.createSpy('popListener')
};
const queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn],
onException: onException,
globalErrors: globalErrors
});
queueRunner.execute();
expect(onException).toHaveBeenCalledWith('nope');
});
it('handles exceptions thrown while waiting for the stack to clear', function() {
const queueableFn = {
fn: function(done) {
@@ -492,6 +518,40 @@ describe('QueueRunner', function() {
clearStack.calls.argsFor(0)[0]();
expect(onException).toHaveBeenCalledWith(error);
});
it('handles a global error event with no error while waiting for the stack to clear', function() {
const queueableFn = {
fn: function(done) {
done();
}
};
const errorListeners = [];
const globalErrors = {
pushListener: function(f) {
errorListeners.push(f);
},
popListener: function() {
errorListeners.pop();
}
};
const clearStack = jasmine.createSpy('clearStack');
const onException = jasmine.createSpy('onException');
const queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn],
globalErrors: globalErrors,
clearStack: clearStack,
onException: onException
});
queueRunner.execute();
jasmine.clock().tick();
expect(clearStack).toHaveBeenCalled();
expect(errorListeners.length).toEqual(1);
errorListeners[0](undefined, { message: 'nope' });
clearStack.calls.argsFor(0)[0]();
expect(onException).toHaveBeenCalledWith('nope');
});
});
describe('with a function that returns a promise', function() {

View File

@@ -94,6 +94,30 @@ describe('SpyRegistry', function() {
}).not.toThrowError(/is not declared writable or has no setter/);
});
it('throws if assigning to the property is a no-op', function() {
const scope = {};
function original() {
return 1;
}
Object.defineProperty(scope, 'myFunc', {
get() {
return original;
},
set() {}
});
const spyRegistry = new jasmineUnderTest.SpyRegistry({
createSpy: createSpy
});
expect(function() {
spyRegistry.spyOn(scope, 'myFunc');
}).toThrowError(
"<spyOn> : Can't spy on myFunc because assigning to it had no effect"
);
});
it('overrides the method on the object and returns the spy', function() {
const originalFunctionWasCalled = false,
spyRegistry = new jasmineUnderTest.SpyRegistry({

View File

@@ -51,6 +51,27 @@ describe('StackTrace', function() {
]);
});
it('understands Chrome/Edge style traces with messages containing blank lines', function() {
const error = {
message: 'line 1\n\nline 2',
stack:
'Error: line 1\n\nline 2\n' +
' at UserContext.<anonymous> (http://localhost:8888/__spec__/core/UtilSpec.js:115:19)\n' +
' at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)'
};
const result = new jasmineUnderTest.StackTrace(error);
expect(result.message).toEqual('Error: line 1\n\nline 2');
const rawFrames = result.frames.map(function(f) {
return f.raw;
});
expect(rawFrames).toEqual([
' at UserContext.<anonymous> (http://localhost:8888/__spec__/core/UtilSpec.js:115:19)',
' at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)'
]);
});
it('understands Node style traces', function() {
const error = {
message: 'nope',
@@ -95,7 +116,7 @@ describe('StackTrace', function() {
]);
});
it('understands Safari <=14/Firefox/Phantom-OS X style traces', function() {
it('understands Safari <=14/Firefox style traces', function() {
const error = {
message: 'nope',
stack:
@@ -149,7 +170,7 @@ describe('StackTrace', function() {
]);
});
it('does not mistake gibberish for Safari/Firefox/Phantom-OS X style traces', function() {
it('does not mistake gibberish for Safari/Firefox style traces', function() {
const error = {
message: 'nope',
stack: 'randomcharsnotincludingwhitespace'
@@ -159,36 +180,6 @@ describe('StackTrace', function() {
expect(result.frames).toEqual([{ raw: error.stack }]);
});
it('understands Phantom-Linux style traces', function() {
const error = {
message: 'nope',
stack:
' at UserContext.<anonymous> (http://localhost:8888/__spec__/core/UtilSpec.js:115:19)\n' +
' at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)'
};
const result = new jasmineUnderTest.StackTrace(error);
expect(result.message).toBeFalsy();
expect(result.style).toEqual('v8');
expect(result.frames).toEqual([
{
raw:
' at UserContext.<anonymous> (http://localhost:8888/__spec__/core/UtilSpec.js:115:19)',
func: 'UserContext.<anonymous>',
file: 'http://localhost:8888/__spec__/core/UtilSpec.js',
line: 115
},
{
raw:
' at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)',
func: 'QueueRunner.run',
file: 'http://localhost:8888/__jasmine__/jasmine.js',
line: 4320
}
]);
});
it('ignores blank lines', function() {
const error = {
message: 'nope',
@@ -241,7 +232,7 @@ describe('StackTrace', function() {
]);
});
it('consideres different types of errors', function() {
it('considers different types of errors', function() {
const error = {
message: 'nope',
stack:

View File

@@ -176,6 +176,117 @@ describe('SuiteBuilder', function() {
};
}
describe('Duplicate name handling', function() {
describe('When forbidDuplicateNames is true', function() {
let env;
beforeEach(function() {
env = { configuration: () => ({ forbidDuplicateNames: true }) };
});
it('forbids duplicate spec names', function() {
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
expect(function() {
suiteBuilder.describe('a suite', function() {
suiteBuilder.describe('a nested suite', function() {
suiteBuilder.it('a spec');
suiteBuilder.it('a spec');
});
});
}).toThrowError(
'Duplicate spec name "a spec" found in "a suite a nested suite"'
);
});
it('forbids duplicate spec names in the top suite', function() {
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
expect(function() {
suiteBuilder.it('another spec');
suiteBuilder.it('another spec');
}).toThrowError(
'Duplicate spec name "another spec" found in top suite'
);
});
it('forbids duplicate suite names', function() {
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
expect(function() {
suiteBuilder.describe('a suite', function() {
suiteBuilder.describe('a nested suite', function() {
suiteBuilder.describe('another suite', function() {
suiteBuilder.it('a spec');
});
suiteBuilder.describe('another suite', function() {
suiteBuilder.it('a spec');
});
});
});
}).toThrowError(
'Duplicate suite name "another suite" found in "a suite a nested suite"'
);
});
it('forbids duplicate suite names in the top suite', function() {
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
expect(function() {
suiteBuilder.describe('a suite', function() {
suiteBuilder.it('a spec');
});
suiteBuilder.describe('a suite', function() {
suiteBuilder.it('a spec');
});
}).toThrowError('Duplicate suite name "a suite" found in top suite');
});
it('allows spec and suite names to be duplicated in different suites', function() {
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
expect(function() {
suiteBuilder.describe('suite a', function() {
suiteBuilder.describe('dupe suite', function() {
suiteBuilder.it('dupe spec');
suiteBuilder.describe('child suite', function() {
suiteBuilder.it('dupe spec');
});
});
});
suiteBuilder.describe('suite b', function() {
suiteBuilder.describe('dupe suite', function() {
suiteBuilder.it('dupe spec');
});
});
}).not.toThrow();
});
});
describe('When forbidDuplicateNames is false', function() {
let env;
beforeEach(function() {
env = { configuration: () => ({ forbidDuplicateNames: false }) };
});
it('allows duplicate spec and suite names', function() {
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
expect(function() {
suiteBuilder.describe('dupe suite', function() {
suiteBuilder.it('dupe spec');
suiteBuilder.it('dupe spec');
});
suiteBuilder.describe('dupe suite', function() {
suiteBuilder.it('dupe spec');
suiteBuilder.it('dupe spec');
});
}).not.toThrow();
});
});
});
describe('#parallelReset', function() {
it('resets the top suite result', function() {
jasmineUnderTest.Suite.prototype.handleException.and.callThrough();

View File

@@ -378,4 +378,31 @@ describe('Suite', function() {
);
});
});
describe('#hasChildWithDescription', function() {
it('returns true if there is a child with the given description', function() {
const subject = new jasmineUnderTest.Suite({});
const description = 'a spec';
subject.addChild({ description });
expect(subject.hasChildWithDescription(description)).toBeTrue();
});
it('returns false if there is no child with the given description', function() {
const subject = new jasmineUnderTest.Suite({});
subject.addChild({ description: 'a different spec' });
expect(subject.hasChildWithDescription('a spec')).toBeFalse();
});
it('does not recurse into child suites', function() {
const subject = new jasmineUnderTest.Suite({});
const childSuite = new jasmineUnderTest.Suite({});
subject.addChild(childSuite);
const description = 'a spec';
childSuite.addChild(description);
expect(subject.hasChildWithDescription('a spec')).toBeFalse();
});
});
});

View File

@@ -179,7 +179,10 @@ describe('base helpers', function() {
f2 = jasmine.createSpy('setTimeout callback for ' + (max + 1));
// Suppress printing of TimeoutOverflowWarning in node
spyOn(console, 'error');
if (typeof process !== 'undefined' && process.emitWarning) {
spyOn(process, 'emitWarning'); // Node 22
}
spyOn(console, 'error'); // Node <22
let id = setTimeout(f1, max);
setTimeout(function() {

View File

@@ -3,7 +3,7 @@ describe('Env integration', function() {
const isBrowser = typeof window !== 'undefined';
beforeEach(function() {
jasmine.getEnv().registerIntegrationMatchers();
specHelpers.registerIntegrationMatchers();
env = new jasmineUnderTest.Env();
});
@@ -1315,8 +1315,9 @@ describe('Env integration', function() {
'works with constructors when using callThrough spy strategy',
function() {
function MyClass(foo) {
if (!(this instanceof MyClass))
if (!(this instanceof MyClass)) {
throw new Error('You must use the new keyword.');
}
this.foo = foo;
}
const subject = { MyClass: MyClass };
@@ -4443,6 +4444,15 @@ describe('Env integration', function() {
});
});
it('forbids duplicates when forbidDuplicateNames is true', function() {
env.configure({ forbidDuplicateNames: true });
env.it('a spec');
expect(function() {
env.it('a spec');
}).toThrowError('Duplicate spec name "a spec" found in top suite');
});
function browserEventMethods() {
return {
listeners_: { error: [], unhandledrejection: [] },

View File

@@ -469,6 +469,24 @@ describe('Matchers (Integration)', function() {
});
});
describe('toBeNullish', function() {
verifyPasses(function(env) {
env.expect(undefined).toBeNullish();
});
verifyPasses(function(env) {
env.expect(null).toBeNullish();
});
verifyFails(function(env) {
env.expect(1).toBeNullish();
});
verifyFails(function(env) {
env.expect('').toBeNullish();
});
});
describe('toContain', function() {
verifyPasses(function(env) {
env.addCustomEqualityTester(function(a, b) {
@@ -610,23 +628,31 @@ describe('Matchers (Integration)', function() {
});
describe('toHaveClass', function() {
beforeEach(function() {
this.domHelpers = jasmine.getEnv().domHelpers();
});
verifyPasses(function(env) {
const domHelpers = jasmine.getEnv().domHelpers();
const el = domHelpers.createElementWithClassName('foo');
const el = specHelpers.domHelpers.createElementWithClassName('foo');
env.expect(el).toHaveClass('foo');
});
verifyFails(function(env) {
const domHelpers = jasmine.getEnv().domHelpers();
const el = domHelpers.createElementWithClassName('foo');
const el = specHelpers.domHelpers.createElementWithClassName('foo');
env.expect(el).toHaveClass('bar');
});
});
describe('toHaveClasses', function() {
verifyPasses(function(env) {
const el = specHelpers.domHelpers.createElementWithClassName(
'foo bar baz'
);
env.expect(el).toHaveClasses(['bar', 'baz']);
});
verifyFails(function(env) {
const el = specHelpers.domHelpers.createElementWithClassName('foo bar');
env.expect(el).toHaveClasses(['bar', 'baz']);
});
});
describe('toHaveSpyInteractions', function() {
let spyObj;
beforeEach(function() {
@@ -649,6 +675,23 @@ describe('Matchers (Integration)', function() {
});
});
describe('toHaveNoOtherSpyInteractions', function() {
let spyObj;
beforeEach(function() {
spyObj = env.createSpyObj('NewClass', ['spyA', 'spyB']);
});
verifyPasses(function(env) {
env.expect(spyObj).toHaveNoOtherSpyInteractions();
});
verifyFails(function(env) {
spyObj.spyA();
env.expect(spyObj).toHaveNoOtherSpyInteractions();
});
});
describe('toMatch', function() {
verifyPasses(function(env) {
env.expect('foo').toMatch(/oo$/);

View File

@@ -2,7 +2,7 @@ describe('spec running', function() {
let env;
beforeEach(function() {
jasmine.getEnv().registerIntegrationMatchers();
specHelpers.registerIntegrationMatchers();
env = new jasmineUnderTest.Env();
env.configure({ random: false });
});

View File

@@ -38,6 +38,8 @@ describe('toBePending', function() {
return matcher.compare(actual);
}
expect(f).toThrowError('Expected toBePending to be called on a promise.');
expect(f).toThrowError(
'Expected toBePending to be called on a promise but was on a string.'
);
});
});

View File

@@ -28,6 +28,8 @@ describe('toBeRejected', function() {
return matcher.compare(actual);
}
expect(f).toThrowError('Expected toBeRejected to be called on a promise.');
expect(f).toThrowError(
'Expected toBeRejected to be called on a promise but was on a string.'
);
});
});

View File

@@ -232,7 +232,7 @@ describe('#toBeRejectedWithError', function() {
}
expect(f).toThrowError(
'Expected toBeRejectedWithError to be called on a promise.'
'Expected toBeRejectedWithError to be called on a promise but was on a string.'
);
});
});

View File

@@ -83,7 +83,7 @@ describe('#toBeRejectedWith', function() {
}
expect(f).toThrowError(
'Expected toBeRejectedWith to be called on a promise.'
'Expected toBeRejectedWith to be called on a promise but was on a string.'
);
});
});

View File

@@ -35,6 +35,8 @@ describe('toBeResolved', function() {
return matcher.compare(actual);
}
expect(f).toThrowError('Expected toBeResolved to be called on a promise.');
expect(f).toThrowError(
'Expected toBeResolved to be called on a promise but was on a string.'
);
});
});

View File

@@ -93,7 +93,7 @@ describe('#toBeResolvedTo', function() {
}
expect(f).toThrowError(
'Expected toBeResolvedTo to be called on a promise.'
'Expected toBeResolvedTo to be called on a promise but was on a string.'
);
});
});

View File

@@ -757,7 +757,9 @@ describe('matchersUtil', function() {
const a2 = new TypedArrayCtor(2);
a1[0] = a2[0] = 0;
a1[1] = a2[1] = 1;
expect(matchersUtil.equals(a1, a2)).toBe(true);
const diffBuilder = new jasmineUnderTest.DiffBuilder();
expect(matchersUtil.equals(a1, a2, diffBuilder)).toBe(true);
jasmine.debugLog('Diff: ' + diffBuilder.getMessage());
}
);

View File

@@ -0,0 +1,57 @@
describe('toBeNullish', function() {
it('passes for null values', function() {
const matcher = jasmineUnderTest.matchers.toBeNullish();
const result = matcher.compare(null);
expect(result.pass).toBe(true);
});
it('passes for undefined values', function() {
const matcher = jasmineUnderTest.matchers.toBeNullish();
const result = matcher.compare(void 0);
expect(result.pass).toBe(true);
});
it('fails when matching defined values', function() {
const matcher = jasmineUnderTest.matchers.toBeNullish();
const result = matcher.compare('foo');
expect(result.pass).toBe(false);
});
describe('falsy values', () => {
it('fails for 0', function() {
const matcher = jasmineUnderTest.matchers.toBeNullish();
const result = matcher.compare(0);
expect(result.pass).toBe(false);
});
it('fails for -0', function() {
const matcher = jasmineUnderTest.matchers.toBeNullish();
const result = matcher.compare(-0);
expect(result.pass).toBe(false);
});
it('fails for empty string', function() {
const matcher = jasmineUnderTest.matchers.toBeNullish();
const result = matcher.compare('');
expect(result.pass).toBe(false);
});
it('fails for false', function() {
const matcher = jasmineUnderTest.matchers.toBeNullish();
const result = matcher.compare(false);
expect(result.pass).toBe(false);
});
it('fails for NaN', function() {
const matcher = jasmineUnderTest.matchers.toBeNullish();
const result = matcher.compare(NaN);
expect(result.pass).toBe(false);
});
it('fails for 0n', function() {
const matcher = jasmineUnderTest.matchers.toBeNullish();
const result = matcher.compare(BigInt(0));
expect(result.pass).toBe(false);
});
});
});

View File

@@ -96,6 +96,17 @@ describe('toEqual', function() {
expect(compareEquals(actual, expected).message).toEqual(message);
});
it('reports mismatches as well as missing or extra properties', function() {
const actual = { x: { z: 2 } },
expected = { x: { y: 1, z: 3 } },
message =
'Expected $.x to have properties\n' +
' y: 1\n' +
'Expected $.x.z = 2 to equal 3.';
expect(compareEquals(actual, expected).message).toEqual(message);
});
it('reports missing symbol properties', function() {
const actual = { x: {} },
expected = { x: { [Symbol('y')]: 1 } },

View File

@@ -112,4 +112,20 @@ describe('toHaveBeenCalledBefore', function() {
'Expected spy first spy to not have been called before spy second spy, but it was'
);
});
it('set the correct calls as verified when passing', function() {
const matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(),
firstSpy = new jasmineUnderTest.Spy('first spy'),
secondSpy = new jasmineUnderTest.Spy('second spy');
firstSpy();
secondSpy();
matcher.compare(firstSpy, secondSpy);
expect(firstSpy.calls.count()).toBe(1);
expect(firstSpy.calls.unverifiedCount()).toBe(0);
expect(secondSpy.calls.count()).toBe(1);
expect(secondSpy.calls.unverifiedCount()).toBe(0);
});
});

View File

@@ -105,4 +105,18 @@ describe('toHaveBeenCalledOnceWith', function() {
matcher.compare(fn);
}).toThrowError(/Expected a spy, but got Function./);
});
it('set the correct calls as verified when passing', function() {
const pp = jasmineUnderTest.makePrettyPrinter(),
util = new jasmineUnderTest.MatchersUtil({ pp: pp }),
matcher = jasmineUnderTest.matchers.toHaveBeenCalledOnceWith(util),
calledSpy = new jasmineUnderTest.Spy('called-spy');
calledSpy('x');
matcher.compare(calledSpy, 'x');
expect(calledSpy.calls.count()).toBe(1);
expect(calledSpy.calls.unverifiedCount()).toBe(0);
});
});

View File

@@ -50,4 +50,16 @@ describe('toHaveBeenCalled', function() {
'Expected spy sample-spy to have been called.'
);
});
it('set the correct calls as verified when passing', function() {
const matcher = jasmineUnderTest.matchers.toHaveBeenCalled(),
spy = new jasmineUnderTest.Spy('sample-spy');
spy();
matcher.compare(spy);
expect(spy.calls.count()).toBe(1);
expect(spy.calls.unverifiedCount()).toBe(0);
});
});

View File

@@ -87,4 +87,17 @@ describe('toHaveBeenCalledTimes', function() {
' times.'
);
});
it('set the correct calls as verified when passing', function() {
const matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(),
spy = new jasmineUnderTest.Spy('sample-spy');
spy();
spy();
matcher.compare(spy, 2);
expect(spy.calls.count()).toBe(2);
expect(spy.calls.unverifiedCount()).toBe(0);
});
});

View File

@@ -2,6 +2,7 @@ describe('toHaveBeenCalledWith', function() {
it('passes when the actual was called with matching parameters', function() {
const matchersUtil = {
contains: jasmine.createSpy('delegated-contains').and.returnValue(true),
equals: jasmine.createSpy('delegated-equals').and.returnValue(true),
pp: jasmineUnderTest.makePrettyPrinter()
},
matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(matchersUtil),
@@ -92,4 +93,20 @@ describe('toHaveBeenCalledWith', function() {
matcher.compare(fn);
}).toThrowError(/Expected a spy, but got Function./);
});
it('set the correct calls as verified when passing', function() {
const matchersUtil = {
contains: jasmine.createSpy('delegated-contains').and.returnValue(true),
equals: jasmine.createSpy('delegated-equals').and.returnValue(true),
pp: jasmineUnderTest.makePrettyPrinter()
},
matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(matchersUtil),
calledSpy = new jasmineUnderTest.Spy('called-spy');
calledSpy('a', 'b');
matcher.compare(calledSpy, 'a', 'b');
expect(calledSpy.calls.count()).toBe(1);
expect(calledSpy.calls.unverifiedCount()).toBe(0);
});
});

View File

@@ -1,12 +1,8 @@
describe('toHaveClass', function() {
beforeEach(function() {
this.domHelpers = jasmine.getEnv().domHelpers();
});
it('fails for a DOM element that lacks the expected class', function() {
const matcher = jasmineUnderTest.matchers.toHaveClass(),
result = matcher.compare(
this.domHelpers.createElementWithClassName(''),
specHelpers.domHelpers.createElementWithClassName(''),
'foo'
);
@@ -15,7 +11,7 @@ describe('toHaveClass', function() {
it('passes for a DOM element that has the expected class', function() {
const matcher = jasmineUnderTest.matchers.toHaveClass(),
el = this.domHelpers.createElementWithClassName('foo bar baz');
el = specHelpers.domHelpers.createElementWithClassName('foo bar baz');
expect(matcher.compare(el, 'foo').pass).toBe(true);
expect(matcher.compare(el, 'bar').pass).toBe(true);
@@ -24,7 +20,7 @@ describe('toHaveClass', function() {
it('fails for a DOM element that only has other classes', function() {
const matcher = jasmineUnderTest.matchers.toHaveClass(),
el = this.domHelpers.createElementWithClassName('foo bar');
el = specHelpers.domHelpers.createElementWithClassName('foo bar');
expect(matcher.compare(el, 'fo').pass).toBe(false);
});
@@ -42,7 +38,7 @@ describe('toHaveClass', function() {
matcher.compare(undefined, 'foo');
}).toThrowError('undefined is not a DOM element');
const textNode = this.domHelpers.document.createTextNode('');
const textNode = specHelpers.domHelpers.document.createTextNode('');
expect(function() {
matcher.compare(textNode, 'foo');
}).toThrowError('HTMLNode is not a DOM element');

View File

@@ -0,0 +1,48 @@
describe('toHaveClasses', function() {
it('fails for a DOM element that lacks all the expected classes', function() {
const matcher = jasmineUnderTest.matchers.toHaveClasses(),
result = matcher.compare(
specHelpers.domHelpers.createElementWithClassName(''),
['foo', 'bar']
);
expect(result.pass).toBe(false);
});
it('passes for a DOM element that has all the expected classes', function() {
const matcher = jasmineUnderTest.matchers.toHaveClasses(),
el = specHelpers.domHelpers.createElementWithClassName('foo bar baz');
expect(matcher.compare(el, ['foo', 'bar']).pass).toBe(true);
});
it('fails for a DOM element that only has some matching classes', function() {
const matcher = jasmineUnderTest.matchers.toHaveClasses(),
el = specHelpers.domHelpers.createElementWithClassName('foo bar');
expect(matcher.compare(el, ['foo', 'can']).pass).toBe(false);
});
it('throws an exception when actual is not a DOM element', function() {
const matcher = jasmineUnderTest.matchers.toHaveClasses({
pp: jasmineUnderTest.makePrettyPrinter()
});
expect(function() {
matcher.compare('x', ['foo']);
}).toThrowError("'x' is not a DOM element");
expect(function() {
matcher.compare(undefined, ['foo']);
}).toThrowError('undefined is not a DOM element');
const textNode = specHelpers.domHelpers.document.createTextNode('');
expect(function() {
matcher.compare(textNode, ['foo']);
}).toThrowError('HTMLNode is not a DOM element');
expect(function() {
matcher.compare({ classList: '' }, ['foo']);
}).toThrowError("Object({ classList: '' }) is not a DOM element");
});
});

View File

@@ -0,0 +1,154 @@
describe('toHaveNoOtherSpyInteractions', function() {
it('passes when there are no spy interactions', function() {
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
let spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('NewClass', ['spyA', 'spyB']);
let result = matcher.compare(spyObj);
expect(result.pass).toBeTrue();
});
it('passes when there are multiple spy interactions where checked by toHaveBeenCalled', function() {
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
let toHaveBeenCalledMatcher = jasmineUnderTest.matchers.toHaveBeenCalled();
let spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('NewClass', ['spyA', 'spyB']);
spyObj.spyA();
spyObj.spyB();
spyObj.spyA();
toHaveBeenCalledMatcher.compare(spyObj.spyA);
toHaveBeenCalledMatcher.compare(spyObj.spyB);
let result = matcher.compare(spyObj);
expect(result.pass).toBeTrue();
});
it('fails when there are spy interactions', function() {
const matchersUtil = new jasmineUnderTest.MatchersUtil({
pp: jasmineUnderTest.makePrettyPrinter()
});
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions(
matchersUtil
);
let spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('NewClass', ['spyA', 'spyB']);
spyObj.spyA('x');
let result = matcher.compare(spyObj);
expect(result.pass).toBeFalse();
expect(result.message).toEqual(
'Expected a spy object to have no other spy interactions, but it had the following calls:\n' +
" NewClass.spyA called with [ 'x' ]."
);
});
it('shows the right message is negated', function() {
const matchersUtil = new jasmineUnderTest.MatchersUtil({
pp: jasmineUnderTest.makePrettyPrinter()
});
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions(
matchersUtil
);
let spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('NewClass', ['spyA', 'spyB']);
spyObj.spyA();
spyObj.spyB();
let result = matcher.compare(spyObj);
expect(result.pass).toBeFalse();
expect(result.message).toEqual(
'Expected a spy object to have no other spy interactions, but it had the following calls:\n' +
' NewClass.spyA called with [ ],\n' +
' NewClass.spyB called with [ ].'
);
});
it('passes when only non-observed spy object interactions are interacted', function() {
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
let spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('NewClass', ['spyA', 'spyB']);
spyObj.otherMethod = function() {};
spyObj.otherMethod();
let result = matcher.compare(spyObj);
expect(result.pass).toBeTrue();
expect(result.message).toEqual(
"Expected a spy object to have other spy interactions but it didn't."
);
});
it(`throws an error if a non-object is passed`, function() {
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
expect(function() {
matcher.compare(true);
}).toThrowError(Error, /Expected an object, but got/);
expect(function() {
matcher.compare(123);
}).toThrowError(Error, /Expected an object, but got/);
expect(function() {
matcher.compare('string');
}).toThrowError(Error, /Expected an object, but got/);
});
it('throws an error if arguments are passed', function() {
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
let spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('mySpyObj', ['spyA', 'spyB']);
expect(function() {
matcher.compare(spyObj, 'an argument');
}).toThrowError(Error, /Does not take arguments/);
});
it('throws an error if the spy object has no spies', function() {
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
const spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('mySpyObj', ['notSpy']);
// Removing spy since spy objects cannot be created without spies.
spyObj.notSpy = function() {};
expect(function() {
matcher.compare(spyObj);
}).toThrowError(
Error,
/Expected an object with spies, but object has no spies/
);
});
it('handles multiple interactions with a single spy', function() {
const matchersUtil = new jasmineUnderTest.MatchersUtil({
pp: jasmineUnderTest.makePrettyPrinter()
}),
matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions(
matchersUtil
),
toHaveBeenCalledWithMatcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(
matchersUtil
),
spyObj = jasmineUnderTest
.getEnv()
.createSpyObj('NewClass', ['spyA', 'spyB']);
spyObj.spyA('x');
spyObj.spyA('y');
toHaveBeenCalledWithMatcher.compare(spyObj.spyA, 'x');
let result = matcher.compare(spyObj);
expect(result.pass).toBeFalse();
});
});

View File

@@ -15,6 +15,17 @@ describe('toHaveSize', function() {
expect(result.pass).toBe(false);
});
it('informs about the size of an array whose length does not match', function() {
const matcher = jasmineUnderTest.matchers.toHaveSize({
pp: jasmineUnderTest.makePrettyPrinter()
}),
result = matcher.compare([1, 2, 3], 2);
expect(result.message()).toEqual(
'Expected [ 1, 2, 3 ] with size 3 to have size 2.'
);
});
it('passes for an object with the proper number of keys', function() {
const matcher = jasmineUnderTest.matchers.toHaveSize(),
result = matcher.compare({ a: 1, b: 2 }, 2);

View File

@@ -1,4 +1,4 @@
(function(env) {
(function() {
function browserVersion(matchFn) {
const userAgent = jasmine.getGlobal().navigator.userAgent;
if (!userAgent) {
@@ -10,7 +10,7 @@
return match ? parseFloat(match[1]) : void 0;
}
env.firefoxVersion = browserVersion(function(userAgent) {
specHelpers.firefoxVersion = browserVersion(function(userAgent) {
return /Firefox\/([0-9]{0,})/.exec(userAgent);
});
})(jasmine.getEnv());
})();

View File

@@ -1,24 +1,20 @@
(function(env) {
function domHelpers() {
let doc;
(function() {
let doc;
if (typeof document !== 'undefined') {
doc = document;
} else {
const JSDOM = require('jsdom').JSDOM;
const dom = new JSDOM();
doc = dom.window.document;
}
return {
document: doc,
createElementWithClassName: function(className) {
const el = this.document.createElement('div');
el.className = className;
return el;
}
};
if (typeof document !== 'undefined') {
doc = document;
} else {
const JSDOM = require('jsdom').JSDOM;
const dom = new JSDOM();
doc = dom.window.document;
}
env.domHelpers = domHelpers;
})(jasmine.getEnv());
specHelpers.domHelpers = {
document: doc,
createElementWithClassName(className) {
const el = this.document.createElement('div');
el.className = className;
return el;
}
};
})();

1
spec/helpers/init.js Normal file
View File

@@ -0,0 +1 @@
globalThis.specHelpers = {};

View File

@@ -1,5 +1,5 @@
(function(env) {
env.registerIntegrationMatchers = function() {
(function() {
specHelpers.registerIntegrationMatchers = function() {
jasmine.addMatchers({
toHaveFailedExpectationsForRunnable: function() {
return {
@@ -51,4 +51,4 @@
}
});
};
})(jasmine.getEnv());
})();

View File

@@ -12,14 +12,12 @@
};
function getSourceFiles() {
const src_files = ['core/**/*.js', 'version.js'].map(function(file) {
return path.join(__dirname, '../../', 'src/', file);
});
const globs = ['../../src/core/**/*.js', '../../src/version.js'];
const srcFiles = globs.flatMap(g => glob.sync(g, { cwd: __dirname }));
const files = src_files.flatMap(g => glob.sync(g));
files.forEach(function(resolvedFile) {
require(resolvedFile);
});
for (const file of srcFiles) {
require(file);
}
}
getSourceFiles();

View File

@@ -22,7 +22,7 @@ describe('PrettyPrinter (HTML Dependent)', function() {
});
it("should print Firefox's wrapped native objects correctly", function() {
if (jasmine.getEnv().firefoxVersion) {
if (specHelpers.firefoxVersion) {
const pp = jasmineUnderTest.makePrettyPrinter();
let err;
try {

View File

@@ -113,7 +113,7 @@ describe('npm package', function() {
const files = fs.readdirSync(path.resolve(this.tmpDir, 'package'));
files.sort();
expect(files).toEqual([
'MIT.LICENSE',
'LICENSE',
'README.md',
'images',
'lib',

View File

@@ -18,6 +18,7 @@ module.exports = {
specDir: 'spec',
specFiles: ['**/*[Ss]pec.js', '!npmPackage/**/*'],
helpers: [
'helpers/init.js',
'helpers/generator.js',
'helpers/BrowserFlags.js',
'helpers/domHelpers.js',

View File

@@ -5,6 +5,7 @@
"npmPackage/**/*[Ss]pec.js"
],
"helpers": [
"helpers/init.js",
"helpers/domHelpers.js",
"helpers/integrationMatchers.js",
"helpers/overrideConsoleLogForCircleCi.js",

View File

@@ -125,6 +125,10 @@ getJasmineRequireObj().CallTracker = function(j$) {
this.saveArgumentsByValue = function() {
opts.cloneArgs = true;
};
this.unverifiedCount = function() {
return calls.reduce((count, call) => count + (call.verified ? 0 : 1), 0);
};
}
return CallTracker;

View File

@@ -2,7 +2,8 @@ getJasmineRequireObj().clearStack = function(j$) {
const maxInlineCallCount = 10;
function browserQueueMicrotaskImpl(global) {
const { setTimeout, queueMicrotask } = global;
const unclampedSetTimeout = getUnclampedSetTimeout(global);
const { queueMicrotask } = global;
let currentCallCount = 0;
return function clearStack(fn) {
currentCallCount++;
@@ -11,7 +12,7 @@ getJasmineRequireObj().clearStack = function(j$) {
queueMicrotask(fn);
} else {
currentCallCount = 0;
setTimeout(fn);
unclampedSetTimeout(fn);
}
};
}
@@ -25,6 +26,37 @@ getJasmineRequireObj().clearStack = function(j$) {
}
function messageChannelImpl(global) {
const { setTimeout } = global;
const postMessage = getPostMessage(global);
let currentCallCount = 0;
return function clearStack(fn) {
currentCallCount++;
if (currentCallCount < maxInlineCallCount) {
postMessage(fn);
} else {
currentCallCount = 0;
setTimeout(fn);
}
};
}
function getUnclampedSetTimeout(global) {
const { setTimeout } = global;
if (j$.util.isUndefined(global.MessageChannel)) {
return setTimeout;
}
const postMessage = getPostMessage(global);
return function unclampedSetTimeout(fn) {
postMessage(function() {
setTimeout(fn);
});
};
}
function getPostMessage(global) {
const { MessageChannel, setTimeout } = global;
const channel = new MessageChannel();
let head = {};
@@ -48,17 +80,9 @@ getJasmineRequireObj().clearStack = function(j$) {
}
};
let currentCallCount = 0;
return function clearStack(fn) {
currentCallCount++;
if (currentCallCount < maxInlineCallCount) {
tail = tail.next = { task: fn };
channel.port2.postMessage(0);
} else {
currentCallCount = 0;
setTimeout(fn);
}
return function postMessage(fn) {
tail = tail.next = { task: fn };
channel.port2.postMessage(0);
};
}
@@ -68,20 +92,25 @@ getJasmineRequireObj().clearStack = function(j$) {
global.process.versions &&
typeof global.process.versions.node === 'string';
const SAFARI =
// Windows builds of WebKit have a fairly generic user agent string when no application name is provided:
// e.g. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/605.1.15 (KHTML, like Gecko)"
const SAFARI_OR_WIN_WEBKIT =
global.navigator &&
/^((?!chrome|android).)*safari/i.test(global.navigator.userAgent);
/(^((?!chrome|android).)*safari)|(Win64; x64\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)$)/i.test(
global.navigator.userAgent
);
if (NODE_JS) {
// Unlike browsers, Node doesn't require us to do a periodic setTimeout
// so we avoid the overhead.
return nodeQueueMicrotaskImpl(global);
} else if (
SAFARI ||
SAFARI_OR_WIN_WEBKIT ||
j$.util.isUndefined(global.MessageChannel) /* tests */
) {
// queueMicrotask is dramatically faster than MessageChannel in Safari,
// at least through version 16.
// queueMicrotask is dramatically faster than MessageChannel in Safari
// and other WebKit-based browsers, such as the one distributed by Playwright
// to test Safari-like behavior on Windows.
// Some of our own integration tests provide a mock queueMicrotask in all
// environments because it's simpler to mock than MessageChannel.
return browserQueueMicrotaskImpl(global);

View File

@@ -138,6 +138,15 @@ getJasmineRequireObj().Env = function(j$) {
* @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
@@ -186,7 +195,8 @@ getJasmineRequireObj().Env = function(j$) {
'hideDisabled',
'stopOnSpecFailure',
'stopSpecOnExpectationFailure',
'autoCleanClosures'
'autoCleanClosures',
'forbidDuplicateNames'
];
booleanProps.forEach(function(prop) {
@@ -272,12 +282,19 @@ getJasmineRequireObj().Env = function(j$) {
* @extends Error
* @description Represents a failure of an expectation evaluated with
* {@link throwUnless}. Properties of this error are a subset of the
* properties of {@link Expectation} and have the same values.
* properties of {@link ExpectationResult} and have the same values.
*
* Note: The expected and actual properties are deprecated and may be removed
* in a future release. In many Jasmine configurations they are passed
* through JSON serialization and deserialization, which is inherently
* lossy. In such cases, the expected and actual values may be placeholders
* or approximations of the original objects.
*
* @property {String} matcherName - The name of the matcher that was executed for this expectation.
* @property {String} message - The failure message for the expectation.
* @property {Boolean} passed - Whether the expectation passed or failed.
* @property {Object} expected - If the expectation failed, what was the expected value.
* @property {Object} actual - If the expectation failed, what actual value was produced.
* @property {Object} expected - Deprecated. If the expectation failed, what was the expected value.
* @property {Object} actual - Deprecated. If the expectation failed, what actual value was produced.
*/
const error = new Error(result.message);
error.passed = result.passed;

View File

@@ -16,19 +16,24 @@ getJasmineRequireObj().Expectation = function(j$) {
}
/**
* Add some context for an {@link expect}
* Add some context to be included in matcher failures for an
* {@link expect|expectation}, so that it can be more easily distinguished
* from similar expectations.
* @function
* @name matchers#withContext
* @since 3.3.0
* @param {String} message - Additional context to show when the matcher fails
* @return {matchers}
* @example
* expect(things[0]).withContext('thing 0').toEqual('a');
* expect(things[1]).withContext('thing 1').toEqual('b');
*/
Expectation.prototype.withContext = function withContext(message) {
return addFilter(this, new ContextAddingFilter(message));
};
/**
* Invert the matcher following this {@link expect}
* Invert the matcher following this {@link expect|expectation}
* @member
* @name matchers#not
* @since 1.3.0

View File

@@ -12,6 +12,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
function dispatchBrowserError(error, event) {
if (overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
overrideHandler(error);
return;
}
@@ -55,6 +56,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
const handler = handlers[handlers.length - 1];
if (overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
overrideHandler(error);
return;
}

View File

@@ -65,8 +65,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}
QueueRunner.prototype.execute = function() {
this.handleFinalError = error => {
this.onException(error);
this.handleFinalError = (error, event) => {
this.onException(errorOrMsgForGlobalError(error, event));
};
this.globalErrors.pushListener(this.handleFinalError);
this.run(0);
@@ -96,10 +96,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
this.recordError_(iterativeIndex);
};
function handleError(error) {
// TODO probably shouldn't next() right away here.
// That makes debugging async failures much more confusing.
onException(error);
function handleError(error, event) {
onException(errorOrMsgForGlobalError(error, event));
}
const cleanup = once(() => {
if (timeoutId !== void 0) {
@@ -285,5 +283,16 @@ getJasmineRequireObj().QueueRunner = function(j$) {
};
}
function errorOrMsgForGlobalError(error, event) {
// TODO: In cases where error is a string or undefined, the error message
// that gets sent to reporters will be `${message} thrown`, which could
// be improved to not say "thrown" when the cause wasn't necessarily
// an exception or to provide hints about throwing Errors rather than
// strings.
return (
error || (event && event.message) || 'Global error event with no message'
);
}
return QueueRunner;
};

View File

@@ -177,8 +177,8 @@ getJasmineRequireObj().Runner = function(j$) {
* @property {String} incompleteCode - Machine-readable explanation of why the suite was incomplete: 'focused', 'noSpecsFound', or undefined.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite. Note that this property is not present when Jasmine is run in parallel mode.
* @property {Int} numWorkers - Number of parallel workers. Note that this property is only present when Jasmine is run in parallel mode.
* @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level.
* @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level.
* @property {ExpectationResult[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level.
* @property {ExpectationResult[]} deprecationWarnings - List of deprecation warnings that occurred at the global level.
* @since 2.4.0
*/
const jasmineDoneInfo = {

View File

@@ -148,9 +148,9 @@ getJasmineRequireObj().Spec = function(j$) {
* @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 {Expectation[]} failedExpectations - The list of expectations that failed during execution of this spec.
* @property {Expectation[]} passedExpectations - The list of expectations that passed during execution of this spec.
* @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec.
* @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.

View File

@@ -26,7 +26,8 @@ getJasmineRequireObj().Spy = function(j$) {
const callData = {
object: context,
invocationOrder: nextOrder(),
args: Array.prototype.slice.apply(args)
args: Array.prototype.slice.apply(args),
verified: false
};
callTracker.track(callData);

View File

@@ -84,6 +84,16 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
obj[methodName] = spiedMethod;
// Check if setting the property actually worked. Some objects, such as
// localStorage in Firefox and later Safari versions, have no-op setters.
if (obj[methodName] !== spiedMethod) {
throw new Error(
j$.formatErrorMsg('<spyOn>')(
`Can't spy on ${methodName} because assigning to it had no effect`
)
);
}
return spiedMethod;
};

View File

@@ -1,8 +1,6 @@
getJasmineRequireObj().StackTrace = function(j$) {
function StackTrace(error) {
let lines = error.stack.split('\n').filter(function(line) {
return line !== '';
});
let lines = error.stack.split('\n');
const extractResult = extractMessage(error.message, lines);
@@ -11,6 +9,10 @@ getJasmineRequireObj().StackTrace = function(j$) {
lines = extractResult.remainder;
}
lines = lines.filter(function(line) {
return line !== '';
});
const parseResult = tryParseFrames(lines);
this.frames = parseResult.frames;
this.style = parseResult.style;
@@ -32,7 +34,7 @@ getJasmineRequireObj().StackTrace = function(j$) {
// e.g. " at /some/path:4320:20
{ re: /\s*at (.+)$/, fileLineColIx: 1, style: 'v8' },
// PhantomJS on OS X, Safari, Firefox
// Safari, most Firefox stack frames
// e.g. "run@http://localhost:8888/__jasmine__/jasmine.js:4320:27"
// or "http://localhost:8888/__jasmine__/jasmine.js:4320:27"
{
@@ -40,6 +42,15 @@ getJasmineRequireObj().StackTrace = function(j$) {
fnIx: 2,
fileLineColIx: 3,
style: 'webkit'
},
// Some Firefox stack frames when the developer tools are open
// e.g. "promise callback*specStarted@http://localhost:8888/__jasmine__/jasmine.js:1880:41"
{
re: /^^(?:((?:promise callback|[^\s]+ handler)\*([^@\s]+)@)|@)?([^\s]+)$/,
fnIx: 2,
fileLineColIx: 3,
style: 'webkit'
}
];

View File

@@ -105,8 +105,8 @@ getJasmineRequireObj().Suite = function(j$) {
* @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 {Expectation[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
* @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite.
* @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}
@@ -251,6 +251,16 @@ getJasmineRequireObj().Suite = function(j$) {
);
};
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_) {

View File

@@ -161,6 +161,8 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
j$.util.validateTimeout(timeout);
}
this.checkDuplicate_(description, 'spec');
const spec = this.specFactory_(description, fn, timeout, filename);
if (this.currentDeclarationSuite_.markedExcluding) {
spec.exclude();
@@ -170,7 +172,27 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
return spec;
}
checkDuplicate_(description, type) {
if (!this.env_.configuration().forbidDuplicateNames) {
return;
}
if (this.currentDeclarationSuite_.hasChildWithDescription(description)) {
const parentDesc =
this.currentDeclarationSuite_ === this.topSuite
? 'top suite'
: `"${this.currentDeclarationSuite_.getFullName()}"`;
throw new Error(
`Duplicate ${type} name "${description}" found in ${parentDesc}`
);
}
}
suiteFactory_(description, filename) {
if (this.topSuite) {
this.checkDuplicate_(description, 'suite');
}
const config = this.env_.configuration();
const parentSuite = this.currentDeclarationSuite_;
const reportedParentSuiteId =

View File

@@ -11,7 +11,9 @@ getJasmineRequireObj().MapContaining = function(j$) {
}
MapContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
if (!j$.isMap(other)) return false;
if (!j$.isMap(other)) {
return false;
}
for (const [key, value] of this.sample) {
// for each key/value pair in `sample`

View File

@@ -11,7 +11,9 @@ getJasmineRequireObj().SetContaining = function(j$) {
}
SetContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
if (!j$.isSet(other)) return false;
if (!j$.isSet(other)) {
return false;
}
for (const item of this.sample) {
// for each item in `sample` there should be at least one matching item in `other`

View File

@@ -224,9 +224,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
/**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
* that will succeed if the actual value being compared is an instance of the specified class/constructor.
* @name jasmine.any
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* value being compared is an instance of the specified class/constructor.
* @name asymmetricEqualityTesters.any
* @emittedName jasmine.any
* @since 1.3.0
* @function
* @param {Constructor} clazz - The constructor to check against.
@@ -236,9 +237,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
/**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
* that will succeed if the actual value being compared is not `null` and not `undefined`.
* @name jasmine.anything
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* value being compared is not `null` and not `undefined`.
* @name asymmetricEqualityTesters.anything
* @emittedName jasmine.anything
* @since 2.2.0
* @function
*/
@@ -247,9 +249,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
/**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
* that will succeed if the actual value being compared is `true` or anything truthy.
* @name jasmine.truthy
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* value being compared is `true` or anything truthy.
* @name asymmetricEqualityTesters.truthy
* @emittedName jasmine.truthy
* @since 3.1.0
* @function
*/
@@ -258,9 +261,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
/**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
* that will succeed if the actual value being compared is `null`, `undefined`, `0`, `false` or anything falsey.
* @name jasmine.falsy
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* value being compared is `null`, `undefined`, `0`, `false` or anything
* falsy.
* @name asymmetricEqualityTesters.falsy
* @emittedName jasmine.falsy
* @since 3.1.0
* @function
*/
@@ -269,9 +274,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
/**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
* that will succeed if the actual value being compared is empty.
* @name jasmine.empty
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* value being compared is empty.
* @name asymmetricEqualityTesters.empty
* @emittedName jasmine.empty
* @since 3.1.0
* @function
*/
@@ -280,10 +286,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
/**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher}
* that passes if the actual value is the same as the sample as determined
* by the `===` operator.
* @name jasmine.is
* Get an {@link AsymmetricEqualityTester} that passes if the actual value is
* the same as the sample as determined by the `===` operator.
* @name asymmetricEqualityTesters.is
* @emittedName jasmine.is
* @function
* @param {Object} sample - The value to compare the actual to.
*/
@@ -292,9 +298,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
/**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
* that will succeed if the actual value being compared is not empty.
* @name jasmine.notEmpty
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* value being compared is not empty.
* @name asymmetricEqualityTesters.notEmpty
* @emittedName jasmine.notEmpty
* @since 3.1.0
* @function
*/
@@ -303,9 +310,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
/**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
* that will succeed if the actual value being compared contains at least the keys and values.
* @name jasmine.objectContaining
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* value being compared contains at least the specified keys and values.
* @name asymmetricEqualityTesters.objectContaining
* @emittedName jasmine.objectContaining
* @since 1.3.0
* @function
* @param {Object} sample - The subset of properties that _must_ be in the actual.
@@ -315,9 +323,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
/**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
* that will succeed if the actual value is a `String` that matches the `RegExp` or `String`.
* @name jasmine.stringMatching
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* value is a `String` that matches the `RegExp` or `String`.
* @name asymmetricEqualityTesters.stringMatching
* @emittedName jasmine.stringMatching
* @since 2.2.0
* @function
* @param {RegExp|String} expected
@@ -327,9 +336,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
/**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
* that will succeed if the actual value is a `String` that contains the specified `String`.
* @name jasmine.stringContaining
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* value is a `String` that contains the specified `String`.
* @name asymmetricEqualityTesters.stringContaining
* @emittedName jasmine.stringContaining
* @since 3.10.0
* @function
* @param {String} expected
@@ -339,9 +349,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
/**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
* that will succeed if the actual value is an `Array` that contains at least the elements in the sample.
* @name jasmine.arrayContaining
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* value is an `Array` that contains at least the elements in the sample.
* @name asymmetricEqualityTesters.arrayContaining
* @emittedName jasmine.arrayContaining
* @since 2.2.0
* @function
* @param {Array} sample
@@ -351,9 +362,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
/**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
* that will succeed if the actual value is an `Array` that contains all of the elements in the sample in any order.
* @name jasmine.arrayWithExactContents
* Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* value is an `Array` that contains all of the elements in the sample in
* any order.
* @name asymmetricEqualityTesters.arrayWithExactContents
* @emittedName jasmine.arrayWithExactContents
* @since 2.8.0
* @function
* @param {Array} sample
@@ -363,10 +376,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
/**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
* that will succeed if every key/value pair in the sample passes the deep equality comparison
* Get an {@link AsymmetricEqualityTester} that will succeed if every
* key/value pair in the sample passes the deep equality comparison
* with at least one key/value pair in the actual value being compared
* @name jasmine.mapContaining
* @name asymmetricEqualityTesters.mapContaining
* @emittedName jasmine.mapContaining
* @since 3.5.0
* @function
* @param {Map} sample - The subset of items that _must_ be in the actual.
@@ -376,10 +390,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
};
/**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
* that will succeed if every item in the sample passes the deep equality comparison
* Get an {@link AsymmetricEqualityTester} that will succeed if every item
* in the sample passes the deep equality comparison
* with at least one item in the actual value being compared
* @name jasmine.setContaining
* @name asymmetricEqualityTesters.setContaining
* @emittedName jasmine.setContaining
* @since 3.5.0
* @function
* @param {Set} sample - The subset of items that _must_ be in the actual.
@@ -435,14 +450,26 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
* handling will be restored when the promise returned from the callback is
* settled.
*
* When the JavaScript runtime reports an uncaught error or unhandled rejection,
* the spy will be called with a single parameter representing Jasmine's best
* effort at describing the error. This parameter may be of any type, because
* JavaScript allows anything to be thrown or used as the reason for a
* rejected promise, but Error instances and strings are most common.
*
* Note: The JavaScript runtime may deliver uncaught error events and unhandled
* rejection events asynchronously, especially in browsers. If the event
* occurs after the promise returned from the callback is settled, it won't
* be routed to the spy even if the underlying error occurred previously.
* It's up to you to ensure that the returned promise isn't resolved until
* all of the error/rejection events that you want to handle have occurred.
* It's up to you to ensure that all of the error/rejection events that you
* want to handle have occurred before you resolve the promise returned from
* the callback.
*
* You must await the return value of spyOnGlobalErrorsAsync.
* You must ensure that the `it`/`beforeEach`/etc fn that called
* `spyOnGlobalErrorsAsync` does not signal completion until after the
* promise returned by `spyOnGlobalErrorsAsync` is resolved. Normally this is
* done by `await`ing the returned promise. Leaving the global error spy
* installed after the `it`/`beforeEach`/etc fn that installed it signals
* completion is likely to cause problems and is not supported.
* @name jasmine.spyOnGlobalErrorsAsync
* @function
* @async

View File

@@ -4,13 +4,21 @@ getJasmineRequireObj().buildExpectationResult = function(j$) {
const exceptionFormatter = new j$.ExceptionFormatter();
/**
* @typedef Expectation
* Describes the result of evaluating an expectation
*
* Note: The expected and actual properties are deprecated and may be removed
* in a future release. In many Jasmine configurations they are passed
* through JSON serialization and deserialization, which is inherently
* lossy. In such cases, the expected and actual values may be placeholders
* or approximations of the original objects.
*
* @typedef ExpectationResult
* @property {String} matcherName - The name of the matcher that was executed for this expectation.
* @property {String} message - The failure message for the expectation.
* @property {String} stack - The stack trace for the failure if available.
* @property {Boolean} passed - Whether the expectation passed or failed.
* @property {Object} expected - If the expectation failed, what was the expected value.
* @property {Object} actual - If the expectation failed, what actual value was produced.
* @property {Object} expected - Deprecated. If the expectation failed, what was the expected value.
* @property {Object} actual - Deprecated. If the expectation failed, what actual value was produced.
* @property {String|undefined} globalErrorType - The type of an error that
* is reported on the top suite. Valid values are undefined, "afterAll",
* "load", "lateExpectation", and "lateError".

View File

@@ -12,7 +12,9 @@ getJasmineRequireObj().toBePending = function(j$) {
return {
compare: function(actual) {
if (!j$.isPromiseLike(actual)) {
throw new Error('Expected toBePending to be called on a promise.');
throw new Error(
`Expected toBePending to be called on a promise but was on a ${typeof actual}.`
);
}
const want = {};
return Promise.race([actual, Promise.resolve(want)]).then(

View File

@@ -14,7 +14,9 @@ getJasmineRequireObj().toBeRejected = function(j$) {
return {
compare: function(actual) {
if (!j$.isPromiseLike(actual)) {
throw new Error('Expected toBeRejected to be called on a promise.');
throw new Error(
`Expected toBeRejected to be called on a promise but was on a ${typeof actual}.`
);
}
return actual.then(
function() {

View File

@@ -16,7 +16,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) {
compare: function(actualPromise, expectedValue) {
if (!j$.isPromiseLike(actualPromise)) {
throw new Error(
'Expected toBeRejectedWith to be called on a promise.'
`Expected toBeRejectedWith to be called on a promise but was on a ${typeof actualPromise}.`
);
}

View File

@@ -19,7 +19,7 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) {
compare: function(actualPromise, arg1, arg2) {
if (!j$.isPromiseLike(actualPromise)) {
throw new Error(
'Expected toBeRejectedWithError to be called on a promise.'
`Expected toBeRejectedWithError to be called on a promise but was on a ${typeof actualPromise}.`
);
}

View File

@@ -14,7 +14,9 @@ getJasmineRequireObj().toBeResolved = function(j$) {
return {
compare: function(actual) {
if (!j$.isPromiseLike(actual)) {
throw new Error('Expected toBeResolved to be called on a promise.');
throw new Error(
`Expected toBeResolved to be called on a promise but was on a ${typeof actual}.`
);
}
return actual.then(

View File

@@ -15,7 +15,9 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) {
return {
compare: function(actualPromise, expectedValue) {
if (!j$.isPromiseLike(actualPromise)) {
throw new Error('Expected toBeResolvedTo to be called on a promise.');
throw new Error(
`Expected toBeResolvedTo to be called on a promise but was on a ${typeof actualPromise}.`
);
}
function prefix(passed) {

View File

@@ -483,7 +483,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
diffBuilder.recordMismatch(
objectKeysAreDifferentFormatter.bind(null, this.pp)
);
return false;
result = false;
}
for (const key of aKeys) {

View File

@@ -18,6 +18,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
'toBeTrue',
'toBeTruthy',
'toBeUndefined',
'toBeNullish',
'toContain',
'toEqual',
'toHaveSize',
@@ -27,7 +28,9 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
'toHaveBeenCalledTimes',
'toHaveBeenCalledWith',
'toHaveClass',
'toHaveClasses',
'toHaveSpyInteractions',
'toHaveNoOtherSpyInteractions',
'toMatch',
'toThrow',
'toThrowError',

View File

@@ -0,0 +1,21 @@
getJasmineRequireObj().toBeNullish = function() {
/**
* {@link expect} the actual value to be `null` or `undefined`.
* @function
* @name matchers#toBeNullish
* @since 5.6.0
* @example
* expect(result).toBeNullish():
*/
function toBeNullish() {
return {
compare: function(actual) {
return {
pass: null === actual || void 0 === actual
};
}
};
}
return toBeNullish;
};

View File

@@ -34,6 +34,8 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) {
result.pass = actual.calls.any();
actual.calls.all().forEach(call => (call.verified = true));
result.message = result.pass
? 'Expected spy ' + actual.and.identity + ' not to have been called.'
: 'Expected spy ' + actual.and.identity + ' to have been called.';

View File

@@ -50,6 +50,9 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) {
result.pass = latest1stSpyCall < first2ndSpyCall;
if (result.pass) {
firstSpy.calls.mostRecent().verified = true;
latterSpy.calls.first().verified = true;
result.message =
'Expected spy ' +
firstSpy.and.identity +

View File

@@ -13,7 +13,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
* @example
* expect(mySpy).toHaveBeenCalledOnceWith('foo', 'bar', 2);
*/
function toHaveBeenCalledOnceWith(util) {
function toHaveBeenCalledOnceWith(matchersUtil) {
return {
compare: function() {
const args = Array.prototype.slice.call(arguments, 0),
@@ -22,20 +22,29 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
if (!j$.isSpy(actual)) {
throw new Error(
getErrorMsg('Expected a spy, but got ' + util.pp(actual) + '.')
getErrorMsg(
'Expected a spy, but got ' + matchersUtil.pp(actual) + '.'
)
);
}
const prettyPrintedCalls = actual.calls
.allArgs()
.map(function(argsForCall) {
return ' ' + util.pp(argsForCall);
return ' ' + matchersUtil.pp(argsForCall);
});
if (
actual.calls.count() === 1 &&
util.contains(actual.calls.allArgs(), expectedArgs)
matchersUtil.contains(actual.calls.allArgs(), expectedArgs)
) {
const firstIndex = actual.calls
.all()
.findIndex(call => matchersUtil.equals(call.args, expectedArgs));
if (firstIndex > -1) {
actual.calls.all()[firstIndex].verified = true;
}
return {
pass: true,
message:
@@ -43,7 +52,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
actual.and.identity +
' to have been called 0 times, multiple times, or once, but with arguments different from:\n' +
' ' +
util.pp(expectedArgs) +
matchersUtil.pp(expectedArgs) +
'\n' +
'But the actual call was:\n' +
prettyPrintedCalls.join(',\n') +
@@ -54,7 +63,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
function getDiffs() {
return actual.calls.allArgs().map(function(argsForCall, callIx) {
const diffBuilder = new j$.DiffBuilder();
util.equals(argsForCall, expectedArgs, diffBuilder);
matchersUtil.equals(argsForCall, expectedArgs, diffBuilder);
return diffBuilder.getMessage();
});
}
@@ -87,7 +96,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
actual.and.identity +
' to have been called only once, and with given args:\n' +
' ' +
util.pp(expectedArgs) +
matchersUtil.pp(expectedArgs) +
'\n' +
butString()
};

View File

@@ -36,23 +36,35 @@ getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) {
}
actual = args[0];
const calls = actual.calls.count();
const callsCount = actual.calls.count();
const timesMessage = expected === 1 ? 'once' : expected + ' times';
result.pass = calls === expected;
result.pass = callsCount === expected;
if (result.pass) {
const allCalls = actual.calls.all();
const max = Math.min(expected, callsCount);
for (let i = 0; i < max; i++) {
allCalls[i].verified = true;
}
}
result.message = result.pass
? 'Expected spy ' +
actual.and.identity +
' not to have been called ' +
timesMessage +
'. It was called ' +
calls +
callsCount +
' times.'
: 'Expected spy ' +
actual.and.identity +
' to have been called ' +
timesMessage +
'. It was called ' +
calls +
callsCount +
' times.';
return result;
}

View File

@@ -44,6 +44,11 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
}
if (matchersUtil.contains(actual.calls.allArgs(), expectedArgs)) {
actual.calls
.all()
.filter(call => matchersUtil.equals(call.args, expectedArgs))
.forEach(call => (call.verified = true));
result.pass = true;
result.message = function() {
return (

View File

@@ -0,0 +1,34 @@
getJasmineRequireObj().toHaveClasses = function(j$) {
/**
* {@link expect} the actual value to be a DOM element that has the expected classes
* @function
* @name matchers#toHaveClasses
* @since 5.6.0
* @param {Object} expected - The class names to test for
* @example
* const el = document.createElement('div');
* el.className = 'foo bar baz';
* expect(el).toHaveClasses(['bar', 'baz']);
*/
function toHaveClasses(matchersUtil) {
return {
compare: function(actual, expected) {
if (!isElement(actual)) {
throw new Error(matchersUtil.pp(actual) + ' is not a DOM element');
}
return {
pass: expected.every(e => actual.classList.contains(e))
};
}
};
}
function isElement(maybeEl) {
return (
maybeEl && maybeEl.classList && j$.isFunction_(maybeEl.classList.contains)
);
}
return toHaveClasses;
};

View File

@@ -0,0 +1,85 @@
getJasmineRequireObj().toHaveNoOtherSpyInteractions = function(j$) {
const getErrorMsg = j$.formatErrorMsg(
'<toHaveNoOtherSpyInteractions>',
'expect(<spyObj>).toHaveNoOtherSpyInteractions()'
);
/**
* {@link expect} the actual (a {@link SpyObj}) spies to have not been called except interactions which was already tracked with `toHaveBeenCalled`.
* @function
* @name matchers#toHaveNoOtherSpyInteractions
* @example
* expect(mySpyObj).toHaveNoOtherSpyInteractions();
* expect(mySpyObj).not.toHaveNoOtherSpyInteractions();
*/
function toHaveNoOtherSpyInteractions(matchersUtil) {
return {
compare: function(actual) {
const result = {};
if (!j$.isObject_(actual)) {
throw new Error(
getErrorMsg('Expected an object, but got ' + typeof actual + '.')
);
}
if (arguments.length > 1) {
throw new Error(getErrorMsg('Does not take arguments'));
}
result.pass = true;
let hasSpy = false;
const unexpectedCalls = [];
for (const spy of Object.values(actual)) {
if (!j$.isSpy(spy)) {
continue;
}
hasSpy = true;
const unverifiedCalls = spy.calls
.all()
.filter(call => !call.verified);
if (unverifiedCalls.length > 0) {
result.pass = false;
}
unverifiedCalls.forEach(unverifiedCall => {
unexpectedCalls.push([
spy.and.identity,
matchersUtil.pp(unverifiedCall.args)
]);
});
}
if (!hasSpy) {
throw new Error(
getErrorMsg(
'Expected an object with spies, but object has no spies.'
)
);
}
if (result.pass) {
result.message =
"Expected a spy object to have other spy interactions but it didn't.";
} else {
const ppUnexpectedCalls = unexpectedCalls
.map(([spyName, args]) => ` ${spyName} called with ${args}`)
.join(',\n');
result.message =
'Expected a spy object to have no other spy interactions, but it had the following calls:\n' +
ppUnexpectedCalls +
'.';
}
return result;
}
};
}
return toHaveNoOtherSpyInteractions;
};

View File

@@ -9,7 +9,7 @@ getJasmineRequireObj().toHaveSize = function(j$) {
* array = [1,2];
* expect(array).toHaveSize(2);
*/
function toHaveSize() {
function toHaveSize(matchersUtil) {
return {
compare: function(actual, expected) {
const result = {
@@ -24,12 +24,29 @@ getJasmineRequireObj().toHaveSize = function(j$) {
throw new Error('Cannot get size of ' + actual + '.');
}
let actualSize;
if (j$.isSet(actual) || j$.isMap(actual)) {
result.pass = actual.size === expected;
actualSize = actual.size;
} else if (isLength(actual.length)) {
result.pass = actual.length === expected;
actualSize = actual.length;
} else {
result.pass = Object.keys(actual).length === expected;
actualSize = Object.keys(actual).length;
}
result.pass = actualSize === expected;
if (!result.pass) {
result.message = function() {
return (
'Expected ' +
matchersUtil.pp(actual) +
' with size ' +
actualSize +
' to have size ' +
expected +
'.'
);
};
}
return result;

View File

@@ -32,7 +32,10 @@ getJasmineRequireObj().toHaveSpyInteractions = function(j$) {
let hasSpy = false;
const calledSpies = [];
for (const spy of Object.values(actual)) {
if (!j$.isSpy(spy)) continue;
if (!j$.isSpy(spy)) {
continue;
}
hasSpy = true;
if (spy.calls.any()) {

View File

@@ -1,4 +1,30 @@
getJasmineRequireObj().reporterEvents = function() {
/**
* Used to tell Jasmine what optional or uncommonly implemented features
* the reporter supports. If not specified, the defaults described in
* {@link ReporterCapabilities} will apply.
* @name Reporter#reporterCapabilities
* @type ReporterCapabilities | undefined
* @since 5.0
*/
/**
* Used to tell Jasmine what optional or uncommonly implemented features
* the reporter supports.
* @interface ReporterCapabilities
* @see Reporter#reporterCapabilities
* @since 5.0
*/
/**
* Indicates whether the reporter supports parallel execution. Jasmine will
* not allow parallel execution unless all reporters that are in use set this
* capability to true.
* @name ReporterCapabilities#parallel
* @type boolean | undefined
* @default false
* @see running_specs_in_parallel
* @since 5.0
*/
const events = [
/**
* `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts.

View File

@@ -246,7 +246,7 @@ getJasmineRequireObj().interface = function(jasmine, env) {
* @return {matchers}
*/
throwUnlessAsync: function(actual) {
return env.throwUnless(actual);
return env.throwUnlessAsync(actual);
},
/**
@@ -345,6 +345,12 @@ getJasmineRequireObj().interface = function(jasmine, env) {
}),
/**
* <p>Members of the jasmine global.</p>
* <p>Note: The members of the
* {@link asymmetricEqualityTesters|asymmetricEqualityTesters namespace}
* are also accessed via the jasmine global, but due to jsdoc limitations
* they are not listed here.</p>
*
* @namespace jasmine
*/
jasmine: jasmine
@@ -478,5 +484,27 @@ getJasmineRequireObj().interface = function(jasmine, env) {
return env.setDefaultSpyStrategy(defaultStrategyFn);
};
/**
* {@link AsymmetricEqualityTester|Asymmetric equality testers} allow for
* non-exact matching in matchers that use Jasmine's deep value equality
* semantics, such as {@link matchers#toEqual|toEqual},
* {@link matchers#toContain|toContain}, and
* {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}.
*
* @example
* const someComplexObject = {
* foo: 'bar',
* baz: 'a string that contains "something"',
* qux: 'whatever'
* };
* // Passes.
* expect(someComplexObject).toEqual(jasmine.objectContaining({
* foo: 'bar',
* baz: jasmine.stringContaining('something')
* });
*
* @namespace asymmetricEqualityTesters
*/
return jasmineInterface;
};

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