From db615e4186e9110184b846a6afe40c988ac4a54a Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 14 Nov 2017 14:49:41 -0800 Subject: [PATCH] Determine overall status in core, not reporters [#92261606] [#78679648] --- lib/jasmine-core/jasmine-html.js | 11 +- lib/jasmine-core/jasmine.css | 1 + lib/jasmine-core/jasmine.js | 26 ++++ spec/core/integration/EnvSpec.js | 211 +++++++++++++++++++++++++++++++ spec/html/HtmlReporterSpec.js | 180 ++++++++++++-------------- src/core/Env.js | 26 ++++ src/html/HtmlReporter.js | 11 +- src/html/_HTMLReporter.scss | 5 + 8 files changed, 366 insertions(+), 105 deletions(-) diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 26248ed4..f77c75e1 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -225,10 +225,15 @@ jasmineRequire.HtmlReporter = function(j$) { if (totalSpecsDefined > 0 || failed) { statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); } - statusBarClassName += failed ? 'jasmine-failed' : 'jasmine-passed'; + } + + if (doneResult.overallStatus === 'passed') { + statusBarClassName += ' jasmine-passed '; + } else if (doneResult.overallStatus === 'incomplete') { + statusBarClassName += ' jasmine-incomplete '; + statusBarMessage = 'Incomplete: ' + doneResult.incompleteReason + ', ' + statusBarMessage; } else { - statusBarClassName += 'jasmine-skipped'; - statusBarMessage += 'No specs found'; + statusBarClassName += ' jasmine-failed '; } var seedBar; diff --git a/lib/jasmine-core/jasmine.css b/lib/jasmine-core/jasmine.css index 02042214..002cb17b 100644 --- a/lib/jasmine-core/jasmine.css +++ b/lib/jasmine-core/jasmine.css @@ -31,6 +31,7 @@ body { overflow-y: scroll; } .jasmine_html-reporter .jasmine-bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } .jasmine_html-reporter .jasmine-bar.jasmine-failed, .jasmine_html-reporter .jasmine-bar.jasmine-errored { background-color: #ca3a11; border-bottom: 1px solid #eee; } .jasmine_html-reporter .jasmine-bar.jasmine-passed { background-color: #007069; } +.jasmine_html-reporter .jasmine-bar.jasmine-incomplete { background-color: #bababa; } .jasmine_html-reporter .jasmine-bar.jasmine-skipped { background-color: #bababa; } .jasmine_html-reporter .jasmine-bar.jasmine-menu { background-color: #fff; color: #aaa; } .jasmine_html-reporter .jasmine-bar.jasmine-menu a { color: #333; } diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 69698fdc..324944c9 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -763,6 +763,7 @@ getJasmineRequireObj().Env = function(j$) { var random = true; var seed = null; var suppressLoadErrors = false; + var hasFailures = false; var currentSuite = function() { return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; @@ -1045,6 +1046,10 @@ getJasmineRequireObj().Env = function(j$) { } currentlyExecutingSuites.pop(); reporter.suiteDone(result); + + if (result.status === 'failed') { + hasFailures = true; + } }, orderChildren: function(node) { return order.sort(node.children); @@ -1071,14 +1076,31 @@ getJasmineRequireObj().Env = function(j$) { 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} - The overall result of the sute: 'passed', 'failed', or 'incomplete'. + * @property {IncompleteReason} - Explanation of why the suite was incimplete. * @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. */ reporter.jasmineDone({ + overallStatus: overallStatus, + incompleteReason: incompleteReason, order: order, failedExpectations: topSuite.result.failedExpectations }); @@ -1270,6 +1292,10 @@ getJasmineRequireObj().Env = function(j$) { clearResourcesForRunnable(spec.id); currentSpec = null; reporter.specDone(result); + + if (result.status === 'failed') { + hasFailures = true; + } } function specStarted(spec) { diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index dee3d9be..fa5b3dfa 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -2075,4 +2075,215 @@ describe("Env integration", function() { env.execute(); }); }); + + describe('Overall status in the jasmineDone event', function() { + describe('When everything passes', function() { + it('is "passed"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('passed'); + done(); + }); + + env.addReporter(reporter); + env.it('passes', function() {}); + env.execute(); + }); + }); + + describe('When a spec fails', function() { + it('is "failed"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('failed'); + done(); + }); + + env.addReporter(reporter); + env.it('fails', function() { + env.expect(true).toBe(false); + }); + env.execute(); + }); + }); + + describe('When a top-level beforeAll fails', function() { + it('is "failed"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('failed'); + done(); + }); + + env.addReporter(reporter); + env.beforeAll(function() { + throw new Error('nope'); + }); + env.it('does not run', function() {}); + env.execute(); + }); + }); + + describe('When a suite beforeAll fails', function() { + it('is "failed"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('failed'); + done(); + }); + + env.addReporter(reporter); + env.describe('something', function() { + env.beforeAll(function() { + throw new Error('nope'); + }); + env.it('does not run', function() {}); + }); + env.execute(); + }); + }); + + describe('When a top-level afterAll fails', function() { + it('is "failed"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('failed'); + done(); + }); + + env.addReporter(reporter); + env.afterAll(function() { + throw new Error('nope'); + }); + env.it('does not run', function() {}); + env.execute(); + }); + }); + + describe('When a suite afterAll fails', function() { + it('is "failed"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('failed'); + done(); + }); + + env.addReporter(reporter); + env.describe('something', function() { + env.afterAll(function() { + throw new Error('nope'); + }); + env.it('does not run', function() {}); + }); + env.execute(); + }); + }); + + describe("When there are load errors", function() { + it('is "failed"', function(done) { + var global = { + setTimeout: function(fn, delay) { setTimeout(fn, delay) }, + clearTimeout: function(fn, delay) { clearTimeout(fn, delay) }, + }; + spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); + + var env = new jasmineUnderTest.Env(); + var reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + debugger; + expect(e.overallStatus).toEqual('failed'); + done(); + }); + + env.addReporter(reporter); + env.it('passes', function() {}); + global.onerror('Uncaught Error: ENOCHEESE'); + env.execute(); + }); + }); + + describe('When there are no specs', function() { + it('is "incomplete"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('incomplete'); + expect(e.incompleteReason).toEqual('No specs found'); + done(); + }); + + env.addReporter(reporter); + env.execute(); + }); + }); + + describe('When a spec is focused', function() { + it('is "incomplete"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('incomplete'); + expect(e.incompleteReason).toEqual('fit() or fdescribe() was found'); + done(); + }); + + env.addReporter(reporter); + env.fit('is focused', function() {}); + env.execute(); + }); + }); + + describe('When a suite is focused', function() { + it('is "incomplete"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('incomplete'); + expect(e.incompleteReason).toEqual('fit() or fdescribe() was found'); + done(); + }); + + env.addReporter(reporter); + env.fdescribe('something focused', function() { + env.it('does a thing', function() {}); + }); + env.execute(); + }); + }); + + describe('When there are both failures and focused specs', function() { + it('is "failed"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('failed'); + expect(e.incompleteReason).toBeUndefined(); + done(); + }); + + env.addReporter(reporter); + env.fit('is focused', function() { + env.expect(true).toBe(false); + }); + env.execute(); + }); + }); + }); }); diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index 87f7a093..c5154511 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -179,27 +179,6 @@ describe("HtmlReporter", function() { }); describe("when there are suite failures", function () { - it("displays an overall result of failure even if no other failures occurred", function() { - var env = new jasmineUnderTest.Env(), - container = document.createElement("div"), - getContainer = function() { return container; }, - reporter = new jasmineUnderTest.HtmlReporter({ - env: env, - getContainer: getContainer, - createElement: function() { return document.createElement.apply(document, arguments); }, - createTextNode: function() { return document.createTextNode.apply(document, arguments); } - }); - - reporter.initialize(); - - reporter.jasmineStarted({}); - reporter.suiteDone({ status: 'failed', failedExpectations: [{ message: 'My After All Exception' }] }); - reporter.jasmineDone({ failedExpectations: [] }); - - var alertBar = container.querySelector(".jasmine-overall-result"); - expect(alertBar.classList).toContain("jasmine-failed"); - }); - it("displays the exceptions in their own alert bars", function(){ var env = new jasmineUnderTest.Env(), container = document.createElement("div"), @@ -319,7 +298,7 @@ describe("HtmlReporter", function() { reporter.jasmineStarted({}); timer.elapsed.and.returnValue(100); - reporter.jasmineDone(); + reporter.jasmineDone({}); var duration = container.querySelector(".jasmine-alert .jasmine-duration"); expect(duration.innerHTML).toMatch(/finished in 0.1s/); @@ -740,7 +719,7 @@ describe("HtmlReporter", function() { }); reporter.initialize(); - reporter.jasmineDone(); + reporter.jasmineDone({}); var seedBar = container.querySelector(".jasmine-seed-bar"); expect(seedBar).toBeNull(); @@ -766,79 +745,6 @@ describe("HtmlReporter", function() { }); }); - it("shows a message if no specs are run", function(){ - var env, container, reporter; - env = new jasmineUnderTest.Env(); - container = document.createElement("div"); - var getContainer = function() { return container; }, - reporter = new jasmineUnderTest.HtmlReporter({ - env: env, - getContainer: getContainer, - createElement: function() { return document.createElement.apply(document, arguments); }, - createTextNode: function() { return document.createTextNode.apply(document, arguments); } - }); - reporter.initialize(); - - reporter.jasmineStarted({}); - reporter.jasmineDone({}); - - var alertBars = container.querySelectorAll(".jasmine-alert .jasmine-bar"); - expect(alertBars[0].getAttribute('class')).toMatch(/jasmine-skipped/); - expect(alertBars[0].innerHTML).toMatch(/No specs found/); - }); - - it("reports failure if there are global errors and no specs", function() { - var env = new jasmineUnderTest.Env(), - container = document.createElement("div"), - reporter = new jasmineUnderTest.HtmlReporter({ - env: env, - getContainer: function() { return container; }, - createElement: function() { return document.createElement.apply(document, arguments); }, - createTextNode: function() { return document.createTextNode.apply(document, arguments); } - }); - reporter.initialize(); - reporter.jasmineStarted({ totalSpecsDefined: 0 }); - reporter.jasmineDone({ - failedExpectations: [{ - passed: false, - message: 'nope' - }] - }); - - var alertBar = container.querySelector(".jasmine-overall-result"); - expect(alertBar.getAttribute('class')).toMatch(/jasmine-failed/); - }); - - it("reports failure if there are global errors and some specs", function() { - var env = new jasmineUnderTest.Env(), - container = document.createElement("div"), - reporter = new jasmineUnderTest.HtmlReporter({ - env: env, - getContainer: function() { return container; }, - createElement: function() { return document.createElement.apply(document, arguments); }, - createTextNode: function() { return document.createTextNode.apply(document, arguments); } - }); - reporter.initialize(); - reporter.jasmineStarted({ totalSpecsDefined: 0 }); - reporter.specDone({ - id: 123, - description: "with a spec", - fullName: "A Suite with a spec", - status: "passed", - passedExpectations: [{passed: true}], - failedExpectations: [] - }); - reporter.jasmineDone({ - failedExpectations: [{ - passed: false, - message: 'nope' - }] - }); - - var alertBar = container.querySelector(".jasmine-overall-result"); - expect(alertBar.getAttribute('class')).toMatch(/jasmine-failed/); - }); - describe("and all specs pass", function() { var env, container, reporter; beforeEach(function() { @@ -877,7 +783,6 @@ describe("HtmlReporter", function() { var alertBars = container.querySelectorAll(".jasmine-alert .jasmine-bar"); expect(alertBars.length).toEqual(1); - expect(alertBars[0].getAttribute('class')).toMatch(/jasmine-passed/); expect(alertBars[0].innerHTML).toMatch(/2 specs, 0 failures/); }); @@ -1042,8 +947,6 @@ describe("HtmlReporter", function() { it("reports the specs counts", function() { var alertBar = container.querySelector(".jasmine-alert .jasmine-bar"); - - expect(alertBar.getAttribute('class')).toMatch(/jasmine-failed/); expect(alertBar.innerHTML).toMatch(/2 specs, 1 failure/); }); @@ -1086,4 +989,83 @@ describe("HtmlReporter", function() { }); }); }); + + describe("The overall result bar", function() { + describe("When the jasmineDone event's overallStatus is 'passed'", function() { + it("has class jasmine-passed", function() { + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + getContainer = function() { return container; }, + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + getContainer: getContainer, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); } + }); + + reporter.initialize(); + + reporter.jasmineStarted({}); + reporter.jasmineDone({ + overallStatus: 'passed', + failedExpectations: [] + }); + + var alertBar = container.querySelector(".jasmine-overall-result"); + expect(alertBar.classList).toContain("jasmine-passed"); + }); + }); + + describe("When the jasmineDone event's overallStatus is 'failed'", function() { + it("has class jasmine-failed", function() { + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + getContainer = function() { return container; }, + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + getContainer: getContainer, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); } + }); + + reporter.initialize(); + + reporter.jasmineStarted({}); + reporter.jasmineDone({ + overallStatus: 'failed', + failedExpectations: [] + }); + + var alertBar = container.querySelector(".jasmine-overall-result"); + expect(alertBar.classList).toContain("jasmine-failed"); + }); + }); + + describe("When the jasmineDone event's overallStatus is 'failed'", function() { + it("has class jasmine-incomplete", function() { + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + getContainer = function() { return container; }, + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + getContainer: getContainer, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); } + }); + + reporter.initialize(); + + reporter.jasmineStarted({}); + reporter.jasmineDone({ + overallStatus: 'incomplete', + incompleteReason: 'because nope', + failedExpectations: [] + }); + + var alertBar = container.querySelector(".jasmine-overall-result"); + expect(alertBar.classList).toContain("jasmine-incomplete"); + expect(alertBar.textContent).toContain("Incomplete: because nope"); + }); + }); + }); }); diff --git a/src/core/Env.js b/src/core/Env.js index 966a6065..473cc1f8 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -29,6 +29,7 @@ getJasmineRequireObj().Env = function(j$) { var random = true; var seed = null; var suppressLoadErrors = false; + var hasFailures = false; var currentSuite = function() { return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; @@ -311,6 +312,10 @@ getJasmineRequireObj().Env = function(j$) { } currentlyExecutingSuites.pop(); reporter.suiteDone(result); + + if (result.status === 'failed') { + hasFailures = true; + } }, orderChildren: function(node) { return order.sort(node.children); @@ -337,14 +342,31 @@ getJasmineRequireObj().Env = function(j$) { 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} - The overall result of the sute: 'passed', 'failed', or 'incomplete'. + * @property {IncompleteReason} - Explanation of why the suite was incimplete. * @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. */ reporter.jasmineDone({ + overallStatus: overallStatus, + incompleteReason: incompleteReason, order: order, failedExpectations: topSuite.result.failedExpectations }); @@ -536,6 +558,10 @@ getJasmineRequireObj().Env = function(j$) { clearResourcesForRunnable(spec.id); currentSpec = null; reporter.specDone(result); + + if (result.status === 'failed') { + hasFailures = true; + } } function specStarted(spec) { diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index e4ec91bf..e089d536 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -196,10 +196,15 @@ jasmineRequire.HtmlReporter = function(j$) { if (totalSpecsDefined > 0 || failed) { statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); } - statusBarClassName += failed ? 'jasmine-failed' : 'jasmine-passed'; + } + + if (doneResult.overallStatus === 'passed') { + statusBarClassName += ' jasmine-passed '; + } else if (doneResult.overallStatus === 'incomplete') { + statusBarClassName += ' jasmine-incomplete '; + statusBarMessage = 'Incomplete: ' + doneResult.incompleteReason + ', ' + statusBarMessage; } else { - statusBarClassName += 'jasmine-skipped'; - statusBarMessage += 'No specs found'; + statusBarClassName += ' jasmine-failed '; } var seedBar; diff --git a/src/html/_HTMLReporter.scss b/src/html/_HTMLReporter.scss index 201ffdc1..4031a777 100644 --- a/src/html/_HTMLReporter.scss +++ b/src/html/_HTMLReporter.scss @@ -209,6 +209,11 @@ body { background-color: $passing-color; } + &.jasmine-incomplete { + background-color: $neutral-color; + } + + &.jasmine-skipped { background-color: $neutral-color; }