diff --git a/lib/console/console.js b/lib/console/console.js index c00897f0..15b941fa 100644 --- a/lib/console/console.js +++ b/lib/console/console.js @@ -55,7 +55,7 @@ getJasmineRequireObj().ConsoleReporter = function() { yellow: '\x1B[33m', none: '\x1B[0m' }, - exceptionList = []; + failedSuites = []; this.jasmineStarted = function() { specCount = 0; @@ -87,12 +87,8 @@ getJasmineRequireObj().ConsoleReporter = function() { print('Finished in ' + seconds + ' ' + plural('second', seconds)); printNewline(); - for(i = 0; i < exceptionList.length; i++) { - printNewline(); - print(colored('red', 'An error was thrown in an afterAll')); - printNewline(); - print(colored('red', (exceptionList[i].message || exceptionList[i].description))); - printNewline(); + for(i = 0; i < failedSuites.length; i++) { + suiteFailureDetails(failedSuites[i]); } onComplete(failureCount === 0); @@ -119,8 +115,11 @@ getJasmineRequireObj().ConsoleReporter = function() { } }; - this.afterAllError = function(error) { - exceptionList.push(error); + this.suiteDone = function(result) { + if (result.failedExpectations && result.failedExpectations.length > 0) { + failureCount++; + failedSuites.push(result); + } }; return this; @@ -166,6 +165,17 @@ getJasmineRequireObj().ConsoleReporter = function() { printNewline(); } + + function suiteFailureDetails(result) { + for (var i = 0; i < result.failedExpectations.length; i++) { + printNewline(); + print(colored('red', 'An error was thrown in an afterAll')); + printNewline(); + print(colored('red', 'AfterAll ' + result.failedExpectations[i].message)); + + } + printNewline(); + } } return ConsoleReporter; diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index b372e3bb..50fb1f8b 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -47,7 +47,7 @@ jasmineRequire.HtmlReporter = function(j$) { pendingSpecCount = 0, htmlReporterMain, symbols, - exceptionList = []; + failedSuites = []; this.initialize = function() { htmlReporterMain = createDom('div', {className: 'html-reporter'}, @@ -83,6 +83,10 @@ jasmineRequire.HtmlReporter = function(j$) { }; this.suiteDone = function(result) { + if (result.failedExpectations && result.failedExpectations.length > 0) { + failedSuites.push(result); + } + if (currentParent == topResults) { return; } @@ -94,10 +98,6 @@ jasmineRequire.HtmlReporter = function(j$) { currentParent.addChild(result, 'spec'); }; - this.afterAllException = function(error) { - exceptionList.push(error); - }; - var failures = []; this.specDone = function(result) { if (result.status != 'disabled') { @@ -170,10 +170,13 @@ jasmineRequire.HtmlReporter = function(j$) { var statusBarClassName = 'bar ' + ((failureCount > 0) ? 'failed' : 'passed'); alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage)); - for(i = 0; i < exceptionList.length; i++) { - var errorBarMessage = 'An error was thrown in an afterAll: ' + (exceptionList[i].message || exceptionList[i].description); - var errorBarClassName = 'bar errored'; - alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage)); + for(i = 0; i < failedSuites.length; i++) { + var failedSuite = failedSuites[i]; + for(var j = 0; j < failedSuite.failedExpectations.length; j++) { + var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message; + var errorBarClassName = 'bar errored'; + alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage)); + } } var results = find('.results'); diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index ca98bd43..61f83f34 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -250,11 +250,9 @@ getJasmineRequireObj().Spec = function(j$) { this.id = attrs.id; this.description = attrs.description || ''; this.queueableFn = attrs.queueableFn; - this.beforeFns = attrs.beforeFns || function() { return []; }; - this.afterFns = attrs.afterFns || function() { return []; }; + this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; }; this.userContext = attrs.userContext || function() { return {}; }; this.onStart = attrs.onStart || function() {}; - this.exceptionFormatter = attrs.exceptionFormatter || function() {}; this.getSpecName = attrs.getSpecName || function() { return ''; }; this.expectationResultFactory = attrs.expectationResultFactory || function() { }; this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; @@ -293,7 +291,8 @@ getJasmineRequireObj().Spec = function(j$) { return; } - var allFns = this.beforeFns().concat(this.queueableFn).concat(this.afterFns()); + var fns = this.beforeAndAfterFns(); + var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); this.queueRunnerFactory({ queueableFns: allFns, @@ -408,8 +407,7 @@ getJasmineRequireObj().Env = function(j$) { 'suiteStarted', 'suiteDone', 'specStarted', - 'specDone', - 'afterAllException' + 'specDone' ]); this.specFilter = function() { @@ -475,25 +473,28 @@ getJasmineRequireObj().Env = function(j$) { delete runnableResources[id]; }; - var beforeFns = function(suite) { + var beforeAndAfterFns = function(suite, runnablesExplictlySet) { return function() { - var befores = []; + var befores = [], + afters = [], + beforeAlls = [], + afterAlls = []; + while(suite) { befores = befores.concat(suite.beforeFns); - suite = suite.parentSuite; - } - return befores.reverse(); - }; - }; - - var afterFns = function(suite) { - return function() { - var afters = []; - while(suite) { afters = afters.concat(suite.afterFns); + + if (runnablesExplictlySet()) { + beforeAlls = beforeAlls.concat(suite.beforeAllFns); + afterAlls = afterAlls.concat(suite.afterAllFns); + } + suite = suite.parentSuite; } - return afters; + return { + befores: beforeAlls.reverse().concat(befores.reverse()), + afters: afters.concat(afterAlls) + }; }; }; @@ -540,7 +541,6 @@ getJasmineRequireObj().Env = function(j$) { var queueRunnerFactory = function(options) { options.catchException = catchException; - options.reporter = reporter; options.clearStack = options.clearStack || clearStack; options.timer = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; @@ -552,7 +552,9 @@ getJasmineRequireObj().Env = function(j$) { id: getNextSuiteId(), description: 'Jasmine__TopLevel__Suite', queueRunner: queueRunnerFactory, - resultCallback: function() {} // TODO - hook this up + resultCallback: function(attrs) { + reporter.suiteDone(attrs); + } }); runnableLookupTable[topSuite.id] = topSuite; defaultResourcesForRunnable(topSuite.id); @@ -563,7 +565,14 @@ getJasmineRequireObj().Env = function(j$) { }; this.execute = function(runnablesToRun) { - runnablesToRun = runnablesToRun || [topSuite.id]; + if(runnablesToRun) { + runnablesExplictlySet = true; + } else if (focusedRunnables.length) { + runnablesExplictlySet = true; + runnablesToRun = focusedRunnables; + } else { + runnablesToRun = [topSuite.id]; + } var allFns = []; for(var i = 0; i < runnablesToRun.length; i++) { @@ -602,6 +611,7 @@ getJasmineRequireObj().Env = function(j$) { queueRunner: queueRunnerFactory, onStart: suiteStarted, expectationFactory: expectationFactory, + expectationResultFactory: expectationResultFactory, resultCallback: function(attrs) { if (!suite.disabled) { clearResourcesForRunnable(suite.id); @@ -623,7 +633,28 @@ getJasmineRequireObj().Env = function(j$) { this.describe = function(description, specDefinitions) { var suite = suiteFactory(description); + addSpecsToSuite(suite, specDefinitions); + return suite; + }; + this.xdescribe = function(description, specDefinitions) { + var suite = this.describe(description, specDefinitions); + suite.disable(); + return suite; + }; + + this.fdescribe = function(description, specDefinitions) { + var suite = suiteFactory(description); + suite.isFocused = true; + addSpecsToSuite(suite, specDefinitions); + + if (!hasFocusedAncestor(suite.parentSuite)) { + focusedRunnables.push(suite.id); + } + return suite; + }; + + function addSpecsToSuite(suite, specDefinitions) { var parentSuite = currentDeclarationSuite; parentSuite.addChild(suite); currentDeclarationSuite = suite; @@ -636,31 +667,37 @@ getJasmineRequireObj().Env = function(j$) { } if (declarationError) { - this.it('encountered a declaration exception', function() { + self.it('encountered a declaration exception', function() { throw declarationError; }); } currentDeclarationSuite = parentSuite; + } - return suite; - }; + function hasFocusedAncestor(suite) { + while (suite) { + if (suite.isFocused) { + return true; + } + suite = suite.parentSuite; + } - this.xdescribe = function(description, specDefinitions) { - var suite = this.describe(description, specDefinitions); - suite.disable(); - return suite; + return false; + } + + var runnablesExplictlySet = false; + + var runnablesExplictlySetGetter = function(){ + return runnablesExplictlySet; }; var specFactory = function(description, fn, suite) { totalSpecsDefined++; - var spec = new j$.Spec({ id: getNextSpecId(), - beforeFns: beforeFns(suite), - afterFns: afterFns(suite), + beforeAndAfterFns: beforeAndAfterFns(suite, runnablesExplictlySetGetter), expectationFactory: expectationFactory, - exceptionFormatter: exceptionFormatter, resultCallback: specResultCallback, getSpecName: function(spec) { return getSpecName(spec, suite); @@ -670,7 +707,7 @@ getJasmineRequireObj().Env = function(j$) { expectationResultFactory: expectationResultFactory, queueRunnerFactory: queueRunnerFactory, userContext: function() { return suite.clonedSharedUserContext(); }, - queueableFn: { fn: fn, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } } + queueableFn: { fn: fn, type: 'it', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } } }); runnableLookupTable[spec.id] = spec; @@ -706,24 +743,35 @@ getJasmineRequireObj().Env = function(j$) { return spec; }; + var focusedRunnables = []; + this.fit = function(description, fn ){ + var spec = this.it(description, fn); + + if (!hasFocusedAncestor(currentDeclarationSuite)) { + focusedRunnables.push(spec.id); + } + + return spec; + }; + this.expect = function(actual) { return currentRunnable().expect(actual); }; this.beforeEach = function(beforeEachFunction) { - currentDeclarationSuite.beforeEach({ fn: beforeEachFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + currentDeclarationSuite.beforeEach({ fn: beforeEachFunction, type: 'beforeEach', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.beforeAll = function(beforeAllFunction) { - currentDeclarationSuite.beforeAll({ fn: beforeAllFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + currentDeclarationSuite.beforeAll({ fn: beforeAllFunction, type: 'beforeAll', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.afterEach = function(afterEachFunction) { - currentDeclarationSuite.afterEach({ fn: afterEachFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + currentDeclarationSuite.afterEach({ fn: afterEachFunction, type: 'afterEach', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.afterAll = function(afterAllFunction) { - currentDeclarationSuite.afterAll({ fn: afterAllFunction, isAfterAll: true, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + currentDeclarationSuite.afterAll({ fn: afterAllFunction, type: 'afterAll', timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.pending = function() { @@ -1583,7 +1631,6 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.catchException = attrs.catchException || function() { return true; }; this.userContext = attrs.userContext || {}; this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; - this.reporter = attrs.reporter; } QueueRunner.prototype.execute = function() { @@ -1591,10 +1638,10 @@ getJasmineRequireObj().QueueRunner = function(j$) { }; QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { - var runner = this, - length = queueableFns.length, - self = this, - iterativeIndex; + var length = queueableFns.length, + self = this, + iterativeIndex; + for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { var queueableFn = queueableFns[iterativeIndex]; @@ -1615,10 +1662,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { try { queueableFn.fn.call(self.userContext); } catch (e) { - if(queueableFn.isAfterAll){ - runner.reporter.afterAllException(e); - } - handleException(e); + handleException(e, queueableFn); } } @@ -1634,7 +1678,8 @@ getJasmineRequireObj().QueueRunner = function(j$) { if (queueableFn.timeout) { timeoutId = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() { - self.onException(new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.')); + var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); + onException(error, queueableFn); next(); }, queueableFn.timeout()]]); } @@ -1642,16 +1687,17 @@ getJasmineRequireObj().QueueRunner = function(j$) { try { queueableFn.fn.call(self.userContext, next); } catch (e) { - if(queueableFn.isAfterAll) { - runner.reporter.afterAllException(e); - } - handleException(e); + handleException(e, queueableFn); next(); } } - function handleException(e) { + function onException(e, queueableFn) { self.onException(e); + } + + function handleException(e, queueableFn) { + onException(e, queueableFn); if (!self.catchException(e)) { //TODO: set a var when we catch an exception and //use a finally block to close the loop in a nice way.. @@ -1807,6 +1853,7 @@ getJasmineRequireObj().Suite = function() { this.resultCallback = attrs.resultCallback || function() {}; this.clearStack = attrs.clearStack || function(fn) {fn();}; this.expectationFactory = attrs.expectationFactory; + this.expectationResultFactory = attrs.expectationResultFactory; this.beforeFns = []; this.afterFns = []; @@ -1821,7 +1868,8 @@ getJasmineRequireObj().Suite = function() { id: this.id, status: this.disabled ? 'disabled' : '', description: this.description, - fullName: this.getFullName() + fullName: this.getFullName(), + failedExpectations: [] }; } @@ -1873,7 +1921,7 @@ getJasmineRequireObj().Suite = function() { var allFns = []; if (this.isExecutable()) { - allFns = this.beforeAllFns; + allFns = allFns.concat(this.beforeAllFns); for (var i = 0; i < this.children.length; i++) { allFns.push(wrapChildAsAsync(this.children[i])); @@ -1928,19 +1976,43 @@ getJasmineRequireObj().Suite = function() { }; Suite.prototype.onException = function() { - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; - child.onException.apply(child, arguments); + if(isAfterAll(this.children)) { + var data = { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: arguments[0] + }; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.onException.apply(child, arguments); + } } }; Suite.prototype.addExpectationResult = function () { - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; - child.addExpectationResult.apply(child, arguments); + if(isAfterAll(this.children) && isFailure(arguments)){ + var data = arguments[1]; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.addExpectationResult.apply(child, arguments); + } } }; + function isAfterAll(children) { + return children && children[0].result.status; + } + + function isFailure(args) { + return !args[0]; + } + function clone(obj) { var clonedObj = {}; for (var prop in obj) { diff --git a/spec/console/ConsoleReporterSpec.js b/spec/console/ConsoleReporterSpec.js index 8ea830bb..eae3d506 100644 --- a/spec/console/ConsoleReporterSpec.js +++ b/spec/console/ConsoleReporterSpec.js @@ -187,9 +187,9 @@ describe("ConsoleReporter", function() { expect(onComplete).toHaveBeenCalledWith(false); }); - it('calls it with false if there are afterAll events', function() { - reporter.afterAllEvent("bananas"); + it('calls it with false if there are suite failures', function() { reporter.specDone({status: "passed"}); + reporter.suiteDone({failedExpectations: [{ message: 'bananas' }] }); reporter.jasmineDone(); expect(onComplete).toHaveBeenCalledWith(false); }); @@ -244,12 +244,10 @@ describe("ConsoleReporter", function() { var reporter = new j$.ConsoleReporter({ print: out.print, showColors: true - }), - error = new Error('After All Exception'), - anotherError = new Error('Some Other Exception'); + }); - reporter.afterAllEvent(error); - reporter.afterAllEvent(anotherError); + reporter.suiteDone({ failedExpectations: [{ message: 'After All Exception' }] }); + reporter.suiteDone({ failedExpectations: [{ message: 'Some Other Exception' }] }); reporter.jasmineDone(); expect(out.getOutput()).toMatch(/After All Exception/); diff --git a/spec/core/QueueRunnerSpec.js b/spec/core/QueueRunnerSpec.js index 217467e7..8c4eb9a5 100644 --- a/spec/core/QueueRunnerSpec.js +++ b/spec/core/QueueRunnerSpec.js @@ -98,12 +98,10 @@ describe("QueueRunner", function() { beforeFn = { fn: function(done) { }, type: 'before', timeout: function() { return timeout; } }, queueableFn = { fn: jasmine.createSpy('fn'), type: 'queueable' }, onComplete = jasmine.createSpy('onComplete'), - reportException = jasmine.createSpy('reportException'), onException = jasmine.createSpy('onException'), queueRunner = new j$.QueueRunner({ queueableFns: [beforeFn, queueableFn], onComplete: onComplete, - reportException: reportException, onException: onException }); @@ -112,7 +110,6 @@ describe("QueueRunner", function() { jasmine.clock().tick(timeout); - expect(reportException).toHaveBeenCalledWith(jasmine.any(Error), 'before'); expect(onException).toHaveBeenCalledWith(jasmine.any(Error)); expect(queueableFn.fn).toHaveBeenCalled(); expect(onComplete).toHaveBeenCalled(); @@ -122,12 +119,10 @@ describe("QueueRunner", function() { var beforeFn = { fn: function(done) { } }, queueableFn = { fn: jasmine.createSpy('fn') }, onComplete = jasmine.createSpy('onComplete'), - reportException = jasmine.createSpy('reportException'), onException = jasmine.createSpy('onException'), queueRunner = new j$.QueueRunner({ queueableFns: [beforeFn, queueableFn], onComplete: onComplete, - reportException: reportException, onException: onException, }); @@ -136,7 +131,6 @@ describe("QueueRunner", function() { jasmine.clock().tick(j$.DEFAULT_TIMEOUT_INTERVAL); - expect(reportException).not.toHaveBeenCalled(); expect(onException).not.toHaveBeenCalled(); expect(queueableFn.fn).not.toHaveBeenCalled(); expect(onComplete).not.toHaveBeenCalled(); @@ -145,35 +139,29 @@ describe("QueueRunner", function() { it("clears the timeout when an async function throws an exception, to prevent additional exception reporting", function() { var queueableFn = { fn: function(done) { throw new Error("error!"); } }, onComplete = jasmine.createSpy('onComplete'), - reportException = jasmine.createSpy('reportException'), onException = jasmine.createSpy('onException'), queueRunner = new j$.QueueRunner({ queueableFns: [queueableFn], onComplete: onComplete, - reportException: reportException, onException: onException }); queueRunner.execute(); expect(onComplete).toHaveBeenCalled(); - expect(reportException).toHaveBeenCalled(); expect(onException).toHaveBeenCalled(); jasmine.clock().tick(j$.DEFAULT_TIMEOUT_INTERVAL); - expect(reportException.calls.count()).toEqual(1); expect(onException.calls.count()).toEqual(1); }); it("clears the timeout when the done callback is called", function() { var queueableFn = { fn: function(done) { done(); } }, onComplete = jasmine.createSpy('onComplete'), - reportException = jasmine.createSpy('reportException'), onException = jasmine.createSpy('onException'), queueRunner = new j$.QueueRunner({ queueableFns: [queueableFn], onComplete: onComplete, - reportException: reportException, onException: onException }); @@ -182,7 +170,6 @@ describe("QueueRunner", function() { expect(onComplete).toHaveBeenCalled(); jasmine.clock().tick(j$.DEFAULT_TIMEOUT_INTERVAL); - expect(reportException).not.toHaveBeenCalled(); expect(onException).not.toHaveBeenCalled(); }); @@ -218,17 +205,14 @@ describe("QueueRunner", function() { fn: function() { throw new Error('fake error'); } }, - exceptionCallback = jasmine.createSpy('exception callback'), onExceptionCallback = jasmine.createSpy('on exception callback'), queueRunner = new j$.QueueRunner({ queueableFns: [queueableFn], - reportException: exceptionCallback, onException: onExceptionCallback }); queueRunner.execute(); - expect(exceptionCallback).toHaveBeenCalledWith(jasmine.any(Error), 'queueable'); expect(onExceptionCallback).toHaveBeenCalledWith(jasmine.any(Error)); }); diff --git a/spec/core/SuiteSpec.js b/spec/core/SuiteSpec.js index 5a72a96d..e0dc9649 100644 --- a/spec/core/SuiteSpec.js +++ b/spec/core/SuiteSpec.js @@ -262,7 +262,8 @@ describe("Suite", function() { id: suite.id, status: '', description: "with a child suite", - fullName: "with a child suite" + fullName: "with a child suite", + failedExpectations: [] }); }); }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 89686535..986bd859 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -330,11 +330,29 @@ describe("Env integration", function() { it("reports when an afterAll fails an expectation", function(done) { var env = new j$.Env(), - reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllEvent']); + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.afterAllEvent).toHaveBeenCalledWith('Expectation failed: Expected 1 to equal 2.'); - expect(reporter.afterAllEvent).toHaveBeenCalledWith('Expectation failed: Expected 2 to equal 3.'); + expect(reporter.suiteDone).toHaveBeenCalledWith(jasmine.objectContaining({ + failedExpectations: [ + { + matcherName : 'toEqual', + expected : 2, + actual : 1, + message : 'Expected 1 to equal 2.', + stack: jasmine.any(String), + passed: false + }, + { + matcherName : 'toEqual', + expected : 3, + actual : 2, + message : 'Expected 2 to equal 3.', + stack: jasmine.any(String), + passed: false + } + ] + })); done(); }); @@ -353,46 +371,23 @@ describe("Env integration", function() { env.execute(); }); - it("only reports afterAll expectation failures once, regardless of suite children", function(done) { - var env = new j$.Env(), - reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllEvent']); - - reporter.jasmineDone.and.callFake(function() { - expect(reporter.afterAllEvent.calls.count()).toEqual(1); - expect(reporter.afterAllEvent).toHaveBeenCalledWith('Expectation failed: Expected 1 to equal 2.'); - done(); - }); - - env.addReporter(reporter); - - env.describe('my suite', function() { - env.it('my spec', function() { - }); - - env.it('my spec2', function() { - }); - - env.describe('nested suite', function(){ - env.it('my spec3', function() { - }); - }); - - env.afterAll(function() { - env.expect(1).toEqual(2); - }); - }); - - env.execute(); - }); - it("reports when afterAll throws an exception", function(done) { var env = new j$.Env(), error = new Error('After All Exception'), - reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllEvent']); + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.afterAllEvent.calls.count()).toEqual(1); - expect(reporter.afterAllEvent).toHaveBeenCalledWith('Error thrown: After All Exception'); + expect(reporter.suiteDone).toHaveBeenCalledWith(jasmine.objectContaining({ + description: 'my suite', + failedExpectations: [{ + matcherName : '', + expected : '', + actual : '', + message : 'Error: After All Exception', + stack : jasmine.any(String), + passed: false + }] + })); done(); }); @@ -412,10 +407,19 @@ describe("Env integration", function() { it("reports when an async afterAll fails an expectation", function(done) { var env = new j$.Env(), - reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllEvent']); + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.afterAllEvent).toHaveBeenCalledWith('Expectation failed: Expected 1 to equal 2.'); + expect(reporter.suiteDone).toHaveBeenCalledWith(jasmine.objectContaining({ + failedExpectations: [{ + matcherName : 'toEqual', + expected : 2, + actual : 1, + message : 'Expected 1 to equal 2.', + stack: jasmine.any(String), + passed: false + }] + })); done(); }); @@ -437,11 +441,21 @@ describe("Env integration", function() { it("reports when an async afterAll throws an exception", function(done) { var env = new j$.Env(), error = new Error('After All Exception'), - reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllEvent']); + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.afterAllEvent).toHaveBeenCalled(); + expect(reporter.suiteDone).toHaveBeenCalledWith(jasmine.objectContaining({ + description: 'my suite', + failedExpectations: [{ + matcherName : '', + expected : '', + actual : '', + message : 'Error: After All Exception', + stack : jasmine.any(String), + passed: false + }] + })); done(); }); @@ -747,10 +761,19 @@ describe("Env integration", function() { it("should wait the specified interval before reporting an afterAll that fails to call done", function(done) { var env = new j$.Env(), - reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllEvent']); + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.afterAllEvent).toHaveBeenCalledWith(jasmine.any(String)); + expect(reporter.suiteDone).toHaveBeenCalledWith(jasmine.objectContaining({ + failedExpectations: [{ + matcherName : '', + expected : '', + actual : '', + message : 'Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.', + stack : jasmine.any(String), + passed: false + }] + })); done(); }); diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index ce3b526d..5db3804b 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -130,7 +130,7 @@ describe("New HtmlReporter", function() { }); }); - describe("when there are afterAllEvents", function () { + describe("when there are suite failures", function () { it("displays the exceptions in their own alert bars", function(){ var env = new j$.Env(), container = document.createElement("div"), @@ -140,15 +140,13 @@ describe("New HtmlReporter", function() { getContainer: getContainer, createElement: function() { return document.createElement.apply(document, arguments); }, createTextNode: function() { return document.createTextNode.apply(document, arguments); } - }), - error = new Error('My After All Exception'), - otherError = new Error('My Other Exception'); + }); reporter.initialize(); reporter.jasmineStarted({}); - reporter.afterAllEvent(error); - reporter.afterAllEvent(otherError); + reporter.suiteDone({ failedExpectations: [{ message: 'My After All Exception' }] }); + reporter.suiteDone({ failedExpectations: [{ message: 'My Other Exception' }] }); reporter.jasmineDone({}); var alertBars = container.querySelectorAll(".alert .bar"); diff --git a/src/console/ConsoleReporter.js b/src/console/ConsoleReporter.js index 75291bdc..7d2f6fd8 100644 --- a/src/console/ConsoleReporter.js +++ b/src/console/ConsoleReporter.js @@ -20,7 +20,7 @@ getJasmineRequireObj().ConsoleReporter = function() { yellow: '\x1B[33m', none: '\x1B[0m' }, - exceptionList = []; + failedSuites = []; this.jasmineStarted = function() { specCount = 0; @@ -52,10 +52,8 @@ getJasmineRequireObj().ConsoleReporter = function() { print('Finished in ' + seconds + ' ' + plural('second', seconds)); printNewline(); - for(i = 0; i < exceptionList.length; i++) { - printNewline(); - print(colored('red', 'AfterAll ' + exceptionList[i])); - printNewline(); + for(i = 0; i < failedSuites.length; i++) { + suiteFailureDetails(failedSuites[i]); } onComplete(failureCount === 0); @@ -82,9 +80,11 @@ getJasmineRequireObj().ConsoleReporter = function() { } }; - this.afterAllEvent = function(error) { - failureCount++; - exceptionList.push(error); + this.suiteDone = function(result) { + if (result.failedExpectations && result.failedExpectations.length > 0) { + failureCount++; + failedSuites.push(result); + } }; return this; @@ -130,6 +130,17 @@ getJasmineRequireObj().ConsoleReporter = function() { printNewline(); } + + function suiteFailureDetails(result) { + for (var i = 0; i < result.failedExpectations.length; i++) { + printNewline(); + print(colored('red', 'An error was thrown in an afterAll')); + printNewline(); + print(colored('red', 'AfterAll ' + result.failedExpectations[i].message)); + + } + printNewline(); + } } return ConsoleReporter; diff --git a/src/core/Env.js b/src/core/Env.js index 8fcb091b..7e676d4d 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -34,8 +34,7 @@ getJasmineRequireObj().Env = function(j$) { 'suiteStarted', 'suiteDone', 'specStarted', - 'specDone', - 'afterAllEvent' + 'specDone' ]); this.specFilter = function() { @@ -171,11 +170,6 @@ getJasmineRequireObj().Env = function(j$) { options.catchException = catchException; options.clearStack = options.clearStack || clearStack; options.timer = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; - options.reportException = function(e, type) { - if (type === 'afterAll') { - reporter.afterAllEvent('Error thrown: '+ (e.message || e.description)); - } - }; new j$.QueueRunner(options).execute(); }; @@ -185,8 +179,9 @@ getJasmineRequireObj().Env = function(j$) { id: getNextSuiteId(), description: 'Jasmine__TopLevel__Suite', queueRunner: queueRunnerFactory, - resultCallback: function() {}, // TODO - hook this up - reportExpectationFailure: reportExpectationFailure + resultCallback: function(attrs) { + reporter.suiteDone(attrs); + } }); runnableLookupTable[topSuite.id] = topSuite; defaultResourcesForRunnable(topSuite.id); @@ -243,14 +238,14 @@ getJasmineRequireObj().Env = function(j$) { queueRunner: queueRunnerFactory, onStart: suiteStarted, expectationFactory: expectationFactory, + expectationResultFactory: expectationResultFactory, resultCallback: function(attrs) { if (!suite.disabled) { clearResourcesForRunnable(suite.id); currentlyExecutingSuites.pop(); } reporter.suiteDone(attrs); - }, - reportExpectationFailure: reportExpectationFailure + } }); runnableLookupTable[suite.id] = suite; @@ -330,7 +325,6 @@ getJasmineRequireObj().Env = function(j$) { id: getNextSpecId(), beforeAndAfterFns: beforeAndAfterFns(suite, runnablesExplictlySetGetter), expectationFactory: expectationFactory, - exceptionFormatter: exceptionFormatter, resultCallback: specResultCallback, getSpecName: function(spec) { return getSpecName(spec, suite); @@ -410,10 +404,6 @@ getJasmineRequireObj().Env = function(j$) { this.pending = function() { throw j$.Spec.pendingSpecExceptionMessage; }; - - function reportExpectationFailure(message) { - reporter.afterAllEvent('Expectation failed: '+ message); - } } return Env; diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index 6feb23e4..516c9e84 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -18,7 +18,6 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.catchException = attrs.catchException || function() { return true; }; this.userContext = attrs.userContext || {}; this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; - this.reportException = attrs.reportException || function() {}; } QueueRunner.prototype.execute = function() { @@ -81,7 +80,6 @@ getJasmineRequireObj().QueueRunner = function(j$) { } function onException(e, queueableFn) { - self.reportException(e, queueableFn.type); self.onException(e); } diff --git a/src/core/Spec.js b/src/core/Spec.js index 43bfc7c1..92e11811 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -8,7 +8,6 @@ getJasmineRequireObj().Spec = function(j$) { this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; }; this.userContext = attrs.userContext || function() { return {}; }; this.onStart = attrs.onStart || function() {}; - this.exceptionFormatter = attrs.exceptionFormatter || function() {}; this.getSpecName = attrs.getSpecName || function() { return ''; }; this.expectationResultFactory = attrs.expectationResultFactory || function() { }; this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; diff --git a/src/core/Suite.js b/src/core/Suite.js index 3eb21c58..bd96afad 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -8,7 +8,7 @@ getJasmineRequireObj().Suite = function() { this.resultCallback = attrs.resultCallback || function() {}; this.clearStack = attrs.clearStack || function(fn) {fn();}; this.expectationFactory = attrs.expectationFactory; - this.reportExpectationFailure = attrs.reportExpectationFailure || function() {}; + this.expectationResultFactory = attrs.expectationResultFactory; this.beforeFns = []; this.afterFns = []; @@ -23,7 +23,8 @@ getJasmineRequireObj().Suite = function() { id: this.id, status: this.disabled ? 'disabled' : '', description: this.description, - fullName: this.getFullName() + fullName: this.getFullName(), + failedExpectations: [] }; } @@ -130,31 +131,43 @@ getJasmineRequireObj().Suite = function() { }; Suite.prototype.onException = function() { - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; - child.onException.apply(child, arguments); + if(isAfterAll(this.children)) { + var data = { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: arguments[0] + }; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.onException.apply(child, arguments); + } } }; Suite.prototype.addExpectationResult = function () { if(isAfterAll(this.children) && isFailure(arguments)){ - this.reportExpectationFailure(arguments[1].message); + var data = arguments[1]; + this.result.failedExpectations.push(this.expectationResultFactory(data)); } else { for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; child.addExpectationResult.apply(child, arguments); } } - - function isAfterAll(children) { - return children && children[0].result.status; - } - - function isFailure(args) { - return !args[0]; - } }; + function isAfterAll(children) { + return children && children[0].result.status; + } + + function isFailure(args) { + return !args[0]; + } + function clone(obj) { var clonedObj = {}; for (var prop in obj) { diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index 85da9c35..95074fad 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -18,7 +18,7 @@ jasmineRequire.HtmlReporter = function(j$) { pendingSpecCount = 0, htmlReporterMain, symbols, - exceptionList = []; + failedSuites = []; this.initialize = function() { htmlReporterMain = createDom('div', {className: 'html-reporter'}, @@ -54,6 +54,10 @@ jasmineRequire.HtmlReporter = function(j$) { }; this.suiteDone = function(result) { + if (result.failedExpectations && result.failedExpectations.length > 0) { + failedSuites.push(result); + } + if (currentParent == topResults) { return; } @@ -65,10 +69,6 @@ jasmineRequire.HtmlReporter = function(j$) { currentParent.addChild(result, 'spec'); }; - this.afterAllEvent = function(error) { - exceptionList.push(error); - }; - var failures = []; this.specDone = function(result) { if (result.status != 'disabled') { @@ -141,10 +141,13 @@ jasmineRequire.HtmlReporter = function(j$) { var statusBarClassName = 'bar ' + ((failureCount > 0) ? 'failed' : 'passed'); alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage)); - for(i = 0; i < exceptionList.length; i++) { - var errorBarMessage = 'AfterAll ' + (exceptionList[i]); - var errorBarClassName = 'bar errored'; - alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage)); + for(i = 0; i < failedSuites.length; i++) { + var failedSuite = failedSuites[i]; + for(var j = 0; j < failedSuite.failedExpectations.length; j++) { + var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message; + var errorBarClassName = 'bar errored'; + alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage)); + } } var results = find('.results');