diff --git a/.circleci/config.yml b/.circleci/config.yml
index 5eee1065..f2ac69ba 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -58,13 +58,6 @@ jobs:
name: Run tests
command: npm test
- # Warning: Sometimes takes a very long time (>25 minutes) on Circle.
- # Probably not a good idea to run it from anything but a nightly.
- test_node_with_long_property_tests:
- <<: *test_node
- environment:
- JASMINE_LONG_PROPERTY_TESTS: y
-
test_browsers: &test_browsers
executor: node14
environment:
@@ -133,10 +126,6 @@ workflows:
name: test_node_16
requires:
- build_node_16
- - test_node_with_long_property_tests:
- executor: node14
- requires:
- - build_node_14
- test_node:
executor: node12
name: test_node_12
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 6c961b54..3c8ecdbe 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -1,6 +1,12 @@
# Developing for Jasmine Core
-We welcome your contributions! Thanks for helping make Jasmine a better project for everyone. Please review the backlog and discussion lists before starting work. What you're looking for may already have been done. If it hasn't, the community can help make your contribution better. If you want to contribute but don't know what to work on, [issues tagged ready for work](https://github.com/jasmine/jasmine/labels/ready%20for%20work) should have enough detail to get started.
+We welcome your contributions! Thanks for helping make Jasmine a better project
+for everyone. Please review the backlog and discussion lists before starting
+work. What you're looking for may already have been done. If it hasn't, the
+community can help make your contribution better. If you want to contribute but
+don't know what to work on,
+[issues tagged help needed](https://github.com/jasmine/jasmine/labels/help%20needed)
+should have enough detail to get started.
## Links
@@ -35,8 +41,8 @@ Once you've pushed a feature branch to your forked repo, you're ready to open a
* `/spec` contains all of the tests
* mirrors the source directory
* there are some additional files
-* `/dist` contains the standalone distributions as zip files
-* `/lib` contains the generated files for distribution as the Jasmine Rubygem and the Python package
+* `/lib` contains the compiled copy of Jasmine. This is used to self-test and
+ distributed as the `jasmine-core` Node, Ruby, and Python packages.
### Self-testing
@@ -48,35 +54,23 @@ The tests should always use `jasmineUnderTest` to refer to the objects and funct
This file does all of the setup necessary for Jasmine to work. It loads all of the code, creates an `Env`, attaches the global functions, and builds the reporter. It also sets up the execution of the `Env` - for browsers this is in `window.onload`. While the default in `lib` is appropriate for browsers, projects may wish to customize this file.
-For example, for Jasmine development there is a different `dev_boot.js` for Jasmine development that does more work.
-
### Compatibility
-Jasmine supports the following environments:
-
-* Browsers
- * IE10+
- * Edge latest
- * Firefox latest, 78, and 68
- * Chrome latest
- * Safari 8+
-
-* Node.js
- * 10
- * 12
- * 14
+Jasmine runs in both Node and browsers, including some older browsers that do
+not support the latest JavaScript features. See the README for the list of
+currently supported environments.
## Development
All source code belongs in `src/`. The `core/` directory contains the bulk of Jasmine's functionality. This code should remain browser- and environment-agnostic. If your feature or fix cannot be, as mentioned above, please degrade gracefully. Any code that depends on a browser (specifically, it expects `window` to be the global or `document` is present) should live in `src/html/`.
-### Install Dependencies
+### Install Dev Dependencies
Jasmine Core relies on Node.js.
-To install the Node dependencies, you will need Node.js, Npm, and [Grunt](http://gruntjs.com/), the [grunt-cli](https://github.com/gruntjs/grunt-cli) and ensure that `grunt` is on your path.
+To install the Node dependencies, you will need Node.js and npm.
- $ npm install --local
+ $ npm install
...will install all of the node modules locally. Now run
@@ -88,12 +82,18 @@ To install the Node dependencies, you will need Node.js, Npm, and [Grunt](http:/
Or, How to make a successful pull request
-* _Do not change the public interface_. Lots of projects depend on Jasmine and if you aren't careful you'll break them
-* _Be environment agnostic_ - server-side developers are just as important as browser developers
-* _Be browser agnostic_ - if you must rely on browser-specific functionality, please write it in a way that degrades gracefully
-* _Write specs_ - Jasmine's a testing framework; don't add functionality without test-driving it
-* _Write code in the style of the rest of the repo_ - Jasmine should look like a cohesive whole
-* _Ensure the *entire* test suite is green_ in all the big browsers, Node, and ESLint - your contribution shouldn't break Jasmine for other users
+* _Do not change the public interface_. Lots of projects depend on Jasmine and
+ if you aren't careful you'll break them.
+* _Be environment agnostic_ - server-side developers are just as important as
+ browser developers.
+* _Be browser agnostic_ - if you must rely on browser-specific functionality,
+ please write it in a way that degrades gracefully.
+* _Write specs_ - Jasmine's a testing framework. Don't add functionality
+ without test-driving it.
+* _Write code in the style of the rest of the repo_ - Jasmine should look like
+ a cohesive whole.
+* _Ensure the *entire* test suite is green_ in all the big browsers, Node, and
+ ESLint. Your contribution shouldn't break Jasmine for other users.
Follow these tips and your pull request, patch, or suggestion is much more likely to be integrated.
@@ -123,9 +123,9 @@ The easiest way to run the tests in **Internet Explorer** is to run a VM that ha
## Before Committing or Submitting a Pull Request
-1. Ensure all specs are green in browser *and* node
+1. Ensure all specs are green in browser *and* node.
1. Ensure eslint and prettier are clean as part of your `npm test` command. You can run `npm run cleanup` to have prettier re-write the files.
-1. Build `jasmine.js` with `npm run build` and run all specs again - this ensures that your changes self-test well
+1. Build `jasmine.js` with `npm run build` and run all specs again - this ensures that your changes self-test well.
1. Revert your changes to `jasmine.js` and `jasmine-html.js`
* We do this because `jasmine.js` and `jasmine-html.js` are auto-generated (as you've seen in the previous steps) and accepting multiple pull requests when this auto-generated file changes causes lots of headaches
* When we accept your pull request, we will generate these files as a separate commit and merge the entire branch into main
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index db4e6437..7070a58e 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -3,9 +3,12 @@
- When in doubt, create an issue here.
- If you have an issue with the Jasmine docs, file an issue in the docs repo
here: https://github.com/jasmine/jasmine.github.io
+- If you have an issue with TypeScript typings, start a discussion at
+ [DefinitelyTpyed](https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/new?category=issues-with-a-types-package)
- This repository is for the core Jasmine framework
- If you are using a test runner that wraps Jasmine, consider filing an issue with that library if appropriate:
- [Jasmine npm](https://github.com/jasmine/jasmine-npm/issues)
+ - [Jasmine browser runner](https://github.com/jasmine/jasmine-browser/issues)
- [Jasmine gem](https://github.com/jasmine/jasmine-gem/issues)
- [Jasmine py](https://github.com/jasmine/jasmine-py/issues)
- [Gulp Jasmine Browser](https://github.com/jasmine/gulp-jasmine-browser/issues)
diff --git a/.gitignore b/.gitignore
index 4536a071..644ebecd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,4 @@ build/
dist
nbproject/
*.iml
+.envrc
diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index 2f53304a..00000000
--- a/.npmignore
+++ /dev/null
@@ -1,30 +0,0 @@
-dist/
-grunt/
-node_modules
-pkg/
-release_notes/
-spec/
-src/
-Gemfile
-Gemfile.lock
-Rakefile
-jasmine-core.gemspec
-.bundle/
-.gitignore
-.gitmodules
-.idea
-.jshintrc
-.rspec
-.sass-cache/
-.circleci
-scripts/
-*.sh
-*.py
-Gruntfile.js
-lib/jasmine-core.rb
-lib/jasmine-core/boot/
-lib/jasmine-core/spec
-lib/jasmine-core/version.rb
-lib/jasmine-core/*.py
-sauce_connect.log
-ci.js
diff --git a/README.md b/README.md
index f0906320..caf68c70 100644
--- a/README.md
+++ b/README.md
@@ -26,9 +26,6 @@ For the Jasmine browser runner:
For the Jasmine Ruby Gem:
[https://github.com/jasmine/jasmine-gem](https://github.com/jasmine/jasmine-gem).
-For the Jasmine headless browser gulp plugin:
-[https://github.com/jasmine/gulp-jasmine-browser](https://github.com/jasmine/gulp-jasmine-browser).
-
To install Jasmine standalone on your local box (where **_{#.#.#}_** below is substituted by the release number downloaded):
* Download the standalone distribution for your desired release from the [releases page](https://github.com/jasmine/jasmine/releases).
diff --git a/RELEASE.md b/RELEASE.md
index bb527c62..7e3d959b 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -28,7 +28,7 @@ When jasmine-core revs its major or minor version, the binding libraries should
When ready to release - specs are all green and the stories are done:
-1. Update the release notes in `release_notes` - use the Anchorman gem to generate the markdown file and edit accordingly
+1. Update the release notes in `release_notes` - use the Anchorman gem to generate the markdown file and edit accordingly. Include a list of supported environments.
1. Update the version in `package.json`
1. Run `npm run build`.
1. Copy version to the Ruby gem with `grunt build:copyVersionToGem`
@@ -80,6 +80,7 @@ Probably only need to do this when releasing a minor version, and not a patch ve
1. In `package.json`, update both the package version and the jasmine-core dependency version
1. Commit and push.
1. Wait for Circle CI to go green again.
+1. Run the tests on Windows locally.
1. `grunt release `. (Note: This will publish the package by running `npm publish`.)
#### Gem
diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js
index ca67c293..43edd002 100644
--- a/lib/jasmine-core/jasmine-html.js
+++ b/lib/jasmine-core/jasmine-html.js
@@ -208,7 +208,10 @@ jasmineRequire.HtmlReporter = function(j$) {
' of ' +
totalSpecsDefined +
' specs - run all';
- var skippedLink = addToExistingQueryString('spec', '');
+ // include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
+ var skippedLink =
+ (window.location.pathname || '') +
+ addToExistingQueryString('spec', '');
alert.appendChild(
createDom(
'span',
@@ -576,7 +579,7 @@ jasmineRequire.HtmlReporter = function(j$) {
);
throwCheckbox.checked = config.oneFailurePerSpec;
throwCheckbox.onclick = function() {
- navigateWithNewParam('throwFailures', !config.oneFailurePerSpec);
+ navigateWithNewParam('oneFailurePerSpec', !config.oneFailurePerSpec);
};
var randomCheckbox = optionsMenuDom.querySelector(
@@ -646,7 +649,11 @@ jasmineRequire.HtmlReporter = function(j$) {
suite = suite.parent;
}
- return addToExistingQueryString('spec', els.join(' '));
+ // include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
+ return (
+ (window.location.pathname || '') +
+ addToExistingQueryString('spec', els.join(' '))
+ );
}
function addDeprecationWarnings(result, runnableType) {
@@ -749,11 +756,19 @@ jasmineRequire.HtmlReporter = function(j$) {
}
function specHref(result) {
- return addToExistingQueryString('spec', result.fullName);
+ // include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
+ return (
+ (window.location.pathname || '') +
+ addToExistingQueryString('spec', result.fullName)
+ );
}
function seedHref(seed) {
- return addToExistingQueryString('seed', seed);
+ // include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
+ return (
+ (window.location.pathname || '') +
+ addToExistingQueryString('seed', seed)
+ );
}
function defaultQueryString(key, value) {
diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js
index 8d5998a4..33099d61 100644
--- a/lib/jasmine-core/jasmine.js
+++ b/lib/jasmine-core/jasmine.js
@@ -362,6 +362,19 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
return matches ? matches[1] : '';
};
+ j$.isPending_ = function(promise) {
+ var sentinel = {};
+ // eslint-disable-next-line compat/compat
+ return Promise.race([promise, Promise.resolve(sentinel)]).then(
+ function(result) {
+ return result === sentinel;
+ },
+ function() {
+ return false;
+ }
+ );
+ };
+
/**
* Get a matcher, 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.
@@ -978,11 +991,12 @@ getJasmineRequireObj().Order = function() {
getJasmineRequireObj().Env = function(j$) {
/**
- * _Note:_ Do not construct this directly, Jasmine will make one during booting.
- * @name Env
+ * @class Env
* @since 2.0.0
- * @classdesc The Jasmine environment
- * @constructor
+ * @classdesc The Jasmine environment.
+ * _Note:_ Do not construct this directly. You can obtain the Env instance by
+ * calling {@link jasmine.getEnv}.
+ * @hideconstructor
*/
function Env(options) {
options = options || {};
@@ -1013,7 +1027,8 @@ getJasmineRequireObj().Env = function(j$) {
/**
* This represents the available options to configure Jasmine.
- * Options that are not provided will use their default values
+ * Options that are not provided will use their default values.
+ * @see Env#configure
* @interface Configuration
* @since 3.3.0
*/
@@ -2960,9 +2975,11 @@ getJasmineRequireObj().Clock = function() {
typeof process.versions.node === 'string';
/**
- * _Note:_ Do not construct this directly, Jasmine will make one during booting. You can get the current clock with {@link jasmine.clock}.
* @class Clock
- * @classdesc Jasmine's mock clock is used when testing time dependent code.
+ * @classdesc Jasmine's mock clock is used when testing time dependent code.
+ * _Note:_ Do not construct this directly. You can get the current clock with
+ * {@link jasmine.clock}.
+ * @hideconstructor
*/
function Clock(global, delayedFunctionSchedulerFactory, mockDate) {
var self = this,
@@ -3612,6 +3629,12 @@ getJasmineRequireObj().Expectation = function(j$) {
* Asynchronous matchers that operate on an actual value which is a promise,
* and return a promise.
*
+ * Most async matchers will wait indefinitely for the promise to be resolved
+ * or rejected, resulting in a spec timeout if that never happens. If you
+ * expect that the promise will already be resolved or rejected at the time
+ * the matcher is called, you can use the {@link async-matchers#already}
+ * modifier to get a faster failure with a more helpful message.
+ *
* Note: Specs must await the result of each async matcher, return the
* promise returned by the matcher, or return a promise that's derived from
* the one returned by the matcher. Otherwise the matcher will not be
@@ -3681,6 +3704,23 @@ getJasmineRequireObj().Expectation = function(j$) {
}
});
+ /**
+ * Fail as soon as possible if the actual is pending.
+ * Otherwise evaluate the matcher.
+ * @member
+ * @name async-matchers#already
+ * @type {async-matchers}
+ * @example
+ * await expectAsync(myPromise).already.toBeResolved();
+ * @example
+ * return expectAsync(myPromise).already.toBeResolved();
+ */
+ Object.defineProperty(AsyncExpectation.prototype, 'already', {
+ get: function() {
+ return addFilter(this, expectSettledPromiseFilter);
+ }
+ });
+
function wrapSyncCompare(name, matcherFactory) {
return function() {
var result = this.expector.compare(name, matcherFactory, arguments);
@@ -3759,6 +3799,27 @@ getJasmineRequireObj().Expectation = function(j$) {
buildFailureMessage: negatedFailureMessage
};
+ var expectSettledPromiseFilter = {
+ selectComparisonFunc: function(matcher) {
+ return function(actual) {
+ var matcherArgs = arguments;
+
+ return j$.isPending_(actual).then(function(isPending) {
+ if (isPending) {
+ return {
+ pass: false,
+ message:
+ 'Expected a promise to be settled (via ' +
+ 'expectAsync(...).already) but it was pending.'
+ };
+ } else {
+ return matcher.compare.apply(null, matcherArgs);
+ }
+ });
+ };
+ }
+ };
+
function ContextAddingFilter(message) {
this.message = message;
}
@@ -4418,7 +4479,7 @@ getJasmineRequireObj().toBeResolved = function(j$) {
* @example
* return expectAsync(aPromise).toBeResolved();
*/
- return function toBeResolved() {
+ return function toBeResolved(matchersUtil) {
return {
compare: function(actual) {
if (!j$.isPromiseLike(actual)) {
@@ -4429,8 +4490,15 @@ getJasmineRequireObj().toBeResolved = function(j$) {
function() {
return { pass: true };
},
- function() {
- return { pass: false };
+ function(e) {
+ return {
+ pass: false,
+ message:
+ 'Expected a promise to be resolved but it was ' +
+ 'rejected with ' +
+ matchersUtil.pp(e) +
+ '.'
+ };
}
);
}
@@ -4485,10 +4553,14 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) {
};
}
},
- function() {
+ function(e) {
return {
pass: false,
- message: prefix(false) + ' but it was rejected.'
+ message:
+ prefix(false) +
+ ' but it was rejected with ' +
+ matchersUtil.pp(e) +
+ '.'
};
}
);
@@ -4618,11 +4690,11 @@ getJasmineRequireObj().DiffBuilder = function(j$) {
getJasmineRequireObj().MatchersUtil = function(j$) {
/**
+ * @class MatchersUtil
+ * @classdesc Utilities for use in implementing matchers.
* _Note:_ Do not construct this directly. Jasmine will construct one and
* pass it to matchers and asymmetric equality testers.
- * @name MatchersUtil
- * @classdesc Utilities for use in implementing matchers
- * @constructor
+ * @hideconstructor
*/
function MatchersUtil(options) {
options = options || {};
@@ -5218,6 +5290,31 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
return MatchersUtil;
};
+/**
+ * @interface AsymmetricEqualityTester
+ * @classdesc An asymmetric equality tester is an object that can match multiple
+ * objects. Examples include jasmine.any() and jasmine.stringMatching().
+ * User-defined asymmetric equality testers can also be defined and used in
+ * expectations.
+ * @see custom_asymmetric_equality_testers
+ * @since 2.0.0
+ */
+/**
+ * Determines whether a value matches this tester
+ * @function
+ * @name AsymmetricEqualityTester#asymmetricMatch
+ * @param value {any} The value to test
+ * @param matchersUtil {MatchersUtil} utilities for testing equality, etc
+ * @return {Boolean}
+ */
+/**
+ * Returns a string representation of this tester to use in matcher failure messages
+ * @function
+ * @name AsymmetricEqualityTester#jasmineToString
+ * @param pp {function} Function that takes a value and returns a pretty-printed representation
+ * @return {String}
+ */
+
getJasmineRequireObj().MismatchTree = function(j$) {
/*
To be able to apply custom object formatters at all possible levels of an
@@ -7941,10 +8038,11 @@ getJasmineRequireObj().interface = function(jasmine, env) {
* @function
* @global
* @param {Object} obj - The object upon which to install the {@link Spy}s
+ * @param {boolean} includeNonEnumerable - Whether or not to add spies to non-enumerable properties
* @returns {Object} the spied object
*/
- spyOnAllFunctions: function(obj) {
- return env.spyOnAllFunctions(obj);
+ spyOnAllFunctions: function(obj, includeNonEnumerable) {
+ return env.spyOnAllFunctions(obj, includeNonEnumerable);
},
jsApiReporter: new jasmine.JsApiReporter({
@@ -8099,9 +8197,11 @@ getJasmineRequireObj().Spy = function(j$) {
});
/**
- * _Note:_ Do not construct this directly, use {@link spyOn}, {@link spyOnProperty}, {@link jasmine.createSpy}, or {@link jasmine.createSpyObj}
- * @constructor
- * @name Spy
+ * @classdesc _Note:_ Do not construct this directly. Use {@link spyOn},
+ * {@link spyOnProperty}, {@link jasmine.createSpy}, or
+ * {@link jasmine.createSpyObj} instead.
+ * @class Spy
+ * @hideconstructor
*/
function Spy(
name,
@@ -8546,7 +8646,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
return spy;
};
- this.spyOnAllFunctions = function(obj) {
+ this.spyOnAllFunctions = function(obj, includeNonEnumerable) {
if (j$.util.isUndefined(obj)) {
throw new Error(
'spyOnAllFunctions could not find an object to spy upon'
@@ -8554,30 +8654,27 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
}
var pointer = obj,
- props = [],
- prop,
- descriptor;
+ propsToSpyOn = [],
+ properties,
+ propertiesToSkip = [];
- while (pointer) {
- for (prop in pointer) {
- if (
- Object.prototype.hasOwnProperty.call(pointer, prop) &&
- pointer[prop] instanceof Function
- ) {
- descriptor = Object.getOwnPropertyDescriptor(pointer, prop);
- if (
- (descriptor.writable || descriptor.set) &&
- descriptor.configurable
- ) {
- props.push(prop);
- }
- }
- }
+ while (
+ pointer &&
+ (!includeNonEnumerable || pointer !== Object.prototype)
+ ) {
+ properties = getProps(pointer, includeNonEnumerable);
+ properties = properties.filter(function(prop) {
+ return propertiesToSkip.indexOf(prop) === -1;
+ });
+ propertiesToSkip = propertiesToSkip.concat(properties);
+ propsToSpyOn = propsToSpyOn.concat(
+ getSpyableFunctionProps(pointer, properties)
+ );
pointer = Object.getPrototypeOf(pointer);
}
- for (var i = 0; i < props.length; i++) {
- this.spyOn(obj, props[i]);
+ for (var i = 0; i < propsToSpyOn.length; i++) {
+ this.spyOn(obj, propsToSpyOn[i]);
}
return obj;
@@ -8592,6 +8689,50 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
};
}
+ function getProps(obj, includeNonEnumerable) {
+ var enumerableProperties = Object.keys(obj);
+
+ if (!includeNonEnumerable) {
+ return enumerableProperties;
+ }
+
+ return Object.getOwnPropertyNames(obj).filter(function(prop) {
+ return (
+ prop !== 'constructor' ||
+ enumerableProperties.indexOf('constructor') > -1
+ );
+ });
+ }
+
+ function getSpyableFunctionProps(obj, propertiesToCheck) {
+ var props = [],
+ prop;
+ for (var i = 0; i < propertiesToCheck.length; i++) {
+ prop = propertiesToCheck[i];
+ if (
+ Object.prototype.hasOwnProperty.call(obj, prop) &&
+ isSpyableProp(obj, prop)
+ ) {
+ props.push(prop);
+ }
+ }
+ return props;
+ }
+
+ function isSpyableProp(obj, prop) {
+ var value, descriptor;
+ try {
+ value = obj[prop];
+ } catch (e) {
+ return false;
+ }
+ if (value instanceof Function) {
+ descriptor = Object.getOwnPropertyDescriptor(obj, prop);
+ return (descriptor.writable || descriptor.set) && descriptor.configurable;
+ }
+ return false;
+ }
+
return SpyRegistry;
};
@@ -9465,5 +9606,5 @@ getJasmineRequireObj().UserContext = function(j$) {
};
getJasmineRequireObj().version = function() {
- return '3.7.1';
+ return '3.8.0';
};
diff --git a/lib/jasmine-core/version.rb b/lib/jasmine-core/version.rb
index 9f6452ff..f879e1d5 100644
--- a/lib/jasmine-core/version.rb
+++ b/lib/jasmine-core/version.rb
@@ -4,6 +4,6 @@
#
module Jasmine
module Core
- VERSION = "3.7.1"
+ VERSION = "3.8.0"
end
end
diff --git a/package.json b/package.json
index 482f41f7..4449c324 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,14 @@
{
"name": "jasmine-core",
"license": "MIT",
- "version": "3.7.1",
+ "version": "3.8.0",
"repository": {
"type": "git",
"url": "https://github.com/jasmine/jasmine.git"
},
"keywords": [
"test",
+ "testing",
"jasmine",
"tdd",
"bdd"
@@ -22,14 +23,20 @@
"ci": "node spec/support/ci.js",
"ci:performance": "node spec/support/ci.js jasmine-browser-performance.json"
},
- "description": "Official packaging of Jasmine's core files for use by Node.js projects.",
+ "description": "Simple JavaScript testing framework for browsers and node.js",
"homepage": "https://jasmine.github.io",
"main": "./lib/jasmine-core.js",
+ "files": [
+ "MIT.LICENSE",
+ "README.md",
+ "images/*.{png,svg}",
+ "lib/**/*.{js,css}",
+ "package.json"
+ ],
"devDependencies": {
"ejs": "^2.5.5",
"eslint": "^6.8.0",
"eslint-plugin-compat": "^3.8.0",
- "fast-check": "^1.21.0",
"fast-glob": "^2.2.6",
"grunt": "^1.0.4",
"grunt-cli": "^1.3.2",
diff --git a/release_notes/3.8.0.md b/release_notes/3.8.0.md
new file mode 100644
index 00000000..b40a13ab
--- /dev/null
+++ b/release_notes/3.8.0.md
@@ -0,0 +1,132 @@
+# Jasmine Core 3.8 Release Notes
+
+## Summary
+
+This is a maintenance release of Jasmine with a number of new features and fixes.
+
+## Python deprecation
+
+The Jasmine packages for Python are deprecated. We intend to continue releasing
+them through the end of the 3.x series, but after that they will be
+discontinued. We recommend migrating to the following alternatives:
+
+* The [jasmine-browser-runner](https://github.com/jasmine/jasmine-browser)
+ npm package to run specs in browsers, including headless Chrome and
+ Saucelabs. This is the most direct replacement for the `jasmine server`
+ and `jasmine ci` commands provided by the `jasmine` Python package.
+* The [jasmine](https://github.com/jasmine/jasmine-npm) npm package (
+ `npm install jasmine`) to run specs under Node.js.
+* The standalone distribution from the
+ [latest Jasmine release](https://github.com/jasmine/jasmine/releases) to
+ run specs in browsers with no additional tools.
+* The [jasmine-core](https://github.com/jasmine/jasmine) npm package if all
+ you need is the Jasmine assets. This is the direct equivalent of the
+ jasmine-core Python package.
+
+
+## New features and bug fixes
+
+* Fixed spec filtering in Karma
+ * Merges [#1920](https://github.com/jasmine/jasmine/pull/1920) from @jlpstolwijk
+ * Fixes [#1906](https://github.com/jasmine/jasmine/issues/1906)
+
+* Added expectAsync(...).already
+ * Causes async matchers to immediately fail if the promise is pending
+ * See https://jasmine.github.io/api/3.8/async-matchers.html#already
+ * Fixes [#1845](https://github.com/jasmine/jasmine/issues/1845)
+
+* Include rejection details in failure messages for toBeResolved and toBeResolvedWith
+
+* Fixed "stop spec on expectation failure" checkbox in standalone
+
+* Added option for spyOnAllFunctions to include non-enumerable props
+ * Makes spyOnAllFunctions work on instance methods of ES6 classes
+ * Merges [#1909](https://github.com/jasmine/jasmine/pull/1909) from @Dari-k
+ * Fixes [#1897](https://github.com/jasmine/jasmine/issues/1897)
+
+* Added Spy#calls#thisFor
+ * Provides the `this` value for a given spy call
+ * Merges [#1903](https://github.com/jasmine/jasmine/pull/1903) from @ajvincent
+
+* Improved handling of unhandled promise rejections with no error in Node
+ * Fixes [#1759](https://github.com/jasmine/jasmine/issues/1759)
+
+
+## Documentation updates
+
+* Updated package description
+
+* Updated contributing guide
+
+* Added TypeScript typings and jasmine-browser-runner to issue template
+
+* Removed constructors from jsdocs of classes that aren't user-constructable
+
+* Fixed config.seed type in jsdocs
+ * Merges [#1892](https://github.com/jasmine/jasmine/pull/1892) from @UziTech
+
+* Added jsdocs for the following:
+ * asymmetric equality testers
+ * Env#execute
+ * Env#allowRespy
+ * The public portion of Spec
+ * Spy.callData.returnValue
+ * Env#topSuite and Suite
+
+* Added a jsdoc cross-reference from Configuration to its usage
+
+* Added a note about correct usage of async matchers
+
+* Added support for ArrayBuffers to matchersUtil.equals
+ * Merges [#1891](https://github.com/jasmine/jasmine/pull/1892) from @Finesse
+ * Merges [#1689](https://github.com/jasmine/jasmine/pull/1892) from @dankurka
+ * Fixes [#1687](https://github.com/jasmine/jasmine/issues/1687)
+
+
+## Internal notes
+
+* Fixed typo in spec name
+ * Merges [#1918](https://github.com/jasmine/jasmine/pull/1918) from @eltociear
+
+* Specify files to include in the NPM package rather than files to exclude
+
+* Added test coverage for MatchersUtil#equals with typed arrays
+
+* Removed checks for typed array support in the test suite
+ * All supported browsers have all typed arrays except for Uint8ClampedArray,
+ BigInt64Array, and BigUint64Array.
+
+* Fixed test failures on IE 10
+
+* Test matrix updates
+ * Added Node 16
+ * Added Safari 14
+ * Added Firefox 78 (closest match to current ESR)
+ * Removed Safari 10-12 to speed up CI. The newer and older versions we test
+ provide a good measure of safety.
+
+* Replaced node-sass dev dependency that isn't compatible with Node 16
+
+* Removed unused dev dependencies
+
+* Migrated CI from Travis to Circle
+
+* Compensate for clock jitter in specs
+
+
+## Supported environments
+
+jasmine-core 3.8.0 has been tested in the following environments.
+
+| Environment | Supported versions |
+|-------------------|--------------------|
+| Node | 10, 12, 14, 16 |
+| Safari | 8-14 |
+| Chrome | 91 |
+| Firefox | 89, 68, 78 |
+| Edge | 91 |
+| Internet Explorer | 10, 11 |
+
+------
+
+_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
diff --git a/spec/core/AsyncExpectationSpec.js b/spec/core/AsyncExpectationSpec.js
index 7cd1dcc8..338e0faf 100644
--- a/spec/core/AsyncExpectationSpec.js
+++ b/spec/core/AsyncExpectationSpec.js
@@ -96,8 +96,8 @@ describe('AsyncExpectation', function() {
jasmine.getEnv().requirePromises();
var matchersUtil = {
- buildFailureMessage: function() {
- return 'failure message';
+ pp: function(val) {
+ return val.toString();
}
},
addExpectationResult = jasmine.createSpy('addExpectationResult'),
@@ -114,7 +114,8 @@ describe('AsyncExpectation', function() {
expect(addExpectationResult).toHaveBeenCalledWith(
false,
jasmine.objectContaining({
- message: 'Some context: failure message'
+ message:
+ 'Some context: Expected a promise to be resolved but it was rejected with rejected.'
})
);
});
@@ -144,7 +145,8 @@ describe('AsyncExpectation', function() {
false,
jasmine.objectContaining({
message:
- "Some context: Expected a promise to be resolved to 'a' but it was rejected."
+ "Some context: Expected a promise to be resolved to 'a' " +
+ "but it was rejected with 'b'."
})
);
});
diff --git a/spec/core/SpyRegistrySpec.js b/spec/core/SpyRegistrySpec.js
index f3c81a47..11d1e5dd 100644
--- a/spec/core/SpyRegistrySpec.js
+++ b/spec/core/SpyRegistrySpec.js
@@ -407,6 +407,123 @@ describe('SpyRegistry', function() {
expect(subject.toString).not.toBe('I am a spy');
expect(subject.hasOwnProperty).not.toBe('I am a spy');
});
+ describe('when includeNonEnumerable is true', function() {
+ it('does not override Object.prototype methods', function() {
+ var spyRegistry = new jasmineUnderTest.SpyRegistry({
+ createSpy: function() {
+ return 'I am a spy';
+ }
+ });
+ var subject = {
+ spied1: function() {}
+ };
+
+ spyRegistry.spyOnAllFunctions(subject, true);
+
+ expect(subject.spied1).toBe('I am a spy');
+ expect(subject.toString).not.toBe('I am a spy');
+ expect(subject.hasOwnProperty).not.toBe('I am a spy');
+ });
+
+ it('overrides non-enumerable properties', function() {
+ var spyRegistry = new jasmineUnderTest.SpyRegistry({
+ createSpy: function() {
+ return 'I am a spy';
+ }
+ });
+ var subject = {
+ spied1: function() {},
+ spied2: function() {}
+ };
+
+ Object.defineProperty(subject, 'spied2', {
+ enumerable: false,
+ writable: true,
+ configurable: true
+ });
+
+ spyRegistry.spyOnAllFunctions(subject, true);
+
+ expect(subject.spied1).toBe('I am a spy');
+ expect(subject.spied2).toBe('I am a spy');
+ });
+
+ it('should not spy on non-enumerable functions named constructor', function() {
+ var spyRegistry = new jasmineUnderTest.SpyRegistry({
+ createSpy: function() {
+ return 'I am a spy';
+ }
+ });
+ var subject = {
+ constructor: function() {}
+ };
+
+ Object.defineProperty(subject, 'constructor', {
+ enumerable: false,
+ writable: true,
+ configurable: true
+ });
+
+ spyRegistry.spyOnAllFunctions(subject, true);
+
+ expect(subject.constructor).not.toBe('I am a spy');
+ });
+
+ it('should spy on enumerable functions named constructor', function() {
+ var spyRegistry = new jasmineUnderTest.SpyRegistry({
+ createSpy: function() {
+ return 'I am a spy';
+ }
+ });
+ var subject = {
+ constructor: function() {}
+ };
+
+ spyRegistry.spyOnAllFunctions(subject, true);
+
+ expect(subject.constructor).toBe('I am a spy');
+ });
+
+ it('should not throw an exception if we try and access strict mode restricted properties', function() {
+ var spyRegistry = new jasmineUnderTest.SpyRegistry({
+ createSpy: function() {
+ return 'I am a spy';
+ }
+ });
+ var subject = function() {};
+ var fn = function() {
+ spyRegistry.spyOnAllFunctions(subject, true);
+ };
+
+ expect(fn).not.toThrow();
+ });
+
+ it('should not spy on properties which are more permissable further up the prototype chain', function() {
+ var spyRegistry = new jasmineUnderTest.SpyRegistry({
+ createSpy: function() {
+ return 'I am a spy';
+ }
+ });
+ var subjectParent = Object.defineProperty({}, 'sharedProp', {
+ value: function() {},
+ writable: true,
+ configurable: true
+ });
+
+ var subject = Object.create(subjectParent);
+
+ Object.defineProperty(subject, 'sharedProp', {
+ value: function() {}
+ });
+
+ var fn = function() {
+ spyRegistry.spyOnAllFunctions(subject, true);
+ };
+
+ expect(fn).not.toThrow();
+ expect(subject).not.toBe('I am a spy');
+ });
+ });
});
describe('#clearSpies', function() {
diff --git a/spec/core/SpySpec.js b/spec/core/SpySpec.js
index f7a2c9ec..97fc9544 100644
--- a/spec/core/SpySpec.js
+++ b/spec/core/SpySpec.js
@@ -213,7 +213,7 @@ describe('Spies', function() {
).toBe(1);
});
- it('allows base name to be ommitted when assigning methods and properties', function() {
+ it('allows base name to be omitted when assigning methods and properties', function() {
var spyObj = env.createSpyObj({ m: 3 }, { p: 4 });
expect(spyObj.m()).toEqual(3);
diff --git a/spec/core/baseSpec.js b/spec/core/baseSpec.js
index 3ce313cb..27d97e3c 100644
--- a/spec/core/baseSpec.js
+++ b/spec/core/baseSpec.js
@@ -74,4 +74,33 @@ describe('base helpers', function() {
expect(jasmineUnderTest.isURL({})).toBe(false);
});
});
+
+ describe('isPending_', function() {
+ it('returns a promise that resolves to true when the promise is pending', function() {
+ jasmine.getEnv().requirePromises();
+ // eslint-disable-next-line compat/compat
+ var promise = new Promise(function() {});
+ return expectAsync(jasmineUnderTest.isPending_(promise)).toBeResolvedTo(
+ true
+ );
+ });
+
+ it('returns a promise that resolves to false when the promise is resolved', function() {
+ jasmine.getEnv().requirePromises();
+ // eslint-disable-next-line compat/compat
+ var promise = Promise.resolve();
+ return expectAsync(jasmineUnderTest.isPending_(promise)).toBeResolvedTo(
+ false
+ );
+ });
+
+ it('returns a promise that resolves to false when the promise is rejected', function() {
+ jasmine.getEnv().requirePromises();
+ // eslint-disable-next-line compat/compat
+ var promise = Promise.reject();
+ return expectAsync(jasmineUnderTest.isPending_(promise)).toBeResolvedTo(
+ false
+ );
+ });
+ });
});
diff --git a/spec/core/integration/MatchersSpec.js b/spec/core/integration/MatchersSpec.js
index ac9f5e90..86fe77c4 100644
--- a/spec/core/integration/MatchersSpec.js
+++ b/spec/core/integration/MatchersSpec.js
@@ -754,4 +754,89 @@ describe('Matchers (Integration)', function() {
'a predicate, but it threw Error with message |nope|.'
});
});
+
+ describe('When an async matcher is used with .already()', function() {
+ it('propagates the matcher result when the promise is resolved', function(done) {
+ jasmine.getEnv().requirePromises();
+
+ env.it('a spec', function() {
+ // eslint-disable-next-line compat/compat
+ return env.expectAsync(Promise.resolve()).already.toBeRejected();
+ });
+
+ var specExpectations = function(result) {
+ expect(result.status).toEqual('failed');
+ expect(result.failedExpectations.length)
+ .withContext('Number of failed expectations')
+ .toEqual(1);
+ expect(result.failedExpectations[0].message).toEqual(
+ 'Expected [object Promise] to be rejected.'
+ );
+ expect(result.failedExpectations[0].matcherName)
+ .withContext('Matcher name')
+ .not.toEqual('');
+ };
+
+ env.addReporter({ specDone: specExpectations });
+ env.execute(null, done);
+ });
+
+ it('propagates the matcher result when the promise is rejected', function(done) {
+ jasmine.getEnv().requirePromises();
+
+ env.it('a spec', function() {
+ return (
+ env
+ // eslint-disable-next-line compat/compat
+ .expectAsync(Promise.reject(new Error('nope')))
+ .already.toBeResolved()
+ );
+ });
+
+ var specExpectations = function(result) {
+ expect(result.status).toEqual('failed');
+ expect(result.failedExpectations.length)
+ .withContext('Number of failed expectations')
+ .toEqual(1);
+ expect(result.failedExpectations[0].message).toEqual(
+ 'Expected a promise to be resolved but it was ' +
+ 'rejected with Error: nope.'
+ );
+ expect(result.failedExpectations[0].matcherName)
+ .withContext('Matcher name')
+ .not.toEqual('');
+ };
+
+ env.addReporter({ specDone: specExpectations });
+ env.execute(null, done);
+ });
+
+ it('fails when the promise is pending', function(done) {
+ jasmine.getEnv().requirePromises();
+
+ // eslint-disable-next-line compat/compat
+ var promise = new Promise(function() {});
+
+ env.it('a spec', function() {
+ return env.expectAsync(promise).already.toBeResolved();
+ });
+
+ var specExpectations = function(result) {
+ expect(result.status).toEqual('failed');
+ expect(result.failedExpectations.length)
+ .withContext('Number of failed expectations')
+ .toEqual(1);
+ expect(result.failedExpectations[0].message).toEqual(
+ 'Expected a promise to be settled ' +
+ '(via expectAsync(...).already) but it was pending.'
+ );
+ expect(result.failedExpectations[0].matcherName)
+ .withContext('Matcher name')
+ .not.toEqual('');
+ };
+
+ env.addReporter({ specDone: specExpectations });
+ env.execute(null, done);
+ });
+ });
});
diff --git a/spec/core/matchers/async/toBeResolvedSpec.js b/spec/core/matchers/async/toBeResolvedSpec.js
index 668b1bb3..b90a537d 100644
--- a/spec/core/matchers/async/toBeResolvedSpec.js
+++ b/spec/core/matchers/async/toBeResolvedSpec.js
@@ -15,12 +15,19 @@ describe('toBeResolved', function() {
it('fails if the actual is rejected', function() {
jasmine.getEnv().requirePromises();
- var matchersUtil = new jasmineUnderTest.MatchersUtil(),
+ var matchersUtil = new jasmineUnderTest.MatchersUtil({
+ pp: jasmineUnderTest.makePrettyPrinter([])
+ }),
matcher = jasmineUnderTest.asyncMatchers.toBeResolved(matchersUtil),
- actual = Promise.reject('AsyncExpectationSpec rejection');
+ actual = Promise.reject(new Error('AsyncExpectationSpec rejection'));
return matcher.compare(actual).then(function(result) {
- expect(result).toEqual(jasmine.objectContaining({ pass: false }));
+ expect(result).toEqual({
+ pass: false,
+ message:
+ 'Expected a promise to be resolved but it was rejected ' +
+ 'with Error: AsyncExpectationSpec rejection.'
+ });
});
});
diff --git a/spec/core/matchers/async/toBeResolvedToSpec.js b/spec/core/matchers/async/toBeResolvedToSpec.js
index 459e93ac..734b780e 100644
--- a/spec/core/matchers/async/toBeResolvedToSpec.js
+++ b/spec/core/matchers/async/toBeResolvedToSpec.js
@@ -19,14 +19,15 @@ describe('#toBeResolvedTo', function() {
pp: jasmineUnderTest.makePrettyPrinter()
}),
matcher = jasmineUnderTest.asyncMatchers.toBeResolvedTo(matchersUtil),
- actual = Promise.reject('AsyncExpectationSpec error');
+ actual = Promise.reject(new Error('AsyncExpectationSpec error'));
return matcher.compare(actual, '').then(function(result) {
expect(result).toEqual(
jasmine.objectContaining({
pass: false,
message:
- "Expected a promise to be resolved to '' but it was rejected."
+ "Expected a promise to be resolved to '' but it was rejected " +
+ 'with Error: AsyncExpectationSpec error.'
})
);
});
diff --git a/spec/core/matchers/matchersUtilSpec.js b/spec/core/matchers/matchersUtilSpec.js
index 20adb01e..d8b284a1 100644
--- a/spec/core/matchers/matchersUtilSpec.js
+++ b/spec/core/matchers/matchersUtilSpec.js
@@ -7,78 +7,6 @@ describe('matchersUtil', function() {
});
describe('equals', function() {
- describe('Properties', function() {
- var fc;
-
- beforeEach(function() {
- fc = jasmine.getEnv().requireFastCheck();
- });
-
- function basicAnythingSettings() {
- return {
- key: fc.oneof(fc.string(), fc.constantFrom('k1', 'k2', 'k3')),
- // Limiting depth & number of keys allows fast-check to try
- // a lot more scalar values.
- maxDepth: 2,
- maxKeys: 5,
- withBoxedValues: true,
- withMap: true,
- withSet: true
- };
- }
-
- function numRuns() {
- var many = 5000000;
-
- // Be thorough but very slow when specified (usually on CI).
- if (process.env.JASMINE_LONG_PROPERTY_TESTS) {
- /* eslint-disable-next-line no-console */
- console.log(
- 'Using',
- many,
- 'runs of fc.assert because JASMINE_LONG_PROPERTY_TESTS was set. This may take several minutes.'
- );
- return many;
- } else {
- return undefined;
- }
- }
-
- it('is symmetric', function() {
- var matchersUtil = new jasmineUnderTest.MatchersUtil();
-
- fc.assert(
- fc.property(
- fc.anything(basicAnythingSettings()),
- fc.anything(basicAnythingSettings()),
- function(a, b) {
- return matchersUtil.equals(a, b) === matchersUtil.equals(b, a);
- }
- ),
- {
- numRuns: numRuns(),
- examples: [[0, 5e-324]]
- }
- );
- });
-
- it('is reflexive', function() {
- var matchersUtil = new jasmineUnderTest.MatchersUtil();
- var anythingSettings = basicAnythingSettings();
- anythingSettings.withMap = false;
- fc.assert(
- fc.property(fc.dedup(fc.anything(anythingSettings), 2), function(
- values
- ) {
- return matchersUtil.equals(values[0], values[1]);
- }),
- {
- numRuns: numRuns()
- }
- );
- });
- });
-
it('passes for literals that are triple-equal', function() {
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(matchersUtil.equals(null, null)).toBe(true);
@@ -790,6 +718,154 @@ describe('matchersUtil', function() {
expect(matchersUtil.equals(buffer1, buffer2)).toBe(false);
});
+ describe('Typed arrays', function() {
+ it('fails for typed arrays of same length and contents but different types', function() {
+ var matchersUtil = new jasmineUnderTest.MatchersUtil();
+ // eslint-disable-next-line compat/compat
+ var a1 = new Int8Array(1);
+ // eslint-disable-next-line compat/compat
+ var a2 = new Uint8Array(1);
+ a1[0] = a2[0] = 0;
+ expect(matchersUtil.equals(a1, a2)).toBe(false);
+ });
+
+ // eslint-disable-next-line compat/compat
+ [
+ 'Int8Array',
+ 'Uint8Array',
+ 'Uint8ClampedArray',
+ 'Int16Array',
+ 'Uint16Array',
+ 'Int32Array',
+ 'Uint32Array',
+ 'Float32Array',
+ 'Float64Array'
+ ].forEach(function(typeName) {
+ function requireType() {
+ var TypedArrayCtor = jasmine.getGlobal()[typeName];
+
+ if (!TypedArrayCtor) {
+ pending('Browser does not support ' + typeName);
+ }
+
+ return TypedArrayCtor;
+ }
+
+ it(
+ 'passes for ' + typeName + 's with same length and content',
+ function() {
+ var TypedArrayCtor = requireType();
+ var matchersUtil = new jasmineUnderTest.MatchersUtil();
+ var a1 = new TypedArrayCtor(2);
+ var a2 = new TypedArrayCtor(2);
+ a1[0] = a2[0] = 0;
+ a1[1] = a2[1] = 1;
+ expect(matchersUtil.equals(a1, a2)).toBe(true);
+ }
+ );
+
+ it('fails for ' + typeName + 's with different length', function() {
+ var TypedArrayCtor = requireType();
+ var matchersUtil = new jasmineUnderTest.MatchersUtil();
+ var a1 = new TypedArrayCtor(2);
+ var a2 = new TypedArrayCtor(1);
+ a1[0] = a1[1] = a2[0] = 0;
+ expect(matchersUtil.equals(a1, a2)).toBe(false);
+ });
+
+ it(
+ 'fails for ' + typeName + 's with same length but different content',
+ function() {
+ var TypedArrayCtor = requireType();
+ var matchersUtil = new jasmineUnderTest.MatchersUtil();
+ var a1 = new TypedArrayCtor(1);
+ var a2 = new TypedArrayCtor(1);
+ a1[0] = 0;
+ a2[0] = 1;
+ expect(matchersUtil.equals(a1, a2)).toBe(false);
+ }
+ );
+
+ it('checks nonstandard properties of ' + typeName, function() {
+ var TypedArrayCtor = requireType();
+ var matchersUtil = new jasmineUnderTest.MatchersUtil();
+ var a1 = new TypedArrayCtor(1);
+ var a2 = new TypedArrayCtor(1);
+ a1[0] = a2[0] = 0;
+ a1.extra = 'yes';
+ expect(matchersUtil.equals(a1, a2)).toBe(false);
+ });
+
+ it('works with custom equality testers with ' + typeName, function() {
+ var TypedArrayCtor = requireType();
+ var a1 = new TypedArrayCtor(1);
+ var a2 = new TypedArrayCtor(1);
+ var matchersUtil = new jasmineUnderTest.MatchersUtil({
+ customTesters: [
+ function() {
+ return true;
+ }
+ ]
+ });
+ a1[0] = 0;
+ a2[0] = 1;
+ expect(matchersUtil.equals(a1, a2)).toBe(true);
+ });
+ });
+
+ ['BigInt64Array', 'BigUint64Array'].forEach(function(typeName) {
+ function requireType() {
+ var TypedArrayCtor = jasmine.getGlobal()[typeName];
+
+ if (!TypedArrayCtor) {
+ pending('Browser does not support ' + typeName);
+ }
+
+ return TypedArrayCtor;
+ }
+
+ it(
+ 'passes for ' + typeName + 's with same length and content',
+ function() {
+ var TypedArrayCtor = requireType();
+ var matchersUtil = new jasmineUnderTest.MatchersUtil();
+ var a1 = new TypedArrayCtor(2);
+ var 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);
+ }
+ );
+
+ it('fails for ' + typeName + 's with different length', function() {
+ var TypedArrayCtor = requireType();
+ var matchersUtil = new jasmineUnderTest.MatchersUtil();
+ var a1 = new TypedArrayCtor(2);
+ var 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);
+ });
+
+ it(
+ 'fails for ' + typeName + 's with same length but different content',
+ function() {
+ var TypedArrayCtor = requireType();
+ var matchersUtil = new jasmineUnderTest.MatchersUtil();
+ var a1 = new TypedArrayCtor(2);
+ var 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);
+ }
+ );
+ });
+ });
+
describe('when running in an environment with array polyfills', function() {
var findIndexDescriptor = Object.getOwnPropertyDescriptor(
Array.prototype,
diff --git a/spec/helpers/requireFastCheck.js b/spec/helpers/requireFastCheck.js
deleted file mode 100644
index 140b8e80..00000000
--- a/spec/helpers/requireFastCheck.js
+++ /dev/null
@@ -1,16 +0,0 @@
-(function(env) {
- var NODE_JS =
- typeof process !== 'undefined' &&
- process.versions &&
- typeof process.versions.node === 'string';
-
- env.requireFastCheck = function() {
- if (!NODE_JS) {
- env.pending(
- "Property tests don't run in the browser. Use `npm test` to run them."
- );
- }
-
- return require('fast-check');
- };
-})(jasmine.getEnv());
diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js
index c21aa66e..a245e82d 100644
--- a/spec/html/HtmlReporterSpec.js
+++ b/spec/html/HtmlReporterSpec.js
@@ -606,7 +606,7 @@ describe('HtmlReporter', function() {
var suiteDetail = outerSuite.childNodes[0];
var suiteLink = suiteDetail.childNodes[0];
expect(suiteLink.innerHTML).toEqual('A Suite');
- expect(suiteLink.getAttribute('href')).toEqual('?foo=bar&spec=A Suite');
+ expect(suiteLink.getAttribute('href')).toEqual('/?foo=bar&spec=A Suite');
var specs = outerSuite.childNodes[1];
var spec = specs.childNodes[0];
@@ -616,7 +616,7 @@ describe('HtmlReporter', function() {
var specLink = spec.childNodes[0];
expect(specLink.innerHTML).toEqual('with a spec');
expect(specLink.getAttribute('href')).toEqual(
- '?foo=bar&spec=A Suite with a spec'
+ '/?foo=bar&spec=A Suite with a spec'
);
});
@@ -924,7 +924,7 @@ describe('HtmlReporter', function() {
var throwingExpectationsUI = container.querySelector('.jasmine-throw');
throwingExpectationsUI.click();
- expect(navigateHandler).toHaveBeenCalledWith('throwFailures', true);
+ expect(navigateHandler).toHaveBeenCalledWith('oneFailurePerSpec', true);
});
it('should navigate and change the setting to off', function() {
@@ -953,7 +953,10 @@ describe('HtmlReporter', function() {
var throwingExpectationsUI = container.querySelector('.jasmine-throw');
throwingExpectationsUI.click();
- expect(navigateHandler).toHaveBeenCalledWith('throwFailures', false);
+ expect(navigateHandler).toHaveBeenCalledWith(
+ 'oneFailurePerSpec',
+ false
+ );
});
});
describe('UI for hiding disabled specs', function() {
@@ -1169,7 +1172,7 @@ describe('HtmlReporter', function() {
var seedBar = container.querySelector('.jasmine-seed-bar');
expect(seedBar.textContent).toBe(', randomized with seed 424242');
var seedLink = container.querySelector('.jasmine-seed-bar a');
- expect(seedLink.getAttribute('href')).toBe('?seed=424242');
+ expect(seedLink.getAttribute('href')).toBe('/?seed=424242');
});
it('should not show the current seed bar if not randomizing', function() {
@@ -1218,7 +1221,7 @@ describe('HtmlReporter', function() {
reporter.jasmineDone({ order: { random: true } });
var skippedLink = container.querySelector('.jasmine-skipped a');
- expect(skippedLink.getAttribute('href')).toEqual('?foo=bar&spec=');
+ expect(skippedLink.getAttribute('href')).toEqual('/?foo=bar&spec=');
});
});
diff --git a/spec/npmPackage/npmPackageSpec.js b/spec/npmPackage/npmPackageSpec.js
index 70af391b..ada121fa 100644
--- a/spec/npmPackage/npmPackageSpec.js
+++ b/spec/npmPackage/npmPackageSpec.js
@@ -108,4 +108,56 @@ describe('npm package', function() {
false
);
});
+
+ it('does not have any unexpected files in the root directory', function() {
+ var files = fs.readdirSync(this.tmpDir);
+ expect(files).toEqual(['package']);
+ });
+
+ it('does not have any unexpected files in the package directory', function() {
+ var files = fs.readdirSync(path.resolve(this.tmpDir, 'package'));
+ files.sort();
+ expect(files).toEqual([
+ 'MIT.LICENSE',
+ 'README.md',
+ 'images',
+ 'lib',
+ 'package.json'
+ ]);
+ });
+
+ it('only has images in the images dir', function() {
+ var files = fs.readdirSync(path.resolve(this.tmpDir, 'package/images')),
+ i;
+
+ for (i = 0; i < files.length; i++) {
+ expect(files[i]).toMatch(/\.(svg|png)$/);
+ }
+ });
+
+ it('only has JS and CSS files in the lib dir', function() {
+ var files = [],
+ i;
+
+ function getFiles(dir) {
+ var dirents = fs.readdirSync(dir, { withFileTypes: true }),
+ j;
+
+ for (j = 0; j < dirents.length; j++) {
+ dirent = dirents[j];
+
+ if (dirent.isDirectory()) {
+ getFiles(path.resolve(dir, dirent.name));
+ } else {
+ files.push(path.resolve(dir, dirent.name));
+ }
+ }
+ }
+
+ getFiles(path.resolve(this.tmpDir, 'package/lib'));
+
+ for (i = 0; i < files.length; i++) {
+ expect(files[i]).toMatch(/\.(js|css)$/);
+ }
+ });
});
diff --git a/spec/support/jasmine-browser.js b/spec/support/jasmine-browser.js
index 9062bf8a..0845989b 100644
--- a/spec/support/jasmine-browser.js
+++ b/spec/support/jasmine-browser.js
@@ -27,7 +27,6 @@ module.exports = {
'helpers/domHelpers.js',
'helpers/integrationMatchers.js',
'helpers/promises.js',
- 'helpers/requireFastCheck.js',
'helpers/defineJasmineUnderTest.js',
'helpers/resetEnv.js'
],
diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json
index 1a0ca3b2..2c3c5107 100644
--- a/spec/support/jasmine.json
+++ b/spec/support/jasmine.json
@@ -14,7 +14,6 @@
"helpers/domHelpers.js",
"helpers/integrationMatchers.js",
"helpers/promises.js",
- "helpers/requireFastCheck.js",
"helpers/overrideConsoleLogForCircleCi.js",
"helpers/nodeDefineJasmineUnderTest.js",
"helpers/resetEnv.js"
diff --git a/src/core/Clock.js b/src/core/Clock.js
index 0ec89e23..30c1ca7a 100644
--- a/src/core/Clock.js
+++ b/src/core/Clock.js
@@ -6,9 +6,11 @@ getJasmineRequireObj().Clock = function() {
typeof process.versions.node === 'string';
/**
- * _Note:_ Do not construct this directly, Jasmine will make one during booting. You can get the current clock with {@link jasmine.clock}.
* @class Clock
- * @classdesc Jasmine's mock clock is used when testing time dependent code.
+ * @classdesc Jasmine's mock clock is used when testing time dependent code.
+ * _Note:_ Do not construct this directly. You can get the current clock with
+ * {@link jasmine.clock}.
+ * @hideconstructor
*/
function Clock(global, delayedFunctionSchedulerFactory, mockDate) {
var self = this,
diff --git a/src/core/Env.js b/src/core/Env.js
index f106b426..6640adfd 100644
--- a/src/core/Env.js
+++ b/src/core/Env.js
@@ -1,10 +1,11 @@
getJasmineRequireObj().Env = function(j$) {
/**
- * _Note:_ Do not construct this directly, Jasmine will make one during booting.
- * @name Env
+ * @class Env
* @since 2.0.0
- * @classdesc The Jasmine environment
- * @constructor
+ * @classdesc The Jasmine environment.
+ * _Note:_ Do not construct this directly. You can obtain the Env instance by
+ * calling {@link jasmine.getEnv}.
+ * @hideconstructor
*/
function Env(options) {
options = options || {};
@@ -35,7 +36,8 @@ getJasmineRequireObj().Env = function(j$) {
/**
* This represents the available options to configure Jasmine.
- * Options that are not provided will use their default values
+ * Options that are not provided will use their default values.
+ * @see Env#configure
* @interface Configuration
* @since 3.3.0
*/
diff --git a/src/core/Expectation.js b/src/core/Expectation.js
index fb2bcf9e..0309ec34 100644
--- a/src/core/Expectation.js
+++ b/src/core/Expectation.js
@@ -46,6 +46,12 @@ getJasmineRequireObj().Expectation = function(j$) {
* Asynchronous matchers that operate on an actual value which is a promise,
* and return a promise.
*
+ * Most async matchers will wait indefinitely for the promise to be resolved
+ * or rejected, resulting in a spec timeout if that never happens. If you
+ * expect that the promise will already be resolved or rejected at the time
+ * the matcher is called, you can use the {@link async-matchers#already}
+ * modifier to get a faster failure with a more helpful message.
+ *
* Note: Specs must await the result of each async matcher, return the
* promise returned by the matcher, or return a promise that's derived from
* the one returned by the matcher. Otherwise the matcher will not be
@@ -115,6 +121,23 @@ getJasmineRequireObj().Expectation = function(j$) {
}
});
+ /**
+ * Fail as soon as possible if the actual is pending.
+ * Otherwise evaluate the matcher.
+ * @member
+ * @name async-matchers#already
+ * @type {async-matchers}
+ * @example
+ * await expectAsync(myPromise).already.toBeResolved();
+ * @example
+ * return expectAsync(myPromise).already.toBeResolved();
+ */
+ Object.defineProperty(AsyncExpectation.prototype, 'already', {
+ get: function() {
+ return addFilter(this, expectSettledPromiseFilter);
+ }
+ });
+
function wrapSyncCompare(name, matcherFactory) {
return function() {
var result = this.expector.compare(name, matcherFactory, arguments);
@@ -193,6 +216,27 @@ getJasmineRequireObj().Expectation = function(j$) {
buildFailureMessage: negatedFailureMessage
};
+ var expectSettledPromiseFilter = {
+ selectComparisonFunc: function(matcher) {
+ return function(actual) {
+ var matcherArgs = arguments;
+
+ return j$.isPending_(actual).then(function(isPending) {
+ if (isPending) {
+ return {
+ pass: false,
+ message:
+ 'Expected a promise to be settled (via ' +
+ 'expectAsync(...).already) but it was pending.'
+ };
+ } else {
+ return matcher.compare.apply(null, matcherArgs);
+ }
+ });
+ };
+ }
+ };
+
function ContextAddingFilter(message) {
this.message = message;
}
diff --git a/src/core/Spy.js b/src/core/Spy.js
index e8f17712..101725a9 100644
--- a/src/core/Spy.js
+++ b/src/core/Spy.js
@@ -13,9 +13,11 @@ getJasmineRequireObj().Spy = function(j$) {
});
/**
- * _Note:_ Do not construct this directly, use {@link spyOn}, {@link spyOnProperty}, {@link jasmine.createSpy}, or {@link jasmine.createSpyObj}
- * @constructor
- * @name Spy
+ * @classdesc _Note:_ Do not construct this directly. Use {@link spyOn},
+ * {@link spyOnProperty}, {@link jasmine.createSpy}, or
+ * {@link jasmine.createSpyObj} instead.
+ * @class Spy
+ * @hideconstructor
*/
function Spy(
name,
diff --git a/src/core/SpyRegistry.js b/src/core/SpyRegistry.js
index 6e0bb33e..140bd990 100644
--- a/src/core/SpyRegistry.js
+++ b/src/core/SpyRegistry.js
@@ -163,7 +163,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
return spy;
};
- this.spyOnAllFunctions = function(obj) {
+ this.spyOnAllFunctions = function(obj, includeNonEnumerable) {
if (j$.util.isUndefined(obj)) {
throw new Error(
'spyOnAllFunctions could not find an object to spy upon'
@@ -171,30 +171,27 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
}
var pointer = obj,
- props = [],
- prop,
- descriptor;
+ propsToSpyOn = [],
+ properties,
+ propertiesToSkip = [];
- while (pointer) {
- for (prop in pointer) {
- if (
- Object.prototype.hasOwnProperty.call(pointer, prop) &&
- pointer[prop] instanceof Function
- ) {
- descriptor = Object.getOwnPropertyDescriptor(pointer, prop);
- if (
- (descriptor.writable || descriptor.set) &&
- descriptor.configurable
- ) {
- props.push(prop);
- }
- }
- }
+ while (
+ pointer &&
+ (!includeNonEnumerable || pointer !== Object.prototype)
+ ) {
+ properties = getProps(pointer, includeNonEnumerable);
+ properties = properties.filter(function(prop) {
+ return propertiesToSkip.indexOf(prop) === -1;
+ });
+ propertiesToSkip = propertiesToSkip.concat(properties);
+ propsToSpyOn = propsToSpyOn.concat(
+ getSpyableFunctionProps(pointer, properties)
+ );
pointer = Object.getPrototypeOf(pointer);
}
- for (var i = 0; i < props.length; i++) {
- this.spyOn(obj, props[i]);
+ for (var i = 0; i < propsToSpyOn.length; i++) {
+ this.spyOn(obj, propsToSpyOn[i]);
}
return obj;
@@ -209,5 +206,49 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
};
}
+ function getProps(obj, includeNonEnumerable) {
+ var enumerableProperties = Object.keys(obj);
+
+ if (!includeNonEnumerable) {
+ return enumerableProperties;
+ }
+
+ return Object.getOwnPropertyNames(obj).filter(function(prop) {
+ return (
+ prop !== 'constructor' ||
+ enumerableProperties.indexOf('constructor') > -1
+ );
+ });
+ }
+
+ function getSpyableFunctionProps(obj, propertiesToCheck) {
+ var props = [],
+ prop;
+ for (var i = 0; i < propertiesToCheck.length; i++) {
+ prop = propertiesToCheck[i];
+ if (
+ Object.prototype.hasOwnProperty.call(obj, prop) &&
+ isSpyableProp(obj, prop)
+ ) {
+ props.push(prop);
+ }
+ }
+ return props;
+ }
+
+ function isSpyableProp(obj, prop) {
+ var value, descriptor;
+ try {
+ value = obj[prop];
+ } catch (e) {
+ return false;
+ }
+ if (value instanceof Function) {
+ descriptor = Object.getOwnPropertyDescriptor(obj, prop);
+ return (descriptor.writable || descriptor.set) && descriptor.configurable;
+ }
+ return false;
+ }
+
return SpyRegistry;
};
diff --git a/src/core/base.js b/src/core/base.js
index 37707541..9741081e 100644
--- a/src/core/base.js
+++ b/src/core/base.js
@@ -201,6 +201,19 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
return matches ? matches[1] : '';
};
+ j$.isPending_ = function(promise) {
+ var sentinel = {};
+ // eslint-disable-next-line compat/compat
+ return Promise.race([promise, Promise.resolve(sentinel)]).then(
+ function(result) {
+ return result === sentinel;
+ },
+ function() {
+ return false;
+ }
+ );
+ };
+
/**
* Get a matcher, 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.
diff --git a/src/core/matchers/async/toBeResolved.js b/src/core/matchers/async/toBeResolved.js
index b9dda27a..0ef192be 100644
--- a/src/core/matchers/async/toBeResolved.js
+++ b/src/core/matchers/async/toBeResolved.js
@@ -10,7 +10,7 @@ getJasmineRequireObj().toBeResolved = function(j$) {
* @example
* return expectAsync(aPromise).toBeResolved();
*/
- return function toBeResolved() {
+ return function toBeResolved(matchersUtil) {
return {
compare: function(actual) {
if (!j$.isPromiseLike(actual)) {
@@ -21,8 +21,15 @@ getJasmineRequireObj().toBeResolved = function(j$) {
function() {
return { pass: true };
},
- function() {
- return { pass: false };
+ function(e) {
+ return {
+ pass: false,
+ message:
+ 'Expected a promise to be resolved but it was ' +
+ 'rejected with ' +
+ matchersUtil.pp(e) +
+ '.'
+ };
}
);
}
diff --git a/src/core/matchers/async/toBeResolvedTo.js b/src/core/matchers/async/toBeResolvedTo.js
index f95e7b10..235c3a22 100644
--- a/src/core/matchers/async/toBeResolvedTo.js
+++ b/src/core/matchers/async/toBeResolvedTo.js
@@ -45,10 +45,14 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) {
};
}
},
- function() {
+ function(e) {
return {
pass: false,
- message: prefix(false) + ' but it was rejected.'
+ message:
+ prefix(false) +
+ ' but it was rejected with ' +
+ matchersUtil.pp(e) +
+ '.'
};
}
);
diff --git a/src/core/matchers/matchersUtil.js b/src/core/matchers/matchersUtil.js
index f0e09575..ddd53798 100644
--- a/src/core/matchers/matchersUtil.js
+++ b/src/core/matchers/matchersUtil.js
@@ -1,10 +1,10 @@
getJasmineRequireObj().MatchersUtil = function(j$) {
/**
+ * @class MatchersUtil
+ * @classdesc Utilities for use in implementing matchers.
* _Note:_ Do not construct this directly. Jasmine will construct one and
* pass it to matchers and asymmetric equality testers.
- * @name MatchersUtil
- * @classdesc Utilities for use in implementing matchers
- * @constructor
+ * @hideconstructor
*/
function MatchersUtil(options) {
options = options || {};
@@ -599,3 +599,28 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
return MatchersUtil;
};
+
+/**
+ * @interface AsymmetricEqualityTester
+ * @classdesc An asymmetric equality tester is an object that can match multiple
+ * objects. Examples include jasmine.any() and jasmine.stringMatching().
+ * User-defined asymmetric equality testers can also be defined and used in
+ * expectations.
+ * @see custom_asymmetric_equality_testers
+ * @since 2.0.0
+ */
+/**
+ * Determines whether a value matches this tester
+ * @function
+ * @name AsymmetricEqualityTester#asymmetricMatch
+ * @param value {any} The value to test
+ * @param matchersUtil {MatchersUtil} utilities for testing equality, etc
+ * @return {Boolean}
+ */
+/**
+ * Returns a string representation of this tester to use in matcher failure messages
+ * @function
+ * @name AsymmetricEqualityTester#jasmineToString
+ * @param pp {function} Function that takes a value and returns a pretty-printed representation
+ * @return {String}
+ */
diff --git a/src/core/requireInterface.js b/src/core/requireInterface.js
index 6473ffaa..71f7d276 100644
--- a/src/core/requireInterface.js
+++ b/src/core/requireInterface.js
@@ -285,10 +285,11 @@ getJasmineRequireObj().interface = function(jasmine, env) {
* @function
* @global
* @param {Object} obj - The object upon which to install the {@link Spy}s
+ * @param {boolean} includeNonEnumerable - Whether or not to add spies to non-enumerable properties
* @returns {Object} the spied object
*/
- spyOnAllFunctions: function(obj) {
- return env.spyOnAllFunctions(obj);
+ spyOnAllFunctions: function(obj, includeNonEnumerable) {
+ return env.spyOnAllFunctions(obj, includeNonEnumerable);
},
jsApiReporter: new jasmine.JsApiReporter({
diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js
index 98f958d1..86061385 100644
--- a/src/html/HtmlReporter.js
+++ b/src/html/HtmlReporter.js
@@ -177,7 +177,10 @@ jasmineRequire.HtmlReporter = function(j$) {
' of ' +
totalSpecsDefined +
' specs - run all';
- var skippedLink = addToExistingQueryString('spec', '');
+ // include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
+ var skippedLink =
+ (window.location.pathname || '') +
+ addToExistingQueryString('spec', '');
alert.appendChild(
createDom(
'span',
@@ -545,7 +548,7 @@ jasmineRequire.HtmlReporter = function(j$) {
);
throwCheckbox.checked = config.oneFailurePerSpec;
throwCheckbox.onclick = function() {
- navigateWithNewParam('throwFailures', !config.oneFailurePerSpec);
+ navigateWithNewParam('oneFailurePerSpec', !config.oneFailurePerSpec);
};
var randomCheckbox = optionsMenuDom.querySelector(
@@ -615,7 +618,11 @@ jasmineRequire.HtmlReporter = function(j$) {
suite = suite.parent;
}
- return addToExistingQueryString('spec', els.join(' '));
+ // include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
+ return (
+ (window.location.pathname || '') +
+ addToExistingQueryString('spec', els.join(' '))
+ );
}
function addDeprecationWarnings(result, runnableType) {
@@ -718,11 +725,19 @@ jasmineRequire.HtmlReporter = function(j$) {
}
function specHref(result) {
- return addToExistingQueryString('spec', result.fullName);
+ // include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
+ return (
+ (window.location.pathname || '') +
+ addToExistingQueryString('spec', result.fullName)
+ );
}
function seedHref(seed) {
- return addToExistingQueryString('seed', seed);
+ // include window.location.pathname to fix issue with karma-jasmine-html-reporter in angular: see https://github.com/jasmine/jasmine/issues/1906
+ return (
+ (window.location.pathname || '') +
+ addToExistingQueryString('seed', seed)
+ );
}
function defaultQueryString(key, value) {