diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 41588cce..3e31d0cd 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -94,6 +94,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$); @@ -774,7 +775,6 @@ getJasmineRequireObj().Spec = function(j$) { this.expectationFactory = attrs.expectationFactory; this.asyncExpectationFactory = attrs.asyncExpectationFactory; this.setTimeout = attrs.setTimeout; - this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; this.filename = attrs.filename; this.parentSuiteId = attrs.parentSuiteId; @@ -790,7 +790,6 @@ getJasmineRequireObj().Spec = function(j$) { function() { return {}; }; - this.onStart = attrs.onStart || function() {}; this.autoCleanClosures = attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures; @@ -845,15 +844,18 @@ getJasmineRequireObj().Spec = function(j$) { Spec.prototype.execute = function( runQueue, globalErrors, + onStart, + // TODO: may be able to merge resultCallback into onComplete + resultCallback, onComplete, excluded, failSpecWithNoExp, detectLateRejectionHandling ) { - const onStart = { + const start = { fn: done => { this.timer.start(); - this.onStart(this, done); + onStart(done); } }; @@ -869,7 +871,7 @@ getJasmineRequireObj().Spec = function(j$) { this.result.debugLogs = null; } - this.resultCallback(this.result, done); + resultCallback(this.result, done); }, type: 'specCleanup' }; @@ -908,7 +910,7 @@ getJasmineRequireObj().Spec = function(j$) { runnerConfig.queueableFns = []; } - runnerConfig.queueableFns.unshift(onStart); + runnerConfig.queueableFns.unshift(start); if (detectLateRejectionHandling) { // Conditional because the setTimeout imposes a significant performance @@ -1674,8 +1676,6 @@ getJasmineRequireObj().Env = function(j$) { expectationFactory, asyncExpectationFactory, onLateError: recordLateError, - specResultCallback, - specStarted, runQueue }); topSuite = suiteBuilder.topSuite; @@ -1717,8 +1717,7 @@ getJasmineRequireObj().Env = function(j$) { runQueue, TreeProcessor: j$.TreeProcessor, globalErrors, - getConfig: () => config, - reportSpecDone + getConfig: () => config }); this.setParallelLoadingState = function(state) { @@ -1960,28 +1959,6 @@ getJasmineRequireObj().Env = function(j$) { .metadata; }; - function specResultCallback(spec, result, next) { - runableResources.clearForRunable(spec.id); - runner.setCurrentSpec(null); - - if (result.status === 'failed') { - runner.hasFailures = true; - } - - reportSpecDone(spec, result, next); - } - - function specStarted(spec, suite, next) { - runner.setCurrentSpec(spec); - runableResources.initForRunable(spec.id, suite.id); - reportDispatcher.specStarted(spec.result).then(next); - } - - function reportSpecDone(spec, result, next) { - spec.reportedDone = true; - reportDispatcher.specDone(result).then(next); - } - this.it = function(description, fn, timeout) { ensureIsNotNested('it'); const filename = callerCallerFilename(); @@ -3488,6 +3465,45 @@ getJasmineRequireObj().CompleteOnFirstErrorSkipPolicy = function(j$) { return CompleteOnFirstErrorSkipPolicy; }; +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; +}; + // Warning: don't add "use strict" to this file. Doing so potentially changes // the behavior of user code that does things like setTimeout("var x = 1;") // while the mock clock is installed. @@ -9423,7 +9439,6 @@ getJasmineRequireObj().Runner = function(j$) { #globalErrors; #reportDispatcher; #getConfig; - #reportSpecDone; #executedBefore; #currentRunableTracker; @@ -9437,10 +9452,8 @@ getJasmineRequireObj().Runner = function(j$) { this.#globalErrors = options.globalErrors; this.#reportDispatcher = options.reportDispatcher; this.#getConfig = options.getConfig; - this.#reportSpecDone = options.reportSpecDone; - this.hasFailures = false; this.#executedBefore = false; - this.#currentRunableTracker = new CurrentRunableTracker(); + this.#currentRunableTracker = new j$.CurrentRunableTracker(); } currentSpec() { @@ -9469,7 +9482,6 @@ getJasmineRequireObj().Runner = function(j$) { } this.#executedBefore = true; - this.hasFailures = false; const focusedRunables = this.#getFocusedRunables(); const config = this.#getConfig(); @@ -9537,8 +9549,7 @@ getJasmineRequireObj().Runner = function(j$) { ), currentRunableTracker: this.#currentRunableTracker }); - await treeRunner.execute(); - this.hasFailures = this.hasFailures || treeRunner.hasFailures; + const { hasFailures } = await treeRunner.execute(); if (this.#topSuite.hadBeforeAllFailure) { await this.#reportChildrenOfBeforeAllFailure(this.#topSuite); @@ -9548,10 +9559,7 @@ getJasmineRequireObj().Runner = function(j$) { 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.#getFocusedRunables().length > 0) { overallStatus = 'incomplete'; @@ -9592,6 +9600,12 @@ getJasmineRequireObj().Runner = function(j$) { return jasmineDoneInfo; } + // TODO: de-duplicate w/TreeRunner + #reportSpecDone(spec, result, next) { + spec.reportedDone = true; + this.#reportDispatcher.specDone(result).then(next); + } + async #reportChildrenOfBeforeAllFailure(suite) { for (const child of suite.children) { if (child instanceof j$.Suite) { @@ -9628,41 +9642,6 @@ getJasmineRequireObj().Runner = function(j$) { } } - 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 Runner; }; @@ -10937,8 +10916,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; @@ -11176,11 +11153,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { asyncExpectationFactory: this.specAsyncExpectationFactory_, setTimeout: global.setTimeout.bind(global), 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(); @@ -11544,6 +11517,7 @@ getJasmineRequireObj().TreeRunner = function(j$) { #getConfig; #reportChildrenOfBeforeAllFailure; #currentRunableTracker; + #hasFailures; constructor(attrs) { this.#executionTree = attrs.executionTree; @@ -11558,7 +11532,7 @@ getJasmineRequireObj().TreeRunner = function(j$) { } async execute() { - this.hasFailures = false; + this.#hasFailures = false; const topSuite = this.#executionTree.topSuite; const wrappedChildren = this.#wrapNodes( this.#executionTree.childrenOfTopSuite() @@ -11581,6 +11555,8 @@ getJasmineRequireObj().TreeRunner = function(j$) { : null }); }); + + return { hasFailures: this.#hasFailures }; } #wrapNodes(nodes) { @@ -11602,6 +11578,14 @@ getJasmineRequireObj().TreeRunner = function(j$) { spec.execute( this.#runQueueWithSkipPolicy.bind(this), this.#globalErrors, + next => { + this.#currentRunableTracker.setCurrentSpec(spec); + this.#runableResources.initForRunable(spec.id, spec.parentSuiteId); + this.#reportDispatcher.specStarted(spec.result).then(next); + }, + (result, next) => { + this.#specComplete(spec, result, next); + }, done, this.#executionTree.isExcluded(spec), config.failSpecWithNoExpectations, @@ -11661,7 +11645,7 @@ getJasmineRequireObj().TreeRunner = function(j$) { this.#currentRunableTracker.popSuite(); if (result.status === 'failed') { - this.hasFailures = true; + this.#hasFailures = true; } suite.endTimer(); @@ -11679,6 +11663,22 @@ getJasmineRequireObj().TreeRunner = function(j$) { this.#reportDispatcher.suiteDone(result).then(next); } + #specComplete(spec, result, next) { + this.#runableResources.clearForRunable(spec.id); + this.#currentRunableTracker.setCurrentSpec(null); + + if (result.status === 'failed') { + this.#hasFailures = true; + } + + this.#reportSpecDone(spec, result, next); + } + + #reportSpecDone(spec, result, next) { + spec.reportedDone = true; + this.#reportDispatcher.specDone(result).then(next); + } + #addBeforeAndAfterAlls(suite, wrappedChildren) { if (this.#executionTree.isExcluded(suite)) { return wrappedChildren; diff --git a/spec/core/RunnerSpec.js b/spec/core/RunnerSpec.js index cea10e70..33f9719e 100644 --- a/spec/core/RunnerSpec.js +++ b/spec/core/RunnerSpec.js @@ -311,6 +311,8 @@ describe('Runner', function() { expect(spec.execute).toHaveBeenCalledWith( jasmine.any(Function), globalErrors, + jasmine.any(Function), + jasmine.any(Function), 'onComplete', shouldBeExcluded, failSpecWithNoExpectations, @@ -468,6 +470,8 @@ describe('Runner', function() { expect(spec.execute).toHaveBeenCalledWith( jasmine.any(Function), globalErrors, + jasmine.any(Function), + jasmine.any(Function), 'foo', false, true, @@ -592,6 +596,8 @@ describe('Runner', function() { expect(specified.execute).toHaveBeenCalledWith( jasmine.any(Function), globalErrors, + jasmine.any(Function), + jasmine.any(Function), undefined, false, false, @@ -603,6 +609,8 @@ describe('Runner', function() { expect(nonSpecified.execute).toHaveBeenCalledWith( jasmine.any(Function), globalErrors, + jasmine.any(Function), + jasmine.any(Function), undefined, true, false, diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index daeed0a0..c18b6993 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -52,15 +52,13 @@ describe('Spec', function() { spec = new jasmineUnderTest.Spec({ id: 123, description: 'foo bar', - queueableFn: { fn: function() {} }, - onStart: startCallback + queueableFn: { fn: function() {} } }); - spec.execute(fakeQueueRunner); + spec.execute(fakeQueueRunner, null, startCallback); 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() { @@ -79,11 +77,10 @@ describe('Spec', function() { beforesWereCalled = true; } ]; - }, - onStart: startCallback + } }); - spec.execute(fakeQueueRunner); + spec.execute(fakeQueueRunner, null, startCallback); fakeQueueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); expect(startCallback).toHaveBeenCalled(); @@ -106,7 +103,7 @@ describe('Spec', function() { } }); - spec.execute(fakeQueueRunner); + spec.execute(fakeQueueRunner, null, null); const options = fakeQueueRunner.calls.mostRecent().args[0]; expect(options.queueableFns).toEqual([ @@ -144,7 +141,16 @@ describe('Spec', function() { } }); - spec.execute(fakeQueueRunner, globalErrors, null, false, false, true); + spec.execute( + fakeQueueRunner, + globalErrors, + null, + null, + null, + false, + false, + true + ); const options = fakeQueueRunner.calls.mostRecent().args[0]; expect(options.queueableFns).toEqual([ @@ -209,12 +215,17 @@ describe('Spec', function() { specBody = jasmine.createSpy('specBody'), resultCallback = jasmine.createSpy('resultCallback'), spec = new jasmineUnderTest.Spec({ - onStart: startCallback, - queueableFn: { fn: specBody }, - resultCallback: resultCallback + queueableFn: { fn: specBody } }); - spec.execute(fakeQueueRunner, null, 'cally-back', true); + spec.execute( + fakeQueueRunner, + null, + startCallback, + resultCallback, + 'onComplete', + true + ); expect(fakeQueueRunner).toHaveBeenCalledWith( jasmine.objectContaining({ @@ -244,8 +255,6 @@ describe('Spec', function() { 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', @@ -259,7 +268,7 @@ describe('Spec', function() { expect(spec.status()).toBe('pending'); - spec.execute(fakeQueueRunner); + spec.execute(fakeQueueRunner, null, startCallback, resultCallback); expect(fakeQueueRunner).toHaveBeenCalled(); @@ -293,11 +302,16 @@ describe('Spec', function() { queueableFn: { fn: function() {} }, catchExceptions: function() { return false; - }, - resultCallback: function() {} + } }); - spec.execute(attrs => attrs.onComplete(), null, done); + spec.execute( + attrs => attrs.onComplete(), + null, + function() {}, + function() {}, + done + ); expect(done).toHaveBeenCalled(); }); @@ -308,15 +322,14 @@ describe('Spec', function() { queueableFn: { fn: function() {} }, catchExceptions: function() { return false; - }, - resultCallback: function() {} + } }); function runQueue(attrs) { spec.result.status = 'failed'; attrs.onComplete(); } - spec.execute(runQueue, null, done); + spec.execute(runQueue, null, function() {}, function() {}, done); expect(done).toHaveBeenCalledWith( jasmine.any(jasmineUnderTest.StopExecutionError) @@ -328,15 +341,12 @@ describe('Spec', function() { start: null, elapsed: 77000 }); - let duration = undefined; + const resultCallback = jasmine.createSpy('resultCallback'); const spec = new jasmineUnderTest.Spec({ queueableFn: { fn: jasmine.createSpy('spec body') }, catchExceptions: function() { return false; }, - resultCallback: function(result) { - duration = result.duration; - }, timer: timer }); @@ -347,8 +357,9 @@ describe('Spec', function() { config.onComplete(); } - spec.execute(runQueue, null, function() {}); - expect(duration).toBe(77000); + spec.execute(runQueue, null, function() {}, resultCallback, function() {}); + expect(resultCallback).toHaveBeenCalled(); + expect(resultCallback.calls.argsFor(0)[0].duration).toEqual(77000); }); it('removes the fn after execution if autoCleanClosures is true', function() { @@ -365,7 +376,7 @@ describe('Spec', function() { config.onComplete(); } - spec.execute(runQueue, null, done); + spec.execute(runQueue, null, function() {}, function() {}, done); expect(done).toHaveBeenCalled(); expect(spec.queueableFn.fn).toBeFalsy(); }); @@ -384,7 +395,7 @@ describe('Spec', function() { config.onComplete(); } - spec.execute(runQueue, null, done); + spec.execute(runQueue, null, function() {}, function() {}, done); expect(done).toHaveBeenCalled(); expect(spec.queueableFn.fn).toBeFalsy(); }); @@ -404,7 +415,7 @@ describe('Spec', function() { config.onComplete(); } - spec.execute(runQueue, null, done); + spec.execute(runQueue, null, function() {}, function() {}, done); expect(done).toHaveBeenCalled(); expect(spec.queueableFn.fn).toBe(originalFn); }); @@ -415,11 +426,16 @@ describe('Spec', function() { queueableFn: { fn: jasmine.createSpy('spec body') }, catchExceptions: function() { return false; - }, - resultCallback: function() {} + } }); spec.setSpecProperty('a', 4); - spec.execute(attrs => attrs.onComplete(), null, done); + spec.execute( + attrs => attrs.onComplete(), + null, + function() {}, + function() {}, + done + ); expect(spec.result.properties).toEqual({ a: 4 }); }); @@ -451,13 +467,12 @@ describe('Spec', function() { const fakeQueueRunner = jasmine.createSpy('queueRunner'), resultCallback = jasmine.createSpy('resultCallback'), spec = new jasmineUnderTest.Spec({ - queueableFn: { fn: jasmine.createSpy('spec body') }, - resultCallback: resultCallback + queueableFn: { fn: jasmine.createSpy('spec body') } }); spec.addExpectationResult(true, { message: 'expectation1' }); spec.addExpectationResult(false, { message: 'expectation2' }); - spec.execute(fakeQueueRunner); + spec.execute(fakeQueueRunner, null, function() {}, resultCallback); const fns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns; fns[fns.length - 1].fn(); @@ -484,7 +499,7 @@ describe('Spec', function() { spec.addExpectationResult(false, { message: 'failed' }); }).toThrowError(jasmineUnderTest.errors.ExpectationFailed); - spec.execute(fakeQueueRunner); + spec.execute(fakeQueueRunner, null, function() {}, resultCallback); const fns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns; fns[fns.length - 1].fn(); @@ -669,12 +684,11 @@ describe('Spec', function() { const fakeQueueRunner = jasmine.createSpy('queueRunner'), resultCallback = jasmine.createSpy('resultCallback'), spec = new jasmineUnderTest.Spec({ - queueableFn: { fn: function() {} }, - resultCallback: resultCallback + queueableFn: { fn: function() {} } }); spec.handleException('foo'); - spec.execute(fakeQueueRunner); + spec.execute(fakeQueueRunner, null, function() {}, resultCallback); const args = fakeQueueRunner.calls.mostRecent().args[0]; args.queueableFns[args.queueableFns.length - 1].fn(); @@ -694,12 +708,11 @@ describe('Spec', function() { const fakeQueueRunner = jasmine.createSpy('queueRunner'), resultCallback = jasmine.createSpy('resultCallback'), spec = new jasmineUnderTest.Spec({ - queueableFn: { fn: function() {} }, - resultCallback: resultCallback + queueableFn: { fn: function() {} } }); spec.handleException(new jasmineUnderTest.errors.ExpectationFailed()); - spec.execute(fakeQueueRunner); + spec.execute(fakeQueueRunner, null, function() {}, resultCallback); const args = fakeQueueRunner.calls.mostRecent().args[0]; args.queueableFns[args.queueableFns.length - 1].fn(); @@ -763,8 +776,7 @@ describe('Spec', function() { spec = new jasmineUnderTest.Spec({ queueableFn: { fn: function() {} - }, - resultCallback: resultCallback + } }); function runQueue(config) { @@ -775,7 +787,13 @@ describe('Spec', function() { config.onComplete(false); } - spec.execute(runQueue, null, function() {}); + spec.execute( + runQueue, + null, + function() {}, + resultCallback, + function() {} + ); expect(resultCallback).toHaveBeenCalledWith( jasmine.objectContaining({ debugLogs: null }), undefined @@ -787,8 +805,7 @@ describe('Spec', function() { spec = new jasmineUnderTest.Spec({ queueableFn: { fn: function() {} - }, - resultCallback: resultCallback + } }); function runQueue(config) { @@ -799,7 +816,13 @@ describe('Spec', function() { config.onComplete(false); } - spec.execute(runQueue, null, function() {}); + spec.execute( + runQueue, + null, + function() {}, + resultCallback, + function() {} + ); expect(resultCallback).toHaveBeenCalled(); expect(spec.result.debugLogs).toBeNull(); }); @@ -813,7 +836,6 @@ describe('Spec', function() { queueableFn: { fn: function() {} }, - resultCallback: resultCallback, timer: timer }), timestamp = 12345; @@ -829,7 +851,13 @@ describe('Spec', function() { config.onComplete(true); } - spec.execute(runQueue, null, function() {}); + spec.execute( + runQueue, + null, + function() {}, + resultCallback, + function() {} + ); expect(resultCallback).toHaveBeenCalledWith( jasmine.objectContaining({ debugLogs: [{ message: 'msg', timestamp: timestamp }] diff --git a/spec/core/TreeRunnerSpec.js b/spec/core/TreeRunnerSpec.js new file mode 100644 index 00000000..cdb73863 --- /dev/null +++ b/spec/core/TreeRunnerSpec.js @@ -0,0 +1,139 @@ +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 topSuiteId = 'suite1'; + const spec = new jasmineUnderTest.Spec({ + id: 'spec1', + parentSuiteId: topSuiteId, + queueableFn: {}, + timer + }); + 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 currentRunableTracker = new jasmineUnderTest.CurrentRunableTracker(); + const subject = new jasmineUnderTest.TreeRunner({ + executionTree, + runQueue, + runableResources, + reportDispatcher, + currentRunableTracker, + getConfig() { + return {}; + }, + reportChildrenOfBeforeAllFailure() {} + }); + + const promise = subject.execute(); + expect(runQueue).toHaveBeenCalledTimes(1); + const suiteRunQueueArgs = runQueue.calls.mostRecent().args[0]; + suiteRunQueueArgs.queueableFns[0].fn(); + + expect(runQueue).toHaveBeenCalledTimes(2); + 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, + topSuite.id + ); + expect(reportDispatcher.specStarted).toHaveBeenCalledWith(spec.result); + await Promise.resolve(); + expect(reportDispatcher.specStarted).toHaveBeenCalledBefore(next); + await expectAsync(promise).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 topSuiteId = 'suite1'; + const spec = new jasmineUnderTest.Spec({ + id: 'spec1', + parentSuiteId: topSuiteId, + queueableFn: {}, + timer + }); + 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 currentRunableTracker = new jasmineUnderTest.CurrentRunableTracker(); + const subject = new jasmineUnderTest.TreeRunner({ + executionTree, + runQueue, + runableResources, + reportDispatcher, + currentRunableTracker, + getConfig() { + return {}; + }, + reportChildrenOfBeforeAllFailure() {} + }); + + const promise = subject.execute(); + expect(runQueue).toHaveBeenCalledTimes(1); + const suiteRunQueueArgs = runQueue.calls.mostRecent().args[0]; + suiteRunQueueArgs.queueableFns[0].fn(); + + expect(runQueue).toHaveBeenCalledTimes(2); + 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(); + expect(reportDispatcher.specDone).toHaveBeenCalledBefore(next); + await expectAsync(promise).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' + ]); + } +}); diff --git a/src/core/CurrentRunableTracker.js b/src/core/CurrentRunableTracker.js new file mode 100644 index 00000000..b93fda3b --- /dev/null +++ b/src/core/CurrentRunableTracker.js @@ -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; +}; diff --git a/src/core/Env.js b/src/core/Env.js index e4ff5d7c..dc0ee9b1 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -477,8 +477,6 @@ getJasmineRequireObj().Env = function(j$) { expectationFactory, asyncExpectationFactory, onLateError: recordLateError, - specResultCallback, - specStarted, runQueue }); topSuite = suiteBuilder.topSuite; @@ -520,8 +518,7 @@ getJasmineRequireObj().Env = function(j$) { runQueue, TreeProcessor: j$.TreeProcessor, globalErrors, - getConfig: () => config, - reportSpecDone + getConfig: () => config }); this.setParallelLoadingState = function(state) { @@ -763,28 +760,6 @@ getJasmineRequireObj().Env = function(j$) { .metadata; }; - function specResultCallback(spec, result, next) { - runableResources.clearForRunable(spec.id); - runner.setCurrentSpec(null); - - if (result.status === 'failed') { - runner.hasFailures = true; - } - - reportSpecDone(spec, result, next); - } - - function specStarted(spec, suite, next) { - runner.setCurrentSpec(spec); - runableResources.initForRunable(spec.id, suite.id); - reportDispatcher.specStarted(spec.result).then(next); - } - - function reportSpecDone(spec, result, next) { - spec.reportedDone = true; - reportDispatcher.specDone(result).then(next); - } - this.it = function(description, fn, timeout) { ensureIsNotNested('it'); const filename = callerCallerFilename(); diff --git a/src/core/Runner.js b/src/core/Runner.js index 6feb1f71..3a29445e 100644 --- a/src/core/Runner.js +++ b/src/core/Runner.js @@ -10,7 +10,6 @@ getJasmineRequireObj().Runner = function(j$) { #globalErrors; #reportDispatcher; #getConfig; - #reportSpecDone; #executedBefore; #currentRunableTracker; @@ -24,10 +23,8 @@ getJasmineRequireObj().Runner = function(j$) { this.#globalErrors = options.globalErrors; this.#reportDispatcher = options.reportDispatcher; this.#getConfig = options.getConfig; - this.#reportSpecDone = options.reportSpecDone; - this.hasFailures = false; this.#executedBefore = false; - this.#currentRunableTracker = new CurrentRunableTracker(); + this.#currentRunableTracker = new j$.CurrentRunableTracker(); } currentSpec() { @@ -56,7 +53,6 @@ getJasmineRequireObj().Runner = function(j$) { } this.#executedBefore = true; - this.hasFailures = false; const focusedRunables = this.#getFocusedRunables(); const config = this.#getConfig(); @@ -124,8 +120,7 @@ getJasmineRequireObj().Runner = function(j$) { ), currentRunableTracker: this.#currentRunableTracker }); - await treeRunner.execute(); - this.hasFailures = this.hasFailures || treeRunner.hasFailures; + const { hasFailures } = await treeRunner.execute(); if (this.#topSuite.hadBeforeAllFailure) { await this.#reportChildrenOfBeforeAllFailure(this.#topSuite); @@ -135,10 +130,7 @@ getJasmineRequireObj().Runner = function(j$) { 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.#getFocusedRunables().length > 0) { overallStatus = 'incomplete'; @@ -179,6 +171,12 @@ getJasmineRequireObj().Runner = function(j$) { return jasmineDoneInfo; } + // TODO: de-duplicate w/TreeRunner + #reportSpecDone(spec, result, next) { + spec.reportedDone = true; + this.#reportDispatcher.specDone(result).then(next); + } + async #reportChildrenOfBeforeAllFailure(suite) { for (const child of suite.children) { if (child instanceof j$.Suite) { @@ -215,40 +213,5 @@ getJasmineRequireObj().Runner = function(j$) { } } - 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 Runner; }; diff --git a/src/core/Spec.js b/src/core/Spec.js index 9fe105a9..bc5d8573 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -3,7 +3,6 @@ getJasmineRequireObj().Spec = function(j$) { this.expectationFactory = attrs.expectationFactory; this.asyncExpectationFactory = attrs.asyncExpectationFactory; this.setTimeout = attrs.setTimeout; - this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; this.filename = attrs.filename; this.parentSuiteId = attrs.parentSuiteId; @@ -19,7 +18,6 @@ getJasmineRequireObj().Spec = function(j$) { function() { return {}; }; - this.onStart = attrs.onStart || function() {}; this.autoCleanClosures = attrs.autoCleanClosures === undefined ? true : !!attrs.autoCleanClosures; @@ -74,15 +72,18 @@ getJasmineRequireObj().Spec = function(j$) { Spec.prototype.execute = function( runQueue, globalErrors, + onStart, + // TODO: may be able to merge resultCallback into onComplete + resultCallback, onComplete, excluded, failSpecWithNoExp, detectLateRejectionHandling ) { - const onStart = { + const start = { fn: done => { this.timer.start(); - this.onStart(this, done); + onStart(done); } }; @@ -98,7 +99,7 @@ getJasmineRequireObj().Spec = function(j$) { this.result.debugLogs = null; } - this.resultCallback(this.result, done); + resultCallback(this.result, done); }, type: 'specCleanup' }; @@ -137,7 +138,7 @@ getJasmineRequireObj().Spec = function(j$) { runnerConfig.queueableFns = []; } - runnerConfig.queueableFns.unshift(onStart); + runnerConfig.queueableFns.unshift(start); if (detectLateRejectionHandling) { // Conditional because the setTimeout imposes a significant performance diff --git a/src/core/SuiteBuilder.js b/src/core/SuiteBuilder.js index 45c7ff7e..affa9693 100644 --- a/src/core/SuiteBuilder.js +++ b/src/core/SuiteBuilder.js @@ -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; @@ -249,11 +247,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { asyncExpectationFactory: this.specAsyncExpectationFactory_, setTimeout: global.setTimeout.bind(global), 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(); diff --git a/src/core/TreeRunner.js b/src/core/TreeRunner.js index 80b2859a..cbac8868 100644 --- a/src/core/TreeRunner.js +++ b/src/core/TreeRunner.js @@ -8,6 +8,7 @@ getJasmineRequireObj().TreeRunner = function(j$) { #getConfig; #reportChildrenOfBeforeAllFailure; #currentRunableTracker; + #hasFailures; constructor(attrs) { this.#executionTree = attrs.executionTree; @@ -22,7 +23,7 @@ getJasmineRequireObj().TreeRunner = function(j$) { } async execute() { - this.hasFailures = false; + this.#hasFailures = false; const topSuite = this.#executionTree.topSuite; const wrappedChildren = this.#wrapNodes( this.#executionTree.childrenOfTopSuite() @@ -45,6 +46,8 @@ getJasmineRequireObj().TreeRunner = function(j$) { : null }); }); + + return { hasFailures: this.#hasFailures }; } #wrapNodes(nodes) { @@ -66,6 +69,14 @@ getJasmineRequireObj().TreeRunner = function(j$) { spec.execute( this.#runQueueWithSkipPolicy.bind(this), this.#globalErrors, + next => { + this.#currentRunableTracker.setCurrentSpec(spec); + this.#runableResources.initForRunable(spec.id, spec.parentSuiteId); + this.#reportDispatcher.specStarted(spec.result).then(next); + }, + (result, next) => { + this.#specComplete(spec, result, next); + }, done, this.#executionTree.isExcluded(spec), config.failSpecWithNoExpectations, @@ -125,7 +136,7 @@ getJasmineRequireObj().TreeRunner = function(j$) { this.#currentRunableTracker.popSuite(); if (result.status === 'failed') { - this.hasFailures = true; + this.#hasFailures = true; } suite.endTimer(); @@ -143,6 +154,22 @@ getJasmineRequireObj().TreeRunner = function(j$) { this.#reportDispatcher.suiteDone(result).then(next); } + #specComplete(spec, result, next) { + this.#runableResources.clearForRunable(spec.id); + this.#currentRunableTracker.setCurrentSpec(null); + + if (result.status === 'failed') { + this.#hasFailures = true; + } + + this.#reportSpecDone(spec, result, next); + } + + #reportSpecDone(spec, result, next) { + spec.reportedDone = true; + this.#reportDispatcher.specDone(result).then(next); + } + #addBeforeAndAfterAlls(suite, wrappedChildren) { if (this.#executionTree.isExcluded(suite)) { return wrappedChildren; diff --git a/src/core/requireCore.js b/src/core/requireCore.js index 46d9c235..df334bb2 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -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$);