Skip to content

Commit 42b10d5

Browse files
committed
Made the promise returned from Jasmine#execute usable
* An exitOnCompletion property to control whether Jasmine should make the Nnode process exit directly, rather than as a side effect of calling onComplete * The promise returned from execute is resolved to the overall status * onComplete is marked deprecated. onComplete's behavior of installing a completion handler and also preventing exit is clever, and actually what anyone using it probably wants, but it's also non-obvious. The two-step process of setting exitOnCompletion = false and then using the promise returned from execute() is not as slick, but it's probably better for an audience that increasingly (for good reason) prefers promise- or async/await-based async.
1 parent 2f014c9 commit 42b10d5

8 files changed

+182
-12
lines changed

lib/jasmine.js

+27-4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ function Jasmine(options) {
7272
this.coreVersion = function() {
7373
return jasmineCore.version();
7474
};
75+
76+
/**
77+
* Whether to cause the Node process to exit when the suite finishes executing.
78+
*
79+
* _Note_: If {@link Jasmine#onComplete|onComplete} is called, Jasmine will not
80+
* exit when the suite completes even if exitOnCompletion is set to true.
81+
* @name Jasmine#exitOnCompletion
82+
* @type {boolean}
83+
* @default true
84+
*/
85+
this.exitOnCompletion = true;
7586
}
7687

7788
/**
@@ -432,8 +443,12 @@ function addFiles(kind) {
432443
*
433444
* _Note_: Only one callback can be registered. The callback will be called
434445
* after the suite has completed and the results have been finalized, but not
435-
* necessarily before all of Jasmine's cleanup has finished.
446+
* necessarily before all of Jasmine's cleanup has finished. Calling this
447+
* function will also prevent Jasmine from exiting the Node process at the end
448+
* of suite execution.
436449
*
450+
* @deprecated Set {@link Jasmine#exitOnCompletion|exitOnCompletion} to false
451+
* and use the promise returned from {@link Jasmine#execute|execute} instead.
437452
* @param {function} onCompleteCallback
438453
*/
439454
Jasmine.prototype.onComplete = function(onCompleteCallback) {
@@ -476,7 +491,7 @@ Jasmine.prototype.exitCodeCompletion = function(passed) {
476491
});
477492
function exitIfAllStreamsCompleted() {
478493
writesToWait--;
479-
if (writesToWait === 0) {
494+
if (writesToWait === 0 && jasmineRunner.exitOnCompletion) {
480495
if(passed) {
481496
jasmineRunner.exit(0);
482497
}
@@ -508,11 +523,15 @@ function checkForJsFileImportSupport() {
508523

509524
/**
510525
* Runs the test suite.
526+
*
527+
* _Note_: Set {@link Jasmine#exitOnCompletion|exitOnCompletion} to false if you
528+
* intend to use the returned promise. Otherwise, the Node process will
529+
* ordinarily exit before the promise is settled.
511530
* @param {Array.<string>} [files] Spec files to run instead of the previously
512531
* configured set
513532
* @param {string} [filterString] Regex used to filter specs. If specified, only
514533
* specs with matching full names will be run.
515-
* @return {Promise<void>} Promise that is resolved when the suite completes.
534+
* @return {Promise<JasmineDoneInfo>} Promise that is resolved when the suite completes.
516535
*/
517536
Jasmine.prototype.execute = async function(files, filterString) {
518537
this.completionReporter.exitHandler = this.checkExit;
@@ -541,8 +560,12 @@ Jasmine.prototype.execute = async function(files, filterString) {
541560
await this.loadSpecs();
542561

543562
this.addReporter(this.completionReporter);
544-
563+
let overallResult;
564+
this.addReporter({
565+
jasmineDone: r => overallResult = r
566+
});
545567
await new Promise(resolve => {
546568
this.env.execute(null, resolve);
547569
});
570+
return overallResult;
548571
};
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const Jasmine = require('../..');
2+
const jasmine = new Jasmine();
3+
4+
it('fails', function() {
5+
expect(1).toBe(2);
6+
});
7+
8+
jasmine.execute();

spec/fixtures/dontExitOnCompletion.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const Jasmine = require('../..');
2+
const jasmine = new Jasmine();
3+
4+
it('a spec', function() {});
5+
6+
jasmine.exitOnCompletion = false;
7+
jasmine.execute().finally(function() {
8+
setTimeout(function() {
9+
console.log("in setTimeout cb");
10+
});
11+
});

spec/fixtures/promiseFailure.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const Jasmine = require('../..');
2+
const jasmine = new Jasmine();
3+
4+
it('a spec', function() {
5+
expect(1).toBe(2);
6+
});
7+
8+
jasmine.exitOnCompletion = false;
9+
jasmine.execute().then(function(result) {
10+
if (result.overallStatus === 'failed') {
11+
console.log("Promise failure!");
12+
}
13+
});

spec/fixtures/promiseIncomplete.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const Jasmine = require('../..');
2+
const jasmine = new Jasmine();
3+
4+
it('a spec', function() {});
5+
6+
fit('another spec', function() {});
7+
8+
jasmine.exitOnCompletion = false;
9+
jasmine.execute().then(function(result) {
10+
if (result.overallStatus === 'incomplete') {
11+
console.log("Promise incomplete!");
12+
}
13+
});

spec/fixtures/promiseSuccess.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const Jasmine = require('../..');
2+
const jasmine = new Jasmine();
3+
4+
it('a spec', function() {});
5+
6+
jasmine.exitOnCompletion = false;
7+
jasmine.execute().then(function(result) {
8+
if (result.overallStatus === 'passed') {
9+
console.log("Promise success!");
10+
}
11+
});

spec/integration_spec.js

+42-6
Original file line numberDiff line numberDiff line change
@@ -94,18 +94,54 @@ describe('Integration', function () {
9494
expect(exitCode).toEqual(0);
9595
expect(output).toContain('in spec 1\n.in spec 2\n.in spec 3\n.in spec 4\n.in spec 5');
9696
});
97+
98+
describe('Programmatic usage', function() {
99+
it('exits on completion by default', async function() {
100+
const {exitCode, output} = await runCommand('node', ['spec/fixtures/defaultProgrammaticFail.js']);
101+
expect(exitCode).toEqual(1);
102+
expect(output).toContain('1 spec, 1 failure');
103+
});
104+
105+
it('does not exit on completion when exitOnCompletion is set to false', async function() {
106+
const {exitCode, output} = await runCommand('node', ['spec/fixtures/dontExitOnCompletion.js']);
107+
expect(exitCode).toEqual(0);
108+
expect(output).toContain('in setTimeout cb');
109+
});
110+
111+
it('resolves the returned promise when the suite passes', async function() {
112+
const {exitCode, output} = await runCommand('node', ['spec/fixtures/promiseSuccess.js']);
113+
expect(exitCode).toEqual(0);
114+
expect(output).toContain('Promise success!');
115+
});
116+
117+
it('resolves the returned promise when the suite fails', async function() {
118+
const {exitCode, output} = await runCommand('node', ['spec/fixtures/promiseFailure.js']);
119+
expect(exitCode).toEqual(0);
120+
expect(output).toContain('Promise failure!');
121+
});
122+
123+
it('resolves the returned promise when the suite is incomplete', async function() {
124+
const {exitCode, output} = await runCommand('node', ['spec/fixtures/promiseIncomplete.js']);
125+
expect(exitCode).toEqual(0);
126+
expect(output).toContain('Promise incomplete!');
127+
});
128+
});
97129
});
98130

99131
async function runJasmine(cwd, useExperimentalModulesFlag) {
100-
return new Promise(function(resolve) {
101-
const args = ['../../../bin/jasmine.js', '--config=jasmine.json'];
132+
const args = ['../../../bin/jasmine.js', '--config=jasmine.json'];
102133

103-
if (useExperimentalModulesFlag) {
104-
args.unshift('--experimental-modules');
105-
}
134+
if (useExperimentalModulesFlag) {
135+
args.unshift('--experimental-modules');
136+
}
137+
138+
return runCommand('node', args, cwd);
139+
}
106140

141+
async function runCommand(cmd, args, cwd = '.') {
142+
return new Promise(function(resolve) {
107143
const child = child_process.spawn(
108-
'node',
144+
cmd,
109145
args,
110146
{
111147
cwd,

spec/jasmine_spec.js

+57-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ describe('Jasmine', function() {
2727
};
2828

2929
this.testJasmine = new Jasmine({ jasmineCore: this.fakeJasmineCore });
30+
this.testJasmine.exit = function() {
31+
// Don't actually exit the node process
32+
};
3033
});
3134

3235
describe('constructor options', function() {
@@ -572,13 +575,15 @@ describe('Jasmine', function() {
572575
});
573576
});
574577

575-
describe('When #onComplete has been called', function() {
578+
describe('When exitOnCompletion is set to false', function() {
576579
it('does not exit', async function() {
577-
this.testJasmine.onComplete(function() {});
580+
this.testJasmine.exitOnCompletion = false;
578581
await this.runWithOverallStatus('anything');
579582
expect(this.testJasmine.exit).not.toHaveBeenCalled();
580583
});
584+
});
581585

586+
describe('When #onComplete has been called', function() {
582587
it('calls the supplied completion handler with true when the whole suite is green', async function() {
583588
const completionHandler = jasmine.createSpy('completionHandler');
584589
this.testJasmine.onComplete(completionHandler);
@@ -592,6 +597,56 @@ describe('Jasmine', function() {
592597
await this.runWithOverallStatus('failed');
593598
expect(completionHandler).toHaveBeenCalledWith(false);
594599
});
600+
601+
it('does not exit', async function() {
602+
this.testJasmine.onComplete(function() {});
603+
await this.runWithOverallStatus('anything');
604+
expect(this.testJasmine.exit).not.toHaveBeenCalled();
605+
});
606+
607+
it('ignores exitOnCompletion', async function() {
608+
this.testJasmine.onComplete(function() {});
609+
this.testJasmine.exitOnCompletion = true;
610+
await this.runWithOverallStatus('anything');
611+
expect(this.testJasmine.exit).not.toHaveBeenCalled();
612+
});
613+
});
614+
});
615+
616+
describe('The returned promise', function() {
617+
beforeEach(function() {
618+
this.autocompletingFakeEnv = function(overallStatus) {
619+
let reporters = [];
620+
return {
621+
execute: function(ignored, callback) {
622+
for (const reporter of reporters) {
623+
reporter.jasmineDone({overallStatus});
624+
}
625+
callback();
626+
},
627+
addReporter: reporter => {
628+
reporters.push(reporter);
629+
},
630+
clearReporters: function() {
631+
reporters = [];
632+
}
633+
};
634+
};
635+
});
636+
637+
it('is resolved with the overall suite status', async function() {
638+
this.testJasmine.env = this.autocompletingFakeEnv('failed');
639+
640+
await expectAsync(this.testJasmine.execute())
641+
.toBeResolvedTo(jasmine.objectContaining({overallStatus: 'failed'}));
642+
});
643+
644+
it('is resolved with the overall suite status even if clearReporters was called', async function() {
645+
this.testJasmine.env = this.autocompletingFakeEnv('incomplete');
646+
this.testJasmine.clearReporters();
647+
648+
await expectAsync(this.testJasmine.execute())
649+
.toBeResolvedTo(jasmine.objectContaining({overallStatus: 'incomplete'}));
595650
});
596651
});
597652
});

0 commit comments

Comments
 (0)