Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7978ad9889 | ||
|
|
af4662ad31 | ||
|
|
15c38c7728 | ||
|
|
b597975c7e | ||
|
|
09ce3a30b6 | ||
|
|
3bcbc2e3af | ||
|
|
fbaba902dc | ||
|
|
bf2e8e759e | ||
|
|
50e566bd67 | ||
|
|
4b7d5e3623 | ||
|
|
6449832e7e | ||
|
|
c6266b24b7 | ||
|
|
f16b81d4ef | ||
|
|
7feec406d9 | ||
|
|
f822ffea21 | ||
|
|
db65c3b131 | ||
|
|
fd37a7eac0 | ||
|
|
12219e80c1 | ||
|
|
a980ae6bf2 | ||
|
|
56ac8f5505 | ||
|
|
3780fe0b35 | ||
|
|
164a393932 | ||
|
|
759a867094 | ||
|
|
f94d0ceda9 | ||
|
|
8d99f27be8 | ||
|
|
63774597f0 | ||
|
|
a3e1abfa12 | ||
|
|
b89a870a59 | ||
|
|
ea3fc88803 | ||
|
|
d5884e33c6 | ||
|
|
138bf9be4b | ||
|
|
98d5284c19 | ||
|
|
2299c85751 | ||
|
|
8e3ec25f6d | ||
|
|
b009cd2922 | ||
|
|
8eee6ebb91 | ||
|
|
c15a1aaa6d | ||
|
|
5b06531cac | ||
|
|
42cca93926 | ||
|
|
395ef85954 | ||
|
|
5e88fde655 |
14
README.md
14
README.md
@@ -27,13 +27,13 @@ for information on writing specs, and [the FAQ](https://jasmine.github.io/pages/
|
||||
Jasmine tests itself across popular browsers (Safari, Chrome, Firefox, and
|
||||
Microsoft Edge) as well as Node.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------------|----------------------------|
|
||||
| Node | 18.20.5+*, 20, 22, 24 |
|
||||
| Safari | 15*, 16*, 17* |
|
||||
| Chrome | Evergreen |
|
||||
| Firefox | Evergreen, 102*, 115*, 128 |
|
||||
| Edge | Evergreen |
|
||||
| Environment | Supported versions |
|
||||
|-------------------|----------------------------------|
|
||||
| Node | 18.20.5+*, 20, 22, 24 |
|
||||
| Safari | 15*, 16*, 17* |
|
||||
| Chrome | Evergreen |
|
||||
| Firefox | Evergreen, 102*, 115*, 128*, 140 |
|
||||
| Edge | Evergreen |
|
||||
|
||||
For evergreen browsers, each version of Jasmine is tested against the version of the browser that is available to us
|
||||
at the time of release. Other browsers, as well as older & newer versions of some supported browsers, are likely to work.
|
||||
|
||||
@@ -557,6 +557,11 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
'a',
|
||||
{ href: specHref(resultNode.result) },
|
||||
specDescription
|
||||
),
|
||||
createDom(
|
||||
'span',
|
||||
{ className: 'jasmine-spec-duration' },
|
||||
'(' + resultNode.result.duration + 'ms)'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -229,6 +229,9 @@ body {
|
||||
.jasmine_html-reporter .jasmine-specs li.jasmine-excluded a:before {
|
||||
content: "• ";
|
||||
}
|
||||
.jasmine_html-reporter .jasmine-specs li .jasmine-spec-duration {
|
||||
margin-left: 1em;
|
||||
}
|
||||
.jasmine_html-reporter .jasmine-description + .jasmine-suite {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jasmine-core",
|
||||
"license": "MIT",
|
||||
"version": "5.9.0",
|
||||
"version": "5.10.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jasmine/jasmine.git"
|
||||
|
||||
53
release_notes/5.10.0.md
Normal file
53
release_notes/5.10.0.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Jasmine Core 5.10.0 Release Notes
|
||||
|
||||
## New Features
|
||||
|
||||
* Optionally detect late promise rejections 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`.
|
||||
* Merges [#2072](https://github.com/jasmine/jasmine/pull/2072) from @bonkevin
|
||||
* Show spec duration in the HTML reporter.
|
||||
* Merges [#2073](https://github.com/jasmine/jasmine/pull/2073) from @bonkevin
|
||||
* Protect `GlobalErrors` against monkey patching.
|
||||
|
||||
All currently shipped versions of zone.js contain a monkey patch that fails
|
||||
to pass constructor arguments on to `GlobalErrors`. This patch normally has
|
||||
no effect because zone.js is normally installed after `GlobalErrors` is
|
||||
instantiated, but it would crash Jasmine if it was applied early enough.
|
||||
|
||||
## Deprecations
|
||||
|
||||
* Issue a deprecation warning if the suite/spec order passed as a parameter to
|
||||
`Env#execute` causes a suite to be re-entered.
|
||||
|
||||
## Changes to supported environments
|
||||
|
||||
* Added Firefox 140 (current ESR) to supported environments
|
||||
* Demoted Firefox 128 (previous ESR) to best-effort support
|
||||
|
||||
## Internal improvements
|
||||
|
||||
* Core suite/spec execution flow has been significantly simplified.
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------------|--------------------------------|
|
||||
| Node | 18.20.5**, 20, 22, 24 |
|
||||
| Safari | 15**, 16**, 17** |
|
||||
| Chrome | 139* |
|
||||
| Firefox | 102**, 115**, 128**, 140, 142* |
|
||||
| Edge | 139* |
|
||||
|
||||
\* 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,6 +28,7 @@ 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
|
||||
run_browser firefox 102
|
||||
|
||||
@@ -129,33 +129,33 @@ describe('AsyncExpectation', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('prepends the context to a custom failure message from a function', function() {
|
||||
pending('should actually work, but no custom matchers for async yet');
|
||||
|
||||
it('prepends the context to a custom failure message from a matcher', async function() {
|
||||
const matchersUtil = {
|
||||
buildFailureMessage: function() {
|
||||
return 'failure message';
|
||||
}
|
||||
buildFailureMessage() {
|
||||
return 'failure message';
|
||||
},
|
||||
addExpectationResult = jasmine.createSpy('addExpectationResult'),
|
||||
actual = Promise.reject(new Error('nope')),
|
||||
expectation = jasmineUnderTest.Expectation.asyncFactory({
|
||||
actual: actual,
|
||||
addExpectationResult: addExpectationResult,
|
||||
matchersUtil: matchersUtil
|
||||
});
|
||||
pp(v) {
|
||||
return v.toString();
|
||||
}
|
||||
};
|
||||
const addExpectationResult = jasmine.createSpy('addExpectationResult');
|
||||
const actual = Promise.reject(new Error('nope'));
|
||||
const expectation = jasmineUnderTest.Expectation.asyncFactory({
|
||||
actual: actual,
|
||||
addExpectationResult: addExpectationResult,
|
||||
matchersUtil: matchersUtil
|
||||
});
|
||||
|
||||
return expectation
|
||||
.withContext('Some context')
|
||||
.toBeResolved()
|
||||
.then(function() {
|
||||
expect(addExpectationResult).toHaveBeenCalledWith(
|
||||
false,
|
||||
jasmine.objectContaining({
|
||||
message: 'Some context: msg'
|
||||
})
|
||||
);
|
||||
});
|
||||
await expectation.withContext('Some context').toBeResolved();
|
||||
|
||||
expect(addExpectationResult).toHaveBeenCalledWith(
|
||||
false,
|
||||
jasmine.objectContaining({
|
||||
message:
|
||||
'Some context: Expected a promise to be resolved but it ' +
|
||||
'was rejected with Error: nope.'
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('works with #not', function() {
|
||||
|
||||
@@ -625,9 +625,12 @@ describe('Env', function() {
|
||||
'popListener',
|
||||
'removeOverrideListener'
|
||||
]);
|
||||
spyOn(jasmineUnderTest, 'GlobalErrors').and.returnValue(globalErrors);
|
||||
env.cleanup_();
|
||||
env = new jasmineUnderTest.Env();
|
||||
env = new jasmineUnderTest.Env({
|
||||
GlobalErrors: function() {
|
||||
return globalErrors;
|
||||
}
|
||||
});
|
||||
expect(globalErrors.install).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -641,9 +644,13 @@ describe('Env', function() {
|
||||
'popListener',
|
||||
'removeOverrideListener'
|
||||
]);
|
||||
spyOn(jasmineUnderTest, 'GlobalErrors').and.returnValue(globalErrors);
|
||||
env.cleanup_();
|
||||
env = new jasmineUnderTest.Env({ suppressLoadErrors: true });
|
||||
env = new jasmineUnderTest.Env({
|
||||
suppressLoadErrors: true,
|
||||
GlobalErrors: function() {
|
||||
return globalErrors;
|
||||
}
|
||||
});
|
||||
expect(globalErrors.install).not.toHaveBeenCalled();
|
||||
env.execute();
|
||||
expect(globalErrors.install).toHaveBeenCalled();
|
||||
|
||||
@@ -2,7 +2,10 @@ describe('GlobalErrors', function() {
|
||||
it('calls the added handler on error', function() {
|
||||
const globals = browserGlobals();
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
@@ -19,7 +22,10 @@ describe('GlobalErrors', function() {
|
||||
it('is not affected by overriding global.onerror', function() {
|
||||
const globals = browserGlobals();
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
@@ -39,7 +45,10 @@ describe('GlobalErrors', function() {
|
||||
const globals = browserGlobals();
|
||||
const handler1 = jasmine.createSpy('errorHandler1');
|
||||
const handler2 = jasmine.createSpy('errorHandler2');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler1);
|
||||
@@ -59,7 +68,10 @@ describe('GlobalErrors', function() {
|
||||
const globals = browserGlobals();
|
||||
const handler1 = jasmine.createSpy('errorHandler1');
|
||||
const handler2 = jasmine.createSpy('errorHandler2');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler1);
|
||||
@@ -86,7 +98,10 @@ describe('GlobalErrors', function() {
|
||||
|
||||
it('uninstalls itself', function() {
|
||||
const globals = browserGlobals();
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
function unrelatedListener() {}
|
||||
|
||||
errors.install();
|
||||
@@ -98,7 +113,10 @@ describe('GlobalErrors', function() {
|
||||
|
||||
it('rethrows the original error when there is no handler', function() {
|
||||
const globals = browserGlobals();
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
const originalError = new Error('nope');
|
||||
|
||||
errors.install();
|
||||
@@ -114,7 +132,10 @@ describe('GlobalErrors', function() {
|
||||
|
||||
it('reports uncaught exceptions in node.js', function() {
|
||||
const globals = nodeGlobals();
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
function originalHandler() {}
|
||||
globals.listeners.uncaughtException = [originalHandler];
|
||||
@@ -144,7 +165,10 @@ describe('GlobalErrors', function() {
|
||||
describe('Reporting unhandled promise rejections in node.js', function() {
|
||||
it('reports rejections with `Error` reasons', function() {
|
||||
const globals = nodeGlobals();
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
function originalHandler() {}
|
||||
globals.listeners.unhandledRejection = [originalHandler];
|
||||
@@ -173,7 +197,10 @@ describe('GlobalErrors', function() {
|
||||
|
||||
it('reports rejections with non-`Error` reasons', function() {
|
||||
const globals = nodeGlobals();
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
@@ -193,7 +220,10 @@ describe('GlobalErrors', function() {
|
||||
|
||||
it('reports rejections with no reason provided', function() {
|
||||
const globals = nodeGlobals();
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
@@ -210,12 +240,149 @@ describe('GlobalErrors', function() {
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
describe('When detectLateRejectionHandling is true', function() {
|
||||
let globals, errors;
|
||||
|
||||
beforeEach(function() {
|
||||
globals = nodeGlobals();
|
||||
errors = new jasmineUnderTest.GlobalErrors(globals.global, () => ({
|
||||
detectLateRejectionHandling: true
|
||||
}));
|
||||
});
|
||||
|
||||
it('subscribes and unsubscribes from the rejectionHandled event', function() {
|
||||
function originalHandler() {}
|
||||
globals.global.process.on('rejectionHandled', originalHandler);
|
||||
errors.install();
|
||||
|
||||
expect(globals.listeners.rejectionHandled).toEqual([
|
||||
jasmine.any(Function)
|
||||
]);
|
||||
expect(globals.listeners.rejectionHandled).not.toEqual([
|
||||
originalHandler
|
||||
]);
|
||||
|
||||
errors.uninstall();
|
||||
expect(globals.listeners.rejectionHandled).toEqual([originalHandler]);
|
||||
});
|
||||
|
||||
describe("When the unhandledRejection event doesn't have a promise", function() {
|
||||
it('immediately reports the rejection', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
dispatchEvent(
|
||||
globals.listeners,
|
||||
'unhandledRejection',
|
||||
new Error('nope'),
|
||||
undefined
|
||||
);
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(new Error('nope'), undefined);
|
||||
expect(handler.calls.argsFor(0)[0].jasmineMessage).toBe(
|
||||
'Unhandled promise rejection: Error: nope'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the unhandledRejection event has a promise property', function() {
|
||||
it('does not immediately report the rejection', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const promise = Promise.reject('nope');
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(
|
||||
globals.listeners,
|
||||
'unhandledRejection',
|
||||
'nope',
|
||||
promise
|
||||
);
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('When reportUnhandledRejections is called', function() {
|
||||
it('reports rejections that have not been handled', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const reason = new Error('nope');
|
||||
const promise = Promise.reject(reason);
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(
|
||||
globals.listeners,
|
||||
'unhandledRejection',
|
||||
reason,
|
||||
promise
|
||||
);
|
||||
errors.reportUnhandledRejections();
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(new Error('nope'), undefined);
|
||||
expect(handler.calls.argsFor(0)[0].jasmineMessage).toBe(
|
||||
'Unhandled promise rejection: Error: nope'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not report rejections that have been handled', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const reason = new Error('nope');
|
||||
const promise = Promise.reject(reason);
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(
|
||||
globals.listeners,
|
||||
'unhandledRejection',
|
||||
reason,
|
||||
promise
|
||||
);
|
||||
dispatchEvent(globals.listeners, 'rejectionHandled', promise);
|
||||
errors.reportUnhandledRejections();
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not report the same rejection on subsequent calls', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const promise = Promise.reject('nope');
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(
|
||||
globals.listeners,
|
||||
'unhandledRejection',
|
||||
'nope',
|
||||
promise
|
||||
);
|
||||
errors.reportUnhandledRejections();
|
||||
expect(handler).toHaveBeenCalled();
|
||||
handler.calls.reset();
|
||||
|
||||
errors.reportUnhandledRejections();
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Reporting unhandled promise rejections in the browser', function() {
|
||||
it('subscribes and unsubscribes from the unhandledrejection event', function() {
|
||||
const globals = browserGlobals();
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
|
||||
errors.install();
|
||||
expect(globals.listeners.unhandledrejection).toEqual([
|
||||
@@ -229,7 +396,10 @@ describe('GlobalErrors', function() {
|
||||
it('reports rejections whose reason is a string', function() {
|
||||
const globals = browserGlobals();
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
@@ -246,7 +416,10 @@ describe('GlobalErrors', function() {
|
||||
it('reports rejections whose reason is an Error', function() {
|
||||
const globals = browserGlobals();
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
@@ -264,12 +437,129 @@ describe('GlobalErrors', function() {
|
||||
event
|
||||
);
|
||||
});
|
||||
|
||||
describe('When detectLateRejectionHandling is true', function() {
|
||||
let globals, errors;
|
||||
|
||||
beforeEach(function() {
|
||||
globals = browserGlobals();
|
||||
errors = new jasmineUnderTest.GlobalErrors(globals.global, () => ({
|
||||
detectLateRejectionHandling: true
|
||||
}));
|
||||
});
|
||||
|
||||
it('subscribes and unsubscribes from the rejectionhandled event', function() {
|
||||
errors.install();
|
||||
expect(globals.listeners.rejectionhandled).toEqual([
|
||||
jasmine.any(Function)
|
||||
]);
|
||||
|
||||
errors.uninstall();
|
||||
expect(globals.listeners.rejectionhandled).toEqual([]);
|
||||
});
|
||||
|
||||
describe("When the unhandledrejection event doesn't have a promise property", function() {
|
||||
it('immediately reports the rejection', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const event = { reason: 'nope' };
|
||||
dispatchEvent(globals.listeners, 'unhandledrejection', event);
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(
|
||||
'Unhandled promise rejection: nope',
|
||||
event
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the unhandledrejection event has a promise property', function() {
|
||||
it('does not immediately report the rejection', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const promise = Promise.reject('nope');
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(globals.listeners, 'unhandledrejection', {
|
||||
reason: 'nope',
|
||||
promise
|
||||
});
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('When reportUnhandledRejections is called', function() {
|
||||
it('reports rejections that have not been handled', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const promise = Promise.reject('nope');
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(globals.listeners, 'unhandledrejection', {
|
||||
reason: 'nope',
|
||||
promise
|
||||
});
|
||||
errors.reportUnhandledRejections();
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(
|
||||
'Unhandled promise rejection: nope',
|
||||
{ reason: 'nope', promise }
|
||||
);
|
||||
});
|
||||
|
||||
it('does not report rejections that have been handled', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const promise = Promise.reject('nope');
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(globals.listeners, 'unhandledrejection', {
|
||||
reason: 'nope',
|
||||
promise
|
||||
});
|
||||
dispatchEvent(globals.listeners, 'rejectionhandled', { promise });
|
||||
errors.reportUnhandledRejections();
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not report the same rejection on subsequent calls', function() {
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const promise = Promise.reject('nope');
|
||||
promise.catch(() => {});
|
||||
dispatchEvent(globals.listeners, 'unhandledrejection', {
|
||||
reason: 'nope',
|
||||
promise
|
||||
});
|
||||
errors.reportUnhandledRejections();
|
||||
expect(handler).toHaveBeenCalled();
|
||||
handler.calls.reset();
|
||||
|
||||
errors.reportUnhandledRejections();
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Reporting uncaught exceptions in node.js', function() {
|
||||
it('prepends a descriptive message when the error is not an `Error`', function() {
|
||||
const globals = nodeGlobals();
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
@@ -285,7 +575,10 @@ describe('GlobalErrors', function() {
|
||||
|
||||
it('substitutes a descriptive message when the error is falsy', function() {
|
||||
const globals = nodeGlobals();
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
|
||||
errors.install();
|
||||
@@ -306,7 +599,10 @@ describe('GlobalErrors', function() {
|
||||
const handler0 = jasmine.createSpy('handler0');
|
||||
const handler1 = jasmine.createSpy('handler1');
|
||||
const overrideHandler = jasmine.createSpy('overrideHandler');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler0);
|
||||
@@ -331,7 +627,10 @@ describe('GlobalErrors', function() {
|
||||
const handler0 = jasmine.createSpy('handler0');
|
||||
const handler1 = jasmine.createSpy('handler1');
|
||||
const overrideHandler = jasmine.createSpy('overrideHandler');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler0);
|
||||
@@ -356,7 +655,10 @@ describe('GlobalErrors', function() {
|
||||
const globals = browserGlobals();
|
||||
const handler = jasmine.createSpy('handler');
|
||||
const overrideHandler = jasmine.createSpy('overrideHandler');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
@@ -381,7 +683,10 @@ describe('GlobalErrors', function() {
|
||||
const handler0 = jasmine.createSpy('handler0');
|
||||
const handler1 = jasmine.createSpy('handler1');
|
||||
const overrideHandler = jasmine.createSpy('overrideHandler');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(globals.global);
|
||||
const errors = new jasmineUnderTest.GlobalErrors(
|
||||
globals.global,
|
||||
() => ({})
|
||||
);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler0);
|
||||
@@ -424,7 +729,11 @@ describe('GlobalErrors', function() {
|
||||
});
|
||||
|
||||
function browserGlobals() {
|
||||
const listeners = { error: [], unhandledrejection: [] };
|
||||
const listeners = {
|
||||
error: [],
|
||||
unhandledrejection: [],
|
||||
rejectionhandled: []
|
||||
};
|
||||
return {
|
||||
listeners,
|
||||
global: {
|
||||
@@ -441,7 +750,11 @@ describe('GlobalErrors', function() {
|
||||
}
|
||||
|
||||
function nodeGlobals() {
|
||||
const listeners = { uncaughtException: [], unhandledRejection: [] };
|
||||
const listeners = {
|
||||
uncaughtException: [],
|
||||
unhandledRejection: [],
|
||||
rejectionHandled: []
|
||||
};
|
||||
return {
|
||||
listeners,
|
||||
global: {
|
||||
@@ -465,13 +778,13 @@ describe('GlobalErrors', function() {
|
||||
};
|
||||
}
|
||||
|
||||
function dispatchEvent(listeners, eventName, event) {
|
||||
function dispatchEvent(listeners, eventName, ...args) {
|
||||
expect(listeners[eventName].length)
|
||||
.withContext(`number of ${eventName} listeners`)
|
||||
.toBeGreaterThan(0);
|
||||
|
||||
for (const l of listeners[eventName]) {
|
||||
l(event);
|
||||
l.apply(null, args);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
describe('QueueRunner', function() {
|
||||
it('validates that queueableFns are truthy', function() {
|
||||
expect(function() {
|
||||
new jasmineUnderTest.QueueRunner({
|
||||
queueableFns: [undefined]
|
||||
});
|
||||
}).toThrowError('Received a falsy queueableFn');
|
||||
});
|
||||
|
||||
it('validates that queueableFns have fn properties', function() {
|
||||
expect(function() {
|
||||
new jasmineUnderTest.QueueRunner({
|
||||
queueableFns: [{ fn: undefined }]
|
||||
});
|
||||
}).toThrowError('Received a queueableFn with no fn');
|
||||
});
|
||||
|
||||
it("runs all the functions it's passed", function() {
|
||||
const calls = [],
|
||||
queueableFn1 = { fn: jasmine.createSpy('fn1') },
|
||||
|
||||
@@ -12,10 +12,10 @@ describe('ReportDispatcher', function() {
|
||||
});
|
||||
|
||||
it('dispatches requested methods to added reporters', function() {
|
||||
const queueRunnerFactory = jasmine.createSpy('queueRunner'),
|
||||
const runQueue = jasmine.createSpy('runQueue'),
|
||||
dispatcher = new jasmineUnderTest.ReportDispatcher(
|
||||
['foo', 'bar'],
|
||||
queueRunnerFactory
|
||||
runQueue
|
||||
),
|
||||
reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']),
|
||||
anotherReporter = jasmine.createSpyObj('reporter', ['foo', 'bar']);
|
||||
@@ -25,7 +25,7 @@ describe('ReportDispatcher', function() {
|
||||
|
||||
dispatcher.foo(123, 456);
|
||||
|
||||
expect(queueRunnerFactory).toHaveBeenCalledWith(
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
queueableFns: [
|
||||
{ fn: jasmine.any(Function) },
|
||||
@@ -35,7 +35,7 @@ describe('ReportDispatcher', function() {
|
||||
})
|
||||
);
|
||||
|
||||
let fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
|
||||
let fns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
fns[0].fn();
|
||||
expect(reporter.foo).toHaveBeenCalledWith(123, 456);
|
||||
expect(reporter.foo.calls.mostRecent().object).toBe(reporter);
|
||||
@@ -44,11 +44,11 @@ describe('ReportDispatcher', function() {
|
||||
expect(anotherReporter.foo).toHaveBeenCalledWith(123, 456);
|
||||
expect(anotherReporter.foo.calls.mostRecent().object).toBe(anotherReporter);
|
||||
|
||||
queueRunnerFactory.calls.reset();
|
||||
runQueue.calls.reset();
|
||||
|
||||
dispatcher.bar('a', 'b');
|
||||
|
||||
expect(queueRunnerFactory).toHaveBeenCalledWith(
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
queueableFns: [
|
||||
{ fn: jasmine.any(Function) },
|
||||
@@ -58,7 +58,7 @@ describe('ReportDispatcher', function() {
|
||||
})
|
||||
);
|
||||
|
||||
fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
|
||||
fns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
fns[0].fn();
|
||||
expect(reporter.bar).toHaveBeenCalledWith('a', 'b');
|
||||
|
||||
@@ -67,17 +67,14 @@ describe('ReportDispatcher', function() {
|
||||
});
|
||||
|
||||
it("does not dispatch to a reporter if the reporter doesn't accept the method", function() {
|
||||
const queueRunnerFactory = jasmine.createSpy('queueRunner'),
|
||||
dispatcher = new jasmineUnderTest.ReportDispatcher(
|
||||
['foo'],
|
||||
queueRunnerFactory
|
||||
),
|
||||
const runQueue = jasmine.createSpy('runQueue'),
|
||||
dispatcher = new jasmineUnderTest.ReportDispatcher(['foo'], runQueue),
|
||||
reporter = jasmine.createSpyObj('reporter', ['baz']);
|
||||
|
||||
dispatcher.addReporter(reporter);
|
||||
|
||||
dispatcher.foo(123, 456);
|
||||
expect(queueRunnerFactory).toHaveBeenCalledWith(
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
queueableFns: []
|
||||
})
|
||||
@@ -85,33 +82,33 @@ describe('ReportDispatcher', function() {
|
||||
});
|
||||
|
||||
it("allows providing a fallback reporter in case there's no other reporter", function() {
|
||||
const queueRunnerFactory = jasmine.createSpy('queueRunner'),
|
||||
const runQueue = jasmine.createSpy('runQueue'),
|
||||
dispatcher = new jasmineUnderTest.ReportDispatcher(
|
||||
['foo', 'bar'],
|
||||
queueRunnerFactory
|
||||
runQueue
|
||||
),
|
||||
reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']);
|
||||
|
||||
dispatcher.provideFallbackReporter(reporter);
|
||||
dispatcher.foo(123, 456);
|
||||
|
||||
expect(queueRunnerFactory).toHaveBeenCalledWith(
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
queueableFns: [{ fn: jasmine.any(Function) }],
|
||||
isReporter: true
|
||||
})
|
||||
);
|
||||
|
||||
const fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
|
||||
const fns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
fns[0].fn();
|
||||
expect(reporter.foo).toHaveBeenCalledWith(123, 456);
|
||||
});
|
||||
|
||||
it('does not call fallback reporting methods when another reporter is provided', function() {
|
||||
const queueRunnerFactory = jasmine.createSpy('queueRunner'),
|
||||
const runQueue = jasmine.createSpy('runQueue'),
|
||||
dispatcher = new jasmineUnderTest.ReportDispatcher(
|
||||
['foo', 'bar'],
|
||||
queueRunnerFactory
|
||||
runQueue
|
||||
),
|
||||
reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']),
|
||||
fallbackReporter = jasmine.createSpyObj('otherReporter', ['foo', 'bar']);
|
||||
@@ -120,38 +117,38 @@ describe('ReportDispatcher', function() {
|
||||
dispatcher.addReporter(reporter);
|
||||
dispatcher.foo(123, 456);
|
||||
|
||||
expect(queueRunnerFactory).toHaveBeenCalledWith(
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
queueableFns: [{ fn: jasmine.any(Function) }],
|
||||
isReporter: true
|
||||
})
|
||||
);
|
||||
|
||||
const fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
|
||||
const fns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
fns[0].fn();
|
||||
expect(reporter.foo).toHaveBeenCalledWith(123, 456);
|
||||
expect(fallbackReporter.foo).not.toHaveBeenCalledWith(123, 456);
|
||||
});
|
||||
|
||||
it('allows registered reporters to be cleared', function() {
|
||||
const queueRunnerFactory = jasmine.createSpy('queueRunner'),
|
||||
const runQueue = jasmine.createSpy('runQueue'),
|
||||
dispatcher = new jasmineUnderTest.ReportDispatcher(
|
||||
['foo', 'bar'],
|
||||
queueRunnerFactory
|
||||
runQueue
|
||||
),
|
||||
reporter1 = jasmine.createSpyObj('reporter1', ['foo', 'bar']),
|
||||
reporter2 = jasmine.createSpyObj('reporter2', ['foo', 'bar']);
|
||||
|
||||
dispatcher.addReporter(reporter1);
|
||||
dispatcher.foo(123);
|
||||
expect(queueRunnerFactory).toHaveBeenCalledWith(
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
queueableFns: [{ fn: jasmine.any(Function) }],
|
||||
isReporter: true
|
||||
})
|
||||
);
|
||||
|
||||
let fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
|
||||
let fns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
fns[0].fn();
|
||||
expect(reporter1.foo).toHaveBeenCalledWith(123);
|
||||
|
||||
@@ -159,14 +156,14 @@ describe('ReportDispatcher', function() {
|
||||
dispatcher.addReporter(reporter2);
|
||||
dispatcher.bar(456);
|
||||
|
||||
expect(queueRunnerFactory).toHaveBeenCalledWith(
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
queueableFns: [{ fn: jasmine.any(Function) }],
|
||||
isReporter: true
|
||||
})
|
||||
);
|
||||
|
||||
fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
|
||||
fns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
fns[0].fn();
|
||||
expect(reporter1.bar).not.toHaveBeenCalled();
|
||||
expect(reporter2.bar).toHaveBeenCalledWith(456);
|
||||
|
||||
630
spec/core/RunnerSpec.js
Normal file
630
spec/core/RunnerSpec.js
Normal file
@@ -0,0 +1,630 @@
|
||||
describe('Runner', function() {
|
||||
describe('Integration with TreeProcessor and TreeRunner', function() {
|
||||
let suiteNumber,
|
||||
specNumber,
|
||||
runQueue,
|
||||
globalErrors,
|
||||
reportDispatcher,
|
||||
failSpecWithNoExpectations,
|
||||
detectLateRejectionHandling;
|
||||
|
||||
beforeEach(function() {
|
||||
suiteNumber = 0;
|
||||
specNumber = 0;
|
||||
runQueue = jasmine.createSpy('runQueue');
|
||||
globalErrors = 'the global errors instance';
|
||||
reportDispatcher = jasmine.createSpyObj(
|
||||
'reportDispatcher',
|
||||
jasmineUnderTest.reporterEvents
|
||||
);
|
||||
|
||||
for (const k of jasmineUnderTest.reporterEvents) {
|
||||
reportDispatcher[k].and.returnValue(Promise.resolve());
|
||||
}
|
||||
|
||||
// Reasonable defaults, may be overridden in some cases
|
||||
failSpecWithNoExpectations = false;
|
||||
detectLateRejectionHandling = false;
|
||||
|
||||
spyOn(jasmineUnderTest.TreeRunner.prototype, '_executeSpec');
|
||||
});
|
||||
|
||||
function StubSuite(attrs) {
|
||||
attrs = attrs || {};
|
||||
this.id = 'suite' + suiteNumber++;
|
||||
this.children = attrs.children || [];
|
||||
this.canBeReentered = function() {
|
||||
return !attrs.noReenter;
|
||||
};
|
||||
this.markedPending = attrs.markedPending || false;
|
||||
this.sharedUserContext = function() {
|
||||
return attrs.userContext || {};
|
||||
};
|
||||
this.result = {
|
||||
id: this.id,
|
||||
failedExpectations: []
|
||||
};
|
||||
this.getResult = jasmine.createSpy('getResult');
|
||||
this.beforeAllFns = attrs.beforeAllFns || [];
|
||||
this.afterAllFns = attrs.afterAllFns || [];
|
||||
this.cleanupBeforeAfter = function() {};
|
||||
this.startTimer = function() {};
|
||||
this.endTimer = function() {};
|
||||
}
|
||||
|
||||
function StubSpec(attrs) {
|
||||
attrs = attrs || {};
|
||||
this.id = 'spec' + specNumber++;
|
||||
this.markedPending = attrs.markedPending || false;
|
||||
this.execute = jasmine.createSpy(this.id + '#execute');
|
||||
this.beforeAndAfterFns = () => ({ befores: [], afters: [] });
|
||||
this.userContext = () => ({});
|
||||
this.getFullName = () => '';
|
||||
this.queueableFn = () => {};
|
||||
}
|
||||
|
||||
function makeRunner(topSuite) {
|
||||
const defaultOptions = {
|
||||
getConfig: () => ({
|
||||
specFilter: () => true,
|
||||
failSpecWithNoExpectations,
|
||||
detectLateRejectionHandling
|
||||
}),
|
||||
focusedRunables: () => [],
|
||||
totalSpecsDefined: () => 1,
|
||||
TreeProcessor: jasmineUnderTest.TreeProcessor,
|
||||
runableResources: {
|
||||
initForRunable: () => {},
|
||||
clearForRunable: () => {}
|
||||
},
|
||||
reportDispatcher,
|
||||
globalErrors,
|
||||
runQueue
|
||||
};
|
||||
return new jasmineUnderTest.Runner({
|
||||
...defaultOptions,
|
||||
topSuite
|
||||
});
|
||||
}
|
||||
|
||||
function arrayNotContaining(item) {
|
||||
return {
|
||||
asymmetricMatch(other, matchersUtil) {
|
||||
if (!jasmine.isArray_(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const x of other) {
|
||||
if (matchersUtil.equals(x, item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Precondition: jasmineUnderTest.TreeRunner.prototype._executeSpec is a spy
|
||||
function verifyAndFinishSpec(spec, queueableFn, shouldBeExcluded) {
|
||||
const ex = jasmineUnderTest.TreeRunner.prototype._executeSpec;
|
||||
ex.withArgs(spec, 'onComplete').and.callThrough();
|
||||
|
||||
queueableFn.fn('onComplete');
|
||||
expect(ex).toHaveBeenCalledWith(spec, 'onComplete');
|
||||
|
||||
expect(runQueue).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
isLeaf: true,
|
||||
SkipPolicy: jasmineUnderTest.CompleteOnFirstErrorSkipPolicy,
|
||||
queueableFns: shouldBeExcluded
|
||||
? arrayNotContaining(spec.queueableFn)
|
||||
: jasmine.arrayContaining([spec.queueableFn])
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
it('runs a single spec', async function() {
|
||||
const spec = new StubSpec();
|
||||
const topSuite = new StubSuite({
|
||||
children: [spec],
|
||||
userContext: { root: 'context' }
|
||||
});
|
||||
detectLateRejectionHandling = true;
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
|
||||
expect(runQueue).toHaveBeenCalledWith({
|
||||
onComplete: jasmine.any(Function),
|
||||
onException: jasmine.any(Function),
|
||||
userContext: { root: 'context' },
|
||||
queueableFns: [{ fn: jasmine.any(Function) }],
|
||||
onMultipleDone: null,
|
||||
SkipPolicy: jasmineUnderTest.SkipAfterBeforeAllErrorPolicy
|
||||
});
|
||||
|
||||
const runQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
verifyAndFinishSpec(spec, runQueueArgs.queueableFns[0], false);
|
||||
runQueueArgs.onComplete();
|
||||
await promise;
|
||||
});
|
||||
|
||||
it('runs an empty suite', async function() {
|
||||
const suite = new StubSuite({ userContext: { for: 'suite' } });
|
||||
const topSuite = new StubSuite({
|
||||
children: [suite],
|
||||
userContext: { for: 'topSuite' }
|
||||
});
|
||||
suite.parentSuite = topSuite;
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
|
||||
expect(runQueue).toHaveBeenCalledWith({
|
||||
onComplete: jasmine.any(Function),
|
||||
onException: jasmine.any(Function),
|
||||
userContext: { for: 'topSuite' },
|
||||
queueableFns: [{ fn: jasmine.any(Function) }],
|
||||
onMultipleDone: null,
|
||||
SkipPolicy: jasmineUnderTest.SkipAfterBeforeAllErrorPolicy
|
||||
});
|
||||
|
||||
const runQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
const nodeDone = jasmine.createSpy('nodeDone');
|
||||
runQueueArgs.queueableFns[0].fn(nodeDone);
|
||||
expect(runQueue).toHaveBeenCalledWith({
|
||||
onComplete: jasmine.any(Function),
|
||||
onMultipleDone: null,
|
||||
queueableFns: [{ fn: jasmine.any(Function) }],
|
||||
userContext: { for: 'suite' },
|
||||
onException: jasmine.any(Function),
|
||||
onMultipleDone: null,
|
||||
SkipPolicy: jasmineUnderTest.SkipAfterBeforeAllErrorPolicy
|
||||
});
|
||||
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[0].fn('foo');
|
||||
expect(reportDispatcher.suiteStarted).toHaveBeenCalledWith(suite.result);
|
||||
|
||||
suite.getResult.and.returnValue({ my: 'result' });
|
||||
|
||||
runQueue.calls.mostRecent().args[0].onComplete();
|
||||
expect(reportDispatcher.suiteDone).toHaveBeenCalledWith({ my: 'result' });
|
||||
|
||||
runQueueArgs.onComplete();
|
||||
await promise;
|
||||
});
|
||||
|
||||
it('runs a non-empty suite', async function() {
|
||||
const spec1 = new StubSpec();
|
||||
const spec2 = new StubSpec();
|
||||
const suite = new StubSuite({ children: [spec1, spec2] });
|
||||
const topSuite = new StubSuite({ children: [suite] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
let queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(2);
|
||||
queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
expect(queueableFns.length).toBe(3);
|
||||
|
||||
verifyAndFinishSpec(spec1, queueableFns[1], false);
|
||||
verifyAndFinishSpec(spec2, queueableFns[2], false);
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('"runs" an excluded suite', async function() {
|
||||
const spec = new StubSpec();
|
||||
const parent = new StubSuite({ children: [spec] });
|
||||
const topSuite = new StubSuite({ children: [parent] });
|
||||
parent.parentSuite = topSuite;
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
// Empty list of runable IDs excludes everything
|
||||
const promise = subject.execute([]);
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
let queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn(function() {});
|
||||
|
||||
queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
expect(queueableFns.length).toBe(2);
|
||||
|
||||
queueableFns[0].fn();
|
||||
expect(reportDispatcher.suiteStarted).toHaveBeenCalledWith(parent.result);
|
||||
|
||||
verifyAndFinishSpec(spec, queueableFns[1], true);
|
||||
|
||||
parent.getResult.and.returnValue(parent.result);
|
||||
runQueue.calls.argsFor(1)[0].onComplete();
|
||||
expect(reportDispatcher.suiteDone).toHaveBeenCalledWith(parent.result);
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('handles the failSpecWithNoExpectations option', async function() {
|
||||
failSpecWithNoExpectations = true;
|
||||
const spec = new StubSpec();
|
||||
const parent = new StubSuite({ children: [spec] });
|
||||
const topSuite = new StubSuite({ children: [parent] });
|
||||
parent.parentSuite = topSuite;
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
let queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn(function() {});
|
||||
|
||||
queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
expect(queueableFns.length).toBe(2);
|
||||
|
||||
queueableFns[1].fn('foo');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec, 'foo');
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs beforeAlls and afterAlls for a suite with children', async function() {
|
||||
const spec = new StubSpec();
|
||||
const target = new StubSuite({
|
||||
children: [spec],
|
||||
beforeAllFns: [
|
||||
{ fn: 'beforeAll1', timeout: 1 },
|
||||
{ fn: 'beforeAll2', timeout: 2 }
|
||||
],
|
||||
afterAllFns: [
|
||||
{ fn: 'afterAll1', timeout: 3 },
|
||||
{ fn: 'afterAll2', timeout: 4 }
|
||||
]
|
||||
});
|
||||
const topSuite = new StubSuite({ children: [target] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns).toEqual([
|
||||
{ fn: jasmine.any(Function) },
|
||||
{ fn: 'beforeAll1', timeout: 1 },
|
||||
{ fn: 'beforeAll2', timeout: 2 },
|
||||
{ fn: jasmine.any(Function) },
|
||||
{ fn: 'afterAll1', timeout: 3 },
|
||||
{ fn: 'afterAll2', timeout: 4 }
|
||||
]);
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('does not run beforeAlls or afterAlls for a suite with no children', async function() {
|
||||
const target = new StubSuite({
|
||||
beforeAllFns: [{ fn: 'before' }],
|
||||
afterAllFns: [{ fn: 'after' }]
|
||||
});
|
||||
const topSuite = new StubSuite({ children: [target] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toEqual(
|
||||
1
|
||||
);
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('does not run beforeAlls or afterAlls for a suite with only pending children', async function() {
|
||||
const spec = new StubSpec({ markedPending: true });
|
||||
const target = new StubSuite({
|
||||
children: [spec],
|
||||
beforeAllFns: [{ fn: 'before' }],
|
||||
afterAllFns: [{ fn: 'after' }]
|
||||
});
|
||||
const topSuite = new StubSuite({ children: [target] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn(function() {});
|
||||
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toEqual(
|
||||
2
|
||||
);
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs specs in the order specified', async function() {
|
||||
const specs = [new StubSpec(), new StubSpec()];
|
||||
const topSuite = new StubSuite({ children: specs });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute([specs[1].id, specs[0].id]);
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn('done');
|
||||
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).not.toHaveBeenCalledWith(specs[0], jasmine.anything());
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(specs[1], 'done');
|
||||
|
||||
queueableFns[1].fn('done');
|
||||
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(specs[0], 'done');
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs specified specs before non-specified specs within a suite', async function() {
|
||||
const specified = new StubSpec();
|
||||
const nonSpecified = new StubSpec();
|
||||
const topSuite = new StubSuite({ children: [nonSpecified, specified] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute([specified.id]);
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn('done');
|
||||
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).not.toHaveBeenCalledWith(nonSpecified, jasmine.anything());
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(specified, 'done');
|
||||
|
||||
queueableFns[1].fn('done');
|
||||
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(nonSpecified, 'done');
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs suites and specs with a specified order', async function() {
|
||||
const specifiedSpec = new StubSpec();
|
||||
const nonSpecifiedSpec = new StubSpec();
|
||||
const specifiedSuite = new StubSuite({ children: [nonSpecifiedSpec] });
|
||||
const topSuite = new StubSuite({
|
||||
children: [specifiedSpec, specifiedSuite]
|
||||
});
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute([specifiedSuite.id, specifiedSpec.id]);
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
queueableFns[0].fn();
|
||||
|
||||
expect(specifiedSpec.execute).not.toHaveBeenCalled();
|
||||
const nodeQueueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
nodeQueueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(nonSpecifiedSpec, 'done');
|
||||
|
||||
queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(specifiedSpec, 'done');
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs suites and specs in the order they were declared', async function() {
|
||||
const spec1 = new StubSpec();
|
||||
const spec2 = new StubSpec();
|
||||
const spec3 = new StubSpec();
|
||||
const parent = new StubSuite({ children: [spec2, spec3] });
|
||||
const topSuite = new StubSuite({ children: [spec1, parent] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
expect(queueableFns.length).toBe(2);
|
||||
|
||||
queueableFns[0].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec1, 'done');
|
||||
|
||||
queueableFns[1].fn();
|
||||
const childFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
expect(childFns.length).toBe(3);
|
||||
childFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec2, 'done');
|
||||
|
||||
childFns[2].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec3, 'done');
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs a suite multiple times if the order specified leaves and re-enters it', async function() {
|
||||
const spec1 = new StubSpec();
|
||||
const spec2 = new StubSpec();
|
||||
const spec3 = new StubSpec();
|
||||
const spec4 = new StubSpec();
|
||||
const spec5 = new StubSpec();
|
||||
const reentered = new StubSuite({ children: [spec1, spec2, spec3] });
|
||||
const topSuite = new StubSuite({ children: [reentered, spec4, spec5] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
spyOn(jasmineUnderTest.getEnv(), 'deprecated');
|
||||
const promise = subject.execute([
|
||||
spec1.id,
|
||||
spec4.id,
|
||||
spec2.id,
|
||||
spec5.id,
|
||||
spec3.id
|
||||
]);
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
|
||||
queueableFns[0].fn();
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec1, 'done');
|
||||
|
||||
queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec4, 'done');
|
||||
|
||||
queueableFns[2].fn();
|
||||
expect(runQueue.calls.count()).toBe(3);
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec2, 'done');
|
||||
|
||||
queueableFns[3].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec5, 'done');
|
||||
|
||||
queueableFns[4].fn();
|
||||
expect(runQueue.calls.count()).toBe(4);
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec3, 'done');
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs a parent of a suite with multiple segments correctly', async function() {
|
||||
const spec1 = new StubSpec();
|
||||
const spec2 = new StubSpec();
|
||||
const spec3 = new StubSpec();
|
||||
const spec4 = new StubSpec();
|
||||
const spec5 = new StubSpec();
|
||||
const parent = new StubSuite({ children: [spec1, spec2, spec3] });
|
||||
const grandparent = new StubSuite({ children: [parent] });
|
||||
const topSuite = new StubSuite({ children: [grandparent, spec4, spec5] });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
spyOn(jasmineUnderTest.getEnv(), 'deprecated');
|
||||
const promise = subject.execute([
|
||||
spec1.id,
|
||||
spec4.id,
|
||||
spec2.id,
|
||||
spec5.id,
|
||||
spec3.id
|
||||
]);
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
expect(queueableFns.length).toBe(5);
|
||||
|
||||
queueableFns[0].fn();
|
||||
expect(runQueue.calls.count()).toBe(2);
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
|
||||
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn();
|
||||
expect(runQueue.calls.count()).toBe(3);
|
||||
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec1, 'done');
|
||||
|
||||
queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec4, 'done');
|
||||
|
||||
queueableFns[2].fn();
|
||||
expect(runQueue.calls.count()).toBe(4);
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
|
||||
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn();
|
||||
expect(runQueue.calls.count()).toBe(5);
|
||||
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec2, 'done');
|
||||
|
||||
queueableFns[3].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec5, 'done');
|
||||
|
||||
queueableFns[4].fn();
|
||||
expect(runQueue.calls.count()).toBe(6);
|
||||
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
|
||||
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn();
|
||||
expect(runQueue.calls.count()).toBe(7);
|
||||
|
||||
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(spec3, 'done');
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
|
||||
it('runs large segments of nodes in the order they were declared', async function() {
|
||||
const specs = [];
|
||||
|
||||
for (let i = 0; i < 11; i++) {
|
||||
specs.push(new StubSpec());
|
||||
}
|
||||
|
||||
const topSuite = new StubSuite({ children: specs });
|
||||
const subject = makeRunner(topSuite);
|
||||
|
||||
const promise = subject.execute();
|
||||
await Promise.resolve();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
|
||||
expect(queueableFns.length).toBe(11);
|
||||
|
||||
for (let i = 0; i < 11; i++) {
|
||||
queueableFns[i].fn('done');
|
||||
expect(
|
||||
jasmineUnderTest.TreeRunner.prototype._executeSpec
|
||||
).toHaveBeenCalledWith(specs[i], 'done');
|
||||
}
|
||||
|
||||
await expectAsync(promise).toBePending();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -33,112 +33,6 @@ describe('Spec', function() {
|
||||
expect(jasmineUnderTest.Spec.isPendingSpecException(void 0)).toBe(false);
|
||||
});
|
||||
|
||||
it('delegates execution to a QueueRunner', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
description: 'my test',
|
||||
id: 'some-id',
|
||||
queueableFn: { fn: function() {} }
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
expect(fakeQueueRunner).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call the start callback on execution', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
|
||||
startCallback = jasmine.createSpy('startCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
id: 123,
|
||||
description: 'foo bar',
|
||||
queueableFn: { fn: function() {} },
|
||||
onStart: startCallback
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
fakeQueueRunner.calls.mostRecent().args[0].queueableFns[0].fn();
|
||||
expect(startCallback).toHaveBeenCalled();
|
||||
expect(startCallback.calls.first().object).toEqual(spec);
|
||||
});
|
||||
|
||||
it('should call the start callback on execution but before any befores are called', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('fakeQueueRunner');
|
||||
let beforesWereCalled = false;
|
||||
const startCallback = jasmine
|
||||
.createSpy('start-callback')
|
||||
.and.callFake(function() {
|
||||
expect(beforesWereCalled).toBe(false);
|
||||
});
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: function() {} },
|
||||
beforeFns: function() {
|
||||
return [
|
||||
function() {
|
||||
beforesWereCalled = true;
|
||||
}
|
||||
];
|
||||
},
|
||||
onStart: startCallback
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
fakeQueueRunner.calls.mostRecent().args[0].queueableFns[0].fn();
|
||||
expect(startCallback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('provides all before fns and after fns to be run', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
|
||||
before = jasmine.createSpy('before'),
|
||||
after = jasmine.createSpy('after'),
|
||||
queueableFn = {
|
||||
fn: jasmine.createSpy('test body').and.callFake(function() {
|
||||
expect(before).toHaveBeenCalled();
|
||||
expect(after).not.toHaveBeenCalled();
|
||||
})
|
||||
},
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: queueableFn,
|
||||
beforeAndAfterFns: function() {
|
||||
return { befores: [before], afters: [after] };
|
||||
}
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
const options = fakeQueueRunner.calls.mostRecent().args[0];
|
||||
expect(options.queueableFns).toEqual([
|
||||
{ fn: jasmine.any(Function) },
|
||||
before,
|
||||
queueableFn,
|
||||
after,
|
||||
{
|
||||
fn: jasmine.any(Function),
|
||||
type: 'specCleanup'
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it("tells the queue runner that it's a leaf node", function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: function() {} },
|
||||
beforeAndAfterFns: function() {
|
||||
return { befores: [], afters: [] };
|
||||
}
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
expect(fakeQueueRunner).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
isLeaf: true
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('is marked pending if created without a function body', function() {
|
||||
const startCallback = jasmine.createSpy('startCallback'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
@@ -151,166 +45,75 @@ describe('Spec', function() {
|
||||
expect(spec.status()).toBe('pending');
|
||||
});
|
||||
|
||||
it('can be excluded at execution time by a parent', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
|
||||
startCallback = jasmine.createSpy('startCallback'),
|
||||
specBody = jasmine.createSpy('specBody'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
onStart: startCallback,
|
||||
queueableFn: { fn: specBody },
|
||||
resultCallback: resultCallback
|
||||
describe('#executionFinished', function() {
|
||||
it('removes the fn if autoCleanClosures is true', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} },
|
||||
autoCleanClosures: true
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner, 'cally-back', true);
|
||||
|
||||
expect(fakeQueueRunner).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
onComplete: jasmine.any(Function),
|
||||
queueableFns: [
|
||||
{ fn: jasmine.any(Function) },
|
||||
{
|
||||
fn: jasmine.any(Function),
|
||||
type: 'specCleanup'
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
expect(specBody).not.toHaveBeenCalled();
|
||||
|
||||
const args = fakeQueueRunner.calls.mostRecent().args[0];
|
||||
args.queueableFns[0].fn();
|
||||
expect(startCallback).toHaveBeenCalled();
|
||||
args.queueableFns[args.queueableFns.length - 1].fn();
|
||||
expect(resultCallback).toHaveBeenCalled();
|
||||
|
||||
expect(spec.result.status).toBe('excluded');
|
||||
});
|
||||
|
||||
it('can be marked pending, but still calls callbacks when executed', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
|
||||
startCallback = jasmine.createSpy('startCallback'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
onStart: startCallback,
|
||||
resultCallback: resultCallback,
|
||||
description: 'with a spec',
|
||||
parentSuiteId: 'suite1',
|
||||
filename: 'someSpecFile.js',
|
||||
getPath: function() {
|
||||
return ['a suite', 'with a spec'];
|
||||
},
|
||||
queueableFn: { fn: null }
|
||||
});
|
||||
|
||||
spec.pend();
|
||||
|
||||
expect(spec.status()).toBe('pending');
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
expect(fakeQueueRunner).toHaveBeenCalled();
|
||||
|
||||
const args = fakeQueueRunner.calls.mostRecent().args[0];
|
||||
args.queueableFns[0].fn();
|
||||
expect(startCallback).toHaveBeenCalled();
|
||||
args.queueableFns[1].fn('things');
|
||||
expect(resultCallback).toHaveBeenCalledWith(
|
||||
{
|
||||
id: spec.id,
|
||||
status: 'pending',
|
||||
description: 'with a spec',
|
||||
fullName: 'a suite with a spec',
|
||||
parentSuiteId: 'suite1',
|
||||
filename: 'someSpecFile.js',
|
||||
failedExpectations: [],
|
||||
passedExpectations: [],
|
||||
deprecationWarnings: [],
|
||||
pendingReason: '',
|
||||
duration: jasmine.any(Number),
|
||||
properties: null,
|
||||
debugLogs: null
|
||||
},
|
||||
'things'
|
||||
);
|
||||
});
|
||||
|
||||
it('should call the done callback on execution complete', function() {
|
||||
const done = jasmine.createSpy('done callback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: function() {} },
|
||||
catchExceptions: function() {
|
||||
return false;
|
||||
},
|
||||
resultCallback: function() {}
|
||||
});
|
||||
|
||||
spec.execute(attrs => attrs.onComplete(), done);
|
||||
|
||||
expect(done).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call the done callback with an error if the spec is failed', function() {
|
||||
const done = jasmine.createSpy('done callback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: function() {} },
|
||||
catchExceptions: function() {
|
||||
return false;
|
||||
},
|
||||
resultCallback: function() {}
|
||||
});
|
||||
|
||||
function queueRunnerFactory(attrs) {
|
||||
spec.result.status = 'failed';
|
||||
attrs.onComplete();
|
||||
}
|
||||
spec.execute(queueRunnerFactory, done);
|
||||
|
||||
expect(done).toHaveBeenCalledWith(
|
||||
jasmine.any(jasmineUnderTest.StopExecutionError)
|
||||
);
|
||||
});
|
||||
|
||||
it('should report the duration of the test', function() {
|
||||
const timer = jasmine.createSpyObj('timer', {
|
||||
start: null,
|
||||
elapsed: 77000
|
||||
});
|
||||
let duration = undefined;
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: jasmine.createSpy('spec body') },
|
||||
catchExceptions: function() {
|
||||
return false;
|
||||
},
|
||||
resultCallback: function(result) {
|
||||
duration = result.duration;
|
||||
},
|
||||
timer: timer
|
||||
spec.executionFinished();
|
||||
expect(spec.queueableFn.fn).toBeFalsy();
|
||||
});
|
||||
|
||||
function queueRunnerFactory(config) {
|
||||
config.queueableFns.forEach(function(qf) {
|
||||
qf.fn();
|
||||
it('removes the fn after execution if autoCleanClosures is undefined', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} },
|
||||
autoCleanClosures: undefined
|
||||
});
|
||||
config.onComplete();
|
||||
}
|
||||
|
||||
spec.execute(queueRunnerFactory, function() {});
|
||||
expect(duration).toBe(77000);
|
||||
spec.executionFinished();
|
||||
expect(spec.queueableFn.fn).toBeFalsy();
|
||||
});
|
||||
|
||||
it('does not remove the fn after execution if autoCleanClosures is false', function() {
|
||||
function originalFn() {}
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: originalFn },
|
||||
autoCleanClosures: false
|
||||
});
|
||||
|
||||
spec.executionFinished();
|
||||
expect(spec.queueableFn.fn).toBe(originalFn);
|
||||
});
|
||||
});
|
||||
|
||||
it('should report properties set during the test', function() {
|
||||
const done = jasmine.createSpy('done callback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: jasmine.createSpy('spec body') },
|
||||
catchExceptions: function() {
|
||||
return false;
|
||||
},
|
||||
resultCallback: function() {}
|
||||
describe('#getSpecProperty', function() {
|
||||
it('get the property value', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
spec.setSpecProperty('a', 4);
|
||||
spec.execute(attrs => attrs.onComplete(), done);
|
||||
expect(spec.result.properties).toEqual({ a: 4 });
|
||||
|
||||
spec.setSpecProperty('a', 4);
|
||||
expect(spec.getSpecProperty('a')).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setSpecProperty', function() {
|
||||
it('adds the property to the result', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
spec.setSpecProperty('a', 4);
|
||||
|
||||
expect(spec.result.properties).toEqual({ a: 4 });
|
||||
});
|
||||
|
||||
it('replace the property result when it was previously set', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
spec.setSpecProperty('a', 'original-value');
|
||||
spec.setSpecProperty('b', 'original-value');
|
||||
spec.setSpecProperty('a', 'new-value');
|
||||
|
||||
expect(spec.result.properties).toEqual({
|
||||
a: 'new-value',
|
||||
b: 'original-value'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('#status returns passing by default', function() {
|
||||
@@ -320,70 +123,84 @@ describe('Spec', function() {
|
||||
expect(spec.status()).toBe('passed');
|
||||
});
|
||||
|
||||
it('#status returns passed if all expectations in the spec have passed', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: jasmine.createSpy('spec body') }
|
||||
});
|
||||
spec.addExpectationResult(true, {});
|
||||
expect(spec.status()).toBe('passed');
|
||||
});
|
||||
|
||||
it('#status returns failed if any expectations in the spec have failed', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: jasmine.createSpy('spec body') }
|
||||
});
|
||||
spec.addExpectationResult(true, {});
|
||||
spec.addExpectationResult(false, {});
|
||||
expect(spec.status()).toBe('failed');
|
||||
});
|
||||
|
||||
it('keeps track of passed and failed expectations', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('queueRunner'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: jasmine.createSpy('spec body') },
|
||||
resultCallback: resultCallback
|
||||
describe('#status', function() {
|
||||
it('returns "passed"" by default', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
spec.addExpectationResult(true, { message: 'expectation1' });
|
||||
spec.addExpectationResult(false, { message: 'expectation2' });
|
||||
expect(spec.status()).toBe('passed');
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
const fns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns;
|
||||
fns[fns.length - 1].fn();
|
||||
|
||||
expect(resultCallback.calls.first().args[0].passedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'expectation1' })
|
||||
]);
|
||||
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'expectation2' })
|
||||
]);
|
||||
});
|
||||
|
||||
it("throws an ExpectationFailed error upon receiving a failed expectation when 'throwOnExpectationFailure' is set", function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('queueRunner'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: function() {} },
|
||||
resultCallback: resultCallback,
|
||||
throwOnExpectationFailure: true
|
||||
it('returns "passed"" if all expectations passed', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
spec.addExpectationResult(true, { message: 'passed' });
|
||||
expect(function() {
|
||||
spec.addExpectationResult(false, { message: 'failed' });
|
||||
}).toThrowError(jasmineUnderTest.errors.ExpectationFailed);
|
||||
spec.addExpectationResult(true, {});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
expect(spec.status()).toBe('passed');
|
||||
});
|
||||
|
||||
const fns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns;
|
||||
fns[fns.length - 1].fn();
|
||||
expect(resultCallback.calls.first().args[0].passedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'passed' })
|
||||
]);
|
||||
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'failed' })
|
||||
]);
|
||||
it('returns "failed" if any expectation failed', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
spec.addExpectationResult(true, {});
|
||||
spec.addExpectationResult(false, {});
|
||||
|
||||
expect(spec.status()).toBe('failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#addExpectationResult', function() {
|
||||
it('keeps track of passed and failed expectations', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
spec.addExpectationResult(true, { message: 'expectation1' });
|
||||
spec.addExpectationResult(false, { message: 'expectation2' });
|
||||
|
||||
expect(spec.result.passedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'expectation1' })
|
||||
]);
|
||||
expect(spec.result.failedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'expectation2' })
|
||||
]);
|
||||
});
|
||||
|
||||
describe("when 'throwOnExpectationFailure' is set", function() {
|
||||
it('throws an ExpectationFailed error', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} },
|
||||
throwOnExpectationFailure: true
|
||||
});
|
||||
|
||||
spec.addExpectationResult(true, { message: 'passed' });
|
||||
expect(function() {
|
||||
spec.addExpectationResult(false, { message: 'failed' });
|
||||
}).toThrowError(jasmineUnderTest.errors.ExpectationFailed);
|
||||
|
||||
expect(spec.result.failedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'failed' })
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when 'throwOnExpectationFailure' is not set", function() {
|
||||
it('does not throw', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
spec.addExpectationResult(false, { message: 'failed' });
|
||||
|
||||
expect(spec.result.failedExpectations).toEqual([
|
||||
jasmine.objectContaining({ message: 'failed' })
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('forwards late expectation failures to onLateError', function() {
|
||||
@@ -514,125 +331,47 @@ describe('Spec', function() {
|
||||
expect(spec.metadata.getPath()).toEqual(['expected val']);
|
||||
});
|
||||
|
||||
describe('when a spec is marked pending during execution', function() {
|
||||
it('should mark the spec as pending', function() {
|
||||
const fakeQueueRunner = function(opts) {
|
||||
opts.onException(
|
||||
new Error(jasmineUnderTest.Spec.pendingSpecExceptionMessage)
|
||||
);
|
||||
},
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
description: 'my test',
|
||||
id: 'some-id',
|
||||
queueableFn: { fn: function() {} }
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
expect(spec.status()).toEqual('pending');
|
||||
expect(spec.result.pendingReason).toEqual('');
|
||||
});
|
||||
|
||||
it('should set the pendingReason', function() {
|
||||
const fakeQueueRunner = function(opts) {
|
||||
opts.onException(
|
||||
new Error(
|
||||
jasmineUnderTest.Spec.pendingSpecExceptionMessage +
|
||||
'custom message'
|
||||
)
|
||||
);
|
||||
},
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
description: 'my test',
|
||||
id: 'some-id',
|
||||
queueableFn: { fn: function() {} }
|
||||
});
|
||||
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
expect(spec.status()).toEqual('pending');
|
||||
expect(spec.result.pendingReason).toEqual('custom message');
|
||||
});
|
||||
});
|
||||
|
||||
it('should log a failure when handling an exception', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('queueRunner'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: function() {} },
|
||||
resultCallback: resultCallback
|
||||
describe('#handleException', function() {
|
||||
it('records a failure', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: {}
|
||||
});
|
||||
|
||||
spec.handleException('foo');
|
||||
spec.execute(fakeQueueRunner);
|
||||
spec.handleException('foo');
|
||||
|
||||
const args = fakeQueueRunner.calls.mostRecent().args[0];
|
||||
args.queueableFns[args.queueableFns.length - 1].fn();
|
||||
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([
|
||||
{
|
||||
message: 'foo thrown',
|
||||
matcherName: '',
|
||||
passed: false,
|
||||
expected: '',
|
||||
actual: '',
|
||||
stack: null
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not log an additional failure when handling an ExpectationFailed error', function() {
|
||||
const fakeQueueRunner = jasmine.createSpy('queueRunner'),
|
||||
resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: function() {} },
|
||||
resultCallback: resultCallback
|
||||
});
|
||||
|
||||
spec.handleException(new jasmineUnderTest.errors.ExpectationFailed());
|
||||
spec.execute(fakeQueueRunner);
|
||||
|
||||
const args = fakeQueueRunner.calls.mostRecent().args[0];
|
||||
args.queueableFns[args.queueableFns.length - 1].fn();
|
||||
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([]);
|
||||
});
|
||||
|
||||
it('treats multiple done calls as late errors', function() {
|
||||
const queueRunnerFactory = jasmine.createSpy('queueRunnerFactory'),
|
||||
onLateError = jasmine.createSpy('onLateError'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
onLateError: onLateError,
|
||||
queueableFn: { fn: function() {} },
|
||||
getPath: function() {
|
||||
return ['a spec'];
|
||||
expect(spec.result.failedExpectations).toEqual([
|
||||
{
|
||||
message: 'foo thrown',
|
||||
matcherName: '',
|
||||
passed: false,
|
||||
expected: '',
|
||||
actual: '',
|
||||
stack: null
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not record an additional failure when the error is ExpectationFailed', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: {}
|
||||
});
|
||||
|
||||
spec.execute(queueRunnerFactory);
|
||||
spec.handleException(new jasmineUnderTest.errors.ExpectationFailed());
|
||||
|
||||
expect(queueRunnerFactory).toHaveBeenCalled();
|
||||
queueRunnerFactory.calls.argsFor(0)[0].onMultipleDone();
|
||||
|
||||
expect(onLateError).toHaveBeenCalledTimes(1);
|
||||
expect(onLateError.calls.argsFor(0)[0]).toBeInstanceOf(Error);
|
||||
expect(onLateError.calls.argsFor(0)[0].message).toEqual(
|
||||
'An asynchronous spec, beforeEach, or afterEach function called its ' +
|
||||
"'done' callback more than once.\n(in spec: a spec)"
|
||||
);
|
||||
expect(spec.result.failedExpectations).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#trace', function() {
|
||||
describe('#debugLog', function() {
|
||||
it('adds the messages to the result', function() {
|
||||
const timer = jasmine.createSpyObj('timer', ['start', 'elapsed']),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: {
|
||||
fn: function() {}
|
||||
},
|
||||
timer: timer
|
||||
}),
|
||||
t1 = 123,
|
||||
t2 = 456;
|
||||
const timer = jasmine.createSpyObj('timer', ['start', 'elapsed']);
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} },
|
||||
timer: timer
|
||||
});
|
||||
const t1 = 123;
|
||||
const t2 = 456;
|
||||
|
||||
spec.execute(() => {});
|
||||
expect(spec.result.debugLogs).toBeNull();
|
||||
timer.elapsed.and.returnValue(t1);
|
||||
spec.debugLog('msg 1');
|
||||
@@ -648,84 +387,36 @@ describe('Spec', function() {
|
||||
});
|
||||
|
||||
describe('When the spec passes', function() {
|
||||
it('omits the messages from the reported result', function() {
|
||||
const resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: {
|
||||
fn: function() {}
|
||||
},
|
||||
resultCallback: resultCallback
|
||||
});
|
||||
it('removes the logs from the result', function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} }
|
||||
});
|
||||
|
||||
function queueRunnerFactory(config) {
|
||||
spec.debugLog('msg');
|
||||
for (const fn of config.queueableFns) {
|
||||
fn.fn();
|
||||
}
|
||||
config.onComplete(false);
|
||||
}
|
||||
spec.debugLog('msg');
|
||||
spec.executionFinished();
|
||||
|
||||
spec.execute(queueRunnerFactory, function() {});
|
||||
expect(resultCallback).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({ debugLogs: null }),
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
it('removes the messages to save memory', function() {
|
||||
const resultCallback = jasmine.createSpy('resultCallback'),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: {
|
||||
fn: function() {}
|
||||
},
|
||||
resultCallback: resultCallback
|
||||
});
|
||||
|
||||
function queueRunnerFactory(config) {
|
||||
spec.debugLog('msg');
|
||||
for (const fn of config.queueableFns) {
|
||||
fn.fn();
|
||||
}
|
||||
config.onComplete(false);
|
||||
}
|
||||
|
||||
spec.execute(queueRunnerFactory, function() {});
|
||||
expect(resultCallback).toHaveBeenCalled();
|
||||
expect(spec.result.debugLogs).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the spec fails', function() {
|
||||
it('includes the messages in the reported result', function() {
|
||||
const resultCallback = jasmine.createSpy('resultCallback'),
|
||||
timer = jasmine.createSpyObj('timer', ['start', 'elapsed']),
|
||||
spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: {
|
||||
fn: function() {}
|
||||
},
|
||||
resultCallback: resultCallback,
|
||||
timer: timer
|
||||
}),
|
||||
timestamp = 12345;
|
||||
it('includes the messages in the result', function() {
|
||||
const timer = jasmine.createSpyObj('timer', ['start', 'elapsed']);
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
queueableFn: { fn: () => {} },
|
||||
timer: timer
|
||||
});
|
||||
const timestamp = 12345;
|
||||
|
||||
timer.elapsed.and.returnValue(timestamp);
|
||||
|
||||
function queueRunnerFactory(config) {
|
||||
spec.debugLog('msg');
|
||||
spec.handleException(new Error('nope'));
|
||||
for (const fn of config.queueableFns) {
|
||||
fn.fn();
|
||||
}
|
||||
config.onComplete(true);
|
||||
}
|
||||
spec.debugLog('msg');
|
||||
spec.handleException(new Error('nope'));
|
||||
spec.executionFinished();
|
||||
|
||||
spec.execute(queueRunnerFactory, function() {});
|
||||
expect(resultCallback).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
debugLogs: [{ message: 'msg', timestamp: timestamp }]
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
expect(spec.result.debugLogs).toEqual([
|
||||
{ message: 'msg', timestamp: timestamp }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -153,7 +153,7 @@ describe('Spies', function() {
|
||||
it('should throw if you do not pass an array or object argument', function() {
|
||||
expect(function() {
|
||||
env.createSpyObj('BaseName');
|
||||
}).toThrow(
|
||||
}).toThrowError(
|
||||
'createSpyObj requires a non-empty array or object of method names to create spies for'
|
||||
);
|
||||
});
|
||||
@@ -161,7 +161,7 @@ describe('Spies', function() {
|
||||
it('should throw if you pass an empty array argument', function() {
|
||||
expect(function() {
|
||||
env.createSpyObj('BaseName', []);
|
||||
}).toThrow(
|
||||
}).toThrowError(
|
||||
'createSpyObj requires a non-empty array or object of method names to create spies for'
|
||||
);
|
||||
});
|
||||
@@ -169,7 +169,7 @@ describe('Spies', function() {
|
||||
it('should throw if you pass an empty object argument', function() {
|
||||
expect(function() {
|
||||
env.createSpyObj('BaseName', {});
|
||||
}).toThrow(
|
||||
}).toThrowError(
|
||||
'createSpyObj requires a non-empty array or object of method names to create spies for'
|
||||
);
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
542
spec/core/TreeRunnerSpec.js
Normal file
542
spec/core/TreeRunnerSpec.js
Normal file
@@ -0,0 +1,542 @@
|
||||
describe('TreeRunner', function() {
|
||||
describe('spec execution', function() {
|
||||
it('starts the timer, reports the spec started, and updates run state at the start of the queue', async function() {
|
||||
const timer = jasmine.createSpyObj('timer', ['start']);
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
id: 'spec1',
|
||||
queueableFn: {},
|
||||
timer
|
||||
});
|
||||
const {
|
||||
runQueue,
|
||||
currentRunableTracker,
|
||||
runableResources,
|
||||
reportDispatcher,
|
||||
suiteRunQueueArgs,
|
||||
executePromise
|
||||
} = runSingleSpecSuite(spec);
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
const next = jasmine.createSpy('next');
|
||||
specRunQueueArgs.queueableFns[0].fn(next);
|
||||
|
||||
expect(timer.start).toHaveBeenCalled();
|
||||
expect(currentRunableTracker.currentRunable()).toBe(spec);
|
||||
expect(runableResources.initForRunable).toHaveBeenCalledWith(
|
||||
spec.id,
|
||||
spec.parentSuiteId
|
||||
);
|
||||
expect(reportDispatcher.specStarted).toHaveBeenCalledWith(spec.result);
|
||||
await Promise.resolve();
|
||||
expect(reportDispatcher.specStarted).toHaveBeenCalledBefore(next);
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
|
||||
it('stops the timer, updates run state, and reports the spec done at the end of the queue', async function() {
|
||||
const timer = jasmine.createSpyObj('timer', ['start', 'elapsed']);
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
id: 'spec1',
|
||||
queueableFn: {},
|
||||
timer
|
||||
});
|
||||
const {
|
||||
runQueue,
|
||||
currentRunableTracker,
|
||||
runableResources,
|
||||
reportDispatcher,
|
||||
suiteRunQueueArgs,
|
||||
executePromise
|
||||
} = runSingleSpecSuite(spec);
|
||||
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
const next = jasmine.createSpy('next');
|
||||
timer.elapsed.and.returnValue('the elapsed time');
|
||||
currentRunableTracker.setCurrentSpec(spec);
|
||||
specRunQueueArgs.queueableFns[1].fn(next);
|
||||
|
||||
expect(currentRunableTracker.currentSpec()).toBeFalsy();
|
||||
expect(runableResources.clearForRunable).toHaveBeenCalledWith(spec.id);
|
||||
expect(reportDispatcher.specDone).toHaveBeenCalledWith(spec.result);
|
||||
expect(spec.result.duration).toEqual('the elapsed time');
|
||||
expect(spec.reportedDone).toEqual(true);
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
expect(reportDispatcher.specDone).toHaveBeenCalledBefore(next);
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
|
||||
it('runs before and after fns', function() {
|
||||
const before = { fn: jasmine.createSpy('before') };
|
||||
const after = { fn: 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: queueableFn,
|
||||
beforeAndAfterFns: function() {
|
||||
return { befores: [before], afters: [after] };
|
||||
}
|
||||
});
|
||||
|
||||
const { runQueue, suiteRunQueueArgs } = runSingleSpecSuite(spec);
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
|
||||
expect(specRunQueueArgs.queueableFns[1]).toEqual(before);
|
||||
expect(specRunQueueArgs.queueableFns[2]).toEqual(queueableFn);
|
||||
expect(specRunQueueArgs.queueableFns[3]).toEqual(after);
|
||||
});
|
||||
|
||||
it('marks specs pending at runtime', function() {
|
||||
let spec;
|
||||
const queueableFn = {
|
||||
fn() {
|
||||
spec.pend();
|
||||
}
|
||||
};
|
||||
spec = new jasmineUnderTest.Spec({ queueableFn });
|
||||
|
||||
const { runQueue, suiteRunQueueArgs } = runSingleSpecSuite(spec);
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
|
||||
expect(specRunQueueArgs.queueableFns[1]).toEqual(queueableFn);
|
||||
queueableFn.fn();
|
||||
|
||||
expect(spec.status()).toEqual('pending');
|
||||
expect(spec.getResult().status).toEqual('pending');
|
||||
expect(spec.getResult().pendingReason).toEqual('');
|
||||
});
|
||||
|
||||
it('marks specs pending at runtime with a message', function() {
|
||||
let spec;
|
||||
const queueableFn = {
|
||||
fn() {
|
||||
spec.pend('some reason');
|
||||
}
|
||||
};
|
||||
spec = new jasmineUnderTest.Spec({ queueableFn });
|
||||
|
||||
const { runQueue, suiteRunQueueArgs } = runSingleSpecSuite(spec);
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
it('passes failSpecWithNoExp to Spec#executionFinished', async function() {
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
id: 'spec1',
|
||||
queueableFn: {}
|
||||
});
|
||||
spyOn(spec, 'executionFinished');
|
||||
const {
|
||||
runQueue,
|
||||
suiteRunQueueArgs,
|
||||
executePromise
|
||||
} = runSingleSpecSuite(spec, { failSpecWithNoExpectations: true });
|
||||
|
||||
suiteRunQueueArgs.queueableFns[0].fn();
|
||||
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const specRunQueueArgs = runQueue.calls.mostRecent().args[0];
|
||||
expect(specRunQueueArgs.queueableFns[1].type).toEqual('specCleanup');
|
||||
specRunQueueArgs.queueableFns[1].fn();
|
||||
|
||||
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() {
|
||||
it('reports the duration of the suite', async function() {
|
||||
const timer = jasmine.createSpyObj('timer', ['start', 'elapsed']);
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
|
||||
const suite = new jasmineUnderTest.Suite({
|
||||
id: 'suite1',
|
||||
parentSuite: topSuite,
|
||||
timer
|
||||
});
|
||||
topSuite.addChild(suite);
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ suite }];
|
||||
},
|
||||
childrenOfSuiteSegment() {
|
||||
return [];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors: mockGlobalErrors(),
|
||||
runableResources: mockRunableResources(),
|
||||
reportDispatcher,
|
||||
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
|
||||
getConfig() {
|
||||
return {};
|
||||
},
|
||||
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);
|
||||
expect(timer.start).not.toHaveBeenCalled();
|
||||
const suiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
suiteRunQueueOpts.queueableFns[0].fn();
|
||||
expect(timer.start).toHaveBeenCalled();
|
||||
expect(timer.elapsed).not.toHaveBeenCalled();
|
||||
|
||||
timer.elapsed.and.returnValue('the duration');
|
||||
suiteRunQueueOpts.onComplete();
|
||||
expect(timer.elapsed).toHaveBeenCalled();
|
||||
const result = suite.getResult();
|
||||
expect(result.duration).toEqual('the duration');
|
||||
expect(reportDispatcher.suiteDone).toHaveBeenCalledWith(result);
|
||||
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
|
||||
it('returns false if a suite failed', async function() {
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
|
||||
const failingSuite = new jasmineUnderTest.Suite({
|
||||
id: 'failingSuite',
|
||||
parentSuite: topSuite
|
||||
});
|
||||
const passingSuite = new jasmineUnderTest.Suite({
|
||||
id: 'passingSuite',
|
||||
parentSuite: topSuite
|
||||
});
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ suite: failingSuite }, { suite: passingSuite }];
|
||||
},
|
||||
childrenOfSuiteSegment() {
|
||||
return [];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors: mockGlobalErrors(),
|
||||
runableResources: mockRunableResources(),
|
||||
reportDispatcher,
|
||||
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
|
||||
getConfig() {
|
||||
return {};
|
||||
},
|
||||
reportChildrenOfBeforeAllFailure() {}
|
||||
});
|
||||
|
||||
const executePromise = subject.execute();
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
topSuiteRunQueueOpts.queueableFns[0].fn(function() {});
|
||||
|
||||
// Fail the first suite.
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const failingSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
runQueue.calls.reset();
|
||||
failingSuiteRunQueueOpts.queueableFns[0].fn();
|
||||
failingSuite.addExpectationResult(false, {});
|
||||
failingSuiteRunQueueOpts.onComplete();
|
||||
|
||||
// Passing the second suite should not reset the overall result.
|
||||
topSuiteRunQueueOpts.queueableFns[1].fn(function() {});
|
||||
expect(runQueue).toHaveBeenCalledTimes(1);
|
||||
const passingSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0];
|
||||
passingSuiteRunQueueOpts.queueableFns[0].fn();
|
||||
passingSuiteRunQueueOpts.onComplete();
|
||||
|
||||
topSuiteRunQueueOpts.onComplete();
|
||||
|
||||
const result = await executePromise;
|
||||
expect(result.hasFailures).toEqual(true);
|
||||
});
|
||||
|
||||
it('reports children when there is a beforeAll failure', async function() {
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
|
||||
const suite = new jasmineUnderTest.Suite({
|
||||
id: 'suite',
|
||||
parentSuite: topSuite
|
||||
});
|
||||
suite.beforeAll({ fn() {} });
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
id: 'spec',
|
||||
parentSuite: suite,
|
||||
queueableFn: { fn() {} }
|
||||
});
|
||||
suite.addChild(spec);
|
||||
topSuite.addChild(suite);
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ suite }];
|
||||
},
|
||||
childrenOfSuiteSegment() {
|
||||
return [{ spec }];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const reportChildrenOfBeforeAllFailure = jasmine
|
||||
.createSpy('reportChildrenOfBeforeAllFailure')
|
||||
.and.returnValue(Promise.resolve());
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors: mockGlobalErrors(),
|
||||
runableResources: mockRunableResources(),
|
||||
reportDispatcher,
|
||||
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
|
||||
reportChildrenOfBeforeAllFailure,
|
||||
getConfig() {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
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];
|
||||
suiteRunQueueOpts.queueableFns[0].fn();
|
||||
suite.hadBeforeAllFailure = true;
|
||||
suiteRunQueueOpts.onComplete();
|
||||
|
||||
while (reportDispatcher.suiteDone.calls.count() === 0) {
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
expect(reportDispatcher.specDone).toHaveBeenCalledBefore(
|
||||
reportDispatcher.suiteDone
|
||||
);
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
|
||||
it('throws if the wrong suite is completed', async function() {
|
||||
const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' });
|
||||
const suite = new jasmineUnderTest.Suite({
|
||||
id: 'suite',
|
||||
parentSuite: topSuite
|
||||
});
|
||||
const spec = new jasmineUnderTest.Spec({
|
||||
id: 'spec',
|
||||
parentSuite: suite,
|
||||
queueableFn: { fn() {} }
|
||||
});
|
||||
const executionTree = {
|
||||
topSuite,
|
||||
childrenOfTopSuite() {
|
||||
return [{ suite }];
|
||||
},
|
||||
childrenOfSuiteSegment() {
|
||||
return [{ spec }];
|
||||
},
|
||||
isExcluded() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const runQueue = jasmine.createSpy('runQueue');
|
||||
const reportDispatcher = mockReportDispatcher();
|
||||
const subject = new jasmineUnderTest.TreeRunner({
|
||||
executionTree,
|
||||
runQueue,
|
||||
globalErrors: mockGlobalErrors(),
|
||||
runableResources: mockRunableResources(),
|
||||
reportDispatcher,
|
||||
currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(),
|
||||
getConfig() {
|
||||
return {};
|
||||
},
|
||||
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];
|
||||
|
||||
// Complete the suite without starting it
|
||||
expect(function() {
|
||||
suiteRunQueueOpts.onComplete();
|
||||
}).toThrowError('Tried to complete the wrong suite');
|
||||
|
||||
await expectAsync(executePromise).toBePending();
|
||||
});
|
||||
});
|
||||
|
||||
function mockReportDispatcher() {
|
||||
const reportDispatcher = jasmine.createSpyObj(
|
||||
'reportDispatcher',
|
||||
jasmineUnderTest.reporterEvents
|
||||
);
|
||||
|
||||
for (const k of jasmineUnderTest.reporterEvents) {
|
||||
reportDispatcher[k].and.returnValue(Promise.resolve());
|
||||
}
|
||||
|
||||
return reportDispatcher;
|
||||
}
|
||||
|
||||
function mockRunableResources() {
|
||||
return jasmine.createSpyObj('runableResources', [
|
||||
'initForRunable',
|
||||
'clearForRunable'
|
||||
]);
|
||||
}
|
||||
|
||||
function mockGlobalErrors() {
|
||||
return jasmine.createSpyObj('globalErrors', ['reportUnhandledRejections']);
|
||||
}
|
||||
});
|
||||
@@ -2,8 +2,7 @@ describe('base helpers', function() {
|
||||
describe('isError_', function() {
|
||||
it('correctly handles WebSocket events', function(done) {
|
||||
if (typeof jasmine.getGlobal().WebSocket === 'undefined') {
|
||||
done();
|
||||
return;
|
||||
pending('Environment does not provide WebSocket');
|
||||
}
|
||||
|
||||
const obj = (function() {
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('buildExpectationResult', function() {
|
||||
|
||||
it('handles nodejs assertions', function() {
|
||||
if (typeof require === 'undefined') {
|
||||
return;
|
||||
pending('This test only runs in Node');
|
||||
}
|
||||
const assert = require('assert');
|
||||
const value = 8421;
|
||||
|
||||
@@ -2262,17 +2262,54 @@ describe('Env integration', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('throws an exception if you try to getSpecProperty outside of a spec', async function() {
|
||||
const env = new jasmineUnderTest.Env();
|
||||
let exception;
|
||||
|
||||
env.describe('a suite', function() {
|
||||
env.it('a spec');
|
||||
try {
|
||||
env.getSpecProperty('a prop');
|
||||
} catch (e) {
|
||||
exception = e;
|
||||
}
|
||||
env.it('has a test', function() {});
|
||||
});
|
||||
|
||||
await env.execute();
|
||||
|
||||
expect(exception.message).toBe(
|
||||
"'getSpecProperty' was used when there was no current spec"
|
||||
);
|
||||
});
|
||||
|
||||
it('reports test properties on specs', async function() {
|
||||
const env = new jasmineUnderTest.Env(),
|
||||
reporter = jasmine.createSpyObj('reporter', ['suiteDone', 'specDone']);
|
||||
|
||||
reporter.specDone.and.callFake(function(e) {
|
||||
expect(e.properties).toEqual({ a: 'Bee' });
|
||||
expect(e.properties).toEqual({
|
||||
fromBeforeEach: 'Pie',
|
||||
fromSpecInnerCallback: 'Bee',
|
||||
willChangeInAfterEach: 2,
|
||||
fromAfterEach: 'Cheese'
|
||||
});
|
||||
});
|
||||
|
||||
env.addReporter(reporter);
|
||||
env.beforeEach(function() {
|
||||
env.setSpecProperty('fromBeforeEach', 'Pie');
|
||||
});
|
||||
env.afterEach(function() {
|
||||
env.setSpecProperty(
|
||||
'willChangeInAfterEach',
|
||||
env.getSpecProperty('willChangeInAfterEach') + 1
|
||||
);
|
||||
env.setSpecProperty('fromAfterEach', 'Cheese');
|
||||
});
|
||||
env.it('calls setSpecProperty', function() {
|
||||
env.setSpecProperty('a', 'Bee');
|
||||
env.setSpecProperty('fromSpecInnerCallback', 'Bee');
|
||||
env.setSpecProperty('willChangeInAfterEach', 1);
|
||||
});
|
||||
await env.execute();
|
||||
|
||||
@@ -3697,7 +3734,7 @@ describe('Env integration', function() {
|
||||
|
||||
function browserEventMethods() {
|
||||
return {
|
||||
listeners_: { error: [], unhandledrejection: [] },
|
||||
listeners_: { error: [], unhandledrejection: [], rejectionhandled: [] },
|
||||
addEventListener(eventName, listener) {
|
||||
this.listeners_[eventName].push(listener);
|
||||
},
|
||||
|
||||
@@ -88,10 +88,14 @@ describe('Global error handling (integration)', function() {
|
||||
const globalErrors = new jasmineUnderTest.GlobalErrors(global);
|
||||
const onerror = jasmine.createSpy('onerror');
|
||||
globalErrors.pushListener(onerror);
|
||||
spyOn(jasmineUnderTest, 'GlobalErrors').and.returnValue(globalErrors);
|
||||
|
||||
env.cleanup_();
|
||||
env = new jasmineUnderTest.Env({ suppressLoadErrors: true });
|
||||
env = new jasmineUnderTest.Env({
|
||||
suppressLoadErrors: true,
|
||||
GlobalErrors: function() {
|
||||
return globalErrors;
|
||||
}
|
||||
});
|
||||
const reporter = jasmine.createSpyObj('reporter', [
|
||||
'jasmineDone',
|
||||
'suiteDone',
|
||||
@@ -802,6 +806,118 @@ 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 };
|
||||
}
|
||||
|
||||
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({ detectLateRejectionHandling: true });
|
||||
const 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);
|
||||
|
||||
for (const e of events) {
|
||||
dispatchErrorEvent(global, 'unhandledrejection', e);
|
||||
}
|
||||
|
||||
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'
|
||||
})
|
||||
]
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("When the unhandled rejection event doesn't have a promise", function() {
|
||||
it('reports the rejection', 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({ detectLateRejectionHandling: true });
|
||||
const reporter = jasmine.createSpyObj('fakeReporter', [
|
||||
'specDone',
|
||||
'suiteDone'
|
||||
]);
|
||||
|
||||
env.addReporter(reporter);
|
||||
|
||||
env.describe('A suite', function() {
|
||||
env.it('fails', function(specDone) {
|
||||
setTimeout(function() {
|
||||
dispatchErrorEvent(global, 'unhandledrejection', {
|
||||
reason: 'fail'
|
||||
});
|
||||
specDone();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await env.execute();
|
||||
|
||||
expect(reporter.specDone).toHaveFailedExpectationsForRunnable(
|
||||
'A suite fails',
|
||||
['Unhandled promise rejection: fail thrown']
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#spyOnGlobalErrorsAsync', function() {
|
||||
@@ -1147,7 +1263,7 @@ describe('Global error handling (integration)', function() {
|
||||
|
||||
function browserEventMethods() {
|
||||
return {
|
||||
listeners_: { error: [], unhandledrejection: [] },
|
||||
listeners_: { error: [], unhandledrejection: [], rejectionhandled: [] },
|
||||
addEventListener(eventName, listener) {
|
||||
this.listeners_[eventName].push(listener);
|
||||
},
|
||||
|
||||
@@ -621,9 +621,14 @@ describe('spec running', function() {
|
||||
actions.push('spec3');
|
||||
});
|
||||
|
||||
spyOn(jasmineUnderTest.getEnv(), 'deprecated');
|
||||
|
||||
await env.execute([spec2.id, spec3.id, spec1.id]);
|
||||
|
||||
expect(actions).toEqual(['spec2', 'spec3', 'spec1']);
|
||||
expect(jasmineUnderTest.getEnv().deprecated).toHaveBeenCalledWith(
|
||||
'The specified spec/suite order splits up a suite, running unrelated specs in the middle of it. This will become an error in a future release.'
|
||||
);
|
||||
});
|
||||
|
||||
it('refuses to re-enter suites with a beforeAll', async function() {
|
||||
|
||||
@@ -250,10 +250,6 @@ describe('matchersUtil', function() {
|
||||
});
|
||||
|
||||
it('passes for equivalent Promises (GitHub issue #1314)', function() {
|
||||
if (typeof Promise === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const p1 = new Promise(function() {}),
|
||||
p2 = new Promise(function() {}),
|
||||
matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
@@ -263,14 +259,13 @@ describe('matchersUtil', function() {
|
||||
});
|
||||
|
||||
describe('when running in a browser', function() {
|
||||
function isNotRunningInBrowser() {
|
||||
return typeof document === 'undefined';
|
||||
}
|
||||
beforeEach(function() {
|
||||
if (typeof document === 'undefined') {
|
||||
pending('This test only runs in browsers');
|
||||
}
|
||||
});
|
||||
|
||||
it('passes for equivalent DOM nodes', function() {
|
||||
if (isNotRunningInBrowser()) {
|
||||
return;
|
||||
}
|
||||
const a = document.createElement('div');
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
|
||||
@@ -285,9 +280,6 @@ describe('matchersUtil', function() {
|
||||
});
|
||||
|
||||
it('passes for equivalent objects from different frames', function() {
|
||||
if (isNotRunningInBrowser()) {
|
||||
return;
|
||||
}
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
@@ -299,9 +291,6 @@ describe('matchersUtil', function() {
|
||||
});
|
||||
|
||||
it('fails for DOM nodes with different attributes or child nodes', function() {
|
||||
if (isNotRunningInBrowser()) {
|
||||
return;
|
||||
}
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
const a = document.createElement('div');
|
||||
a.setAttribute('test-attr', 'attr-value');
|
||||
@@ -325,14 +314,13 @@ describe('matchersUtil', function() {
|
||||
});
|
||||
|
||||
describe('when running in Node', function() {
|
||||
function isNotRunningInNode() {
|
||||
return typeof require !== 'function';
|
||||
}
|
||||
beforeEach(function() {
|
||||
if (typeof require !== 'function') {
|
||||
pending('This test only runs in Node');
|
||||
}
|
||||
});
|
||||
|
||||
it('passes for equivalent objects from different vm contexts', function() {
|
||||
if (isNotRunningInNode()) {
|
||||
return;
|
||||
}
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
const vm = require('vm');
|
||||
const sandbox = {
|
||||
@@ -344,9 +332,6 @@ describe('matchersUtil', function() {
|
||||
});
|
||||
|
||||
it('passes for equivalent arrays from different vm contexts', function() {
|
||||
if (isNotRunningInNode()) {
|
||||
return;
|
||||
}
|
||||
const matchersUtil = new jasmineUnderTest.MatchersUtil();
|
||||
const vm = require('vm');
|
||||
const sandbox = {
|
||||
|
||||
@@ -82,7 +82,7 @@ describe('toThrowError', function() {
|
||||
|
||||
it('passes if thrown is an instanceof Error regardless of global that contains its constructor', function() {
|
||||
if (isNotRunningInBrowser()) {
|
||||
return;
|
||||
pending('This test only runs in browsers.');
|
||||
}
|
||||
|
||||
const matcher = jasmineUnderTest.matchers.toThrowError();
|
||||
|
||||
@@ -499,7 +499,7 @@ describe('HtmlReporter', function() {
|
||||
expect(duration.innerHTML).toMatch(/finished in 0.1s/);
|
||||
});
|
||||
|
||||
it('reports the suite and spec names with status', function() {
|
||||
it('reports the suite names with status, and spec names with status and duration', function() {
|
||||
const container = document.createElement('div'),
|
||||
getContainer = function() {
|
||||
return container;
|
||||
@@ -532,7 +532,8 @@ describe('HtmlReporter', function() {
|
||||
fullName: 'A Suite with a spec',
|
||||
status: 'passed',
|
||||
failedExpectations: [],
|
||||
passedExpectations: [{ passed: true }]
|
||||
passedExpectations: [{ passed: true }],
|
||||
duration: 1230
|
||||
};
|
||||
reporter.specStarted(specResult);
|
||||
reporter.specDone(specResult);
|
||||
@@ -549,7 +550,8 @@ describe('HtmlReporter', function() {
|
||||
fullName: 'A Suite inner suite with another spec',
|
||||
status: 'passed',
|
||||
failedExpectations: [],
|
||||
passedExpectations: [{ passed: true }]
|
||||
passedExpectations: [{ passed: true }],
|
||||
duration: 1240
|
||||
};
|
||||
reporter.specStarted(specResult);
|
||||
reporter.specDone(specResult);
|
||||
@@ -567,7 +569,8 @@ describe('HtmlReporter', function() {
|
||||
fullName: 'A Suite inner with a failing spec',
|
||||
status: 'failed',
|
||||
failedExpectations: [{}],
|
||||
passedExpectations: []
|
||||
passedExpectations: [],
|
||||
duration: 2090
|
||||
};
|
||||
reporter.specStarted(specResult);
|
||||
reporter.specDone(specResult);
|
||||
@@ -614,6 +617,9 @@ describe('HtmlReporter', function() {
|
||||
expect(specLink.getAttribute('href')).toEqual(
|
||||
'/?foo=bar&spec=A Suite with a spec'
|
||||
);
|
||||
|
||||
const specDuration = spec.childNodes[1];
|
||||
expect(specDuration.innerHTML).toEqual('(1230ms)');
|
||||
});
|
||||
|
||||
it('has an options menu', function() {
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
describe('MatchersSpec - HTML Dependent', function() {
|
||||
let env, spec;
|
||||
|
||||
beforeEach(function() {
|
||||
env = new jasmineUnderTest.Env();
|
||||
|
||||
env.describe('suite', function() {
|
||||
spec = env.it('spec', function() {});
|
||||
});
|
||||
spyOn(spec, 'addExpectationResult');
|
||||
|
||||
addMatchers({
|
||||
toPass: function() {
|
||||
return lastResult().passed;
|
||||
},
|
||||
toFail: function() {
|
||||
return !lastResult().passed;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
env.cleanup_();
|
||||
});
|
||||
|
||||
function match(value) {
|
||||
return spec.expect(value);
|
||||
}
|
||||
|
||||
function lastResult() {
|
||||
return spec.addExpectationResult.mostRecentCall.args[1];
|
||||
}
|
||||
|
||||
xit('toEqual with DOM nodes', function() {
|
||||
const nodeA = document.createElement('div');
|
||||
const nodeB = document.createElement('div');
|
||||
expect(match(nodeA).toEqual(nodeA)).toPass();
|
||||
expect(match(nodeA).toEqual(nodeB)).toFail();
|
||||
});
|
||||
});
|
||||
38
src/core/CurrentRunableTracker.js
Normal file
38
src/core/CurrentRunableTracker.js
Normal file
@@ -0,0 +1,38 @@
|
||||
getJasmineRequireObj().CurrentRunableTracker = function() {
|
||||
class CurrentRunableTracker {
|
||||
#currentSpec;
|
||||
#currentlyExecutingSuites;
|
||||
|
||||
constructor() {
|
||||
this.#currentlyExecutingSuites = [];
|
||||
}
|
||||
|
||||
currentRunable() {
|
||||
return this.currentSpec() || this.currentSuite();
|
||||
}
|
||||
|
||||
currentSpec() {
|
||||
return this.#currentSpec;
|
||||
}
|
||||
|
||||
setCurrentSpec(spec) {
|
||||
this.#currentSpec = spec;
|
||||
}
|
||||
|
||||
currentSuite() {
|
||||
return this.#currentlyExecutingSuites[
|
||||
this.#currentlyExecutingSuites.length - 1
|
||||
];
|
||||
}
|
||||
|
||||
pushSuite(suite) {
|
||||
this.#currentlyExecutingSuites.push(suite);
|
||||
}
|
||||
|
||||
popSuite() {
|
||||
this.#currentlyExecutingSuites.pop();
|
||||
}
|
||||
}
|
||||
|
||||
return CurrentRunableTracker;
|
||||
};
|
||||
102
src/core/Env.js
102
src/core/Env.js
@@ -11,6 +11,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
options = options || {};
|
||||
|
||||
const self = this;
|
||||
const GlobalErrors = options.GlobalErrors || j$.GlobalErrors;
|
||||
const global = options.global || j$.getGlobal();
|
||||
|
||||
const realSetTimeout = global.setTimeout;
|
||||
@@ -24,7 +25,12 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
new j$.MockDate(global)
|
||||
);
|
||||
|
||||
const globalErrors = new j$.GlobalErrors();
|
||||
const globalErrors = new GlobalErrors(
|
||||
undefined,
|
||||
// Configuration is late-bound because GlobalErrors needs to be constructed
|
||||
// before it's set to detect load-time errors in browsers
|
||||
() => this.configuration()
|
||||
);
|
||||
const installGlobalErrors = (function() {
|
||||
let installed = false;
|
||||
return function() {
|
||||
@@ -43,7 +49,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
globalErrors
|
||||
});
|
||||
|
||||
let reporter;
|
||||
let reportDispatcher;
|
||||
let topSuite;
|
||||
let runner;
|
||||
let parallelLoadingState = null; // 'specs', 'helpers', or null for non-parallel
|
||||
@@ -121,7 +127,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
* Whether or not reporters should hide disabled specs from their output.
|
||||
* Whether reporters should hide disabled specs from their output.
|
||||
* Currently only supported by Jasmine's HTMLReporter
|
||||
* @name Configuration#hideDisabled
|
||||
* @since 3.3.0
|
||||
@@ -148,17 +154,31 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
*/
|
||||
forbidDuplicateNames: false,
|
||||
/**
|
||||
* Whether or not to issue warnings for certain deprecated functionality
|
||||
* 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 to prevent the suite output from being flooded
|
||||
* 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
|
||||
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
|
||||
};
|
||||
|
||||
if (!options.suppressLoadErrors) {
|
||||
@@ -196,7 +216,8 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
'stopOnSpecFailure',
|
||||
'stopSpecOnExpectationFailure',
|
||||
'autoCleanClosures',
|
||||
'forbidDuplicateNames'
|
||||
'forbidDuplicateNames',
|
||||
'detectLateRejectionHandling'
|
||||
];
|
||||
|
||||
booleanProps.forEach(function(prop) {
|
||||
@@ -434,7 +455,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
deprecator.addDeprecationWarning(runable, deprecation, options);
|
||||
};
|
||||
|
||||
function queueRunnerFactory(options) {
|
||||
function runQueue(options) {
|
||||
options.clearStack = options.clearStack || clearStack;
|
||||
options.timeout = {
|
||||
setTimeout: realSetTimeout,
|
||||
@@ -456,9 +477,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
expectationFactory,
|
||||
asyncExpectationFactory,
|
||||
onLateError: recordLateError,
|
||||
specResultCallback,
|
||||
specStarted,
|
||||
queueRunnerFactory
|
||||
runQueue
|
||||
});
|
||||
topSuite = suiteBuilder.topSuite;
|
||||
const deprecator = new j$.Deprecator(topSuite);
|
||||
@@ -481,11 +500,11 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
* @interface Reporter
|
||||
* @see custom_reporter
|
||||
*/
|
||||
reporter = new j$.ReportDispatcher(
|
||||
reportDispatcher = new j$.ReportDispatcher(
|
||||
j$.reporterEvents,
|
||||
function(options) {
|
||||
options.SkipPolicy = j$.NeverSkipPolicy;
|
||||
return queueRunnerFactory(options);
|
||||
return runQueue(options);
|
||||
},
|
||||
recordLateError
|
||||
);
|
||||
@@ -495,10 +514,11 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
totalSpecsDefined: () => suiteBuilder.totalSpecsDefined,
|
||||
focusedRunables: () => suiteBuilder.focusedRunables,
|
||||
runableResources,
|
||||
reporter,
|
||||
queueRunnerFactory,
|
||||
getConfig: () => config,
|
||||
reportSpecDone
|
||||
reportDispatcher,
|
||||
runQueue,
|
||||
TreeProcessor: j$.TreeProcessor,
|
||||
globalErrors,
|
||||
getConfig: () => config
|
||||
});
|
||||
|
||||
this.setParallelLoadingState = function(state) {
|
||||
@@ -561,7 +581,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
throw new Error('Reporters cannot be added via Env in parallel mode');
|
||||
}
|
||||
|
||||
reporter.addReporter(reporterToAdd);
|
||||
reportDispatcher.addReporter(reporterToAdd);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -573,7 +593,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
* @see custom_reporter
|
||||
*/
|
||||
this.provideFallbackReporter = function(reporterToAdd) {
|
||||
reporter.provideFallbackReporter(reporterToAdd);
|
||||
reportDispatcher.provideFallbackReporter(reporterToAdd);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -587,7 +607,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
throw new Error('Reporters cannot be removed via Env in parallel mode');
|
||||
}
|
||||
|
||||
reporter.clearReporters();
|
||||
reportDispatcher.clearReporters();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -740,28 +760,6 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
.metadata;
|
||||
};
|
||||
|
||||
function specResultCallback(spec, result, next) {
|
||||
runableResources.clearForRunable(spec.id);
|
||||
runner.currentSpec = null;
|
||||
|
||||
if (result.status === 'failed') {
|
||||
runner.hasFailures = true;
|
||||
}
|
||||
|
||||
reportSpecDone(spec, result, next);
|
||||
}
|
||||
|
||||
function specStarted(spec, suite, next) {
|
||||
runner.currentSpec = spec;
|
||||
runableResources.initForRunable(spec.id, suite.id);
|
||||
reporter.specStarted(spec.result).then(next);
|
||||
}
|
||||
|
||||
function reportSpecDone(spec, result, next) {
|
||||
spec.reportedDone = true;
|
||||
reporter.specDone(result).then(next);
|
||||
}
|
||||
|
||||
this.it = function(description, fn, timeout) {
|
||||
ensureIsNotNested('it');
|
||||
const filename = callerCallerFilename();
|
||||
@@ -781,6 +779,26 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
return suiteBuilder.fit(description, fn, timeout, filename).metadata;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a user-defined property as part of the properties field of {@link SpecResult}
|
||||
* @name Env#getSpecProperty
|
||||
* @since 5.10.0
|
||||
* @function
|
||||
* @param {String} key The name of the property
|
||||
* @returns {*} The value of the property
|
||||
*/
|
||||
this.getSpecProperty = function(key) {
|
||||
if (
|
||||
!runner.currentRunable() ||
|
||||
runner.currentRunable() == runner.currentSuite()
|
||||
) {
|
||||
throw new Error(
|
||||
"'getSpecProperty' was used when there was no current spec"
|
||||
);
|
||||
}
|
||||
return runner.currentRunable().getSpecProperty(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets a user-defined property that will be provided to reporters as part of the properties field of {@link SpecResult}
|
||||
* @name Env#setSpecProperty
|
||||
|
||||
@@ -1,27 +1,35 @@
|
||||
getJasmineRequireObj().GlobalErrors = function(j$) {
|
||||
class GlobalErrors {
|
||||
#getConfig;
|
||||
#adapter;
|
||||
#handlers;
|
||||
#overrideHandler;
|
||||
#onRemoveOverrideHandler;
|
||||
#pendingUnhandledRejections;
|
||||
|
||||
constructor(global) {
|
||||
constructor(global, getConfig) {
|
||||
global = global || j$.getGlobal();
|
||||
const dispatchError = this.#dispatchError.bind(this);
|
||||
this.#getConfig = getConfig;
|
||||
this.#pendingUnhandledRejections = new Map();
|
||||
this.#handlers = [];
|
||||
this.#overrideHandler = null;
|
||||
this.#onRemoveOverrideHandler = null;
|
||||
|
||||
const dispatch = {
|
||||
onUncaughtException: this.#onUncaughtException.bind(this),
|
||||
onUnhandledRejection: this.#onUnhandledRejection.bind(this),
|
||||
onRejectionHandled: this.#onRejectionHandled.bind(this)
|
||||
};
|
||||
|
||||
if (
|
||||
global.process &&
|
||||
global.process.listeners &&
|
||||
j$.isFunction_(global.process.on)
|
||||
) {
|
||||
this.#adapter = new NodeAdapter(global, dispatchError);
|
||||
this.#adapter = new NodeAdapter(global, dispatch);
|
||||
} else {
|
||||
this.#adapter = new BrowserAdapter(global, dispatchError);
|
||||
this.#adapter = new BrowserAdapter(global, dispatch);
|
||||
}
|
||||
|
||||
this.#handlers = [];
|
||||
this.#overrideHandler = null;
|
||||
this.#onRemoveOverrideHandler = null;
|
||||
}
|
||||
|
||||
install() {
|
||||
@@ -69,6 +77,41 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
|
||||
this.#onRemoveOverrideHandler = null;
|
||||
}
|
||||
|
||||
reportUnhandledRejections() {
|
||||
for (const {
|
||||
reason,
|
||||
event
|
||||
} of this.#pendingUnhandledRejections.values()) {
|
||||
this.#dispatchError(reason, event);
|
||||
}
|
||||
|
||||
this.#pendingUnhandledRejections.clear();
|
||||
}
|
||||
|
||||
// Either error or event may be undefined
|
||||
#onUncaughtException(error, event) {
|
||||
this.#dispatchError(error, event);
|
||||
}
|
||||
|
||||
// event or promise may be undefined
|
||||
// event is passed through for backwards compatibility reasons. It's probably
|
||||
// unnecessary, but user code could depend on it.
|
||||
#onUnhandledRejection(reason, promise, event) {
|
||||
if (this.#detectLateRejectionHandling() && promise) {
|
||||
this.#pendingUnhandledRejections.set(promise, { reason, event });
|
||||
} else {
|
||||
this.#dispatchError(reason, event);
|
||||
}
|
||||
}
|
||||
|
||||
#detectLateRejectionHandling() {
|
||||
return this.#getConfig().detectLateRejectionHandling;
|
||||
}
|
||||
|
||||
#onRejectionHandled(promise) {
|
||||
this.#pendingUnhandledRejections.delete(promise);
|
||||
}
|
||||
|
||||
// Either error or event may be undefined
|
||||
#dispatchError(error, event) {
|
||||
if (this.#overrideHandler) {
|
||||
@@ -89,24 +132,29 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
|
||||
|
||||
class BrowserAdapter {
|
||||
#global;
|
||||
#dispatchError;
|
||||
#dispatch;
|
||||
#onError;
|
||||
#onUnhandledRejection;
|
||||
#onRejectionHandled;
|
||||
|
||||
constructor(global, dispatchError) {
|
||||
constructor(global, dispatch) {
|
||||
this.#global = global;
|
||||
this.#dispatchError = dispatchError;
|
||||
this.#onError = event => this.#dispatchError(event.error, event);
|
||||
this.#dispatch = dispatch;
|
||||
this.#onError = event => dispatch.onUncaughtException(event.error, event);
|
||||
this.#onUnhandledRejection = this.#unhandledRejectionHandler.bind(this);
|
||||
this.#onRejectionHandled = this.#rejectionHandledHandler.bind(this);
|
||||
}
|
||||
|
||||
install() {
|
||||
this.#global.addEventListener('error', this.#onError);
|
||||
|
||||
this.#global.addEventListener(
|
||||
'unhandledrejection',
|
||||
this.#onUnhandledRejection
|
||||
);
|
||||
this.#global.addEventListener(
|
||||
'rejectionhandled',
|
||||
this.#onRejectionHandled
|
||||
);
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
@@ -115,50 +163,55 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
|
||||
'unhandledrejection',
|
||||
this.#onUnhandledRejection
|
||||
);
|
||||
this.#global.removeEventListener(
|
||||
'rejectionhandled',
|
||||
this.#onRejectionHandled
|
||||
);
|
||||
}
|
||||
|
||||
#unhandledRejectionHandler(event) {
|
||||
const jasmineMessage = 'Unhandled promise rejection: ' + event.reason;
|
||||
let reason;
|
||||
|
||||
if (j$.isError_(event.reason)) {
|
||||
event.reason.jasmineMessage =
|
||||
'Unhandled promise rejection: ' + event.reason;
|
||||
this.#dispatchError(event.reason, event);
|
||||
reason = event.reason;
|
||||
reason.jasmineMessage = jasmineMessage;
|
||||
} else {
|
||||
this.#dispatchError(
|
||||
'Unhandled promise rejection: ' + event.reason,
|
||||
event
|
||||
);
|
||||
reason = jasmineMessage;
|
||||
}
|
||||
|
||||
this.#dispatch.onUnhandledRejection(reason, event.promise, event);
|
||||
}
|
||||
|
||||
#rejectionHandledHandler(event) {
|
||||
this.#dispatch.onRejectionHandled(event.promise);
|
||||
}
|
||||
}
|
||||
|
||||
class NodeAdapter {
|
||||
#global;
|
||||
#dispatchError;
|
||||
#dispatch;
|
||||
#originalHandlers;
|
||||
#jasmineHandlers;
|
||||
#onError;
|
||||
#onUnhandledRejection;
|
||||
|
||||
constructor(global, dispatchError) {
|
||||
constructor(global, dispatch) {
|
||||
this.#global = global;
|
||||
this.#dispatchError = dispatchError;
|
||||
this.#dispatch = dispatch;
|
||||
|
||||
this.#jasmineHandlers = {};
|
||||
this.#originalHandlers = {};
|
||||
|
||||
this.#onError = error =>
|
||||
this.#eventHandler(error, 'uncaughtException', 'Uncaught exception');
|
||||
this.#onUnhandledRejection = error =>
|
||||
this.#eventHandler(
|
||||
error,
|
||||
'unhandledRejection',
|
||||
'Unhandled promise rejection'
|
||||
);
|
||||
this.onError = this.onError.bind(this);
|
||||
this.onUnhandledRejection = this.onUnhandledRejection.bind(this);
|
||||
}
|
||||
|
||||
install() {
|
||||
this.#installHandler('uncaughtException', this.#onError);
|
||||
this.#installHandler('unhandledRejection', this.#onUnhandledRejection);
|
||||
this.#installHandler('uncaughtException', this.onError);
|
||||
this.#installHandler('unhandledRejection', this.onUnhandledRejection);
|
||||
this.#installHandler(
|
||||
'rejectionHandled',
|
||||
this.#dispatch.onRejectionHandled
|
||||
);
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
@@ -190,31 +243,49 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
|
||||
this.#global.process.on(errorType, handler);
|
||||
}
|
||||
|
||||
#eventHandler(error, errorType, jasmineMessage) {
|
||||
#augmentError(error, isUnhandledRejection) {
|
||||
let jasmineMessagePrefix;
|
||||
|
||||
if (isUnhandledRejection) {
|
||||
jasmineMessagePrefix = 'Unhandled promise rejection';
|
||||
} else {
|
||||
jasmineMessagePrefix = 'Uncaught exception';
|
||||
}
|
||||
|
||||
if (j$.isError_(error)) {
|
||||
error.jasmineMessage = jasmineMessage + ': ' + error;
|
||||
error.jasmineMessage = jasmineMessagePrefix + ': ' + error;
|
||||
return error;
|
||||
} else {
|
||||
let substituteMsg;
|
||||
|
||||
if (error) {
|
||||
substituteMsg = jasmineMessage + ': ' + error;
|
||||
substituteMsg = jasmineMessagePrefix + ': ' + error;
|
||||
} else {
|
||||
substituteMsg = jasmineMessage + ' with no error or message';
|
||||
substituteMsg = jasmineMessagePrefix + ' with no error or message';
|
||||
}
|
||||
|
||||
if (errorType === 'unhandledRejection') {
|
||||
if (isUnhandledRejection) {
|
||||
substituteMsg +=
|
||||
'\n' +
|
||||
'(Tip: to get a useful stack trace, use ' +
|
||||
'Promise.reject(new Error(...)) instead of Promise.reject(' +
|
||||
'Promise.reject(n' +
|
||||
'ew Error(...)) instead of Promise.reject(' +
|
||||
(error ? '...' : '') +
|
||||
').)';
|
||||
}
|
||||
|
||||
error = new Error(substituteMsg);
|
||||
return new Error(substituteMsg);
|
||||
}
|
||||
}
|
||||
|
||||
this.#dispatchError(error);
|
||||
onError(error) {
|
||||
error = this.#augmentError(error, false);
|
||||
this.#dispatch.onUncaughtException(error);
|
||||
}
|
||||
|
||||
onUnhandledRejection(reason, promise) {
|
||||
reason = this.#augmentError(reason, true);
|
||||
this.#dispatch.onUnhandledRejection(reason, promise);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,17 @@ getJasmineRequireObj().QueueRunner = function(j$) {
|
||||
function QueueRunner(attrs) {
|
||||
this.id_ = nextid++;
|
||||
this.queueableFns = attrs.queueableFns || [];
|
||||
|
||||
for (const f of this.queueableFns) {
|
||||
if (!f) {
|
||||
throw new Error('Received a falsy queueableFn');
|
||||
}
|
||||
|
||||
if (!f.fn) {
|
||||
throw new Error('Received a queueableFn with no fn');
|
||||
}
|
||||
}
|
||||
|
||||
this.onComplete = attrs.onComplete || emptyFn;
|
||||
this.clearStack =
|
||||
attrs.clearStack ||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
getJasmineRequireObj().ReportDispatcher = function(j$) {
|
||||
'use strict';
|
||||
|
||||
function ReportDispatcher(methods, queueRunnerFactory, onLateError) {
|
||||
function ReportDispatcher(methods, runQueue, onLateError) {
|
||||
const dispatchedMethods = methods || [];
|
||||
|
||||
for (const method of dispatchedMethods) {
|
||||
@@ -39,7 +39,7 @@ getJasmineRequireObj().ReportDispatcher = function(j$) {
|
||||
}
|
||||
|
||||
return new Promise(function(resolve) {
|
||||
queueRunnerFactory({
|
||||
runQueue({
|
||||
queueableFns: fns,
|
||||
onComplete: resolve,
|
||||
isReporter: true,
|
||||
|
||||
@@ -1,51 +1,66 @@
|
||||
getJasmineRequireObj().Runner = function(j$) {
|
||||
class Runner {
|
||||
constructor(options) {
|
||||
this.topSuite_ = options.topSuite;
|
||||
// TODO use names that read like getters
|
||||
this.totalSpecsDefined_ = options.totalSpecsDefined;
|
||||
this.focusedRunables_ = options.focusedRunables;
|
||||
this.runableResources_ = options.runableResources;
|
||||
this.queueRunnerFactory_ = options.queueRunnerFactory;
|
||||
this.reporter_ = options.reporter;
|
||||
this.getConfig_ = options.getConfig;
|
||||
this.reportSpecDone_ = options.reportSpecDone;
|
||||
this.hasFailures = false;
|
||||
this.executedBefore_ = false;
|
||||
#topSuite;
|
||||
#getTotalSpecsDefined;
|
||||
#getFocusedRunables;
|
||||
#runableResources;
|
||||
#runQueue;
|
||||
#TreeProcessor;
|
||||
#executionTree;
|
||||
#globalErrors;
|
||||
#reportDispatcher;
|
||||
#getConfig;
|
||||
#executedBefore;
|
||||
#currentRunableTracker;
|
||||
|
||||
this.currentlyExecutingSuites_ = [];
|
||||
this.currentSpec = null;
|
||||
constructor(options) {
|
||||
this.#topSuite = options.topSuite;
|
||||
this.#getTotalSpecsDefined = options.totalSpecsDefined;
|
||||
this.#getFocusedRunables = options.focusedRunables;
|
||||
this.#runableResources = options.runableResources;
|
||||
this.#runQueue = options.runQueue;
|
||||
this.#TreeProcessor = options.TreeProcessor;
|
||||
this.#globalErrors = options.globalErrors;
|
||||
this.#reportDispatcher = options.reportDispatcher;
|
||||
this.#getConfig = options.getConfig;
|
||||
this.#executedBefore = false;
|
||||
this.#currentRunableTracker = new j$.CurrentRunableTracker();
|
||||
}
|
||||
|
||||
currentSpec() {
|
||||
return this.#currentRunableTracker.currentSpec();
|
||||
}
|
||||
|
||||
setCurrentSpec(spec) {
|
||||
this.#currentRunableTracker.setCurrentSpec(spec);
|
||||
}
|
||||
|
||||
currentRunable() {
|
||||
return this.currentSpec || this.currentSuite();
|
||||
return this.#currentRunableTracker.currentRunable();
|
||||
}
|
||||
|
||||
currentSuite() {
|
||||
return this.currentlyExecutingSuites_[
|
||||
this.currentlyExecutingSuites_.length - 1
|
||||
];
|
||||
return this.#currentRunableTracker.currentSuite();
|
||||
}
|
||||
|
||||
parallelReset() {
|
||||
this.executedBefore_ = false;
|
||||
this.#executedBefore = false;
|
||||
}
|
||||
|
||||
async execute(runablesToRun) {
|
||||
if (this.executedBefore_) {
|
||||
this.topSuite_.reset();
|
||||
if (this.#executedBefore) {
|
||||
this.#topSuite.reset();
|
||||
}
|
||||
this.executedBefore_ = true;
|
||||
this.#executedBefore = true;
|
||||
|
||||
this.hasFailures = false;
|
||||
const focusedRunables = this.focusedRunables_();
|
||||
const config = this.getConfig_();
|
||||
const focusedRunables = this.#getFocusedRunables();
|
||||
const config = this.#getConfig();
|
||||
|
||||
if (!runablesToRun) {
|
||||
if (focusedRunables.length) {
|
||||
runablesToRun = focusedRunables;
|
||||
} else {
|
||||
runablesToRun = [this.topSuite_.id];
|
||||
runablesToRun = [this.#topSuite.id];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,52 +69,9 @@ getJasmineRequireObj().Runner = function(j$) {
|
||||
seed: j$.isNumber_(config.seed) ? config.seed + '' : config.seed
|
||||
});
|
||||
|
||||
const processor = new j$.TreeProcessor({
|
||||
tree: this.topSuite_,
|
||||
const treeProcessor = new this.#TreeProcessor({
|
||||
tree: this.#topSuite,
|
||||
runnableIds: runablesToRun,
|
||||
queueRunnerFactory: options => {
|
||||
if (options.isLeaf) {
|
||||
// A spec
|
||||
options.SkipPolicy = j$.CompleteOnFirstErrorSkipPolicy;
|
||||
} else {
|
||||
// A suite
|
||||
if (config.stopOnSpecFailure) {
|
||||
options.SkipPolicy = j$.CompleteOnFirstErrorSkipPolicy;
|
||||
} else {
|
||||
options.SkipPolicy = j$.SkipAfterBeforeAllErrorPolicy;
|
||||
}
|
||||
}
|
||||
|
||||
return this.queueRunnerFactory_(options);
|
||||
},
|
||||
failSpecWithNoExpectations: config.failSpecWithNoExpectations,
|
||||
nodeStart: (suite, next) => {
|
||||
this.currentlyExecutingSuites_.push(suite);
|
||||
this.runableResources_.initForRunable(suite.id, suite.parentSuite.id);
|
||||
this.reporter_.suiteStarted(suite.result).then(next);
|
||||
suite.startTimer();
|
||||
},
|
||||
nodeComplete: (suite, result, next) => {
|
||||
if (suite !== this.currentSuite()) {
|
||||
throw new Error('Tried to complete the wrong suite');
|
||||
}
|
||||
|
||||
this.runableResources_.clearForRunable(suite.id);
|
||||
this.currentlyExecutingSuites_.pop();
|
||||
|
||||
if (result.status === 'failed') {
|
||||
this.hasFailures = true;
|
||||
}
|
||||
suite.endTimer();
|
||||
|
||||
if (suite.hadBeforeAllFailure) {
|
||||
this.reportChildrenOfBeforeAllFailure_(suite).then(() => {
|
||||
this.reportSuiteDone_(suite, result, next);
|
||||
});
|
||||
} else {
|
||||
this.reportSuiteDone_(suite, result, next);
|
||||
}
|
||||
},
|
||||
orderChildren: function(node) {
|
||||
return order.sort(node.children);
|
||||
},
|
||||
@@ -107,20 +79,15 @@ getJasmineRequireObj().Runner = function(j$) {
|
||||
return !config.specFilter(spec);
|
||||
}
|
||||
});
|
||||
this.#executionTree = treeProcessor.processTree();
|
||||
|
||||
if (!processor.processTree().valid) {
|
||||
throw new Error(
|
||||
'Invalid order: would cause a beforeAll or afterAll to be run multiple times'
|
||||
);
|
||||
}
|
||||
|
||||
return this.execute2_(runablesToRun, order, processor);
|
||||
return this.#execute2(runablesToRun, order);
|
||||
}
|
||||
|
||||
async execute2_(runablesToRun, order, processor) {
|
||||
const totalSpecsDefined = this.totalSpecsDefined_();
|
||||
async #execute2(runablesToRun, order) {
|
||||
const totalSpecsDefined = this.#getTotalSpecsDefined();
|
||||
|
||||
this.runableResources_.initForRunable(this.topSuite_.id);
|
||||
this.#runableResources.initForRunable(this.#topSuite.id);
|
||||
const jasmineTimer = new j$.Timer();
|
||||
jasmineTimer.start();
|
||||
|
||||
@@ -132,7 +99,7 @@ getJasmineRequireObj().Runner = function(j$) {
|
||||
* @property {Boolean} parallel - Whether Jasmine is being run in parallel mode.
|
||||
* @since 2.0.0
|
||||
*/
|
||||
await this.reporter_.jasmineStarted({
|
||||
await this.#reportDispatcher.jasmineStarted({
|
||||
// In parallel mode, the jasmineStarted event is separately dispatched
|
||||
// by jasmine-npm. This event only reaches reporters in non-parallel.
|
||||
totalSpecsDefined,
|
||||
@@ -140,23 +107,25 @@ getJasmineRequireObj().Runner = function(j$) {
|
||||
parallel: false
|
||||
});
|
||||
|
||||
this.currentlyExecutingSuites_.push(this.topSuite_);
|
||||
await processor.execute();
|
||||
this.#currentRunableTracker.pushSuite(this.#topSuite);
|
||||
const treeRunner = new j$.TreeRunner({
|
||||
executionTree: this.#executionTree,
|
||||
globalErrors: this.#globalErrors,
|
||||
runableResources: this.#runableResources,
|
||||
reportDispatcher: this.#reportDispatcher,
|
||||
runQueue: this.#runQueue,
|
||||
getConfig: this.#getConfig,
|
||||
currentRunableTracker: this.#currentRunableTracker
|
||||
});
|
||||
const { hasFailures } = await treeRunner.execute();
|
||||
|
||||
if (this.topSuite_.hadBeforeAllFailure) {
|
||||
await this.reportChildrenOfBeforeAllFailure_(this.topSuite_);
|
||||
}
|
||||
|
||||
this.runableResources_.clearForRunable(this.topSuite_.id);
|
||||
this.currentlyExecutingSuites_.pop();
|
||||
this.#runableResources.clearForRunable(this.#topSuite.id);
|
||||
this.#currentRunableTracker.popSuite();
|
||||
let overallStatus, incompleteReason, incompleteCode;
|
||||
|
||||
if (
|
||||
this.hasFailures ||
|
||||
this.topSuite_.result.failedExpectations.length > 0
|
||||
) {
|
||||
if (hasFailures || this.#topSuite.result.failedExpectations.length > 0) {
|
||||
overallStatus = 'failed';
|
||||
} else if (this.focusedRunables_().length > 0) {
|
||||
} else if (this.#getFocusedRunables().length > 0) {
|
||||
overallStatus = 'incomplete';
|
||||
incompleteReason = 'fit() or fdescribe() was found';
|
||||
incompleteCode = 'focused';
|
||||
@@ -187,53 +156,13 @@ getJasmineRequireObj().Runner = function(j$) {
|
||||
incompleteReason: incompleteReason,
|
||||
incompleteCode: incompleteCode,
|
||||
order: order,
|
||||
failedExpectations: this.topSuite_.result.failedExpectations,
|
||||
deprecationWarnings: this.topSuite_.result.deprecationWarnings
|
||||
failedExpectations: this.#topSuite.result.failedExpectations,
|
||||
deprecationWarnings: this.#topSuite.result.deprecationWarnings
|
||||
};
|
||||
this.topSuite_.reportedDone = true;
|
||||
await this.reporter_.jasmineDone(jasmineDoneInfo);
|
||||
this.#topSuite.reportedDone = true;
|
||||
await this.#reportDispatcher.jasmineDone(jasmineDoneInfo);
|
||||
return jasmineDoneInfo;
|
||||
}
|
||||
|
||||
reportSuiteDone_(suite, result, next) {
|
||||
suite.reportedDone = true;
|
||||
this.reporter_.suiteDone(result).then(next);
|
||||
}
|
||||
|
||||
async reportChildrenOfBeforeAllFailure_(suite) {
|
||||
for (const child of suite.children) {
|
||||
if (child instanceof j$.Suite) {
|
||||
await this.reporter_.suiteStarted(child.result);
|
||||
await this.reportChildrenOfBeforeAllFailure_(child);
|
||||
|
||||
// Marking the suite passed is consistent with how suites that
|
||||
// contain failed specs but no suite-level failures are reported.
|
||||
child.result.status = 'passed';
|
||||
|
||||
await this.reporter_.suiteDone(child.result);
|
||||
} else {
|
||||
/* a spec */
|
||||
await this.reporter_.specStarted(child.result);
|
||||
|
||||
child.addExpectationResult(
|
||||
false,
|
||||
{
|
||||
passed: false,
|
||||
message:
|
||||
'Not run because a beforeAll function failed. The ' +
|
||||
'beforeAll failure will be reported on the suite that ' +
|
||||
'caused it.'
|
||||
},
|
||||
true
|
||||
);
|
||||
child.result.status = 'failed';
|
||||
|
||||
await new Promise(resolve => {
|
||||
this.reportSpecDone_(child, child.result, resolve);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Runner;
|
||||
|
||||
@@ -2,7 +2,6 @@ getJasmineRequireObj().Spec = function(j$) {
|
||||
function Spec(attrs) {
|
||||
this.expectationFactory = attrs.expectationFactory;
|
||||
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
|
||||
this.resultCallback = attrs.resultCallback || function() {};
|
||||
this.id = attrs.id;
|
||||
this.filename = attrs.filename;
|
||||
this.parentSuiteId = attrs.parentSuiteId;
|
||||
@@ -18,7 +17,6 @@ getJasmineRequireObj().Spec = function(j$) {
|
||||
function() {
|
||||
return {};
|
||||
};
|
||||
this.onStart = attrs.onStart || function() {};
|
||||
this.autoCleanClosures =
|
||||
attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures;
|
||||
|
||||
@@ -65,79 +63,31 @@ getJasmineRequireObj().Spec = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
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.execute = function(
|
||||
queueRunnerFactory,
|
||||
onComplete,
|
||||
excluded,
|
||||
failSpecWithNoExp
|
||||
) {
|
||||
const onStart = {
|
||||
fn: done => {
|
||||
this.timer.start();
|
||||
this.onStart(this, done);
|
||||
}
|
||||
};
|
||||
Spec.prototype.executionStarted = function() {
|
||||
this.timer.start();
|
||||
};
|
||||
|
||||
const complete = {
|
||||
fn: done => {
|
||||
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;
|
||||
}
|
||||
|
||||
this.resultCallback(this.result, done);
|
||||
},
|
||||
type: 'specCleanup'
|
||||
};
|
||||
|
||||
const fns = this.beforeAndAfterFns();
|
||||
|
||||
const runnerConfig = {
|
||||
isLeaf: true,
|
||||
queueableFns: [...fns.befores, this.queueableFn, ...fns.afters],
|
||||
onException: e => this.handleException(e),
|
||||
onMultipleDone: () => {
|
||||
// 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.
|
||||
this.onLateError(
|
||||
new Error(
|
||||
'An asynchronous spec, beforeEach, or afterEach function called its ' +
|
||||
"'done' callback more than once.\n(in spec: " +
|
||||
this.getFullName() +
|
||||
')'
|
||||
)
|
||||
);
|
||||
},
|
||||
onComplete: () => {
|
||||
if (this.result.status === 'failed') {
|
||||
onComplete(new j$.StopExecutionError('spec failed'));
|
||||
} else {
|
||||
onComplete();
|
||||
}
|
||||
},
|
||||
userContext: this.userContext(),
|
||||
runnableName: this.getFullName.bind(this)
|
||||
};
|
||||
|
||||
if (this.markedPending || excluded === true) {
|
||||
runnerConfig.queueableFns = [];
|
||||
Spec.prototype.executionFinished = function(excluded, failSpecWithNoExp) {
|
||||
if (this.autoCleanClosures) {
|
||||
this.queueableFn.fn = null;
|
||||
}
|
||||
|
||||
runnerConfig.queueableFns.unshift(onStart);
|
||||
runnerConfig.queueableFns.push(complete);
|
||||
this.result.status = this.status(excluded, failSpecWithNoExp);
|
||||
this.result.duration = this.timer.elapsed();
|
||||
|
||||
queueRunnerFactory(runnerConfig);
|
||||
if (this.result.status !== 'failed') {
|
||||
this.result.debugLogs = null;
|
||||
}
|
||||
};
|
||||
|
||||
Spec.prototype.reset = function() {
|
||||
@@ -228,6 +178,8 @@ getJasmineRequireObj().Spec = function(j$) {
|
||||
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;
|
||||
|
||||
@@ -54,7 +54,9 @@ getJasmineRequireObj().SpyFactory = function(j$) {
|
||||
}
|
||||
|
||||
if (methods.length === 0 && properties.length === 0) {
|
||||
throw 'createSpyObj requires a non-empty array or object of method names to create spies for';
|
||||
throw new Error(
|
||||
'createSpyObj requires a non-empty array or object of method names to create spies for'
|
||||
);
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
||||
@@ -10,8 +10,6 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
|
||||
return options.asyncExpectationFactory(actual, suite, 'Spec');
|
||||
};
|
||||
this.onLateError_ = options.onLateError;
|
||||
this.specResultCallback_ = options.specResultCallback;
|
||||
this.specStarted_ = options.specStarted;
|
||||
|
||||
this.nextSuiteId_ = 0;
|
||||
this.nextSpecId_ = 0;
|
||||
@@ -247,11 +245,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
|
||||
expectationFactory: this.expectationFactory_,
|
||||
asyncExpectationFactory: this.specAsyncExpectationFactory_,
|
||||
onLateError: this.onLateError_,
|
||||
resultCallback: (result, next) => {
|
||||
this.specResultCallback_(spec, result, next);
|
||||
},
|
||||
getPath: spec => this.getSpecPath_(spec, suite),
|
||||
onStart: (spec, next) => this.specStarted_(spec, suite, next),
|
||||
description: description,
|
||||
userContext: function() {
|
||||
return suite.clonedSharedUserContext();
|
||||
|
||||
@@ -1,76 +1,59 @@
|
||||
getJasmineRequireObj().TreeProcessor = function() {
|
||||
function TreeProcessor(attrs) {
|
||||
const tree = attrs.tree;
|
||||
const runnableIds = attrs.runnableIds;
|
||||
const queueRunnerFactory = attrs.queueRunnerFactory;
|
||||
const nodeStart = attrs.nodeStart || function() {};
|
||||
const nodeComplete = attrs.nodeComplete || function() {};
|
||||
const failSpecWithNoExpectations = !!attrs.failSpecWithNoExpectations;
|
||||
const orderChildren =
|
||||
attrs.orderChildren ||
|
||||
function(node) {
|
||||
return node.children;
|
||||
};
|
||||
const excludeNode =
|
||||
attrs.excludeNode ||
|
||||
function(node) {
|
||||
return false;
|
||||
};
|
||||
let stats = { valid: true };
|
||||
let processed = false;
|
||||
const defaultMin = Infinity;
|
||||
const defaultMax = 1 - Infinity;
|
||||
getJasmineRequireObj().TreeProcessor = function(j$) {
|
||||
const defaultMin = Infinity;
|
||||
const defaultMax = 1 - Infinity;
|
||||
|
||||
this.processTree = function() {
|
||||
processNode(tree, true);
|
||||
processed = true;
|
||||
return stats;
|
||||
};
|
||||
// Transforms the suite tree into an execution tree, which represents the set
|
||||
// of specs and (possibly interleaved) suites to be run in the order they are
|
||||
// to be run in.
|
||||
class TreeProcessor {
|
||||
#tree;
|
||||
#runnableIds;
|
||||
#orderChildren;
|
||||
#excludeNode;
|
||||
#stats;
|
||||
|
||||
this.execute = async function() {
|
||||
if (!processed) {
|
||||
this.processTree();
|
||||
}
|
||||
constructor(attrs) {
|
||||
this.#tree = attrs.tree;
|
||||
this.#runnableIds = attrs.runnableIds;
|
||||
|
||||
if (!stats.valid) {
|
||||
throw 'invalid order';
|
||||
}
|
||||
this.#orderChildren =
|
||||
attrs.orderChildren ||
|
||||
function(node) {
|
||||
return node.children;
|
||||
};
|
||||
this.#excludeNode =
|
||||
attrs.excludeNode ||
|
||||
function(node) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
const childFns = wrapChildren(tree, 0);
|
||||
processTree() {
|
||||
this.#stats = {};
|
||||
this.#processNode(this.#tree, true);
|
||||
const result = new ExecutionTree(this.#tree, this.#stats);
|
||||
this.#stats = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
await new Promise(function(resolve) {
|
||||
queueRunnerFactory({
|
||||
queueableFns: childFns,
|
||||
userContext: tree.sharedUserContext(),
|
||||
onException: function() {
|
||||
tree.handleException.apply(tree, arguments);
|
||||
},
|
||||
onComplete: resolve,
|
||||
onMultipleDone: tree.onMultipleDone
|
||||
? tree.onMultipleDone.bind(tree)
|
||||
: null
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function runnableIndex(id) {
|
||||
for (let i = 0; i < runnableIds.length; i++) {
|
||||
if (runnableIds[i] === id) {
|
||||
#runnableIndex(id) {
|
||||
for (let i = 0; i < this.#runnableIds.length; i++) {
|
||||
if (this.#runnableIds[i] === id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processNode(node, parentExcluded) {
|
||||
const executableIndex = runnableIndex(node.id);
|
||||
#processNode(node, parentExcluded) {
|
||||
const executableIndex = this.#runnableIndex(node.id);
|
||||
|
||||
if (executableIndex !== undefined) {
|
||||
parentExcluded = false;
|
||||
}
|
||||
|
||||
if (!node.children) {
|
||||
const excluded = parentExcluded || excludeNode(node);
|
||||
stats[node.id] = {
|
||||
const excluded = parentExcluded || this.#excludeNode(node);
|
||||
this.#stats[node.id] = {
|
||||
excluded: excluded,
|
||||
willExecute: !excluded && !node.markedPending,
|
||||
segments: [
|
||||
@@ -86,178 +69,147 @@ getJasmineRequireObj().TreeProcessor = function() {
|
||||
} else {
|
||||
let hasExecutableChild = false;
|
||||
|
||||
const orderedChildren = orderChildren(node);
|
||||
const orderedChildren = this.#orderChildren(node);
|
||||
|
||||
for (let i = 0; i < orderedChildren.length; i++) {
|
||||
const child = orderedChildren[i];
|
||||
|
||||
processNode(child, parentExcluded);
|
||||
|
||||
if (!stats.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const childStats = stats[child.id];
|
||||
|
||||
this.#processNode(child, parentExcluded);
|
||||
const childStats = this.#stats[child.id];
|
||||
hasExecutableChild = hasExecutableChild || childStats.willExecute;
|
||||
}
|
||||
|
||||
stats[node.id] = {
|
||||
this.#stats[node.id] = {
|
||||
excluded: parentExcluded,
|
||||
willExecute: hasExecutableChild
|
||||
};
|
||||
|
||||
segmentChildren(node, orderedChildren, stats[node.id], executableIndex);
|
||||
segmentChildren(node, orderedChildren, this.#stats, executableIndex);
|
||||
|
||||
if (!node.canBeReentered() && stats[node.id].segments.length > 1) {
|
||||
stats = { valid: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startingMin(executableIndex) {
|
||||
return executableIndex === undefined ? defaultMin : executableIndex;
|
||||
}
|
||||
|
||||
function startingMax(executableIndex) {
|
||||
return executableIndex === undefined ? defaultMax : executableIndex;
|
||||
}
|
||||
|
||||
function segmentChildren(
|
||||
node,
|
||||
orderedChildren,
|
||||
nodeStats,
|
||||
executableIndex
|
||||
) {
|
||||
let currentSegment = {
|
||||
index: 0,
|
||||
owner: node,
|
||||
nodes: [],
|
||||
min: startingMin(executableIndex),
|
||||
max: startingMax(executableIndex)
|
||||
},
|
||||
result = [currentSegment],
|
||||
lastMax = defaultMax,
|
||||
orderedChildSegments = orderChildSegments(orderedChildren);
|
||||
|
||||
function isSegmentBoundary(minIndex) {
|
||||
return (
|
||||
lastMax !== defaultMax &&
|
||||
minIndex !== defaultMin &&
|
||||
lastMax < minIndex - 1
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < orderedChildSegments.length; i++) {
|
||||
const childSegment = orderedChildSegments[i],
|
||||
maxIndex = childSegment.max,
|
||||
minIndex = childSegment.min;
|
||||
|
||||
if (isSegmentBoundary(minIndex)) {
|
||||
currentSegment = {
|
||||
index: result.length,
|
||||
owner: node,
|
||||
nodes: [],
|
||||
min: defaultMin,
|
||||
max: defaultMax
|
||||
};
|
||||
result.push(currentSegment);
|
||||
}
|
||||
|
||||
currentSegment.nodes.push(childSegment);
|
||||
currentSegment.min = Math.min(currentSegment.min, minIndex);
|
||||
currentSegment.max = Math.max(currentSegment.max, maxIndex);
|
||||
lastMax = maxIndex;
|
||||
}
|
||||
|
||||
nodeStats.segments = result;
|
||||
}
|
||||
|
||||
function orderChildSegments(children) {
|
||||
const specifiedOrder = [],
|
||||
unspecifiedOrder = [];
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i],
|
||||
segments = stats[child.id].segments;
|
||||
|
||||
for (let j = 0; j < segments.length; j++) {
|
||||
const seg = segments[j];
|
||||
|
||||
if (seg.min === defaultMin) {
|
||||
unspecifiedOrder.push(seg);
|
||||
if (this.#stats[node.id].segments.length > 1) {
|
||||
if (node.canBeReentered()) {
|
||||
j$.getEnv().deprecated(
|
||||
'The specified spec/suite order splits up a suite, running unrelated specs in the middle of it. This will become an error in a future release.'
|
||||
);
|
||||
} else {
|
||||
specifiedOrder.push(seg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
specifiedOrder.sort(function(a, b) {
|
||||
return a.min - b.min;
|
||||
});
|
||||
|
||||
return specifiedOrder.concat(unspecifiedOrder);
|
||||
}
|
||||
|
||||
function executeNode(node, segmentNumber) {
|
||||
if (node.children) {
|
||||
return {
|
||||
fn: function(done) {
|
||||
const onStart = {
|
||||
fn: function(next) {
|
||||
nodeStart(node, next);
|
||||
}
|
||||
};
|
||||
|
||||
queueRunnerFactory({
|
||||
onComplete: function() {
|
||||
const args = Array.prototype.slice.call(arguments, [0]);
|
||||
node.cleanupBeforeAfter();
|
||||
nodeComplete(node, node.getResult(), function() {
|
||||
done.apply(undefined, args);
|
||||
});
|
||||
},
|
||||
queueableFns: [onStart].concat(wrapChildren(node, segmentNumber)),
|
||||
userContext: node.sharedUserContext(),
|
||||
onException: function() {
|
||||
node.handleException.apply(node, arguments);
|
||||
},
|
||||
onMultipleDone: node.onMultipleDone
|
||||
? node.onMultipleDone.bind(node)
|
||||
: null
|
||||
});
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
fn: function(done) {
|
||||
node.execute(
|
||||
queueRunnerFactory,
|
||||
done,
|
||||
stats[node.id].excluded,
|
||||
failSpecWithNoExpectations
|
||||
throw new Error(
|
||||
'Invalid order: would cause a beforeAll or afterAll to be run multiple times'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExecutionTree {
|
||||
#stats;
|
||||
|
||||
constructor(topSuite, stats) {
|
||||
Object.defineProperty(this, 'topSuite', {
|
||||
writable: false,
|
||||
value: topSuite
|
||||
});
|
||||
this.#stats = stats;
|
||||
}
|
||||
|
||||
childrenOfTopSuite() {
|
||||
return this.childrenOfSuiteSegment(this.topSuite, 0);
|
||||
}
|
||||
|
||||
childrenOfSuiteSegment(suite, segmentNumber) {
|
||||
const segmentChildren = this.#stats[suite.id].segments[segmentNumber]
|
||||
.nodes;
|
||||
return segmentChildren.map(function(child) {
|
||||
if (child.owner.children) {
|
||||
return { suite: child.owner, segmentNumber: child.index };
|
||||
} else {
|
||||
return { spec: child.owner };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isExcluded(node) {
|
||||
const nodeStats = this.#stats[node.id];
|
||||
return node.children ? !nodeStats.willExecute : nodeStats.excluded;
|
||||
}
|
||||
}
|
||||
|
||||
function segmentChildren(node, orderedChildren, stats, executableIndex) {
|
||||
let currentSegment = {
|
||||
index: 0,
|
||||
owner: node,
|
||||
nodes: [],
|
||||
min: startingMin(executableIndex),
|
||||
max: startingMax(executableIndex)
|
||||
},
|
||||
result = [currentSegment],
|
||||
lastMax = defaultMax,
|
||||
orderedChildSegments = orderChildSegments(orderedChildren, stats);
|
||||
|
||||
function isSegmentBoundary(minIndex) {
|
||||
return (
|
||||
lastMax !== defaultMax &&
|
||||
minIndex !== defaultMin &&
|
||||
lastMax < minIndex - 1
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < orderedChildSegments.length; i++) {
|
||||
const childSegment = orderedChildSegments[i],
|
||||
maxIndex = childSegment.max,
|
||||
minIndex = childSegment.min;
|
||||
|
||||
if (isSegmentBoundary(minIndex)) {
|
||||
currentSegment = {
|
||||
index: result.length,
|
||||
owner: node,
|
||||
nodes: [],
|
||||
min: defaultMin,
|
||||
max: defaultMax
|
||||
};
|
||||
result.push(currentSegment);
|
||||
}
|
||||
|
||||
currentSegment.nodes.push(childSegment);
|
||||
currentSegment.min = Math.min(currentSegment.min, minIndex);
|
||||
currentSegment.max = Math.max(currentSegment.max, maxIndex);
|
||||
lastMax = maxIndex;
|
||||
}
|
||||
|
||||
stats[node.id].segments = result;
|
||||
}
|
||||
|
||||
function orderChildSegments(children, stats) {
|
||||
const specifiedOrder = [],
|
||||
unspecifiedOrder = [];
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i],
|
||||
segments = stats[child.id].segments;
|
||||
|
||||
for (let j = 0; j < segments.length; j++) {
|
||||
const seg = segments[j];
|
||||
|
||||
if (seg.min === defaultMin) {
|
||||
unspecifiedOrder.push(seg);
|
||||
} else {
|
||||
specifiedOrder.push(seg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function wrapChildren(node, segmentNumber) {
|
||||
const result = [],
|
||||
segmentChildren = stats[node.id].segments[segmentNumber].nodes;
|
||||
specifiedOrder.sort(function(a, b) {
|
||||
return a.min - b.min;
|
||||
});
|
||||
|
||||
for (let i = 0; i < segmentChildren.length; i++) {
|
||||
result.push(
|
||||
executeNode(segmentChildren[i].owner, segmentChildren[i].index)
|
||||
);
|
||||
}
|
||||
return specifiedOrder.concat(unspecifiedOrder);
|
||||
}
|
||||
|
||||
if (!stats[node.id].willExecute) {
|
||||
return result;
|
||||
}
|
||||
function startingMin(executableIndex) {
|
||||
return executableIndex === undefined ? defaultMin : executableIndex;
|
||||
}
|
||||
|
||||
return node.beforeAllFns.concat(result).concat(node.afterAllFns);
|
||||
}
|
||||
function startingMax(executableIndex) {
|
||||
return executableIndex === undefined ? defaultMax : executableIndex;
|
||||
}
|
||||
|
||||
return TreeProcessor;
|
||||
|
||||
303
src/core/TreeRunner.js
Normal file
303
src/core/TreeRunner.js
Normal file
@@ -0,0 +1,303 @@
|
||||
getJasmineRequireObj().TreeRunner = function(j$) {
|
||||
class TreeRunner {
|
||||
#executionTree;
|
||||
#setTimeout;
|
||||
#globalErrors;
|
||||
#runableResources;
|
||||
#reportDispatcher;
|
||||
#runQueue;
|
||||
#getConfig;
|
||||
#currentRunableTracker;
|
||||
#hasFailures;
|
||||
|
||||
constructor(attrs) {
|
||||
this.#executionTree = attrs.executionTree;
|
||||
this.#globalErrors = attrs.globalErrors;
|
||||
this.#setTimeout = attrs.setTimeout || setTimeout.bind(globalThis);
|
||||
this.#runableResources = attrs.runableResources;
|
||||
this.#reportDispatcher = attrs.reportDispatcher;
|
||||
this.#runQueue = attrs.runQueue;
|
||||
this.#getConfig = attrs.getConfig;
|
||||
this.#currentRunableTracker = attrs.currentRunableTracker;
|
||||
}
|
||||
|
||||
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()
|
||||
});
|
||||
});
|
||||
|
||||
if (topSuite.hadBeforeAllFailure) {
|
||||
await this.#reportChildrenOfBeforeAllFailure(topSuite);
|
||||
}
|
||||
|
||||
return { hasFailures: this.#hasFailures };
|
||||
}
|
||||
|
||||
#wrapNodes(nodes) {
|
||||
return nodes.map(node => {
|
||||
return {
|
||||
fn: done => {
|
||||
if (node.suite) {
|
||||
this.#executeSuiteSegment(node.suite, node.segmentNumber, done);
|
||||
} else {
|
||||
this._executeSpec(node.spec, done);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Only exposed for testing.
|
||||
_executeSpec(spec, specOverallDone) {
|
||||
const onStart = next => {
|
||||
this.#currentRunableTracker.setCurrentSpec(spec);
|
||||
this.#runableResources.initForRunable(spec.id, spec.parentSuiteId);
|
||||
this.#reportDispatcher.specStarted(spec.result).then(next);
|
||||
};
|
||||
const resultCallback = (result, next) => {
|
||||
this.#specComplete(spec).then(next);
|
||||
};
|
||||
const queueableFns = this.#specQueueableFns(
|
||||
spec,
|
||||
onStart,
|
||||
resultCallback
|
||||
);
|
||||
|
||||
this.#runQueue({
|
||||
isLeaf: true,
|
||||
queueableFns,
|
||||
onException: e => spec.handleException(e),
|
||||
onMultipleDone: () => {
|
||||
// Issue an erorr. 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.
|
||||
spec.onLateError(
|
||||
new Error(
|
||||
'An asynchronous spec, beforeEach, or afterEach function called its ' +
|
||||
"'done' callback more than once.\n(in spec: " +
|
||||
spec.getFullName() +
|
||||
')'
|
||||
)
|
||||
);
|
||||
},
|
||||
onComplete: () => {
|
||||
if (spec.result.status === 'failed') {
|
||||
specOverallDone(new j$.StopExecutionError('spec failed'));
|
||||
} else {
|
||||
specOverallDone();
|
||||
}
|
||||
},
|
||||
userContext: spec.userContext(),
|
||||
runnableName: spec.getFullName.bind(spec),
|
||||
SkipPolicy: j$.CompleteOnFirstErrorSkipPolicy
|
||||
});
|
||||
}
|
||||
|
||||
#specQueueableFns(spec, onStart, resultCallback) {
|
||||
const config = this.#getConfig();
|
||||
const excluded = this.#executionTree.isExcluded(spec);
|
||||
const ba = spec.beforeAndAfterFns();
|
||||
let fns = [...ba.befores, spec.queueableFn, ...ba.afters];
|
||||
|
||||
if (spec.markedPending || excluded === true) {
|
||||
fns = [];
|
||||
}
|
||||
|
||||
const start = {
|
||||
fn(done) {
|
||||
spec.executionStarted();
|
||||
onStart(done);
|
||||
}
|
||||
};
|
||||
|
||||
const complete = {
|
||||
fn(done) {
|
||||
spec.executionFinished(excluded, config.failSpecWithNoExpectations);
|
||||
resultCallback(spec.result, done);
|
||||
},
|
||||
type: 'specCleanup'
|
||||
};
|
||||
|
||||
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(complete);
|
||||
return fns;
|
||||
}
|
||||
|
||||
#executeSuiteSegment(suite, segmentNumber, done) {
|
||||
const wrappedChildren = this.#wrapNodes(
|
||||
this.#executionTree.childrenOfSuiteSegment(suite, segmentNumber)
|
||||
);
|
||||
const onStart = {
|
||||
fn: next => {
|
||||
this.#suiteSegmentStart(suite, next);
|
||||
}
|
||||
};
|
||||
const queueableFns = [
|
||||
onStart,
|
||||
...this.#addBeforeAndAfterAlls(suite, wrappedChildren)
|
||||
];
|
||||
|
||||
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]);
|
||||
this.#suiteSegmentComplete(suite, suite.getResult(), () => {
|
||||
done.apply(undefined, args);
|
||||
});
|
||||
}.bind(this),
|
||||
queueableFns,
|
||||
userContext: suite.sharedUserContext(),
|
||||
onException: function() {
|
||||
suite.handleException.apply(suite, arguments);
|
||||
},
|
||||
onMultipleDone: suite.onMultipleDone
|
||||
? suite.onMultipleDone.bind(suite)
|
||||
: null,
|
||||
SkipPolicy: this.#suiteSkipPolicy()
|
||||
});
|
||||
}
|
||||
|
||||
#suiteSegmentStart(suite, next) {
|
||||
this.#currentRunableTracker.pushSuite(suite);
|
||||
this.#runableResources.initForRunable(suite.id, suite.parentSuite.id);
|
||||
this.#reportDispatcher.suiteStarted(suite.result).then(next);
|
||||
suite.startTimer();
|
||||
}
|
||||
|
||||
#suiteSegmentComplete(suite, result, next) {
|
||||
suite.cleanupBeforeAfter();
|
||||
|
||||
if (suite !== this.#currentRunableTracker.currentSuite()) {
|
||||
throw new Error('Tried to complete the wrong suite');
|
||||
}
|
||||
|
||||
this.#runableResources.clearForRunable(suite.id);
|
||||
this.#currentRunableTracker.popSuite();
|
||||
|
||||
if (result.status === 'failed') {
|
||||
this.#hasFailures = true;
|
||||
}
|
||||
suite.endTimer();
|
||||
|
||||
if (suite.hadBeforeAllFailure) {
|
||||
this.#reportChildrenOfBeforeAllFailure(suite).then(() => {
|
||||
this.#reportSuiteDone(suite, result, next);
|
||||
});
|
||||
} else {
|
||||
this.#reportSuiteDone(suite, result, next);
|
||||
}
|
||||
}
|
||||
|
||||
#reportSuiteDone(suite, result, next) {
|
||||
suite.reportedDone = true;
|
||||
this.#reportDispatcher.suiteDone(result).then(next);
|
||||
}
|
||||
|
||||
async #specComplete(spec) {
|
||||
this.#runableResources.clearForRunable(spec.id);
|
||||
this.#currentRunableTracker.setCurrentSpec(null);
|
||||
|
||||
if (spec.result.status === 'failed') {
|
||||
this.#hasFailures = true;
|
||||
}
|
||||
|
||||
await this.#reportSpecDone(spec);
|
||||
}
|
||||
|
||||
async #reportSpecDone(spec) {
|
||||
spec.reportedDone = true;
|
||||
await this.#reportDispatcher.specDone(spec.result);
|
||||
}
|
||||
|
||||
async #reportChildrenOfBeforeAllFailure(suite) {
|
||||
for (const child of suite.children) {
|
||||
if (child instanceof j$.Suite) {
|
||||
await this.#reportDispatcher.suiteStarted(child.result);
|
||||
await this.#reportChildrenOfBeforeAllFailure(child);
|
||||
|
||||
// Marking the suite passed is consistent with how suites that
|
||||
// contain failed specs but no suite-level failures are reported.
|
||||
child.result.status = 'passed';
|
||||
|
||||
await this.#reportDispatcher.suiteDone(child.result);
|
||||
} else {
|
||||
/* a spec */
|
||||
await this.#reportDispatcher.specStarted(child.result);
|
||||
|
||||
child.addExpectationResult(
|
||||
false,
|
||||
{
|
||||
passed: false,
|
||||
message:
|
||||
'Not run because a beforeAll function failed. The ' +
|
||||
'beforeAll failure will be reported on the suite that ' +
|
||||
'caused it.'
|
||||
},
|
||||
true
|
||||
);
|
||||
child.result.status = 'failed';
|
||||
await this.#reportSpecDone(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#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;
|
||||
} else {
|
||||
return j$.SkipAfterBeforeAllErrorPolicy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TreeRunner;
|
||||
};
|
||||
@@ -70,6 +70,7 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
|
||||
j$.reporterEvents = jRequire.reporterEvents(j$);
|
||||
j$.ReportDispatcher = jRequire.ReportDispatcher(j$);
|
||||
j$.ParallelReportDispatcher = jRequire.ParallelReportDispatcher(j$);
|
||||
j$.CurrentRunableTracker = jRequire.CurrentRunableTracker();
|
||||
j$.RunableResources = jRequire.RunableResources(j$);
|
||||
j$.Runner = jRequire.Runner(j$);
|
||||
j$.Spec = jRequire.Spec(j$);
|
||||
@@ -83,14 +84,27 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
|
||||
j$.Suite = jRequire.Suite(j$);
|
||||
j$.SuiteBuilder = jRequire.SuiteBuilder(j$);
|
||||
j$.Timer = jRequire.Timer();
|
||||
j$.TreeProcessor = jRequire.TreeProcessor();
|
||||
j$.TreeProcessor = jRequire.TreeProcessor(j$);
|
||||
j$.TreeRunner = jRequire.TreeRunner(j$);
|
||||
j$.version = jRequire.version();
|
||||
j$.Order = jRequire.Order();
|
||||
j$.DiffBuilder = jRequire.DiffBuilder(j$);
|
||||
j$.NullDiffBuilder = jRequire.NullDiffBuilder(j$);
|
||||
j$.ObjectPath = jRequire.ObjectPath(j$);
|
||||
j$.MismatchTree = jRequire.MismatchTree(j$);
|
||||
j$.GlobalErrors = jRequire.GlobalErrors(j$);
|
||||
|
||||
// zone.js tries to monkey patch GlobalErrors in a way that is either a
|
||||
// no-op or causes Jasmine to crash, depending on whether it's done before
|
||||
// or after env creation. Prevent that.
|
||||
const GlobalErrors = jRequire.GlobalErrors(j$);
|
||||
Object.defineProperty(j$, 'GlobalErrors', {
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
get() {
|
||||
return GlobalErrors;
|
||||
},
|
||||
set() {}
|
||||
});
|
||||
|
||||
j$.Truthy = jRequire.Truthy(j$);
|
||||
j$.Falsy = jRequire.Falsy(j$);
|
||||
|
||||
@@ -168,6 +168,18 @@ getJasmineRequireObj().interface = function(jasmine, env) {
|
||||
return env.afterAll.apply(env, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a user-defined property as part of the properties field of {@link SpecResult}
|
||||
* @name getSpecProperty
|
||||
* @since 5.10.0
|
||||
* @function
|
||||
* @param {String} key The name of the property
|
||||
* @returns {*} The value of the property
|
||||
*/
|
||||
getSpecProperty: function(key) {
|
||||
return env.getSpecProperty(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a user-defined property that will be provided to reporters as part of the properties field of {@link SpecResult}
|
||||
* @name setSpecProperty
|
||||
|
||||
@@ -523,6 +523,11 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
'a',
|
||||
{ href: specHref(resultNode.result) },
|
||||
specDescription
|
||||
),
|
||||
createDom(
|
||||
'span',
|
||||
{ className: 'jasmine-spec-duration' },
|
||||
'(' + resultNode.result.duration + 'ms)'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -331,6 +331,10 @@ body {
|
||||
&.jasmine-excluded a:before {
|
||||
content: $passing-mark + $space;
|
||||
}
|
||||
|
||||
.jasmine-spec-duration {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user