Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5872bba66 | ||
|
|
c4f4edda1b | ||
|
|
cf057b6631 | ||
|
|
2a7a157713 | ||
|
|
7463fe511b | ||
|
|
1b724daa10 | ||
|
|
888d3b6250 | ||
|
|
289afbf0a6 | ||
|
|
9b89bee4f5 | ||
|
|
3f8f488a58 | ||
|
|
592d47e971 | ||
|
|
4732012f1c | ||
|
|
7683325d68 | ||
|
|
03d665e243 | ||
|
|
a1591da25d | ||
|
|
1f1e1209d2 | ||
|
|
a389905a38 | ||
|
|
36dd6b07d1 | ||
|
|
2f3689713b | ||
|
|
819fab7b58 | ||
|
|
e5ca1f37f1 | ||
|
|
c3650ea7c7 | ||
|
|
1805337424 | ||
|
|
27bb6ebac1 | ||
|
|
580323c221 | ||
|
|
d9286c549f | ||
|
|
26dfa6d257 | ||
|
|
483d4ab3c3 | ||
|
|
663dfe5932 | ||
|
|
ce9c752899 | ||
|
|
d5e7bc9fd6 | ||
|
|
744e765d6f | ||
|
|
29551ba4f3 | ||
|
|
bd9a3b2305 |
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Questions, requests for help, etc
|
||||
url: https://github.com/jasmine/jasmine/discussions/new/choose
|
||||
about: Please start a discussion.
|
||||
- name: Issues with the `jasmine` CLI
|
||||
url: https://github.com/jasmine/jasmine-npm/issues
|
||||
about: Please create issues related to the `jasmine` package in its repository.
|
||||
|
||||
73
.github/ISSUE_TEMPLATE/support_request.yml
vendored
73
.github/ISSUE_TEMPLATE/support_request.yml
vendored
@@ -1,73 +0,0 @@
|
||||
name: Question or Support Request
|
||||
description: I need help using Jasmine
|
||||
labels: ["question"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Jasmine is supported by volunteers working in their free time. Although we're generally willing to help, we're going to ask you to put in some effort first to help us help you.
|
||||
|
||||
## Troubleshooting
|
||||
Please take the time to rule out problems with your code or third party libraries before opening an issue. If you're running into an error, try to determine whether the error is coming from Jasmine, another library, or your own code.
|
||||
|
||||
Check the [FAQ](https://jasmine.github.io/pages/faq.html) and any other relevant [documentation](https://jasmine.github.io/pages/docs_home.html) to see if your question has already been answered. Consider searching [Stack Overflow](https://stackoverflow.com/questions/tagged/jasmine) and past issues in this repository for related questions as well.
|
||||
|
||||
## Special troubleshooting steps for asynchronous scenarios
|
||||
If the issue has to do with testing asynchronous code, please read the [async tutorial](https://jasmine.github.io/tutorials/async) and the async section of the FAQ. In particular, check for the following common errors:
|
||||
|
||||
* Are you trying to write a synchronous test for asynchronous code?
|
||||
* Does the test signal completion before the code under test finishes?
|
||||
* Do expectations run before the code that they're trying to verify?
|
||||
|
||||
## Consider asking Angular questions in an Angular forum
|
||||
|
||||
Questions like "how do I test this Angular service" are mostly about Angular, not Jasmine. You'll likely get better responses in an Angular forum. Here's a rule of thumb: If you can't demonstrate the problem without Angular, you probably have an Angular question.
|
||||
|
||||
## Try the latest version of Jasmine
|
||||
If at all possible, upgrade to the latest versions of `jasmine-core` and any other relevant packages (e.g. `jasmine`, `jasmine-browser-runner`).
|
||||
|
||||
## Put together a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example)
|
||||
Please help us help you by creating a minimal but complete setup that demonstrates the problem. Remove any code and libraries that aren't absolutely necessary, but make sure it doesn't depend on any code you haven't included. In many cases a simple code snippet is enough. In cases involving external libraries, *especially* Karma or Angular, we're likely to need a runable Git repository or jsbin/stackblitz/etc.
|
||||
|
||||
- type: textarea
|
||||
id: question
|
||||
attributes:
|
||||
label: Your question
|
||||
description: Clearly describe what you'd like help with.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: code-sample
|
||||
attributes:
|
||||
label: Example code that demonstrates the problem
|
||||
description: Please include either a code snippet that demonstrates the problem or a link to a repository or jsbin/stackblitz/etc containing a minimal, reproducible example as described above.
|
||||
render: JavaScript
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: jasmine-core-version
|
||||
attributes:
|
||||
label: jasmine-core version
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: other-versions
|
||||
attributes:
|
||||
label: Versions of other relevant packages
|
||||
placeholder: |
|
||||
jasmine-browser-runner 1.2.0
|
||||
fancy-reporter 132.4.8
|
||||
- type: input
|
||||
id: browser-or-node-version
|
||||
attributes:
|
||||
label: Node.js or browser version
|
||||
placeholder: E.g. "node 16.2.0" or "Safari 15"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
placeholder: E.g. "Windows 10", "MacOS 12.5", "MCC Interim Linux 0.99.p8"
|
||||
validations:
|
||||
required: true
|
||||
2
LICENSE
2
LICENSE
@@ -1,5 +1,5 @@
|
||||
Copyright (c) 2008-2019 Pivotal Labs
|
||||
Copyright (c) 2008-2023 The Jasmine developers
|
||||
Copyright (c) 2008-2025 The Jasmine developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
10
README.md
10
README.md
@@ -30,7 +30,7 @@ Microsoft Edge) as well as Node.
|
||||
| Environment | Supported versions |
|
||||
|-------------------|----------------------------|
|
||||
| Node | 18, 20, 22 |
|
||||
| Safari | 15-17 |
|
||||
| Safari | 15*, 16*, 17* |
|
||||
| Chrome | Evergreen |
|
||||
| Firefox | Evergreen, 102*, 115*, 128 |
|
||||
| Edge | Evergreen |
|
||||
@@ -39,9 +39,9 @@ For evergreen browsers, each version of Jasmine is tested against the version of
|
||||
at the time of release. Other browsers, as well as older & newer versions of some supported browsers, are likely to work.
|
||||
However, Jasmine isn't tested against them and they aren't actively supported.
|
||||
|
||||
\* Environments that are past end of life are supported on a best-effort basis.
|
||||
They may be dropped in a future minor release of Jasmine if continued support
|
||||
becomes impractical.
|
||||
\* Supported on a best-effort basis. Support for these versions may be dropped
|
||||
if it becomes impractical, and bugs affecting only these versions may not be
|
||||
treated as release blockers.
|
||||
|
||||
To find out what environments work with a particular Jasmine release, see the [release notes](https://github.com/jasmine/jasmine/tree/main/release_notes).
|
||||
|
||||
@@ -60,5 +60,5 @@ To find out what environments work with a particular Jasmine release, see the [r
|
||||
* Sheel Choksi
|
||||
|
||||
Copyright (c) 2008-2019 Pivotal Labs<br>
|
||||
Copyright (c) 2008-2023 The Jasmine developers<br>
|
||||
Copyright (c) 2008-2025 The Jasmine developers<br>
|
||||
This software is licensed under the [MIT License](https://github.com/jasmine/jasmine/blob/main/LICENSE).
|
||||
|
||||
@@ -46,8 +46,8 @@ When ready to release - specs are all green and the stories are done:
|
||||
|
||||
### Release the core NPM module
|
||||
|
||||
1. `npm adduser` to save your credentials locally
|
||||
1. `npm publish .` to publish what's in `package.json`
|
||||
1. `npm login` to save your credentials locally
|
||||
2. `npm publish .` to publish what's in `package.json`
|
||||
|
||||
### Release the docs
|
||||
|
||||
@@ -55,11 +55,6 @@ Probably only need to do this when releasing a minor version, and not a patch
|
||||
version. See [the README file in the docs repo](https://github.com/jasmine/jasmine.github.io/blob/master/README.md)
|
||||
for instructions.
|
||||
|
||||
1. `rake update_edge_jasmine`
|
||||
1. `npm run jsdoc`
|
||||
1. `rake release[${version}]` to copy the current edge docs to the new version
|
||||
1. Commit and push.
|
||||
|
||||
### Release the `jasmine` NPM package
|
||||
|
||||
See <https://github.com/jasmine/jasmine-npm/blob/main/RELEASE.md>.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Copyright (c) 2008-2019 Pivotal Labs
|
||||
Copyright (c) 2008-2024 The Jasmine developers
|
||||
Copyright (c) 2008-2025 The Jasmine developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Copyright (c) 2008-2019 Pivotal Labs
|
||||
Copyright (c) 2008-2024 The Jasmine developers
|
||||
Copyright (c) 2008-2025 The Jasmine developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Copyright (c) 2008-2019 Pivotal Labs
|
||||
Copyright (c) 2008-2024 The Jasmine developers
|
||||
Copyright (c) 2008-2025 The Jasmine developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Copyright (c) 2008-2019 Pivotal Labs
|
||||
Copyright (c) 2008-2024 The Jasmine developers
|
||||
Copyright (c) 2008-2025 The Jasmine developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
@@ -150,6 +150,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
|
||||
'toBeTrue',
|
||||
'toBeTruthy',
|
||||
'toBeUndefined',
|
||||
'toBeNullish',
|
||||
'toContain',
|
||||
'toEqual',
|
||||
'toHaveSize',
|
||||
@@ -159,7 +160,9 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
|
||||
'toHaveBeenCalledTimes',
|
||||
'toHaveBeenCalledWith',
|
||||
'toHaveClass',
|
||||
'toHaveClasses',
|
||||
'toHaveSpyInteractions',
|
||||
'toHaveNoOtherSpyInteractions',
|
||||
'toMatch',
|
||||
'toThrow',
|
||||
'toThrowError',
|
||||
@@ -1295,6 +1298,15 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
* @default true
|
||||
*/
|
||||
autoCleanClosures: true,
|
||||
/**
|
||||
* Whether to forbid duplicate spec or suite names. If set to true, using
|
||||
* the same name multiple times in the same immediate parent suite is an
|
||||
* error.
|
||||
* @name Configuration#forbidDuplicateNames
|
||||
* @type boolean
|
||||
* @default false
|
||||
*/
|
||||
forbidDuplicateNames: false,
|
||||
/**
|
||||
* Whether or not to issue warnings for certain deprecated functionality
|
||||
* every time it's used. If not set or set to false, deprecation warnings
|
||||
@@ -1343,7 +1355,8 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
'hideDisabled',
|
||||
'stopOnSpecFailure',
|
||||
'stopSpecOnExpectationFailure',
|
||||
'autoCleanClosures'
|
||||
'autoCleanClosures',
|
||||
'forbidDuplicateNames'
|
||||
];
|
||||
|
||||
booleanProps.forEach(function(prop) {
|
||||
@@ -2886,6 +2899,10 @@ getJasmineRequireObj().CallTracker = function(j$) {
|
||||
this.saveArgumentsByValue = function() {
|
||||
opts.cloneArgs = true;
|
||||
};
|
||||
|
||||
this.unverifiedCount = function() {
|
||||
return calls.reduce((count, call) => count + (call.verified ? 0 : 1), 0);
|
||||
};
|
||||
}
|
||||
|
||||
return CallTracker;
|
||||
@@ -4322,7 +4339,9 @@ getJasmineRequireObj().toBePending = function(j$) {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
if (!j$.isPromiseLike(actual)) {
|
||||
throw new Error('Expected toBePending to be called on a promise.');
|
||||
throw new Error(
|
||||
`Expected toBePending to be called on a promise but was on a ${typeof actual}.`
|
||||
);
|
||||
}
|
||||
const want = {};
|
||||
return Promise.race([actual, Promise.resolve(want)]).then(
|
||||
@@ -4354,7 +4373,9 @@ getJasmineRequireObj().toBeRejected = function(j$) {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
if (!j$.isPromiseLike(actual)) {
|
||||
throw new Error('Expected toBeRejected to be called on a promise.');
|
||||
throw new Error(
|
||||
`Expected toBeRejected to be called on a promise but was on a ${typeof actual}.`
|
||||
);
|
||||
}
|
||||
return actual.then(
|
||||
function() {
|
||||
@@ -4387,7 +4408,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) {
|
||||
compare: function(actualPromise, expectedValue) {
|
||||
if (!j$.isPromiseLike(actualPromise)) {
|
||||
throw new Error(
|
||||
'Expected toBeRejectedWith to be called on a promise.'
|
||||
`Expected toBeRejectedWith to be called on a promise but was on a ${typeof actualPromise}.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4451,7 +4472,7 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) {
|
||||
compare: function(actualPromise, arg1, arg2) {
|
||||
if (!j$.isPromiseLike(actualPromise)) {
|
||||
throw new Error(
|
||||
'Expected toBeRejectedWithError to be called on a promise.'
|
||||
`Expected toBeRejectedWithError to be called on a promise but was on a ${typeof actualPromise}.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4569,7 +4590,9 @@ getJasmineRequireObj().toBeResolved = function(j$) {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
if (!j$.isPromiseLike(actual)) {
|
||||
throw new Error('Expected toBeResolved to be called on a promise.');
|
||||
throw new Error(
|
||||
`Expected toBeResolved to be called on a promise but was on a ${typeof actual}.`
|
||||
);
|
||||
}
|
||||
|
||||
return actual.then(
|
||||
@@ -4609,7 +4632,9 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) {
|
||||
return {
|
||||
compare: function(actualPromise, expectedValue) {
|
||||
if (!j$.isPromiseLike(actualPromise)) {
|
||||
throw new Error('Expected toBeResolvedTo to be called on a promise.');
|
||||
throw new Error(
|
||||
`Expected toBeResolvedTo to be called on a promise but was on a ${typeof actualPromise}.`
|
||||
);
|
||||
}
|
||||
|
||||
function prefix(passed) {
|
||||
@@ -5253,7 +5278,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
|
||||
diffBuilder.recordMismatch(
|
||||
objectKeysAreDifferentFormatter.bind(null, this.pp)
|
||||
);
|
||||
return false;
|
||||
result = false;
|
||||
}
|
||||
|
||||
for (const key of aKeys) {
|
||||
@@ -6000,6 +6025,28 @@ getJasmineRequireObj().toBeNull = function() {
|
||||
return toBeNull;
|
||||
};
|
||||
|
||||
getJasmineRequireObj().toBeNullish = function() {
|
||||
/**
|
||||
* {@link expect} the actual value to be `null` or `undefined`.
|
||||
* @function
|
||||
* @name matchers#toBeNullish
|
||||
* @since 5.6.0
|
||||
* @example
|
||||
* expect(result).toBeNullish():
|
||||
*/
|
||||
function toBeNullish() {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
return {
|
||||
pass: null === actual || void 0 === actual
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return toBeNullish;
|
||||
};
|
||||
|
||||
getJasmineRequireObj().toBePositiveInfinity = function(j$) {
|
||||
/**
|
||||
* {@link expect} the actual value to be `Infinity` (infinity).
|
||||
@@ -6189,6 +6236,8 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) {
|
||||
|
||||
result.pass = actual.calls.any();
|
||||
|
||||
actual.calls.all().forEach(call => (call.verified = true));
|
||||
|
||||
result.message = result.pass
|
||||
? 'Expected spy ' + actual.and.identity + ' not to have been called.'
|
||||
: 'Expected spy ' + actual.and.identity + ' to have been called.';
|
||||
@@ -6253,6 +6302,9 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) {
|
||||
result.pass = latest1stSpyCall < first2ndSpyCall;
|
||||
|
||||
if (result.pass) {
|
||||
firstSpy.calls.mostRecent().verified = true;
|
||||
latterSpy.calls.first().verified = true;
|
||||
|
||||
result.message =
|
||||
'Expected spy ' +
|
||||
firstSpy.and.identity +
|
||||
@@ -6309,7 +6361,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
|
||||
* @example
|
||||
* expect(mySpy).toHaveBeenCalledOnceWith('foo', 'bar', 2);
|
||||
*/
|
||||
function toHaveBeenCalledOnceWith(util) {
|
||||
function toHaveBeenCalledOnceWith(matchersUtil) {
|
||||
return {
|
||||
compare: function() {
|
||||
const args = Array.prototype.slice.call(arguments, 0),
|
||||
@@ -6318,20 +6370,29 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
|
||||
|
||||
if (!j$.isSpy(actual)) {
|
||||
throw new Error(
|
||||
getErrorMsg('Expected a spy, but got ' + util.pp(actual) + '.')
|
||||
getErrorMsg(
|
||||
'Expected a spy, but got ' + matchersUtil.pp(actual) + '.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const prettyPrintedCalls = actual.calls
|
||||
.allArgs()
|
||||
.map(function(argsForCall) {
|
||||
return ' ' + util.pp(argsForCall);
|
||||
return ' ' + matchersUtil.pp(argsForCall);
|
||||
});
|
||||
|
||||
if (
|
||||
actual.calls.count() === 1 &&
|
||||
util.contains(actual.calls.allArgs(), expectedArgs)
|
||||
matchersUtil.contains(actual.calls.allArgs(), expectedArgs)
|
||||
) {
|
||||
const firstIndex = actual.calls
|
||||
.all()
|
||||
.findIndex(call => matchersUtil.equals(call.args, expectedArgs));
|
||||
if (firstIndex > -1) {
|
||||
actual.calls.all()[firstIndex].verified = true;
|
||||
}
|
||||
|
||||
return {
|
||||
pass: true,
|
||||
message:
|
||||
@@ -6339,7 +6400,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
|
||||
actual.and.identity +
|
||||
' to have been called 0 times, multiple times, or once, but with arguments different from:\n' +
|
||||
' ' +
|
||||
util.pp(expectedArgs) +
|
||||
matchersUtil.pp(expectedArgs) +
|
||||
'\n' +
|
||||
'But the actual call was:\n' +
|
||||
prettyPrintedCalls.join(',\n') +
|
||||
@@ -6350,7 +6411,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
|
||||
function getDiffs() {
|
||||
return actual.calls.allArgs().map(function(argsForCall, callIx) {
|
||||
const diffBuilder = new j$.DiffBuilder();
|
||||
util.equals(argsForCall, expectedArgs, diffBuilder);
|
||||
matchersUtil.equals(argsForCall, expectedArgs, diffBuilder);
|
||||
return diffBuilder.getMessage();
|
||||
});
|
||||
}
|
||||
@@ -6383,7 +6444,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
|
||||
actual.and.identity +
|
||||
' to have been called only once, and with given args:\n' +
|
||||
' ' +
|
||||
util.pp(expectedArgs) +
|
||||
matchersUtil.pp(expectedArgs) +
|
||||
'\n' +
|
||||
butString()
|
||||
};
|
||||
@@ -6432,23 +6493,35 @@ getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) {
|
||||
}
|
||||
|
||||
actual = args[0];
|
||||
const calls = actual.calls.count();
|
||||
|
||||
const callsCount = actual.calls.count();
|
||||
const timesMessage = expected === 1 ? 'once' : expected + ' times';
|
||||
result.pass = calls === expected;
|
||||
|
||||
result.pass = callsCount === expected;
|
||||
|
||||
if (result.pass) {
|
||||
const allCalls = actual.calls.all();
|
||||
const max = Math.min(expected, callsCount);
|
||||
|
||||
for (let i = 0; i < max; i++) {
|
||||
allCalls[i].verified = true;
|
||||
}
|
||||
}
|
||||
|
||||
result.message = result.pass
|
||||
? 'Expected spy ' +
|
||||
actual.and.identity +
|
||||
' not to have been called ' +
|
||||
timesMessage +
|
||||
'. It was called ' +
|
||||
calls +
|
||||
callsCount +
|
||||
' times.'
|
||||
: 'Expected spy ' +
|
||||
actual.and.identity +
|
||||
' to have been called ' +
|
||||
timesMessage +
|
||||
'. It was called ' +
|
||||
calls +
|
||||
callsCount +
|
||||
' times.';
|
||||
return result;
|
||||
}
|
||||
@@ -6504,6 +6577,11 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
|
||||
}
|
||||
|
||||
if (matchersUtil.contains(actual.calls.allArgs(), expectedArgs)) {
|
||||
actual.calls
|
||||
.all()
|
||||
.filter(call => matchersUtil.equals(call.args, expectedArgs))
|
||||
.forEach(call => (call.verified = true));
|
||||
|
||||
result.pass = true;
|
||||
result.message = function() {
|
||||
return (
|
||||
@@ -6595,6 +6673,127 @@ getJasmineRequireObj().toHaveClass = function(j$) {
|
||||
return toHaveClass;
|
||||
};
|
||||
|
||||
getJasmineRequireObj().toHaveClasses = function(j$) {
|
||||
/**
|
||||
* {@link expect} the actual value to be a DOM element that has the expected classes
|
||||
* @function
|
||||
* @name matchers#toHaveClasses
|
||||
* @since 5.6.0
|
||||
* @param {Object} expected - The class names to test for
|
||||
* @example
|
||||
* const el = document.createElement('div');
|
||||
* el.className = 'foo bar baz';
|
||||
* expect(el).toHaveClasses(['bar', 'baz']);
|
||||
*/
|
||||
function toHaveClasses(matchersUtil) {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
if (!isElement(actual)) {
|
||||
throw new Error(matchersUtil.pp(actual) + ' is not a DOM element');
|
||||
}
|
||||
|
||||
return {
|
||||
pass: expected.every(e => actual.classList.contains(e))
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function isElement(maybeEl) {
|
||||
return (
|
||||
maybeEl && maybeEl.classList && j$.isFunction_(maybeEl.classList.contains)
|
||||
);
|
||||
}
|
||||
|
||||
return toHaveClasses;
|
||||
};
|
||||
|
||||
getJasmineRequireObj().toHaveNoOtherSpyInteractions = function(j$) {
|
||||
const getErrorMsg = j$.formatErrorMsg(
|
||||
'<toHaveNoOtherSpyInteractions>',
|
||||
'expect(<spyObj>).toHaveNoOtherSpyInteractions()'
|
||||
);
|
||||
|
||||
/**
|
||||
* {@link expect} the actual (a {@link SpyObj}) spies to have not been called except interactions which was already tracked with `toHaveBeenCalled`.
|
||||
* @function
|
||||
* @name matchers#toHaveNoOtherSpyInteractions
|
||||
* @example
|
||||
* expect(mySpyObj).toHaveNoOtherSpyInteractions();
|
||||
* expect(mySpyObj).not.toHaveNoOtherSpyInteractions();
|
||||
*/
|
||||
function toHaveNoOtherSpyInteractions(matchersUtil) {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
const result = {};
|
||||
|
||||
if (!j$.isObject_(actual)) {
|
||||
throw new Error(
|
||||
getErrorMsg('Expected an object, but got ' + typeof actual + '.')
|
||||
);
|
||||
}
|
||||
|
||||
if (arguments.length > 1) {
|
||||
throw new Error(getErrorMsg('Does not take arguments'));
|
||||
}
|
||||
|
||||
result.pass = true;
|
||||
let hasSpy = false;
|
||||
const unexpectedCalls = [];
|
||||
|
||||
for (const spy of Object.values(actual)) {
|
||||
if (!j$.isSpy(spy)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hasSpy = true;
|
||||
|
||||
const unverifiedCalls = spy.calls
|
||||
.all()
|
||||
.filter(call => !call.verified);
|
||||
|
||||
if (unverifiedCalls.length > 0) {
|
||||
result.pass = false;
|
||||
}
|
||||
|
||||
unverifiedCalls.forEach(unverifiedCall => {
|
||||
unexpectedCalls.push([
|
||||
spy.and.identity,
|
||||
matchersUtil.pp(unverifiedCall.args)
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasSpy) {
|
||||
throw new Error(
|
||||
getErrorMsg(
|
||||
'Expected an object with spies, but object has no spies.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (result.pass) {
|
||||
result.message =
|
||||
"Expected a spy object to have other spy interactions but it didn't.";
|
||||
} else {
|
||||
const ppUnexpectedCalls = unexpectedCalls
|
||||
.map(([spyName, args]) => ` ${spyName} called with ${args}`)
|
||||
.join(',\n');
|
||||
|
||||
result.message =
|
||||
'Expected a spy object to have no other spy interactions, but it had the following calls:\n' +
|
||||
ppUnexpectedCalls +
|
||||
'.';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return toHaveNoOtherSpyInteractions;
|
||||
};
|
||||
|
||||
getJasmineRequireObj().toHaveSize = function(j$) {
|
||||
/**
|
||||
* {@link expect} the actual size to be equal to the expected, using array-like length or object keys size.
|
||||
@@ -9173,7 +9372,8 @@ getJasmineRequireObj().Spy = function(j$) {
|
||||
const callData = {
|
||||
object: context,
|
||||
invocationOrder: nextOrder(),
|
||||
args: Array.prototype.slice.apply(args)
|
||||
args: Array.prototype.slice.apply(args),
|
||||
verified: false
|
||||
};
|
||||
|
||||
callTracker.track(callData);
|
||||
@@ -10272,6 +10472,16 @@ getJasmineRequireObj().Suite = function(j$) {
|
||||
);
|
||||
};
|
||||
|
||||
Suite.prototype.hasChildWithDescription = function(description) {
|
||||
for (const child of this.children) {
|
||||
if (child.description === description) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
Object.defineProperty(Suite.prototype, 'metadata', {
|
||||
get: function() {
|
||||
if (!this.metadata_) {
|
||||
@@ -10509,6 +10719,8 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
|
||||
j$.util.validateTimeout(timeout);
|
||||
}
|
||||
|
||||
this.checkDuplicate_(description, 'spec');
|
||||
|
||||
const spec = this.specFactory_(description, fn, timeout, filename);
|
||||
if (this.currentDeclarationSuite_.markedExcluding) {
|
||||
spec.exclude();
|
||||
@@ -10518,7 +10730,27 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
|
||||
return spec;
|
||||
}
|
||||
|
||||
checkDuplicate_(description, type) {
|
||||
if (!this.env_.configuration().forbidDuplicateNames) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.currentDeclarationSuite_.hasChildWithDescription(description)) {
|
||||
const parentDesc =
|
||||
this.currentDeclarationSuite_ === this.topSuite
|
||||
? 'top suite'
|
||||
: `"${this.currentDeclarationSuite_.getFullName()}"`;
|
||||
throw new Error(
|
||||
`Duplicate ${type} name "${description}" found in ${parentDesc}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
suiteFactory_(description, filename) {
|
||||
if (this.topSuite) {
|
||||
this.checkDuplicate_(description, 'suite');
|
||||
}
|
||||
|
||||
const config = this.env_.configuration();
|
||||
const parentSuite = this.currentDeclarationSuite_;
|
||||
const reportedParentSuiteId =
|
||||
@@ -10999,5 +11231,5 @@ getJasmineRequireObj().UserContext = function(j$) {
|
||||
};
|
||||
|
||||
getJasmineRequireObj().version = function() {
|
||||
return '5.4.0';
|
||||
return '5.6.0';
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jasmine-core",
|
||||
"license": "MIT",
|
||||
"version": "5.4.0",
|
||||
"version": "5.6.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jasmine/jasmine.git"
|
||||
|
||||
34
release_notes/5.5.0.md
Normal file
34
release_notes/5.5.0.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Jasmine Core 5.5.0 Release Notes
|
||||
|
||||
## Changes
|
||||
|
||||
* Optionally enforce uniqueness of spec and suite names
|
||||
|
||||
This is off by default for backwards compatibility but can be enabled
|
||||
by setting the `forbidDuplicateNames` env config property to true.
|
||||
Fixes [#1633](https://github.com/jasmine/jasmine/issues/1633).
|
||||
|
||||
* Include property value mismatches in diffs even when there are missing or
|
||||
extra properties
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------------|-------------------------|
|
||||
| Node | 18, 20, 22 |
|
||||
| Safari | 15-17 |
|
||||
| Chrome | 131* |
|
||||
| Firefox | 102**, 115**, 128, 132* |
|
||||
| Edge | 131* |
|
||||
|
||||
\* Evergreen browser. Each version of Jasmine is tested against the latest
|
||||
version available at release time.<br>
|
||||
\** Environments that are past end of life are supported on a best-effort basis.
|
||||
They may be dropped in a future minor release of Jasmine if continued support
|
||||
becomes impractical.
|
||||
|
||||
------
|
||||
|
||||
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
|
||||
51
release_notes/5.6.0.md
Normal file
51
release_notes/5.6.0.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Jasmine Core 5.6.0 Release Notes
|
||||
|
||||
## Changes
|
||||
|
||||
* Added [toHaveNoOtherSpyInteractions](https://jasmine.github.io/api/5.6/matchers.html#toHaveNoOtherSpyInteractions) matcher
|
||||
* Merges [#2051](https://github.com/jasmine/jasmine/pull/2051) from @Eradev
|
||||
* Fixes [#1991](https://github.com/jasmine/jasmine/issues/1991)
|
||||
|
||||
* Added [toBeNullish](https://jasmine.github.io/api/5.6/matchers.html#toBeNullish) matcher
|
||||
* Merges [#2045](https://github.com/jasmine/jasmine/pull/2045) from @MattMcCherry
|
||||
|
||||
* Improved error messages when non-promises are passed to built-in async matchers
|
||||
* Merges [#2049](https://github.com/jasmine/jasmine/pull/2049) from @andiz2
|
||||
* Fixes [#2037](https://github.com/jasmine/jasmine/issues/2037)
|
||||
|
||||
* Added [toHaveClasses](https://jasmine.github.io/api/5.6/matchers.html#toHaveClasses) matcher
|
||||
* Merges [#2046](https://github.com/jasmine/jasmine/pull/2046) from @aYorky
|
||||
|
||||
## Documentation updates
|
||||
|
||||
* Demoted Safari to best-effort support
|
||||
|
||||
Due to limited availability of Safari versions for contributors and maintainers
|
||||
as well as in CI, Safari will be supported on the same best-effort basis as
|
||||
environments that are past end of life, such as previous Firefox ESR versions.
|
||||
See [this discussion](https://github.com/jasmine/jasmine/discussions/2050) for
|
||||
more information about why this change was made and what to expect.
|
||||
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------------|-------------------------|
|
||||
| Node | 18, 20, 22 |
|
||||
| Safari | 15**, 16**, 17** |
|
||||
| Chrome | 133* |
|
||||
| Firefox | 102**, 115**, 128, 135* |
|
||||
| Edge | 132* |
|
||||
|
||||
\* Evergreen browser. Each version of Jasmine is tested against the latest
|
||||
version available at release time.<br>
|
||||
\** Supported on a best-effort basis. Support for these versions may be dropped
|
||||
if it becomes impractical, and bugs affecting only these versions may not be
|
||||
treated as release blockers.
|
||||
|
||||
|
||||
------
|
||||
|
||||
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
|
||||
@@ -176,6 +176,117 @@ describe('SuiteBuilder', function() {
|
||||
};
|
||||
}
|
||||
|
||||
describe('Duplicate name handling', function() {
|
||||
describe('When forbidDuplicateNames is true', function() {
|
||||
let env;
|
||||
|
||||
beforeEach(function() {
|
||||
env = { configuration: () => ({ forbidDuplicateNames: true }) };
|
||||
});
|
||||
|
||||
it('forbids duplicate spec names', function() {
|
||||
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
|
||||
|
||||
expect(function() {
|
||||
suiteBuilder.describe('a suite', function() {
|
||||
suiteBuilder.describe('a nested suite', function() {
|
||||
suiteBuilder.it('a spec');
|
||||
suiteBuilder.it('a spec');
|
||||
});
|
||||
});
|
||||
}).toThrowError(
|
||||
'Duplicate spec name "a spec" found in "a suite a nested suite"'
|
||||
);
|
||||
});
|
||||
|
||||
it('forbids duplicate spec names in the top suite', function() {
|
||||
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
|
||||
|
||||
expect(function() {
|
||||
suiteBuilder.it('another spec');
|
||||
suiteBuilder.it('another spec');
|
||||
}).toThrowError(
|
||||
'Duplicate spec name "another spec" found in top suite'
|
||||
);
|
||||
});
|
||||
|
||||
it('forbids duplicate suite names', function() {
|
||||
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
|
||||
|
||||
expect(function() {
|
||||
suiteBuilder.describe('a suite', function() {
|
||||
suiteBuilder.describe('a nested suite', function() {
|
||||
suiteBuilder.describe('another suite', function() {
|
||||
suiteBuilder.it('a spec');
|
||||
});
|
||||
suiteBuilder.describe('another suite', function() {
|
||||
suiteBuilder.it('a spec');
|
||||
});
|
||||
});
|
||||
});
|
||||
}).toThrowError(
|
||||
'Duplicate suite name "another suite" found in "a suite a nested suite"'
|
||||
);
|
||||
});
|
||||
|
||||
it('forbids duplicate suite names in the top suite', function() {
|
||||
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
|
||||
|
||||
expect(function() {
|
||||
suiteBuilder.describe('a suite', function() {
|
||||
suiteBuilder.it('a spec');
|
||||
});
|
||||
suiteBuilder.describe('a suite', function() {
|
||||
suiteBuilder.it('a spec');
|
||||
});
|
||||
}).toThrowError('Duplicate suite name "a suite" found in top suite');
|
||||
});
|
||||
|
||||
it('allows spec and suite names to be duplicated in different suites', function() {
|
||||
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
|
||||
|
||||
expect(function() {
|
||||
suiteBuilder.describe('suite a', function() {
|
||||
suiteBuilder.describe('dupe suite', function() {
|
||||
suiteBuilder.it('dupe spec');
|
||||
suiteBuilder.describe('child suite', function() {
|
||||
suiteBuilder.it('dupe spec');
|
||||
});
|
||||
});
|
||||
});
|
||||
suiteBuilder.describe('suite b', function() {
|
||||
suiteBuilder.describe('dupe suite', function() {
|
||||
suiteBuilder.it('dupe spec');
|
||||
});
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When forbidDuplicateNames is false', function() {
|
||||
let env;
|
||||
|
||||
beforeEach(function() {
|
||||
env = { configuration: () => ({ forbidDuplicateNames: false }) };
|
||||
});
|
||||
|
||||
it('allows duplicate spec and suite names', function() {
|
||||
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
|
||||
|
||||
expect(function() {
|
||||
suiteBuilder.describe('dupe suite', function() {
|
||||
suiteBuilder.it('dupe spec');
|
||||
suiteBuilder.it('dupe spec');
|
||||
});
|
||||
suiteBuilder.describe('dupe suite', function() {
|
||||
suiteBuilder.it('dupe spec');
|
||||
suiteBuilder.it('dupe spec');
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#parallelReset', function() {
|
||||
it('resets the top suite result', function() {
|
||||
jasmineUnderTest.Suite.prototype.handleException.and.callThrough();
|
||||
|
||||
@@ -378,4 +378,31 @@ describe('Suite', function() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#hasChildWithDescription', function() {
|
||||
it('returns true if there is a child with the given description', function() {
|
||||
const subject = new jasmineUnderTest.Suite({});
|
||||
const description = 'a spec';
|
||||
subject.addChild({ description });
|
||||
|
||||
expect(subject.hasChildWithDescription(description)).toBeTrue();
|
||||
});
|
||||
|
||||
it('returns false if there is no child with the given description', function() {
|
||||
const subject = new jasmineUnderTest.Suite({});
|
||||
subject.addChild({ description: 'a different spec' });
|
||||
|
||||
expect(subject.hasChildWithDescription('a spec')).toBeFalse();
|
||||
});
|
||||
|
||||
it('does not recurse into child suites', function() {
|
||||
const subject = new jasmineUnderTest.Suite({});
|
||||
const childSuite = new jasmineUnderTest.Suite({});
|
||||
subject.addChild(childSuite);
|
||||
const description = 'a spec';
|
||||
childSuite.addChild(description);
|
||||
|
||||
expect(subject.hasChildWithDescription('a spec')).toBeFalse();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4444,6 +4444,15 @@ describe('Env integration', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('forbids duplicates when forbidDuplicateNames is true', function() {
|
||||
env.configure({ forbidDuplicateNames: true });
|
||||
env.it('a spec');
|
||||
|
||||
expect(function() {
|
||||
env.it('a spec');
|
||||
}).toThrowError('Duplicate spec name "a spec" found in top suite');
|
||||
});
|
||||
|
||||
function browserEventMethods() {
|
||||
return {
|
||||
listeners_: { error: [], unhandledrejection: [] },
|
||||
|
||||
@@ -469,6 +469,24 @@ describe('Matchers (Integration)', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('toBeNullish', function() {
|
||||
verifyPasses(function(env) {
|
||||
env.expect(undefined).toBeNullish();
|
||||
});
|
||||
|
||||
verifyPasses(function(env) {
|
||||
env.expect(null).toBeNullish();
|
||||
});
|
||||
|
||||
verifyFails(function(env) {
|
||||
env.expect(1).toBeNullish();
|
||||
});
|
||||
|
||||
verifyFails(function(env) {
|
||||
env.expect('').toBeNullish();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toContain', function() {
|
||||
verifyPasses(function(env) {
|
||||
env.addCustomEqualityTester(function(a, b) {
|
||||
@@ -621,6 +639,20 @@ describe('Matchers (Integration)', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('toHaveClasses', function() {
|
||||
verifyPasses(function(env) {
|
||||
const el = specHelpers.domHelpers.createElementWithClassName(
|
||||
'foo bar baz'
|
||||
);
|
||||
env.expect(el).toHaveClasses(['bar', 'baz']);
|
||||
});
|
||||
|
||||
verifyFails(function(env) {
|
||||
const el = specHelpers.domHelpers.createElementWithClassName('foo bar');
|
||||
env.expect(el).toHaveClasses(['bar', 'baz']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toHaveSpyInteractions', function() {
|
||||
let spyObj;
|
||||
beforeEach(function() {
|
||||
@@ -643,6 +675,23 @@ describe('Matchers (Integration)', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('toHaveNoOtherSpyInteractions', function() {
|
||||
let spyObj;
|
||||
|
||||
beforeEach(function() {
|
||||
spyObj = env.createSpyObj('NewClass', ['spyA', 'spyB']);
|
||||
});
|
||||
|
||||
verifyPasses(function(env) {
|
||||
env.expect(spyObj).toHaveNoOtherSpyInteractions();
|
||||
});
|
||||
|
||||
verifyFails(function(env) {
|
||||
spyObj.spyA();
|
||||
env.expect(spyObj).toHaveNoOtherSpyInteractions();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toMatch', function() {
|
||||
verifyPasses(function(env) {
|
||||
env.expect('foo').toMatch(/oo$/);
|
||||
|
||||
@@ -38,6 +38,8 @@ describe('toBePending', function() {
|
||||
return matcher.compare(actual);
|
||||
}
|
||||
|
||||
expect(f).toThrowError('Expected toBePending to be called on a promise.');
|
||||
expect(f).toThrowError(
|
||||
'Expected toBePending to be called on a promise but was on a string.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,6 +28,8 @@ describe('toBeRejected', function() {
|
||||
return matcher.compare(actual);
|
||||
}
|
||||
|
||||
expect(f).toThrowError('Expected toBeRejected to be called on a promise.');
|
||||
expect(f).toThrowError(
|
||||
'Expected toBeRejected to be called on a promise but was on a string.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -232,7 +232,7 @@ describe('#toBeRejectedWithError', function() {
|
||||
}
|
||||
|
||||
expect(f).toThrowError(
|
||||
'Expected toBeRejectedWithError to be called on a promise.'
|
||||
'Expected toBeRejectedWithError to be called on a promise but was on a string.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,7 +83,7 @@ describe('#toBeRejectedWith', function() {
|
||||
}
|
||||
|
||||
expect(f).toThrowError(
|
||||
'Expected toBeRejectedWith to be called on a promise.'
|
||||
'Expected toBeRejectedWith to be called on a promise but was on a string.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,6 +35,8 @@ describe('toBeResolved', function() {
|
||||
return matcher.compare(actual);
|
||||
}
|
||||
|
||||
expect(f).toThrowError('Expected toBeResolved to be called on a promise.');
|
||||
expect(f).toThrowError(
|
||||
'Expected toBeResolved to be called on a promise but was on a string.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -93,7 +93,7 @@ describe('#toBeResolvedTo', function() {
|
||||
}
|
||||
|
||||
expect(f).toThrowError(
|
||||
'Expected toBeResolvedTo to be called on a promise.'
|
||||
'Expected toBeResolvedTo to be called on a promise but was on a string.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -757,7 +757,9 @@ describe('matchersUtil', function() {
|
||||
const a2 = new TypedArrayCtor(2);
|
||||
a1[0] = a2[0] = 0;
|
||||
a1[1] = a2[1] = 1;
|
||||
expect(matchersUtil.equals(a1, a2)).toBe(true);
|
||||
const diffBuilder = new jasmineUnderTest.DiffBuilder();
|
||||
expect(matchersUtil.equals(a1, a2, diffBuilder)).toBe(true);
|
||||
jasmine.debugLog('Diff: ' + diffBuilder.getMessage());
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
57
spec/core/matchers/toBeNullishSpec.js
Normal file
57
spec/core/matchers/toBeNullishSpec.js
Normal file
@@ -0,0 +1,57 @@
|
||||
describe('toBeNullish', function() {
|
||||
it('passes for null values', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toBeNullish();
|
||||
const result = matcher.compare(null);
|
||||
expect(result.pass).toBe(true);
|
||||
});
|
||||
|
||||
it('passes for undefined values', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toBeNullish();
|
||||
const result = matcher.compare(void 0);
|
||||
expect(result.pass).toBe(true);
|
||||
});
|
||||
|
||||
it('fails when matching defined values', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toBeNullish();
|
||||
const result = matcher.compare('foo');
|
||||
expect(result.pass).toBe(false);
|
||||
});
|
||||
|
||||
describe('falsy values', () => {
|
||||
it('fails for 0', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toBeNullish();
|
||||
const result = matcher.compare(0);
|
||||
expect(result.pass).toBe(false);
|
||||
});
|
||||
|
||||
it('fails for -0', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toBeNullish();
|
||||
const result = matcher.compare(-0);
|
||||
expect(result.pass).toBe(false);
|
||||
});
|
||||
|
||||
it('fails for empty string', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toBeNullish();
|
||||
const result = matcher.compare('');
|
||||
expect(result.pass).toBe(false);
|
||||
});
|
||||
|
||||
it('fails for false', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toBeNullish();
|
||||
const result = matcher.compare(false);
|
||||
expect(result.pass).toBe(false);
|
||||
});
|
||||
|
||||
it('fails for NaN', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toBeNullish();
|
||||
const result = matcher.compare(NaN);
|
||||
expect(result.pass).toBe(false);
|
||||
});
|
||||
|
||||
it('fails for 0n', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toBeNullish();
|
||||
const result = matcher.compare(BigInt(0));
|
||||
expect(result.pass).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -96,6 +96,17 @@ describe('toEqual', function() {
|
||||
expect(compareEquals(actual, expected).message).toEqual(message);
|
||||
});
|
||||
|
||||
it('reports mismatches as well as missing or extra properties', function() {
|
||||
const actual = { x: { z: 2 } },
|
||||
expected = { x: { y: 1, z: 3 } },
|
||||
message =
|
||||
'Expected $.x to have properties\n' +
|
||||
' y: 1\n' +
|
||||
'Expected $.x.z = 2 to equal 3.';
|
||||
|
||||
expect(compareEquals(actual, expected).message).toEqual(message);
|
||||
});
|
||||
|
||||
it('reports missing symbol properties', function() {
|
||||
const actual = { x: {} },
|
||||
expected = { x: { [Symbol('y')]: 1 } },
|
||||
|
||||
@@ -112,4 +112,20 @@ describe('toHaveBeenCalledBefore', function() {
|
||||
'Expected spy first spy to not have been called before spy second spy, but it was'
|
||||
);
|
||||
});
|
||||
|
||||
it('set the correct calls as verified when passing', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(),
|
||||
firstSpy = new jasmineUnderTest.Spy('first spy'),
|
||||
secondSpy = new jasmineUnderTest.Spy('second spy');
|
||||
|
||||
firstSpy();
|
||||
secondSpy();
|
||||
|
||||
matcher.compare(firstSpy, secondSpy);
|
||||
|
||||
expect(firstSpy.calls.count()).toBe(1);
|
||||
expect(firstSpy.calls.unverifiedCount()).toBe(0);
|
||||
expect(secondSpy.calls.count()).toBe(1);
|
||||
expect(secondSpy.calls.unverifiedCount()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -105,4 +105,18 @@ describe('toHaveBeenCalledOnceWith', function() {
|
||||
matcher.compare(fn);
|
||||
}).toThrowError(/Expected a spy, but got Function./);
|
||||
});
|
||||
|
||||
it('set the correct calls as verified when passing', function() {
|
||||
const pp = jasmineUnderTest.makePrettyPrinter(),
|
||||
util = new jasmineUnderTest.MatchersUtil({ pp: pp }),
|
||||
matcher = jasmineUnderTest.matchers.toHaveBeenCalledOnceWith(util),
|
||||
calledSpy = new jasmineUnderTest.Spy('called-spy');
|
||||
|
||||
calledSpy('x');
|
||||
|
||||
matcher.compare(calledSpy, 'x');
|
||||
|
||||
expect(calledSpy.calls.count()).toBe(1);
|
||||
expect(calledSpy.calls.unverifiedCount()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -50,4 +50,16 @@ describe('toHaveBeenCalled', function() {
|
||||
'Expected spy sample-spy to have been called.'
|
||||
);
|
||||
});
|
||||
|
||||
it('set the correct calls as verified when passing', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toHaveBeenCalled(),
|
||||
spy = new jasmineUnderTest.Spy('sample-spy');
|
||||
|
||||
spy();
|
||||
|
||||
matcher.compare(spy);
|
||||
|
||||
expect(spy.calls.count()).toBe(1);
|
||||
expect(spy.calls.unverifiedCount()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,4 +87,17 @@ describe('toHaveBeenCalledTimes', function() {
|
||||
' times.'
|
||||
);
|
||||
});
|
||||
|
||||
it('set the correct calls as verified when passing', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(),
|
||||
spy = new jasmineUnderTest.Spy('sample-spy');
|
||||
|
||||
spy();
|
||||
spy();
|
||||
|
||||
matcher.compare(spy, 2);
|
||||
|
||||
expect(spy.calls.count()).toBe(2);
|
||||
expect(spy.calls.unverifiedCount()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ describe('toHaveBeenCalledWith', function() {
|
||||
it('passes when the actual was called with matching parameters', function() {
|
||||
const matchersUtil = {
|
||||
contains: jasmine.createSpy('delegated-contains').and.returnValue(true),
|
||||
equals: jasmine.createSpy('delegated-equals').and.returnValue(true),
|
||||
pp: jasmineUnderTest.makePrettyPrinter()
|
||||
},
|
||||
matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(matchersUtil),
|
||||
@@ -92,4 +93,20 @@ describe('toHaveBeenCalledWith', function() {
|
||||
matcher.compare(fn);
|
||||
}).toThrowError(/Expected a spy, but got Function./);
|
||||
});
|
||||
|
||||
it('set the correct calls as verified when passing', function() {
|
||||
const matchersUtil = {
|
||||
contains: jasmine.createSpy('delegated-contains').and.returnValue(true),
|
||||
equals: jasmine.createSpy('delegated-equals').and.returnValue(true),
|
||||
pp: jasmineUnderTest.makePrettyPrinter()
|
||||
},
|
||||
matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(matchersUtil),
|
||||
calledSpy = new jasmineUnderTest.Spy('called-spy');
|
||||
|
||||
calledSpy('a', 'b');
|
||||
matcher.compare(calledSpy, 'a', 'b');
|
||||
|
||||
expect(calledSpy.calls.count()).toBe(1);
|
||||
expect(calledSpy.calls.unverifiedCount()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
48
spec/core/matchers/toHaveClassesSpec.js
Normal file
48
spec/core/matchers/toHaveClassesSpec.js
Normal file
@@ -0,0 +1,48 @@
|
||||
describe('toHaveClasses', function() {
|
||||
it('fails for a DOM element that lacks all the expected classes', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toHaveClasses(),
|
||||
result = matcher.compare(
|
||||
specHelpers.domHelpers.createElementWithClassName(''),
|
||||
['foo', 'bar']
|
||||
);
|
||||
|
||||
expect(result.pass).toBe(false);
|
||||
});
|
||||
|
||||
it('passes for a DOM element that has all the expected classes', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toHaveClasses(),
|
||||
el = specHelpers.domHelpers.createElementWithClassName('foo bar baz');
|
||||
|
||||
expect(matcher.compare(el, ['foo', 'bar']).pass).toBe(true);
|
||||
});
|
||||
|
||||
it('fails for a DOM element that only has some matching classes', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toHaveClasses(),
|
||||
el = specHelpers.domHelpers.createElementWithClassName('foo bar');
|
||||
|
||||
expect(matcher.compare(el, ['foo', 'can']).pass).toBe(false);
|
||||
});
|
||||
|
||||
it('throws an exception when actual is not a DOM element', function() {
|
||||
const matcher = jasmineUnderTest.matchers.toHaveClasses({
|
||||
pp: jasmineUnderTest.makePrettyPrinter()
|
||||
});
|
||||
|
||||
expect(function() {
|
||||
matcher.compare('x', ['foo']);
|
||||
}).toThrowError("'x' is not a DOM element");
|
||||
|
||||
expect(function() {
|
||||
matcher.compare(undefined, ['foo']);
|
||||
}).toThrowError('undefined is not a DOM element');
|
||||
|
||||
const textNode = specHelpers.domHelpers.document.createTextNode('');
|
||||
expect(function() {
|
||||
matcher.compare(textNode, ['foo']);
|
||||
}).toThrowError('HTMLNode is not a DOM element');
|
||||
|
||||
expect(function() {
|
||||
matcher.compare({ classList: '' }, ['foo']);
|
||||
}).toThrowError("Object({ classList: '' }) is not a DOM element");
|
||||
});
|
||||
});
|
||||
154
spec/core/matchers/toHaveNoOtherSpyInteractionsSpec.js
Normal file
154
spec/core/matchers/toHaveNoOtherSpyInteractionsSpec.js
Normal file
@@ -0,0 +1,154 @@
|
||||
describe('toHaveNoOtherSpyInteractions', function() {
|
||||
it('passes when there are no spy interactions', function() {
|
||||
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
|
||||
let spyObj = jasmineUnderTest
|
||||
.getEnv()
|
||||
.createSpyObj('NewClass', ['spyA', 'spyB']);
|
||||
|
||||
let result = matcher.compare(spyObj);
|
||||
expect(result.pass).toBeTrue();
|
||||
});
|
||||
|
||||
it('passes when there are multiple spy interactions where checked by toHaveBeenCalled', function() {
|
||||
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
|
||||
let toHaveBeenCalledMatcher = jasmineUnderTest.matchers.toHaveBeenCalled();
|
||||
let spyObj = jasmineUnderTest
|
||||
.getEnv()
|
||||
.createSpyObj('NewClass', ['spyA', 'spyB']);
|
||||
|
||||
spyObj.spyA();
|
||||
spyObj.spyB();
|
||||
spyObj.spyA();
|
||||
toHaveBeenCalledMatcher.compare(spyObj.spyA);
|
||||
toHaveBeenCalledMatcher.compare(spyObj.spyB);
|
||||
let result = matcher.compare(spyObj);
|
||||
expect(result.pass).toBeTrue();
|
||||
});
|
||||
|
||||
it('fails when there are spy interactions', function() {
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil({
|
||||
pp: jasmineUnderTest.makePrettyPrinter()
|
||||
});
|
||||
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions(
|
||||
matchersUtil
|
||||
);
|
||||
let spyObj = jasmineUnderTest
|
||||
.getEnv()
|
||||
.createSpyObj('NewClass', ['spyA', 'spyB']);
|
||||
|
||||
spyObj.spyA('x');
|
||||
|
||||
let result = matcher.compare(spyObj);
|
||||
expect(result.pass).toBeFalse();
|
||||
expect(result.message).toEqual(
|
||||
'Expected a spy object to have no other spy interactions, but it had the following calls:\n' +
|
||||
" NewClass.spyA called with [ 'x' ]."
|
||||
);
|
||||
});
|
||||
|
||||
it('shows the right message is negated', function() {
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil({
|
||||
pp: jasmineUnderTest.makePrettyPrinter()
|
||||
});
|
||||
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions(
|
||||
matchersUtil
|
||||
);
|
||||
let spyObj = jasmineUnderTest
|
||||
.getEnv()
|
||||
.createSpyObj('NewClass', ['spyA', 'spyB']);
|
||||
|
||||
spyObj.spyA();
|
||||
spyObj.spyB();
|
||||
|
||||
let result = matcher.compare(spyObj);
|
||||
expect(result.pass).toBeFalse();
|
||||
expect(result.message).toEqual(
|
||||
'Expected a spy object to have no other spy interactions, but it had the following calls:\n' +
|
||||
' NewClass.spyA called with [ ],\n' +
|
||||
' NewClass.spyB called with [ ].'
|
||||
);
|
||||
});
|
||||
|
||||
it('passes when only non-observed spy object interactions are interacted', function() {
|
||||
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
|
||||
let spyObj = jasmineUnderTest
|
||||
.getEnv()
|
||||
.createSpyObj('NewClass', ['spyA', 'spyB']);
|
||||
spyObj.otherMethod = function() {};
|
||||
|
||||
spyObj.otherMethod();
|
||||
|
||||
let result = matcher.compare(spyObj);
|
||||
expect(result.pass).toBeTrue();
|
||||
expect(result.message).toEqual(
|
||||
"Expected a spy object to have other spy interactions but it didn't."
|
||||
);
|
||||
});
|
||||
|
||||
it(`throws an error if a non-object is passed`, function() {
|
||||
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
|
||||
|
||||
expect(function() {
|
||||
matcher.compare(true);
|
||||
}).toThrowError(Error, /Expected an object, but got/);
|
||||
|
||||
expect(function() {
|
||||
matcher.compare(123);
|
||||
}).toThrowError(Error, /Expected an object, but got/);
|
||||
|
||||
expect(function() {
|
||||
matcher.compare('string');
|
||||
}).toThrowError(Error, /Expected an object, but got/);
|
||||
});
|
||||
|
||||
it('throws an error if arguments are passed', function() {
|
||||
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
|
||||
let spyObj = jasmineUnderTest
|
||||
.getEnv()
|
||||
.createSpyObj('mySpyObj', ['spyA', 'spyB']);
|
||||
|
||||
expect(function() {
|
||||
matcher.compare(spyObj, 'an argument');
|
||||
}).toThrowError(Error, /Does not take arguments/);
|
||||
});
|
||||
|
||||
it('throws an error if the spy object has no spies', function() {
|
||||
let matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions();
|
||||
const spyObj = jasmineUnderTest
|
||||
.getEnv()
|
||||
.createSpyObj('mySpyObj', ['notSpy']);
|
||||
// Removing spy since spy objects cannot be created without spies.
|
||||
spyObj.notSpy = function() {};
|
||||
|
||||
expect(function() {
|
||||
matcher.compare(spyObj);
|
||||
}).toThrowError(
|
||||
Error,
|
||||
/Expected an object with spies, but object has no spies/
|
||||
);
|
||||
});
|
||||
|
||||
it('handles multiple interactions with a single spy', function() {
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil({
|
||||
pp: jasmineUnderTest.makePrettyPrinter()
|
||||
}),
|
||||
matcher = jasmineUnderTest.matchers.toHaveNoOtherSpyInteractions(
|
||||
matchersUtil
|
||||
),
|
||||
toHaveBeenCalledWithMatcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(
|
||||
matchersUtil
|
||||
),
|
||||
spyObj = jasmineUnderTest
|
||||
.getEnv()
|
||||
.createSpyObj('NewClass', ['spyA', 'spyB']);
|
||||
|
||||
spyObj.spyA('x');
|
||||
spyObj.spyA('y');
|
||||
|
||||
toHaveBeenCalledWithMatcher.compare(spyObj.spyA, 'x');
|
||||
|
||||
let result = matcher.compare(spyObj);
|
||||
|
||||
expect(result.pass).toBeFalse();
|
||||
});
|
||||
});
|
||||
@@ -125,6 +125,10 @@ getJasmineRequireObj().CallTracker = function(j$) {
|
||||
this.saveArgumentsByValue = function() {
|
||||
opts.cloneArgs = true;
|
||||
};
|
||||
|
||||
this.unverifiedCount = function() {
|
||||
return calls.reduce((count, call) => count + (call.verified ? 0 : 1), 0);
|
||||
};
|
||||
}
|
||||
|
||||
return CallTracker;
|
||||
|
||||
@@ -138,6 +138,15 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
* @default true
|
||||
*/
|
||||
autoCleanClosures: true,
|
||||
/**
|
||||
* Whether to forbid duplicate spec or suite names. If set to true, using
|
||||
* the same name multiple times in the same immediate parent suite is an
|
||||
* error.
|
||||
* @name Configuration#forbidDuplicateNames
|
||||
* @type boolean
|
||||
* @default false
|
||||
*/
|
||||
forbidDuplicateNames: false,
|
||||
/**
|
||||
* Whether or not to issue warnings for certain deprecated functionality
|
||||
* every time it's used. If not set or set to false, deprecation warnings
|
||||
@@ -186,7 +195,8 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
'hideDisabled',
|
||||
'stopOnSpecFailure',
|
||||
'stopSpecOnExpectationFailure',
|
||||
'autoCleanClosures'
|
||||
'autoCleanClosures',
|
||||
'forbidDuplicateNames'
|
||||
];
|
||||
|
||||
booleanProps.forEach(function(prop) {
|
||||
|
||||
@@ -26,7 +26,8 @@ getJasmineRequireObj().Spy = function(j$) {
|
||||
const callData = {
|
||||
object: context,
|
||||
invocationOrder: nextOrder(),
|
||||
args: Array.prototype.slice.apply(args)
|
||||
args: Array.prototype.slice.apply(args),
|
||||
verified: false
|
||||
};
|
||||
|
||||
callTracker.track(callData);
|
||||
|
||||
@@ -251,6 +251,16 @@ getJasmineRequireObj().Suite = function(j$) {
|
||||
);
|
||||
};
|
||||
|
||||
Suite.prototype.hasChildWithDescription = function(description) {
|
||||
for (const child of this.children) {
|
||||
if (child.description === description) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
Object.defineProperty(Suite.prototype, 'metadata', {
|
||||
get: function() {
|
||||
if (!this.metadata_) {
|
||||
|
||||
@@ -161,6 +161,8 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
|
||||
j$.util.validateTimeout(timeout);
|
||||
}
|
||||
|
||||
this.checkDuplicate_(description, 'spec');
|
||||
|
||||
const spec = this.specFactory_(description, fn, timeout, filename);
|
||||
if (this.currentDeclarationSuite_.markedExcluding) {
|
||||
spec.exclude();
|
||||
@@ -170,7 +172,27 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
|
||||
return spec;
|
||||
}
|
||||
|
||||
checkDuplicate_(description, type) {
|
||||
if (!this.env_.configuration().forbidDuplicateNames) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.currentDeclarationSuite_.hasChildWithDescription(description)) {
|
||||
const parentDesc =
|
||||
this.currentDeclarationSuite_ === this.topSuite
|
||||
? 'top suite'
|
||||
: `"${this.currentDeclarationSuite_.getFullName()}"`;
|
||||
throw new Error(
|
||||
`Duplicate ${type} name "${description}" found in ${parentDesc}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
suiteFactory_(description, filename) {
|
||||
if (this.topSuite) {
|
||||
this.checkDuplicate_(description, 'suite');
|
||||
}
|
||||
|
||||
const config = this.env_.configuration();
|
||||
const parentSuite = this.currentDeclarationSuite_;
|
||||
const reportedParentSuiteId =
|
||||
|
||||
@@ -12,7 +12,9 @@ getJasmineRequireObj().toBePending = function(j$) {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
if (!j$.isPromiseLike(actual)) {
|
||||
throw new Error('Expected toBePending to be called on a promise.');
|
||||
throw new Error(
|
||||
`Expected toBePending to be called on a promise but was on a ${typeof actual}.`
|
||||
);
|
||||
}
|
||||
const want = {};
|
||||
return Promise.race([actual, Promise.resolve(want)]).then(
|
||||
|
||||
@@ -14,7 +14,9 @@ getJasmineRequireObj().toBeRejected = function(j$) {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
if (!j$.isPromiseLike(actual)) {
|
||||
throw new Error('Expected toBeRejected to be called on a promise.');
|
||||
throw new Error(
|
||||
`Expected toBeRejected to be called on a promise but was on a ${typeof actual}.`
|
||||
);
|
||||
}
|
||||
return actual.then(
|
||||
function() {
|
||||
|
||||
@@ -16,7 +16,7 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) {
|
||||
compare: function(actualPromise, expectedValue) {
|
||||
if (!j$.isPromiseLike(actualPromise)) {
|
||||
throw new Error(
|
||||
'Expected toBeRejectedWith to be called on a promise.'
|
||||
`Expected toBeRejectedWith to be called on a promise but was on a ${typeof actualPromise}.`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) {
|
||||
compare: function(actualPromise, arg1, arg2) {
|
||||
if (!j$.isPromiseLike(actualPromise)) {
|
||||
throw new Error(
|
||||
'Expected toBeRejectedWithError to be called on a promise.'
|
||||
`Expected toBeRejectedWithError to be called on a promise but was on a ${typeof actualPromise}.`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,9 @@ getJasmineRequireObj().toBeResolved = function(j$) {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
if (!j$.isPromiseLike(actual)) {
|
||||
throw new Error('Expected toBeResolved to be called on a promise.');
|
||||
throw new Error(
|
||||
`Expected toBeResolved to be called on a promise but was on a ${typeof actual}.`
|
||||
);
|
||||
}
|
||||
|
||||
return actual.then(
|
||||
|
||||
@@ -15,7 +15,9 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) {
|
||||
return {
|
||||
compare: function(actualPromise, expectedValue) {
|
||||
if (!j$.isPromiseLike(actualPromise)) {
|
||||
throw new Error('Expected toBeResolvedTo to be called on a promise.');
|
||||
throw new Error(
|
||||
`Expected toBeResolvedTo to be called on a promise but was on a ${typeof actualPromise}.`
|
||||
);
|
||||
}
|
||||
|
||||
function prefix(passed) {
|
||||
|
||||
@@ -483,7 +483,7 @@ getJasmineRequireObj().MatchersUtil = function(j$) {
|
||||
diffBuilder.recordMismatch(
|
||||
objectKeysAreDifferentFormatter.bind(null, this.pp)
|
||||
);
|
||||
return false;
|
||||
result = false;
|
||||
}
|
||||
|
||||
for (const key of aKeys) {
|
||||
|
||||
@@ -18,6 +18,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
|
||||
'toBeTrue',
|
||||
'toBeTruthy',
|
||||
'toBeUndefined',
|
||||
'toBeNullish',
|
||||
'toContain',
|
||||
'toEqual',
|
||||
'toHaveSize',
|
||||
@@ -27,7 +28,9 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
|
||||
'toHaveBeenCalledTimes',
|
||||
'toHaveBeenCalledWith',
|
||||
'toHaveClass',
|
||||
'toHaveClasses',
|
||||
'toHaveSpyInteractions',
|
||||
'toHaveNoOtherSpyInteractions',
|
||||
'toMatch',
|
||||
'toThrow',
|
||||
'toThrowError',
|
||||
|
||||
21
src/core/matchers/toBeNullish.js
Normal file
21
src/core/matchers/toBeNullish.js
Normal file
@@ -0,0 +1,21 @@
|
||||
getJasmineRequireObj().toBeNullish = function() {
|
||||
/**
|
||||
* {@link expect} the actual value to be `null` or `undefined`.
|
||||
* @function
|
||||
* @name matchers#toBeNullish
|
||||
* @since 5.6.0
|
||||
* @example
|
||||
* expect(result).toBeNullish():
|
||||
*/
|
||||
function toBeNullish() {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
return {
|
||||
pass: null === actual || void 0 === actual
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return toBeNullish;
|
||||
};
|
||||
@@ -34,6 +34,8 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) {
|
||||
|
||||
result.pass = actual.calls.any();
|
||||
|
||||
actual.calls.all().forEach(call => (call.verified = true));
|
||||
|
||||
result.message = result.pass
|
||||
? 'Expected spy ' + actual.and.identity + ' not to have been called.'
|
||||
: 'Expected spy ' + actual.and.identity + ' to have been called.';
|
||||
|
||||
@@ -50,6 +50,9 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) {
|
||||
result.pass = latest1stSpyCall < first2ndSpyCall;
|
||||
|
||||
if (result.pass) {
|
||||
firstSpy.calls.mostRecent().verified = true;
|
||||
latterSpy.calls.first().verified = true;
|
||||
|
||||
result.message =
|
||||
'Expected spy ' +
|
||||
firstSpy.and.identity +
|
||||
|
||||
@@ -13,7 +13,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
|
||||
* @example
|
||||
* expect(mySpy).toHaveBeenCalledOnceWith('foo', 'bar', 2);
|
||||
*/
|
||||
function toHaveBeenCalledOnceWith(util) {
|
||||
function toHaveBeenCalledOnceWith(matchersUtil) {
|
||||
return {
|
||||
compare: function() {
|
||||
const args = Array.prototype.slice.call(arguments, 0),
|
||||
@@ -22,20 +22,29 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
|
||||
|
||||
if (!j$.isSpy(actual)) {
|
||||
throw new Error(
|
||||
getErrorMsg('Expected a spy, but got ' + util.pp(actual) + '.')
|
||||
getErrorMsg(
|
||||
'Expected a spy, but got ' + matchersUtil.pp(actual) + '.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const prettyPrintedCalls = actual.calls
|
||||
.allArgs()
|
||||
.map(function(argsForCall) {
|
||||
return ' ' + util.pp(argsForCall);
|
||||
return ' ' + matchersUtil.pp(argsForCall);
|
||||
});
|
||||
|
||||
if (
|
||||
actual.calls.count() === 1 &&
|
||||
util.contains(actual.calls.allArgs(), expectedArgs)
|
||||
matchersUtil.contains(actual.calls.allArgs(), expectedArgs)
|
||||
) {
|
||||
const firstIndex = actual.calls
|
||||
.all()
|
||||
.findIndex(call => matchersUtil.equals(call.args, expectedArgs));
|
||||
if (firstIndex > -1) {
|
||||
actual.calls.all()[firstIndex].verified = true;
|
||||
}
|
||||
|
||||
return {
|
||||
pass: true,
|
||||
message:
|
||||
@@ -43,7 +52,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
|
||||
actual.and.identity +
|
||||
' to have been called 0 times, multiple times, or once, but with arguments different from:\n' +
|
||||
' ' +
|
||||
util.pp(expectedArgs) +
|
||||
matchersUtil.pp(expectedArgs) +
|
||||
'\n' +
|
||||
'But the actual call was:\n' +
|
||||
prettyPrintedCalls.join(',\n') +
|
||||
@@ -54,7 +63,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
|
||||
function getDiffs() {
|
||||
return actual.calls.allArgs().map(function(argsForCall, callIx) {
|
||||
const diffBuilder = new j$.DiffBuilder();
|
||||
util.equals(argsForCall, expectedArgs, diffBuilder);
|
||||
matchersUtil.equals(argsForCall, expectedArgs, diffBuilder);
|
||||
return diffBuilder.getMessage();
|
||||
});
|
||||
}
|
||||
@@ -87,7 +96,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) {
|
||||
actual.and.identity +
|
||||
' to have been called only once, and with given args:\n' +
|
||||
' ' +
|
||||
util.pp(expectedArgs) +
|
||||
matchersUtil.pp(expectedArgs) +
|
||||
'\n' +
|
||||
butString()
|
||||
};
|
||||
|
||||
@@ -36,23 +36,35 @@ getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) {
|
||||
}
|
||||
|
||||
actual = args[0];
|
||||
const calls = actual.calls.count();
|
||||
|
||||
const callsCount = actual.calls.count();
|
||||
const timesMessage = expected === 1 ? 'once' : expected + ' times';
|
||||
result.pass = calls === expected;
|
||||
|
||||
result.pass = callsCount === expected;
|
||||
|
||||
if (result.pass) {
|
||||
const allCalls = actual.calls.all();
|
||||
const max = Math.min(expected, callsCount);
|
||||
|
||||
for (let i = 0; i < max; i++) {
|
||||
allCalls[i].verified = true;
|
||||
}
|
||||
}
|
||||
|
||||
result.message = result.pass
|
||||
? 'Expected spy ' +
|
||||
actual.and.identity +
|
||||
' not to have been called ' +
|
||||
timesMessage +
|
||||
'. It was called ' +
|
||||
calls +
|
||||
callsCount +
|
||||
' times.'
|
||||
: 'Expected spy ' +
|
||||
actual.and.identity +
|
||||
' to have been called ' +
|
||||
timesMessage +
|
||||
'. It was called ' +
|
||||
calls +
|
||||
callsCount +
|
||||
' times.';
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,11 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
|
||||
}
|
||||
|
||||
if (matchersUtil.contains(actual.calls.allArgs(), expectedArgs)) {
|
||||
actual.calls
|
||||
.all()
|
||||
.filter(call => matchersUtil.equals(call.args, expectedArgs))
|
||||
.forEach(call => (call.verified = true));
|
||||
|
||||
result.pass = true;
|
||||
result.message = function() {
|
||||
return (
|
||||
|
||||
34
src/core/matchers/toHaveClasses.js
Normal file
34
src/core/matchers/toHaveClasses.js
Normal file
@@ -0,0 +1,34 @@
|
||||
getJasmineRequireObj().toHaveClasses = function(j$) {
|
||||
/**
|
||||
* {@link expect} the actual value to be a DOM element that has the expected classes
|
||||
* @function
|
||||
* @name matchers#toHaveClasses
|
||||
* @since 5.6.0
|
||||
* @param {Object} expected - The class names to test for
|
||||
* @example
|
||||
* const el = document.createElement('div');
|
||||
* el.className = 'foo bar baz';
|
||||
* expect(el).toHaveClasses(['bar', 'baz']);
|
||||
*/
|
||||
function toHaveClasses(matchersUtil) {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
if (!isElement(actual)) {
|
||||
throw new Error(matchersUtil.pp(actual) + ' is not a DOM element');
|
||||
}
|
||||
|
||||
return {
|
||||
pass: expected.every(e => actual.classList.contains(e))
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function isElement(maybeEl) {
|
||||
return (
|
||||
maybeEl && maybeEl.classList && j$.isFunction_(maybeEl.classList.contains)
|
||||
);
|
||||
}
|
||||
|
||||
return toHaveClasses;
|
||||
};
|
||||
85
src/core/matchers/toHaveNoOtherSpyInteractions.js
Normal file
85
src/core/matchers/toHaveNoOtherSpyInteractions.js
Normal file
@@ -0,0 +1,85 @@
|
||||
getJasmineRequireObj().toHaveNoOtherSpyInteractions = function(j$) {
|
||||
const getErrorMsg = j$.formatErrorMsg(
|
||||
'<toHaveNoOtherSpyInteractions>',
|
||||
'expect(<spyObj>).toHaveNoOtherSpyInteractions()'
|
||||
);
|
||||
|
||||
/**
|
||||
* {@link expect} the actual (a {@link SpyObj}) spies to have not been called except interactions which was already tracked with `toHaveBeenCalled`.
|
||||
* @function
|
||||
* @name matchers#toHaveNoOtherSpyInteractions
|
||||
* @example
|
||||
* expect(mySpyObj).toHaveNoOtherSpyInteractions();
|
||||
* expect(mySpyObj).not.toHaveNoOtherSpyInteractions();
|
||||
*/
|
||||
function toHaveNoOtherSpyInteractions(matchersUtil) {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
const result = {};
|
||||
|
||||
if (!j$.isObject_(actual)) {
|
||||
throw new Error(
|
||||
getErrorMsg('Expected an object, but got ' + typeof actual + '.')
|
||||
);
|
||||
}
|
||||
|
||||
if (arguments.length > 1) {
|
||||
throw new Error(getErrorMsg('Does not take arguments'));
|
||||
}
|
||||
|
||||
result.pass = true;
|
||||
let hasSpy = false;
|
||||
const unexpectedCalls = [];
|
||||
|
||||
for (const spy of Object.values(actual)) {
|
||||
if (!j$.isSpy(spy)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hasSpy = true;
|
||||
|
||||
const unverifiedCalls = spy.calls
|
||||
.all()
|
||||
.filter(call => !call.verified);
|
||||
|
||||
if (unverifiedCalls.length > 0) {
|
||||
result.pass = false;
|
||||
}
|
||||
|
||||
unverifiedCalls.forEach(unverifiedCall => {
|
||||
unexpectedCalls.push([
|
||||
spy.and.identity,
|
||||
matchersUtil.pp(unverifiedCall.args)
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasSpy) {
|
||||
throw new Error(
|
||||
getErrorMsg(
|
||||
'Expected an object with spies, but object has no spies.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (result.pass) {
|
||||
result.message =
|
||||
"Expected a spy object to have other spy interactions but it didn't.";
|
||||
} else {
|
||||
const ppUnexpectedCalls = unexpectedCalls
|
||||
.map(([spyName, args]) => ` ${spyName} called with ${args}`)
|
||||
.join(',\n');
|
||||
|
||||
result.message =
|
||||
'Expected a spy object to have no other spy interactions, but it had the following calls:\n' +
|
||||
ppUnexpectedCalls +
|
||||
'.';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return toHaveNoOtherSpyInteractions;
|
||||
};
|
||||
Reference in New Issue
Block a user