Compare commits

..

39 Commits

Author SHA1 Message Date
Steve Gravrock
f5915d7963 Bump version to 5.7.1 2025-05-01 19:31:30 -07:00
Steve Gravrock
15587f3ce3 Merge branch 'autotickuninstall' of https://github.com/atscott/jasmine
Merges #2057 from @atscott
2025-05-01 16:46:30 -07:00
Andrew Scott
3ecddc2555 fixup! fix(Clock): Ensure that uninstalling the clock also stops auto tick 2025-05-01 10:25:24 -07:00
Andrew Scott
84daa0f5dc fix(Clock): Ensure that uninstalling the clock also stops auto tick
The autotick feature mistakenly does not account for the clock being a
singleton and the re-installation of the clock causes the auto ticking
exit conditions to become true again, before it has a chance to break.
2025-04-30 13:38:10 -07:00
Steve Gravrock
c6b3e947e9 Fixed errors in 5.7.0 release notes 2025-04-29 07:28:36 -07:00
Steve Gravrock
0e604de0db Bump version to 5.7.0 2025-04-26 09:29:07 -07:00
Steve Gravrock
e7ca9c5765 Built distribution 2025-04-26 09:28:21 -07:00
Steve Gravrock
cbff6f95cb Fixed autoTick jsdoc 2025-04-26 08:27:12 -07:00
Steve Gravrock
361640f52e Document that SpecResult#filename and SuiteResult#filename are wrong in some common scenarios
See:

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

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

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

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

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

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

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

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

resolves #1725
resolves #1932

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

View File

@@ -61,7 +61,7 @@ jobs:
at: .
- run:
name: Run tests in parallel
command: npx grunt execSpecsInParallel
command: npm run test:parallel
test_browsers: &test_browsers
executor: node18
@@ -71,12 +71,14 @@ jobs:
- run:
name: Install Sauce Connect
command: |
cd /tmp
curl https://saucelabs.com/downloads/sc-4.7.1-linux.tar.gz | tar zxf -
chmod +x sc-4.7.1-linux/bin/sc
tmpdir=$(mktemp -d)
cd "$tmpdir"
curl https://saucelabs.com/downloads/sauce-connect/5.2.2/sauce-connect-5.2.2_linux.x86_64.tar.gz | tar zxf -
chmod +x sc
mkdir ~/workspace/bin
cp sc-4.7.1-linux/bin/sc ~/workspace/bin
~/workspace/bin/sc --version
cp sc ~/workspace/bin
echo "Sauce Connect version info:"
~/workspace/bin/sc version
- run:
name: Run tests
command: |
@@ -84,13 +86,13 @@ jobs:
# cleanly if we kill it from a different step than it started in.
export PATH=$PATH:$HOME/workspace/bin
export SAUCE_TUNNEL_IDENTIFIER=$CIRCLE_WORKFLOW_JOB_ID
scripts/start-sauce-connect sauce-pidfile
export SAUCE_TUNNEL_NAME=$CIRCLE_WORKFLOW_JOB_ID
scripts/start-sauce-connect
set +o errexit
scripts/run-all-browsers
exitcode=$?
set -o errexit
scripts/stop-sauce-connect $(cat sauce-pidfile)
scripts/stop-sauce-connect
exit $exitcode
workflows:

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
<a name="README">[<img src="https://rawgithub.com/jasmine/jasmine/main/images/jasmine-horizontal.svg" width="400px" />](http://jasmine.github.io)</a>
<a name="README"><img src="https://raw.githubusercontent.com/jasmine/jasmine/main/images/jasmine-horizontal.svg" width="400px" alt="Jasmine"></a>
# A JavaScript Testing Framework

View File

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

54
eslint.config.mjs Normal file
View File

@@ -0,0 +1,54 @@
import { defineConfig } from "eslint/config";
import globals from "globals";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default defineConfig([{
extends: compat.extends("plugin:compat/recommended"),
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
ecmaVersion: 2018,
sourceType: "commonjs",
},
rules: {
curly: "error",
quotes: ["error", "single", {
avoidEscape: true,
}],
"no-unused-vars": ["error", {
args: "none",
}],
"no-implicit-globals": "error",
"block-spacing": "error",
"func-call-spacing": ["error", "never"],
"key-spacing": "error",
"no-tabs": "error",
"no-trailing-spaces": "error",
"no-whitespace-before-property": "error",
semi: ["error", "always"],
"space-before-blocks": "error",
"no-eval": "error",
"no-var": "error",
"no-debugger": "error",
"no-console": "error",
},
}]);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,6 +21,7 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// eslint-disable-next-line no-unused-vars,no-var
var getJasmineRequireObj = (function(jasmineGlobal) {
let jasmineRequire;
@@ -791,11 +792,11 @@ getJasmineRequireObj().Spec = function(j$) {
this.onStart = attrs.onStart || function() {};
this.autoCleanClosures =
attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures;
this.getSpecName =
attrs.getSpecName ||
function() {
return '';
};
this.getPath = function() {
return attrs.getPath ? attrs.getPath(this) : [];
};
this.onLateError = attrs.onLateError || function() {};
this.catchingExceptions =
attrs.catchingExceptions ||
@@ -918,6 +919,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.
* Note: The value may be incorrect if zone.js is installed or
* `it`/`fit`/`xit` have been replaced with versions that don't maintain the
* same call stack height as the originals.
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec.
* @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec.
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec.
@@ -1021,7 +1025,7 @@ getJasmineRequireObj().Spec = function(j$) {
};
Spec.prototype.getFullName = function() {
return this.getSpecName(this);
return this.getPath().join(' ');
};
Spec.prototype.addDeprecationWarning = function(deprecation) {
@@ -1075,6 +1079,10 @@ getJasmineRequireObj().Spec = function(j$) {
* @since 2.0.0
*/
Object.defineProperty(Spec.prototype, 'metadata', {
// NOTE: Although most of jasmine-core only exposes these metadata objects,
// actual Spec instances are still passed to Configuration#specFilter. Until
// that is fixed, it's important to make sure that all metadata properties
// also exist in compatible form on the underlying Spec.
get: function() {
if (!this.metadata_) {
this.metadata_ = {
@@ -1103,7 +1111,16 @@ getJasmineRequireObj().Spec = function(j$) {
* @returns {string}
* @since 2.0.0
*/
getFullName: this.getFullName.bind(this)
getFullName: this.getFullName.bind(this),
/**
* The full path of the spec, as an array of names.
* @name Spec#getPath
* @function
* @returns {Array.<string>}
* @since 5.7.0
*/
getPath: this.getPath.bind(this)
};
}
@@ -1545,7 +1562,9 @@ getJasmineRequireObj().Env = function(j$) {
// If we get here, all results have been reported and there's nothing we
// can do except log the result and hope the user sees it.
// eslint-disable-next-line no-console
console.error('Jasmine received a result after the suite finished:');
// eslint-disable-next-line no-console
console.error(expectationResult);
}
@@ -2693,7 +2712,8 @@ getJasmineRequireObj().buildExpectationResult = function(j$) {
* in a future release. In many Jasmine configurations they are passed
* through JSON serialization and deserialization, which is inherently
* lossy. In such cases, the expected and actual values may be placeholders
* or approximations of the original objects.
* or approximations of the original objects. jasmine-browser-runner 3.0 and
* later omits them entirely.
*
* @typedef ExpectationResult
* @property {String} matcherName - The name of the matcher that was executed for this expectation.
@@ -3065,6 +3085,15 @@ getJasmineRequireObj().Clock = function() {
let installed = false;
let delayedFunctionScheduler;
let timer;
// Tracks how the clock ticking behaves.
// By default, the clock only ticks when the user manually calls a tick method.
// There is also an 'auto' mode which will advance the clock automatically to
// to the next task. Once enabled, there is currently no mechanism for users
// to disable the auto ticking.
let tickMode = {
mode: 'manual',
counter: 0
};
this.FakeTimeout = FakeTimeout;
@@ -3096,6 +3125,10 @@ getJasmineRequireObj().Clock = function() {
* @function
*/
this.uninstall = function() {
// Ensure auto ticking loop is aborted when clock is uninstalled
if (tickMode.mode === 'auto') {
tickMode = { mode: 'manual', counter: tickMode.counter + 1 };
}
delayedFunctionScheduler = null;
mockDate.uninstall();
replace(global, realTimingFunctions);
@@ -3174,8 +3207,98 @@ getJasmineRequireObj().Clock = function() {
}
};
/**
* Updates the clock to automatically advance time.
*
* With this mode, the clock advances to the first scheduled timer and fires it, in a loop.
* Between each timer, it will also break the event loop, allowing any scheduled promise
callbacks to execute _before_ running the next one.
*
* This mode allows tests to be authored in a way that does not need to be aware of the
* mock clock. Consequently, tests which have been authored without a mock clock installed
* can one with auto tick enabled without any other updates to the test logic.
*
* In many cases, this can greatly improve test execution speed because asynchronous tasks
* will execute as quickly as possible rather than waiting real time to complete.
*
* Furthermore, tests can be authored in a consistent manner. They can always be written in an asynchronous style
* rather than having `tick` sprinkled throughout the tests with mock time in order to manually
* advance the clock.
*
* When auto tick is enabled, `tick` can still be used to synchronously advance the clock if necessary.
* @name Clock#autoTick
* @function
* @since 5.7.0
*/
this.autoTick = function() {
if (tickMode.mode === 'auto') {
return;
}
tickMode = { mode: 'auto', counter: tickMode.counter + 1 };
advanceUntilModeChanges();
};
return this;
// Advances the Clock's time until the mode changes.
//
// The time is advanced asynchronously, giving microtasks and events a chance
// to run before each timer runs.
//
// @function
// @return {!Promise<undefined>}
async function advanceUntilModeChanges() {
if (!installed) {
throw new Error(
'Mock clock is not installed, use jasmine.clock().install()'
);
}
const { counter } = tickMode;
while (true) {
await newMacrotask();
if (
tickMode.counter !== counter ||
!installed ||
delayedFunctionScheduler === null
) {
return;
}
if (!delayedFunctionScheduler.isEmpty()) {
delayedFunctionScheduler.runNextQueuedFunction(function(millis) {
mockDate.tick(millis);
});
}
}
}
// Waits until a new macro task.
//
// Used with autoTick(), which is meant to act when the test is waiting, we need
// to insert ourselves in the macro task queue.
//
// @return {!Promise<undefined>}
async function newMacrotask() {
// MessageChannel ensures that setTimeout is not throttled to 4ms.
// https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#reasons_for_delays_longer_than_specified
// https://stackblitz.com/edit/stackblitz-starters-qtlpcc
// Note: This trick does not work in Safari, which will still throttle the setTimeout
const channel = new MessageChannel();
await new Promise(resolve => {
channel.port1.onmessage = resolve;
channel.port2.postMessage(undefined);
});
channel.port1.close();
channel.port2.close();
// setTimeout ensures that we interleave with other setTimeouts.
await new Promise(resolve => {
realTimingFunctions.setTimeout.call(global, resolve);
});
}
function originalTimingFunctionsIntact() {
return (
global.setTimeout === realTimingFunctions.setTimeout &&
@@ -3406,6 +3529,37 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
}
};
// Returns whether there are any scheduled functions.
// Returns true if there are any scheduled functions, otherwise false.
this.isEmpty = function() {
return this.scheduledFunctions_.length === 0;
};
// Runs the next timeout in the queue, advancing the clock.
this.runNextQueuedFunction = function(tickDate) {
if (this.scheduledLookup_.length === 0) {
return;
}
const newCurrentTime = this.scheduledLookup_[0];
if (newCurrentTime >= this.currentTime_) {
tickDate(newCurrentTime - this.currentTime_);
this.currentTime_ = newCurrentTime;
}
const funcsAtTime = this.scheduledFunctions_[this.currentTime_];
const fn = funcsAtTime.shift();
if (funcsAtTime.length === 0) {
delete this.scheduledFunctions_[this.currentTime_];
this.scheduledLookup_.splice(0, 1);
}
if (fn.recurring) {
this.reschedule_(fn);
}
fn.funcToCall.apply(null, fn.params || []);
};
return this;
}
@@ -3542,6 +3696,7 @@ getJasmineRequireObj().Deprecator = function(j$) {
Deprecator.prototype.log_ = function(runnable, deprecation, options) {
if (j$.isError_(deprecation)) {
// eslint-disable-next-line no-console
console.error(deprecation);
return;
}
@@ -3564,6 +3719,7 @@ getJasmineRequireObj().Deprecator = function(j$) {
context += '\n' + verboseNote;
}
// eslint-disable-next-line no-console
console.error('DEPRECATION: ' + deprecation + context);
};
@@ -5861,6 +6017,7 @@ getJasmineRequireObj().toBeInstanceOf = function(j$) {
try {
expectedMatcher = new j$.Any(expected);
pass = expectedMatcher.asymmetricMatch(actual);
// eslint-disable-next-line no-unused-vars
} catch (error) {
throw new Error(
usageError('Expected value is not a constructor function')
@@ -7618,6 +7775,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
) {
try {
this.emitScalar(value.toString());
// eslint-disable-next-line no-unused-vars
} catch (e) {
this.emitScalar('has-invalid-toString-method');
}
@@ -7864,6 +8022,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
value.toString !== Object.prototype.toString &&
value.toString() !== Object.prototype.toString.call(value)
);
// eslint-disable-next-line no-unused-vars
} catch (e) {
// The custom toString() threw.
return true;
@@ -7942,6 +8101,7 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}
function fallbackOnMultipleDone() {
// eslint-disable-next-line no-console
console.error(
new Error(
"An asynchronous function called its 'done' " +
@@ -8055,6 +8215,7 @@ getJasmineRequireObj().QueueRunner = function(j$) {
// Any error we catch here is probably due to a bug in Jasmine,
// and it's not likely to end up anywhere useful if we let it
// propagate. Log it so it can at least show up when debugging.
// eslint-disable-next-line no-console
console.error(error);
}
}
@@ -9894,6 +10055,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
let value;
try {
value = obj[prop];
// eslint-disable-next-line no-unused-vars
} catch (e) {
return false;
}
@@ -10326,6 +10488,9 @@ 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.
* Note: The value may be incorrect if zone.js is installed or
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions that
* don't maintain the same call stack height as the originals.
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite.
* @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite.
@@ -10808,9 +10973,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
resultCallback: (result, next) => {
this.specResultCallback_(spec, result, next);
},
getSpecName: function(spec) {
return getSpecName(spec, suite);
},
getPath: spec => this.getSpecPath_(spec, suite),
onStart: (spec, next) => this.specStarted_(spec, suite, next),
description: description,
userContext: function() {
@@ -10827,6 +10990,17 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
return spec;
}
getSpecPath_(spec, suite) {
const path = [spec.description];
while (suite && suite !== this.topSuite) {
path.unshift(suite.description);
suite = suite.parentSuite;
}
return path;
}
unfocusAncestor_() {
const focusedAncestor = findFocusedAncestor(
this.currentDeclarationSuite_
@@ -10890,16 +11064,6 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
};
}
function getSpecName(spec, suite) {
const fullName = [spec.description],
suiteFullName = suite.getFullName();
if (suiteFullName !== '') {
fullName.unshift(suiteFullName);
}
return fullName.join(' ');
}
return SuiteBuilder;
};
@@ -11231,5 +11395,5 @@ getJasmineRequireObj().UserContext = function(j$) {
};
getJasmineRequireObj().version = function() {
return '5.6.0';
return '5.7.1';
};

View File

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

62
release_notes/5.7.0.md Normal file
View File

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

28
release_notes/5.7.1.md Normal file
View File

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

View File

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

View File

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

View File

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

28
scripts/runSpecsInNode.js Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -264,6 +264,42 @@ describe('DelayedFunctionScheduler', function() {
expect(fn).toHaveBeenCalled();
});
it('runs the next scheduled funtion', function() {
const scheduler = new jasmineUnderTest.DelayedFunctionScheduler();
const fn = jasmine.createSpy('fn');
const tickSpy = jasmine.createSpy('tick');
scheduler.scheduleFunction(fn, 10, [], false, 'foo');
expect(fn).not.toHaveBeenCalled();
scheduler.runNextQueuedFunction(tickSpy);
expect(fn).toHaveBeenCalled();
expect(tickSpy).toHaveBeenCalledWith(10);
});
it('runs the only a single scheduled funtion in a time slot', function() {
const scheduler = new jasmineUnderTest.DelayedFunctionScheduler();
const fn1 = jasmine.createSpy('fn');
const fn2 = jasmine.createSpy('fn2');
const tickSpy = jasmine.createSpy('tick');
scheduler.scheduleFunction(fn1, 10, [], false, 'foo1');
scheduler.scheduleFunction(fn2, 10, [], false, 'foo2');
scheduler.runNextQueuedFunction(tickSpy);
expect(fn1).toHaveBeenCalled();
expect(fn2).not.toHaveBeenCalled();
expect(tickSpy).toHaveBeenCalledWith(10);
tickSpy.calls.reset();
scheduler.runNextQueuedFunction(tickSpy);
expect(fn2).toHaveBeenCalled();
expect(tickSpy).toHaveBeenCalledWith(0);
});
it('updates the mockDate per scheduled time', function() {
const scheduler = new jasmineUnderTest.DelayedFunctionScheduler(),
tickDate = jasmine.createSpy('tickDate');

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1071,7 +1071,6 @@ describe('Env integration', function() {
env.describe('my suite', function() {
env.it('my spec', function() {});
// eslint-disable-next-line no-unused-vars
env.afterAll(function(afterAllDone) {
throw error;
});
@@ -1569,7 +1568,6 @@ describe('Env integration', function() {
env.addReporter(reporter);
jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = 8414;
// eslint-disable-next-line no-unused-vars
env.it("async spec that doesn't call done", function(underTestCallback) {
env.expect(true).toBeTruthy();
jasmine.clock().tick(8416);
@@ -1642,13 +1640,13 @@ describe('Env integration', function() {
realSetTimeout(function() {
try {
jasmine.clock().tick(10);
// eslint-disable-next-line no-unused-vars
} catch (e) {
// don't worry if the clock is already uninstalled
}
}, 100);
});
env.describe('beforeAll', function() {
// eslint-disable-next-line no-unused-vars
env.beforeAll(function(innerDone) {
realSetTimeout(function() {
jasmine.clock().tick(5001);
@@ -1664,7 +1662,6 @@ describe('Env integration', function() {
});
env.describe('afterAll', function() {
// eslint-disable-next-line no-unused-vars
env.afterAll(function(innerDone) {
realSetTimeout(function() {
jasmine.clock().tick(2001);
@@ -1680,7 +1677,6 @@ describe('Env integration', function() {
});
env.describe('beforeEach', function() {
// eslint-disable-next-line no-unused-vars
env.beforeEach(function(innerDone) {
realSetTimeout(function() {
jasmine.clock().tick(1001);
@@ -1696,7 +1692,6 @@ describe('Env integration', function() {
});
env.describe('afterEach', function() {
// eslint-disable-next-line no-unused-vars
env.afterEach(function(innerDone) {
realSetTimeout(function() {
jasmine.clock().tick(4001);
@@ -1713,7 +1708,6 @@ describe('Env integration', function() {
env.it(
'it times out',
// eslint-disable-next-line no-unused-vars
function(innerDone) {
realSetTimeout(function() {
jasmine.clock().tick(6001);
@@ -2699,7 +2693,6 @@ describe('Env integration', function() {
env.addReporter(reporter);
env.describe('async suite', function() {
// eslint-disable-next-line no-unused-vars
env.afterAll(function(innerDone) {
setTimeout(function() {
throw new Error('suite');
@@ -2712,7 +2705,6 @@ describe('Env integration', function() {
env.describe('suite', function() {
env.it(
'async spec',
// eslint-disable-next-line no-unused-vars
function(innerDone) {
setTimeout(function() {
throw new Error('spec');
@@ -4362,6 +4354,7 @@ describe('Env integration', function() {
env.it('a spec', function() {
try {
env.throwUnless(1).toEqual(1);
// eslint-disable-next-line no-unused-vars
} catch (e) {
threw = true;
}
@@ -4375,6 +4368,7 @@ describe('Env integration', function() {
env.it('a spec', function() {
try {
env.throwUnless(1).toEqual(2);
// eslint-disable-next-line no-unused-vars
} catch (e) {}
});
@@ -4418,6 +4412,7 @@ describe('Env integration', function() {
env.it('a spec', async function() {
try {
await env.throwUnlessAsync(Promise.resolve()).toBeResolved();
// eslint-disable-next-line no-unused-vars
} catch (e) {
threw = true;
}
@@ -4431,6 +4426,7 @@ describe('Env integration', function() {
env.it('a spec', async function() {
try {
await env.throwUnlessAsync(Promise.resolve()).toBeRejected();
// eslint-disable-next-line no-unused-vars
} catch (e) {}
});

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
/* eslint-disable compat/compat */
describe('toEqual', function() {
'use strict';

View File

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

View File

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

View File

@@ -1,14 +1,16 @@
describe('npm package', function() {
const path = require('path'),
temp = require('temp').track(),
fs = require('fs');
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');
const { rimrafSync } = require('rimraf');
describe('npm package', function() {
beforeAll(function() {
const shell = require('shelljs'),
pack = shell.exec('npm pack', { silent: true });
this.tarball = pack.stdout.split('\n')[0];
this.tmpDir = temp.mkdirSync(); // automatically deleted on exit
const prefix = path.join(os.tmpdir(), 'jasmine-npm-package');
this.tmpDir = fs.mkdtempSync(prefix);
const untar = shell.exec(
'tar -xzf ' + this.tarball + ' -C ' + this.tmpDir,
@@ -41,6 +43,7 @@ describe('npm package', function() {
afterAll(function() {
fs.unlinkSync(this.tarball);
rimrafSync(this.tmpDir);
});
it('has a root path', function() {

View File

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

View File

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

View File

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

View File

@@ -87,6 +87,37 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
}
};
// Returns whether there are any scheduled functions.
// Returns true if there are any scheduled functions, otherwise false.
this.isEmpty = function() {
return this.scheduledFunctions_.length === 0;
};
// Runs the next timeout in the queue, advancing the clock.
this.runNextQueuedFunction = function(tickDate) {
if (this.scheduledLookup_.length === 0) {
return;
}
const newCurrentTime = this.scheduledLookup_[0];
if (newCurrentTime >= this.currentTime_) {
tickDate(newCurrentTime - this.currentTime_);
this.currentTime_ = newCurrentTime;
}
const funcsAtTime = this.scheduledFunctions_[this.currentTime_];
const fn = funcsAtTime.shift();
if (funcsAtTime.length === 0) {
delete this.scheduledFunctions_[this.currentTime_];
this.scheduledLookup_.splice(0, 1);
}
if (fn.recurring) {
this.reschedule_(fn);
}
fn.funcToCall.apply(null, fn.params || []);
};
return this;
}

View File

@@ -36,6 +36,7 @@ getJasmineRequireObj().Deprecator = function(j$) {
Deprecator.prototype.log_ = function(runnable, deprecation, options) {
if (j$.isError_(deprecation)) {
// eslint-disable-next-line no-console
console.error(deprecation);
return;
}
@@ -58,6 +59,7 @@ getJasmineRequireObj().Deprecator = function(j$) {
context += '\n' + verboseNote;
}
// eslint-disable-next-line no-console
console.error('DEPRECATION: ' + deprecation + context);
};

View File

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

View File

@@ -58,6 +58,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
) {
try {
this.emitScalar(value.toString());
// eslint-disable-next-line no-unused-vars
} catch (e) {
this.emitScalar('has-invalid-toString-method');
}
@@ -304,6 +305,7 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
value.toString !== Object.prototype.toString &&
value.toString() !== Object.prototype.toString.call(value)
);
// eslint-disable-next-line no-unused-vars
} catch (e) {
// The custom toString() threw.
return true;

View File

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

View File

@@ -21,11 +21,11 @@ getJasmineRequireObj().Spec = function(j$) {
this.onStart = attrs.onStart || function() {};
this.autoCleanClosures =
attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures;
this.getSpecName =
attrs.getSpecName ||
function() {
return '';
};
this.getPath = function() {
return attrs.getPath ? attrs.getPath(this) : [];
};
this.onLateError = attrs.onLateError || function() {};
this.catchingExceptions =
attrs.catchingExceptions ||
@@ -148,6 +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.
* Note: The value may be incorrect if zone.js is installed or
* `it`/`fit`/`xit` have been replaced with versions that don't maintain the
* same call stack height as the originals.
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec.
* @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec.
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec.
@@ -251,7 +254,7 @@ getJasmineRequireObj().Spec = function(j$) {
};
Spec.prototype.getFullName = function() {
return this.getSpecName(this);
return this.getPath().join(' ');
};
Spec.prototype.addDeprecationWarning = function(deprecation) {
@@ -305,6 +308,10 @@ getJasmineRequireObj().Spec = function(j$) {
* @since 2.0.0
*/
Object.defineProperty(Spec.prototype, 'metadata', {
// NOTE: Although most of jasmine-core only exposes these metadata objects,
// actual Spec instances are still passed to Configuration#specFilter. Until
// that is fixed, it's important to make sure that all metadata properties
// also exist in compatible form on the underlying Spec.
get: function() {
if (!this.metadata_) {
this.metadata_ = {
@@ -333,7 +340,16 @@ getJasmineRequireObj().Spec = function(j$) {
* @returns {string}
* @since 2.0.0
*/
getFullName: this.getFullName.bind(this)
getFullName: this.getFullName.bind(this),
/**
* The full path of the spec, as an array of names.
* @name Spec#getPath
* @function
* @returns {Array.<string>}
* @since 5.7.0
*/
getPath: this.getPath.bind(this)
};
}

View File

@@ -252,6 +252,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
let value;
try {
value = obj[prop];
// eslint-disable-next-line no-unused-vars
} catch (e) {
return false;
}

View File

@@ -105,6 +105,9 @@ 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.
* Note: The value may be incorrect if zone.js is installed or
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions that
* don't maintain the same call stack height as the originals.
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite.
* @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite.

View File

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

View File

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

View File

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

View File

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

View File

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