diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index abb59a37..f1b79721 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -11631,7 +11631,6 @@ getJasmineRequireObj().TreeRunner = function(j$) { } #suiteSegmentComplete(suite, result, next) { - suite.cleanupBeforeAfter(); const isTopSuite = suite === this.#executionTree.topSuite; if (!isTopSuite) { @@ -11639,6 +11638,11 @@ getJasmineRequireObj().TreeRunner = function(j$) { throw new Error('Tried to complete the wrong suite'); } + // suite.cleanupBeforeAfter() is conditional because calling it on the + // top suite breaks parallel mode. The top suite is reentered every time + // a runner runs another file, so its before and after fns need to be + // preserved. + suite.cleanupBeforeAfter(); this.#runableResources.clearForRunable(suite.id); this.#currentRunableTracker.popSuite(); diff --git a/spec/core/TreeRunnerSpec.js b/spec/core/TreeRunnerSpec.js index 0e2eea1a..93eab8c0 100644 --- a/spec/core/TreeRunnerSpec.js +++ b/spec/core/TreeRunnerSpec.js @@ -409,6 +409,45 @@ describe('TreeRunner', function() { }); }); + it('does not remove before and after fns from the top suite', async function() { + const topSuite = new jasmineUnderTest.Suite({ id: 'topSuite' }); + spyOn(topSuite, 'cleanupBeforeAfter'); + const executionTree = { + topSuite, + childrenOfTopSuite() { + return []; + }, + isExcluded() { + return false; + } + }; + const runQueue = jasmine.createSpy('runQueue'); + const subject = new jasmineUnderTest.TreeRunner({ + executionTree, + runQueue, + globalErrors: mockGlobalErrors(), + runableResources: mockRunableResources(), + reportDispatcher: mockReportDispatcher(), + currentRunableTracker: new jasmineUnderTest.CurrentRunableTracker(), + getConfig() { + return {}; + } + }); + + const executePromise = subject.execute(); + expect(runQueue).toHaveBeenCalledTimes(1); + const topSuiteRunQueueOpts = runQueue.calls.mostRecent().args[0]; + runQueue.calls.reset(); + + for (const qfn of topSuiteRunQueueOpts.queueableFns) { + qfn.fn(); + } + topSuiteRunQueueOpts.onComplete(); + + await expectAsync(executePromise).toBeResolved(); + expect(topSuite.cleanupBeforeAfter).not.toHaveBeenCalled(); + }); + describe('Late promise rejection handling', function() { it('works for specs when the detectLateRejectionHandling param is true', function() { const before = jasmine.createSpy('before'); diff --git a/src/core/TreeRunner.js b/src/core/TreeRunner.js index f6d36c9f..5952739f 100644 --- a/src/core/TreeRunner.js +++ b/src/core/TreeRunner.js @@ -209,7 +209,6 @@ getJasmineRequireObj().TreeRunner = function(j$) { } #suiteSegmentComplete(suite, result, next) { - suite.cleanupBeforeAfter(); const isTopSuite = suite === this.#executionTree.topSuite; if (!isTopSuite) { @@ -217,6 +216,11 @@ getJasmineRequireObj().TreeRunner = function(j$) { throw new Error('Tried to complete the wrong suite'); } + // suite.cleanupBeforeAfter() is conditional because calling it on the + // top suite breaks parallel mode. The top suite is reentered every time + // a runner runs another file, so its before and after fns need to be + // preserved. + suite.cleanupBeforeAfter(); this.#runableResources.clearForRunable(suite.id); this.#currentRunableTracker.popSuite();