Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0688db88e9 | ||
|
|
124effe04b | ||
|
|
418e9a7728 | ||
|
|
6715f24fd0 | ||
|
|
fa481b2bd1 | ||
|
|
8309416cb2 | ||
|
|
4ccc7bf3ac | ||
|
|
cca6b2aa07 | ||
|
|
78940aa0fb | ||
|
|
3d8396da0a | ||
|
|
0bf9aff195 | ||
|
|
55b2e8846f | ||
|
|
3493519c9f | ||
|
|
62b5698a99 | ||
|
|
98849882a2 | ||
|
|
6665c4e123 | ||
|
|
3698f6fb5d | ||
|
|
60f34ec087 | ||
|
|
91bd3f8201 | ||
|
|
ca4fbcbccb | ||
|
|
e1532be726 | ||
|
|
54465f6f6a | ||
|
|
fa9939ae94 |
@@ -30,7 +30,7 @@ Microsoft Edge) as well as Node.
|
||||
| Environment | Supported versions |
|
||||
|-------------------|----------------------------------|
|
||||
| Node | 18.20.5+*, 20, 22, 24 |
|
||||
| Safari | 15*, 16*, 17* |
|
||||
| Safari | 16*, 17* |
|
||||
| Chrome | Evergreen |
|
||||
| Firefox | Evergreen, 102*, 115*, 128*, 140 |
|
||||
| Edge | Evergreen |
|
||||
|
||||
@@ -38,20 +38,12 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
(function() {
|
||||
const env = jasmine.getEnv();
|
||||
|
||||
/**
|
||||
* ## Runner Parameters
|
||||
*
|
||||
* More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
|
||||
*/
|
||||
|
||||
const queryString = new jasmine.QueryString({
|
||||
getWindowLocation: function() {
|
||||
return window.location;
|
||||
}
|
||||
});
|
||||
|
||||
const filterSpecs = !!queryString.getParam('spec');
|
||||
|
||||
const config = {
|
||||
stopOnSpecFailure: queryString.getParam('stopOnSpecFailure'),
|
||||
stopSpecOnExpectationFailure: queryString.getParam(
|
||||
@@ -93,7 +85,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
return document.createTextNode.apply(document, arguments);
|
||||
},
|
||||
timer: new jasmine.Timer(),
|
||||
filterSpecs: filterSpecs
|
||||
queryString
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -105,14 +97,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
/**
|
||||
* Filter which specs will be run by matching the start of the full name against the `spec` query param.
|
||||
*/
|
||||
const specFilter = new jasmine.HtmlSpecFilter({
|
||||
filterString: function() {
|
||||
return queryString.getParam('spec');
|
||||
}
|
||||
});
|
||||
|
||||
const specFilter = new jasmine.HtmlExactSpecFilter({ queryString });
|
||||
config.specFilter = function(spec) {
|
||||
return specFilter.matches(spec.getFullName());
|
||||
return specFilter.matches(spec);
|
||||
};
|
||||
|
||||
env.configure(config);
|
||||
|
||||
@@ -30,6 +30,7 @@ jasmineRequire.html = function(j$) {
|
||||
j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
|
||||
j$.QueryString = jasmineRequire.QueryString();
|
||||
j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
|
||||
j$.HtmlExactSpecFilter = jasmineRequire.HtmlExactSpecFilter();
|
||||
};
|
||||
|
||||
jasmineRequire.HtmlReporter = function(j$) {
|
||||
@@ -39,11 +40,13 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
this.specsExecuted = 0;
|
||||
this.failureCount = 0;
|
||||
this.pendingSpecCount = 0;
|
||||
this.suitesById = [];
|
||||
}
|
||||
|
||||
ResultsStateBuilder.prototype.suiteStarted = function(result) {
|
||||
this.currentParent.addChild(result, 'suite');
|
||||
this.currentParent = this.currentParent.last();
|
||||
this.suitesById[result.id] = this.currentParent;
|
||||
};
|
||||
|
||||
ResultsStateBuilder.prototype.suiteDone = function(result) {
|
||||
@@ -81,6 +84,13 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @class HtmlReporter
|
||||
* @classdesc Displays results and allows re-running individual specs and suites.
|
||||
* @implements {Reporter}
|
||||
* @param options Options object. See lib/jasmine-core/boot1.js for details.
|
||||
* @since 1.2.0
|
||||
*/
|
||||
function HtmlReporter(options) {
|
||||
function config() {
|
||||
return (options.env && options.env.configuration()) || {};
|
||||
@@ -89,15 +99,24 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
const getContainer = options.getContainer;
|
||||
const createElement = options.createElement;
|
||||
const createTextNode = options.createTextNode;
|
||||
// TODO: in the next major release, replace navigateWithNewParam and
|
||||
// addToExistingQueryString with direct usage of options.queryString
|
||||
const navigateWithNewParam = options.navigateWithNewParam || function() {};
|
||||
const addToExistingQueryString =
|
||||
options.addToExistingQueryString || defaultQueryString;
|
||||
const filterSpecs = options.filterSpecs;
|
||||
const filterSpecs = options.queryString
|
||||
? !!options.queryString.getParam('spec')
|
||||
: options.filterSpecs; // For compatibility with pre-5.11 boot files
|
||||
let htmlReporterMain;
|
||||
let symbols;
|
||||
const deprecationWarnings = [];
|
||||
const failures = [];
|
||||
|
||||
/**
|
||||
* Initializes the reporter. Should be called before {@link Env#execute}.
|
||||
* @function
|
||||
* @name HtmlReporter#initialize
|
||||
*/
|
||||
this.initialize = function() {
|
||||
clearPrior();
|
||||
htmlReporterMain = createDom(
|
||||
@@ -716,21 +735,6 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
function suiteHref(suite) {
|
||||
const els = [];
|
||||
|
||||
while (suite && suite.parent) {
|
||||
els.unshift(suite.result.description);
|
||||
suite = suite.parent;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (result && result.deprecationWarnings) {
|
||||
for (let i = 0; i < result.deprecationWarnings.length; i++) {
|
||||
@@ -828,11 +832,33 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
return '' + count + ' ' + word;
|
||||
}
|
||||
|
||||
function suitePath(suite) {
|
||||
const els = [];
|
||||
|
||||
while (suite && suite.parent) {
|
||||
els.unshift(suite.result.description);
|
||||
suite = suite.parent;
|
||||
}
|
||||
|
||||
return els;
|
||||
}
|
||||
|
||||
function suiteHref(suite) {
|
||||
return pathHref(suitePath(suite));
|
||||
}
|
||||
|
||||
function specHref(result) {
|
||||
const suite = stateBuilder.suitesById[result.parentSuiteId];
|
||||
const path = suitePath(suite);
|
||||
path.push(result.description);
|
||||
return pathHref(path);
|
||||
}
|
||||
|
||||
function pathHref(path) {
|
||||
// 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)
|
||||
addToExistingQueryString('spec', JSON.stringify(path))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -881,13 +907,36 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
};
|
||||
|
||||
jasmineRequire.HtmlSpecFilter = function() {
|
||||
/**
|
||||
* @name HtmlSpecFilter
|
||||
* @classdesc Legacy HTML spec filter, for backward compatibility
|
||||
* with boot files that predate {@link HtmlExactSpecFilter}.
|
||||
* @param options Object with a filterString method
|
||||
* @constructor
|
||||
* @deprecated
|
||||
* @since 1.2.0
|
||||
*/
|
||||
// Legacy HTML spec filter, preserved for backward compatibility with
|
||||
// boot files that predate HtmlExactSpecFilterV2
|
||||
function HtmlSpecFilter(options) {
|
||||
const filterString =
|
||||
options &&
|
||||
options.filterString() &&
|
||||
options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
||||
let filterString = (options && options.filterString()) || '';
|
||||
|
||||
if (filterString.startsWith('[')) {
|
||||
// Convert an HtmlExactSpecFilterV2 string into something we can use
|
||||
filterString = JSON.parse(filterString).join(' ');
|
||||
}
|
||||
|
||||
filterString = filterString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
||||
|
||||
const filterPattern = new RegExp(filterString);
|
||||
|
||||
/**
|
||||
* Determines whether the spec with the specified name should be executed.
|
||||
* @name HtmlSpecFilter#matches
|
||||
* @function
|
||||
* @param {string} specName The full name of the spec
|
||||
* @returns {boolean}
|
||||
*/
|
||||
this.matches = function(specName) {
|
||||
return filterPattern.test(specName);
|
||||
};
|
||||
@@ -921,38 +970,57 @@ jasmineRequire.ResultsNode = function() {
|
||||
};
|
||||
|
||||
jasmineRequire.QueryString = function() {
|
||||
function QueryString(options) {
|
||||
this.navigateWithNewParam = function(key, value) {
|
||||
options.getWindowLocation().search = this.fullStringWithNewParam(
|
||||
/**
|
||||
* Reads and manipulates the query string.
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class QueryString {
|
||||
#getWindowLocation;
|
||||
|
||||
/**
|
||||
* @param options Object with a getWindowLocation property, which should be
|
||||
* a function returning the current value of window.location.
|
||||
*/
|
||||
constructor(options) {
|
||||
this.#getWindowLocation = options.getWindowLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the specified query parameter and navigates to the resulting URL.
|
||||
* @param {string} key
|
||||
* @param {string} value
|
||||
*/
|
||||
navigateWithNewParam(key, value) {
|
||||
this.#getWindowLocation().search = this.fullStringWithNewParam(
|
||||
key,
|
||||
value
|
||||
);
|
||||
};
|
||||
|
||||
this.fullStringWithNewParam = function(key, value) {
|
||||
const paramMap = queryStringToParamMap();
|
||||
paramMap[key] = value;
|
||||
return toQueryString(paramMap);
|
||||
};
|
||||
|
||||
this.getParam = function(key) {
|
||||
return queryStringToParamMap()[key];
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
function toQueryString(paramMap) {
|
||||
const qStrPairs = [];
|
||||
for (const prop in paramMap) {
|
||||
qStrPairs.push(
|
||||
encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])
|
||||
);
|
||||
}
|
||||
return '?' + qStrPairs.join('&');
|
||||
}
|
||||
|
||||
function queryStringToParamMap() {
|
||||
const paramStr = options.getWindowLocation().search.substring(1);
|
||||
/**
|
||||
* Returns a new URL based on the current location, with the specified
|
||||
* query parameter set.
|
||||
* @param {string} key
|
||||
* @param {string} value
|
||||
* @return {string}
|
||||
*/
|
||||
fullStringWithNewParam(key, value) {
|
||||
const paramMap = this.#queryStringToParamMap();
|
||||
paramMap[key] = value;
|
||||
return toQueryString(paramMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the specified query parameter.
|
||||
* @param {string} key
|
||||
* @return {string}
|
||||
*/
|
||||
getParam(key) {
|
||||
return this.#queryStringToParamMap()[key];
|
||||
}
|
||||
|
||||
#queryStringToParamMap() {
|
||||
const paramStr = this.#getWindowLocation().search.substring(1);
|
||||
let params = [];
|
||||
const paramMap = {};
|
||||
|
||||
@@ -972,5 +1040,68 @@ jasmineRequire.QueryString = function() {
|
||||
}
|
||||
}
|
||||
|
||||
function toQueryString(paramMap) {
|
||||
const qStrPairs = [];
|
||||
for (const prop in paramMap) {
|
||||
qStrPairs.push(
|
||||
encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])
|
||||
);
|
||||
}
|
||||
return '?' + qStrPairs.join('&');
|
||||
}
|
||||
|
||||
return QueryString;
|
||||
};
|
||||
|
||||
jasmineRequire.HtmlExactSpecFilter = function() {
|
||||
/**
|
||||
* Spec filter for use with {@link HtmlReporter}
|
||||
*
|
||||
* See lib/jasmine-core/boot1.js for usage.
|
||||
* @since 5.11.0
|
||||
*/
|
||||
class HtmlExactSpecFilter {
|
||||
#getFilterString;
|
||||
|
||||
/**
|
||||
* Create a filter instance.
|
||||
* @param options Object with a queryString property, which should be an
|
||||
* instance of {@link QueryString}.
|
||||
*/
|
||||
constructor(options) {
|
||||
this.#getFilterString = function() {
|
||||
return options.queryString.getParam('spec');
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the specified spec should be executed.
|
||||
* @param {Spec} spec
|
||||
* @returns {boolean}
|
||||
*/
|
||||
matches(spec) {
|
||||
const filterString = this.#getFilterString();
|
||||
|
||||
if (!filterString) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const filterPath = JSON.parse(this.#getFilterString());
|
||||
const specPath = spec.getPath();
|
||||
|
||||
if (filterPath.length > specPath.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < filterPath.length; i++) {
|
||||
if (specPath[i] !== filterPath[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return HtmlExactSpecFilter;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jasmine-core",
|
||||
"license": "MIT",
|
||||
"version": "5.10.0",
|
||||
"version": "5.11.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jasmine/jasmine.git"
|
||||
@@ -52,7 +52,7 @@
|
||||
"sass": "^1.58.3"
|
||||
},
|
||||
"browserslist": [
|
||||
"Safari >= 15",
|
||||
"Safari >= 16",
|
||||
"Firefox >= 102",
|
||||
"last 2 Chrome versions",
|
||||
"last 2 Edge versions"
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
## New Features
|
||||
|
||||
* Optionally detect late promise rejections and don't report them as errors
|
||||
* Optionally detect promise rejections that are handled after an initial
|
||||
unhandled promise rejection event and don't report them as errors.
|
||||
This is off by default because it comes with a performance cost. It can be
|
||||
enabled by setting the `detectLateRejectionHandling` config property to true.
|
||||
* Add `getSpecProperty` to retrieve data that was set with `setSpecProperty`.
|
||||
|
||||
66
release_notes/5.11.0.md
Normal file
66
release_notes/5.11.0.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Jasmine Core 5.11.0 Release Notes
|
||||
|
||||
## New features
|
||||
|
||||
* `detectLateRejectionHandling` works in `beforeAll` and `afterAll` as well as
|
||||
in specs.
|
||||
* Clicking a link in the HTML reporter does exact filtering rather than a
|
||||
substring match.
|
||||
|
||||
If you use jasmine-browser-runner or load boot1.js directly from jasmine-core,
|
||||
you'll get the new filtering behavior automatically. Otherwise, it requires
|
||||
several changes to boot1.js:
|
||||
|
||||
1. Add `queryString` to the options object passed to `HtmlReporter`, and
|
||||
remove `filterSpecs`.
|
||||
2. Instantiate a `jasmine.HtmlExactSpecFilter` instead of a
|
||||
`jasmine.HtmlSpecFilter`.
|
||||
2. Change the body of `config.specFilter` from
|
||||
`return specFilter.matches(spec.getFullName());` to
|
||||
`return specFilter.matches(spec)`
|
||||
|
||||
For a working example, see lib/jasmine-core/boot1.js in this package.
|
||||
Old boot1.js files will still work, but you'll get the old filtering behavior.
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fixed global error handling when the env is executed repeatedly
|
||||
|
||||
## Changes to supported environments
|
||||
|
||||
* Safari 15 is no longer supported.
|
||||
|
||||
## Documentation improvements
|
||||
|
||||
* Added API reference docs for classes used in browser boot files
|
||||
* Documented the order properties of `jasmineStarted` and `jasmineDone` events
|
||||
|
||||
## Internal improvements
|
||||
|
||||
* Unified top suite and regular suite execution
|
||||
* Converted `Spec`, `Suite`, and `QueryString` to ES6 classes
|
||||
* Extracted configuration out of `Env`
|
||||
* Updated tests to characterize suite/spec reporting more completely
|
||||
* Adopted `forbidDuplicateNames: true` in jasmine-core's own tests
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------------|--------------------------------|
|
||||
| Node | 18.20.5**, 20, 22, 24 |
|
||||
| Safari | 16**, 17** |
|
||||
| Chrome | 140* |
|
||||
| Firefox | 102**, 115**, 128**, 140, 143* |
|
||||
| Edge | 140* |
|
||||
|
||||
\* Evergreen browser. Each version of Jasmine is tested against the latest
|
||||
version available at release time.<br>
|
||||
\** Supported on a best-effort basis. Support for these versions may be dropped
|
||||
if it becomes impractical, and bugs affecting only these versions may not be
|
||||
treated as release blockers.
|
||||
|
||||
------
|
||||
|
||||
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
|
||||
@@ -28,13 +28,20 @@ failfile=`mktemp -t jasmine-results.XXXXXX` || exit 1
|
||||
run_browser chrome latest
|
||||
|
||||
run_browser firefox latest
|
||||
run_browser firefox 140
|
||||
run_browser firefox 128
|
||||
run_browser firefox 115
|
||||
if [ "$1" = "--not-actually-all" ]; then
|
||||
echo "SKIPPED: firefox 140" >> "$passfile"
|
||||
echo "SKIPPED: firefox 128" >> "$passfile"
|
||||
echo "SKIPPED: firefox 115" >> "$passfile"
|
||||
else
|
||||
run_browser firefox 140
|
||||
run_browser firefox 128
|
||||
run_browser firefox 115
|
||||
fi
|
||||
run_browser firefox 102
|
||||
|
||||
run_browser safari 17
|
||||
run_browser safari 16
|
||||
run_browser safari 15
|
||||
|
||||
run_browser MicrosoftEdge latest
|
||||
|
||||
echo
|
||||
|
||||
@@ -242,7 +242,7 @@ describe('Clock', function() {
|
||||
expect(fakeGlobal.clearInterval).toBe(replacedClearInterval);
|
||||
});
|
||||
|
||||
it('replaces the global timer functions on uninstall', function() {
|
||||
it('restores the global timer functions on uninstall', function() {
|
||||
const fakeSetTimeout = jasmine.createSpy('global setTimeout'),
|
||||
fakeClearTimeout = jasmine.createSpy('global clearTimeout'),
|
||||
fakeSetInterval = jasmine.createSpy('global setInterval'),
|
||||
@@ -408,211 +408,219 @@ describe('Clock', function() {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('schedules the delayed function (via setTimeout) with the fake timer', function() {
|
||||
const fakeSetTimeout = jasmine.createSpy('setTimeout'),
|
||||
scheduleFunction = jasmine.createSpy('scheduleFunction'),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setTimeout: fakeSetTimeout },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
describe('setTimeout', function() {
|
||||
it('schedules the delayed function with the fake timer', function() {
|
||||
const fakeSetTimeout = jasmine.createSpy('setTimeout'),
|
||||
scheduleFunction = jasmine.createSpy('scheduleFunction'),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setTimeout: fakeSetTimeout },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
mockDate
|
||||
),
|
||||
timeout = new clock.FakeTimeout();
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
),
|
||||
timeout = new clock.FakeTimeout();
|
||||
|
||||
clock.install();
|
||||
clock.setTimeout(delayedFn, 0, 'a', 'b');
|
||||
clock.install();
|
||||
clock.setTimeout(delayedFn, 0, 'a', 'b');
|
||||
|
||||
expect(fakeSetTimeout).not.toHaveBeenCalled();
|
||||
expect(fakeSetTimeout).not.toHaveBeenCalled();
|
||||
|
||||
if (!NODE_JS) {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b']
|
||||
);
|
||||
} else {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b'],
|
||||
false,
|
||||
timeout
|
||||
);
|
||||
}
|
||||
if (!NODE_JS) {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b']
|
||||
);
|
||||
} else {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b'],
|
||||
false,
|
||||
timeout
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an id for the delayed function', function() {
|
||||
const fakeSetTimeout = jasmine.createSpy('setTimeout'),
|
||||
scheduleId = 123,
|
||||
scheduleFunction = jasmine
|
||||
.createSpy('scheduleFunction')
|
||||
.and.returnValue(scheduleId),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setTimeout: fakeSetTimeout },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
|
||||
clock.install();
|
||||
const timeout = clock.setTimeout(delayedFn, 0);
|
||||
|
||||
if (!NODE_JS) {
|
||||
expect(timeout).toEqual(123);
|
||||
} else {
|
||||
expect(timeout.constructor.name).toEqual('FakeTimeout');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an id for the delayed function', function() {
|
||||
const fakeSetTimeout = jasmine.createSpy('setTimeout'),
|
||||
scheduleId = 123,
|
||||
scheduleFunction = jasmine
|
||||
.createSpy('scheduleFunction')
|
||||
.and.returnValue(scheduleId),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setTimeout: fakeSetTimeout },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
describe('clearTimeout', function() {
|
||||
it('clears the scheduled function with the scheduler', function() {
|
||||
const fakeClearTimeout = jasmine.createSpy('clearTimeout'),
|
||||
delayedFunctionScheduler = jasmine.createSpyObj(
|
||||
'delayedFunctionScheduler',
|
||||
['removeFunctionWithId']
|
||||
),
|
||||
fakeGlobal = { setTimeout: fakeClearTimeout },
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
|
||||
clock.install();
|
||||
const timeout = clock.setTimeout(delayedFn, 0);
|
||||
clock.install();
|
||||
clock.clearTimeout(123);
|
||||
|
||||
if (!NODE_JS) {
|
||||
expect(timeout).toEqual(123);
|
||||
} else {
|
||||
expect(timeout.constructor.name).toEqual('FakeTimeout');
|
||||
}
|
||||
expect(fakeClearTimeout).not.toHaveBeenCalled();
|
||||
expect(
|
||||
delayedFunctionScheduler.removeFunctionWithId
|
||||
).toHaveBeenCalledWith(123);
|
||||
});
|
||||
});
|
||||
|
||||
it('clears the scheduled function with the scheduler', function() {
|
||||
const fakeClearTimeout = jasmine.createSpy('clearTimeout'),
|
||||
delayedFunctionScheduler = jasmine.createSpyObj(
|
||||
'delayedFunctionScheduler',
|
||||
['removeFunctionWithId']
|
||||
),
|
||||
fakeGlobal = { setTimeout: fakeClearTimeout },
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
describe('setInterval', function() {
|
||||
it('schedules the delayed function with the fake timer', function() {
|
||||
const fakeSetInterval = jasmine.createSpy('setInterval'),
|
||||
scheduleFunction = jasmine.createSpy('scheduleFunction'),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setInterval: fakeSetInterval },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
),
|
||||
timeout = new clock.FakeTimeout();
|
||||
|
||||
clock.install();
|
||||
clock.clearTimeout(123);
|
||||
clock.install();
|
||||
clock.setInterval(delayedFn, 0, 'a', 'b');
|
||||
|
||||
expect(fakeClearTimeout).not.toHaveBeenCalled();
|
||||
expect(delayedFunctionScheduler.removeFunctionWithId).toHaveBeenCalledWith(
|
||||
123
|
||||
);
|
||||
expect(fakeSetInterval).not.toHaveBeenCalled();
|
||||
|
||||
if (!NODE_JS) {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b'],
|
||||
true
|
||||
);
|
||||
} else {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b'],
|
||||
true,
|
||||
timeout
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an id for the delayed function', function() {
|
||||
const fakeSetInterval = jasmine.createSpy('setInterval'),
|
||||
scheduleId = 123,
|
||||
scheduleFunction = jasmine
|
||||
.createSpy('scheduleFunction')
|
||||
.and.returnValue(scheduleId),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setInterval: fakeSetInterval },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
|
||||
clock.install();
|
||||
const interval = clock.setInterval(delayedFn, 0);
|
||||
|
||||
if (!NODE_JS) {
|
||||
expect(interval).toEqual(123);
|
||||
} else {
|
||||
expect(interval.constructor.name).toEqual('FakeTimeout');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('schedules the delayed function with the fake timer', function() {
|
||||
const fakeSetInterval = jasmine.createSpy('setInterval'),
|
||||
scheduleFunction = jasmine.createSpy('scheduleFunction'),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setInterval: fakeSetInterval },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
describe('clearInterval', function() {
|
||||
it('clears the scheduled function with the scheduler', function() {
|
||||
const clearInterval = jasmine.createSpy('clearInterval'),
|
||||
delayedFunctionScheduler = jasmine.createSpyObj(
|
||||
'delayedFunctionScheduler',
|
||||
['removeFunctionWithId']
|
||||
),
|
||||
fakeGlobal = { setInterval: clearInterval },
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
mockDate
|
||||
),
|
||||
timeout = new clock.FakeTimeout();
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
|
||||
clock.install();
|
||||
clock.setInterval(delayedFn, 0, 'a', 'b');
|
||||
clock.install();
|
||||
clock.clearInterval(123);
|
||||
|
||||
expect(fakeSetInterval).not.toHaveBeenCalled();
|
||||
|
||||
if (!NODE_JS) {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b'],
|
||||
true
|
||||
);
|
||||
} else {
|
||||
expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(
|
||||
delayedFn,
|
||||
0,
|
||||
['a', 'b'],
|
||||
true,
|
||||
timeout
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns an id for the delayed function', function() {
|
||||
const fakeSetInterval = jasmine.createSpy('setInterval'),
|
||||
scheduleId = 123,
|
||||
scheduleFunction = jasmine
|
||||
.createSpy('scheduleFunction')
|
||||
.and.returnValue(scheduleId),
|
||||
delayedFunctionScheduler = { scheduleFunction: scheduleFunction },
|
||||
fakeGlobal = { setInterval: fakeSetInterval },
|
||||
delayedFn = jasmine.createSpy('delayedFn'),
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
|
||||
clock.install();
|
||||
const interval = clock.setInterval(delayedFn, 0);
|
||||
|
||||
if (!NODE_JS) {
|
||||
expect(interval).toEqual(123);
|
||||
} else {
|
||||
expect(interval.constructor.name).toEqual('FakeTimeout');
|
||||
}
|
||||
});
|
||||
|
||||
it('clears the scheduled function with the scheduler', function() {
|
||||
const clearInterval = jasmine.createSpy('clearInterval'),
|
||||
delayedFunctionScheduler = jasmine.createSpyObj(
|
||||
'delayedFunctionScheduler',
|
||||
['removeFunctionWithId']
|
||||
),
|
||||
fakeGlobal = { setInterval: clearInterval },
|
||||
mockDate = {
|
||||
install: function() {},
|
||||
tick: function() {},
|
||||
uninstall: function() {}
|
||||
},
|
||||
clock = new jasmineUnderTest.Clock(
|
||||
fakeGlobal,
|
||||
function() {
|
||||
return delayedFunctionScheduler;
|
||||
},
|
||||
mockDate
|
||||
);
|
||||
|
||||
clock.install();
|
||||
clock.clearInterval(123);
|
||||
|
||||
expect(clearInterval).not.toHaveBeenCalled();
|
||||
expect(delayedFunctionScheduler.removeFunctionWithId).toHaveBeenCalledWith(
|
||||
123
|
||||
);
|
||||
expect(clearInterval).not.toHaveBeenCalled();
|
||||
expect(
|
||||
delayedFunctionScheduler.removeFunctionWithId
|
||||
).toHaveBeenCalledWith(123);
|
||||
});
|
||||
});
|
||||
|
||||
it('gives you a friendly reminder if the Clock is not installed and you tick', function() {
|
||||
|
||||
134
spec/core/ConfigurationSpec.js
Normal file
134
spec/core/ConfigurationSpec.js
Normal file
@@ -0,0 +1,134 @@
|
||||
describe('Configuration', function() {
|
||||
const standardBooleanKeys = [
|
||||
'random',
|
||||
'stopOnSpecFailure',
|
||||
'stopSpecOnExpectationFailure',
|
||||
'failSpecWithNoExpectations',
|
||||
'hideDisabled',
|
||||
'autoCleanClosures',
|
||||
'forbidDuplicateNames',
|
||||
'detectLateRejectionHandling'
|
||||
];
|
||||
const allKeys = [
|
||||
...standardBooleanKeys,
|
||||
'seed',
|
||||
'specFilter',
|
||||
'verboseDeprecations'
|
||||
];
|
||||
Object.freeze(standardBooleanKeys);
|
||||
Object.freeze(allKeys);
|
||||
|
||||
it('provides defaults', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
expect(subject.random).toEqual(true);
|
||||
expect(subject.seed).toBeNull();
|
||||
expect(subject.stopOnSpecFailure).toEqual(false);
|
||||
expect(subject.stopSpecOnExpectationFailure).toEqual(false);
|
||||
expect(subject.failSpecWithNoExpectations).toEqual(false);
|
||||
expect(subject.specFilter).toEqual(jasmine.any(Function));
|
||||
expect(subject.specFilter()).toEqual(true);
|
||||
expect(subject.hideDisabled).toEqual(false);
|
||||
expect(subject.autoCleanClosures).toEqual(true);
|
||||
expect(subject.forbidDuplicateNames).toEqual(false);
|
||||
expect(subject.verboseDeprecations).toEqual(false);
|
||||
expect(subject.detectLateRejectionHandling).toEqual(false);
|
||||
});
|
||||
|
||||
describe('copy()', function() {
|
||||
it('returns a copy of the configuration as a plain old JS object', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
|
||||
const copy = subject.copy();
|
||||
|
||||
expect(copy.constructor.name).toEqual('Object');
|
||||
|
||||
expect(new Set(Object.keys(copy))).toEqual(new Set(allKeys));
|
||||
for (const k of allKeys) {
|
||||
expect(copy[k]).toEqual(subject[k]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('update()', function() {
|
||||
it('does not update properties that are absent from the parameter', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
const originalValues = subject.copy();
|
||||
|
||||
subject.update({});
|
||||
expect(subject.copy()).toEqual(originalValues);
|
||||
});
|
||||
|
||||
function booleanPropertyBehavior(key) {
|
||||
it('does not update the property if the specified value is undefined', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
const orig = subject[key];
|
||||
|
||||
subject.update({ [key]: undefined });
|
||||
|
||||
expect(subject[key]).toEqual(orig);
|
||||
});
|
||||
|
||||
it('updates the property if the specified value is not undefined', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
const orig = subject[key];
|
||||
|
||||
subject.update({ [key]: !orig });
|
||||
expect(subject[key]).toEqual(!orig);
|
||||
|
||||
subject.update({ [key]: orig });
|
||||
expect(subject[key]).toEqual(orig);
|
||||
});
|
||||
}
|
||||
|
||||
for (const k of standardBooleanKeys) {
|
||||
describe(k, function() {
|
||||
booleanPropertyBehavior(k);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: in the next major release, treat verboseDeprecations like other booleans
|
||||
it('sets verboseDeprecations when present', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
const orig = subject.verboseDeprecations;
|
||||
|
||||
subject.update({ verboseDeprecations: !orig });
|
||||
expect(subject.verboseDeprecations).toEqual(!orig);
|
||||
|
||||
subject.update({ verboseDeprecations: orig });
|
||||
expect(subject.verboseDeprecations).toEqual(orig);
|
||||
|
||||
// For backwards compatibility, explicitly setting to undefined should
|
||||
// work. Undefined isn't officially valid but gets treated like false.
|
||||
subject.update({ verboseDeprecations: undefined });
|
||||
expect(subject.verboseDeprecations).toBeUndefined();
|
||||
});
|
||||
|
||||
it('sets specFilter when truthy', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
const orig = subject.specFilter;
|
||||
|
||||
subject.update({ specFilter: undefined });
|
||||
expect(subject.specFilter).toBe(orig);
|
||||
|
||||
subject.update({ specFilter: false });
|
||||
expect(subject.specFilter).toBe(orig);
|
||||
|
||||
function newSpecFilter() {}
|
||||
subject.update({ specFilter: newSpecFilter });
|
||||
expect(subject.specFilter).toBe(newSpecFilter);
|
||||
});
|
||||
|
||||
it('sets seed when not undefined', function() {
|
||||
const subject = new jasmineUnderTest.Configuration();
|
||||
|
||||
subject.update({ seed: undefined });
|
||||
expect(subject.seed).toBeNull();
|
||||
|
||||
subject.update({ seed: 1234 });
|
||||
expect(subject.seed).toEqual(1234);
|
||||
|
||||
subject.update({ seed: null });
|
||||
expect(subject.seed).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -33,18 +33,6 @@ describe('Spec', function() {
|
||||
expect(jasmineUnderTest.Spec.isPendingSpecException(void 0)).toBe(false);
|
||||
});
|
||||
|
||||
it('is marked pending if created without a function body', function() {
|
||||
const startCallback = jasmine.createSpy('startCallback'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
onStart: startCallback,
|
||||
queueableFn: { fn: null },
|
||||
resultCallback: resultCallback
|
||||
});
|
||||
|
||||
expect(spec.status()).toBe('pending');
|
||||
});
|
||||
|
||||
describe('#executionFinished', function() {
|
||||
it('removes the fn if autoCleanClosures is true', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
@@ -116,32 +104,25 @@ describe('Spec', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('#status returns passing by default', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: jasmine.createSpy('spec body') }
|
||||
});
|
||||
expect(spec.status()).toBe('passed');
|
||||
});
|
||||
|
||||
describe('#status', function() {
|
||||
it('returns "passed"" by default', function() {
|
||||
describe('status', function() {
|
||||
it('is "passed" by default', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
expect(spec.status()).toBe('passed');
|
||||
expect(spec.getResult().status).toBe('passed');
|
||||
});
|
||||
|
||||
it('returns "passed"" if all expectations passed', function() {
|
||||
it('is "passed" if all expectations passed', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
spec.addExpectationResult(true, {});
|
||||
|
||||
expect(spec.status()).toBe('passed');
|
||||
expect(spec.getResult().status).toBe('passed');
|
||||
});
|
||||
|
||||
it('returns "failed" if any expectation failed', function() {
|
||||
it('is "failed" if any expectation failed', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
@@ -149,7 +130,19 @@ describe('Spec', function() {
|
||||
spec.addExpectationResult(true, {});
|
||||
spec.addExpectationResult(false, {});
|
||||
|
||||
expect(spec.status()).toBe('failed');
|
||||
expect(spec.getResult().status).toBe('failed');
|
||||
});
|
||||
|
||||
it('is "pending" if created without a function body', function() {
|
||||
const startCallback = jasmine.createSpy('startCallback'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
onStart: startCallback,
|
||||
queueableFn: { fn: null },
|
||||
resultCallback: resultCallback
|
||||
});
|
||||
|
||||
expect(spec.getResult().status).toBe('pending');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ describe('Suite', function() {
|
||||
const suite = new jasmineUnderTest.Suite({});
|
||||
|
||||
suite.addExpectationResult(false, {});
|
||||
expect(suite.status()).toBe('failed');
|
||||
expect(suite.getResult().status).toBe('failed');
|
||||
});
|
||||
|
||||
it('retrieves a result with updated status', function() {
|
||||
@@ -140,7 +140,7 @@ describe('Suite', function() {
|
||||
suite.addExpectationResult(false, { message: 'failed' });
|
||||
}).toThrowError(jasmineUnderTest.errors.ExpectationFailed);
|
||||
|
||||
expect(suite.status()).toBe('failed');
|
||||
expect(suite.getResult().status).toBe('failed');
|
||||
expect(suite.result.failedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'failed' })
|
||||
]);
|
||||
|
||||
@@ -114,7 +114,6 @@ describe('TreeRunner', function() {
|
||||
expect(specRunQueueArgs.queueableFns[1]).toEqual(queueableFn);
|
||||
queueableFn.fn();
|
||||
|
||||
expect(spec.status()).toEqual('pending');
|
||||
expect(spec.getResult().status).toEqual('pending');
|
||||
expect(spec.getResult().pendingReason).toEqual('');
|
||||
});
|
||||
@@ -136,7 +135,6 @@ describe('TreeRunner', function() {
|
||||
expect(specRunQueueArgs.queueableFns[1]).toEqual(queueableFn);
|
||||
queueableFn.fn();
|
||||
|
||||
expect(spec.status()).toEqual('pending');
|
||||
expect(spec.getResult().status).toEqual('pending');
|
||||
expect(spec.getResult().pendingReason).toEqual('some reason');
|
||||
});
|
||||
@@ -163,111 +161,6 @@ describe('TreeRunner', function() {
|
||||
expect(spec.executionFinished).toHaveBeenCalledWith(false, true);
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
|
||||
describe('Late promise rejection handling', function() {
|
||||
it('is enabled when the detectLateRejectionHandling param is true', function() {
|
||||
const before = jasmine.createSpy('before');
|
||||
const after = jasmine.createSpy('after');
|
||||
const queueableFn = {
|
||||
fn: jasmine.createSpy('test body').and.callFake(function() {
|
||||
expect(before).toHaveBeenCalled();
|
||||
expect(after).not.toHaveBeenCalled();
|
||||
})
|
||||
};
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn,
|
||||
beforeAndAfterFns: function() {
|
||||
return { befores: [before], afters: [after] };
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
runQueue,
|
||||
setTimeout,
|
||||
suiteRunQueueArgs,
|
||||
globalErrors
|
||||
} = runSingleSpecSuite(spec, { detectLateRejectionHandling: true });
|
||||
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
|
||||
expect(specRunQueueOpts.queueableFns).toEqual([
|
||||
{ fn: jasmine.any(Function) },
|
||||
before,
|
||||
queueableFn,
|
||||
after,
|
||||
{ fn: jasmine.any(Function) },
|
||||
{
|
||||
fn: jasmine.any(Function),
|
||||
type: 'specCleanup'
|
||||
}
|
||||
]);
|
||||
|
||||
const done = jasmine.createSpy('done');
|
||||
specRunQueueOpts.queueableFns[4].fn(done);
|
||||
expect(globalErrors.reportUnhandledRejections).not.toHaveBeenCalled();
|
||||
expect(done).not.toHaveBeenCalled();
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledOnceWith(jasmine.any(Function));
|
||||
setTimeout.calls.argsFor(0)[0]();
|
||||
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalled();
|
||||
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalledBefore(
|
||||
done
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function runSingleSpecSuite(spec, optionalConfig) {
|
||||
const topSuiteId = 'suite1';
|
||||
spec.parentSuiteId = topSuiteId;
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: topSuiteId });
|
||||
topSuite.addChild(spec);
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ spec }];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const runableResources = mockRunableResources();
|
||||
const globalErrors = mockGlobalErrors();
|
||||
const setTimeout = jasmine.createSpy('setTimeout');
|
||||
const currentRunableTracker = new jasmineUnderTest.CurrentRunableTracker();
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors,
|
||||
setTimeout,
|
||||
runableResources,
|
||||
reportDispatcher,
|
||||
currentRunableTracker,
|
||||
getConfig() {
|
||||
return optionalConfig || {};
|
||||
},
|
||||
reportChildrenOfBeforeAllFailure() {}
|
||||
});
|
||||
|
||||
const executePromise = subject.execute();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const suiteRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
|
||||
return {
|
||||
runQueue,
|
||||
globalErrors,
|
||||
setTimeout,
|
||||
currentRunableTracker,
|
||||
runableResources,
|
||||
reportDispatcher,
|
||||
suiteRunQueueArgs,
|
||||
executePromise
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
describe('Suite execution', function() {
|
||||
@@ -516,6 +409,288 @@ describe('TreeRunner', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('does not remove before and after fns from the top suite', async function() {
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
|
||||
spyOn(topSuite, 'cleanupBeforeAfter');
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors: mockGlobalErrors(),
|
||||
runableResources: mockRunableResources(),
|
||||
reportDispatcher: mockReportDispatcher(),
|
||||
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
|
||||
getConfig() {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
const executePromise = subject.execute();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
|
||||
for (const qfn of topSuiteRunQueueOpts.queueableFns) {
|
||||
qfn.fn();
|
||||
}
|
||||
topSuiteRunQueueOpts.onComplete();
|
||||
|
||||
await expectAsync(executePromise).toBeResolved();
|
||||
expect(topSuite.cleanupBeforeAfter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('Late promise rejection handling', function() {
|
||||
it('works for specs when the detectLateRejectionHandling param is true', function() {
|
||||
const before = jasmine.createSpy('before');
|
||||
const after = jasmine.createSpy('after');
|
||||
const queueableFn = {
|
||||
fn: jasmine.createSpy('test body').and.callFake(function() {
|
||||
expect(before).toHaveBeenCalled();
|
||||
expect(after).not.toHaveBeenCalled();
|
||||
})
|
||||
};
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn,
|
||||
beforeAndAfterFns: function() {
|
||||
return { befores: [before], afters: [after] };
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
runQueue,
|
||||
setTimeout,
|
||||
suiteRunQueueArgs,
|
||||
globalErrors
|
||||
} = runSingleSpecSuite(spec, { detectLateRejectionHandling: true });
|
||||
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
|
||||
expect(specRunQueueOpts.queueableFns).toEqual([
|
||||
{ fn: jasmine.any(Function) },
|
||||
before,
|
||||
queueableFn,
|
||||
after,
|
||||
{ fn: jasmine.any(Function) },
|
||||
{
|
||||
fn: jasmine.any(Function),
|
||||
type: 'specCleanup'
|
||||
}
|
||||
]);
|
||||
|
||||
const done = jasmine.createSpy('done');
|
||||
specRunQueueOpts.queueableFns[4].fn(done);
|
||||
expect(globalErrors.reportUnhandledRejections).not.toHaveBeenCalled();
|
||||
expect(done).not.toHaveBeenCalled();
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledOnceWith(jasmine.any(Function));
|
||||
setTimeout.calls.argsFor(0)[0]();
|
||||
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalled();
|
||||
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalledBefore(
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('works for beforeAll when the detectLateRejectionHandling param is true', async function() {
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
|
||||
const suite = new jasmineUnderTest.Suite({
|
||||
id: 'suite',
|
||||
parentSuite: topSuite
|
||||
});
|
||||
suite.beforeAll(function() {});
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn() {} },
|
||||
parentSuite: suite
|
||||
});
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ suite }];
|
||||
},
|
||||
childrenOfSuiteSegment() {
|
||||
return [{ spec }];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const globalErrors = mockGlobalErrors();
|
||||
const setTimeout = jasmine.createSpy('setTimeout');
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors,
|
||||
runableResources: mockRunableResources(),
|
||||
reportDispatcher,
|
||||
setTimeout,
|
||||
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
|
||||
getConfig() {
|
||||
return { detectLateRejectionHandling: true };
|
||||
},
|
||||
reportChildrenOfBeforeAllFailure() {}
|
||||
});
|
||||
|
||||
const executePromise = subject.execute();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
topSuiteRunQueueOpts.queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const suiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
expect(suiteRunQueueOpts.queueableFns).toEqual([
|
||||
{ fn: jasmine.any(Function) }, // onStart
|
||||
jasmine.objectContaining({ type: 'beforeAll' }),
|
||||
{ fn: jasmine.any(Function) }, // detect late rejection handling
|
||||
{ fn: jasmine.any(Function) } // spec
|
||||
]);
|
||||
suiteRunQueueOpts.queueableFns[0].fn();
|
||||
const done = jasmine.createSpy('done');
|
||||
suiteRunQueueOpts.queueableFns[2].fn(done);
|
||||
expect(globalErrors.reportUnhandledRejections).not.toHaveBeenCalled();
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledOnceWith(jasmine.any(Function));
|
||||
setTimeout.calls.argsFor(0)[0]();
|
||||
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalledBefore(
|
||||
done
|
||||
);
|
||||
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
|
||||
it('works for afterAll when the detectLateRejectionHandling param is true', async function() {
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
|
||||
const suite = new jasmineUnderTest.Suite({
|
||||
id: 'suite',
|
||||
parentSuite: topSuite
|
||||
});
|
||||
suite.afterAll(function() {});
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn() {} },
|
||||
parentSuite: suite
|
||||
});
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ suite }];
|
||||
},
|
||||
childrenOfSuiteSegment() {
|
||||
return [{ spec }];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const globalErrors = mockGlobalErrors();
|
||||
const setTimeout = jasmine.createSpy('setTimeout');
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors,
|
||||
runableResources: mockRunableResources(),
|
||||
reportDispatcher,
|
||||
setTimeout,
|
||||
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
|
||||
getConfig() {
|
||||
return { detectLateRejectionHandling: true };
|
||||
},
|
||||
reportChildrenOfBeforeAllFailure() {}
|
||||
});
|
||||
|
||||
const executePromise = subject.execute();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
topSuiteRunQueueOpts.queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const suiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
expect(suiteRunQueueOpts.queueableFns).toEqual([
|
||||
{ fn: jasmine.any(Function) }, // onStart
|
||||
{ fn: jasmine.any(Function) }, // spec
|
||||
jasmine.objectContaining({ type: 'afterAll' }),
|
||||
{ fn: jasmine.any(Function) } // detect late rejection handling
|
||||
]);
|
||||
suiteRunQueueOpts.queueableFns[0].fn();
|
||||
const done = jasmine.createSpy('done');
|
||||
suiteRunQueueOpts.queueableFns[3].fn(done);
|
||||
expect(globalErrors.reportUnhandledRejections).not.toHaveBeenCalled();
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledOnceWith(jasmine.any(Function));
|
||||
setTimeout.calls.argsFor(0)[0]();
|
||||
expect(globalErrors.reportUnhandledRejections).toHaveBeenCalledBefore(
|
||||
done
|
||||
);
|
||||
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
});
|
||||
|
||||
function runSingleSpecSuite(spec, optionalConfig) {
|
||||
const topSuiteId = 'suite1';
|
||||
spec.parentSuiteId = topSuiteId;
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: topSuiteId });
|
||||
topSuite.addChild(spec);
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ spec }];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const runableResources = mockRunableResources();
|
||||
const globalErrors = mockGlobalErrors();
|
||||
const setTimeout = jasmine.createSpy('setTimeout');
|
||||
const currentRunableTracker = new jasmineUnderTest.CurrentRunableTracker();
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors,
|
||||
setTimeout,
|
||||
runableResources,
|
||||
reportDispatcher,
|
||||
currentRunableTracker,
|
||||
getConfig() {
|
||||
return optionalConfig || {};
|
||||
},
|
||||
reportChildrenOfBeforeAllFailure() {}
|
||||
});
|
||||
|
||||
const executePromise = subject.execute();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const suiteRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
|
||||
return {
|
||||
runQueue,
|
||||
globalErrors,
|
||||
setTimeout,
|
||||
currentRunableTracker,
|
||||
runableResources,
|
||||
reportDispatcher,
|
||||
suiteRunQueueArgs,
|
||||
executePromise
|
||||
};
|
||||
}
|
||||
|
||||
function mockReportDispatcher() {
|
||||
const reportDispatcher = jasmine.createSpyObj(
|
||||
'reportDispatcher',
|
||||
|
||||
@@ -1600,6 +1600,16 @@ describe('Env integration', function() {
|
||||
suiteFullNameToId[e.fullName] = e.id;
|
||||
});
|
||||
|
||||
// Clone args to work around Jasmine mutating the result after passing it
|
||||
// to the reporter event.
|
||||
// TODO: remove this once Jasmine no longer does that
|
||||
const clone = structuredClone.bind(globalThis);
|
||||
reporter.specStarted.calls.saveArgumentsByValue(clone);
|
||||
reporter.specDone.calls.saveArgumentsByValue(clone);
|
||||
reporter.specStarted.calls.saveArgumentsByValue(clone);
|
||||
reporter.suiteDone.calls.saveArgumentsByValue(clone);
|
||||
|
||||
env.configure({ random: false });
|
||||
env.addReporter(reporter);
|
||||
|
||||
env.it('a top level spec', function() {});
|
||||
@@ -1636,110 +1646,179 @@ describe('Env integration', function() {
|
||||
expect(reporter.specStarted.calls.count()).toBe(6);
|
||||
expect(reporter.specDone.calls.count()).toBe(6);
|
||||
|
||||
expect(reporter.specStarted).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
description: 'a top level spec',
|
||||
parentSuiteId: null
|
||||
})
|
||||
);
|
||||
expect(reporter.specDone).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
description: 'a top level spec',
|
||||
status: 'passed',
|
||||
parentSuiteId: null
|
||||
})
|
||||
);
|
||||
expect(reporter.specStarted).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
description: 'with a spec',
|
||||
parentSuiteId: suiteFullNameToId['A Suite']
|
||||
})
|
||||
);
|
||||
expect(reporter.specDone).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
description: 'with a spec',
|
||||
status: 'passed',
|
||||
parentSuiteId: suiteFullNameToId['A Suite']
|
||||
})
|
||||
);
|
||||
const baseSpecEvent = {
|
||||
passedExpectations: [],
|
||||
failedExpectations: [],
|
||||
deprecationWarnings: [],
|
||||
pendingReason: '',
|
||||
duration: null,
|
||||
properties: null,
|
||||
debugLogs: null,
|
||||
id: jasmine.any(String),
|
||||
filename: jasmine.any(String)
|
||||
};
|
||||
|
||||
expect(reporter.specStarted).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
description: "with an x'ed spec",
|
||||
parentSuiteId: suiteFullNameToId['A Suite with a nested suite']
|
||||
})
|
||||
);
|
||||
expect(reporter.specDone).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
description: "with an x'ed spec",
|
||||
status: 'pending',
|
||||
parentSuiteId: suiteFullNameToId['A Suite with a nested suite']
|
||||
})
|
||||
);
|
||||
expect(reporter.specStarted.calls.argsFor(0)[0]).toEqual({
|
||||
...baseSpecEvent,
|
||||
description: 'a top level spec',
|
||||
fullName: 'a top level spec',
|
||||
parentSuiteId: null
|
||||
});
|
||||
expect(reporter.specDone.calls.argsFor(0)[0]).toEqual({
|
||||
...baseSpecEvent,
|
||||
description: 'a top level spec',
|
||||
fullName: 'a top level spec',
|
||||
status: 'passed',
|
||||
parentSuiteId: null,
|
||||
duration: jasmine.any(Number)
|
||||
});
|
||||
expect(reporter.specStarted.calls.argsFor(1)[0]).toEqual({
|
||||
...baseSpecEvent,
|
||||
description: 'with a spec',
|
||||
fullName: 'A Suite with a spec',
|
||||
parentSuiteId: suiteFullNameToId['A Suite']
|
||||
});
|
||||
expect(reporter.specDone.calls.argsFor(1)[0]).toEqual({
|
||||
...baseSpecEvent,
|
||||
description: 'with a spec',
|
||||
fullName: 'A Suite with a spec',
|
||||
status: 'passed',
|
||||
parentSuiteId: suiteFullNameToId['A Suite'],
|
||||
passedExpectations: [
|
||||
{ matcherName: 'toBe', message: 'Passed.', stack: '', passed: true }
|
||||
],
|
||||
duration: jasmine.any(Number)
|
||||
});
|
||||
|
||||
expect(reporter.specStarted).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
description: 'with a spec',
|
||||
parentSuiteId: suiteFullNameToId['A Suite with a nested suite']
|
||||
})
|
||||
);
|
||||
expect(reporter.specDone).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
description: 'with a spec',
|
||||
status: 'failed',
|
||||
parentSuiteId: suiteFullNameToId['A Suite with a nested suite']
|
||||
})
|
||||
);
|
||||
expect(reporter.specStarted.calls.argsFor(2)[0]).toEqual({
|
||||
...baseSpecEvent,
|
||||
description: "with an x'ed spec",
|
||||
fullName: "A Suite with a nested suite with an x'ed spec",
|
||||
parentSuiteId: suiteFullNameToId['A Suite with a nested suite'],
|
||||
pendingReason: 'Temporarily disabled with xit'
|
||||
});
|
||||
expect(reporter.specDone.calls.argsFor(2)[0]).toEqual({
|
||||
...baseSpecEvent,
|
||||
description: "with an x'ed spec",
|
||||
fullName: "A Suite with a nested suite with an x'ed spec",
|
||||
status: 'pending',
|
||||
parentSuiteId: suiteFullNameToId['A Suite with a nested suite'],
|
||||
pendingReason: 'Temporarily disabled with xit',
|
||||
duration: jasmine.any(Number)
|
||||
});
|
||||
|
||||
expect(reporter.specStarted).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
description: 'is pending',
|
||||
parentSuiteId:
|
||||
suiteFullNameToId['A Suite with only non-executable specs']
|
||||
})
|
||||
);
|
||||
expect(reporter.specDone).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
description: 'is pending',
|
||||
status: 'pending',
|
||||
parentSuiteId:
|
||||
suiteFullNameToId['A Suite with only non-executable specs']
|
||||
})
|
||||
);
|
||||
expect(reporter.specStarted.calls.argsFor(3)[0]).toEqual({
|
||||
...baseSpecEvent,
|
||||
description: 'with a spec',
|
||||
fullName: 'A Suite with a nested suite with a spec',
|
||||
parentSuiteId: suiteFullNameToId['A Suite with a nested suite']
|
||||
});
|
||||
expect(reporter.specDone.calls.argsFor(3)[0]).toEqual({
|
||||
...baseSpecEvent,
|
||||
description: 'with a spec',
|
||||
fullName: 'A Suite with a nested suite with a spec',
|
||||
status: 'failed',
|
||||
parentSuiteId: suiteFullNameToId['A Suite with a nested suite'],
|
||||
failedExpectations: [
|
||||
jasmine.objectContaining({
|
||||
matcherName: 'toBe',
|
||||
message: 'Expected true to be false.'
|
||||
})
|
||||
],
|
||||
duration: jasmine.any(Number)
|
||||
});
|
||||
|
||||
expect(reporter.suiteStarted).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
description: 'A Suite',
|
||||
parentSuiteId: null
|
||||
})
|
||||
);
|
||||
expect(reporter.suiteDone).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
description: 'A Suite',
|
||||
status: 'passed',
|
||||
parentSuiteId: null
|
||||
})
|
||||
);
|
||||
expect(reporter.specStarted.calls.argsFor(4)[0]).toEqual({
|
||||
...baseSpecEvent,
|
||||
description: 'is pending',
|
||||
fullName: 'A Suite with only non-executable specs is pending',
|
||||
parentSuiteId: suiteFullNameToId['A Suite with only non-executable specs']
|
||||
});
|
||||
expect(reporter.specDone.calls.argsFor(4)[0]).toEqual({
|
||||
...baseSpecEvent,
|
||||
description: 'is pending',
|
||||
status: 'pending',
|
||||
fullName: 'A Suite with only non-executable specs is pending',
|
||||
parentSuiteId:
|
||||
suiteFullNameToId['A Suite with only non-executable specs'],
|
||||
duration: jasmine.any(Number)
|
||||
});
|
||||
|
||||
expect(reporter.suiteStarted).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
description: 'with a nested suite',
|
||||
parentSuiteId: suiteFullNameToId['A Suite']
|
||||
})
|
||||
);
|
||||
expect(reporter.suiteDone).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
description: 'with a nested suite',
|
||||
status: 'passed',
|
||||
parentSuiteId: suiteFullNameToId['A Suite']
|
||||
})
|
||||
);
|
||||
expect(reporter.specStarted.calls.argsFor(5)[0]).toEqual({
|
||||
...baseSpecEvent,
|
||||
description: 'is xed',
|
||||
fullName: 'A Suite with only non-executable specs is xed',
|
||||
parentSuiteId:
|
||||
suiteFullNameToId['A Suite with only non-executable specs'],
|
||||
pendingReason: 'Temporarily disabled with xit'
|
||||
});
|
||||
expect(reporter.specDone.calls.argsFor(5)[0]).toEqual({
|
||||
...baseSpecEvent,
|
||||
description: 'is xed',
|
||||
status: 'pending',
|
||||
fullName: 'A Suite with only non-executable specs is xed',
|
||||
parentSuiteId:
|
||||
suiteFullNameToId['A Suite with only non-executable specs'],
|
||||
pendingReason: 'Temporarily disabled with xit',
|
||||
duration: jasmine.any(Number)
|
||||
});
|
||||
|
||||
const suiteDone = reporter.suiteDone.calls.argsFor(0)[0];
|
||||
expect(typeof suiteDone.duration).toBe('number');
|
||||
expect(reporter.suiteStarted.calls.count()).toBe(3);
|
||||
expect(reporter.suiteDone.calls.count()).toBe(3);
|
||||
|
||||
const suiteResult = reporter.suiteStarted.calls.argsFor(0)[0];
|
||||
expect(suiteResult.description).toEqual('A Suite');
|
||||
const baseSuiteEvent = {
|
||||
id: jasmine.any(String),
|
||||
filename: jasmine.any(String),
|
||||
failedExpectations: [],
|
||||
deprecationWarnings: [],
|
||||
duration: null,
|
||||
properties: null
|
||||
};
|
||||
|
||||
expect(reporter.suiteStarted.calls.argsFor(0)[0]).toEqual({
|
||||
...baseSuiteEvent,
|
||||
description: 'A Suite',
|
||||
fullName: 'A Suite',
|
||||
parentSuiteId: null
|
||||
});
|
||||
expect(reporter.suiteDone.calls.argsFor(2)[0]).toEqual({
|
||||
...baseSuiteEvent,
|
||||
description: 'A Suite',
|
||||
fullName: 'A Suite',
|
||||
status: 'passed',
|
||||
parentSuiteId: null,
|
||||
duration: jasmine.any(Number)
|
||||
});
|
||||
|
||||
expect(reporter.suiteStarted.calls.argsFor(1)[0]).toEqual({
|
||||
...baseSuiteEvent,
|
||||
description: 'with a nested suite',
|
||||
fullName: 'A Suite with a nested suite',
|
||||
parentSuiteId: suiteFullNameToId['A Suite']
|
||||
});
|
||||
expect(reporter.suiteDone.calls.argsFor(0)[0]).toEqual({
|
||||
...baseSuiteEvent,
|
||||
description: 'with a nested suite',
|
||||
status: 'passed',
|
||||
fullName: 'A Suite with a nested suite',
|
||||
parentSuiteId: suiteFullNameToId['A Suite'],
|
||||
duration: jasmine.any(Number)
|
||||
});
|
||||
|
||||
expect(reporter.suiteStarted.calls.argsFor(2)[0]).toEqual({
|
||||
...baseSuiteEvent,
|
||||
description: 'with only non-executable specs',
|
||||
fullName: 'A Suite with only non-executable specs',
|
||||
parentSuiteId: suiteFullNameToId['A Suite']
|
||||
});
|
||||
expect(reporter.suiteDone.calls.argsFor(1)[0]).toEqual({
|
||||
...baseSuiteEvent,
|
||||
description: 'with only non-executable specs',
|
||||
status: 'passed',
|
||||
fullName: 'A Suite with only non-executable specs',
|
||||
parentSuiteId: suiteFullNameToId['A Suite'],
|
||||
duration: jasmine.any(Number)
|
||||
});
|
||||
});
|
||||
|
||||
it('reports focused specs and suites as expected', async function() {
|
||||
|
||||
@@ -809,15 +809,17 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
describe('When the detectLateRejectionHandling config option is set', function() {
|
||||
describe('When the unhandled rejection event has a promise', function() {
|
||||
it('reports the rejection unless a corresponding rejection handled event occurs', async function() {
|
||||
function makeEvent(suffix) {
|
||||
const reason = `rejection ${suffix}`;
|
||||
const promise = Promise.reject(reason);
|
||||
promise.catch(() => {});
|
||||
return { reason, promise };
|
||||
}
|
||||
function makeEvent(suffix) {
|
||||
const reason = `rejection ${suffix}`;
|
||||
const promise = Promise.reject(reason);
|
||||
promise.catch(() => {});
|
||||
return { reason, promise };
|
||||
}
|
||||
|
||||
const global = {
|
||||
let global, reporter;
|
||||
|
||||
beforeEach(function() {
|
||||
global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
@@ -833,43 +835,132 @@ describe('Global error handling (integration)', function() {
|
||||
env.cleanup_();
|
||||
env = new jasmineUnderTest.Env();
|
||||
env.configure({ detectLateRejectionHandling: true });
|
||||
const reporter = jasmine.createSpyObj('fakeReporter', [
|
||||
|
||||
reporter = jasmine.createSpyObj('fakeReporter', [
|
||||
'specDone',
|
||||
'suiteDone'
|
||||
]);
|
||||
|
||||
env.addReporter(reporter);
|
||||
});
|
||||
|
||||
env.describe('A suite', function() {
|
||||
env.it('fails', function(specDone) {
|
||||
setTimeout(function() {
|
||||
const events = ['spec 1', 'spec 2'].map(makeEvent);
|
||||
describe('During spec execution', function() {
|
||||
it('reports the rejection unless a corresponding rejection handled event occurs', async function() {
|
||||
env.describe('A suite', function() {
|
||||
env.it('fails', function(specDone) {
|
||||
setTimeout(function() {
|
||||
const events = ['spec 1', 'spec 2'].map(makeEvent);
|
||||
|
||||
for (const e of events) {
|
||||
dispatchErrorEvent(global, 'unhandledrejection', e);
|
||||
}
|
||||
for (const e of events) {
|
||||
dispatchErrorEvent(global, 'unhandledrejection', e);
|
||||
}
|
||||
|
||||
dispatchErrorEvent(global, 'rejectionhandled', events[0]);
|
||||
specDone();
|
||||
dispatchErrorEvent(global, 'rejectionhandled', events[0]);
|
||||
specDone();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await env.execute();
|
||||
|
||||
expect(reporter.specDone).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
fullName: 'A suite fails',
|
||||
failedExpectations: [
|
||||
// Only the second rejection should be reported, since the first
|
||||
// one was eventually handled.
|
||||
jasmine.objectContaining({
|
||||
message:
|
||||
'Unhandled promise rejection: rejection spec 2 thrown'
|
||||
})
|
||||
]
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
await env.execute();
|
||||
describe('During beforeAll execution', function() {
|
||||
it('reports the rejection unless a corresponding rejection handled event occurs by the end of the beforeAll', async function() {
|
||||
env.describe('A suite', function() {
|
||||
let events;
|
||||
|
||||
expect(reporter.specDone).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
fullName: 'A suite fails',
|
||||
failedExpectations: [
|
||||
// Only the second rejection should be reported, since the first
|
||||
// one was eventually handled.
|
||||
jasmine.objectContaining({
|
||||
message:
|
||||
'Unhandled promise rejection: rejection spec 2 thrown'
|
||||
})
|
||||
]
|
||||
})
|
||||
);
|
||||
env.beforeAll(function(beforeAllDone) {
|
||||
setTimeout(function() {
|
||||
events = ['suite 1', 'suite 2'].map(makeEvent);
|
||||
|
||||
for (const e of events) {
|
||||
dispatchErrorEvent(global, 'unhandledrejection', e);
|
||||
}
|
||||
|
||||
dispatchErrorEvent(global, 'rejectionhandled', events[0]);
|
||||
beforeAllDone();
|
||||
});
|
||||
});
|
||||
|
||||
env.it('is a spec', function(specDone) {
|
||||
setTimeout(function() {
|
||||
// Should not prevent the second rejection from being reported
|
||||
dispatchErrorEvent(global, 'rejectionhandled', events[1]);
|
||||
specDone();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await env.execute();
|
||||
|
||||
expect(reporter.suiteDone).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
fullName: 'A suite',
|
||||
failedExpectations: [
|
||||
// Only the second rejection should be reported, since the first
|
||||
// one was eventually handled.
|
||||
jasmine.objectContaining({
|
||||
message:
|
||||
'Unhandled promise rejection: rejection suite 2 thrown'
|
||||
})
|
||||
]
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('During afterAll execution', function() {
|
||||
it('reports the rejection unless a corresponding rejection handled event occurs by the end of the afterAll', async function() {
|
||||
env.describe('A suite', function() {
|
||||
let events;
|
||||
|
||||
env.afterAll(function(beforeAllDone) {
|
||||
setTimeout(function() {
|
||||
events = ['suite 1', 'suite 2'].map(makeEvent);
|
||||
|
||||
for (const e of events) {
|
||||
dispatchErrorEvent(global, 'unhandledrejection', e);
|
||||
}
|
||||
|
||||
dispatchErrorEvent(global, 'rejectionhandled', events[0]);
|
||||
beforeAllDone();
|
||||
});
|
||||
});
|
||||
|
||||
env.it('is a spec', function() {});
|
||||
});
|
||||
|
||||
await env.execute();
|
||||
|
||||
expect(reporter.suiteDone).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
fullName: 'A suite',
|
||||
failedExpectations: [
|
||||
// Only the second rejection should be reported, since the first
|
||||
// one was eventually handled.
|
||||
jasmine.objectContaining({
|
||||
message:
|
||||
'Unhandled promise rejection: rejection suite 2 thrown'
|
||||
})
|
||||
]
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -920,6 +1011,43 @@ describe('Global error handling (integration)', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('works when the suite is run multiple times', async function() {
|
||||
const global = {
|
||||
...browserEventMethods(),
|
||||
setTimeout: function(fn, delay) {
|
||||
return setTimeout(fn, delay);
|
||||
},
|
||||
clearTimeout: function(fn, delay) {
|
||||
clearTimeout(fn, delay);
|
||||
},
|
||||
queueMicrotask: function(fn) {
|
||||
queueMicrotask(fn);
|
||||
}
|
||||
};
|
||||
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
|
||||
env.cleanup_();
|
||||
env = new jasmineUnderTest.Env();
|
||||
env.configure({ autoCleanClosures: false });
|
||||
const reporter = jasmine.createSpyObj('fakeReporter', ['specDone']);
|
||||
|
||||
env.addReporter(reporter);
|
||||
|
||||
env.it('fails', function(specDone) {
|
||||
setTimeout(function() {
|
||||
dispatchErrorEvent(global, 'error', { error: 'fail' });
|
||||
specDone();
|
||||
});
|
||||
});
|
||||
|
||||
await env.execute();
|
||||
reporter.specDone.calls.reset();
|
||||
await env.execute();
|
||||
|
||||
expect(reporter.specDone).toHaveFailedExpectationsForRunnable('fails', [
|
||||
'fail thrown'
|
||||
]);
|
||||
});
|
||||
|
||||
describe('#spyOnGlobalErrorsAsync', function() {
|
||||
const leftInstalledMessage =
|
||||
'Global error spy was not uninstalled. ' +
|
||||
|
||||
@@ -470,20 +470,28 @@ describe('Matchers (Integration)', function() {
|
||||
});
|
||||
|
||||
describe('toBeNullish', function() {
|
||||
verifyPasses(function(env) {
|
||||
env.expect(undefined).toBeNullish();
|
||||
describe('with undefined', function() {
|
||||
verifyPasses(function(env) {
|
||||
env.expect(undefined).toBeNullish();
|
||||
});
|
||||
});
|
||||
|
||||
verifyPasses(function(env) {
|
||||
env.expect(null).toBeNullish();
|
||||
describe('with null', function() {
|
||||
verifyPasses(function(env) {
|
||||
env.expect(null).toBeNullish();
|
||||
});
|
||||
});
|
||||
|
||||
verifyFails(function(env) {
|
||||
env.expect(1).toBeNullish();
|
||||
describe('with a truthy value', function() {
|
||||
verifyFails(function(env) {
|
||||
env.expect(1).toBeNullish();
|
||||
});
|
||||
});
|
||||
|
||||
verifyFails(function(env) {
|
||||
env.expect('').toBeNullish();
|
||||
describe('with a non-null falsy value', function() {
|
||||
verifyFails(function(env) {
|
||||
env.expect('').toBeNullish();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -665,13 +673,17 @@ describe('Matchers (Integration)', function() {
|
||||
env.expect(spyObj).toHaveSpyInteractions();
|
||||
});
|
||||
|
||||
verifyFails(function(env) {
|
||||
env.expect(spyObj).toHaveSpyInteractions();
|
||||
describe('with no methods called', function() {
|
||||
verifyFails(function(env) {
|
||||
env.expect(spyObj).toHaveSpyInteractions();
|
||||
});
|
||||
});
|
||||
|
||||
verifyFails(function(env) {
|
||||
spyObj.otherMethod();
|
||||
env.expect(spyObj).toHaveSpyInteractions();
|
||||
describe('with only non-spy methods called', function() {
|
||||
verifyFails(function(env) {
|
||||
spyObj.otherMethod();
|
||||
env.expect(spyObj).toHaveSpyInteractions();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -281,22 +281,44 @@ describe('toThrowError', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if thrown is a type of Error and the expected is a different Error', function() {
|
||||
const matchersUtil = {
|
||||
equals: jasmine.createSpy('delegated-equal').and.returnValue(false),
|
||||
pp: jasmineUnderTest.makePrettyPrinter()
|
||||
},
|
||||
matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil),
|
||||
fn = function() {
|
||||
throw new TypeError('foo');
|
||||
};
|
||||
describe('with a string', function() {
|
||||
it('fails if thrown is a type of Error and the expected is a different Error', function() {
|
||||
const matchersUtil = {
|
||||
equals: jasmine.createSpy('delegated-equal').and.returnValue(false),
|
||||
pp: jasmineUnderTest.makePrettyPrinter()
|
||||
},
|
||||
matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil),
|
||||
fn = function() {
|
||||
throw new TypeError('foo');
|
||||
};
|
||||
|
||||
const result = matcher.compare(fn, TypeError, 'bar');
|
||||
const result = matcher.compare(fn, TypeError, 'bar');
|
||||
|
||||
expect(result.pass).toBe(false);
|
||||
expect(result.message()).toEqual(
|
||||
"Expected function to throw TypeError with message 'bar', but it threw TypeError with message 'foo'."
|
||||
);
|
||||
expect(result.pass).toBe(false);
|
||||
expect(result.message()).toEqual(
|
||||
"Expected function to throw TypeError with message 'bar', but it threw TypeError with message 'foo'."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a regex', function() {
|
||||
it('fails if thrown is a type of Error and the expected is a different Error', function() {
|
||||
const matchersUtil = {
|
||||
equals: jasmine.createSpy('delegated-equal').and.returnValue(false),
|
||||
pp: jasmineUnderTest.makePrettyPrinter()
|
||||
},
|
||||
matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil),
|
||||
fn = function() {
|
||||
throw new TypeError('foo');
|
||||
};
|
||||
|
||||
const result = matcher.compare(fn, TypeError, /bar/);
|
||||
|
||||
expect(result.pass).toBe(false);
|
||||
expect(result.message()).toEqual(
|
||||
"Expected function to throw TypeError with a message matching /bar/, but it threw TypeError with message 'foo'."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('passes if thrown is a type of Error and has the same type as the expected Error and the message matches the expected message', function() {
|
||||
@@ -316,22 +338,4 @@ describe('toThrowError', function() {
|
||||
'Expected function not to throw TypeError with a message matching /foo/.'
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if thrown is a type of Error and the expected is a different Error', function() {
|
||||
const matchersUtil = {
|
||||
equals: jasmine.createSpy('delegated-equal').and.returnValue(false),
|
||||
pp: jasmineUnderTest.makePrettyPrinter()
|
||||
},
|
||||
matcher = jasmineUnderTest.matchers.toThrowError(matchersUtil),
|
||||
fn = function() {
|
||||
throw new TypeError('foo');
|
||||
};
|
||||
|
||||
const result = matcher.compare(fn, TypeError, /bar/);
|
||||
|
||||
expect(result.pass).toBe(false);
|
||||
expect(result.message()).toEqual(
|
||||
"Expected function to throw TypeError with a message matching /bar/, but it threw TypeError with message 'foo'."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
63
spec/html/HtmlExactSpecFilterSpec.js
Normal file
63
spec/html/HtmlExactSpecFilterSpec.js
Normal file
@@ -0,0 +1,63 @@
|
||||
describe('HtmlExactSpecFilter', function() {
|
||||
it('matches everything when no string is provided', function() {
|
||||
const specFilter = new jasmineUnderTest.HtmlExactSpecFilter({
|
||||
queryString: {
|
||||
getParam(name) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(specFilter.matches({})).toBeTrue();
|
||||
});
|
||||
|
||||
it('matches a spec with the exact same path', function() {
|
||||
const specFilter = new jasmineUnderTest.HtmlExactSpecFilter({
|
||||
queryString: {
|
||||
getParam(name) {
|
||||
if (name === 'spec') {
|
||||
return '["a","b","c"]';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(specFilter.matches(stubSpec(['a', 'b', 'c']))).toBeTrue();
|
||||
});
|
||||
|
||||
it('matches a spec whose path has the filter path as a prefix', function() {
|
||||
const specFilter = new jasmineUnderTest.HtmlExactSpecFilter({
|
||||
queryString: {
|
||||
getParam(name) {
|
||||
if (name === 'spec') {
|
||||
return '["a","b"]';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(specFilter.matches(stubSpec(['a', 'b', 'c']))).toBeTrue();
|
||||
});
|
||||
|
||||
it('does not match a spec with a different path', function() {
|
||||
const specFilter = new jasmineUnderTest.HtmlExactSpecFilter({
|
||||
queryString: {
|
||||
getParam(name) {
|
||||
if (name === 'spec') {
|
||||
return '["a","b","c"]';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(specFilter.matches(stubSpec(['a', 'd', 'c']))).toBeFalse();
|
||||
});
|
||||
|
||||
function stubSpec(path) {
|
||||
return {
|
||||
getPath() {
|
||||
return path;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -528,6 +528,7 @@ describe('HtmlReporter', function() {
|
||||
|
||||
let specResult = {
|
||||
id: 123,
|
||||
parentSuiteId: 1,
|
||||
description: 'with a spec',
|
||||
fullName: 'A Suite with a spec',
|
||||
status: 'passed',
|
||||
@@ -605,7 +606,9 @@ describe('HtmlReporter', function() {
|
||||
const suiteDetail = outerSuite.childNodes[0];
|
||||
const 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"]'
|
||||
);
|
||||
|
||||
const specs = outerSuite.childNodes[1];
|
||||
const spec = specs.childNodes[0];
|
||||
@@ -615,7 +618,7 @@ describe('HtmlReporter', function() {
|
||||
const 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"]'
|
||||
);
|
||||
|
||||
const specDuration = spec.childNodes[1];
|
||||
@@ -1365,6 +1368,11 @@ describe('HtmlReporter', function() {
|
||||
},
|
||||
createTextNode: function() {
|
||||
return document.createTextNode.apply(document, arguments);
|
||||
},
|
||||
queryString: {
|
||||
getParam(name) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
};
|
||||
specStatus = {
|
||||
@@ -1379,7 +1387,12 @@ describe('HtmlReporter', function() {
|
||||
|
||||
describe('when the specs are not filtered', function() {
|
||||
beforeEach(function() {
|
||||
reporterConfig.filterSpecs = false;
|
||||
reporterConfig.queryString.getParam = function(name) {
|
||||
if (name !== 'spec') {
|
||||
throw new Error('Unexpected query param ' + name);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
reporter = new jasmineUnderTest.HtmlReporter(reporterConfig);
|
||||
reporter.initialize();
|
||||
reporter.jasmineStarted({ totalSpecsDefined: 1 });
|
||||
@@ -1397,7 +1410,12 @@ describe('HtmlReporter', function() {
|
||||
|
||||
describe('when the specs are filtered', function() {
|
||||
beforeEach(function() {
|
||||
reporterConfig.filterSpecs = true;
|
||||
reporterConfig.queryString.getParam = function(name) {
|
||||
if (name !== 'spec') {
|
||||
throw new Error('Unexpected query param ' + name);
|
||||
}
|
||||
return 'not the empty string';
|
||||
};
|
||||
reporter = new jasmineUnderTest.HtmlReporter(reporterConfig);
|
||||
reporter.initialize();
|
||||
reporter.jasmineStarted({ totalSpecsDefined: 1 });
|
||||
@@ -1541,6 +1559,7 @@ describe('HtmlReporter', function() {
|
||||
|
||||
const failingSpecResult = {
|
||||
id: 124,
|
||||
parentSuiteId: 2,
|
||||
status: 'failed',
|
||||
description: 'a failing spec',
|
||||
fullName: 'a suite inner suite a failing spec',
|
||||
@@ -1664,16 +1683,18 @@ describe('HtmlReporter', function() {
|
||||
expect(links.length).toEqual(3);
|
||||
expect(links[0].textContent).toEqual('A suite');
|
||||
|
||||
expect(links[0].getAttribute('href')).toMatch(/\?foo=bar&spec=A suite/);
|
||||
expect(links[0].getAttribute('href')).toEqual(
|
||||
'/?foo=bar&spec=["A suite"]'
|
||||
);
|
||||
|
||||
expect(links[1].textContent).toEqual('inner suite');
|
||||
expect(links[1].getAttribute('href')).toMatch(
|
||||
/\?foo=bar&spec=A suite inner suite/
|
||||
expect(links[1].getAttribute('href')).toEqual(
|
||||
'/?foo=bar&spec=["A suite","inner suite"]'
|
||||
);
|
||||
|
||||
expect(links[2].textContent).toEqual('a failing spec');
|
||||
expect(links[2].getAttribute('href')).toMatch(
|
||||
/\?foo=bar&spec=a suite inner suite a failing spec/
|
||||
expect(links[2].getAttribute('href')).toEqual(
|
||||
'/?foo=bar&spec=["A suite","inner suite","a failing spec"]'
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
describe('jasmineUnderTest.HtmlSpecFilter', function() {
|
||||
describe('HtmlSpecFilter', function() {
|
||||
it('should match when no string is provided', function() {
|
||||
const specFilter = new jasmineUnderTest.HtmlSpecFilter();
|
||||
|
||||
@@ -16,4 +16,17 @@ describe('jasmineUnderTest.HtmlSpecFilter', function() {
|
||||
expect(specFilter.matches('foo')).toBe(true);
|
||||
expect(specFilter.matches('bar')).toBe(false);
|
||||
});
|
||||
|
||||
it('copes with HtmlExactSpecFilterV2 filter strings', function() {
|
||||
const specFilter = new jasmineUnderTest.HtmlSpecFilter({
|
||||
filterString: function() {
|
||||
return '["foo","bar"]';
|
||||
}
|
||||
});
|
||||
|
||||
expect(specFilter.matches('foo bar')).toBe(true);
|
||||
expect(specFilter.matches('baz foo bar qux')).toBe(true);
|
||||
expect(specFilter.matches('foo')).toBe(false);
|
||||
expect(specFilter.matches('bar')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,9 @@ module.exports = {
|
||||
'helpers/defineJasmineUnderTest.js',
|
||||
'helpers/resetEnv.js'
|
||||
],
|
||||
env: {
|
||||
forbidDuplicateNames: true
|
||||
},
|
||||
random: true,
|
||||
browser: {
|
||||
name: process.env.JASMINE_BROWSER || 'firefox',
|
||||
|
||||
@@ -12,5 +12,8 @@
|
||||
"helpers/nodeDefineJasmineUnderTest.js",
|
||||
"helpers/resetEnv.js"
|
||||
],
|
||||
"env": {
|
||||
"forbidDuplicateNames": true
|
||||
},
|
||||
"random": true
|
||||
}
|
||||
|
||||
@@ -14,20 +14,12 @@
|
||||
(function() {
|
||||
const env = jasmine.getEnv();
|
||||
|
||||
/**
|
||||
* ## Runner Parameters
|
||||
*
|
||||
* More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
|
||||
*/
|
||||
|
||||
const queryString = new jasmine.QueryString({
|
||||
getWindowLocation: function() {
|
||||
return window.location;
|
||||
}
|
||||
});
|
||||
|
||||
const filterSpecs = !!queryString.getParam('spec');
|
||||
|
||||
const config = {
|
||||
stopOnSpecFailure: queryString.getParam('stopOnSpecFailure'),
|
||||
stopSpecOnExpectationFailure: queryString.getParam(
|
||||
@@ -69,7 +61,7 @@
|
||||
return document.createTextNode.apply(document, arguments);
|
||||
},
|
||||
timer: new jasmine.Timer(),
|
||||
filterSpecs: filterSpecs
|
||||
queryString
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -81,14 +73,9 @@
|
||||
/**
|
||||
* Filter which specs will be run by matching the start of the full name against the `spec` query param.
|
||||
*/
|
||||
const specFilter = new jasmine.HtmlSpecFilter({
|
||||
filterString: function() {
|
||||
return queryString.getParam('spec');
|
||||
}
|
||||
});
|
||||
|
||||
const specFilter = new jasmine.HtmlExactSpecFilter({ queryString });
|
||||
config.specFilter = function(spec) {
|
||||
return specFilter.matches(spec.getFullName());
|
||||
return specFilter.matches(spec);
|
||||
};
|
||||
|
||||
env.configure(config);
|
||||
|
||||
186
src/core/Configuration.js
Normal file
186
src/core/Configuration.js
Normal file
@@ -0,0 +1,186 @@
|
||||
getJasmineRequireObj().Configuration = function(j$) {
|
||||
/**
|
||||
* This represents the available options to configure Jasmine.
|
||||
* Options that are not provided will use their default values.
|
||||
* @see Env#configure
|
||||
* @interface Configuration
|
||||
* @since 3.3.0
|
||||
*/
|
||||
const defaultConfig = {
|
||||
/**
|
||||
* Whether to randomize spec execution order
|
||||
* @name Configuration#random
|
||||
* @since 3.3.0
|
||||
* @type Boolean
|
||||
* @default true
|
||||
*/
|
||||
random: true,
|
||||
/**
|
||||
* Seed to use as the basis of randomization.
|
||||
* Null causes the seed to be determined randomly at the start of execution.
|
||||
* @name Configuration#seed
|
||||
* @since 3.3.0
|
||||
* @type (number|string)
|
||||
* @default null
|
||||
*/
|
||||
seed: null,
|
||||
/**
|
||||
* Whether to stop execution of the suite after the first spec failure
|
||||
*
|
||||
* <p>In parallel mode, `stopOnSpecFailure` works on a "best effort"
|
||||
* basis. Jasmine will stop execution as soon as practical after a failure
|
||||
* but it might not be immediate.</p>
|
||||
* @name Configuration#stopOnSpecFailure
|
||||
* @since 3.9.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
stopOnSpecFailure: false,
|
||||
/**
|
||||
* Whether to fail the spec if it ran no expectations. By default
|
||||
* a spec that ran no expectations is reported as passed. Setting this
|
||||
* to true will report such spec as a failure.
|
||||
* @name Configuration#failSpecWithNoExpectations
|
||||
* @since 3.5.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
failSpecWithNoExpectations: false,
|
||||
/**
|
||||
* Whether to cause specs to only have one expectation failure.
|
||||
* @name Configuration#stopSpecOnExpectationFailure
|
||||
* @since 3.3.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
stopSpecOnExpectationFailure: false,
|
||||
/**
|
||||
* A function that takes a spec and returns true if it should be executed
|
||||
* or false if it should be skipped.
|
||||
* @callback SpecFilter
|
||||
* @param {Spec} spec - The spec that the filter is being applied to.
|
||||
* @return boolean
|
||||
*/
|
||||
/**
|
||||
* Function to use to filter specs
|
||||
* @name Configuration#specFilter
|
||||
* @since 3.3.0
|
||||
* @type SpecFilter
|
||||
* @default A function that always returns true.
|
||||
*/
|
||||
specFilter: function() {
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
* Whether reporters should hide disabled specs from their output.
|
||||
* Currently only supported by Jasmine's HTMLReporter
|
||||
* @name Configuration#hideDisabled
|
||||
* @since 3.3.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
hideDisabled: false,
|
||||
/**
|
||||
* Clean closures when a suite is done running (done by clearing the stored function reference).
|
||||
* This prevents memory leaks, but you won't be able to run jasmine multiple times.
|
||||
* @name Configuration#autoCleanClosures
|
||||
* @since 3.10.0
|
||||
* @type boolean
|
||||
* @default true
|
||||
*/
|
||||
autoCleanClosures: true,
|
||||
/**
|
||||
* Whether to forbid duplicate spec or suite names. If set to true, using
|
||||
* the same name multiple times in the same immediate parent suite is an
|
||||
* error.
|
||||
* @name Configuration#forbidDuplicateNames
|
||||
* @type boolean
|
||||
* @default false
|
||||
*/
|
||||
forbidDuplicateNames: false,
|
||||
/**
|
||||
* Whether to issue warnings for certain deprecated functionality
|
||||
* every time it's used. If not set or set to false, deprecation warnings
|
||||
* for methods that tend to be called frequently will be issued only once
|
||||
* or otherwise throttled to prevent the suite output from being flooded
|
||||
* with warnings.
|
||||
* @name Configuration#verboseDeprecations
|
||||
* @since 3.6.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
verboseDeprecations: false,
|
||||
|
||||
/**
|
||||
* Whether to detect late promise rejection handling during spec
|
||||
* execution. If this option is enabled, a promise rejection that triggers
|
||||
* the JavaScript runtime's unhandled rejection event will not be treated
|
||||
* as an error as long as it's handled before the spec finishes.
|
||||
*
|
||||
* This option is off by default because it imposes a performance penalty.
|
||||
* @name Configuration#detectLateRejectionHandling
|
||||
* @since 5.10.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
detectLateRejectionHandling: false
|
||||
};
|
||||
Object.freeze(defaultConfig);
|
||||
|
||||
class Configuration {
|
||||
#values;
|
||||
|
||||
constructor() {
|
||||
this.#values = { ...defaultConfig };
|
||||
|
||||
for (const k of Object.keys(defaultConfig)) {
|
||||
Object.defineProperty(this, k, {
|
||||
enumerable: true,
|
||||
get() {
|
||||
return this.#values[k];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
copy() {
|
||||
return { ...this.#values };
|
||||
}
|
||||
|
||||
update(changes) {
|
||||
const booleanProps = [
|
||||
'random',
|
||||
'failSpecWithNoExpectations',
|
||||
'hideDisabled',
|
||||
'stopOnSpecFailure',
|
||||
'stopSpecOnExpectationFailure',
|
||||
'autoCleanClosures',
|
||||
'forbidDuplicateNames',
|
||||
'detectLateRejectionHandling'
|
||||
];
|
||||
|
||||
for (const k of booleanProps) {
|
||||
if (typeof changes[k] !== 'undefined') {
|
||||
this.#values[k] = changes[k];
|
||||
}
|
||||
}
|
||||
|
||||
if (changes.specFilter) {
|
||||
this.#values.specFilter = changes.specFilter;
|
||||
}
|
||||
|
||||
// 0 and null are valid values, so a truthiness check wouldn't work
|
||||
if (typeof changes.seed !== 'undefined') {
|
||||
this.#values.seed = changes.seed;
|
||||
}
|
||||
|
||||
// TODO: in the next major release, make verboseDeprecations work like
|
||||
// other boolean properties.
|
||||
if (changes.hasOwnProperty('verboseDeprecations')) {
|
||||
this.#values.verboseDeprecations = changes.verboseDeprecations;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Configuration;
|
||||
};
|
||||
199
src/core/Env.js
199
src/core/Env.js
@@ -7,12 +7,12 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
* calling {@link jasmine.getEnv}.
|
||||
* @hideconstructor
|
||||
*/
|
||||
function Env(options) {
|
||||
options = options || {};
|
||||
function Env(envOptions) {
|
||||
envOptions = envOptions || {};
|
||||
|
||||
const self = this;
|
||||
const GlobalErrors = options.GlobalErrors || j$.GlobalErrors;
|
||||
const global = options.global || j$.getGlobal();
|
||||
const GlobalErrors = envOptions.GlobalErrors || j$.GlobalErrors;
|
||||
const global = envOptions.global || j$.getGlobal();
|
||||
|
||||
const realSetTimeout = global.setTimeout;
|
||||
const realClearTimeout = global.clearTimeout;
|
||||
@@ -31,12 +31,21 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
// before it's set to detect load-time errors in browsers
|
||||
() => this.configuration()
|
||||
);
|
||||
const installGlobalErrors = (function() {
|
||||
const { installGlobalErrors, uninstallGlobalErrors } = (function() {
|
||||
let installed = false;
|
||||
return function() {
|
||||
if (!installed) {
|
||||
globalErrors.install();
|
||||
installed = true;
|
||||
|
||||
return {
|
||||
installGlobalErrors() {
|
||||
if (!installed) {
|
||||
globalErrors.install();
|
||||
installed = true;
|
||||
}
|
||||
},
|
||||
uninstallGlobalErrors() {
|
||||
if (installed) {
|
||||
globalErrors.uninstall();
|
||||
installed = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
@@ -54,134 +63,9 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
let runner;
|
||||
let parallelLoadingState = null; // 'specs', 'helpers', or null for non-parallel
|
||||
|
||||
/**
|
||||
* This represents the available options to configure Jasmine.
|
||||
* Options that are not provided will use their default values.
|
||||
* @see Env#configure
|
||||
* @interface Configuration
|
||||
* @since 3.3.0
|
||||
*/
|
||||
const config = {
|
||||
/**
|
||||
* Whether to randomize spec execution order
|
||||
* @name Configuration#random
|
||||
* @since 3.3.0
|
||||
* @type Boolean
|
||||
* @default true
|
||||
*/
|
||||
random: true,
|
||||
/**
|
||||
* Seed to use as the basis of randomization.
|
||||
* Null causes the seed to be determined randomly at the start of execution.
|
||||
* @name Configuration#seed
|
||||
* @since 3.3.0
|
||||
* @type (number|string)
|
||||
* @default null
|
||||
*/
|
||||
seed: null,
|
||||
/**
|
||||
* Whether to stop execution of the suite after the first spec failure
|
||||
*
|
||||
* <p>In parallel mode, `stopOnSpecFailure` works on a "best effort"
|
||||
* basis. Jasmine will stop execution as soon as practical after a failure
|
||||
* but it might not be immediate.</p>
|
||||
* @name Configuration#stopOnSpecFailure
|
||||
* @since 3.9.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
stopOnSpecFailure: false,
|
||||
/**
|
||||
* Whether to fail the spec if it ran no expectations. By default
|
||||
* a spec that ran no expectations is reported as passed. Setting this
|
||||
* to true will report such spec as a failure.
|
||||
* @name Configuration#failSpecWithNoExpectations
|
||||
* @since 3.5.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
failSpecWithNoExpectations: false,
|
||||
/**
|
||||
* Whether to cause specs to only have one expectation failure.
|
||||
* @name Configuration#stopSpecOnExpectationFailure
|
||||
* @since 3.3.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
stopSpecOnExpectationFailure: false,
|
||||
/**
|
||||
* A function that takes a spec and returns true if it should be executed
|
||||
* or false if it should be skipped.
|
||||
* @callback SpecFilter
|
||||
* @param {Spec} spec - The spec that the filter is being applied to.
|
||||
* @return boolean
|
||||
*/
|
||||
/**
|
||||
* Function to use to filter specs
|
||||
* @name Configuration#specFilter
|
||||
* @since 3.3.0
|
||||
* @type SpecFilter
|
||||
* @default A function that always returns true.
|
||||
*/
|
||||
specFilter: function() {
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
* Whether reporters should hide disabled specs from their output.
|
||||
* Currently only supported by Jasmine's HTMLReporter
|
||||
* @name Configuration#hideDisabled
|
||||
* @since 3.3.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
hideDisabled: false,
|
||||
/**
|
||||
* Clean closures when a suite is done running (done by clearing the stored function reference).
|
||||
* This prevents memory leaks, but you won't be able to run jasmine multiple times.
|
||||
* @name Configuration#autoCleanClosures
|
||||
* @since 3.10.0
|
||||
* @type boolean
|
||||
* @default true
|
||||
*/
|
||||
autoCleanClosures: true,
|
||||
/**
|
||||
* Whether to forbid duplicate spec or suite names. If set to true, using
|
||||
* the same name multiple times in the same immediate parent suite is an
|
||||
* error.
|
||||
* @name Configuration#forbidDuplicateNames
|
||||
* @type boolean
|
||||
* @default false
|
||||
*/
|
||||
forbidDuplicateNames: false,
|
||||
/**
|
||||
* Whether to issue warnings for certain deprecated functionality
|
||||
* every time it's used. If not set or set to false, deprecation warnings
|
||||
* for methods that tend to be called frequently will be issued only once
|
||||
* or otherwise throttled to prevent the suite output from being flooded
|
||||
* with warnings.
|
||||
* @name Configuration#verboseDeprecations
|
||||
* @since 3.6.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
verboseDeprecations: false,
|
||||
const config = new j$.Configuration();
|
||||
|
||||
/**
|
||||
* Whether to detect late promise rejection handling during spec
|
||||
* execution. If this option is enabled, a promise rejection that triggers
|
||||
* the JavaScript runtime's unhandled rejection event will not be treated
|
||||
* as an error as long as it's handled before the spec finishes.
|
||||
*
|
||||
* This option is off by default because it imposes a performance penalty.
|
||||
* @name Configuration#detectLateRejectionHandling
|
||||
* @since 5.10.0
|
||||
* @type Boolean
|
||||
* @default false
|
||||
*/
|
||||
detectLateRejectionHandling: false
|
||||
};
|
||||
|
||||
if (!options.suppressLoadErrors) {
|
||||
if (!envOptions.suppressLoadErrors) {
|
||||
installGlobalErrors();
|
||||
globalErrors.pushListener(function loadtimeErrorHandler(error, event) {
|
||||
topSuite.result.failedExpectations.push({
|
||||
@@ -202,42 +86,15 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
* @argument {Configuration} configuration
|
||||
* @function
|
||||
*/
|
||||
this.configure = function(configuration) {
|
||||
this.configure = function(changes) {
|
||||
if (parallelLoadingState) {
|
||||
throw new Error(
|
||||
'Jasmine cannot be configured via Env in parallel mode'
|
||||
);
|
||||
}
|
||||
|
||||
const booleanProps = [
|
||||
'random',
|
||||
'failSpecWithNoExpectations',
|
||||
'hideDisabled',
|
||||
'stopOnSpecFailure',
|
||||
'stopSpecOnExpectationFailure',
|
||||
'autoCleanClosures',
|
||||
'forbidDuplicateNames',
|
||||
'detectLateRejectionHandling'
|
||||
];
|
||||
|
||||
booleanProps.forEach(function(prop) {
|
||||
if (typeof configuration[prop] !== 'undefined') {
|
||||
config[prop] = !!configuration[prop];
|
||||
}
|
||||
});
|
||||
|
||||
if (configuration.specFilter) {
|
||||
config.specFilter = configuration.specFilter;
|
||||
}
|
||||
|
||||
if (typeof configuration.seed !== 'undefined') {
|
||||
config.seed = configuration.seed;
|
||||
}
|
||||
|
||||
if (configuration.hasOwnProperty('verboseDeprecations')) {
|
||||
config.verboseDeprecations = configuration.verboseDeprecations;
|
||||
deprecator.verboseDeprecations(config.verboseDeprecations);
|
||||
}
|
||||
config.update(changes);
|
||||
deprecator.verboseDeprecations(config.verboseDeprecations);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -248,11 +105,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
* @returns {Configuration}
|
||||
*/
|
||||
this.configuration = function() {
|
||||
const result = {};
|
||||
for (const property in config) {
|
||||
result[property] = config[property];
|
||||
}
|
||||
return result;
|
||||
return config.copy();
|
||||
};
|
||||
|
||||
this.setDefaultSpyStrategy = function(defaultStrategyFn) {
|
||||
@@ -960,9 +813,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
};
|
||||
|
||||
this.cleanup_ = function() {
|
||||
if (globalErrors) {
|
||||
globalErrors.uninstall();
|
||||
}
|
||||
uninstallGlobalErrors();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -103,6 +103,12 @@ getJasmineRequireObj().Runner = function(j$) {
|
||||
// In parallel mode, the jasmineStarted event is separately dispatched
|
||||
// by jasmine-npm. This event only reaches reporters in non-parallel.
|
||||
totalSpecsDefined,
|
||||
/**
|
||||
* Information about the ordering (random or not) of this execution of the suite.
|
||||
* @typedef Order
|
||||
* @property {boolean} random - Whether the suite is running in random order
|
||||
* @property {string} seed - The random seed
|
||||
*/
|
||||
order: order,
|
||||
parallel: false
|
||||
});
|
||||
|
||||
494
src/core/Spec.js
494
src/core/Spec.js
@@ -1,274 +1,249 @@
|
||||
getJasmineRequireObj().Spec = function(j$) {
|
||||
function Spec(attrs) {
|
||||
this.expectationFactory = attrs.expectationFactory;
|
||||
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
|
||||
this.id = attrs.id;
|
||||
this.filename = attrs.filename;
|
||||
this.parentSuiteId = attrs.parentSuiteId;
|
||||
this.description = attrs.description || '';
|
||||
this.queueableFn = attrs.queueableFn;
|
||||
this.beforeAndAfterFns =
|
||||
attrs.beforeAndAfterFns ||
|
||||
function() {
|
||||
return { befores: [], afters: [] };
|
||||
};
|
||||
this.userContext =
|
||||
attrs.userContext ||
|
||||
function() {
|
||||
return {};
|
||||
};
|
||||
this.autoCleanClosures =
|
||||
attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures;
|
||||
class Spec {
|
||||
#autoCleanClosures;
|
||||
#throwOnExpectationFailure;
|
||||
#timer;
|
||||
#metadata;
|
||||
|
||||
this.getPath = function() {
|
||||
return attrs.getPath ? attrs.getPath(this) : [];
|
||||
};
|
||||
|
||||
this.onLateError = attrs.onLateError || function() {};
|
||||
this.catchingExceptions =
|
||||
attrs.catchingExceptions ||
|
||||
function() {
|
||||
return true;
|
||||
constructor(attrs) {
|
||||
this.expectationFactory = attrs.expectationFactory;
|
||||
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
|
||||
this.id = attrs.id;
|
||||
this.filename = attrs.filename;
|
||||
this.parentSuiteId = attrs.parentSuiteId;
|
||||
this.description = attrs.description || '';
|
||||
this.queueableFn = attrs.queueableFn;
|
||||
this.beforeAndAfterFns =
|
||||
attrs.beforeAndAfterFns ||
|
||||
function() {
|
||||
return { befores: [], afters: [] };
|
||||
};
|
||||
this.userContext =
|
||||
attrs.userContext ||
|
||||
function() {
|
||||
return {};
|
||||
};
|
||||
this.getPath = function() {
|
||||
return attrs.getPath ? attrs.getPath(this) : [];
|
||||
};
|
||||
this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
|
||||
this.timer = attrs.timer || new j$.Timer();
|
||||
|
||||
if (!this.queueableFn.fn) {
|
||||
this.exclude();
|
||||
this.#autoCleanClosures =
|
||||
attrs.autoCleanClosures === undefined
|
||||
? true
|
||||
: !!attrs.autoCleanClosures;
|
||||
this.onLateError = attrs.onLateError || function() {};
|
||||
this.#throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
|
||||
this.#timer = attrs.timer || new j$.Timer();
|
||||
|
||||
if (!this.queueableFn.fn) {
|
||||
this.exclude();
|
||||
}
|
||||
|
||||
this.reset();
|
||||
}
|
||||
|
||||
this.reset();
|
||||
}
|
||||
addExpectationResult(passed, data, isError) {
|
||||
const expectationResult = j$.buildExpectationResult(data);
|
||||
|
||||
Spec.prototype.addExpectationResult = function(passed, data, isError) {
|
||||
const expectationResult = j$.buildExpectationResult(data);
|
||||
|
||||
if (passed) {
|
||||
this.result.passedExpectations.push(expectationResult);
|
||||
} else {
|
||||
if (this.reportedDone) {
|
||||
this.onLateError(expectationResult);
|
||||
if (passed) {
|
||||
this.result.passedExpectations.push(expectationResult);
|
||||
} else {
|
||||
this.result.failedExpectations.push(expectationResult);
|
||||
if (this.reportedDone) {
|
||||
this.onLateError(expectationResult);
|
||||
} else {
|
||||
this.result.failedExpectations.push(expectationResult);
|
||||
|
||||
// TODO: refactor so that we don't need to override cached status
|
||||
if (this.result.status) {
|
||||
this.result.status = 'failed';
|
||||
// TODO: refactor so that we don't need to override cached status
|
||||
if (this.result.status) {
|
||||
this.result.status = 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
if (this.#throwOnExpectationFailure && !isError) {
|
||||
throw new j$.errors.ExpectationFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.throwOnExpectationFailure && !isError) {
|
||||
throw new j$.errors.ExpectationFailed();
|
||||
getSpecProperty(key) {
|
||||
this.result.properties = this.result.properties || {};
|
||||
return this.result.properties[key];
|
||||
}
|
||||
|
||||
setSpecProperty(key, value) {
|
||||
this.result.properties = this.result.properties || {};
|
||||
this.result.properties[key] = value;
|
||||
}
|
||||
|
||||
executionStarted() {
|
||||
this.#timer.start();
|
||||
}
|
||||
|
||||
executionFinished(excluded, failSpecWithNoExp) {
|
||||
if (this.#autoCleanClosures) {
|
||||
this.queueableFn.fn = null;
|
||||
}
|
||||
|
||||
this.result.status = this.#status(excluded, failSpecWithNoExp);
|
||||
this.result.duration = this.#timer.elapsed();
|
||||
|
||||
if (this.result.status !== 'failed') {
|
||||
this.result.debugLogs = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Spec.prototype.getSpecProperty = function(key) {
|
||||
this.result.properties = this.result.properties || {};
|
||||
return this.result.properties[key];
|
||||
};
|
||||
|
||||
Spec.prototype.setSpecProperty = function(key, value) {
|
||||
this.result.properties = this.result.properties || {};
|
||||
this.result.properties[key] = value;
|
||||
};
|
||||
|
||||
Spec.prototype.executionStarted = function() {
|
||||
this.timer.start();
|
||||
};
|
||||
|
||||
Spec.prototype.executionFinished = function(excluded, failSpecWithNoExp) {
|
||||
if (this.autoCleanClosures) {
|
||||
this.queueableFn.fn = null;
|
||||
reset() {
|
||||
/**
|
||||
* @typedef SpecResult
|
||||
* @property {String} id - The unique id of this spec.
|
||||
* @property {String} description - The description passed to the {@link it} that created this spec.
|
||||
* @property {String} fullName - The full description including all ancestors of this spec.
|
||||
* @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe().
|
||||
* @property {String} filename - Deprecated. The name of the file the spec was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `it`/`fit`/`xit` have been replaced with versions that don't maintain the
|
||||
* same call stack height as the originals. This property may be removed in
|
||||
* a future version unless there is enough user interest in keeping it.
|
||||
* See {@link https://github.com/jasmine/jasmine/issues/2065}.
|
||||
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec.
|
||||
* @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec.
|
||||
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec.
|
||||
* @property {String} pendingReason - If the spec is {@link pending}, this will be the reason.
|
||||
* @property {String} status - Once the spec has completed, this string represents the pass/fail status of this spec.
|
||||
* @property {number} duration - The time in ms used by the spec execution, including any before/afterEach.
|
||||
* @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSpecProperty}
|
||||
* @property {DebugLogEntry[]|null} debugLogs - Messages, if any, that were logged using {@link jasmine.debugLog} during a failing spec.
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.result = {
|
||||
id: this.id,
|
||||
description: this.description,
|
||||
fullName: this.getFullName(),
|
||||
parentSuiteId: this.parentSuiteId,
|
||||
filename: this.filename,
|
||||
failedExpectations: [],
|
||||
passedExpectations: [],
|
||||
deprecationWarnings: [],
|
||||
pendingReason: this.excludeMessage || '',
|
||||
duration: null,
|
||||
properties: null,
|
||||
debugLogs: null
|
||||
};
|
||||
this.markedPending = this.markedExcluding;
|
||||
this.reportedDone = false;
|
||||
}
|
||||
|
||||
this.result.status = this.status(excluded, failSpecWithNoExp);
|
||||
this.result.duration = this.timer.elapsed();
|
||||
handleException(e) {
|
||||
if (Spec.isPendingSpecException(e)) {
|
||||
this.pend(extractCustomPendingMessage(e));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.result.status !== 'failed') {
|
||||
this.result.debugLogs = null;
|
||||
if (e instanceof j$.errors.ExpectationFailed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addExpectationResult(
|
||||
false,
|
||||
{
|
||||
matcherName: '',
|
||||
passed: false,
|
||||
expected: '',
|
||||
actual: '',
|
||||
error: e
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
pend(message) {
|
||||
this.markedPending = true;
|
||||
if (message) {
|
||||
this.result.pendingReason = message;
|
||||
}
|
||||
}
|
||||
|
||||
// Like pend(), but pending state will survive reset().
|
||||
// Useful for fit, xit, where pending state remains.
|
||||
exclude(message) {
|
||||
this.markedExcluding = true;
|
||||
if (this.message) {
|
||||
this.excludeMessage = message;
|
||||
}
|
||||
this.pend(message);
|
||||
}
|
||||
|
||||
// TODO: ensure that all access to result goes through .getResult()
|
||||
// so that the status is correct.
|
||||
getResult() {
|
||||
this.result.status = this.#status();
|
||||
return this.result;
|
||||
}
|
||||
|
||||
#status(excluded, failSpecWithNoExpectations) {
|
||||
if (excluded === true) {
|
||||
return 'excluded';
|
||||
}
|
||||
|
||||
if (this.markedPending) {
|
||||
return 'pending';
|
||||
}
|
||||
|
||||
if (
|
||||
this.result.failedExpectations.length > 0 ||
|
||||
(failSpecWithNoExpectations &&
|
||||
this.result.failedExpectations.length +
|
||||
this.result.passedExpectations.length ===
|
||||
0)
|
||||
) {
|
||||
return 'failed';
|
||||
}
|
||||
|
||||
return 'passed';
|
||||
}
|
||||
|
||||
getFullName() {
|
||||
return this.getPath().join(' ');
|
||||
}
|
||||
|
||||
addDeprecationWarning(deprecation) {
|
||||
if (typeof deprecation === 'string') {
|
||||
deprecation = { message: deprecation };
|
||||
}
|
||||
this.result.deprecationWarnings.push(
|
||||
j$.buildExpectationResult(deprecation)
|
||||
);
|
||||
}
|
||||
|
||||
debugLog(msg) {
|
||||
if (!this.result.debugLogs) {
|
||||
this.result.debugLogs = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef DebugLogEntry
|
||||
* @property {String} message - The message that was passed to {@link jasmine.debugLog}.
|
||||
* @property {number} timestamp - The time when the entry was added, in
|
||||
* milliseconds from the spec's start time
|
||||
*/
|
||||
this.result.debugLogs.push({
|
||||
message: msg,
|
||||
timestamp: this.#timer.elapsed()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Spec.prototype.reset = function() {
|
||||
/**
|
||||
* @typedef SpecResult
|
||||
* @property {String} id - The unique id of this spec.
|
||||
* @property {String} description - The description passed to the {@link it} that created this spec.
|
||||
* @property {String} fullName - The full description including all ancestors of this spec.
|
||||
* @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe().
|
||||
* @property {String} filename - Deprecated. The name of the file the spec was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `it`/`fit`/`xit` have been replaced with versions that don't maintain the
|
||||
* same call stack height as the originals. This property may be removed in
|
||||
* a future version unless there is enough user interest in keeping it.
|
||||
* See {@link https://github.com/jasmine/jasmine/issues/2065}.
|
||||
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed during execution of this spec.
|
||||
* @property {ExpectationResult[]} passedExpectations - The list of expectations that passed during execution of this spec.
|
||||
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred during execution this spec.
|
||||
* @property {String} pendingReason - If the spec is {@link pending}, this will be the reason.
|
||||
* @property {String} status - Once the spec has completed, this string represents the pass/fail status of this spec.
|
||||
* @property {number} duration - The time in ms used by the spec execution, including any before/afterEach.
|
||||
* @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSpecProperty}
|
||||
* @property {DebugLogEntry[]|null} debugLogs - Messages, if any, that were logged using {@link jasmine.debugLog} during a failing spec.
|
||||
* @interface Spec
|
||||
* @see Configuration#specFilter
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.result = {
|
||||
id: this.id,
|
||||
description: this.description,
|
||||
fullName: this.getFullName(),
|
||||
parentSuiteId: this.parentSuiteId,
|
||||
filename: this.filename,
|
||||
failedExpectations: [],
|
||||
passedExpectations: [],
|
||||
deprecationWarnings: [],
|
||||
pendingReason: this.excludeMessage || '',
|
||||
duration: null,
|
||||
properties: null,
|
||||
debugLogs: null
|
||||
};
|
||||
this.markedPending = this.markedExcluding;
|
||||
this.reportedDone = false;
|
||||
};
|
||||
|
||||
Spec.prototype.handleException = function handleException(e) {
|
||||
if (Spec.isPendingSpecException(e)) {
|
||||
this.pend(extractCustomPendingMessage(e));
|
||||
return;
|
||||
}
|
||||
|
||||
if (e instanceof j$.errors.ExpectationFailed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addExpectationResult(
|
||||
false,
|
||||
{
|
||||
matcherName: '',
|
||||
passed: false,
|
||||
expected: '',
|
||||
actual: '',
|
||||
error: e
|
||||
},
|
||||
true
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* Marks state as pending
|
||||
* @param {string} [message] An optional reason message
|
||||
*/
|
||||
Spec.prototype.pend = function(message) {
|
||||
this.markedPending = true;
|
||||
if (message) {
|
||||
this.result.pendingReason = message;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Like {@link Spec#pend}, but pending state will survive {@link Spec#reset}
|
||||
* Useful for fit, xit, where pending state remains.
|
||||
* @param {string} [message] An optional reason message
|
||||
*/
|
||||
Spec.prototype.exclude = function(message) {
|
||||
this.markedExcluding = true;
|
||||
if (this.message) {
|
||||
this.excludeMessage = message;
|
||||
}
|
||||
this.pend(message);
|
||||
};
|
||||
|
||||
// TODO: ensure that all access to result goes through .getResult()
|
||||
// so that the status is correct.
|
||||
Spec.prototype.getResult = function() {
|
||||
this.result.status = this.status();
|
||||
return this.result;
|
||||
};
|
||||
|
||||
Spec.prototype.status = function(excluded, failSpecWithNoExpectations) {
|
||||
if (excluded === true) {
|
||||
return 'excluded';
|
||||
}
|
||||
|
||||
if (this.markedPending) {
|
||||
return 'pending';
|
||||
}
|
||||
|
||||
if (
|
||||
this.result.failedExpectations.length > 0 ||
|
||||
(failSpecWithNoExpectations &&
|
||||
this.result.failedExpectations.length +
|
||||
this.result.passedExpectations.length ===
|
||||
0)
|
||||
) {
|
||||
return 'failed';
|
||||
}
|
||||
|
||||
return 'passed';
|
||||
};
|
||||
|
||||
Spec.prototype.getFullName = function() {
|
||||
return this.getPath().join(' ');
|
||||
};
|
||||
|
||||
Spec.prototype.addDeprecationWarning = function(deprecation) {
|
||||
if (typeof deprecation === 'string') {
|
||||
deprecation = { message: deprecation };
|
||||
}
|
||||
this.result.deprecationWarnings.push(
|
||||
j$.buildExpectationResult(deprecation)
|
||||
);
|
||||
};
|
||||
|
||||
Spec.prototype.debugLog = function(msg) {
|
||||
if (!this.result.debugLogs) {
|
||||
this.result.debugLogs = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef DebugLogEntry
|
||||
* @property {String} message - The message that was passed to {@link jasmine.debugLog}.
|
||||
* @property {number} timestamp - The time when the entry was added, in
|
||||
* milliseconds from the spec's start time
|
||||
*/
|
||||
this.result.debugLogs.push({
|
||||
message: msg,
|
||||
timestamp: this.timer.elapsed()
|
||||
});
|
||||
};
|
||||
|
||||
const extractCustomPendingMessage = function(e) {
|
||||
const fullMessage = e.toString(),
|
||||
boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage),
|
||||
boilerplateEnd =
|
||||
boilerplateStart + Spec.pendingSpecExceptionMessage.length;
|
||||
|
||||
return fullMessage.slice(boilerplateEnd);
|
||||
};
|
||||
|
||||
Spec.pendingSpecExceptionMessage = '=> marked Pending';
|
||||
|
||||
Spec.isPendingSpecException = function(e) {
|
||||
return !!(
|
||||
e &&
|
||||
e.toString &&
|
||||
e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @interface Spec
|
||||
* @see Configuration#specFilter
|
||||
* @since 2.0.0
|
||||
*/
|
||||
Object.defineProperty(Spec.prototype, 'metadata', {
|
||||
// NOTE: Although most of jasmine-core only exposes these metadata objects,
|
||||
// actual Spec instances are still passed to Configuration#specFilter. Until
|
||||
// that is fixed, it's important to make sure that all metadata properties
|
||||
// also exist in compatible form on the underlying Spec.
|
||||
get: function() {
|
||||
if (!this.metadata_) {
|
||||
this.metadata_ = {
|
||||
get metadata() {
|
||||
// NOTE: Although most of jasmine-core only exposes these metadata objects,
|
||||
// actual Spec instances are still passed to Configuration#specFilter. Until
|
||||
// that is fixed, it's important to make sure that all metadata properties
|
||||
// also exist in compatible form on the underlying Spec.
|
||||
if (!this.#metadata) {
|
||||
this.#metadata = {
|
||||
/**
|
||||
* The unique ID of this spec.
|
||||
* @name Spec#id
|
||||
@@ -307,9 +282,28 @@ getJasmineRequireObj().Spec = function(j$) {
|
||||
};
|
||||
}
|
||||
|
||||
return this.metadata_;
|
||||
return this.#metadata;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const extractCustomPendingMessage = function(e) {
|
||||
const fullMessage = e.toString(),
|
||||
boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage),
|
||||
boilerplateEnd =
|
||||
boilerplateStart + Spec.pendingSpecExceptionMessage.length;
|
||||
|
||||
return fullMessage.slice(boilerplateEnd);
|
||||
};
|
||||
|
||||
Spec.pendingSpecExceptionMessage = '=> marked Pending';
|
||||
|
||||
Spec.isPendingSpecException = function(e) {
|
||||
return !!(
|
||||
e &&
|
||||
e.toString &&
|
||||
e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1
|
||||
);
|
||||
};
|
||||
|
||||
return Spec;
|
||||
};
|
||||
|
||||
@@ -1,86 +1,276 @@
|
||||
getJasmineRequireObj().Suite = function(j$) {
|
||||
function Suite(attrs) {
|
||||
this.env = attrs.env;
|
||||
this.id = attrs.id;
|
||||
this.parentSuite = attrs.parentSuite;
|
||||
this.description = attrs.description;
|
||||
this.reportedParentSuiteId = attrs.reportedParentSuiteId;
|
||||
this.filename = attrs.filename;
|
||||
this.expectationFactory = attrs.expectationFactory;
|
||||
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
|
||||
this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
|
||||
this.autoCleanClosures =
|
||||
attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures;
|
||||
this.onLateError = attrs.onLateError || function() {};
|
||||
class Suite {
|
||||
#reportedParentSuiteId;
|
||||
#throwOnExpectationFailure;
|
||||
#autoCleanClosures;
|
||||
#timer;
|
||||
|
||||
this.beforeFns = [];
|
||||
this.afterFns = [];
|
||||
this.beforeAllFns = [];
|
||||
this.afterAllFns = [];
|
||||
this.timer = attrs.timer || new j$.Timer();
|
||||
this.children = [];
|
||||
constructor(attrs) {
|
||||
this.id = attrs.id;
|
||||
this.parentSuite = attrs.parentSuite;
|
||||
this.description = attrs.description;
|
||||
this.filename = attrs.filename;
|
||||
this.expectationFactory = attrs.expectationFactory;
|
||||
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
|
||||
this.onLateError = attrs.onLateError || function() {};
|
||||
this.#reportedParentSuiteId = attrs.reportedParentSuiteId;
|
||||
this.#throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
|
||||
this.#autoCleanClosures =
|
||||
attrs.autoCleanClosures === undefined
|
||||
? true
|
||||
: !!attrs.autoCleanClosures;
|
||||
this.#timer = attrs.timer || new j$.Timer();
|
||||
|
||||
this.reset();
|
||||
}
|
||||
this.beforeFns = [];
|
||||
this.afterFns = [];
|
||||
this.beforeAllFns = [];
|
||||
this.afterAllFns = [];
|
||||
this.children = [];
|
||||
|
||||
Suite.prototype.setSuiteProperty = function(key, value) {
|
||||
this.result.properties = this.result.properties || {};
|
||||
this.result.properties[key] = value;
|
||||
};
|
||||
this.reset();
|
||||
}
|
||||
|
||||
Suite.prototype.getFullName = function() {
|
||||
const fullName = [];
|
||||
for (
|
||||
let parentSuite = this;
|
||||
parentSuite;
|
||||
parentSuite = parentSuite.parentSuite
|
||||
) {
|
||||
if (parentSuite.parentSuite) {
|
||||
fullName.unshift(parentSuite.description);
|
||||
setSuiteProperty(key, value) {
|
||||
this.result.properties = this.result.properties || {};
|
||||
this.result.properties[key] = value;
|
||||
}
|
||||
|
||||
getFullName() {
|
||||
const fullName = [];
|
||||
for (
|
||||
let parentSuite = this;
|
||||
parentSuite;
|
||||
parentSuite = parentSuite.parentSuite
|
||||
) {
|
||||
if (parentSuite.parentSuite) {
|
||||
fullName.unshift(parentSuite.description);
|
||||
}
|
||||
}
|
||||
return fullName.join(' ');
|
||||
}
|
||||
|
||||
// Mark the suite with "pending" status
|
||||
pend() {
|
||||
this.markedPending = true;
|
||||
}
|
||||
|
||||
// Like pend(), but pending state will survive reset().
|
||||
// Useful for fdescribe, xdescribe, where pending state should remain.
|
||||
exclude() {
|
||||
this.pend();
|
||||
this.markedExcluding = true;
|
||||
}
|
||||
|
||||
beforeEach(fn) {
|
||||
this.beforeFns.unshift({ ...fn, suite: this });
|
||||
}
|
||||
|
||||
beforeAll(fn) {
|
||||
this.beforeAllFns.push({ ...fn, type: 'beforeAll', suite: this });
|
||||
}
|
||||
|
||||
afterEach(fn) {
|
||||
this.afterFns.unshift({ ...fn, suite: this, type: 'afterEach' });
|
||||
}
|
||||
|
||||
afterAll(fn) {
|
||||
this.afterAllFns.unshift({ ...fn, type: 'afterAll' });
|
||||
}
|
||||
|
||||
startTimer() {
|
||||
this.#timer.start();
|
||||
}
|
||||
|
||||
endTimer() {
|
||||
this.result.duration = this.#timer.elapsed();
|
||||
}
|
||||
|
||||
cleanupBeforeAfter() {
|
||||
if (this.#autoCleanClosures) {
|
||||
removeFns(this.beforeAllFns);
|
||||
removeFns(this.afterAllFns);
|
||||
removeFns(this.beforeFns);
|
||||
removeFns(this.afterFns);
|
||||
}
|
||||
}
|
||||
return fullName.join(' ');
|
||||
};
|
||||
|
||||
/*
|
||||
* Mark the suite with "pending" status
|
||||
*/
|
||||
Suite.prototype.pend = function() {
|
||||
this.markedPending = true;
|
||||
};
|
||||
reset() {
|
||||
/**
|
||||
* @typedef SuiteResult
|
||||
* @property {String} id - The unique id of this suite.
|
||||
* @property {String} description - The description text passed to the {@link describe} that made this suite.
|
||||
* @property {String} fullName - The full description including all ancestors of this suite.
|
||||
* @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe().
|
||||
* @property {String} filename - Deprecated. The name of the file the suite was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions that
|
||||
* don't maintain the same call stack height as the originals. This property
|
||||
* may be removed in a future version unless there is enough user interest
|
||||
* in keeping it. See {@link https://github.com/jasmine/jasmine/issues/2065}.
|
||||
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
|
||||
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite.
|
||||
* @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite.
|
||||
* @property {number} duration - The time in ms for Suite execution, including any before/afterAll, before/afterEach.
|
||||
* @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSuiteProperty}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.result = {
|
||||
id: this.id,
|
||||
description: this.description,
|
||||
fullName: this.getFullName(),
|
||||
parentSuiteId: this.#reportedParentSuiteId,
|
||||
filename: this.filename,
|
||||
failedExpectations: [],
|
||||
deprecationWarnings: [],
|
||||
duration: null,
|
||||
properties: null
|
||||
};
|
||||
this.markedPending = this.markedExcluding;
|
||||
this.children.forEach(function(child) {
|
||||
child.reset();
|
||||
});
|
||||
this.reportedDone = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Like {@link Suite#pend}, but pending state will survive {@link Spec#reset}
|
||||
* Useful for fdescribe, xdescribe, where pending state should remain.
|
||||
*/
|
||||
Suite.prototype.exclude = function() {
|
||||
this.pend();
|
||||
this.markedExcluding = true;
|
||||
};
|
||||
removeChildren() {
|
||||
this.children = [];
|
||||
}
|
||||
|
||||
Suite.prototype.beforeEach = function(fn) {
|
||||
this.beforeFns.unshift({ ...fn, suite: this });
|
||||
};
|
||||
addChild(child) {
|
||||
this.children.push(child);
|
||||
}
|
||||
|
||||
Suite.prototype.beforeAll = function(fn) {
|
||||
this.beforeAllFns.push({ ...fn, type: 'beforeAll', suite: this });
|
||||
};
|
||||
#status() {
|
||||
if (this.markedPending) {
|
||||
return 'pending';
|
||||
}
|
||||
|
||||
Suite.prototype.afterEach = function(fn) {
|
||||
this.afterFns.unshift({ ...fn, suite: this, type: 'afterEach' });
|
||||
};
|
||||
if (this.result.failedExpectations.length > 0) {
|
||||
return 'failed';
|
||||
} else {
|
||||
return 'passed';
|
||||
}
|
||||
}
|
||||
|
||||
Suite.prototype.afterAll = function(fn) {
|
||||
this.afterAllFns.unshift({ ...fn, type: 'afterAll' });
|
||||
};
|
||||
getResult() {
|
||||
this.result.status = this.#status();
|
||||
return this.result;
|
||||
}
|
||||
|
||||
Suite.prototype.startTimer = function() {
|
||||
this.timer.start();
|
||||
};
|
||||
canBeReentered() {
|
||||
return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
|
||||
}
|
||||
|
||||
Suite.prototype.endTimer = function() {
|
||||
this.result.duration = this.timer.elapsed();
|
||||
};
|
||||
sharedUserContext() {
|
||||
if (!this.sharedContext) {
|
||||
this.sharedContext = this.parentSuite
|
||||
? this.parentSuite.clonedSharedUserContext()
|
||||
: new j$.UserContext();
|
||||
}
|
||||
|
||||
return this.sharedContext;
|
||||
}
|
||||
|
||||
clonedSharedUserContext() {
|
||||
return j$.UserContext.fromExisting(this.sharedUserContext());
|
||||
}
|
||||
|
||||
handleException() {
|
||||
if (arguments[0] instanceof j$.errors.ExpectationFailed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
matcherName: '',
|
||||
passed: false,
|
||||
expected: '',
|
||||
actual: '',
|
||||
error: arguments[0]
|
||||
};
|
||||
const failedExpectation = j$.buildExpectationResult(data);
|
||||
|
||||
if (!this.parentSuite) {
|
||||
failedExpectation.globalErrorType = 'afterAll';
|
||||
}
|
||||
|
||||
if (this.reportedDone) {
|
||||
this.onLateError(failedExpectation);
|
||||
} else {
|
||||
this.result.failedExpectations.push(failedExpectation);
|
||||
}
|
||||
}
|
||||
|
||||
onMultipleDone() {
|
||||
let msg;
|
||||
|
||||
// Issue an error. Include the context ourselves and pass
|
||||
// ignoreRunnable: true, since getting here always means that we've already
|
||||
// moved on and the current runnable isn't the one that caused the problem.
|
||||
if (this.parentSuite) {
|
||||
msg =
|
||||
"An asynchronous beforeAll or afterAll function called its 'done' " +
|
||||
'callback more than once.\n' +
|
||||
'(in suite: ' +
|
||||
this.getFullName() +
|
||||
')';
|
||||
} else {
|
||||
msg =
|
||||
'A top-level beforeAll or afterAll function called its ' +
|
||||
"'done' callback more than once.";
|
||||
}
|
||||
|
||||
this.onLateError(new Error(msg));
|
||||
}
|
||||
|
||||
addExpectationResult() {
|
||||
if (isFailure(arguments)) {
|
||||
const data = arguments[1];
|
||||
const expectationResult = j$.buildExpectationResult(data);
|
||||
|
||||
if (this.reportedDone) {
|
||||
this.onLateError(expectationResult);
|
||||
} else {
|
||||
this.result.failedExpectations.push(expectationResult);
|
||||
|
||||
// TODO: refactor so that we don't need to override cached status
|
||||
if (this.result.status) {
|
||||
this.result.status = 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
if (this.#throwOnExpectationFailure) {
|
||||
throw new j$.errors.ExpectationFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addDeprecationWarning(deprecation) {
|
||||
if (typeof deprecation === 'string') {
|
||||
deprecation = { message: deprecation };
|
||||
}
|
||||
this.result.deprecationWarnings.push(
|
||||
j$.buildExpectationResult(deprecation)
|
||||
);
|
||||
}
|
||||
|
||||
hasChildWithDescription(description) {
|
||||
for (const child of this.children) {
|
||||
if (child.description === description) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get metadata() {
|
||||
if (!this.metadata_) {
|
||||
this.metadata_ = new SuiteMetadata(this);
|
||||
}
|
||||
|
||||
return this.metadata_;
|
||||
}
|
||||
}
|
||||
|
||||
function removeFns(queueableFns) {
|
||||
for (const qf of queueableFns) {
|
||||
@@ -88,250 +278,64 @@ getJasmineRequireObj().Suite = function(j$) {
|
||||
}
|
||||
}
|
||||
|
||||
Suite.prototype.cleanupBeforeAfter = function() {
|
||||
if (this.autoCleanClosures) {
|
||||
removeFns(this.beforeAllFns);
|
||||
removeFns(this.afterAllFns);
|
||||
removeFns(this.beforeFns);
|
||||
removeFns(this.afterFns);
|
||||
}
|
||||
};
|
||||
|
||||
Suite.prototype.reset = function() {
|
||||
/**
|
||||
* @typedef SuiteResult
|
||||
* @property {String} id - The unique id of this suite.
|
||||
* @property {String} description - The description text passed to the {@link describe} that made this suite.
|
||||
* @property {String} fullName - The full description including all ancestors of this suite.
|
||||
* @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe().
|
||||
* @property {String} filename - Deprecated. The name of the file the suite was defined in.
|
||||
* Note: The value may be incorrect if zone.js is installed or
|
||||
* `describe`/`fdescribe`/`xdescribe` have been replaced with versions that
|
||||
* don't maintain the same call stack height as the originals. This property
|
||||
* may be removed in a future version unless there is enough user interest
|
||||
* in keeping it. See {@link https://github.com/jasmine/jasmine/issues/2065}.
|
||||
* @property {ExpectationResult[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite.
|
||||
* @property {ExpectationResult[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite.
|
||||
* @property {String} status - Once the suite has completed, this string represents the pass/fail status of this suite.
|
||||
* @property {number} duration - The time in ms for Suite execution, including any before/afterAll, before/afterEach.
|
||||
* @property {Object} properties - User-supplied properties, if any, that were set using {@link Env#setSuiteProperty}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.result = {
|
||||
id: this.id,
|
||||
description: this.description,
|
||||
fullName: this.getFullName(),
|
||||
parentSuiteId: this.reportedParentSuiteId,
|
||||
filename: this.filename,
|
||||
failedExpectations: [],
|
||||
deprecationWarnings: [],
|
||||
duration: null,
|
||||
properties: null
|
||||
};
|
||||
this.markedPending = this.markedExcluding;
|
||||
this.children.forEach(function(child) {
|
||||
child.reset();
|
||||
});
|
||||
this.reportedDone = false;
|
||||
};
|
||||
|
||||
Suite.prototype.removeChildren = function() {
|
||||
this.children = [];
|
||||
};
|
||||
|
||||
Suite.prototype.addChild = function(child) {
|
||||
this.children.push(child);
|
||||
};
|
||||
|
||||
Suite.prototype.status = function() {
|
||||
if (this.markedPending) {
|
||||
return 'pending';
|
||||
}
|
||||
|
||||
if (this.result.failedExpectations.length > 0) {
|
||||
return 'failed';
|
||||
} else {
|
||||
return 'passed';
|
||||
}
|
||||
};
|
||||
|
||||
Suite.prototype.canBeReentered = function() {
|
||||
return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
|
||||
};
|
||||
|
||||
Suite.prototype.getResult = function() {
|
||||
this.result.status = this.status();
|
||||
return this.result;
|
||||
};
|
||||
|
||||
Suite.prototype.sharedUserContext = function() {
|
||||
if (!this.sharedContext) {
|
||||
this.sharedContext = this.parentSuite
|
||||
? this.parentSuite.clonedSharedUserContext()
|
||||
: new j$.UserContext();
|
||||
}
|
||||
|
||||
return this.sharedContext;
|
||||
};
|
||||
|
||||
Suite.prototype.clonedSharedUserContext = function() {
|
||||
return j$.UserContext.fromExisting(this.sharedUserContext());
|
||||
};
|
||||
|
||||
Suite.prototype.handleException = function() {
|
||||
if (arguments[0] instanceof j$.errors.ExpectationFailed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
matcherName: '',
|
||||
passed: false,
|
||||
expected: '',
|
||||
actual: '',
|
||||
error: arguments[0]
|
||||
};
|
||||
const failedExpectation = j$.buildExpectationResult(data);
|
||||
|
||||
if (!this.parentSuite) {
|
||||
failedExpectation.globalErrorType = 'afterAll';
|
||||
}
|
||||
|
||||
if (this.reportedDone) {
|
||||
this.onLateError(failedExpectation);
|
||||
} else {
|
||||
this.result.failedExpectations.push(failedExpectation);
|
||||
}
|
||||
};
|
||||
|
||||
Suite.prototype.onMultipleDone = function() {
|
||||
let msg;
|
||||
|
||||
// Issue a deprecation. Include the context ourselves and pass
|
||||
// ignoreRunnable: true, since getting here always means that we've already
|
||||
// moved on and the current runnable isn't the one that caused the problem.
|
||||
if (this.parentSuite) {
|
||||
msg =
|
||||
"An asynchronous beforeAll or afterAll function called its 'done' " +
|
||||
'callback more than once.\n' +
|
||||
'(in suite: ' +
|
||||
this.getFullName() +
|
||||
')';
|
||||
} else {
|
||||
msg =
|
||||
'A top-level beforeAll or afterAll function called its ' +
|
||||
"'done' callback more than once.";
|
||||
}
|
||||
|
||||
this.onLateError(new Error(msg));
|
||||
};
|
||||
|
||||
Suite.prototype.addExpectationResult = function() {
|
||||
if (isFailure(arguments)) {
|
||||
const data = arguments[1];
|
||||
const expectationResult = j$.buildExpectationResult(data);
|
||||
|
||||
if (this.reportedDone) {
|
||||
this.onLateError(expectationResult);
|
||||
} else {
|
||||
this.result.failedExpectations.push(expectationResult);
|
||||
|
||||
// TODO: refactor so that we don't need to override cached status
|
||||
if (this.result.status) {
|
||||
this.result.status = 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
if (this.throwOnExpectationFailure) {
|
||||
throw new j$.errors.ExpectationFailed();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Suite.prototype.addDeprecationWarning = function(deprecation) {
|
||||
if (typeof deprecation === 'string') {
|
||||
deprecation = { message: deprecation };
|
||||
}
|
||||
this.result.deprecationWarnings.push(
|
||||
j$.buildExpectationResult(deprecation)
|
||||
);
|
||||
};
|
||||
|
||||
Suite.prototype.hasChildWithDescription = function(description) {
|
||||
for (const child of this.children) {
|
||||
if (child.description === description) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
Object.defineProperty(Suite.prototype, 'metadata', {
|
||||
get: function() {
|
||||
if (!this.metadata_) {
|
||||
this.metadata_ = new SuiteMetadata(this);
|
||||
}
|
||||
|
||||
return this.metadata_;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @interface Suite
|
||||
* @see Env#topSuite
|
||||
* @since 2.0.0
|
||||
*/
|
||||
function SuiteMetadata(suite) {
|
||||
this.suite_ = suite;
|
||||
/**
|
||||
* The unique ID of this suite.
|
||||
* @name Suite#id
|
||||
* @readonly
|
||||
* @type {string}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.id = suite.id;
|
||||
class SuiteMetadata {
|
||||
#suite;
|
||||
|
||||
/**
|
||||
* The parent of this suite, or null if this is the top suite.
|
||||
* @name Suite#parentSuite
|
||||
* @readonly
|
||||
* @type {Suite}
|
||||
*/
|
||||
this.parentSuite = suite.parentSuite ? suite.parentSuite.metadata : null;
|
||||
constructor(suite) {
|
||||
this.#suite = suite;
|
||||
/**
|
||||
* The unique ID of this suite.
|
||||
* @name Suite#id
|
||||
* @readonly
|
||||
* @type {string}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.id = suite.id;
|
||||
|
||||
/**
|
||||
* The description passed to the {@link describe} that created this suite.
|
||||
* @name Suite#description
|
||||
* @readonly
|
||||
* @type {string}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.description = suite.description;
|
||||
}
|
||||
/**
|
||||
* The parent of this suite, or null if this is the top suite.
|
||||
* @name Suite#parentSuite
|
||||
* @readonly
|
||||
* @type {Suite}
|
||||
*/
|
||||
this.parentSuite = suite.parentSuite ? suite.parentSuite.metadata : null;
|
||||
|
||||
/**
|
||||
* The full description including all ancestors of this suite.
|
||||
* @name Suite#getFullName
|
||||
* @function
|
||||
* @returns {string}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
SuiteMetadata.prototype.getFullName = function() {
|
||||
return this.suite_.getFullName();
|
||||
};
|
||||
|
||||
/**
|
||||
* The suite's children.
|
||||
* @name Suite#children
|
||||
* @type {Array.<(Spec|Suite)>}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
Object.defineProperty(SuiteMetadata.prototype, 'children', {
|
||||
get: function() {
|
||||
return this.suite_.children.map(child => child.metadata);
|
||||
/**
|
||||
* The description passed to the {@link describe} that created this suite.
|
||||
* @name Suite#description
|
||||
* @readonly
|
||||
* @type {string}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
this.description = suite.description;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The full description including all ancestors of this suite.
|
||||
* @name Suite#getFullName
|
||||
* @function
|
||||
* @returns {string}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
getFullName() {
|
||||
return this.#suite.getFullName();
|
||||
}
|
||||
|
||||
/**
|
||||
* The suite's children.
|
||||
* @name Suite#children
|
||||
* @type {Array.<(Spec|Suite)>}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
get children() {
|
||||
return this.#suite.children.map(child => child.metadata);
|
||||
}
|
||||
}
|
||||
|
||||
function isFailure(args) {
|
||||
return !args[0];
|
||||
|
||||
@@ -23,34 +23,9 @@ getJasmineRequireObj().TreeRunner = function(j$) {
|
||||
|
||||
async execute() {
|
||||
this.#hasFailures = false;
|
||||
const topSuite = this.#executionTree.topSuite;
|
||||
const wrappedChildren = this.#wrapNodes(
|
||||
this.#executionTree.childrenOfTopSuite()
|
||||
);
|
||||
const queueableFns = this.#addBeforeAndAfterAlls(
|
||||
topSuite,
|
||||
wrappedChildren
|
||||
);
|
||||
|
||||
await new Promise(resolve => {
|
||||
this.#runQueue({
|
||||
queueableFns,
|
||||
userContext: this.#executionTree.topSuite.sharedUserContext(),
|
||||
onException: function() {
|
||||
topSuite.handleException.apply(topSuite, arguments);
|
||||
}.bind(this),
|
||||
onComplete: resolve,
|
||||
onMultipleDone: topSuite.onMultipleDone
|
||||
? topSuite.onMultipleDone.bind(topSuite)
|
||||
: null,
|
||||
SkipPolicy: this.#suiteSkipPolicy()
|
||||
});
|
||||
this.#executeSuiteSegment(this.#executionTree.topSuite, 0, resolve);
|
||||
});
|
||||
|
||||
if (topSuite.hadBeforeAllFailure) {
|
||||
await this.#reportChildrenOfBeforeAllFailure(topSuite);
|
||||
}
|
||||
|
||||
return { hasFailures: this.#hasFailures };
|
||||
}
|
||||
|
||||
@@ -142,19 +117,7 @@ getJasmineRequireObj().TreeRunner = function(j$) {
|
||||
fns.unshift(start);
|
||||
|
||||
if (config.detectLateRejectionHandling) {
|
||||
// Conditional because the setTimeout imposes a significant performance
|
||||
// penalty in suites with lots of fast specs.
|
||||
const globalErrors = this.#globalErrors;
|
||||
fns.push({
|
||||
fn: done => {
|
||||
// setTimeout is necessary to trigger rejectionhandled events
|
||||
// TODO: let clearStack know about this so it doesn't do redundant setTimeouts
|
||||
this.#setTimeout(function() {
|
||||
globalErrors.reportUnhandledRejections();
|
||||
done();
|
||||
});
|
||||
}
|
||||
});
|
||||
fns.push(this.#lateUnhandledRejectionChecker());
|
||||
}
|
||||
|
||||
fns.push(complete);
|
||||
@@ -162,28 +125,48 @@ getJasmineRequireObj().TreeRunner = function(j$) {
|
||||
}
|
||||
|
||||
#executeSuiteSegment(suite, segmentNumber, done) {
|
||||
const wrappedChildren = this.#wrapNodes(
|
||||
this.#executionTree.childrenOfSuiteSegment(suite, segmentNumber)
|
||||
);
|
||||
const onStart = {
|
||||
fn: next => {
|
||||
this.#suiteSegmentStart(suite, next);
|
||||
const isTopSuite = suite === this.#executionTree.topSuite;
|
||||
const isExcluded = this.#executionTree.isExcluded(suite);
|
||||
let befores = [];
|
||||
let afters = [];
|
||||
|
||||
if (suite.beforeAllFns.length > 0 && !isExcluded) {
|
||||
befores = [...suite.beforeAllFns];
|
||||
if (this.#getConfig().detectLateRejectionHandling) {
|
||||
befores.push(this.#lateUnhandledRejectionChecker());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (suite.afterAllFns.length > 0 && !isExcluded) {
|
||||
afters = [...suite.afterAllFns];
|
||||
if (this.#getConfig().detectLateRejectionHandling) {
|
||||
afters.push(this.#lateUnhandledRejectionChecker());
|
||||
}
|
||||
}
|
||||
|
||||
const children = isTopSuite
|
||||
? this.#executionTree.childrenOfTopSuite()
|
||||
: this.#executionTree.childrenOfSuiteSegment(suite, segmentNumber);
|
||||
const queueableFns = [
|
||||
onStart,
|
||||
...this.#addBeforeAndAfterAlls(suite, wrappedChildren)
|
||||
...befores,
|
||||
...this.#wrapNodes(children),
|
||||
...afters
|
||||
];
|
||||
|
||||
if (!isTopSuite) {
|
||||
queueableFns.unshift({
|
||||
fn: next => {
|
||||
this.#suiteSegmentStart(suite, next);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.#runQueue({
|
||||
// TODO: if onComplete always takes 0-1 arguments (and it probably does)
|
||||
// then it can be switched to an arrow fn with a named arg.
|
||||
onComplete: function() {
|
||||
const args = Array.prototype.slice.call(arguments, [0]);
|
||||
onComplete: maybeError => {
|
||||
this.#suiteSegmentComplete(suite, suite.getResult(), () => {
|
||||
done.apply(undefined, args);
|
||||
done(maybeError);
|
||||
});
|
||||
}.bind(this),
|
||||
},
|
||||
queueableFns,
|
||||
userContext: suite.sharedUserContext(),
|
||||
onException: function() {
|
||||
@@ -196,6 +179,24 @@ getJasmineRequireObj().TreeRunner = function(j$) {
|
||||
});
|
||||
}
|
||||
|
||||
// Returns a queueable fn that reports any still-unhandled rejections in
|
||||
// cases where detectLateRejectionHandling is enabled. Should only be called
|
||||
// when detectLateRejectionHandling is enabled, because the setTimeout
|
||||
// imposes a significant performance penalty in suites with lots of fast
|
||||
// specs.
|
||||
#lateUnhandledRejectionChecker() {
|
||||
const globalErrors = this.#globalErrors;
|
||||
return {
|
||||
fn: done => {
|
||||
// setTimeout is necessary to trigger rejectionhandled events
|
||||
this.#setTimeout(function() {
|
||||
globalErrors.reportUnhandledRejections();
|
||||
done();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#suiteSegmentStart(suite, next) {
|
||||
this.#currentRunableTracker.pushSuite(suite);
|
||||
this.#runableResources.initForRunable(suite.id, suite.parentSuite.id);
|
||||
@@ -204,26 +205,35 @@ getJasmineRequireObj().TreeRunner = function(j$) {
|
||||
}
|
||||
|
||||
#suiteSegmentComplete(suite, result, next) {
|
||||
suite.cleanupBeforeAfter();
|
||||
const isTopSuite = suite === this.#executionTree.topSuite;
|
||||
|
||||
if (suite !== this.#currentRunableTracker.currentSuite()) {
|
||||
throw new Error('Tried to complete the wrong suite');
|
||||
if (!isTopSuite) {
|
||||
if (suite !== this.#currentRunableTracker.currentSuite()) {
|
||||
throw new Error('Tried to complete the wrong suite');
|
||||
}
|
||||
|
||||
// suite.cleanupBeforeAfter() is conditional because calling it on the
|
||||
// top suite breaks parallel mode. The top suite is reentered every time
|
||||
// a runner runs another file, so its before and after fns need to be
|
||||
// preserved.
|
||||
suite.cleanupBeforeAfter();
|
||||
this.#runableResources.clearForRunable(suite.id);
|
||||
this.#currentRunableTracker.popSuite();
|
||||
|
||||
if (result.status === 'failed') {
|
||||
this.#hasFailures = true;
|
||||
}
|
||||
suite.endTimer();
|
||||
}
|
||||
|
||||
this.#runableResources.clearForRunable(suite.id);
|
||||
this.#currentRunableTracker.popSuite();
|
||||
|
||||
if (result.status === 'failed') {
|
||||
this.#hasFailures = true;
|
||||
}
|
||||
suite.endTimer();
|
||||
const finish = isTopSuite
|
||||
? next
|
||||
: () => this.#reportSuiteDone(suite, result, next);
|
||||
|
||||
if (suite.hadBeforeAllFailure) {
|
||||
this.#reportChildrenOfBeforeAllFailure(suite).then(() => {
|
||||
this.#reportSuiteDone(suite, result, next);
|
||||
});
|
||||
this.#reportChildrenOfBeforeAllFailure(suite).then(finish);
|
||||
} else {
|
||||
this.#reportSuiteDone(suite, result, next);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,16 +290,6 @@ getJasmineRequireObj().TreeRunner = function(j$) {
|
||||
}
|
||||
}
|
||||
|
||||
#addBeforeAndAfterAlls(suite, wrappedChildren) {
|
||||
if (this.#executionTree.isExcluded(suite)) {
|
||||
return wrappedChildren;
|
||||
}
|
||||
|
||||
return suite.beforeAllFns
|
||||
.concat(wrappedChildren)
|
||||
.concat(suite.afterAllFns);
|
||||
}
|
||||
|
||||
#suiteSkipPolicy() {
|
||||
if (this.#getConfig().stopOnSpecFailure) {
|
||||
return j$.CompleteOnFirstErrorSkipPolicy;
|
||||
|
||||
@@ -43,6 +43,7 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
|
||||
j$.Clock = jRequire.Clock();
|
||||
j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$);
|
||||
j$.Deprecator = jRequire.Deprecator(j$);
|
||||
j$.Configuration = jRequire.Configuration(j$);
|
||||
j$.Env = jRequire.Env(j$);
|
||||
j$.StackTrace = jRequire.StackTrace(j$);
|
||||
j$.ExceptionFormatter = jRequire.ExceptionFormatter(j$);
|
||||
|
||||
52
src/html/HtmlExactSpecFilter.js
Normal file
52
src/html/HtmlExactSpecFilter.js
Normal file
@@ -0,0 +1,52 @@
|
||||
jasmineRequire.HtmlExactSpecFilter = function() {
|
||||
/**
|
||||
* Spec filter for use with {@link HtmlReporter}
|
||||
*
|
||||
* See lib/jasmine-core/boot1.js for usage.
|
||||
* @since 5.11.0
|
||||
*/
|
||||
class HtmlExactSpecFilter {
|
||||
#getFilterString;
|
||||
|
||||
/**
|
||||
* Create a filter instance.
|
||||
* @param options Object with a queryString property, which should be an
|
||||
* instance of {@link QueryString}.
|
||||
*/
|
||||
constructor(options) {
|
||||
this.#getFilterString = function() {
|
||||
return options.queryString.getParam('spec');
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the specified spec should be executed.
|
||||
* @param {Spec} spec
|
||||
* @returns {boolean}
|
||||
*/
|
||||
matches(spec) {
|
||||
const filterString = this.#getFilterString();
|
||||
|
||||
if (!filterString) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const filterPath = JSON.parse(this.#getFilterString());
|
||||
const specPath = spec.getPath();
|
||||
|
||||
if (filterPath.length > specPath.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < filterPath.length; i++) {
|
||||
if (specPath[i] !== filterPath[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return HtmlExactSpecFilter;
|
||||
};
|
||||
@@ -5,11 +5,13 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
this.specsExecuted = 0;
|
||||
this.failureCount = 0;
|
||||
this.pendingSpecCount = 0;
|
||||
this.suitesById = [];
|
||||
}
|
||||
|
||||
ResultsStateBuilder.prototype.suiteStarted = function(result) {
|
||||
this.currentParent.addChild(result, 'suite');
|
||||
this.currentParent = this.currentParent.last();
|
||||
this.suitesById[result.id] = this.currentParent;
|
||||
};
|
||||
|
||||
ResultsStateBuilder.prototype.suiteDone = function(result) {
|
||||
@@ -47,6 +49,13 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @class HtmlReporter
|
||||
* @classdesc Displays results and allows re-running individual specs and suites.
|
||||
* @implements {Reporter}
|
||||
* @param options Options object. See lib/jasmine-core/boot1.js for details.
|
||||
* @since 1.2.0
|
||||
*/
|
||||
function HtmlReporter(options) {
|
||||
function config() {
|
||||
return (options.env && options.env.configuration()) || {};
|
||||
@@ -55,15 +64,24 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
const getContainer = options.getContainer;
|
||||
const createElement = options.createElement;
|
||||
const createTextNode = options.createTextNode;
|
||||
// TODO: in the next major release, replace navigateWithNewParam and
|
||||
// addToExistingQueryString with direct usage of options.queryString
|
||||
const navigateWithNewParam = options.navigateWithNewParam || function() {};
|
||||
const addToExistingQueryString =
|
||||
options.addToExistingQueryString || defaultQueryString;
|
||||
const filterSpecs = options.filterSpecs;
|
||||
const filterSpecs = options.queryString
|
||||
? !!options.queryString.getParam('spec')
|
||||
: options.filterSpecs; // For compatibility with pre-5.11 boot files
|
||||
let htmlReporterMain;
|
||||
let symbols;
|
||||
const deprecationWarnings = [];
|
||||
const failures = [];
|
||||
|
||||
/**
|
||||
* Initializes the reporter. Should be called before {@link Env#execute}.
|
||||
* @function
|
||||
* @name HtmlReporter#initialize
|
||||
*/
|
||||
this.initialize = function() {
|
||||
clearPrior();
|
||||
htmlReporterMain = createDom(
|
||||
@@ -682,21 +700,6 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
function suiteHref(suite) {
|
||||
const els = [];
|
||||
|
||||
while (suite && suite.parent) {
|
||||
els.unshift(suite.result.description);
|
||||
suite = suite.parent;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (result && result.deprecationWarnings) {
|
||||
for (let i = 0; i < result.deprecationWarnings.length; i++) {
|
||||
@@ -794,11 +797,33 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
return '' + count + ' ' + word;
|
||||
}
|
||||
|
||||
function suitePath(suite) {
|
||||
const els = [];
|
||||
|
||||
while (suite && suite.parent) {
|
||||
els.unshift(suite.result.description);
|
||||
suite = suite.parent;
|
||||
}
|
||||
|
||||
return els;
|
||||
}
|
||||
|
||||
function suiteHref(suite) {
|
||||
return pathHref(suitePath(suite));
|
||||
}
|
||||
|
||||
function specHref(result) {
|
||||
const suite = stateBuilder.suitesById[result.parentSuiteId];
|
||||
const path = suitePath(suite);
|
||||
path.push(result.description);
|
||||
return pathHref(path);
|
||||
}
|
||||
|
||||
function pathHref(path) {
|
||||
// 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)
|
||||
addToExistingQueryString('spec', JSON.stringify(path))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,34 @@
|
||||
jasmineRequire.HtmlSpecFilter = function() {
|
||||
/**
|
||||
* @name HtmlSpecFilter
|
||||
* @classdesc Legacy HTML spec filter, for backward compatibility
|
||||
* with boot files that predate {@link HtmlExactSpecFilter}.
|
||||
* @param options Object with a filterString method
|
||||
* @constructor
|
||||
* @deprecated
|
||||
* @since 1.2.0
|
||||
*/
|
||||
// Legacy HTML spec filter, preserved for backward compatibility with
|
||||
// boot files that predate HtmlExactSpecFilterV2
|
||||
function HtmlSpecFilter(options) {
|
||||
const filterString =
|
||||
options &&
|
||||
options.filterString() &&
|
||||
options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
||||
let filterString = (options && options.filterString()) || '';
|
||||
|
||||
if (filterString.startsWith('[')) {
|
||||
// Convert an HtmlExactSpecFilterV2 string into something we can use
|
||||
filterString = JSON.parse(filterString).join(' ');
|
||||
}
|
||||
|
||||
filterString = filterString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
||||
|
||||
const filterPattern = new RegExp(filterString);
|
||||
|
||||
/**
|
||||
* Determines whether the spec with the specified name should be executed.
|
||||
* @name HtmlSpecFilter#matches
|
||||
* @function
|
||||
* @param {string} specName The full name of the spec
|
||||
* @returns {boolean}
|
||||
*/
|
||||
this.matches = function(specName) {
|
||||
return filterPattern.test(specName);
|
||||
};
|
||||
|
||||
@@ -1,36 +1,55 @@
|
||||
jasmineRequire.QueryString = function() {
|
||||
function QueryString(options) {
|
||||
this.navigateWithNewParam = function(key, value) {
|
||||
options.getWindowLocation().search = this.fullStringWithNewParam(
|
||||
/**
|
||||
* Reads and manipulates the query string.
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class QueryString {
|
||||
#getWindowLocation;
|
||||
|
||||
/**
|
||||
* @param options Object with a getWindowLocation property, which should be
|
||||
* a function returning the current value of window.location.
|
||||
*/
|
||||
constructor(options) {
|
||||
this.#getWindowLocation = options.getWindowLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the specified query parameter and navigates to the resulting URL.
|
||||
* @param {string} key
|
||||
* @param {string} value
|
||||
*/
|
||||
navigateWithNewParam(key, value) {
|
||||
this.#getWindowLocation().search = this.fullStringWithNewParam(
|
||||
key,
|
||||
value
|
||||
);
|
||||
};
|
||||
|
||||
this.fullStringWithNewParam = function(key, value) {
|
||||
const paramMap = queryStringToParamMap();
|
||||
paramMap[key] = value;
|
||||
return toQueryString(paramMap);
|
||||
};
|
||||
|
||||
this.getParam = function(key) {
|
||||
return queryStringToParamMap()[key];
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
function toQueryString(paramMap) {
|
||||
const qStrPairs = [];
|
||||
for (const prop in paramMap) {
|
||||
qStrPairs.push(
|
||||
encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])
|
||||
);
|
||||
}
|
||||
return '?' + qStrPairs.join('&');
|
||||
}
|
||||
|
||||
function queryStringToParamMap() {
|
||||
const paramStr = options.getWindowLocation().search.substring(1);
|
||||
/**
|
||||
* Returns a new URL based on the current location, with the specified
|
||||
* query parameter set.
|
||||
* @param {string} key
|
||||
* @param {string} value
|
||||
* @return {string}
|
||||
*/
|
||||
fullStringWithNewParam(key, value) {
|
||||
const paramMap = this.#queryStringToParamMap();
|
||||
paramMap[key] = value;
|
||||
return toQueryString(paramMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the specified query parameter.
|
||||
* @param {string} key
|
||||
* @return {string}
|
||||
*/
|
||||
getParam(key) {
|
||||
return this.#queryStringToParamMap()[key];
|
||||
}
|
||||
|
||||
#queryStringToParamMap() {
|
||||
const paramStr = this.#getWindowLocation().search.substring(1);
|
||||
let params = [];
|
||||
const paramMap = {};
|
||||
|
||||
@@ -50,5 +69,15 @@ jasmineRequire.QueryString = function() {
|
||||
}
|
||||
}
|
||||
|
||||
function toQueryString(paramMap) {
|
||||
const qStrPairs = [];
|
||||
for (const prop in paramMap) {
|
||||
qStrPairs.push(
|
||||
encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])
|
||||
);
|
||||
}
|
||||
return '?' + qStrPairs.join('&');
|
||||
}
|
||||
|
||||
return QueryString;
|
||||
};
|
||||
|
||||
@@ -6,4 +6,5 @@ jasmineRequire.html = function(j$) {
|
||||
j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
|
||||
j$.QueryString = jasmineRequire.QueryString();
|
||||
j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
|
||||
j$.HtmlExactSpecFilter = jasmineRequire.HtmlExactSpecFilter();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user