Skip to content

Commit

Permalink
Return a promise from Env#execute in environments that support promises
Browse files Browse the repository at this point in the history
[#178373231]
  • Loading branch information
sgravrock committed Aug 7, 2021
1 parent dcaac62 commit e72d161
Show file tree
Hide file tree
Showing 5 changed files with 299 additions and 112 deletions.
139 changes: 83 additions & 56 deletions lib/jasmine-core/jasmine.js
Original file line number Diff line number Diff line change
Expand Up @@ -1817,11 +1817,17 @@ getJasmineRequireObj().Env = function(j$) {
*
* execute should not be called more than once.
*
* If the environment supports promises, execute will return a promise that
* is resolved after the suite finishes executing. The promise will be
* resolved (not rejected) as long as the suite runs to completion. Use a
* {@link Reporter} to determine whether or not the suite passed.
*
* @name Env#execute
* @since 2.0.0
* @function
* @param {(string[])=} runnablesToRun IDs of suites and/or specs to run
* @param {Function=} onComplete Function that will be called after all specs have run
* @return {Promise<undefined>}
*/
this.execute = function(runnablesToRun, onComplete) {
installGlobalErrors();
Expand Down Expand Up @@ -1881,65 +1887,86 @@ getJasmineRequireObj().Env = function(j$) {
var jasmineTimer = new j$.Timer();
jasmineTimer.start();

/**
* Information passed to the {@link Reporter#jasmineStarted} event.
* @typedef JasmineStartedInfo
* @property {Int} totalSpecsDefined - The total number of specs defined in this suite.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
*/
reporter.jasmineStarted(
{
totalSpecsDefined: totalSpecsDefined,
order: order
},
function() {
currentlyExecutingSuites.push(topSuite);

processor.execute(function() {
clearResourcesForRunnable(topSuite.id);
currentlyExecutingSuites.pop();
var overallStatus, incompleteReason;

if (hasFailures || topSuite.result.failedExpectations.length > 0) {
overallStatus = 'failed';
} else if (focusedRunnables.length > 0) {
overallStatus = 'incomplete';
incompleteReason = 'fit() or fdescribe() was found';
} else if (totalSpecsDefined === 0) {
overallStatus = 'incomplete';
incompleteReason = 'No specs found';
} else {
overallStatus = 'passed';
var Promise = customPromise || global.Promise;

if (Promise) {
return new Promise(function(resolve) {
runAll(function() {
if (onComplete) {
onComplete();
}

/**
* Information passed to the {@link Reporter#jasmineDone} event.
* @typedef JasmineDoneInfo
* @property {OverallStatus} overallStatus - The overall result of the suite: 'passed', 'failed', or 'incomplete'.
* @property {Int} totalTime - The total time (in ms) that it took to execute the suite
* @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level.
* @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level.
*/
reporter.jasmineDone(
{
overallStatus: overallStatus,
totalTime: jasmineTimer.elapsed(),
incompleteReason: incompleteReason,
order: order,
failedExpectations: topSuite.result.failedExpectations,
deprecationWarnings: topSuite.result.deprecationWarnings
},
function() {
if (onComplete) {
onComplete();
}
}
);
resolve();
});
}
);
});
} else {
runAll(function() {
if (onComplete) {
onComplete();
}
});
}

function runAll(done) {
/**
* Information passed to the {@link Reporter#jasmineStarted} event.
* @typedef JasmineStartedInfo
* @property {Int} totalSpecsDefined - The total number of specs defined in this suite.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
*/
reporter.jasmineStarted(
{
totalSpecsDefined: totalSpecsDefined,
order: order
},
function() {
currentlyExecutingSuites.push(topSuite);

processor.execute(function() {
clearResourcesForRunnable(topSuite.id);
currentlyExecutingSuites.pop();
var overallStatus, incompleteReason;

if (
hasFailures ||
topSuite.result.failedExpectations.length > 0
) {
overallStatus = 'failed';
} else if (focusedRunnables.length > 0) {
overallStatus = 'incomplete';
incompleteReason = 'fit() or fdescribe() was found';
} else if (totalSpecsDefined === 0) {
overallStatus = 'incomplete';
incompleteReason = 'No specs found';
} else {
overallStatus = 'passed';
}

/**
* Information passed to the {@link Reporter#jasmineDone} event.
* @typedef JasmineDoneInfo
* @property {OverallStatus} overallStatus - The overall result of the suite: 'passed', 'failed', or 'incomplete'.
* @property {Int} totalTime - The total time (in ms) that it took to execute the suite
* @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level.
* @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level.
*/
reporter.jasmineDone(
{
overallStatus: overallStatus,
totalTime: jasmineTimer.elapsed(),
incompleteReason: incompleteReason,
order: order,
failedExpectations: topSuite.result.failedExpectations,
deprecationWarnings: topSuite.result.deprecationWarnings
},
done
);
});
}
);
}
};

/**
Expand Down
21 changes: 21 additions & 0 deletions spec/core/EnvSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -465,4 +465,25 @@ describe('Env', function() {
done();
});
});

describe('#execute', function() {
it('returns a promise when the environment supports promises', function() {
jasmine.getEnv().requirePromises();
expect(env.execute()).toBeInstanceOf(Promise);
});

it('returns a promise when a custom promise constructor is provided', function() {
function CustomPromise() {}
CustomPromise.resolve = function() {};
CustomPromise.reject = function() {};

env.configure({ Promise: CustomPromise });
expect(env.execute()).toBeInstanceOf(CustomPromise);
});

it('returns undefined when promises are unavailable', function() {
jasmine.getEnv().requireNoPromises();
expect(env.execute()).toBeUndefined();
});
});
});
106 changes: 106 additions & 0 deletions spec/core/integration/EnvSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3038,6 +3038,112 @@ describe('Env integration', function() {
env.execute(null, done);
});

describe('The promise returned by #execute', function() {
beforeEach(function() {
this.savedInterval = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL;
});

afterEach(function() {
jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = this.savedInterval;
});

it('is resolved after reporter events are dispatched', function() {
jasmine.getEnv().requirePromises();
var reporter = jasmine.createSpyObj('reporter', [
'specDone',
'suiteDone',
'jasmineDone'
]);

env.addReporter(reporter);
env.describe('suite', function() {
env.it('spec', function() {});
});

return env.execute(null).then(function() {
expect(reporter.specDone).toHaveBeenCalled();
expect(reporter.suiteDone).toHaveBeenCalled();
expect(reporter.jasmineDone).toHaveBeenCalled();
});
});

it('is resolved after the stack is cleared', function(done) {
jasmine.getEnv().requirePromises();
var realClearStack = jasmineUnderTest.getClearStack(
jasmineUnderTest.getGlobal()
),
clearStackSpy = jasmine
.createSpy('clearStack')
.and.callFake(realClearStack);
spyOn(jasmineUnderTest, 'getClearStack').and.returnValue(clearStackSpy);

// Create a new env that has the clearStack defined above
env.cleanup_();
env = new jasmineUnderTest.Env();

env.describe('suite', function() {
env.it('spec', function() {});
});

env.execute(null).then(function() {
expect(clearStackSpy).toHaveBeenCalled(); // (many times)
clearStackSpy.calls.reset();
setTimeout(function() {
expect(clearStackSpy).not.toHaveBeenCalled();
done();
});
});
});

it('is resolved after QueueRunner timeouts are cleared', function() {
jasmine.getEnv().requirePromises();
var setTimeoutSpy = spyOn(
jasmineUnderTest.getGlobal(),
'setTimeout'
).and.callThrough();
var clearTimeoutSpy = spyOn(
jasmineUnderTest.getGlobal(),
'clearTimeout'
).and.callThrough();

jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = 123456; // a distinctive value

env = new jasmineUnderTest.Env();

env.describe('suite', function() {
env.it('spec', function() {});
});

return env.execute(null).then(function() {
var timeoutIds = setTimeoutSpy.calls
.all()
.filter(function(call) {
return call.args[1] === 123456;
})
.map(function(call) {
return call.returnValue;
});

expect(timeoutIds.length).toBeGreaterThan(0);

timeoutIds.forEach(function(timeoutId) {
expect(clearTimeoutSpy).toHaveBeenCalledWith(timeoutId);
});
});
});

it('is resolved even if specs fail', function() {
jasmine.getEnv().requirePromises();
env.describe('suite', function() {
env.it('spec', function() {
env.expect(true).toBe(false);
});
});

return expectAsync(env.execute(null)).toBeResolved();
});
});

describe('The optional callback argument to #execute', function() {
beforeEach(function() {
this.savedInterval = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL;
Expand Down
6 changes: 6 additions & 0 deletions spec/helpers/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,10 @@
env.pending('Environment does not support promises');
}
};

env.requireNoPromises = function() {
if (typeof Promise === 'function') {
env.pending('Environment supports promises');
}
};
})(jasmine.getEnv());
Loading

0 comments on commit e72d161

Please sign in to comment.