From ec5695acc10f0d21eadb50e064dd135053c813a9 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove and Sheel Choksi Date: Mon, 3 Mar 2014 09:26:39 -0800 Subject: [PATCH 01/40] first (naive) pass at beforeAll/afterAll --- spec/core/SuiteSpec.js | 98 ++++++++++++++++++++++++++++++-- spec/core/integration/EnvSpec.js | 71 +++++++++++++++++++++++ src/core/Env.js | 8 +++ src/core/Spec.js | 4 ++ src/core/Suite.js | 35 +++++++++++- 5 files changed, 209 insertions(+), 7 deletions(-) diff --git a/spec/core/SuiteSpec.js b/spec/core/SuiteSpec.js index be884a20..38ada7d6 100644 --- a/spec/core/SuiteSpec.js +++ b/spec/core/SuiteSpec.js @@ -52,6 +52,31 @@ describe("Suite", function() { expect(suite.beforeFns).toEqual([innerBefore, outerBefore]); }); + it("runs beforeAll functions in order of needed execution", function() { + var env = new j$.Env(), + fakeQueueRunner = jasmine.createSpy('fake queue runner'), + suite = new j$.Suite({ + env: env, + description: "I am a suite", + queueRunner: fakeQueueRunner + }), + firstBefore = jasmine.createSpy('outerBeforeAll'), + lastBefore = jasmine.createSpy('insideBeforeAll'), + fakeIt = {execute: jasmine.createSpy('it'), isExecutable: function() { return true; } }; + + suite.beforeAll(firstBefore); + suite.beforeAll(lastBefore); + suite.addChild(fakeIt); + + suite.execute(); + var suiteFns = fakeQueueRunner.calls.mostRecent().args[0].fns; + + suiteFns[0](); + expect(firstBefore).toHaveBeenCalled(); + suiteFns[1](); + expect(lastBefore).toHaveBeenCalled(); + }); + it("adds after functions in order of needed execution", function() { var env = new j$.Env(), suite = new j$.Suite({ @@ -67,6 +92,31 @@ describe("Suite", function() { expect(suite.afterFns).toEqual([innerAfter, outerAfter]); }); + it("runs afterAll functions in order of needed execution", function() { + var env = new j$.Env(), + fakeQueueRunner = jasmine.createSpy('fake queue runner'), + suite = new j$.Suite({ + env: env, + description: "I am a suite", + queueRunner: fakeQueueRunner + }), + firstAfter = jasmine.createSpy('outerAfterAll'), + lastAfter = jasmine.createSpy('insideAfterAll'), + fakeIt = {execute: jasmine.createSpy('it'), isExecutable: function() { return true; } }; + + suite.afterAll(firstAfter); + suite.afterAll(lastAfter); + suite.addChild(fakeIt); + + suite.execute(); + var suiteFns = fakeQueueRunner.calls.mostRecent().args[0].fns; + + suiteFns[1](); + expect(firstAfter).toHaveBeenCalled(); + suiteFns[2](); + expect(lastAfter).toHaveBeenCalled(); + }); + it("can be disabled", function() { var env = new j$.Env(), fakeQueueRunner = jasmine.createSpy('fake queue runner'), @@ -85,7 +135,7 @@ describe("Suite", function() { expect(fakeQueueRunner).not.toHaveBeenCalled(); }); - it("delegates execution of its specs and suites", function() { + it("delegates execution of its specs, suites, beforeAlls, and afterAlls", function() { var env = new j$.Env(), parentSuiteDone = jasmine.createSpy('parent suite done'), fakeQueueRunnerForParent = jasmine.createSpy('fake parent queue runner'), @@ -101,22 +151,59 @@ describe("Suite", function() { queueRunner: fakeQueueRunner }), fakeSpec1 = { - execute: jasmine.createSpy('fakeSpec1') - }; + execute: jasmine.createSpy('fakeSpec1'), + isExecutable: function() { return true; } + }, + beforeAllFn = jasmine.createSpy('beforeAll'), + afterAllFn = jasmine.createSpy('afterAll'); spyOn(suite, "execute"); parentSuite.addChild(fakeSpec1); parentSuite.addChild(suite); + parentSuite.beforeAll(beforeAllFn); + parentSuite.afterAll(afterAllFn); parentSuite.execute(parentSuiteDone); var parentSuiteFns = fakeQueueRunnerForParent.calls.mostRecent().args[0].fns; parentSuiteFns[0](); - expect(fakeSpec1.execute).toHaveBeenCalled(); + expect(beforeAllFn).toHaveBeenCalled(); parentSuiteFns[1](); + expect(fakeSpec1.execute).toHaveBeenCalled(); + parentSuiteFns[2](); expect(suite.execute).toHaveBeenCalled(); + parentSuiteFns[3](); + expect(afterAllFn).toHaveBeenCalled(); + }); + + it("does not run beforeAll or afterAll if there are no child specs to run", function() { + var env = new j$.Env(), + fakeQueueRunnerForParent = jasmine.createSpy('fake parent queue runner'), + fakeQueueRunnerForChild = jasmine.createSpy('fake child queue runner'), + parentSuite = new j$.Suite({ + env: env, + description: "I am a suite", + queueRunner: fakeQueueRunnerForParent + }), + childSuite = new j$.Suite({ + env: env, + description: "I am a suite", + queueRunner: fakeQueueRunnerForChild, + parentSuite: parentSuite + }), + spec1 = new j$.Spec({expectationFactory: function() {}}), + spec2 = new j$.Spec({expectationFactory: function() {}}), + beforeAllFn = jasmine.createSpy('beforeAll'), + afterAllFn = jasmine.createSpy('afterAll'); + + parentSuite.addChild(childSuite); + parentSuite.addChild(spec1); + childSuite.addChild(spec1); + + parentSuite.execute(); + expect(fakeQueueRunnerForParent).toHaveBeenCalledWith(jasmine.objectContaining({fns: []})); }); it("calls a provided onStart callback when starting", function() { @@ -130,7 +217,8 @@ describe("Suite", function() { queueRunner: fakeQueueRunner }), fakeSpec1 = { - execute: jasmine.createSpy('fakeSpec1') + execute: jasmine.createSpy('fakeSpec1'), + isExecutable: function() { return true; } }; suite.execute(); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 3aef1360..668d0db6 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -165,6 +165,77 @@ describe("Env integration", function() { env.execute(); }); + it("calls associated beforeAlls/afterAlls only once per suite", function(done) { + var env = new j$.Env(), + before = jasmine.createSpy('beforeAll'), + after = jasmine.createSpy('afterAll'); + + env.addReporter({ + jasmineDone: function() { + expect(after).toHaveBeenCalled(); + expect(after.calls.count()).toBe(1); + expect(before.calls.count()).toBe(1); + done(); + } + }); + + env.describe("with beforeAll and afterAll", function() { + env.it("spec", function() { + expect(before).toHaveBeenCalled(); + expect(after).not.toHaveBeenCalled(); + }); + + env.it("another spec", function() { + expect(before).toHaveBeenCalled(); + expect(after).not.toHaveBeenCalled(); + }); + + env.beforeAll(before); + env.afterAll(after); + }); + + env.execute(); + }); + + it("calls associated beforeAlls/afterAlls only once per suite for async", function(done) { + var env = new j$.Env(), + before = jasmine.createSpy('beforeAll'), + after = jasmine.createSpy('afterAll'); + + env.addReporter({ + jasmineDone: function() { + expect(after).toHaveBeenCalled(); + expect(after.calls.count()).toBe(1); + expect(before.calls.count()).toBe(1); + done(); + } + }); + + env.describe("with beforeAll and afterAll", function() { + env.it("spec", function() { + expect(before).toHaveBeenCalled(); + expect(after).not.toHaveBeenCalled(); + }); + + env.it("another spec", function() { + expect(before).toHaveBeenCalled(); + expect(after).not.toHaveBeenCalled(); + }); + + env.beforeAll(function(beforeCallbackUnderTest) { + before(); + beforeCallbackUnderTest(); + }); + + env.afterAll(function(afterCallbackUnderTest) { + after(); + afterCallbackUnderTest(); + }); + }); + + env.execute(); + }); + it("Allows specifying which specs and suites to run", function(done) { var env = new j$.Env(), calls = [], diff --git a/src/core/Env.js b/src/core/Env.js index fc2fd27f..682a3923 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -324,10 +324,18 @@ getJasmineRequireObj().Env = function(j$) { currentSuite.beforeEach(beforeEachFunction); }; + this.beforeAll = function(beforeAllFunction) { + currentSuite.beforeAll(beforeAllFunction); + }; + this.afterEach = function(afterEachFunction) { currentSuite.afterEach(afterEachFunction); }; + this.afterAll = function(afterAllFunction) { + currentSuite.afterAll(afterAllFunction); + }; + this.pending = function() { throw j$.Spec.pendingSpecExceptionMessage; }; diff --git a/src/core/Spec.js b/src/core/Spec.js index 94c51522..0e1135f7 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -105,6 +105,10 @@ getJasmineRequireObj().Spec = function(j$) { } }; + Spec.prototype.isExecutable = function() { + return !this.disabled && !this.markedPending; + }; + Spec.prototype.getFullName = function() { return this.getSpecName(this); }; diff --git a/src/core/Suite.js b/src/core/Suite.js index 4b5e573d..ebdc9c4a 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -10,6 +10,8 @@ getJasmineRequireObj().Suite = function() { this.beforeFns = []; this.afterFns = []; + this.beforeAllFns = []; + this.afterAllFns = []; this.queueRunner = attrs.queueRunner || function() {}; this.disabled = false; @@ -41,10 +43,18 @@ getJasmineRequireObj().Suite = function() { this.beforeFns.unshift(fn); }; + Suite.prototype.beforeAll = function(fn) { + this.beforeAllFns.push(fn); + }; + Suite.prototype.afterEach = function(fn) { this.afterFns.unshift(fn); }; + Suite.prototype.afterAll = function(fn) { + this.afterAllFns.push(fn); + }; + Suite.prototype.addChild = function(child) { this.children.push(child); }; @@ -58,8 +68,18 @@ getJasmineRequireObj().Suite = function() { var allFns = []; - for (var i = 0; i < this.children.length; i++) { - allFns.push(wrapChildAsAsync(this.children[i])); + if (this.isExecutable()) { + for (var b = 0; b < this.beforeAllFns.length; b++) { + allFns.push(this.beforeAllFns[b]); + } + + for (var i = 0; i < this.children.length; i++) { + allFns.push(wrapChildAsAsync(this.children[i])); + } + + for (var a = 0; a < this.afterAllFns.length; a++) { + allFns.push(this.afterAllFns[a]); + } } this.onStart(this); @@ -82,6 +102,17 @@ getJasmineRequireObj().Suite = function() { } }; + Suite.prototype.isExecutable = function() { + var foundActive = false; + for(var i = 0; i < this.children.length; i++) { + if(this.children[i].isExecutable()) { + foundActive = true; + break; + } + } + return foundActive; + }; + return Suite; }; From e17a2cb1e06d2355db97c1adf110eb377b2b759a Mon Sep 17 00:00:00 2001 From: Gregg Van Hove and Sheel Choksi Date: Mon, 3 Mar 2014 11:57:32 -0800 Subject: [PATCH 02/40] share usercontext for full suite --- spec/core/integration/EnvSpec.js | 54 ++++++++++++++++++++++++++++++++ src/core/Env.js | 1 + src/core/QueueRunner.js | 2 +- src/core/Spec.js | 4 ++- src/core/Suite.js | 26 ++++++++++++++- 5 files changed, 84 insertions(+), 3 deletions(-) diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 668d0db6..147c2a50 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -236,6 +236,60 @@ describe("Env integration", function() { env.execute(); }); + it("calls associated beforeAlls/afterAlls with the cascaded 'this'", function(done) { + var env = new j$.Env(); + + env.addReporter({jasmineDone: done}); + + env.describe("with beforeAll and afterAll", function() { + env.beforeAll(function() { + this.x = 1; + }); + + env.it("has an x at the root", function() { + expect(this.x).toBe(1); + }); + + env.describe("child that deletes", function() { + env.beforeAll(function() { + expect(this.x).toBe(1); + delete this.x; + }); + + env.it("has no x", function() { + expect(this.x).not.toBeDefined(); + }); + }); + + env.describe("child should still have x", function() { + env.beforeAll(function(innerDone) { + expect(this.x).toBe(1); + innerDone(); + }); + + env.it("has an x", function() { + expect(this.x).toBe(1); + delete this.x; + }); + + env.it("still has an x", function() { + expect(this.x).toBe(1); + }); + + env.it("adds a y", function() { + this.y = 2; + expect(this.y).toBe(2); + }); + + env.it("doesn't have y that was added in sibling", function() { + expect(this.y).not.toBeDefined(); + }); + }); + }); + + env.execute(); + }); + it("Allows specifying which specs and suites to run", function(done) { var env = new j$.Env(), calls = [], diff --git a/src/core/Env.js b/src/core/Env.js index 682a3923..31b96928 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -272,6 +272,7 @@ getJasmineRequireObj().Env = function(j$) { description: description, expectationResultFactory: expectationResultFactory, queueRunnerFactory: queueRunnerFactory, + userContext: function() { return suite.clonedSharedUserContext(); }, fn: fn }); diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index 4a0e66f2..f9846786 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -17,7 +17,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.onException = attrs.onException || function() {}; this.catchException = attrs.catchException || function() { return true; }; this.enforceTimeout = attrs.enforceTimeout || function() { return false; }; - this.userContext = {}; + this.userContext = attrs.userContext || {}; this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; } diff --git a/src/core/Spec.js b/src/core/Spec.js index 0e1135f7..e28050fd 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -7,6 +7,7 @@ getJasmineRequireObj().Spec = function(j$) { this.fn = attrs.fn; this.beforeFns = attrs.beforeFns || function() { return []; }; this.afterFns = attrs.afterFns || function() { return []; }; + this.userContext = attrs.userContext || function() { return {}; }; this.onStart = attrs.onStart || function() {}; this.exceptionFormatter = attrs.exceptionFormatter || function() {}; this.getSpecName = attrs.getSpecName || function() { return ''; }; @@ -53,7 +54,8 @@ getJasmineRequireObj().Spec = function(j$) { fns: allFns, onException: onException, onComplete: complete, - enforceTimeout: function() { return true; } + enforceTimeout: function() { return true; }, + userContext: this.userContext() }); function onException(e) { diff --git a/src/core/Suite.js b/src/core/Suite.js index ebdc9c4a..601c6115 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -86,7 +86,8 @@ getJasmineRequireObj().Suite = function() { this.queueRunner({ fns: allFns, - onComplete: complete + onComplete: complete, + userContext: this.sharedUserContext() }); function complete() { @@ -113,6 +114,29 @@ getJasmineRequireObj().Suite = function() { return foundActive; }; + Suite.prototype.sharedUserContext = function() { + if (!this.sharedContext) { + this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {}; + } + + return this.sharedContext; + }; + + Suite.prototype.clonedSharedUserContext = function() { + return clone(this.sharedUserContext()); + }; + + function clone(obj) { + var clonedObj = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + clonedObj[prop] = obj[prop]; + } + } + + return clonedObj; + } + return Suite; }; From 52026fb0f7a622501aac0f507d52135e9d02f2ad Mon Sep 17 00:00:00 2001 From: Gregg Van Hove and Sheel Choksi Date: Mon, 3 Mar 2014 16:13:59 -0800 Subject: [PATCH 03/40] beforeAll/afterAll can be timed out and errors are applied to all children specs --- spec/core/QueueRunnerSpec.js | 124 ++++++++++++++++--------------- spec/core/SpecSpec.js | 36 ++++----- spec/core/SuiteSpec.js | 28 +++---- spec/core/integration/EnvSpec.js | 35 ++++++++- src/core/Env.js | 14 ++-- src/core/QueueRunner.js | 31 ++++---- src/core/Spec.js | 41 +++++----- src/core/Suite.js | 22 +++--- 8 files changed, 183 insertions(+), 148 deletions(-) diff --git a/spec/core/QueueRunnerSpec.js b/spec/core/QueueRunnerSpec.js index 37e651ee..e677104b 100644 --- a/spec/core/QueueRunnerSpec.js +++ b/spec/core/QueueRunnerSpec.js @@ -2,15 +2,15 @@ describe("QueueRunner", function() { it("runs all the functions it's passed", function() { var calls = [], - fn1 = jasmine.createSpy('fn1'), - fn2 = jasmine.createSpy('fn2'), + queueableFn1 = { fn: jasmine.createSpy('fn1') }, + queueableFn2 = { fn: jasmine.createSpy('fn2') }, queueRunner = new j$.QueueRunner({ - fns: [fn1, fn2] + queueableFns: [queueableFn1, queueableFn2] }); - fn1.and.callFake(function() { + queueableFn1.fn.and.callFake(function() { calls.push('fn1'); }); - fn2.and.callFake(function() { + queueableFn2.fn.and.callFake(function() { calls.push('fn2'); }); @@ -20,19 +20,19 @@ describe("QueueRunner", function() { }); it("calls each function with a consistent 'this'-- an empty object", function() { - var fn1 = jasmine.createSpy('fn1'), - fn2 = jasmine.createSpy('fn2'), - fn3 = function(done) { asyncContext = this; done(); }, + var queueableFn1 = { fn: jasmine.createSpy('fn1') }, + queueableFn2 = { fn: jasmine.createSpy('fn2') }, + queueableFn3 = { fn: function(done) { asyncContext = this; done(); } }, queueRunner = new j$.QueueRunner({ - fns: [fn1, fn2, fn3] + queueableFns: [queueableFn1, queueableFn2, queueableFn3] }), asyncContext; queueRunner.execute(); - var context = fn1.calls.first().object; + var context = queueableFn1.fn.calls.first().object; expect(context).toEqual({}); - expect(fn2.calls.first().object).toBe(context); + expect(queueableFn2.fn.calls.first().object).toBe(context); expect(asyncContext).toBe(context); }); @@ -53,20 +53,20 @@ describe("QueueRunner", function() { beforeCallback = jasmine.createSpy('beforeCallback'), fnCallback = jasmine.createSpy('fnCallback'), afterCallback = jasmine.createSpy('afterCallback'), - fn1 = function(done) { + queueableFn1 = { fn: function(done) { beforeCallback(); setTimeout(done, 100); - }, - fn2 = function(done) { + } }, + queueableFn2 = { fn: function(done) { fnCallback(); setTimeout(done, 100); - }, - fn3 = function(done) { + } }, + queueableFn3 = { fn: function(done) { afterCallback(); setTimeout(done, 100); - }, + } }, queueRunner = new j$.QueueRunner({ - fns: [fn1, fn2, fn3], + queueableFns: [queueableFn1, queueableFn2, queueableFn3], onComplete: onComplete }); @@ -94,54 +94,54 @@ describe("QueueRunner", function() { }); it("sets a timeout if requested for asynchronous functions so they don't go on forever", function() { - var beforeFn = function(done) { }, - fn = jasmine.createSpy('fn'), + var timeout = 3, + beforeFn = { fn: function(done) { }, timeout: function() { return timeout; } }, + queueableFn = { fn: jasmine.createSpy('fn') }, onComplete = jasmine.createSpy('onComplete'), onException = jasmine.createSpy('onException'), queueRunner = new j$.QueueRunner({ - fns: [beforeFn, fn], + queueableFns: [beforeFn, queueableFn], onComplete: onComplete, - onException: onException, - enforceTimeout: function() { return true; } + onException: onException }); queueRunner.execute(); - expect(fn).not.toHaveBeenCalled(); + expect(queueableFn.fn).not.toHaveBeenCalled(); - jasmine.clock().tick(j$.DEFAULT_TIMEOUT_INTERVAL); + jasmine.clock().tick(timeout); expect(onException).toHaveBeenCalledWith(jasmine.any(Error)); - expect(fn).toHaveBeenCalled(); + expect(queueableFn.fn).toHaveBeenCalled(); expect(onComplete).toHaveBeenCalled(); }); it("by default does not set a timeout for asynchronous functions", function() { - var beforeFn = function(done) { }, - fn = jasmine.createSpy('fn'), + var beforeFn = { fn: function(done) { } }, + queueableFn = { fn: jasmine.createSpy('fn') }, onComplete = jasmine.createSpy('onComplete'), onException = jasmine.createSpy('onException'), queueRunner = new j$.QueueRunner({ - fns: [beforeFn, fn], + queueableFns: [beforeFn, queueableFn], onComplete: onComplete, onException: onException, }); queueRunner.execute(); - expect(fn).not.toHaveBeenCalled(); + expect(queueableFn.fn).not.toHaveBeenCalled(); jasmine.clock().tick(j$.DEFAULT_TIMEOUT_INTERVAL); expect(onException).not.toHaveBeenCalled(); - expect(fn).not.toHaveBeenCalled(); + expect(queueableFn.fn).not.toHaveBeenCalled(); expect(onComplete).not.toHaveBeenCalled(); }); it("clears the timeout when an async function throws an exception, to prevent additional onException calls", function() { - var fn = function(done) { throw new Error("error!"); }, + var queueableFn = { fn: function(done) { throw new Error("error!"); } }, onComplete = jasmine.createSpy('onComplete'), onException = jasmine.createSpy('onException'), queueRunner = new j$.QueueRunner({ - fns: [fn], + queueableFns: [queueableFn], onComplete: onComplete, onException: onException }); @@ -156,11 +156,11 @@ describe("QueueRunner", function() { }); it("clears the timeout when the done callback is called", function() { - var fn = function(done) { done(); }, + var queueableFn = { fn: function(done) { done(); } }, onComplete = jasmine.createSpy('onComplete'), onException = jasmine.createSpy('onException'), queueRunner = new j$.QueueRunner({ - fns: [fn], + queueableFns: [queueableFn], onComplete: onComplete, onException: onException }); @@ -174,39 +174,39 @@ describe("QueueRunner", function() { }); it("only moves to the next spec the first time you call done", function() { - var fn = function(done) {done(); done();}, - nextFn = jasmine.createSpy('nextFn'); + var queueableFn = { fn: function(done) {done(); done();} }, + nextQueueableFn = { fn: jasmine.createSpy('nextFn') }; queueRunner = new j$.QueueRunner({ - fns: [fn, nextFn] + queueableFns: [queueableFn, nextQueueableFn] }); queueRunner.execute(); - expect(nextFn.calls.count()).toEqual(1); + expect(nextQueueableFn.fn.calls.count()).toEqual(1); }); it("does not move to the next spec if done is called after an exception has ended the spec", function() { - var fn = function(done) { + var queueableFn = { fn: function(done) { setTimeout(done, 1); throw new Error('error!'); - }, - nextFn = jasmine.createSpy('nextFn'); + } }, + nextQueueableFn = { fn: jasmine.createSpy('nextFn') }; queueRunner = new j$.QueueRunner({ - fns: [fn, nextFn] + queueableFns: [queueableFn, nextQueueableFn] }); queueRunner.execute(); jasmine.clock().tick(1); - expect(nextFn.calls.count()).toEqual(1); + expect(nextQueueableFn.fn.calls.count()).toEqual(1); }); }); it("calls an exception handler when an exception is thrown in a fn", function() { - var fn = function() { + var queueableFn = { fn: function() { throw new Error('fake error'); - }, + } }, exceptionCallback = jasmine.createSpy('exception callback'), queueRunner = new j$.QueueRunner({ - fns: [fn], + queueableFns: [queueableFn], onException: exceptionCallback }); @@ -216,33 +216,35 @@ describe("QueueRunner", function() { }); it("rethrows an exception if told to", function() { - var fn = function() { + var queueableFn = { fn: function() { throw new Error('fake error'); - }, + } }, queueRunner = new j$.QueueRunner({ - fns: [fn], + queueableFns: [queueableFn], catchException: function(e) { return false; } }); - expect(queueRunner.execute).toThrow(); + expect(function() { + queueRunner.execute(); + }).toThrowError('fake error'); }); it("continues running the functions even after an exception is thrown in an async spec", function() { - var fn = function(done) { throw new Error("error"); }, - nextFn = jasmine.createSpy("nextFunction"), + var queueableFn = { fn: function(done) { throw new Error("error"); } }, + nextQueueableFn = { fn: jasmine.createSpy("nextFunction") }, queueRunner = new j$.QueueRunner({ - fns: [fn, nextFn] + queueableFns: [queueableFn, nextQueueableFn] }); queueRunner.execute(); - expect(nextFn).toHaveBeenCalled(); + expect(nextQueueableFn.fn).toHaveBeenCalled(); }); it("calls a provided complete callback when done", function() { - var fn = jasmine.createSpy('fn'), + var queueableFn = { fn: jasmine.createSpy('fn') }, completeCallback = jasmine.createSpy('completeCallback'), queueRunner = new j$.QueueRunner({ - fns: [fn], + queueableFns: [queueableFn], onComplete: completeCallback }); @@ -252,12 +254,12 @@ describe("QueueRunner", function() { }); it("calls a provided stack clearing function when done", function() { - var asyncFn = function(done) { done() }, - afterFn = jasmine.createSpy('afterFn'), + var asyncFn = { fn: function(done) { done() } }, + afterFn = { fn: jasmine.createSpy('afterFn') }, completeCallback = jasmine.createSpy('completeCallback'), clearStack = jasmine.createSpy('clearStack'), queueRunner = new j$.QueueRunner({ - fns: [asyncFn, afterFn], + queueableFns: [asyncFn, afterFn], clearStack: clearStack, onComplete: completeCallback }); @@ -265,7 +267,7 @@ describe("QueueRunner", function() { clearStack.and.callFake(function(fn) { fn(); }); queueRunner.execute(); - expect(afterFn).toHaveBeenCalled(); + expect(afterFn.fn).toHaveBeenCalled(); expect(clearStack).toHaveBeenCalledWith(completeCallback); }); }); diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index c2056884..71fff7d2 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -29,7 +29,7 @@ describe("Spec", function() { spec = new j$.Spec({ description: 'my test', id: 'some-id', - fn: function() {}, + queueableFn: { fn: function() {} }, queueRunnerFactory: fakeQueueRunner }); @@ -44,7 +44,7 @@ describe("Spec", function() { spec = new j$.Spec({ id: 123, description: 'foo bar', - fn: function() {}, + queueableFn: { fn: function() {} }, onStart: startCallback, queueRunnerFactory: fakeQueueRunner }); @@ -66,7 +66,7 @@ describe("Spec", function() { expect(beforesWereCalled).toBe(false); }), spec = new j$.Spec({ - fn: function() {}, + queueableFn: { fn: function() {} }, beforeFns: function() { return [function() { beforesWereCalled = true @@ -85,12 +85,12 @@ describe("Spec", function() { var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'), before = jasmine.createSpy('before'), after = jasmine.createSpy('after'), - fn = jasmine.createSpy('test body').and.callFake(function() { + queueableFn = { fn: jasmine.createSpy('test body').and.callFake(function() { expect(before).toHaveBeenCalled(); expect(after).not.toHaveBeenCalled(); - }), + }) }, spec = new j$.Spec({ - fn: fn, + queueableFn: queueableFn, beforeFns: function() { return [before] }, @@ -102,8 +102,8 @@ describe("Spec", function() { spec.execute(); - var allSpecFns = fakeQueueRunner.calls.mostRecent().args[0].fns; - expect(allSpecFns).toEqual([before, fn, after]); + var allSpecFns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns; + expect(allSpecFns).toEqual([before, queueableFn, after]); }); it("is marked pending if created without a function body", function() { @@ -113,7 +113,7 @@ describe("Spec", function() { resultCallback = jasmine.createSpy('resultCallback'), spec = new j$.Spec({ onStart: startCallback, - fn: null, + queueableFn: { fn: null }, resultCallback: resultCallback, queueRunnerFactory: fakeQueueRunner }); @@ -129,7 +129,7 @@ describe("Spec", function() { resultCallback = jasmine.createSpy('resultCallback'), spec = new j$.Spec({ onStart:startCallback, - fn: specBody, + queueableFn: { fn: specBody }, resultCallback: resultCallback, queueRunnerFactory: fakeQueueRunner }); @@ -158,7 +158,8 @@ describe("Spec", function() { getSpecName: function() { return "a suite with a spec" }, - queueRunnerFactory: fakeQueueRunner + queueRunnerFactory: fakeQueueRunner, + queueableFn: { fn: null } }); spec.pend(); @@ -182,7 +183,7 @@ describe("Spec", function() { it("should call the done callback on execution complete", function() { var done = jasmine.createSpy('done callback'), spec = new j$.Spec({ - fn: function() {}, + queueableFn: { fn: function() {} }, catchExceptions: function() { return false; }, resultCallback: function() {}, queueRunnerFactory: function(attrs) { attrs.onComplete(); } @@ -194,18 +195,18 @@ describe("Spec", function() { }); it("#status returns passing by default", function() { - var spec = new j$.Spec({fn: jasmine.createSpy("spec body")}); + var spec = new j$.Spec({queueableFn: { fn: jasmine.createSpy("spec body")} }); expect(spec.status()).toEqual('passed'); }); it("#status returns passed if all expectations in the spec have passed", function() { - var spec = new j$.Spec({fn: jasmine.createSpy("spec body")}); + var spec = new j$.Spec({queueableFn: { fn: jasmine.createSpy("spec body")} }); spec.addExpectationResult(true); expect(spec.status()).toBe('passed'); }); it("#status returns failed if any expectations in the spec have failed", function() { - var spec = new j$.Spec({ fn: jasmine.createSpy("spec body") }); + var spec = new j$.Spec({queueableFn: { fn: jasmine.createSpy("spec body") } }); spec.addExpectationResult(true); spec.addExpectationResult(false); expect(spec.status()).toBe('failed'); @@ -215,7 +216,8 @@ describe("Spec", function() { var specNameSpy = jasmine.createSpy('specNameSpy').and.returnValue('expected val'); var spec = new j$.Spec({ - getSpecName: specNameSpy + getSpecName: specNameSpy, + queueableFn: { fn: null } }); expect(spec.getFullName()).toBe('expected val'); @@ -230,7 +232,7 @@ describe("Spec", function() { spec = new j$.Spec({ description: 'my test', id: 'some-id', - fn: function() { }, + queueableFn: { fn: function() { } }, queueRunnerFactory: fakeQueueRunner }); diff --git a/spec/core/SuiteSpec.js b/spec/core/SuiteSpec.js index 38ada7d6..297204ba 100644 --- a/spec/core/SuiteSpec.js +++ b/spec/core/SuiteSpec.js @@ -69,7 +69,7 @@ describe("Suite", function() { suite.addChild(fakeIt); suite.execute(); - var suiteFns = fakeQueueRunner.calls.mostRecent().args[0].fns; + var suiteFns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns; suiteFns[0](); expect(firstBefore).toHaveBeenCalled(); @@ -109,7 +109,7 @@ describe("Suite", function() { suite.addChild(fakeIt); suite.execute(); - var suiteFns = fakeQueueRunner.calls.mostRecent().args[0].fns; + var suiteFns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns; suiteFns[1](); expect(firstAfter).toHaveBeenCalled(); @@ -154,8 +154,8 @@ describe("Suite", function() { execute: jasmine.createSpy('fakeSpec1'), isExecutable: function() { return true; } }, - beforeAllFn = jasmine.createSpy('beforeAll'), - afterAllFn = jasmine.createSpy('afterAll'); + beforeAllFn = { fn: jasmine.createSpy('beforeAll') }, + afterAllFn = { fn: jasmine.createSpy('afterAll') }; spyOn(suite, "execute"); @@ -166,16 +166,16 @@ describe("Suite", function() { parentSuite.execute(parentSuiteDone); - var parentSuiteFns = fakeQueueRunnerForParent.calls.mostRecent().args[0].fns; + var parentSuiteFns = fakeQueueRunnerForParent.calls.mostRecent().args[0].queueableFns; - parentSuiteFns[0](); - expect(beforeAllFn).toHaveBeenCalled(); - parentSuiteFns[1](); + parentSuiteFns[0].fn(); + expect(beforeAllFn.fn).toHaveBeenCalled(); + parentSuiteFns[1].fn(); expect(fakeSpec1.execute).toHaveBeenCalled(); - parentSuiteFns[2](); + parentSuiteFns[2].fn(); expect(suite.execute).toHaveBeenCalled(); - parentSuiteFns[3](); - expect(afterAllFn).toHaveBeenCalled(); + parentSuiteFns[3].fn(); + expect(afterAllFn.fn).toHaveBeenCalled(); }); it("does not run beforeAll or afterAll if there are no child specs to run", function() { @@ -193,8 +193,8 @@ describe("Suite", function() { queueRunner: fakeQueueRunnerForChild, parentSuite: parentSuite }), - spec1 = new j$.Spec({expectationFactory: function() {}}), - spec2 = new j$.Spec({expectationFactory: function() {}}), + spec1 = new j$.Spec({expectationFactory: function() {}, queueableFn: {}}), + spec2 = new j$.Spec({expectationFactory: function() {}, queueableFn: {}}), beforeAllFn = jasmine.createSpy('beforeAll'), afterAllFn = jasmine.createSpy('afterAll'); @@ -203,7 +203,7 @@ describe("Suite", function() { childSuite.addChild(spec1); parentSuite.execute(); - expect(fakeQueueRunnerForParent).toHaveBeenCalledWith(jasmine.objectContaining({fns: []})); + expect(fakeQueueRunnerForParent).toHaveBeenCalledWith(jasmine.objectContaining({queueableFns: []})); }); it("calls a provided onStart callback when starting", function() { diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 147c2a50..08c06ee2 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -414,11 +414,11 @@ describe("Env integration", function() { beforeEach(function() { originalTimeout = j$.DEFAULT_TIMEOUT_INTERVAL; - jasmine.getEnv().clock.install(); + jasmine.clock().install(); }); afterEach(function() { - jasmine.getEnv().clock.uninstall(); + jasmine.clock().uninstall(); j$.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; }); @@ -440,7 +440,36 @@ describe("Env integration", function() { env.it("async spec that doesn't call done", function(underTestCallback) { env.expect(true).toBeTruthy(); - jasmine.getEnv().clock.tick(8416); + jasmine.clock().tick(8416); + }); + + env.execute(); + }); + + it("should wait a specified interval before failing beforeAll's and their associated specs that haven't called done", function(done) { + var env = new j$.Env(), + reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone" ]); + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.specDone.calls.count()).toEqual(2); + expect(reporter.specDone.calls.argsFor(0)[0]).toEqual(jasmine.objectContaining({status: 'failed'})); + expect(reporter.specDone.calls.argsFor(1)[0]).toEqual(jasmine.objectContaining({status: 'failed'})); + done(); + }); + + env.addReporter(reporter); + j$.DEFAULT_TIMEOUT_INTERVAL = 1290; + + env.beforeAll(function(done) { + jasmine.clock().tick(1290); + }); + + env.it("spec that will be failed", function() { + }); + + env.describe("nesting", function() { + env.it("another spec to fail", function() { + }); }); env.execute(); diff --git a/src/core/Env.js b/src/core/Env.js index 31b96928..3fb1447c 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -161,14 +161,14 @@ getJasmineRequireObj().Env = function(j$) { var allFns = []; for(var i = 0; i < runnablesToRun.length; i++) { var runnable = runnableLookupTable[runnablesToRun[i]]; - allFns.push((function(runnable) { return function(done) { runnable.execute(done); }; })(runnable)); + allFns.push((function(runnable) { return { fn: function(done) { runnable.execute(done); } }; })(runnable)); } reporter.jasmineStarted({ totalSpecsDefined: totalSpecsDefined }); - queueRunnerFactory({fns: allFns, onComplete: reporter.jasmineDone}); + queueRunnerFactory({queueableFns: allFns, onComplete: reporter.jasmineDone}); }; this.addReporter = function(reporterToAdd) { @@ -273,7 +273,7 @@ getJasmineRequireObj().Env = function(j$) { expectationResultFactory: expectationResultFactory, queueRunnerFactory: queueRunnerFactory, userContext: function() { return suite.clonedSharedUserContext(); }, - fn: fn + queueableFn: { fn: fn, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } } }); runnableLookupTable[spec.id] = spec; @@ -322,19 +322,19 @@ getJasmineRequireObj().Env = function(j$) { }; this.beforeEach = function(beforeEachFunction) { - currentSuite.beforeEach(beforeEachFunction); + currentSuite.beforeEach({ fn: beforeEachFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.beforeAll = function(beforeAllFunction) { - currentSuite.beforeAll(beforeAllFunction); + currentSuite.beforeAll({ fn: beforeAllFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.afterEach = function(afterEachFunction) { - currentSuite.afterEach(afterEachFunction); + currentSuite.afterEach({ fn: afterEachFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.afterAll = function(afterAllFunction) { - currentSuite.afterAll(afterAllFunction); + currentSuite.afterAll({ fn: afterAllFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.pending = function() { diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index f9846786..1ec3c16e 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -11,31 +11,30 @@ getJasmineRequireObj().QueueRunner = function(j$) { } function QueueRunner(attrs) { - this.fns = attrs.fns || []; + this.queueableFns = attrs.queueableFns || []; this.onComplete = attrs.onComplete || function() {}; this.clearStack = attrs.clearStack || function(fn) {fn();}; this.onException = attrs.onException || function() {}; this.catchException = attrs.catchException || function() { return true; }; - this.enforceTimeout = attrs.enforceTimeout || function() { return false; }; this.userContext = attrs.userContext || {}; this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; } QueueRunner.prototype.execute = function() { - this.run(this.fns, 0); + this.run(this.queueableFns, 0); }; - QueueRunner.prototype.run = function(fns, recursiveIndex) { - var length = fns.length, + QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { + var length = queueableFns.length, self = this, iterativeIndex; for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { - var fn = fns[iterativeIndex]; - if (fn.length > 0) { - return attemptAsync(fn); + var queueableFn = queueableFns[iterativeIndex]; + if (queueableFn.fn.length > 0) { + return attemptAsync(queueableFn); } else { - attemptSync(fn); + attemptSync(queueableFn); } } @@ -45,33 +44,33 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.clearStack(this.onComplete); } - function attemptSync(fn) { + function attemptSync(queueableFn) { try { - fn.call(self.userContext); + queueableFn.fn.call(self.userContext); } catch (e) { handleException(e); } } - function attemptAsync(fn) { + function attemptAsync(queueableFn) { var clearTimeout = function () { Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeoutId]]); }, next = once(function () { clearTimeout(timeoutId); - self.run(fns, iterativeIndex + 1); + self.run(queueableFns, iterativeIndex + 1); }), timeoutId; - if (self.enforceTimeout()) { + 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.')); next(); - }, j$.DEFAULT_TIMEOUT_INTERVAL]]); + }, queueableFn.timeout()]]); } try { - fn.call(self.userContext, next); + queueableFn.fn.call(self.userContext, next); } catch (e) { handleException(e); next(); diff --git a/src/core/Spec.js b/src/core/Spec.js index e28050fd..b260a213 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -4,7 +4,7 @@ getJasmineRequireObj().Spec = function(j$) { this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; this.description = attrs.description || ''; - this.fn = attrs.fn; + this.queueableFn = attrs.queueableFn; this.beforeFns = attrs.beforeFns || function() { return []; }; this.afterFns = attrs.afterFns || function() { return []; }; this.userContext = attrs.userContext || function() { return {}; }; @@ -15,7 +15,7 @@ getJasmineRequireObj().Spec = function(j$) { this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; - if (!this.fn) { + if (!this.queueableFn.fn) { this.pend(); } @@ -48,31 +48,15 @@ getJasmineRequireObj().Spec = function(j$) { return; } - var allFns = this.beforeFns().concat(this.fn).concat(this.afterFns()); + var allFns = this.beforeFns().concat(this.queueableFn).concat(this.afterFns()); this.queueRunnerFactory({ - fns: allFns, - onException: onException, + queueableFns: allFns, + onException: function() { self.onException.apply(self, arguments); }, onComplete: complete, - enforceTimeout: function() { return true; }, userContext: this.userContext() }); - function onException(e) { - if (Spec.isPendingSpecException(e)) { - self.pend(); - return; - } - - self.addExpectationResult(false, { - matcherName: '', - passed: false, - expected: '', - actual: '', - error: e - }); - } - function complete() { self.result.status = self.status(); self.resultCallback(self.result); @@ -83,6 +67,21 @@ getJasmineRequireObj().Spec = function(j$) { } }; + Spec.prototype.onException = function onException(e) { + if (Spec.isPendingSpecException(e)) { + this.pend(); + return; + } + + this.addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: e + }); + }; + Spec.prototype.disable = function() { this.disabled = true; }; diff --git a/src/core/Suite.js b/src/core/Suite.js index 601c6115..29d2d85f 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -69,25 +69,22 @@ getJasmineRequireObj().Suite = function() { var allFns = []; if (this.isExecutable()) { - for (var b = 0; b < this.beforeAllFns.length; b++) { - allFns.push(this.beforeAllFns[b]); - } + allFns = this.beforeAllFns; for (var i = 0; i < this.children.length; i++) { allFns.push(wrapChildAsAsync(this.children[i])); } - for (var a = 0; a < this.afterAllFns.length; a++) { - allFns.push(this.afterAllFns[a]); - } + allFns = allFns.concat(this.afterAllFns); } this.onStart(this); this.queueRunner({ - fns: allFns, + queueableFns: allFns, onComplete: complete, - userContext: this.sharedUserContext() + userContext: this.sharedUserContext(), + onException: function() { self.onException.apply(self, arguments); } }); function complete() { @@ -99,7 +96,7 @@ getJasmineRequireObj().Suite = function() { } function wrapChildAsAsync(child) { - return function(done) { child.execute(done); }; + return { fn: function(done) { child.execute(done); } }; } }; @@ -126,6 +123,13 @@ getJasmineRequireObj().Suite = function() { return clone(this.sharedUserContext()); }; + Suite.prototype.onException = function() { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.onException.apply(child, arguments); + } + }; + function clone(obj) { var clonedObj = {}; for (var prop in obj) { From 752a36d3ff67013feeb504a1176ab4dc865e0118 Mon Sep 17 00:00:00 2001 From: "Christopher Amavisca, Greg Cobb and Sheel Choksi" Date: Wed, 5 Mar 2014 10:28:37 -0800 Subject: [PATCH 04/40] Manage spys/matchers/custom equalities for beforeAll - Refactor expectations to take list of matchers - Add spyRegistry to manage runnables' spies - Add clone util [#66789174] --- spec/core/EnvSpec.js | 63 ------ spec/core/ExpectationSpec.js | 68 ++---- spec/core/PrettyPrintSpec.js | 5 +- spec/core/SpyRegistrySpec.js | 55 +++++ spec/core/integration/CustomMatchersSpec.js | 66 +++--- spec/core/integration/EnvSpec.js | 234 ++++++++++++++++---- spec/core/integration/SpecRunningSpec.js | 9 +- src/core/Env.js | 131 +++++------ src/core/Expectation.js | 20 +- src/core/SpyRegistry.js | 46 ++++ src/core/requireCore.js | 1 + src/core/util.js | 15 ++ 12 files changed, 441 insertions(+), 272 deletions(-) create mode 100644 spec/core/SpyRegistrySpec.js create mode 100644 src/core/SpyRegistry.js diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index 86898f56..f8867885 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -5,68 +5,6 @@ describe("Env", function() { env = new j$.Env(); }); - it('removes all spies when env is executed', function(done) { - var originalFoo = function() {}, - testObj = { - foo: originalFoo - }, - firstSpec = jasmine.createSpy('firstSpec').and.callFake(function() { - env.spyOn(testObj, 'foo'); - }), - secondSpec = jasmine.createSpy('secondSpec').and.callFake(function() { - expect(testObj.foo).toBe(originalFoo); - }); - env.describe('test suite', function() { - env.it('spec 0', firstSpec); - env.it('spec 1', secondSpec); - }); - - var assertions = function() { - expect(firstSpec).toHaveBeenCalled(); - expect(secondSpec).toHaveBeenCalled(); - done(); - }; - - env.addReporter({ jasmineDone: assertions }); - - env.execute(); - }); - - describe("#spyOn", function() { - it("checks for the existence of the object", function() { - expect(function() { - env.spyOn(void 0, 'pants'); - }).toThrowError(/could not find an object/); - }); - - it("checks for the existence of the method", function() { - var subject = {}; - - expect(function() { - env.spyOn(subject, 'pants'); - }).toThrowError(/method does not exist/); - }); - - it("checks if it has already been spied upon", function() { - var subject = { spiedFunc: function() {} }; - - env.spyOn(subject, 'spiedFunc'); - - expect(function() { - env.spyOn(subject, 'spiedFunc'); - }).toThrowError(/has already been spied upon/); - }); - - it("overrides the method on the object and returns the spy", function() { - var originalFunctionWasCalled = false; - var subject = { spiedFunc: function() { originalFunctionWasCalled = true; } }; - - var spy = env.spyOn(subject, 'spiedFunc'); - - expect(subject.spiedFunc).toEqual(spy); - }); - }); - describe("#pending", function() { it("throws the Pending Spec exception", function() { expect(function() { @@ -82,4 +20,3 @@ describe("Env", function() { }); }); }); - diff --git a/spec/core/ExpectationSpec.js b/spec/core/ExpectationSpec.js index c5528aa1..b253672b 100644 --- a/spec/core/ExpectationSpec.js +++ b/spec/core/ExpectationSpec.js @@ -1,14 +1,14 @@ describe("Expectation", function() { - it(".addMatchers makes matchers available to any expectation", function() { + it("makes custom matchers available to this expectation", function() { var matchers = { toFoo: function() {}, toBar: function() {} }, expectation; - j$.Expectation.addMatchers(matchers); - - expectation = new j$.Expectation({}); + expectation = new j$.Expectation({ + customMatchers: matchers + }); expect(expectation.toFoo).toBeDefined(); expect(expectation.toBar).toBeDefined(); @@ -27,25 +27,6 @@ describe("Expectation", function() { expect(expectation.toQuux).toBeDefined(); }); - it(".resetMatchers should keep only core matchers", function() { - var matchers = { - toFoo: function() {} - }, - coreMatchers = { - toQuux: function() {} - }, - expectation; - - j$.Expectation.addCoreMatchers(coreMatchers); - j$.Expectation.addMatchers(matchers); - j$.Expectation.resetMatchers(); - - expectation = new j$.Expectation({}); - - expect(expectation.toQuux).toBeDefined(); - expect(expectation.toFoo).toBeUndefined(); - }); - it("Factory builds an expectation/negative expectation", function() { var builtExpectation = j$.Expectation.Factory(); @@ -65,10 +46,9 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ util: util, + customMatchers: matchers, customEqualityTesters: customEqualityTesters, actual: "an actual", addExpectationResult: addExpectationResult @@ -94,10 +74,9 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ util: util, + customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult }); @@ -121,10 +100,8 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, util: util, actual: "an actual", addExpectationResult: addExpectationResult @@ -155,10 +132,8 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, util: util, actual: "an actual", addExpectationResult: addExpectationResult @@ -191,10 +166,9 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ actual: "an actual", + customMatchers: matchers, addExpectationResult: addExpectationResult }); @@ -225,10 +199,8 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult }); @@ -259,10 +231,8 @@ describe("Expectation", function() { actual = "an actual", expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult, isNot: true @@ -294,10 +264,8 @@ describe("Expectation", function() { actual = "an actual", expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, actual: "an actual", util: util, addExpectationResult: addExpectationResult, @@ -332,10 +300,8 @@ describe("Expectation", function() { actual = "an actual", expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult, isNot: true @@ -365,10 +331,8 @@ describe("Expectation", function() { actual = "an actual", expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult, isNot: true @@ -403,10 +367,8 @@ describe("Expectation", function() { actual = "an actual", expectation; - j$.Expectation.addMatchers(matchers); - expectation = new j$.Expectation({ - matchers: matchers, + customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult, isNot: true diff --git a/spec/core/PrettyPrintSpec.js b/spec/core/PrettyPrintSpec.js index 7b7c21ab..0f767c87 100644 --- a/spec/core/PrettyPrintSpec.js +++ b/spec/core/PrettyPrintSpec.js @@ -109,7 +109,9 @@ describe("j$.pp", function () { }, env = new j$.Env(); - env.spyOn(TestObject, 'someFunction'); + var spyRegistry = new j$.SpyRegistry({currentSpies: function() {return [];}}); + + spyRegistry.spyOn(TestObject, 'someFunction'); expect(j$.pp(TestObject.someFunction)).toEqual("spy on someFunction"); expect(j$.pp(j$.createSpy("something"))).toEqual("spy on something"); @@ -132,4 +134,3 @@ describe("j$.pp", function () { expect(j$.pp(obj)).toEqual("{ foo : 'bar' }"); }); }); - diff --git a/spec/core/SpyRegistrySpec.js b/spec/core/SpyRegistrySpec.js new file mode 100644 index 00000000..36dcf68c --- /dev/null +++ b/spec/core/SpyRegistrySpec.js @@ -0,0 +1,55 @@ +describe("SpyRegistry", function() { + describe("#spyOn", function() { + it("checks for the existence of the object", function() { + var spyRegistry = new j$.SpyRegistry(); + expect(function() { + spyRegistry.spyOn(void 0, 'pants'); + }).toThrowError(/could not find an object/); + }); + + it("checks for the existence of the method", function() { + var spyRegistry = new j$.SpyRegistry(), + subject = {}; + + expect(function() { + spyRegistry.spyOn(subject, 'pants'); + }).toThrowError(/method does not exist/); + }); + + it("checks if it has already been spied upon", function() { + var spies = [], + spyRegistry = new j$.SpyRegistry({currentSpies: function() { return spies; }}), + subject = { spiedFunc: function() {} }; + + spyRegistry.spyOn(subject, 'spiedFunc'); + + expect(function() { + spyRegistry.spyOn(subject, 'spiedFunc'); + }).toThrowError(/has already been spied upon/); + }); + + it("overrides the method on the object and returns the spy", function() { + var originalFunctionWasCalled = false, + spyRegistry = new j$.SpyRegistry(), + subject = { spiedFunc: function() { originalFunctionWasCalled = true; } }; + + var spy = spyRegistry.spyOn(subject, 'spiedFunc'); + + expect(subject.spiedFunc).toEqual(spy); + }); + }); + + describe("#clearSpies", function() { + it("restores the original functions on the spied-upon objects", function() { + var spies = [], + spyRegistry = new j$.SpyRegistry({currentSpies: function() { return spies; }}), + originalFunction = function() {}, + subject = { spiedFunc: originalFunction }; + + spyRegistry.spyOn(subject, 'spiedFunc'); + spyRegistry.clearSpies(); + + expect(subject.spiedFunc).toBe(originalFunction); + }); + }); +}); diff --git a/spec/core/integration/CustomMatchersSpec.js b/spec/core/integration/CustomMatchersSpec.js index ee3e49a0..6c03d3d5 100644 --- a/spec/core/integration/CustomMatchersSpec.js +++ b/spec/core/integration/CustomMatchersSpec.js @@ -38,13 +38,13 @@ describe("Custom Matchers (Integration)", function() { }); it("passes the spec if the custom matcher passes", function(done) { - env.addMatchers({ - toBeReal: function() { - return { compare: function() { return { pass: true }; } }; - } - }); - env.it("spec using custom matcher", function() { + env.addMatchers({ + toBeReal: function() { + return { compare: function() { return { pass: true }; } }; + } + }); + env.expect(true).toBeReal(); }); @@ -57,16 +57,16 @@ describe("Custom Matchers (Integration)", function() { }); it("uses the negative compare function for a negative comparison, if provided", function(done) { - env.addMatchers({ - toBeReal: function() { - return { - compare: function() { return { pass: true }; }, - negativeCompare: function() { return { pass: true }; } - }; - } - }); - env.it("spec with custom negative comparison matcher", function() { + env.addMatchers({ + toBeReal: function() { + return { + compare: function() { return { pass: true }; }, + negativeCompare: function() { return { pass: true }; } + }; + } + }); + env.expect(true).not.toBeReal(); }); @@ -79,17 +79,17 @@ describe("Custom Matchers (Integration)", function() { }); it("generates messages with the same rules as built in matchers absent a custom message", function(done) { - env.addMatchers({ - toBeReal: function() { - return { - compare: function() { - return { pass: false }; + env.it('spec with an expectation', function() { + env.addMatchers({ + toBeReal: function() { + return { + compare: function() { + return { pass: false }; + } } } - } - }); + }); - env.it('spec with an expectation', function() { env.expect("a").toBeReal(); }); @@ -103,13 +103,14 @@ describe("Custom Matchers (Integration)", function() { it("passes the expected and actual arguments to the comparison function", function(done) { var argumentSpy = jasmine.createSpy("argument spy").and.returnValue({ pass: true }); - env.addMatchers({ - toBeReal: function() { - return { compare: argumentSpy }; - } - }); env.it('spec with an expectation', function () { + env.addMatchers({ + toBeReal: function() { + return { compare: argumentSpy }; + } + }); + env.expect(true).toBeReal(); env.expect(true).toBeReal("arg"); env.expect(true).toBeReal("arg1", "arg2"); @@ -130,12 +131,13 @@ describe("Custom Matchers (Integration)", function() { argumentSpy = jasmine.createSpy("argument spy").and.returnValue(matcherFactory), customEqualityFn = function() { return true; }; - env.addCustomEqualityTester(customEqualityFn); - env.addMatchers({ - toBeReal: argumentSpy - }); env.it("spec with expectation", function() { + env.addCustomEqualityTester(customEqualityFn); + env.addMatchers({ + toBeReal: argumentSpy + }); + env.expect(true).toBeReal(); }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 08c06ee2..6715d26a 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -326,30 +326,93 @@ describe("Env integration", function() { env.execute([secondSuite.id, firstSpec.id]); }); - it("Functions can be spied on and have their calls tracked", function () { + it("Functions can be spied on and have their calls tracked", function (done) { var env = new j$.Env(); var originalFunctionWasCalled = false; var subject = { spiedFunc: function() { originalFunctionWasCalled = true; } }; - var spy = env.spyOn(subject, 'spiedFunc'); + env.addReporter({jasmineDone: done}); - expect(subject.spiedFunc).toEqual(spy); + env.it("works with spies", function() { + var spy = env.spyOn(subject, 'spiedFunc'); - expect(subject.spiedFunc.calls.any()).toEqual(false); - expect(subject.spiedFunc.calls.count()).toEqual(0); + expect(subject.spiedFunc).toEqual(spy); + expect(subject.spiedFunc.calls.any()).toEqual(false); + expect(subject.spiedFunc.calls.count()).toEqual(0); - subject.spiedFunc('foo'); + subject.spiedFunc('foo'); - expect(subject.spiedFunc.calls.any()).toEqual(true); - expect(subject.spiedFunc.calls.count()).toEqual(1); - expect(subject.spiedFunc.calls.mostRecent().args).toEqual(['foo']); - expect(subject.spiedFunc.calls.mostRecent().object).toEqual(subject); - expect(originalFunctionWasCalled).toEqual(false); + expect(subject.spiedFunc.calls.any()).toEqual(true); + expect(subject.spiedFunc.calls.count()).toEqual(1); + expect(subject.spiedFunc.calls.mostRecent().args).toEqual(['foo']); + expect(subject.spiedFunc.calls.mostRecent().object).toEqual(subject); + expect(originalFunctionWasCalled).toEqual(false); - subject.spiedFunc('bar'); - expect(subject.spiedFunc.calls.count()).toEqual(2); - expect(subject.spiedFunc.calls.mostRecent().args).toEqual(['bar']); + subject.spiedFunc('bar'); + expect(subject.spiedFunc.calls.count()).toEqual(2); + expect(subject.spiedFunc.calls.mostRecent().args).toEqual(['bar']); + }); + + env.execute(); + }); + + it('removes all spies added in a spec after the spec is complete', function(done) { + var env = new j$.Env(), + originalFoo = function() {}, + testObj = { + foo: originalFoo + }, + firstSpec = jasmine.createSpy('firstSpec').and.callFake(function() { + env.spyOn(testObj, 'foo'); + }), + secondSpec = jasmine.createSpy('secondSpec').and.callFake(function() { + expect(testObj.foo).toBe(originalFoo); + }); + env.describe('test suite', function() { + env.it('spec 0', firstSpec); + env.it('spec 1', secondSpec); + }); + + var assertions = function() { + expect(firstSpec).toHaveBeenCalled(); + expect(secondSpec).toHaveBeenCalled(); + done(); + }; + + env.addReporter({ jasmineDone: assertions }); + + env.execute(); + }); + + it('removes all spies added in a suite after the suite is complete', function(done) { + var env = new j$.Env(), + originalFoo = function() {}, + testObj = { + foo: originalFoo + }; + + env.describe('test suite', function() { + env.beforeAll(function() { env.spyOn(testObj, 'foo');}) + + env.it('spec 0', function() { + expect(j$.isSpy(testObj.foo)).toBe(true); + }); + + env.it('spec 1', function() { + expect(j$.isSpy(testObj.foo)).toBe(true); + }); + }); + + env.describe('another suite', function() { + env.it('spec 2', function() { + expect(j$.isSpy(testObj.foo)).toBe(false); + }); + }); + + env.addReporter({ jasmineDone: done }); + + env.execute(); }); it("Mock clock can be installed and used in tests", function(done) { @@ -542,11 +605,7 @@ describe("Env integration", function() { it("Custom equality testers should be per spec", function(done) { var env = new j$.Env({global: { setTimeout: setTimeout }}), reporter = jasmine.createSpyObj('fakeReporter', [ - "jasmineStarted", "jasmineDone", - "suiteStarted", - "suiteDone", - "specStarted", "specDone" ]); @@ -576,30 +635,42 @@ describe("Env integration", function() { env.execute(); }); - it("Custom matchers should be per spec", function() { + it("Custom equality testers should be per suite", function(done) { var env = new j$.Env({global: { setTimeout: setTimeout }}), - matchers = { - toFoo: function() {} - }, reporter = jasmine.createSpyObj('fakeReporter', [ - "jasmineStarted", "jasmineDone", - "suiteStarted", - "suiteDone", - "specStarted", "specDone" ]); + reporter.jasmineDone.and.callFake(function() { + var firstSpecResult = reporter.specDone.calls.first().args[0], + secondSpecResult = reporter.specDone.calls.argsFor(0)[0], + thirdSpecResult = reporter.specDone.calls.mostRecent().args[0]; + + expect(firstSpecResult.status).toEqual("passed"); + expect(secondSpecResult.status).toEqual("passed"); + expect(thirdSpecResult.status).toEqual("failed"); + + done(); + }); + env.addReporter(reporter); - env.describe("testing custom matchers", function() { - env.it("with a custom matcher", function() { - env.addMatchers(matchers); - expect(env.expect().toFoo).toBeDefined(); + env.describe("testing custom equality testers", function() { + env.beforeAll(function() { env.addCustomEqualityTester(function(a, b) { return true; }); }); + + env.it("with a custom tester", function() { + env.expect("a").toEqual("b"); }); - env.it("without a custom matcher", function() { - expect(env.expect().toFoo).toBeUndefined(); + env.it("with the same custom tester", function() { + env.expect("a").toEqual("b"); + }); + }); + + env.describe("another suite", function() { + env.it("without the custom tester", function(){ + env.expect("a").toEqual("b"); }); }); @@ -609,11 +680,7 @@ describe("Env integration", function() { it("Custom equality testers for toContain should be per spec", function(done) { var env = new j$.Env({global: { setTimeout: setTimeout }}), reporter = jasmine.createSpyObj('fakeReporter', [ - "jasmineStarted", "jasmineDone", - "suiteStarted", - "suiteDone", - "specStarted", "specDone" ]); @@ -636,11 +703,104 @@ describe("Env integration", function() { }); env.it("without a custom tester", function() { - env.expect("a").toContain("b"); + env.expect(["a"]).toContain("b"); }); }); env.execute(); }); + + it("Custom equality testers for toContain should be per suite", function(done) { + var env = new j$.Env({global: { setTimeout: setTimeout }}), + reporter = jasmine.createSpyObj('fakeReporter', [ + "jasmineDone", + "specDone" + ]); + + reporter.jasmineDone.and.callFake(function() { + var firstSpecResult = reporter.specDone.calls.first().args[0], + secondSpecResult = reporter.specDone.calls.argsFor(1)[0], + thirdSpecResult = reporter.specDone.calls.mostRecent().args[0]; + + expect(firstSpecResult.status).toEqual("passed"); + expect(secondSpecResult.status).toEqual("passed"); + expect(thirdSpecResult.status).toEqual("failed"); + + done(); + }); + + env.addReporter(reporter); + + env.describe("testing custom equality testers", function() { + env.beforeAll(function() { env.addCustomEqualityTester(function(a, b) { return true; })}); + + env.it("with a custom tester", function() { + env.expect(["a"]).toContain("b"); + }); + + env.it("also with the custom tester", function() { + env.expect(["a"]).toContain("b"); + }); + }); + + env.describe("another suite", function() { + env.it("without the custom tester", function() { + env.expect(["a"]).toContain("b"); + }); + }); + + env.execute(); + }); + + it("Custom matchers should be per spec", function(done) { + var env = new j$.Env({global: { setTimeout: setTimeout }}), + matchers = { + toFoo: function() {} + }; + + env.describe("testing custom matchers", function() { + env.it("with a custom matcher", function() { + env.addMatchers(matchers); + expect(env.expect().toFoo).toBeDefined(); + }); + + env.it("without a custom matcher", function() { + expect(env.expect().toFoo).toBeUndefined(); + }); + }); + + env.addReporter({jasmineDone: done}); + + env.execute(); + }); + + it("Custom matchers should be per suite", function(done) { + var env = new j$.Env({global: { setTimeout: setTimeout }}), + matchers = { + toFoo: function() {} + }; + + env.describe("testing custom matchers", function() { + env.beforeAll(function() { env.addMatchers(matchers); }); + + env.it("with a custom matcher", function() { + expect(env.expect().toFoo).toBeDefined(); + }); + + env.it("with the same custom matcher", function() { + expect(env.expect().toFoo).toBeDefined(); + }); + }); + + env.describe("another suite", function() { + env.it("no longer has the custom matcher", function() { + expect(env.expect().toFoo).not.toBeDefined(); + }); + }); + + env.addReporter({jasmineDone: done}); + + env.execute(); + }); }); diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index 3adf8d06..bc155b4c 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -236,11 +236,14 @@ describe("jasmine spec running", function () { }); }); - suite.execute(function() { + var assertions = function() { expect(specInADisabledSuite).not.toHaveBeenCalled(); done(); - }); - }); + }; + + env.addReporter({jasmineDone: assertions}); + + env.execute(); it("should set all pending specs to pending when a suite is run", function(done) { var pendingSpec, diff --git a/src/core/Env.js b/src/core/Env.js index 3fb1447c..02410685 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -14,11 +14,19 @@ getJasmineRequireObj().Env = function(j$) { this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler(), new j$.MockDate(global)); var runnableLookupTable = {}; - - var spies = []; + var runnableResources = {}; var currentSpec = null; - var currentSuite = null; + var currentlyExecutingSuites = []; + var currentDeclarationSuite = null; + + var currentSuite = function() { + return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; + } + + var currentRunnable = function() { + return currentSpec || currentSuite(); + } var reporter = new j$.ReportDispatcher([ 'jasmineStarted', @@ -33,11 +41,15 @@ getJasmineRequireObj().Env = function(j$) { return true; }; - var equalityTesters = []; - - var customEqualityTesters = []; this.addCustomEqualityTester = function(tester) { - customEqualityTesters.push(tester); + runnableResources[currentRunnable().id].customEqualityTesters.push(tester); + }; + + this.addMatchers = function(matchersToAdd) { + var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { + customMatchers[matcherName] = matchersToAdd[matcherName]; + } }; j$.Expectation.addCoreMatchers(j$.matchers); @@ -55,7 +67,8 @@ getJasmineRequireObj().Env = function(j$) { var expectationFactory = function(actual, spec) { return j$.Expectation.Factory({ util: j$.matchersUtil, - customEqualityTesters: customEqualityTesters, + customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + customMatchers: runnableResources[spec.id].customMatchers, actual: actual, addExpectationResult: addExpectationResult }); @@ -65,9 +78,20 @@ getJasmineRequireObj().Env = function(j$) { } }; - var specStarted = function(spec) { - currentSpec = spec; - reporter.specStarted(spec.result); + var defaultResourcesForRunnable = function(id, parentRunnableId) { + var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; + + if(runnableResources[parentRunnableId]){ + resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); + resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers); + } + + runnableResources[id] = resources; + }; + + var clearResourcesForRunnable = function(id) { + spyRegistry.clearSpies(); + delete runnableResources[id]; }; var beforeFns = function(suite) { @@ -149,7 +173,8 @@ getJasmineRequireObj().Env = function(j$) { resultCallback: function() {} // TODO - hook this up }); runnableLookupTable[topSuite.id] = topSuite; - currentSuite = topSuite; + defaultResourcesForRunnable(topSuite.id); + currentDeclarationSuite = topSuite; this.topSuite = function() { return topSuite; @@ -175,36 +200,12 @@ getJasmineRequireObj().Env = function(j$) { reporter.addReporter(reporterToAdd); }; - this.addMatchers = function(matchersToAdd) { - j$.Expectation.addMatchers(matchersToAdd); - }; + var spyRegistry = new j$.SpyRegistry({currentSpies: function() { + return runnableResources[currentRunnable().id].spies; + }}); - this.spyOn = function(obj, methodName) { - if (j$.util.isUndefined(obj)) { - throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); - } - - if (j$.util.isUndefined(obj[methodName])) { - throw new Error(methodName + '() method does not exist'); - } - - if (obj[methodName] && j$.isSpy(obj[methodName])) { - //TODO?: should this return the current spy? Downside: may cause user confusion about spy state - throw new Error(methodName + ' has already been spied upon'); - } - - var spy = j$.createSpy(methodName, obj[methodName]); - - spies.push({ - spy: spy, - baseObj: obj, - methodName: methodName, - originalValue: obj[methodName] - }); - - obj[methodName] = spy; - - return spy; + this.spyOn = function() { + return spyRegistry.spyOn.apply(spyRegistry, arguments); }; var suiteFactory = function(description) { @@ -212,24 +213,32 @@ getJasmineRequireObj().Env = function(j$) { env: self, id: getNextSuiteId(), description: description, - parentSuite: currentSuite, + parentSuite: currentDeclarationSuite, queueRunner: queueRunnerFactory, onStart: suiteStarted, resultCallback: function(attrs) { + clearResourcesForRunnable(suite.id); + currentlyExecutingSuites.pop(); reporter.suiteDone(attrs); } }); runnableLookupTable[suite.id] = suite; return suite; + + function suiteStarted(suite) { + currentlyExecutingSuites.push(suite); + defaultResourcesForRunnable(suite.id, suite.parentSuite.id); + reporter.suiteStarted(suite.result); + } }; this.describe = function(description, specDefinitions) { var suite = suiteFactory(description); - var parentSuite = currentSuite; + var parentSuite = currentDeclarationSuite; parentSuite.addChild(suite); - currentSuite = suite; + currentDeclarationSuite = suite; var declarationError = null; try { @@ -244,7 +253,7 @@ getJasmineRequireObj().Env = function(j$) { }); } - currentSuite = parentSuite; + currentDeclarationSuite = parentSuite; return suite; }; @@ -284,30 +293,22 @@ getJasmineRequireObj().Env = function(j$) { return spec; - function removeAllSpies() { - for (var i = 0; i < spies.length; i++) { - var spyEntry = spies[i]; - spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; - } - spies = []; - } - function specResultCallback(result) { - removeAllSpies(); - j$.Expectation.resetMatchers(); - customEqualityTesters = []; + clearResourcesForRunnable(spec.id); currentSpec = null; reporter.specDone(result); } - }; - var suiteStarted = function(suite) { - reporter.suiteStarted(suite.result); + function specStarted(spec) { + currentSpec = spec; + defaultResourcesForRunnable(spec.id, suite.id); + reporter.specStarted(spec.result); + } }; this.it = function(description, fn) { - var spec = specFactory(description, fn, currentSuite); - currentSuite.addChild(spec); + var spec = specFactory(description, fn, currentDeclarationSuite); + currentDeclarationSuite.addChild(spec); return spec; }; @@ -322,19 +323,19 @@ getJasmineRequireObj().Env = function(j$) { }; this.beforeEach = function(beforeEachFunction) { - currentSuite.beforeEach({ fn: beforeEachFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + currentDeclarationSuite.beforeEach({ fn: beforeEachFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.beforeAll = function(beforeAllFunction) { - currentSuite.beforeAll({ fn: beforeAllFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + currentDeclarationSuite.beforeAll({ fn: beforeAllFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.afterEach = function(afterEachFunction) { - currentSuite.afterEach({ fn: afterEachFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + currentDeclarationSuite.afterEach({ fn: afterEachFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.afterAll = function(afterAllFunction) { - currentSuite.afterAll({ fn: afterAllFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + currentDeclarationSuite.afterAll({ fn: afterAllFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.pending = function() { diff --git a/src/core/Expectation.js b/src/core/Expectation.js index 626a5cec..ca0695e4 100644 --- a/src/core/Expectation.js +++ b/src/core/Expectation.js @@ -1,7 +1,5 @@ getJasmineRequireObj().Expectation = function() { - var matchers = {}; - function Expectation(options) { this.util = options.util || { buildFailureMessage: function() {} }; this.customEqualityTesters = options.customEqualityTesters || []; @@ -9,8 +7,9 @@ getJasmineRequireObj().Expectation = function() { this.addExpectationResult = options.addExpectationResult || function(){}; this.isNot = options.isNot; - for (var matcherName in matchers) { - this[matcherName] = matchers[matcherName]; + var customMatchers = options.customMatchers || {}; + for (var matcherName in customMatchers) { + this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]); } } @@ -77,19 +76,6 @@ getJasmineRequireObj().Expectation = function() { } }; - Expectation.addMatchers = function(matchersToAdd) { - for (var name in matchersToAdd) { - var matcher = matchersToAdd[name]; - matchers[name] = Expectation.prototype.wrapCompare(name, matcher); - } - }; - - Expectation.resetMatchers = function() { - for (var name in matchers) { - delete matchers[name]; - } - }; - Expectation.Factory = function(options) { options = options || {}; diff --git a/src/core/SpyRegistry.js b/src/core/SpyRegistry.js new file mode 100644 index 00000000..5532127a --- /dev/null +++ b/src/core/SpyRegistry.js @@ -0,0 +1,46 @@ +getJasmineRequireObj().SpyRegistry = function(j$) { + + function SpyRegistry(options) { + var options = options || {}; + var currentSpies = options.currentSpies || function() { return []; }; + + this.spyOn = function(obj, methodName) { + if (j$.util.isUndefined(obj)) { + throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); + } + + if (j$.util.isUndefined(obj[methodName])) { + throw new Error(methodName + '() method does not exist'); + } + + if (obj[methodName] && j$.isSpy(obj[methodName])) { + //TODO?: should this return the current spy? Downside: may cause user confusion about spy state + throw new Error(methodName + ' has already been spied upon'); + } + + var spy = j$.createSpy(methodName, obj[methodName]); + + currentSpies().push({ + spy: spy, + baseObj: obj, + methodName: methodName, + originalValue: obj[methodName] + }); + + obj[methodName] = spy; + + return spy; + }; + + this.clearSpies = function() { + var spies = currentSpies(); + for (var i = 0; i < spies.length; i++) { + var spyEntry = spies[i]; + spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; + } + }; + } + + return SpyRegistry; +}; + diff --git a/src/core/requireCore.js b/src/core/requireCore.js index 528489b6..466a7921 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -28,6 +28,7 @@ getJasmineRequireObj().core = function(jRequire) { j$.QueueRunner = jRequire.QueueRunner(j$); j$.ReportDispatcher = jRequire.ReportDispatcher(); j$.Spec = jRequire.Spec(j$); + j$.SpyRegistry = jRequire.SpyRegistry(j$); j$.SpyStrategy = jRequire.SpyStrategy(); j$.Suite = jRequire.Suite(); j$.Timer = jRequire.Timer(); diff --git a/src/core/util.js b/src/core/util.js index 5285b8d7..9c6f4878 100644 --- a/src/core/util.js +++ b/src/core/util.js @@ -30,5 +30,20 @@ getJasmineRequireObj().util = function() { return obj === void 0; }; + util.clone = function(obj) { + if (Object.prototype.toString.apply(obj) === "[object Array]") { + return obj.slice(); + } + + var cloned = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + cloned[prop] = obj[prop]; + } + } + + return cloned; + } + return util; }; From b2009521952a823a38122328b589d64d2e5b6643 Mon Sep 17 00:00:00 2001 From: Christopher Amavisca and Greg Cobb Date: Wed, 5 Mar 2014 14:00:57 -0800 Subject: [PATCH 05/40] Only clear resources if suite is not disabled [#66789174] --- lib/jasmine-core/jasmine.js | 457 ++++++++++++++++------- spec/core/integration/EnvSpec.js | 3 +- spec/core/integration/SpecRunningSpec.js | 23 ++ src/core/Env.js | 10 +- src/core/MockDate.js | 2 +- src/core/SpyRegistry.js | 3 +- src/core/util.js | 4 +- 7 files changed, 363 insertions(+), 139 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 5670025f..14bd7ed8 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -36,6 +36,7 @@ getJasmineRequireObj().core = function(jRequire) { j$.util = jRequire.util(); j$.Any = jRequire.Any(); j$.CallTracker = jRequire.CallTracker(); + j$.MockDate = jRequire.MockDate(); j$.Clock = jRequire.Clock(); j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); j$.Env = jRequire.Env(j$); @@ -49,6 +50,7 @@ getJasmineRequireObj().core = function(jRequire) { j$.QueueRunner = jRequire.QueueRunner(j$); j$.ReportDispatcher = jRequire.ReportDispatcher(); j$.Spec = jRequire.Spec(j$); + j$.SpyRegistry = jRequire.SpyRegistry(j$); j$.SpyStrategy = jRequire.SpyStrategy(); j$.Suite = jRequire.Suite(); j$.Timer = jRequire.Timer(); @@ -223,6 +225,21 @@ getJasmineRequireObj().util = function() { return obj === void 0; }; + util.clone = function(obj) { + if (Object.prototype.toString.apply(obj) === '[object Array]') { + return obj.slice(); + } + + var cloned = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + cloned[prop] = obj[prop]; + } + } + + return cloned; + }; + return util; }; @@ -232,9 +249,10 @@ getJasmineRequireObj().Spec = function(j$) { this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; this.description = attrs.description || ''; - this.fn = attrs.fn; + this.queueableFn = attrs.queueableFn; this.beforeFns = attrs.beforeFns || function() { return []; }; this.afterFns = attrs.afterFns || function() { return []; }; + this.userContext = attrs.userContext || function() { return {}; }; this.onStart = attrs.onStart || function() {}; this.exceptionFormatter = attrs.exceptionFormatter || function() {}; this.getSpecName = attrs.getSpecName || function() { return ''; }; @@ -242,7 +260,7 @@ getJasmineRequireObj().Spec = function(j$) { this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; - if (!this.fn) { + if (!this.queueableFn.fn) { this.pend(); } @@ -275,30 +293,15 @@ getJasmineRequireObj().Spec = function(j$) { return; } - var allFns = this.beforeFns().concat(this.fn).concat(this.afterFns()); + var allFns = this.beforeFns().concat(this.queueableFn).concat(this.afterFns()); this.queueRunnerFactory({ - fns: allFns, - onException: onException, + queueableFns: allFns, + onException: function() { self.onException.apply(self, arguments); }, onComplete: complete, - enforceTimeout: function() { return true; } + userContext: this.userContext() }); - function onException(e) { - if (Spec.isPendingSpecException(e)) { - self.pend(); - return; - } - - self.addExpectationResult(false, { - matcherName: '', - passed: false, - expected: '', - actual: '', - error: e - }); - } - function complete() { self.result.status = self.status(); self.resultCallback(self.result); @@ -309,6 +312,21 @@ getJasmineRequireObj().Spec = function(j$) { } }; + Spec.prototype.onException = function onException(e) { + if (Spec.isPendingSpecException(e)) { + this.pend(); + return; + } + + this.addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: e + }); + }; + Spec.prototype.disable = function() { this.disabled = true; }; @@ -333,6 +351,10 @@ getJasmineRequireObj().Spec = function(j$) { } }; + Spec.prototype.isExecutable = function() { + return !this.disabled && !this.markedPending; + }; + Spec.prototype.getFullName = function() { return this.getSpecName(this); }; @@ -363,14 +385,22 @@ getJasmineRequireObj().Env = function(j$) { var realSetTimeout = j$.getGlobal().setTimeout; var realClearTimeout = j$.getGlobal().clearTimeout; - this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler()); + this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler(), new j$.MockDate(global)); var runnableLookupTable = {}; - - var spies = []; + var runnableResources = {}; var currentSpec = null; - var currentSuite = null; + var currentlyExecutingSuites = []; + var currentDeclarationSuite = null; + + var currentSuite = function() { + return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; + }; + + var currentRunnable = function() { + return currentSpec || currentSuite(); + }; var reporter = new j$.ReportDispatcher([ 'jasmineStarted', @@ -385,11 +415,15 @@ getJasmineRequireObj().Env = function(j$) { return true; }; - var equalityTesters = []; - - var customEqualityTesters = []; this.addCustomEqualityTester = function(tester) { - customEqualityTesters.push(tester); + runnableResources[currentRunnable().id].customEqualityTesters.push(tester); + }; + + this.addMatchers = function(matchersToAdd) { + var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { + customMatchers[matcherName] = matchersToAdd[matcherName]; + } }; j$.Expectation.addCoreMatchers(j$.matchers); @@ -407,7 +441,8 @@ getJasmineRequireObj().Env = function(j$) { var expectationFactory = function(actual, spec) { return j$.Expectation.Factory({ util: j$.matchersUtil, - customEqualityTesters: customEqualityTesters, + customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + customMatchers: runnableResources[spec.id].customMatchers, actual: actual, addExpectationResult: addExpectationResult }); @@ -417,9 +452,20 @@ getJasmineRequireObj().Env = function(j$) { } }; - var specStarted = function(spec) { - currentSpec = spec; - reporter.specStarted(spec.result); + var defaultResourcesForRunnable = function(id, parentRunnableId) { + var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; + + if(runnableResources[parentRunnableId]){ + resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); + resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers); + } + + runnableResources[id] = resources; + }; + + var clearResourcesForRunnable = function(id) { + spyRegistry.clearSpies(); + delete runnableResources[id]; }; var beforeFns = function(suite) { @@ -501,7 +547,8 @@ getJasmineRequireObj().Env = function(j$) { resultCallback: function() {} // TODO - hook this up }); runnableLookupTable[topSuite.id] = topSuite; - currentSuite = topSuite; + defaultResourcesForRunnable(topSuite.id); + currentDeclarationSuite = topSuite; this.topSuite = function() { return topSuite; @@ -513,50 +560,26 @@ getJasmineRequireObj().Env = function(j$) { var allFns = []; for(var i = 0; i < runnablesToRun.length; i++) { var runnable = runnableLookupTable[runnablesToRun[i]]; - allFns.push((function(runnable) { return function(done) { runnable.execute(done); }; })(runnable)); + allFns.push((function(runnable) { return { fn: function(done) { runnable.execute(done); } }; })(runnable)); } reporter.jasmineStarted({ totalSpecsDefined: totalSpecsDefined }); - queueRunnerFactory({fns: allFns, onComplete: reporter.jasmineDone}); + queueRunnerFactory({queueableFns: allFns, onComplete: reporter.jasmineDone}); }; this.addReporter = function(reporterToAdd) { reporter.addReporter(reporterToAdd); }; - this.addMatchers = function(matchersToAdd) { - j$.Expectation.addMatchers(matchersToAdd); - }; + var spyRegistry = new j$.SpyRegistry({currentSpies: function() { + return runnableResources[currentRunnable().id].spies; + }}); - this.spyOn = function(obj, methodName) { - if (j$.util.isUndefined(obj)) { - throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); - } - - if (j$.util.isUndefined(obj[methodName])) { - throw new Error(methodName + '() method does not exist'); - } - - if (obj[methodName] && j$.isSpy(obj[methodName])) { - //TODO?: should this return the current spy? Downside: may cause user confusion about spy state - throw new Error(methodName + ' has already been spied upon'); - } - - var spy = j$.createSpy(methodName, obj[methodName]); - - spies.push({ - spy: spy, - baseObj: obj, - methodName: methodName, - originalValue: obj[methodName] - }); - - obj[methodName] = spy; - - return spy; + this.spyOn = function() { + return spyRegistry.spyOn.apply(spyRegistry, arguments); }; var suiteFactory = function(description) { @@ -564,24 +587,34 @@ getJasmineRequireObj().Env = function(j$) { env: self, id: getNextSuiteId(), description: description, - parentSuite: currentSuite, + parentSuite: currentDeclarationSuite, queueRunner: queueRunnerFactory, onStart: suiteStarted, resultCallback: function(attrs) { + if (!suite.disabled) { + clearResourcesForRunnable(suite.id); + currentlyExecutingSuites.pop(); + } reporter.suiteDone(attrs); } }); runnableLookupTable[suite.id] = suite; return suite; + + function suiteStarted(suite) { + currentlyExecutingSuites.push(suite); + defaultResourcesForRunnable(suite.id, suite.parentSuite.id); + reporter.suiteStarted(suite.result); + } }; this.describe = function(description, specDefinitions) { var suite = suiteFactory(description); - var parentSuite = currentSuite; + var parentSuite = currentDeclarationSuite; parentSuite.addChild(suite); - currentSuite = suite; + currentDeclarationSuite = suite; var declarationError = null; try { @@ -596,7 +629,7 @@ getJasmineRequireObj().Env = function(j$) { }); } - currentSuite = parentSuite; + currentDeclarationSuite = parentSuite; return suite; }; @@ -624,7 +657,8 @@ getJasmineRequireObj().Env = function(j$) { description: description, expectationResultFactory: expectationResultFactory, queueRunnerFactory: queueRunnerFactory, - fn: fn + userContext: function() { return suite.clonedSharedUserContext(); }, + queueableFn: { fn: fn, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } } }); runnableLookupTable[spec.id] = spec; @@ -635,30 +669,22 @@ getJasmineRequireObj().Env = function(j$) { return spec; - function removeAllSpies() { - for (var i = 0; i < spies.length; i++) { - var spyEntry = spies[i]; - spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; - } - spies = []; - } - function specResultCallback(result) { - removeAllSpies(); - j$.Expectation.resetMatchers(); - customEqualityTesters = []; + clearResourcesForRunnable(spec.id); currentSpec = null; reporter.specDone(result); } - }; - var suiteStarted = function(suite) { - reporter.suiteStarted(suite.result); + function specStarted(spec) { + currentSpec = spec; + defaultResourcesForRunnable(spec.id, suite.id); + reporter.specStarted(spec.result); + } }; this.it = function(description, fn) { - var spec = specFactory(description, fn, currentSuite); - currentSuite.addChild(spec); + var spec = specFactory(description, fn, currentDeclarationSuite); + currentDeclarationSuite.addChild(spec); return spec; }; @@ -673,11 +699,19 @@ getJasmineRequireObj().Env = function(j$) { }; this.beforeEach = function(beforeEachFunction) { - currentSuite.beforeEach(beforeEachFunction); + currentDeclarationSuite.beforeEach({ fn: beforeEachFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + }; + + this.beforeAll = function(beforeAllFunction) { + currentDeclarationSuite.beforeAll({ fn: beforeAllFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.afterEach = function(afterEachFunction) { - currentSuite.afterEach(afterEachFunction); + currentDeclarationSuite.afterEach({ fn: afterEachFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + }; + + this.afterAll = function(afterAllFunction) { + currentDeclarationSuite.afterAll({ fn: afterAllFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.pending = function() { @@ -851,7 +885,7 @@ getJasmineRequireObj().CallTracker = function() { }; getJasmineRequireObj().Clock = function() { - function Clock(global, delayedFunctionScheduler) { + function Clock(global, delayedFunctionScheduler, mockDate) { var self = this, realTimingFunctions = { setTimeout: global.setTimeout, @@ -868,19 +902,28 @@ getJasmineRequireObj().Clock = function() { installed = false, timer; + self.install = function() { replace(global, fakeTimingFunctions); timer = fakeTimingFunctions; installed = true; + + return self; }; self.uninstall = function() { delayedFunctionScheduler.reset(); + mockDate.uninstall(); replace(global, realTimingFunctions); + timer = realTimingFunctions; installed = false; }; + self.mockDate = function(initialDate) { + mockDate.install(initialDate); + }; + self.setTimeout = function(fn, delay, params) { if (legacyIE()) { if (arguments.length > 2) { @@ -911,6 +954,7 @@ getJasmineRequireObj().Clock = function() { self.tick = function(millis) { if (installed) { + mockDate.tick(millis); delayedFunctionScheduler.tick(millis); } else { throw new Error('Mock clock is not installed, use jasmine.clock().install()'); @@ -1132,8 +1176,6 @@ getJasmineRequireObj().ExceptionFormatter = function() { getJasmineRequireObj().Expectation = function() { - var matchers = {}; - function Expectation(options) { this.util = options.util || { buildFailureMessage: function() {} }; this.customEqualityTesters = options.customEqualityTesters || []; @@ -1141,8 +1183,9 @@ getJasmineRequireObj().Expectation = function() { this.addExpectationResult = options.addExpectationResult || function(){}; this.isNot = options.isNot; - for (var matcherName in matchers) { - this[matcherName] = matchers[matcherName]; + var customMatchers = options.customMatchers || {}; + for (var matcherName in customMatchers) { + this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]); } } @@ -1209,19 +1252,6 @@ getJasmineRequireObj().Expectation = function() { } }; - Expectation.addMatchers = function(matchersToAdd) { - for (var name in matchersToAdd) { - var matcher = matchersToAdd[name]; - matchers[name] = Expectation.prototype.wrapCompare(name, matcher); - } - }; - - Expectation.resetMatchers = function() { - for (var name in matchers) { - delete matchers[name]; - } - }; - Expectation.Factory = function(options) { options = options || {}; @@ -1284,6 +1314,73 @@ getJasmineRequireObj().buildExpectationResult = function() { return buildExpectationResult; }; +getJasmineRequireObj().MockDate = function() { + function MockDate(global) { + var self = this; + var currentTime = 0; + + if (!global || !global.Date) { + self.install = function() {}; + self.tick = function() {}; + self.uninstall = function() {}; + return self; + } + + var GlobalDate = global.Date; + + self.install = function(mockDate) { + if (mockDate instanceof GlobalDate) { + currentTime = mockDate.getTime(); + } else { + currentTime = new GlobalDate().getTime(); + } + + global.Date = FakeDate; + }; + + self.tick = function(millis) { + millis = millis || 0; + currentTime = currentTime + millis; + }; + + self.uninstall = function() { + currentTime = 0; + global.Date = GlobalDate; + }; + + createDateProperties(); + + return self; + + function FakeDate() { + if (arguments.length === 0) { + return new GlobalDate(currentTime); + } else { + return new GlobalDate(arguments[0], arguments[1], arguments[2], + arguments[3], arguments[4], arguments[5], arguments[6]); + } + } + + function createDateProperties() { + + FakeDate.now = function() { + if (GlobalDate.now) { + return currentTime; + } else { + throw new Error('Browser does not support Date.now()'); + } + }; + + FakeDate.toSource = GlobalDate.toSource; + FakeDate.toString = GlobalDate.toString; + FakeDate.parse = GlobalDate.parse; + FakeDate.UTC = GlobalDate.UTC; + } + } + + return MockDate; +}; + getJasmineRequireObj().ObjectContaining = function(j$) { function ObjectContaining(sample) { @@ -1467,31 +1564,30 @@ getJasmineRequireObj().QueueRunner = function(j$) { } function QueueRunner(attrs) { - this.fns = attrs.fns || []; + this.queueableFns = attrs.queueableFns || []; this.onComplete = attrs.onComplete || function() {}; this.clearStack = attrs.clearStack || function(fn) {fn();}; this.onException = attrs.onException || function() {}; this.catchException = attrs.catchException || function() { return true; }; - this.enforceTimeout = attrs.enforceTimeout || function() { return false; }; - this.userContext = {}; + this.userContext = attrs.userContext || {}; this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; } QueueRunner.prototype.execute = function() { - this.run(this.fns, 0); + this.run(this.queueableFns, 0); }; - QueueRunner.prototype.run = function(fns, recursiveIndex) { - var length = fns.length, + QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { + var length = queueableFns.length, self = this, iterativeIndex; for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { - var fn = fns[iterativeIndex]; - if (fn.length > 0) { - return attemptAsync(fn); + var queueableFn = queueableFns[iterativeIndex]; + if (queueableFn.fn.length > 0) { + return attemptAsync(queueableFn); } else { - attemptSync(fn); + attemptSync(queueableFn); } } @@ -1501,33 +1597,33 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.clearStack(this.onComplete); } - function attemptSync(fn) { + function attemptSync(queueableFn) { try { - fn.call(self.userContext); + queueableFn.fn.call(self.userContext); } catch (e) { handleException(e); } } - function attemptAsync(fn) { + function attemptAsync(queueableFn) { var clearTimeout = function () { Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeoutId]]); }, next = once(function () { clearTimeout(timeoutId); - self.run(fns, iterativeIndex + 1); + self.run(queueableFns, iterativeIndex + 1); }), timeoutId; - if (self.enforceTimeout()) { + 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.')); next(); - }, j$.DEFAULT_TIMEOUT_INTERVAL]]); + }, queueableFn.timeout()]]); } try { - fn.call(self.userContext, next); + queueableFn.fn.call(self.userContext, next); } catch (e) { handleException(e); next(); @@ -1583,6 +1679,52 @@ getJasmineRequireObj().ReportDispatcher = function() { }; +getJasmineRequireObj().SpyRegistry = function(j$) { + + function SpyRegistry(options) { + options = options || {}; + var currentSpies = options.currentSpies || function() { return []; }; + + this.spyOn = function(obj, methodName) { + if (j$.util.isUndefined(obj)) { + throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); + } + + if (j$.util.isUndefined(obj[methodName])) { + throw new Error(methodName + '() method does not exist'); + } + + if (obj[methodName] && j$.isSpy(obj[methodName])) { + //TODO?: should this return the current spy? Downside: may cause user confusion about spy state + throw new Error(methodName + ' has already been spied upon'); + } + + var spy = j$.createSpy(methodName, obj[methodName]); + + currentSpies().push({ + spy: spy, + baseObj: obj, + methodName: methodName, + originalValue: obj[methodName] + }); + + obj[methodName] = spy; + + return spy; + }; + + this.clearSpies = function() { + var spies = currentSpies(); + for (var i = 0; i < spies.length; i++) { + var spyEntry = spies[i]; + spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; + } + }; + } + + return SpyRegistry; +}; + getJasmineRequireObj().SpyStrategy = function() { function SpyStrategy(options) { @@ -1647,6 +1789,8 @@ getJasmineRequireObj().Suite = function() { this.beforeFns = []; this.afterFns = []; + this.beforeAllFns = []; + this.afterAllFns = []; this.queueRunner = attrs.queueRunner || function() {}; this.disabled = false; @@ -1678,10 +1822,18 @@ getJasmineRequireObj().Suite = function() { this.beforeFns.unshift(fn); }; + Suite.prototype.beforeAll = function(fn) { + this.beforeAllFns.push(fn); + }; + Suite.prototype.afterEach = function(fn) { this.afterFns.unshift(fn); }; + Suite.prototype.afterAll = function(fn) { + this.afterAllFns.push(fn); + }; + Suite.prototype.addChild = function(child) { this.children.push(child); }; @@ -1695,15 +1847,23 @@ getJasmineRequireObj().Suite = function() { var allFns = []; - for (var i = 0; i < this.children.length; i++) { - allFns.push(wrapChildAsAsync(this.children[i])); + if (this.isExecutable()) { + allFns = this.beforeAllFns; + + for (var i = 0; i < this.children.length; i++) { + allFns.push(wrapChildAsAsync(this.children[i])); + } + + allFns = allFns.concat(this.afterAllFns); } this.onStart(this); this.queueRunner({ - fns: allFns, - onComplete: complete + queueableFns: allFns, + onComplete: complete, + userContext: this.sharedUserContext(), + onException: function() { self.onException.apply(self, arguments); } }); function complete() { @@ -1715,10 +1875,51 @@ getJasmineRequireObj().Suite = function() { } function wrapChildAsAsync(child) { - return function(done) { child.execute(done); }; + return { fn: function(done) { child.execute(done); } }; } }; + Suite.prototype.isExecutable = function() { + var foundActive = false; + for(var i = 0; i < this.children.length; i++) { + if(this.children[i].isExecutable()) { + foundActive = true; + break; + } + } + return foundActive; + }; + + Suite.prototype.sharedUserContext = function() { + if (!this.sharedContext) { + this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {}; + } + + return this.sharedContext; + }; + + Suite.prototype.clonedSharedUserContext = function() { + return clone(this.sharedUserContext()); + }; + + Suite.prototype.onException = function() { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.onException.apply(child, arguments); + } + }; + + function clone(obj) { + var clonedObj = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + clonedObj[prop] = obj[prop]; + } + } + + return clonedObj; + } + return Suite; }; diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 6715d26a..3dd0a2fc 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -80,7 +80,7 @@ describe("Env integration", function() { env.describe("Outer suite", function() { env.it("an outer spec", function() { - calls.push('an outer spec') + calls.push('an outer spec'); }); env.describe("Inner suite", function() { env.it("an inner spec", function() { @@ -803,4 +803,3 @@ describe("Env integration", function() { env.execute(); }); }); - diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index bc155b4c..5d629465 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -244,6 +244,29 @@ describe("jasmine spec running", function () { env.addReporter({jasmineDone: assertions}); env.execute(); + }); + + it("should allow top level suites to be disabled", function() { + var specInADisabledSuite = jasmine.createSpy("specInADisabledSuite"), + otherSpec = jasmine.createSpy("otherSpec"); + + env.xdescribe('A disabled suite', function() { + env.it('spec inside a disabled suite', specInADisabledSuite); + }); + env.describe('Another suite', function() { + env.it('another spec', otherSpec); + }); + + var assertions = function() { + expect(specInADisabledSuite).not.toHaveBeenCalled(); + expect(otherSpec).toHaveBeenCalled(); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.execute(); + }); it("should set all pending specs to pending when a suite is run", function(done) { var pendingSpec, diff --git a/src/core/Env.js b/src/core/Env.js index 02410685..dffe97ad 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -22,11 +22,11 @@ getJasmineRequireObj().Env = function(j$) { var currentSuite = function() { return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; - } + }; var currentRunnable = function() { return currentSpec || currentSuite(); - } + }; var reporter = new j$.ReportDispatcher([ 'jasmineStarted', @@ -217,8 +217,10 @@ getJasmineRequireObj().Env = function(j$) { queueRunner: queueRunnerFactory, onStart: suiteStarted, resultCallback: function(attrs) { - clearResourcesForRunnable(suite.id); - currentlyExecutingSuites.pop(); + if (!suite.disabled) { + clearResourcesForRunnable(suite.id); + currentlyExecutingSuites.pop(); + } reporter.suiteDone(attrs); } }); diff --git a/src/core/MockDate.js b/src/core/MockDate.js index bab3b751..9741033e 100644 --- a/src/core/MockDate.js +++ b/src/core/MockDate.js @@ -51,7 +51,7 @@ getJasmineRequireObj().MockDate = function() { if (GlobalDate.now) { return currentTime; } else { - throw new Error("Browser does not support Date.now()"); + throw new Error('Browser does not support Date.now()'); } }; diff --git a/src/core/SpyRegistry.js b/src/core/SpyRegistry.js index 5532127a..776393ed 100644 --- a/src/core/SpyRegistry.js +++ b/src/core/SpyRegistry.js @@ -1,7 +1,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { function SpyRegistry(options) { - var options = options || {}; + options = options || {}; var currentSpies = options.currentSpies || function() { return []; }; this.spyOn = function(obj, methodName) { @@ -43,4 +43,3 @@ getJasmineRequireObj().SpyRegistry = function(j$) { return SpyRegistry; }; - diff --git a/src/core/util.js b/src/core/util.js index 9c6f4878..e987448e 100644 --- a/src/core/util.js +++ b/src/core/util.js @@ -31,7 +31,7 @@ getJasmineRequireObj().util = function() { }; util.clone = function(obj) { - if (Object.prototype.toString.apply(obj) === "[object Array]") { + if (Object.prototype.toString.apply(obj) === '[object Array]') { return obj.slice(); } @@ -43,7 +43,7 @@ getJasmineRequireObj().util = function() { } return cloned; - } + }; return util; }; From a2ac5ef3b6064fe7b613d9548990436cffdd9419 Mon Sep 17 00:00:00 2001 From: Christopher Amavisca and Greg Cobb Date: Wed, 5 Mar 2014 14:27:19 -0800 Subject: [PATCH 06/40] Throw error if you define a spy outside of a runnable [#66789174] --- lib/jasmine-core/jasmine.js | 3 +++ spec/core/integration/EnvSpec.js | 23 +++++++++++++++++++++++ src/core/Env.js | 3 +++ 3 files changed, 29 insertions(+) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 14bd7ed8..fba0f67a 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -575,6 +575,9 @@ getJasmineRequireObj().Env = function(j$) { }; var spyRegistry = new j$.SpyRegistry({currentSpies: function() { + if(!currentRunnable()) { + throw new Error('Spies must be created in a before function or a spec'); + } return runnableResources[currentRunnable().id].spies; }}); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 3dd0a2fc..182edd78 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -802,4 +802,27 @@ describe("Env integration", function() { env.execute(); }); + + it('throws an exception if you try to create a spy outside of a runnable', function (done) { + var env = new j$.Env(), + obj = {fn: function () {}}, + exception; + + env.describe("a suite", function () { + try { + env.spyOn(obj, 'fn'); + } catch(e) { + exception = e; + } + }); + + var assertions = function() { + expect(exception.message).toBe('Spies must be created in a before function or a spec'); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.execute(); + }); }); diff --git a/src/core/Env.js b/src/core/Env.js index dffe97ad..111c2519 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -201,6 +201,9 @@ getJasmineRequireObj().Env = function(j$) { }; var spyRegistry = new j$.SpyRegistry({currentSpies: function() { + if(!currentRunnable()) { + throw new Error('Spies must be created in a before function or a spec'); + } return runnableResources[currentRunnable().id].spies; }}); From a9e0112a9bc616b7cba3730328e07482a316080b Mon Sep 17 00:00:00 2001 From: Christopher Amavisca and Greg Cobb Date: Wed, 5 Mar 2014 14:37:17 -0800 Subject: [PATCH 07/40] Throw error if you add a custom matcher or equality outside of a runnable [#66789174] --- lib/jasmine-core/jasmine.js | 6 ++++ spec/core/integration/EnvSpec.js | 47 ++++++++++++++++++++++++++++++++ src/core/Env.js | 6 ++++ 3 files changed, 59 insertions(+) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index fba0f67a..a86f54f6 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -416,10 +416,16 @@ getJasmineRequireObj().Env = function(j$) { }; this.addCustomEqualityTester = function(tester) { + if(!currentRunnable()) { + throw new Error('Custom Equalities must be added in a before function or a spec'); + } runnableResources[currentRunnable().id].customEqualityTesters.push(tester); }; this.addMatchers = function(matchersToAdd) { + if(!currentRunnable()) { + throw new Error('Matchers must be added in a before function or a spec'); + } var customMatchers = runnableResources[currentRunnable().id].customMatchers; for (var matcherName in matchersToAdd) { customMatchers[matcherName] = matchersToAdd[matcherName]; diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 182edd78..a824db22 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -825,4 +825,51 @@ describe("Env integration", function() { env.execute(); }); + + it('throws an exception if you try to add a matcher outside of a runnable', function (done) { + var env = new j$.Env(), + obj = {fn: function () {}}, + exception; + + env.describe("a suite", function () { + try { + env.addMatchers({myMatcher: function(actual,expected){return false;}}); + } catch(e) { + exception = e; + } + }); + + var assertions = function() { + expect(exception.message).toBe('Matchers must be added in a before function or a spec'); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.execute(); + }); + + it('throws an exception if you try to add a custom equality outside of a runnable', function (done) { + var env = new j$.Env(), + obj = {fn: function () {}}, + exception; + + env.describe("a suite", function () { + try { + env.addCustomEqualityTester(function(first, second) {return true;}); + } catch(e) { + exception = e; + } + }); + + var assertions = function() { + expect(exception.message).toBe('Custom Equalities must be added in a before function or a spec'); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.execute(); + }); + }); diff --git a/src/core/Env.js b/src/core/Env.js index 111c2519..f174cf22 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -42,10 +42,16 @@ getJasmineRequireObj().Env = function(j$) { }; this.addCustomEqualityTester = function(tester) { + if(!currentRunnable()) { + throw new Error('Custom Equalities must be added in a before function or a spec'); + } runnableResources[currentRunnable().id].customEqualityTesters.push(tester); }; this.addMatchers = function(matchersToAdd) { + if(!currentRunnable()) { + throw new Error('Matchers must be added in a before function or a spec'); + } var customMatchers = runnableResources[currentRunnable().id].customMatchers; for (var matcherName in matchersToAdd) { customMatchers[matcherName] = matchersToAdd[matcherName]; From a3c3505086366960c8f07148c66e50b45876b449 Mon Sep 17 00:00:00 2001 From: Christopher Amavisca and Greg Cobb Date: Wed, 5 Mar 2014 16:27:58 -0800 Subject: [PATCH 08/40] beforeAll can have expectations and passes expectation failures to its children [#66789174] --- lib/jasmine-core/boot.js | 8 +++++++ lib/jasmine-core/boot/boot.js | 8 +++++++ lib/jasmine-core/jasmine.js | 15 +++++++++++- spec/core/integration/EnvSpec.js | 39 +++++++++++++++++++++++++++++++- src/core/Env.js | 3 ++- src/core/Suite.js | 12 ++++++++++ 6 files changed, 82 insertions(+), 3 deletions(-) diff --git a/lib/jasmine-core/boot.js b/lib/jasmine-core/boot.js index cf93dda2..1d6820dc 100644 --- a/lib/jasmine-core/boot.js +++ b/lib/jasmine-core/boot.js @@ -79,6 +79,14 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. return env.afterEach(afterEachFunction); }, + beforeAll: function(beforeAllFunction) { + return env.beforeAll(beforeAllFunction); + }, + + afterAll: function(afterAllFunction) { + return env.afterAll(afterAllFunction); + }, + expect: function(actual) { return env.expect(actual); }, diff --git a/lib/jasmine-core/boot/boot.js b/lib/jasmine-core/boot/boot.js index ec8baa0a..32bfb076 100644 --- a/lib/jasmine-core/boot/boot.js +++ b/lib/jasmine-core/boot/boot.js @@ -57,6 +57,14 @@ return env.afterEach(afterEachFunction); }, + beforeAll: function(beforeAllFunction) { + return env.beforeAll(beforeAllFunction); + }, + + afterAll: function(afterAllFunction) { + return env.afterAll(afterAllFunction); + }, + expect: function(actual) { return env.expect(actual); }, diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index a86f54f6..27ccdcf9 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -599,6 +599,7 @@ getJasmineRequireObj().Env = function(j$) { parentSuite: currentDeclarationSuite, queueRunner: queueRunnerFactory, onStart: suiteStarted, + expectationFactory: expectationFactory, resultCallback: function(attrs) { if (!suite.disabled) { clearResourcesForRunnable(suite.id); @@ -704,7 +705,7 @@ getJasmineRequireObj().Env = function(j$) { }; this.expect = function(actual) { - return currentSpec.expect(actual); + return currentRunnable().expect(actual); }; this.beforeEach = function(beforeEachFunction) { @@ -1795,6 +1796,7 @@ getJasmineRequireObj().Suite = function() { this.onStart = attrs.onStart || function() {}; this.resultCallback = attrs.resultCallback || function() {}; this.clearStack = attrs.clearStack || function(fn) {fn();}; + this.expectationFactory = attrs.expectationFactory; this.beforeFns = []; this.afterFns = []; @@ -1813,6 +1815,10 @@ getJasmineRequireObj().Suite = function() { }; } + Suite.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + Suite.prototype.getFullName = function() { var fullName = this.description; for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { @@ -1918,6 +1924,13 @@ getJasmineRequireObj().Suite = function() { } }; + Suite.prototype.addExpectationResult = function () { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.addExpectationResult.apply(child, arguments); + } + }; + function clone(obj) { var clonedObj = {}; for (var prop in obj) { diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index a824db22..6f8e0dbb 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -290,6 +290,44 @@ describe("Env integration", function() { env.execute(); }); + it("fails all underlying specs when the beforeAll fails", function (done) { + var env = new j$.Env(), + reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone" ]); + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.specDone.calls.count()).toEqual(2); + + expect(reporter.specDone.calls.argsFor(0)[0]) + .toEqual(jasmine.objectContaining({status: 'failed'})); + expect(reporter.specDone.calls.argsFor(0)[0].failedExpectations[0].message) + .toEqual("Expected 1 to be 2."); + + expect(reporter.specDone.calls.argsFor(1)[0]) + .toEqual(jasmine.objectContaining({status: 'failed'})); + expect(reporter.specDone.calls.argsFor(1)[0].failedExpectations[0].message) + .toEqual("Expected 1 to be 2."); + done(); + }); + + env.addReporter(reporter); + + env.describe('A suite', function(){ + env.beforeAll(function() { + env.expect(1).toBe(2); + }); + + env.it("spec that will be failed", function() { + }); + + env.describe("nesting", function() { + env.it("another spec to fail", function() { + }); + }); + }); + + env.execute(); + }); + it("Allows specifying which specs and suites to run", function(done) { var env = new j$.Env(), calls = [], @@ -871,5 +909,4 @@ describe("Env integration", function() { env.execute(); }); - }); diff --git a/src/core/Env.js b/src/core/Env.js index f174cf22..fc2b6e80 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -225,6 +225,7 @@ getJasmineRequireObj().Env = function(j$) { parentSuite: currentDeclarationSuite, queueRunner: queueRunnerFactory, onStart: suiteStarted, + expectationFactory: expectationFactory, resultCallback: function(attrs) { if (!suite.disabled) { clearResourcesForRunnable(suite.id); @@ -330,7 +331,7 @@ getJasmineRequireObj().Env = function(j$) { }; this.expect = function(actual) { - return currentSpec.expect(actual); + return currentRunnable().expect(actual); }; this.beforeEach = function(beforeEachFunction) { diff --git a/src/core/Suite.js b/src/core/Suite.js index 29d2d85f..43d02430 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -7,6 +7,7 @@ getJasmineRequireObj().Suite = function() { this.onStart = attrs.onStart || function() {}; this.resultCallback = attrs.resultCallback || function() {}; this.clearStack = attrs.clearStack || function(fn) {fn();}; + this.expectationFactory = attrs.expectationFactory; this.beforeFns = []; this.afterFns = []; @@ -25,6 +26,10 @@ getJasmineRequireObj().Suite = function() { }; } + Suite.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + Suite.prototype.getFullName = function() { var fullName = this.description; for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { @@ -130,6 +135,13 @@ getJasmineRequireObj().Suite = function() { } }; + Suite.prototype.addExpectationResult = function () { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.addExpectationResult.apply(child, arguments); + } + }; + function clone(obj) { var clonedObj = {}; for (var prop in obj) { From 97ae9a2d88a5a1598cb03f283afc6bfc5ed27da5 Mon Sep 17 00:00:00 2001 From: Christopher Amavisca and Greg Cobb Date: Thu, 6 Mar 2014 12:01:37 -0800 Subject: [PATCH 09/40] After all exceptions dispatch to reporter hook - Add 'afterAllException' hook to reporter dispatch, we might want to make this more generic in the future - Add afterAllException function to HtmlReporter [#66789174] --- lib/jasmine-core/jasmine-html.js | 4 +++ lib/jasmine-core/jasmine.js | 16 ++++++++-- spec/core/integration/EnvSpec.js | 54 +++++++++++++++++++++++++++++++- spec/html/HtmlReporterSpec.js | 21 +++++++++++++ src/core/Env.js | 6 ++-- src/core/QueueRunner.js | 10 +++++- src/html/HtmlReporter.js | 4 +++ 7 files changed, 108 insertions(+), 7 deletions(-) diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 74952879..724b6b97 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -93,6 +93,10 @@ jasmineRequire.HtmlReporter = function(j$) { currentParent.addChild(result, 'spec'); }; + this.afterAllException = function(error) { + console.error(error); + }; + var failures = []; this.specDone = function(result) { if (result.status != 'disabled') { diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 27ccdcf9..ca98bd43 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -408,7 +408,8 @@ getJasmineRequireObj().Env = function(j$) { 'suiteStarted', 'suiteDone', 'specStarted', - 'specDone' + 'specDone', + 'afterAllException' ]); this.specFilter = function() { @@ -539,6 +540,7 @@ 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}; @@ -721,7 +723,7 @@ getJasmineRequireObj().Env = function(j$) { }; this.afterAll = function(afterAllFunction) { - currentDeclarationSuite.afterAll({ fn: afterAllFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + currentDeclarationSuite.afterAll({ fn: afterAllFunction, isAfterAll: true, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.pending = function() { @@ -1581,6 +1583,7 @@ 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() { @@ -1588,7 +1591,8 @@ getJasmineRequireObj().QueueRunner = function(j$) { }; QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { - var length = queueableFns.length, + var runner = this, + length = queueableFns.length, self = this, iterativeIndex; @@ -1611,6 +1615,9 @@ getJasmineRequireObj().QueueRunner = function(j$) { try { queueableFn.fn.call(self.userContext); } catch (e) { + if(queueableFn.isAfterAll){ + runner.reporter.afterAllException(e); + } handleException(e); } } @@ -1635,6 +1642,9 @@ getJasmineRequireObj().QueueRunner = function(j$) { try { queueableFn.fn.call(self.userContext, next); } catch (e) { + if(queueableFn.isAfterAll) { + runner.reporter.afterAllException(e); + } handleException(e); next(); } diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 6f8e0dbb..7010c043 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -307,7 +307,7 @@ describe("Env integration", function() { expect(reporter.specDone.calls.argsFor(1)[0].failedExpectations[0].message) .toEqual("Expected 1 to be 2."); done(); - }); + }); env.addReporter(reporter); @@ -328,6 +328,58 @@ describe("Env integration", function() { 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','afterAllException']); + + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.afterAllException).toHaveBeenCalledWith(error); + done(); + }); + + env.addReporter(reporter); + + env.describe('my suite', function() { + env.it('my spec', function() { + }); + + env.afterAll(function() { + throw error; + }); + }); + + env.execute(); + }); + + 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','afterAllException']); + + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.afterAllException).toHaveBeenCalledWith(error); + done(); + }); + + env.addReporter(reporter); + + env.describe('my suite', function() { + env.it('my spec', function() { + }); + + env.afterAll(function(done) { + throw error; + }); + }); + + env.execute(); + }); + + + it("Allows specifying which specs and suites to run", function(done) { var env = new j$.Env(), calls = [], diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index 0919f3ed..25b4ae71 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -130,6 +130,27 @@ describe("New HtmlReporter", function() { }); }); + describe("when afterAllException is called", function () { + it("sends a console error", function(){ + var env = new j$.Env(), + error = new Error('After all exception!'), + container = document.createElement('div'), + getContainer = function () { return container; }, + reporter = new j$.HtmlReporter({ + env: env, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); }, + getContainer: getContainer + }); + + reporter.initialize(); + + spyOn(window.console, 'error'); + reporter.afterAllException(error); + expect(window.console.error).toHaveBeenCalled(); + }); + }); + describe("when Jasmine is done", function() { it("reports the run time", function() { var env = new j$.Env(), diff --git a/src/core/Env.js b/src/core/Env.js index fc2b6e80..59ed8767 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -34,7 +34,8 @@ getJasmineRequireObj().Env = function(j$) { 'suiteStarted', 'suiteDone', 'specStarted', - 'specDone' + 'specDone', + 'afterAllException' ]); this.specFilter = function() { @@ -165,6 +166,7 @@ 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}; @@ -347,7 +349,7 @@ getJasmineRequireObj().Env = function(j$) { }; this.afterAll = function(afterAllFunction) { - currentDeclarationSuite.afterAll({ fn: afterAllFunction, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); + currentDeclarationSuite.afterAll({ fn: afterAllFunction, isAfterAll: true, timeout: function() { return j$.DEFAULT_TIMEOUT_INTERVAL; } }); }; this.pending = function() { diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index 1ec3c16e..726591f4 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -18,6 +18,7 @@ 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() { @@ -25,7 +26,8 @@ getJasmineRequireObj().QueueRunner = function(j$) { }; QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { - var length = queueableFns.length, + var runner = this, + length = queueableFns.length, self = this, iterativeIndex; @@ -48,6 +50,9 @@ getJasmineRequireObj().QueueRunner = function(j$) { try { queueableFn.fn.call(self.userContext); } catch (e) { + if(queueableFn.isAfterAll){ + runner.reporter.afterAllException(e); + } handleException(e); } } @@ -72,6 +77,9 @@ getJasmineRequireObj().QueueRunner = function(j$) { try { queueableFn.fn.call(self.userContext, next); } catch (e) { + if(queueableFn.isAfterAll) { + runner.reporter.afterAllException(e); + } handleException(e); next(); } diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index a81f6583..e03b8948 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -64,6 +64,10 @@ jasmineRequire.HtmlReporter = function(j$) { currentParent.addChild(result, 'spec'); }; + this.afterAllException = function(error) { + console.error(error); + }; + var failures = []; this.specDone = function(result) { if (result.status != 'disabled') { From 6caf4c5de2b2c554feb54cb923a8d73ae297ddb6 Mon Sep 17 00:00:00 2001 From: Christopher Amavisca and Greg Cobb Date: Thu, 6 Mar 2014 15:02:55 -0800 Subject: [PATCH 10/40] Print afterAllExceptions to the console. - Add afterAllException function to ConsoleReporter - Print the stack traces of the errors at the end of the console output [#67055730] --- lib/console/console.js | 16 ++++++++++++++-- spec/console/ConsoleReporterSpec.js | 18 ++++++++++++++++-- spec/node_suite.js | 10 +++++++++- src/console/ConsoleReporter.js | 16 ++++++++++++++-- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/lib/console/console.js b/lib/console/console.js index 97821451..772fc5f4 100644 --- a/lib/console/console.js +++ b/lib/console/console.js @@ -54,7 +54,8 @@ getJasmineRequireObj().ConsoleReporter = function() { red: '\x1B[31m', yellow: '\x1B[33m', none: '\x1B[0m' - }; + }, + exceptionList = []; this.jasmineStarted = function() { specCount = 0; @@ -84,9 +85,16 @@ getJasmineRequireObj().ConsoleReporter = function() { printNewline(); var seconds = timer.elapsed() / 1000; print('Finished in ' + seconds + ' ' + plural('second', seconds)); - printNewline(); + exceptionList.forEach(function(error) { + printNewline(); + print(colored('red', 'An error was thrown in an afterAll')); + printNewline(); + print(colored('red', error.stack)); + printNewline(); + }); + onComplete(failureCount === 0); }; @@ -111,6 +119,10 @@ getJasmineRequireObj().ConsoleReporter = function() { } }; + this.afterAllException = function(error) { + exceptionList.push(error); + }; + return this; function printNewline() { diff --git a/spec/console/ConsoleReporterSpec.js b/spec/console/ConsoleReporterSpec.js index 2a0e927b..5e158d1f 100644 --- a/spec/console/ConsoleReporterSpec.js +++ b/spec/console/ConsoleReporterSpec.js @@ -176,9 +176,7 @@ describe("ConsoleReporter", function() { expect(onComplete).toHaveBeenCalled(); }); - describe("with color", function() { - it("reports that the suite has started to the console", function() { var reporter = new j$.ConsoleReporter({ print: out.print, @@ -222,5 +220,21 @@ describe("ConsoleReporter", function() { expect(out.getOutput()).toEqual("\x1B[31mF\x1B[0m"); }); + + it("displays all afterAll exceptions", function() { + var reporter = new j$.ConsoleReporter({ + print: out.print, + showColors: true + }), + error = new Error('After All Exception'), + anotherError = new Error('Some Other Exception'); + + reporter.afterAllException(error); + reporter.afterAllException(anotherError); + reporter.jasmineDone(); + + expect(out.getOutput()).toMatch(/After All Exception/); + expect(out.getOutput()).toMatch(/Some Other Exception/); + }); }); }); diff --git a/spec/node_suite.js b/spec/node_suite.js index e2d3eeae..54f09488 100644 --- a/spec/node_suite.js +++ b/spec/node_suite.js @@ -47,7 +47,15 @@ var jasmineInterface = { jsApiReporter: new jasmine.JsApiReporter({ timer: new jasmine.Timer() - }) + }), + + beforeAll: function(beforeAllFunction) { + return env.beforeAll(beforeAllFunction); + }, + + afterAll: function(afterAllFunction) { + return env.afterAll(afterAllFunction); + } }; extend(global, jasmineInterface); diff --git a/src/console/ConsoleReporter.js b/src/console/ConsoleReporter.js index f2fac6b8..cdd11b0f 100644 --- a/src/console/ConsoleReporter.js +++ b/src/console/ConsoleReporter.js @@ -19,7 +19,8 @@ getJasmineRequireObj().ConsoleReporter = function() { red: '\x1B[31m', yellow: '\x1B[33m', none: '\x1B[0m' - }; + }, + exceptionList = []; this.jasmineStarted = function() { specCount = 0; @@ -49,9 +50,16 @@ getJasmineRequireObj().ConsoleReporter = function() { printNewline(); var seconds = timer.elapsed() / 1000; print('Finished in ' + seconds + ' ' + plural('second', seconds)); - printNewline(); + exceptionList.forEach(function(error) { + printNewline(); + print(colored('red', 'An error was thrown in an afterAll')); + printNewline(); + print(colored('red', error.stack)); + printNewline(); + }); + onComplete(failureCount === 0); }; @@ -76,6 +84,10 @@ getJasmineRequireObj().ConsoleReporter = function() { } }; + this.afterAllException = function(error) { + exceptionList.push(error); + }; + return this; function printNewline() { From bed1c15ea40194bdf993ba3c45b173006fad90b9 Mon Sep 17 00:00:00 2001 From: Christopher Amavisca and Greg Cobb Date: Thu, 6 Mar 2014 15:39:04 -0800 Subject: [PATCH 11/40] HtmlReporter shows error alerts for afterAllExceptions -Add list of exceptions in HtmlReporter to push to on error -Create alerts for each exception in the list (with stack trace) [#67055688] --- lib/jasmine-core/jasmine-html.js | 11 +++++++++-- lib/jasmine-core/jasmine.css | 1 + spec/html/HtmlReporterSpec.js | 29 +++++++++++++++++++---------- src/html/HtmlReporter.js | 11 +++++++++-- src/html/_HTMLReporter.scss | 6 +++++- 5 files changed, 43 insertions(+), 15 deletions(-) diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 724b6b97..65e27823 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -46,7 +46,8 @@ jasmineRequire.HtmlReporter = function(j$) { failureCount = 0, pendingSpecCount = 0, htmlReporterMain, - symbols; + symbols, + exceptionsList = []; this.initialize = function() { htmlReporterMain = createDom('div', {className: 'html-reporter'}, @@ -94,7 +95,7 @@ jasmineRequire.HtmlReporter = function(j$) { }; this.afterAllException = function(error) { - console.error(error); + exceptionsList.push(error); }; var failures = []; @@ -169,6 +170,12 @@ jasmineRequire.HtmlReporter = function(j$) { var statusBarClassName = 'bar ' + ((failureCount > 0) ? 'failed' : 'passed'); alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage)); + exceptionsList.forEach(function(error) { + var errorBarMessage = 'An error was thrown in an afterAll: ' + error.stack; + var errorBarClassName = 'bar errored'; + alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage)); + }); + var results = find('.results'); results.appendChild(summary); diff --git a/lib/jasmine-core/jasmine.css b/lib/jasmine-core/jasmine.css index c622f643..e91f5fa3 100644 --- a/lib/jasmine-core/jasmine.css +++ b/lib/jasmine-core/jasmine.css @@ -25,6 +25,7 @@ body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } .html-reporter .bar.failed { background-color: #ca3a11; } .html-reporter .bar.passed { background-color: #007069; } .html-reporter .bar.skipped { background-color: #bababa; } +.html-reporter .bar.errored { background-color: #ca3a11; } .html-reporter .bar.menu { background-color: #fff; color: #aaaaaa; } .html-reporter .bar.menu a { color: #333333; } .html-reporter .bar a { color: white; } diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index 25b4ae71..20eb24d9 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -130,24 +130,33 @@ describe("New HtmlReporter", function() { }); }); - describe("when afterAllException is called", function () { - it("sends a console error", function(){ + describe("when there are afterAllExceptions", function () { + it("displays the exceptions in their own alert bars", function(){ var env = new j$.Env(), - error = new Error('After all exception!'), - container = document.createElement('div'), - getContainer = function () { return container; }, + container = document.createElement("div"), + getContainer = function() { return container; }, reporter = new j$.HtmlReporter({ env: env, + getContainer: getContainer, createElement: function() { return document.createElement.apply(document, arguments); }, - createTextNode: function() { return document.createTextNode.apply(document, arguments); }, - getContainer: getContainer - }); + createTextNode: function() { return document.createTextNode.apply(document, arguments); } + }), + error = new Error('My After All Exception'), + otherError = new Error('My Other Exception'); reporter.initialize(); - spyOn(window.console, 'error'); + reporter.jasmineStarted({}); reporter.afterAllException(error); - expect(window.console.error).toHaveBeenCalled(); + reporter.afterAllException(otherError); + reporter.jasmineDone({}); + + var alertBars = container.querySelectorAll(".alert .bar"); + + expect(alertBars.length).toEqual(3); + expect(alertBars[1].innerHTML).toMatch(/My After All Exception/); + expect(alertBars[1].getAttribute("class")).toEqual('bar errored'); + expect(alertBars[2].innerHTML).toMatch(/My Other Exception/); }); }); diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index e03b8948..f4a121a5 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -17,7 +17,8 @@ jasmineRequire.HtmlReporter = function(j$) { failureCount = 0, pendingSpecCount = 0, htmlReporterMain, - symbols; + symbols, + exceptionsList = []; this.initialize = function() { htmlReporterMain = createDom('div', {className: 'html-reporter'}, @@ -65,7 +66,7 @@ jasmineRequire.HtmlReporter = function(j$) { }; this.afterAllException = function(error) { - console.error(error); + exceptionsList.push(error); }; var failures = []; @@ -140,6 +141,12 @@ jasmineRequire.HtmlReporter = function(j$) { var statusBarClassName = 'bar ' + ((failureCount > 0) ? 'failed' : 'passed'); alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage)); + exceptionsList.forEach(function(error) { + var errorBarMessage = 'An error was thrown in an afterAll: ' + error.stack; + var errorBarClassName = 'bar errored'; + alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage)); + }); + var results = find('.results'); results.appendChild(summary); diff --git a/src/html/_HTMLReporter.scss b/src/html/_HTMLReporter.scss index 7b1c76ab..3abfc44a 100644 --- a/src/html/_HTMLReporter.scss +++ b/src/html/_HTMLReporter.scss @@ -152,7 +152,7 @@ body { color: #eee; &.failed { - background-color: $failing-color + background-color: $failing-color; } &.passed { @@ -163,6 +163,10 @@ body { background-color: $neutral-color; } + &.errored { + background-color: $failing-color; + } + &.menu { background-color: #fff; color: $faint-text-color; From 76fafa03881943ff328fb5b1b10092d99805c9ac Mon Sep 17 00:00:00 2001 From: Christopher Amavisca and Greg Cobb Date: Thu, 6 Mar 2014 16:14:13 -0800 Subject: [PATCH 12/40] Deprecate suite/spec ordering hook . - Remove parameter from env.execute() - Remove deprecated test - Set runnablesToRun to always start with the topSuite [#66789174] --- lib/jasmine-core/jasmine.js | 4 ++-- spec/core/integration/EnvSpec.js | 38 -------------------------------- src/core/Env.js | 4 ++-- 3 files changed, 4 insertions(+), 42 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index ca98bd43..9dbd5d4e 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -562,8 +562,8 @@ getJasmineRequireObj().Env = function(j$) { return topSuite; }; - this.execute = function(runnablesToRun) { - runnablesToRun = runnablesToRun || [topSuite.id]; + this.execute = function() { + var runnablesToRun = [topSuite.id]; var allFns = []; for(var i = 0; i < runnablesToRun.length; i++) { diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 7010c043..9da4dd97 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -378,44 +378,6 @@ describe("Env integration", function() { env.execute(); }); - - - it("Allows specifying which specs and suites to run", function(done) { - var env = new j$.Env(), - calls = [], - suiteCallback = jasmine.createSpy('suite callback'), - firstSpec, - secondSuite; - - var assertions = function() { - expect(calls).toEqual([ - 'third spec', - 'first spec' - ]); - expect(suiteCallback).toHaveBeenCalled(); - done(); - }; - - env.addReporter({jasmineDone: assertions, suiteDone: suiteCallback}); - - env.describe("first suite", function() { - firstSpec = env.it("first spec", function() { - calls.push('first spec'); - }); - env.it("second spec", function() { - calls.push('second spec'); - }); - }); - - secondSuite = env.describe("second suite", function() { - env.it("third spec", function() { - calls.push('third spec'); - }); - }); - - env.execute([secondSuite.id, firstSpec.id]); - }); - it("Functions can be spied on and have their calls tracked", function (done) { var env = new j$.Env(); diff --git a/src/core/Env.js b/src/core/Env.js index 59ed8767..40631b7b 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -188,8 +188,8 @@ getJasmineRequireObj().Env = function(j$) { return topSuite; }; - this.execute = function(runnablesToRun) { - runnablesToRun = runnablesToRun || [topSuite.id]; + this.execute = function() { + var runnablesToRun = [topSuite.id]; var allFns = []; for(var i = 0; i < runnablesToRun.length; i++) { From 668846147cecaba6c88812631e7357ac8f369776 Mon Sep 17 00:00:00 2001 From: Christopher Amavisca and Greg Cobb Date: Thu, 6 Mar 2014 18:25:49 -0800 Subject: [PATCH 13/40] Fix issues with displaying error messages for afterAll (browser compatibility) - Switch from showing error stack to showing message/description since only chrome/ff support stack - Fallback to error.description if error.message is undefined - Made exceptionList variable name consistent between both reporters --- lib/console/console.js | 6 +++--- lib/jasmine-core/jasmine-html.js | 10 +++++----- spec/console/ConsoleReporterSpec.js | 2 +- src/console/ConsoleReporter.js | 6 +++--- src/html/HtmlReporter.js | 10 +++++----- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/console/console.js b/lib/console/console.js index 772fc5f4..eccd6062 100644 --- a/lib/console/console.js +++ b/lib/console/console.js @@ -87,13 +87,13 @@ getJasmineRequireObj().ConsoleReporter = function() { print('Finished in ' + seconds + ' ' + plural('second', seconds)); printNewline(); - exceptionList.forEach(function(error) { + for(i = 0; i < exceptionList.length; i++) { printNewline(); print(colored('red', 'An error was thrown in an afterAll')); printNewline(); - print(colored('red', error.stack)); + print(colored('red', (exceptionList[i].message || exceptionList[i].description))); printNewline(); - }); + } onComplete(failureCount === 0); }; diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 65e27823..b372e3bb 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, - exceptionsList = []; + exceptionList = []; this.initialize = function() { htmlReporterMain = createDom('div', {className: 'html-reporter'}, @@ -95,7 +95,7 @@ jasmineRequire.HtmlReporter = function(j$) { }; this.afterAllException = function(error) { - exceptionsList.push(error); + exceptionList.push(error); }; var failures = []; @@ -170,11 +170,11 @@ jasmineRequire.HtmlReporter = function(j$) { var statusBarClassName = 'bar ' + ((failureCount > 0) ? 'failed' : 'passed'); alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage)); - exceptionsList.forEach(function(error) { - var errorBarMessage = 'An error was thrown in an afterAll: ' + error.stack; + 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)); - }); + } var results = find('.results'); results.appendChild(summary); diff --git a/spec/console/ConsoleReporterSpec.js b/spec/console/ConsoleReporterSpec.js index 5e158d1f..86204fd2 100644 --- a/spec/console/ConsoleReporterSpec.js +++ b/spec/console/ConsoleReporterSpec.js @@ -232,7 +232,7 @@ describe("ConsoleReporter", function() { reporter.afterAllException(error); reporter.afterAllException(anotherError); reporter.jasmineDone(); - + expect(out.getOutput()).toMatch(/After All Exception/); expect(out.getOutput()).toMatch(/Some Other Exception/); }); diff --git a/src/console/ConsoleReporter.js b/src/console/ConsoleReporter.js index cdd11b0f..bfd15273 100644 --- a/src/console/ConsoleReporter.js +++ b/src/console/ConsoleReporter.js @@ -52,13 +52,13 @@ getJasmineRequireObj().ConsoleReporter = function() { print('Finished in ' + seconds + ' ' + plural('second', seconds)); printNewline(); - exceptionList.forEach(function(error) { + for(i = 0; i < exceptionList.length; i++) { printNewline(); print(colored('red', 'An error was thrown in an afterAll')); printNewline(); - print(colored('red', error.stack)); + print(colored('red', (exceptionList[i].message || exceptionList[i].description))); printNewline(); - }); + } onComplete(failureCount === 0); }; diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index f4a121a5..effa58bd 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -18,7 +18,7 @@ jasmineRequire.HtmlReporter = function(j$) { pendingSpecCount = 0, htmlReporterMain, symbols, - exceptionsList = []; + exceptionList = []; this.initialize = function() { htmlReporterMain = createDom('div', {className: 'html-reporter'}, @@ -66,7 +66,7 @@ jasmineRequire.HtmlReporter = function(j$) { }; this.afterAllException = function(error) { - exceptionsList.push(error); + exceptionList.push(error); }; var failures = []; @@ -141,11 +141,11 @@ jasmineRequire.HtmlReporter = function(j$) { var statusBarClassName = 'bar ' + ((failureCount > 0) ? 'failed' : 'passed'); alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage)); - exceptionsList.forEach(function(error) { - var errorBarMessage = 'An error was thrown in an afterAll: ' + error.stack; + 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)); - }); + } var results = find('.results'); results.appendChild(summary); From 0d4b04d37cefc52fc19202f411b5f0f767cf1d53 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Fri, 18 Apr 2014 16:00:02 -0700 Subject: [PATCH 14/40] rename afterAllException to afterAllError It should also handle expectation failures --- lib/console/console.js | 2 +- spec/console/ConsoleReporterSpec.js | 6 +++--- spec/core/integration/EnvSpec.js | 10 +++++----- spec/html/HtmlReporterSpec.js | 6 +++--- src/console/ConsoleReporter.js | 2 +- src/core/Env.js | 2 +- src/core/QueueRunner.js | 4 ++-- src/html/HtmlReporter.js | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/console/console.js b/lib/console/console.js index eccd6062..c00897f0 100644 --- a/lib/console/console.js +++ b/lib/console/console.js @@ -119,7 +119,7 @@ getJasmineRequireObj().ConsoleReporter = function() { } }; - this.afterAllException = function(error) { + this.afterAllError = function(error) { exceptionList.push(error); }; diff --git a/spec/console/ConsoleReporterSpec.js b/spec/console/ConsoleReporterSpec.js index 86204fd2..2c1af8c1 100644 --- a/spec/console/ConsoleReporterSpec.js +++ b/spec/console/ConsoleReporterSpec.js @@ -229,10 +229,10 @@ describe("ConsoleReporter", function() { error = new Error('After All Exception'), anotherError = new Error('Some Other Exception'); - reporter.afterAllException(error); - reporter.afterAllException(anotherError); + reporter.afterAllError(error); + reporter.afterAllError(anotherError); reporter.jasmineDone(); - + expect(out.getOutput()).toMatch(/After All Exception/); expect(out.getOutput()).toMatch(/Some Other Exception/); }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 9da4dd97..7b782c41 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -331,11 +331,11 @@ describe("Env integration", function() { 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','afterAllException']); + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllError']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.afterAllException).toHaveBeenCalledWith(error); + expect(reporter.afterAllError).toHaveBeenCalledWith(error); done(); }); @@ -356,11 +356,11 @@ 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','afterAllException']); + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllError']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.afterAllException).toHaveBeenCalledWith(error); + expect(reporter.afterAllError).toHaveBeenCalled(); done(); }); @@ -370,7 +370,7 @@ describe("Env integration", function() { env.it('my spec', function() { }); - env.afterAll(function(done) { + env.afterAll(function(afterAllDone) { throw error; }); }); diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index 20eb24d9..87cd80f0 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -130,7 +130,7 @@ describe("New HtmlReporter", function() { }); }); - describe("when there are afterAllExceptions", function () { + describe("when there are afterAllErrors", function () { it("displays the exceptions in their own alert bars", function(){ var env = new j$.Env(), container = document.createElement("div"), @@ -147,8 +147,8 @@ describe("New HtmlReporter", function() { reporter.initialize(); reporter.jasmineStarted({}); - reporter.afterAllException(error); - reporter.afterAllException(otherError); + reporter.afterAllError(error); + reporter.afterAllError(otherError); reporter.jasmineDone({}); var alertBars = container.querySelectorAll(".alert .bar"); diff --git a/src/console/ConsoleReporter.js b/src/console/ConsoleReporter.js index bfd15273..b40b86d5 100644 --- a/src/console/ConsoleReporter.js +++ b/src/console/ConsoleReporter.js @@ -84,7 +84,7 @@ getJasmineRequireObj().ConsoleReporter = function() { } }; - this.afterAllException = function(error) { + this.afterAllError = function(error) { exceptionList.push(error); }; diff --git a/src/core/Env.js b/src/core/Env.js index 40631b7b..e8174694 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -35,7 +35,7 @@ getJasmineRequireObj().Env = function(j$) { 'suiteDone', 'specStarted', 'specDone', - 'afterAllException' + 'afterAllError' ]); this.specFilter = function() { diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index 726591f4..cc3ece1a 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -51,7 +51,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { queueableFn.fn.call(self.userContext); } catch (e) { if(queueableFn.isAfterAll){ - runner.reporter.afterAllException(e); + runner.reporter.afterAllError(e); } handleException(e); } @@ -78,7 +78,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { queueableFn.fn.call(self.userContext, next); } catch (e) { if(queueableFn.isAfterAll) { - runner.reporter.afterAllException(e); + runner.reporter.afterAllError(e); } handleException(e); next(); diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index effa58bd..7ef6fb67 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -65,7 +65,7 @@ jasmineRequire.HtmlReporter = function(j$) { currentParent.addChild(result, 'spec'); }; - this.afterAllException = function(error) { + this.afterAllError = function(error) { exceptionList.push(error); }; From 6066c71966aeb9182150b1e8151f9a966a1fd1b2 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Fri, 18 Apr 2014 16:37:27 -0700 Subject: [PATCH 15/40] Add failing specs for expectation failures in afterAll --- spec/core/integration/EnvSpec.js | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 7b782c41..0e1adadc 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -328,6 +328,30 @@ describe("Env integration", function() { env.execute(); }); + it("reports when an afterAll fails an expectation", function(done) { + var env = new j$.Env(), + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllError']); + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.afterAllError).toHaveBeenCalled(); + done(); + }); + + env.addReporter(reporter); + + env.describe('my suite', function() { + env.it('my spec', 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'), @@ -353,6 +377,30 @@ describe("Env integration", function() { env.execute(); }); + it("reports when an async afterAll fails an expectation", function(done) { + var env = new j$.Env(), + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllError']); + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.afterAllError).toHaveBeenCalled(); + done(); + }); + + env.addReporter(reporter); + + env.describe('my suite', function() { + env.it('my spec', function() { + }); + + env.afterAll(function(afterAllDone) { + env.expect(1).toEqual(2); + afterAllDone(); + }); + }); + + env.execute(); + }); + it("reports when an async afterAll throws an exception", function(done) { var env = new j$.Env(), error = new Error('After All Exception'), From f0892a55aa9a753f456ffeb9a1bc0dae75b93c9b Mon Sep 17 00:00:00 2001 From: slackersoft Date: Fri, 6 Jun 2014 18:41:06 -0700 Subject: [PATCH 16/40] report when an async afterAll doesn't call in time --- spec/core/integration/EnvSpec.js | 24 +++++++++++++++++++++++- src/core/QueueRunner.js | 6 +++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 0e1adadc..100b4091 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -345,7 +345,6 @@ describe("Env integration", function() { env.afterAll(function() { env.expect(1).toEqual(2); - }); }); @@ -637,6 +636,29 @@ describe("Env integration", function() { env.execute(); }); + + 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','afterAllError']); + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.afterAllError).toHaveBeenCalledWith(jasmine.any(Error)); + done(); + }); + + env.addReporter(reporter); + + env.describe('my suite', function() { + env.it('my spec', function() { + }); + + env.afterAll(function(innerDone) { + jasmine.clock().tick(4312); + }); + }); + + env.execute(); + }); }); // TODO: something is wrong with this spec diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index cc3ece1a..beca52fb 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -69,7 +69,11 @@ 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.'); + if (queueableFn.isAfterAll) { + runner.reporter.afterAllError(error); + } + self.onException(error); next(); }, queueableFn.timeout()]]); } From 1bad048c15aa973ee179832c0e13dbc74f99135e Mon Sep 17 00:00:00 2001 From: slackersoft Date: Fri, 20 Jun 2014 08:16:42 -0700 Subject: [PATCH 17/40] Extract afterAll checking from queueRunner --- spec/core/QueueRunnerSpec.js | 31 ++++++++++++++++++++++++------- src/core/Env.js | 16 ++++++++++------ src/core/QueueRunner.js | 24 ++++++++++-------------- 3 files changed, 44 insertions(+), 27 deletions(-) diff --git a/spec/core/QueueRunnerSpec.js b/spec/core/QueueRunnerSpec.js index e677104b..d715b7cb 100644 --- a/spec/core/QueueRunnerSpec.js +++ b/spec/core/QueueRunnerSpec.js @@ -95,13 +95,15 @@ describe("QueueRunner", function() { it("sets a timeout if requested for asynchronous functions so they don't go on forever", function() { var timeout = 3, - beforeFn = { fn: function(done) { }, timeout: function() { return timeout; } }, - queueableFn = { fn: jasmine.createSpy('fn') }, + 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 }); @@ -110,6 +112,7 @@ 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(); @@ -119,10 +122,12 @@ 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, }); @@ -131,37 +136,44 @@ 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(); }); - it("clears the timeout when an async function throws an exception, to prevent additional onException calls", 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 }); @@ -170,6 +182,7 @@ describe("QueueRunner", function() { expect(onComplete).toHaveBeenCalled(); jasmine.clock().tick(j$.DEFAULT_TIMEOUT_INTERVAL); + expect(reportException).not.toHaveBeenCalled(); expect(onException).not.toHaveBeenCalled(); }); @@ -200,19 +213,23 @@ describe("QueueRunner", function() { }); }); - it("calls an exception handler when an exception is thrown in a fn", function() { - var queueableFn = { fn: function() { + it("calls exception handlers when an exception is thrown in a fn", function() { + var queueableFn = { type: 'queueable', + fn: function() { throw new Error('fake error'); } }, exceptionCallback = jasmine.createSpy('exception callback'), + onExceptionCallback = jasmine.createSpy('on exception callback'), queueRunner = new j$.QueueRunner({ queueableFns: [queueableFn], - onException: exceptionCallback + reportException: exceptionCallback, + onException: onExceptionCallback }); queueRunner.execute(); - expect(exceptionCallback).toHaveBeenCalledWith(jasmine.any(Error)); + expect(exceptionCallback).toHaveBeenCalledWith(jasmine.any(Error), 'queueable'); + expect(onExceptionCallback).toHaveBeenCalledWith(jasmine.any(Error)); }); it("rethrows an exception if told to", function() { diff --git a/src/core/Env.js b/src/core/Env.js index e8174694..165241ad 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -166,9 +166,13 @@ 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}; + options.reportException = function(e, type) { + if (type === 'afterAll') { + reporter.afterAllError(e); + } + }; new j$.QueueRunner(options).execute(); }; @@ -296,7 +300,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; @@ -337,19 +341,19 @@ getJasmineRequireObj().Env = function(j$) { }; 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() { diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index beca52fb..a0facf84 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -18,7 +18,7 @@ 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; + this.reportException = attrs.reportException || function() {}; } QueueRunner.prototype.execute = function() { @@ -50,10 +50,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { try { queueableFn.fn.call(self.userContext); } catch (e) { - if(queueableFn.isAfterAll){ - runner.reporter.afterAllError(e); - } - handleException(e); + handleException(e, queueableFn); } } @@ -70,10 +67,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { if (queueableFn.timeout) { timeoutId = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() { var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); - if (queueableFn.isAfterAll) { - runner.reporter.afterAllError(error); - } - self.onException(error); + onException(error, queueableFn); next(); }, queueableFn.timeout()]]); } @@ -81,16 +75,18 @@ getJasmineRequireObj().QueueRunner = function(j$) { try { queueableFn.fn.call(self.userContext, next); } catch (e) { - if(queueableFn.isAfterAll) { - runner.reporter.afterAllError(e); - } - handleException(e); + handleException(e, queueableFn); next(); } } - function handleException(e) { + function onException(e, queueableFn) { + self.reportException(e, queueableFn.type); 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.. From 97867b2bf54259b49380da8f07097ff23dc5f500 Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Tue, 26 Aug 2014 18:04:12 -0700 Subject: [PATCH 18/40] Reports expectation failures in afterAlls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes the specs green and appears to work for most cases. I have a number of concerns about the implementation and would appreciate ideas/feedback. - Suite#addExpecationResult infers if it is coming from an afterAll fn based on if the first child of the suite is finished. This assumes that the first child of the suite is a spec (this appears to be true as long as there is at least one spec in the suite) - Suites behave like unfinished specs. Because suites will propagate expectation failures to their children suites, the afterAll expectation reporting appears to work for suites without specs unless you have: 1) An otherwise empty suite with an afterAll 2) An afterAll'd suite whose first suite is empty (or whose first suite's first suite is empty (and so on)) - Changed afterAllError to afterAllEvent, so it can accommodate both errors and expectation failures. The reporter now receives a string instead of the actual error object. The loss of the object doesn't affect our reporters, but may be a nice-to-have for other reporters/ the future. - The gap between the expectations caught in Suite and QueueRunner (who triggers reporting via an injected callback) is an array injected into QR by the Suite. The array is then flushed at some point (currently after the attempt… functions). This works, but is a bit goofy. [#73741654] --- spec/console/ConsoleReporterSpec.js | 4 +- spec/core/QueueRunnerSpec.js | 2 +- spec/core/integration/EnvSpec.js | 57 +++++++++++++++++++++++------ spec/html/HtmlReporterSpec.js | 6 +-- src/console/ConsoleReporter.js | 6 +-- src/core/Env.js | 7 +++- src/core/QueueRunner.js | 18 +++++++-- src/core/Suite.js | 22 +++++++++-- src/html/HtmlReporter.js | 4 +- 9 files changed, 93 insertions(+), 33 deletions(-) diff --git a/spec/console/ConsoleReporterSpec.js b/spec/console/ConsoleReporterSpec.js index 2c1af8c1..cc407f97 100644 --- a/spec/console/ConsoleReporterSpec.js +++ b/spec/console/ConsoleReporterSpec.js @@ -229,8 +229,8 @@ describe("ConsoleReporter", function() { error = new Error('After All Exception'), anotherError = new Error('Some Other Exception'); - reporter.afterAllError(error); - reporter.afterAllError(anotherError); + reporter.afterAllEvent(error); + reporter.afterAllEvent(anotherError); reporter.jasmineDone(); expect(out.getOutput()).toMatch(/After All Exception/); diff --git a/spec/core/QueueRunnerSpec.js b/spec/core/QueueRunnerSpec.js index d715b7cb..217467e7 100644 --- a/spec/core/QueueRunnerSpec.js +++ b/spec/core/QueueRunnerSpec.js @@ -173,7 +173,7 @@ describe("QueueRunner", function() { queueRunner = new j$.QueueRunner({ queueableFns: [queueableFn], onComplete: onComplete, - reportException: reportException + reportException: reportException, onException: onException }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 100b4091..f6871f1a 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -330,10 +330,11 @@ describe("Env integration", function() { it("reports when an afterAll fails an expectation", function(done) { var env = new j$.Env(), - reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllError']); + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllEvent']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.afterAllError).toHaveBeenCalled(); + expect(reporter.afterAllEvent).toHaveBeenCalledWith('Expectation failed: Expected 1 to equal 2.'); + expect(reporter.afterAllEvent).toHaveBeenCalledWith('Expectation failed: Expected 2 to equal 3.'); done(); }); @@ -343,6 +344,40 @@ describe("Env integration", function() { env.it('my spec', function() { }); + env.afterAll(function() { + env.expect(1).toEqual(2); + env.expect(2).toEqual(3); + }); + }); + + 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); }); @@ -354,11 +389,11 @@ describe("Env integration", function() { 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','afterAllError']); - + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllEvent']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.afterAllError).toHaveBeenCalledWith(error); + expect(reporter.afterAllEvent.calls.count()).toEqual(1); + expect(reporter.afterAllEvent).toHaveBeenCalledWith('Error thrown: After All Exception'); done(); }); @@ -378,10 +413,10 @@ 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','afterAllError']); + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllEvent']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.afterAllError).toHaveBeenCalled(); + expect(reporter.afterAllEvent).toHaveBeenCalledWith('Expectation failed: Expected 1 to equal 2.'); done(); }); @@ -403,11 +438,11 @@ 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','afterAllError']); + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllEvent']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.afterAllError).toHaveBeenCalled(); + expect(reporter.afterAllEvent).toHaveBeenCalled(); done(); }); @@ -639,10 +674,10 @@ 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','afterAllError']); + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','afterAllEvent']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.afterAllError).toHaveBeenCalledWith(jasmine.any(Error)); + expect(reporter.afterAllEvent).toHaveBeenCalledWith(jasmine.any(String)); done(); }); diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index 87cd80f0..ce3b526d 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -130,7 +130,7 @@ describe("New HtmlReporter", function() { }); }); - describe("when there are afterAllErrors", function () { + describe("when there are afterAllEvents", function () { it("displays the exceptions in their own alert bars", function(){ var env = new j$.Env(), container = document.createElement("div"), @@ -147,8 +147,8 @@ describe("New HtmlReporter", function() { reporter.initialize(); reporter.jasmineStarted({}); - reporter.afterAllError(error); - reporter.afterAllError(otherError); + reporter.afterAllEvent(error); + reporter.afterAllEvent(otherError); reporter.jasmineDone({}); var alertBars = container.querySelectorAll(".alert .bar"); diff --git a/src/console/ConsoleReporter.js b/src/console/ConsoleReporter.js index b40b86d5..a24f80ca 100644 --- a/src/console/ConsoleReporter.js +++ b/src/console/ConsoleReporter.js @@ -54,9 +54,7 @@ getJasmineRequireObj().ConsoleReporter = function() { 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))); + print(colored('red', 'AfterAll ' + exceptionList[i])); printNewline(); } @@ -84,7 +82,7 @@ getJasmineRequireObj().ConsoleReporter = function() { } }; - this.afterAllError = function(error) { + this.afterAllEvent = function(error) { exceptionList.push(error); }; diff --git a/src/core/Env.js b/src/core/Env.js index 165241ad..3fb56eee 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -35,7 +35,7 @@ getJasmineRequireObj().Env = function(j$) { 'suiteDone', 'specStarted', 'specDone', - 'afterAllError' + 'afterAllEvent' ]); this.specFilter = function() { @@ -170,9 +170,12 @@ getJasmineRequireObj().Env = function(j$) { options.timer = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; options.reportException = function(e, type) { if (type === 'afterAll') { - reporter.afterAllError(e); + reporter.afterAllEvent('Error thrown: '+ (e.message || e.description)); } }; + options.reportExpectationFailure = function(message) { + reporter.afterAllEvent('Expectation failed: '+ message); + }; new j$.QueueRunner(options).execute(); }; diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index a0facf84..8a60969b 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -19,6 +19,8 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.userContext = attrs.userContext || {}; this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; this.reportException = attrs.reportException || function() {}; + this.reportExpectationFailure = attrs.reportExpectationFailure || function() {}; + this.afterAllExpectationFailures = attrs.afterAllExpectationFailures || []; } QueueRunner.prototype.execute = function() { @@ -26,10 +28,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]; @@ -37,6 +39,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { return attemptAsync(queueableFn); } else { attemptSync(queueableFn); + flushAfterAllExpectationFailures(); } } @@ -60,6 +63,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { }, next = once(function () { clearTimeout(timeoutId); + flushAfterAllExpectationFailures(); self.run(queueableFns, iterativeIndex + 1); }), timeoutId; @@ -93,6 +97,12 @@ getJasmineRequireObj().QueueRunner = function(j$) { throw e; } } + + function flushAfterAllExpectationFailures() { + while (self.afterAllExpectationFailures.length) { + self.reportExpectationFailure(self.afterAllExpectationFailures.pop()); + } + } }; return QueueRunner; diff --git a/src/core/Suite.js b/src/core/Suite.js index 43d02430..bdca8ad1 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -13,6 +13,7 @@ getJasmineRequireObj().Suite = function() { this.afterFns = []; this.beforeAllFns = []; this.afterAllFns = []; + this.afterAllExpectationFailures = []; this.queueRunner = attrs.queueRunner || function() {}; this.disabled = false; @@ -89,7 +90,8 @@ getJasmineRequireObj().Suite = function() { queueableFns: allFns, onComplete: complete, userContext: this.sharedUserContext(), - onException: function() { self.onException.apply(self, arguments); } + onException: function() { self.onException.apply(self, arguments); }, + afterAllExpectationFailures: this.afterAllExpectationFailures }); function complete() { @@ -136,9 +138,21 @@ getJasmineRequireObj().Suite = function() { }; 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)){ + this.afterAllExpectationFailures.push(arguments[1].message); + } 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]; } }; diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index 7ef6fb67..85da9c35 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -65,7 +65,7 @@ jasmineRequire.HtmlReporter = function(j$) { currentParent.addChild(result, 'spec'); }; - this.afterAllError = function(error) { + this.afterAllEvent = function(error) { exceptionList.push(error); }; @@ -142,7 +142,7 @@ jasmineRequire.HtmlReporter = function(j$) { 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 errorBarMessage = 'AfterAll ' + (exceptionList[i]); var errorBarClassName = 'bar errored'; alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage)); } From f8bccabf0362d7f6ef370f6f419281bc667aeebe Mon Sep 17 00:00:00 2001 From: slackersoft Date: Wed, 27 Aug 2014 08:13:17 -0700 Subject: [PATCH 19/40] Move afterAll reporting into Suite since that's where they're being tracked --- src/core/Env.js | 11 +++++++---- src/core/QueueRunner.js | 10 ---------- src/core/Suite.js | 11 +++++++++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/core/Env.js b/src/core/Env.js index 3fb56eee..965c9ab2 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -173,9 +173,6 @@ getJasmineRequireObj().Env = function(j$) { reporter.afterAllEvent('Error thrown: '+ (e.message || e.description)); } }; - options.reportExpectationFailure = function(message) { - reporter.afterAllEvent('Expectation failed: '+ message); - }; new j$.QueueRunner(options).execute(); }; @@ -185,7 +182,10 @@ getJasmineRequireObj().Env = function(j$) { id: getNextSuiteId(), description: 'Jasmine__TopLevel__Suite', queueRunner: queueRunnerFactory, - resultCallback: function() {} // TODO - hook this up + resultCallback: function() {}, // TODO - hook this up + reportExpectationFailure: function(message) { + reporter.afterAllEvent('Expectation failed: '+ message); + } }); runnableLookupTable[topSuite.id] = topSuite; defaultResourcesForRunnable(topSuite.id); @@ -241,6 +241,9 @@ getJasmineRequireObj().Env = function(j$) { currentlyExecutingSuites.pop(); } reporter.suiteDone(attrs); + }, + reportExpectationFailure: function(message) { + reporter.afterAllEvent('Expectation failed: '+ message); } }); diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index 8a60969b..6feb23e4 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -19,8 +19,6 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.userContext = attrs.userContext || {}; this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; this.reportException = attrs.reportException || function() {}; - this.reportExpectationFailure = attrs.reportExpectationFailure || function() {}; - this.afterAllExpectationFailures = attrs.afterAllExpectationFailures || []; } QueueRunner.prototype.execute = function() { @@ -39,7 +37,6 @@ getJasmineRequireObj().QueueRunner = function(j$) { return attemptAsync(queueableFn); } else { attemptSync(queueableFn); - flushAfterAllExpectationFailures(); } } @@ -63,7 +60,6 @@ getJasmineRequireObj().QueueRunner = function(j$) { }, next = once(function () { clearTimeout(timeoutId); - flushAfterAllExpectationFailures(); self.run(queueableFns, iterativeIndex + 1); }), timeoutId; @@ -97,12 +93,6 @@ getJasmineRequireObj().QueueRunner = function(j$) { throw e; } } - - function flushAfterAllExpectationFailures() { - while (self.afterAllExpectationFailures.length) { - self.reportExpectationFailure(self.afterAllExpectationFailures.pop()); - } - } }; return QueueRunner; diff --git a/src/core/Suite.js b/src/core/Suite.js index bdca8ad1..a21731c2 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -8,6 +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.beforeFns = []; this.afterFns = []; @@ -90,11 +91,11 @@ getJasmineRequireObj().Suite = function() { queueableFns: allFns, onComplete: complete, userContext: this.sharedUserContext(), - onException: function() { self.onException.apply(self, arguments); }, - afterAllExpectationFailures: this.afterAllExpectationFailures + onException: function() { self.onException.apply(self, arguments); } }); function complete() { + self.reportAfterAllExpectationFailures(); self.resultCallback(self.result); if (onComplete) { @@ -107,6 +108,12 @@ getJasmineRequireObj().Suite = function() { } }; + Suite.prototype.reportAfterAllExpectationFailures = function() { + while (this.afterAllExpectationFailures.length) { + this.reportExpectationFailure(this.afterAllExpectationFailures.pop()); + } + }; + Suite.prototype.isExecutable = function() { var foundActive = false; for(var i = 0; i < this.children.length; i++) { From 35f52bcb24eb9d1dc9ac51a1b9a8f7a35c15847e Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Wed, 27 Aug 2014 12:27:11 -0700 Subject: [PATCH 20/40] Rewrite an incorrect suite spec --- spec/core/SuiteSpec.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/core/SuiteSpec.js b/spec/core/SuiteSpec.js index 297204ba..5a72a96d 100644 --- a/spec/core/SuiteSpec.js +++ b/spec/core/SuiteSpec.js @@ -193,14 +193,12 @@ describe("Suite", function() { queueRunner: fakeQueueRunnerForChild, parentSuite: parentSuite }), - spec1 = new j$.Spec({expectationFactory: function() {}, queueableFn: {}}), - spec2 = new j$.Spec({expectationFactory: function() {}, queueableFn: {}}), beforeAllFn = jasmine.createSpy('beforeAll'), afterAllFn = jasmine.createSpy('afterAll'); parentSuite.addChild(childSuite); - parentSuite.addChild(spec1); - childSuite.addChild(spec1); + parentSuite.beforeAll(beforeAllFn); + parentSuite.afterAll(afterAllFn); parentSuite.execute(); expect(fakeQueueRunnerForParent).toHaveBeenCalledWith(jasmine.objectContaining({queueableFns: []})); From cc3678f0336cb0cb0be949393618774e40008e1f Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Wed, 27 Aug 2014 13:50:56 -0700 Subject: [PATCH 21/40] Refactor suite to share reportExpectationFailure func This was shared between our suite factory and the suite factory. Sweet. --- src/core/Env.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/Env.js b/src/core/Env.js index 965c9ab2..a6d61e00 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -183,9 +183,7 @@ getJasmineRequireObj().Env = function(j$) { description: 'Jasmine__TopLevel__Suite', queueRunner: queueRunnerFactory, resultCallback: function() {}, // TODO - hook this up - reportExpectationFailure: function(message) { - reporter.afterAllEvent('Expectation failed: '+ message); - } + reportExpectationFailure: reportExpectationFailure }); runnableLookupTable[topSuite.id] = topSuite; defaultResourcesForRunnable(topSuite.id); @@ -242,9 +240,7 @@ getJasmineRequireObj().Env = function(j$) { } reporter.suiteDone(attrs); }, - reportExpectationFailure: function(message) { - reporter.afterAllEvent('Expectation failed: '+ message); - } + reportExpectationFailure: reportExpectationFailure }); runnableLookupTable[suite.id] = suite; @@ -365,6 +361,10 @@ getJasmineRequireObj().Env = function(j$) { this.pending = function() { throw j$.Spec.pendingSpecExceptionMessage; }; + + function reportExpectationFailure(message) { + reporter.afterAllEvent('Expectation failed: '+ message); + } } return Env; From 5b6edff3fdb59aaac1da9df60fee9273a50ff723 Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Wed, 27 Aug 2014 14:01:28 -0700 Subject: [PATCH 22/40] Report AfterAll failures as they occur There didn't seem to be a valid reason to wait until the very end to report these. --- src/core/Suite.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/core/Suite.js b/src/core/Suite.js index a21731c2..d838328c 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -14,7 +14,6 @@ getJasmineRequireObj().Suite = function() { this.afterFns = []; this.beforeAllFns = []; this.afterAllFns = []; - this.afterAllExpectationFailures = []; this.queueRunner = attrs.queueRunner || function() {}; this.disabled = false; @@ -95,7 +94,6 @@ getJasmineRequireObj().Suite = function() { }); function complete() { - self.reportAfterAllExpectationFailures(); self.resultCallback(self.result); if (onComplete) { @@ -108,12 +106,6 @@ getJasmineRequireObj().Suite = function() { } }; - Suite.prototype.reportAfterAllExpectationFailures = function() { - while (this.afterAllExpectationFailures.length) { - this.reportExpectationFailure(this.afterAllExpectationFailures.pop()); - } - }; - Suite.prototype.isExecutable = function() { var foundActive = false; for(var i = 0; i < this.children.length; i++) { @@ -146,7 +138,7 @@ getJasmineRequireObj().Suite = function() { Suite.prototype.addExpectationResult = function () { if(isAfterAll(this.children) && isFailure(arguments)){ - this.afterAllExpectationFailures.push(arguments[1].message); + this.reportExpectationFailure(arguments[1].message); } else { for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; From cd4d5c24458958f293e6624d8f80a8cb85c9d8fb Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Wed, 27 Aug 2014 14:45:59 -0700 Subject: [PATCH 23/40] ConsoleReporter exits 1 when afterAll events occur [Finishes #67068790] --- spec/console/ConsoleReporterSpec.js | 29 ++++++++++++++++++++++++----- src/console/ConsoleReporter.js | 1 + 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/spec/console/ConsoleReporterSpec.js b/spec/console/ConsoleReporterSpec.js index cc407f97..8ea830bb 100644 --- a/spec/console/ConsoleReporterSpec.js +++ b/spec/console/ConsoleReporterSpec.js @@ -159,21 +159,40 @@ describe("ConsoleReporter", function() { out.clear(); - reporter.jasmineDone({}); + reporter.jasmineDone(); expect(out.getOutput()).toMatch(/foo bar baz/); }); - it("calls the onComplete callback when the suite is done", function() { - var onComplete = jasmine.createSpy('onComplete'), + describe('onComplete callback', function(){ + var onComplete, reporter; + + beforeEach(function() { + onComplete = jasmine.createSpy('onComplete'); reporter = new j$.ConsoleReporter({ print: out.print, onComplete: onComplete }); + reporter.jasmineStarted(); + }); - reporter.jasmineDone({}); + it("is called when the suite is done", function() { + reporter.jasmineDone(); + expect(onComplete).toHaveBeenCalledWith(true); + }); - expect(onComplete).toHaveBeenCalled(); + it('calls it with false if there are spec failures', function() { + reporter.specDone({status: "failed", failedExpectations: []}); + reporter.jasmineDone(); + expect(onComplete).toHaveBeenCalledWith(false); + }); + + it('calls it with false if there are afterAll events', function() { + reporter.afterAllEvent("bananas"); + reporter.specDone({status: "passed"}); + reporter.jasmineDone(); + expect(onComplete).toHaveBeenCalledWith(false); + }); }); describe("with color", function() { diff --git a/src/console/ConsoleReporter.js b/src/console/ConsoleReporter.js index a24f80ca..75291bdc 100644 --- a/src/console/ConsoleReporter.js +++ b/src/console/ConsoleReporter.js @@ -83,6 +83,7 @@ getJasmineRequireObj().ConsoleReporter = function() { }; this.afterAllEvent = function(error) { + failureCount++; exceptionList.push(error); }; From 5b397ff33ed394bd6b7e08f5d812c548ad207c79 Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Wed, 27 Aug 2014 17:52:29 -0700 Subject: [PATCH 24/40] Revert "Deprecate suite/spec ordering hook ." This reverts commit 76fafa03881943ff328fb5b1b10092d99805c9ac. [#73742528] --- lib/jasmine-core/jasmine.js | 4 ++-- spec/core/integration/EnvSpec.js | 38 ++++++++++++++++++++++++++++++++ src/core/Env.js | 4 ++-- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 9dbd5d4e..ca98bd43 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -562,8 +562,8 @@ getJasmineRequireObj().Env = function(j$) { return topSuite; }; - this.execute = function() { - var runnablesToRun = [topSuite.id]; + this.execute = function(runnablesToRun) { + runnablesToRun = runnablesToRun || [topSuite.id]; var allFns = []; for(var i = 0; i < runnablesToRun.length; i++) { diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index f6871f1a..ab8f2149 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -460,6 +460,44 @@ describe("Env integration", function() { env.execute(); }); + + + it("Allows specifying which specs and suites to run", function(done) { + var env = new j$.Env(), + calls = [], + suiteCallback = jasmine.createSpy('suite callback'), + firstSpec, + secondSuite; + + var assertions = function() { + expect(calls).toEqual([ + 'third spec', + 'first spec' + ]); + expect(suiteCallback).toHaveBeenCalled(); + done(); + }; + + env.addReporter({jasmineDone: assertions, suiteDone: suiteCallback}); + + env.describe("first suite", function() { + firstSpec = env.it("first spec", function() { + calls.push('first spec'); + }); + env.it("second spec", function() { + calls.push('second spec'); + }); + }); + + secondSuite = env.describe("second suite", function() { + env.it("third spec", function() { + calls.push('third spec'); + }); + }); + + env.execute([secondSuite.id, firstSpec.id]); + }); + it("Functions can be spied on and have their calls tracked", function (done) { var env = new j$.Env(); diff --git a/src/core/Env.js b/src/core/Env.js index a6d61e00..0faba997 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -193,8 +193,8 @@ getJasmineRequireObj().Env = function(j$) { return topSuite; }; - this.execute = function() { - var runnablesToRun = [topSuite.id]; + this.execute = function(runnablesToRun) { + runnablesToRun = runnablesToRun || [topSuite.id]; var allFns = []; for(var i = 0; i < runnablesToRun.length; i++) { From 61bf9ac7d7e7f277ef9ba30176e04b0581cdd1dc Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Thu, 28 Aug 2014 10:24:22 -0700 Subject: [PATCH 25/40] Improve quality of async timeout for afterAll spec We found that this test was always passing and had strange interactions with the ordering of other specs. Rewriting it to explicitly finish the afterAll after a specified interval makes it fail correctly. [#73742528] --- spec/core/integration/EnvSpec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index ab8f2149..01925def 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -720,13 +720,15 @@ describe("Env integration", function() { }); env.addReporter(reporter); + j$.DEFAULT_TIMEOUT_INTERVAL = 3000; env.describe('my suite', function() { env.it('my spec', function() { }); env.afterAll(function(innerDone) { - jasmine.clock().tick(4312); + jasmine.clock().tick(3001); + innerDone(); }); }); From b984ff2fa67b0b371566c5b7cf4b600ddffbad53 Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Thu, 28 Aug 2014 11:21:41 -0700 Subject: [PATCH 26/40] Run before,afterAlls as 'eachs' when specifying runnablesToRun - Fix bug where beforeAlls were being mutated in Suite#execute - When Env.execute() receives a list of runnables, beforeAlls and afterAlls are collected as beforeEachs and afterEachs. This allows runnables to be specified in any order, regardless of if any of them have before/afterAlls. - Spec constructor takes a single function that returns both before and afters, instead of two functions. This breaks the current interface for constructing a Spec. [#73742528] --- spec/core/SpecSpec.js | 7 ++--- spec/core/integration/EnvSpec.js | 40 ++++++++++++++++++++++-- spec/core/integration/SpecRunningSpec.js | 2 +- src/core/Env.js | 35 ++++++++++++--------- src/core/Spec.js | 6 ++-- src/core/Suite.js | 2 +- 6 files changed, 65 insertions(+), 27 deletions(-) diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index 71fff7d2..e117b654 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -91,11 +91,8 @@ describe("Spec", function() { }) }, spec = new j$.Spec({ queueableFn: queueableFn, - beforeFns: function() { - return [before] - }, - afterFns: function() { - return [after] + beforeAndAfterFns: function() { + return {befores: [before], afters: [after]} }, queueRunnerFactory: fakeQueueRunner }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 01925def..f81678b6 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -460,8 +460,6 @@ describe("Env integration", function() { env.execute(); }); - - it("Allows specifying which specs and suites to run", function(done) { var env = new j$.Env(), calls = [], @@ -498,6 +496,44 @@ describe("Env integration", function() { env.execute([secondSuite.id, firstSpec.id]); }); + it('runs before and after all functions for focused specs', function(done) { + var env = new j$.Env(), + calls = [], + first_spec, + second_spec; + + var assertions = function() { + expect(calls).toEqual([ + "before", + "first spec", + "after", + "before", + "second spec", + "after" + ]); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.describe("first suite", function() { + env.beforeAll(function() { + calls.push("before"); + }); + env.afterAll(function() { + calls.push("after") + }); + first_spec = env.it("spec", function() { + calls.push('first spec'); + }); + second_spec = env.it("spec 2", function() { + calls.push("second spec"); + }); + }); + + env.execute([first_spec.id, second_spec.id]); + }); + it("Functions can be spied on and have their calls tracked", function (done) { var env = new j$.Env(); diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index 5d629465..7c4c4791 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -159,7 +159,7 @@ describe("jasmine spec running", function () { ]; expect(actions).toEqual(expected); done(); - } + }; env.addReporter({jasmineDone: assertions}); diff --git a/src/core/Env.js b/src/core/Env.js index 0faba997..c507feae 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -101,25 +101,23 @@ getJasmineRequireObj().Env = function(j$) { delete runnableResources[id]; }; - var beforeFns = function(suite) { + var beforeAndAfterFns = function(suite, runnablesExplictlySet) { return function() { var befores = []; - while(suite) { - befores = befores.concat(suite.beforeFns); - suite = suite.parentSuite; - } - return befores.reverse(); - }; - }; - - var afterFns = function(suite) { - return function() { var afters = []; while(suite) { + if (runnablesExplictlySet()) { + befores = befores.concat(suite.beforeAllFns); + afters = afters.concat(suite.afterAllFns); + } + befores = befores.concat(suite.beforeFns); afters = afters.concat(suite.afterFns); suite = suite.parentSuite; } - return afters; + return { + befores: befores.reverse(), + afters: afters + }; }; }; @@ -194,6 +192,9 @@ getJasmineRequireObj().Env = function(j$) { }; this.execute = function(runnablesToRun) { + if(runnablesToRun) { + runnablesExplictlySet = true; + } runnablesToRun = runnablesToRun || [topSuite.id]; var allFns = []; @@ -284,13 +285,17 @@ getJasmineRequireObj().Env = function(j$) { return suite; }; + 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, diff --git a/src/core/Spec.js b/src/core/Spec.js index b260a213..43bfc7c1 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -5,8 +5,7 @@ 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() {}; @@ -48,7 +47,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, diff --git a/src/core/Suite.js b/src/core/Suite.js index d838328c..3eb21c58 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -75,7 +75,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])); From 980509cd7b5e74482b86577b1f0aad994103816d Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Thu, 28 Aug 2014 11:52:34 -0700 Subject: [PATCH 27/40] Preserve relative order of "alls" w.r.t. "eachs" [Finishes #73742528] --- spec/core/integration/SpecRunningSpec.js | 138 +++++++++++++++++++++++ src/core/Env.js | 21 ++-- 2 files changed, 151 insertions(+), 8 deletions(-) diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index 7c4c4791..f90ec8b5 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -228,6 +228,144 @@ describe("jasmine spec running", function () { env.execute(); }); + it('should run beforeAlls before beforeEachs and afterAlls after afterEachs', function() { + var actions = []; + + env.beforeAll(function() { + actions.push('runner beforeAll'); + }); + + env.afterAll(function() { + actions.push('runner afterAll'); + }); + + env.beforeEach(function () { + actions.push('runner beforeEach'); + }); + + env.afterEach(function () { + actions.push('runner afterEach'); + }); + + env.describe('Something', function() { + env.beforeEach(function() { + actions.push('inner beforeEach'); + }); + + env.afterEach(function() { + actions.push('inner afterEach'); + }); + + env.beforeAll(function() { + actions.push('inner beforeAll'); + }); + + env.afterAll(function() { + actions.push('inner afterAll'); + }); + + env.it('does something or other', function() { + actions.push('it'); + }); + }); + + var assertions = function() { + var expected = [ + "runner beforeAll", + "inner beforeAll", + "runner beforeEach", + "inner beforeEach", + "it", + "inner afterEach", + "runner afterEach", + "inner afterAll", + "runner afterAll" + ]; + expect(actions).toEqual(expected); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + env.execute(); + }); + + it('should run beforeAlls and afterAlls as beforeEachs and afterEachs in the order declared when runnablesToRun is provided', function() { + var actions = [], + spec, + spec2; + + env.beforeAll(function() { + actions.push('runner beforeAll'); + }); + + env.afterAll(function() { + actions.push('runner afterAll'); + }); + + env.beforeEach(function () { + actions.push('runner beforeEach'); + }); + + env.afterEach(function () { + actions.push('runner afterEach'); + }); + + env.describe('Something', function() { + env.beforeEach(function() { + actions.push('inner beforeEach'); + }); + + env.afterEach(function() { + actions.push('inner afterEach'); + }); + + env.beforeAll(function() { + actions.push('inner beforeAll'); + }); + + env.afterAll(function() { + actions.push('inner afterAll'); + }); + + spec = env.it('does something', function() { + actions.push('it'); + }); + + spec2 = env.it('does something or other', function() { + actions.push('it2'); + }); + }); + + var assertions = function() { + var expected = [ + "runner beforeAll", + "inner beforeAll", + "runner beforeEach", + "inner beforeEach", + "it", + "inner afterEach", + "runner afterEach", + "inner afterAll", + "runner afterAll", + + "runner beforeAll", + "inner beforeAll", + "runner beforeEach", + "inner beforeEach", + "it2", + "inner afterEach", + "runner afterEach", + "inner afterAll", + "runner afterAll" + ]; + expect(actions).toEqual(expected); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + env.execute([spec.id, spec2.id]); + }); + it("shouldn't run disabled suites", function(done) { var specInADisabledSuite = jasmine.createSpy("specInADisabledSuite"), suite = env.describe('A Suite', function() { diff --git a/src/core/Env.js b/src/core/Env.js index c507feae..b57ce68b 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -103,20 +103,25 @@ getJasmineRequireObj().Env = function(j$) { var beforeAndAfterFns = function(suite, runnablesExplictlySet) { return function() { - var befores = []; - var afters = []; + var befores = [], + afters = [], + beforeAlls = [], + afterAlls = []; + while(suite) { - if (runnablesExplictlySet()) { - befores = befores.concat(suite.beforeAllFns); - afters = afters.concat(suite.afterAllFns); - } befores = befores.concat(suite.beforeFns); afters = afters.concat(suite.afterFns); + + if (runnablesExplictlySet()) { + beforeAlls = beforeAlls.concat(suite.beforeAllFns); + afterAlls = afterAlls.concat(suite.afterAllFns); + } + suite = suite.parentSuite; } return { - befores: befores.reverse(), - afters: afters + befores: beforeAlls.reverse().concat(befores.reverse()), + afters: afters.concat(afterAlls) }; }; }; From caee1508d1bb7f9f9386aca95fa7e0690ce9c794 Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Thu, 28 Aug 2014 15:37:18 -0700 Subject: [PATCH 28/40] Adds a fit function to Env [#73742944] --- spec/core/integration/EnvSpec.js | 29 +++++++++++++++++++++++++++-- src/core/Env.js | 12 +++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index f81678b6..383d9662 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -496,7 +496,7 @@ describe("Env integration", function() { env.execute([secondSuite.id, firstSpec.id]); }); - it('runs before and after all functions for focused specs', function(done) { + it('runs before and after all functions for runnables provided to .execute()', function(done) { var env = new j$.Env(), calls = [], first_spec, @@ -772,7 +772,32 @@ describe("Env integration", function() { }); }); - // TODO: something is wrong with this spec + describe('focused tests', function() { + it('should only run the focused tests', function(done) { + var env = new j$.Env(), + calls = []; + + var assertions = function() { + expect(calls).toEqual(['focused']); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.describe('a suite', function() { + env.fit('is focused', function() { + calls.push('focused'); + }); + + env.it('is not focused', function() { + calls.push('freakout'); + }) + }); + + env.execute(); + }); + }); + it("should report as expected", function(done) { var env = new j$.Env(), reporter = jasmine.createSpyObj('fakeReporter', [ diff --git a/src/core/Env.js b/src/core/Env.js index b57ce68b..db079a60 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -199,8 +199,11 @@ getJasmineRequireObj().Env = function(j$) { this.execute = function(runnablesToRun) { if(runnablesToRun) { runnablesExplictlySet = true; + } else if (focusedRunnables.length) { + runnablesToRun = focusedRunnables; + } else { + runnablesToRun = [topSuite.id]; } - runnablesToRun = runnablesToRun || [topSuite.id]; var allFns = []; for(var i = 0; i < runnablesToRun.length; i++) { @@ -348,6 +351,13 @@ getJasmineRequireObj().Env = function(j$) { return spec; }; + var focusedRunnables = []; + this.fit = function(description, fn ){ + var spec = this.it(description, fn); + focusedRunnables.push(spec.id); + return spec; + }; + this.expect = function(actual) { return currentRunnable().expect(actual); }; From d7ab9083be313c7cae5f2448dab938d41828d823 Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Thu, 28 Aug 2014 15:43:59 -0700 Subject: [PATCH 29/40] Add focused describe [#73742944] --- spec/core/integration/EnvSpec.js | 26 ++++++++++++++++++++++++++ src/core/Env.js | 6 ++++++ 2 files changed, 32 insertions(+) diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 383d9662..8b41fb83 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -796,6 +796,32 @@ describe("Env integration", function() { env.execute(); }); + + it('should only run focused suites', function(){ + var env = new j$.Env(), + calls = []; + + var assertions = function() { + expect(calls).toEqual(['focused']); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + + env.fdescribe('a focused suite', function() { + env.it('is focused', function() { + calls.push('focused'); + }); + }); + + env.describe('a regular suite', function() { + env.it('is not focused', function() { + calls.push('freakout'); + }) + }); + + env.execute(); + }); }); it("should report as expected", function(done) { diff --git a/src/core/Env.js b/src/core/Env.js index db079a60..7a17044d 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -293,6 +293,12 @@ getJasmineRequireObj().Env = function(j$) { return suite; }; + this.fdescribe = function(description, specDefinitions) { + var suite = this.describe(description, specDefinitions); + focusedRunnables.push(suite.id); + return suite; + }; + var runnablesExplictlySet = false; var runnablesExplictlySetGetter = function(){ From dee10666524fca5a2a7e2596129284a9c713bdb6 Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Thu, 28 Aug 2014 15:54:27 -0700 Subject: [PATCH 30/40] Focused runnables play nicely with beforeAll/afterAll [#73742944] --- spec/core/integration/SpecRunningSpec.js | 43 ++++++++++++++++++++++++ src/core/Env.js | 1 + 2 files changed, 44 insertions(+) diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index f90ec8b5..97c3fdd7 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -366,6 +366,49 @@ describe("jasmine spec running", function () { env.execute([spec.id, spec2.id]); }); + describe('focused runnables', function() { + it('runs the relevant alls and eachs for each runnable', function() { + var actions = []; + env.beforeAll(function() {actions.push('beforeAll')}); + env.afterAll(function() {actions.push('afterAll')}); + env.beforeEach(function() {actions.push('beforeEach')}); + env.afterEach(function() {actions.push('afterEach')}); + + env.fdescribe('a focused suite', function() { + env.it('is run', function() { + actions.push('spec in fdescribe') + }); + }); + + env.describe('an unfocused suite', function() { + env.fit('has a focused spec', function() { + actions.push('focused spec') + }); + }); + + var assertions = function() { + var expected = [ + 'beforeAll', + 'beforeEach', + 'spec in fdescribe', + 'afterEach', + 'afterAll', + + 'beforeAll', + 'beforeEach', + 'focused spec', + 'afterEach', + 'afterAll' + ]; + expect(actions).toEqual(expected); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + env.execute(); + }); + }); + it("shouldn't run disabled suites", function(done) { var specInADisabledSuite = jasmine.createSpy("specInADisabledSuite"), suite = env.describe('A Suite', function() { diff --git a/src/core/Env.js b/src/core/Env.js index 7a17044d..97c3fa62 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -200,6 +200,7 @@ getJasmineRequireObj().Env = function(j$) { if(runnablesToRun) { runnablesExplictlySet = true; } else if (focusedRunnables.length) { + runnablesExplictlySet = true; runnablesToRun = focusedRunnables; } else { runnablesToRun = [topSuite.id]; From 64a67ed320bcbcab03e3b1a5a92fcbb2c66d1454 Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Thu, 28 Aug 2014 15:55:39 -0700 Subject: [PATCH 31/40] Trim extraneous whitespace --- spec/core/integration/EnvSpec.js | 1 - spec/core/integration/SpecRunningSpec.js | 1 - 2 files changed, 2 deletions(-) diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 8b41fb83..89686535 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -353,7 +353,6 @@ 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']); diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index 97c3fdd7..c46793ce 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -461,7 +461,6 @@ describe("jasmine spec running", function () { }); }); - // TODO: is this useful? It doesn't catch syntax errors xit("should recover gracefully when there are errors in describe functions", function() { var specs = []; From 9afae3d9784e1c5001eb3ed04a0b5e4be2663ec7 Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Thu, 28 Aug 2014 16:16:04 -0700 Subject: [PATCH 32/40] Add a test documenting fits in fdescribes Yo, this probably isn't the best behavior. Rspec and Ginkgo definitely do not exhibit this behavior when you nest focused runnables inside other focused runnables. We thought fixing it, but it seems like a nontrivial refactoring would be necessary to clean this up. [#73742944] --- spec/core/integration/SpecRunningSpec.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index c46793ce..cd9c1fdc 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -407,6 +407,25 @@ describe("jasmine spec running", function () { env.addReporter({jasmineDone: assertions}); env.execute(); }); + + it('runs fits in fdescribes twice', function(){ + var actions = []; + + env.fdescribe('focused suite', function() { + env.fit('focused spec', function() { + actions.push('focused spec') + }); + }); + + var assertions = function() { + var expected = ['focused spec', 'focused spec']; + expect(actions).toEqual(expected); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + env.execute(); + }); }); it("shouldn't run disabled suites", function(done) { From 14824b5f9e0c3b654a8c43629e0c63d450b22449 Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Thu, 28 Aug 2014 16:37:37 -0700 Subject: [PATCH 33/40] Expose fit and fdescribe in boot.js * adds done() callbacks to existing specs (because we forgot, whoops!) [Finishes #73742944] --- lib/jasmine-core/boot.js | 8 ++++++++ lib/jasmine-core/boot/boot.js | 8 ++++++++ spec/core/integration/SpecRunningSpec.js | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/jasmine-core/boot.js b/lib/jasmine-core/boot.js index 1d6820dc..e0167319 100644 --- a/lib/jasmine-core/boot.js +++ b/lib/jasmine-core/boot.js @@ -63,6 +63,10 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. return env.xdescribe(description, specDefinitions); }, + fdescribe: function(description, specDefinitions) { + return env.fdescribe(description, specDefinitions); + }, + it: function(desc, func) { return env.it(desc, func); }, @@ -71,6 +75,10 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. return env.xit(desc, func); }, + fit: function(desc, func) { + return env.fit(desc, func); + }, + beforeEach: function(beforeEachFunction) { return env.beforeEach(beforeEachFunction); }, diff --git a/lib/jasmine-core/boot/boot.js b/lib/jasmine-core/boot/boot.js index 32bfb076..6bd8dbaf 100644 --- a/lib/jasmine-core/boot/boot.js +++ b/lib/jasmine-core/boot/boot.js @@ -41,6 +41,10 @@ return env.xdescribe(description, specDefinitions); }, + fdescribe: function(description, specDefinitions) { + return env.fdescribe(description, specDefinitions); + }, + it: function(desc, func) { return env.it(desc, func); }, @@ -49,6 +53,10 @@ return env.xit(desc, func); }, + fit: function(desc, func) { + return env.fit(desc, func); + }, + beforeEach: function(beforeEachFunction) { return env.beforeEach(beforeEachFunction); }, diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index cd9c1fdc..2de2dc2e 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -367,7 +367,7 @@ describe("jasmine spec running", function () { }); describe('focused runnables', function() { - it('runs the relevant alls and eachs for each runnable', function() { + it('runs the relevant alls and eachs for each runnable', function(done) { var actions = []; env.beforeAll(function() {actions.push('beforeAll')}); env.afterAll(function() {actions.push('afterAll')}); @@ -408,7 +408,7 @@ describe("jasmine spec running", function () { env.execute(); }); - it('runs fits in fdescribes twice', function(){ + it('runs fits in fdescribes twice', function(done){ var actions = []; env.fdescribe('focused suite', function() { From 08a35d134d10ff9ea94cc6256787cf2641870cf6 Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Thu, 28 Aug 2014 17:54:42 -0700 Subject: [PATCH 34/40] Ensure focused tests are only run once Required duplicating some of the logic for constructing a suite from describe so that we could mark a suite as focused in fdescribe, but otherwise this prevents focused tests from being run more than once. [#73742944] --- spec/core/integration/SpecRunningSpec.js | 10 ++++-- src/core/Env.js | 43 ++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index 2de2dc2e..6ed4604a 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -408,17 +408,23 @@ describe("jasmine spec running", function () { env.execute(); }); - it('runs fits in fdescribes twice', function(done){ + it('runs fits and fdescribes in fdescribes only once', function(done){ var actions = []; env.fdescribe('focused suite', function() { env.fit('focused spec', function() { actions.push('focused spec') }); + + env.fdescribe('inner focused suite', function() { + env.it('inner spec', function() { + actions.push('unfocused spec'); + }); + }); }); var assertions = function() { - var expected = ['focused spec', 'focused spec']; + var expected = ['focused spec', 'unfocused spec']; expect(actions).toEqual(expected); done(); }; diff --git a/src/core/Env.js b/src/core/Env.js index 97c3fa62..c16401d2 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -295,11 +295,44 @@ getJasmineRequireObj().Env = function(j$) { }; this.fdescribe = function(description, specDefinitions) { - var suite = this.describe(description, specDefinitions); - focusedRunnables.push(suite.id); + var suite = suiteFactory(description); + suite.isFocused = true; + + var parentSuite = currentDeclarationSuite; + parentSuite.addChild(suite); + currentDeclarationSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch (e) { + declarationError = e; + } + + if (declarationError) { + this.it('encountered a declaration exception', function() { + throw declarationError; + }); + } + + currentDeclarationSuite = parentSuite; + if (!hasFocusedAncestor(parentSuite)) { + focusedRunnables.push(suite.id); + } return suite; }; + function hasFocusedAncestor(suite) { + while (suite) { + if (suite.isFocused) { + return true; + } + suite = suite.parentSuite; + } + + return false; + } + var runnablesExplictlySet = false; var runnablesExplictlySetGetter = function(){ @@ -361,7 +394,11 @@ getJasmineRequireObj().Env = function(j$) { var focusedRunnables = []; this.fit = function(description, fn ){ var spec = this.it(description, fn); - focusedRunnables.push(spec.id); + + if (!hasFocusedAncestor(currentDeclarationSuite)) { + focusedRunnables.push(spec.id); + } + return spec; }; From 6b857d11ce0c92f7e9aac7ae3a0a17f206ea4c21 Mon Sep 17 00:00:00 2001 From: Greg Cobb and Tim Jarratt Date: Fri, 29 Aug 2014 09:25:41 -0700 Subject: [PATCH 35/40] Factor out some common logic between fdesc and desc --- src/core/Env.js | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/src/core/Env.js b/src/core/Env.js index c16401d2..8fcb091b 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -265,26 +265,7 @@ getJasmineRequireObj().Env = function(j$) { this.describe = function(description, specDefinitions) { var suite = suiteFactory(description); - - var parentSuite = currentDeclarationSuite; - parentSuite.addChild(suite); - currentDeclarationSuite = suite; - - var declarationError = null; - try { - specDefinitions.call(suite); - } catch (e) { - declarationError = e; - } - - if (declarationError) { - this.it('encountered a declaration exception', function() { - throw declarationError; - }); - } - - currentDeclarationSuite = parentSuite; - + addSpecsToSuite(suite, specDefinitions); return suite; }; @@ -297,7 +278,15 @@ getJasmineRequireObj().Env = function(j$) { 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; @@ -310,17 +299,13 @@ getJasmineRequireObj().Env = function(j$) { } if (declarationError) { - this.it('encountered a declaration exception', function() { + self.it('encountered a declaration exception', function() { throw declarationError; }); } currentDeclarationSuite = parentSuite; - if (!hasFocusedAncestor(parentSuite)) { - focusedRunnables.push(suite.id); - } - return suite; - }; + } function hasFocusedAncestor(suite) { while (suite) { From 9402d59859ca6214695546b9066f69255e1e921d Mon Sep 17 00:00:00 2001 From: slackersoft Date: Wed, 3 Sep 2014 18:52:13 -0700 Subject: [PATCH 36/40] Suites report errors in `afterAll`s in the `suiteDone` event - remove `afterAllEvent` from reporters --- lib/console/console.js | 28 ++-- lib/jasmine-core/jasmine-html.js | 21 +-- lib/jasmine-core/jasmine.js | 194 +++++++++++++++++++--------- spec/console/ConsoleReporterSpec.js | 12 +- spec/core/QueueRunnerSpec.js | 16 --- spec/core/SuiteSpec.js | 3 +- spec/core/integration/EnvSpec.js | 111 +++++++++------- spec/html/HtmlReporterSpec.js | 10 +- src/console/ConsoleReporter.js | 27 ++-- src/core/Env.js | 22 +--- src/core/QueueRunner.js | 2 - src/core/Spec.js | 1 - src/core/Suite.js | 41 ++++-- src/html/HtmlReporter.js | 21 +-- 14 files changed, 306 insertions(+), 203 deletions(-) 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'); From 137c1a39f68b2ab87fe324b45e97fcd020ab84f6 Mon Sep 17 00:00:00 2001 From: slackersoft Date: Thu, 4 Sep 2014 08:06:24 -0700 Subject: [PATCH 37/40] Check afterAll failure messages with regexp, since some browsers add more info --- spec/core/integration/EnvSpec.js | 37 +++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 986bd859..79e943ce 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -383,11 +383,19 @@ describe("Env integration", function() { matcherName : '', expected : '', actual : '', - message : 'Error: After All Exception', + message : jasmine.any(String), stack : jasmine.any(String), passed: false }] })); + + for (var i = 0; i < reporter.suiteDone.calls.count(); i++) { + var args = reporter.suiteDone.calls.argsFor(i); + if (args.description === 'my suite') { + expect(args.failedExpectations[0].message).toMatch(/^Error: After All Exception/); + } + } + done(); }); @@ -451,11 +459,19 @@ describe("Env integration", function() { matcherName : '', expected : '', actual : '', - message : 'Error: After All Exception', + message : jasmine.any(String), stack : jasmine.any(String), passed: false }] })); + + for (var i = 0; i < reporter.suiteDone.calls.count(); i++) { + var args = reporter.suiteDone.calls.argsFor(i); + if (args.description === 'my suite') { + expect(args.failedExpectations[0].message).toMatch(/^Error: After All Exception/); + } + } + done(); }); @@ -765,15 +781,16 @@ describe("Env integration", function() { reporter.jasmineDone.and.callFake(function() { 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 - }] + description: 'my suite', })); + + for (var i = 0; i < reporter.suiteDone.calls.count(); i++) { + var args = reporter.suiteDone.calls.argsFor(i); + if (args.description === 'my suite') { + expect(args.failedExpectations[0].message).toMatch(/^Error: Timeout - Async callback was not invoked within timeout specified by jasmine\.DEFAULT_TIMEOUT_INTERVAL\./); + } + } + done(); }); From 3b19d662686ff358f0b211c1cd2a1e08f4d9272f Mon Sep 17 00:00:00 2001 From: slackersoft Date: Thu, 4 Sep 2014 18:49:14 -0700 Subject: [PATCH 38/40] Check for afterAll failures in a more browser independent way. - Only look at the message, so browsers that don't provide a stack don't blow up. --- spec/core/integration/EnvSpec.js | 135 +++++++++++++------------------ 1 file changed, 56 insertions(+), 79 deletions(-) diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 79e943ce..d5c818ee 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -1,4 +1,44 @@ describe("Env integration", function() { + beforeEach(function() { + jasmine.addMatchers({ + toHaveFailedExpecationsForSuite: function(util, customeEqualityTesters) { + return { + compare: function(actual, suiteName, expectedFailures) { + var foundSuite = false, expectations = true, foundFailures = []; + for (var i = 0; i < actual.calls.count(); i++) { + var args = actual.calls.argsFor(i)[0]; + + if (args.description === suiteName) { + foundSuite = true; + + for (var j = 0; j < args.failedExpectations.length; j++) { + foundFailures.push(args.failedExpectations[j].message); + } + + for (var j = 0; j < expectedFailures.length; j++) { + var failure = foundFailures[j]; + var expectedFailure = expectedFailures[j]; + + if (Object.prototype.toString.call(expectedFailure) === '[object RegExp]') { + expectations = expectations && expectedFailure.test(failure); + } else { + expectations = expectations && failure === expectedFailure; + } + } + break; + } + } + + return { + pass: foundSuite && expectations, + message: !foundSuite ? 'The suite "' + suiteName + '" never finished' : + 'Expected suite "' + suiteName + '" to have failures ' + jasmine.pp(expectedFailures) + ' but it had ' + jasmine.pp(foundFailures) + }; + } + }; + } + }); + }); it("Suites execute as expected (no nesting)", function(done) { var env = new j$.Env(), @@ -333,26 +373,10 @@ describe("Env integration", function() { reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); reporter.jasmineDone.and.callFake(function() { - 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 - } - ] - })); + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ + 'Expected 1 to equal 2.', + 'Expected 2 to equal 3.' + ]); done(); }); @@ -377,25 +401,9 @@ describe("Env integration", function() { reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.suiteDone).toHaveBeenCalledWith(jasmine.objectContaining({ - description: 'my suite', - failedExpectations: [{ - matcherName : '', - expected : '', - actual : '', - message : jasmine.any(String), - stack : jasmine.any(String), - passed: false - }] - })); - - for (var i = 0; i < reporter.suiteDone.calls.count(); i++) { - var args = reporter.suiteDone.calls.argsFor(i); - if (args.description === 'my suite') { - expect(args.failedExpectations[0].message).toMatch(/^Error: After All Exception/); - } - } - + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ + (/^Error: After All Exception/) + ]); done(); }); @@ -418,16 +426,9 @@ describe("Env integration", function() { reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); reporter.jasmineDone.and.callFake(function() { - 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 - }] - })); + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ + 'Expected 1 to equal 2.' + ]); done(); }); @@ -453,25 +454,9 @@ describe("Env integration", function() { reporter.jasmineDone.and.callFake(function() { - expect(reporter.suiteDone).toHaveBeenCalledWith(jasmine.objectContaining({ - description: 'my suite', - failedExpectations: [{ - matcherName : '', - expected : '', - actual : '', - message : jasmine.any(String), - stack : jasmine.any(String), - passed: false - }] - })); - - for (var i = 0; i < reporter.suiteDone.calls.count(); i++) { - var args = reporter.suiteDone.calls.argsFor(i); - if (args.description === 'my suite') { - expect(args.failedExpectations[0].message).toMatch(/^Error: After All Exception/); - } - } - + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ + (/^Error: After All Exception/) + ]); done(); }); @@ -780,17 +765,9 @@ describe("Env integration", function() { reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.suiteDone).toHaveBeenCalledWith(jasmine.objectContaining({ - description: 'my suite', - })); - - for (var i = 0; i < reporter.suiteDone.calls.count(); i++) { - var args = reporter.suiteDone.calls.argsFor(i); - if (args.description === 'my suite') { - expect(args.failedExpectations[0].message).toMatch(/^Error: Timeout - Async callback was not invoked within timeout specified by jasmine\.DEFAULT_TIMEOUT_INTERVAL\./); - } - } - + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ + (/^Error: Timeout - Async callback was not invoked within timeout specified by jasmine\.DEFAULT_TIMEOUT_INTERVAL\./) + ]); done(); }); From 3332f93a2407993cd2be4d0d8b4f89bfadb99440 Mon Sep 17 00:00:00 2001 From: Greg Cobb Date: Fri, 5 Sep 2014 12:25:28 -0700 Subject: [PATCH 39/40] Only run focused runnables inside focused suites - Focused runnables now walk up the tree to unfocus the first focused ancestor. Because of the way the tree is constructed, this makes sure that each focused runnable has no focused ancestors. [#78289686] --- lib/jasmine-core/jasmine.js | 31 +++++++++----- spec/core/integration/SpecRunningSpec.js | 51 ++++++++++++++++++++++-- src/core/Env.js | 33 ++++++++++----- 3 files changed, 91 insertions(+), 24 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 61f83f34..e33c4a67 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -646,11 +646,11 @@ getJasmineRequireObj().Env = function(j$) { this.fdescribe = function(description, specDefinitions) { var suite = suiteFactory(description); suite.isFocused = true; + + focusedRunnables.push(suite.id); + unfocusAncestors(); addSpecsToSuite(suite, specDefinitions); - if (!hasFocusedAncestor(suite.parentSuite)) { - focusedRunnables.push(suite.id); - } return suite; }; @@ -675,15 +675,27 @@ getJasmineRequireObj().Env = function(j$) { currentDeclarationSuite = parentSuite; } - function hasFocusedAncestor(suite) { + function findFocusedAncestor(suite) { while (suite) { if (suite.isFocused) { - return true; + return suite.id; } suite = suite.parentSuite; } - return false; + return null; + } + + function unfocusAncestors() { + var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); + if (focusedAncestor) { + for (var i = 0; i < focusedRunnables.length; i++) { + if (focusedRunnables[i] === focusedAncestor) { + focusedRunnables.splice(i, 1); + break; + } + } + } } var runnablesExplictlySet = false; @@ -744,13 +756,12 @@ getJasmineRequireObj().Env = function(j$) { }; var focusedRunnables = []; + this.fit = function(description, fn ){ var spec = this.it(description, fn); - if (!hasFocusedAncestor(currentDeclarationSuite)) { - focusedRunnables.push(spec.id); - } - + focusedRunnables.push(spec.id); + unfocusAncestors(); return spec; }; diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index 6ed4604a..e1675d00 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -408,23 +408,68 @@ describe("jasmine spec running", function () { env.execute(); }); - it('runs fits and fdescribes in fdescribes only once', function(done){ + it('focused specs in focused suites cause non-focused siblings to not run', function(done){ var actions = []; env.fdescribe('focused suite', function() { + env.it('unfocused spec', function() { + actions.push('unfocused spec') + }); env.fit('focused spec', function() { actions.push('focused spec') }); + }); + var assertions = function() { + var expected = ['focused spec']; + expect(actions).toEqual(expected); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + env.execute(); + }); + + it('focused suites in focused suites cause non-focused siblings to not run', function(done){ + var actions = []; + + env.fdescribe('focused suite', function() { + env.it('unfocused spec', function() { + actions.push('unfocused spec') + }); env.fdescribe('inner focused suite', function() { env.it('inner spec', function() { - actions.push('unfocused spec'); + actions.push('inner spec'); }); }); }); var assertions = function() { - var expected = ['focused spec', 'unfocused spec']; + var expected = ['inner spec']; + expect(actions).toEqual(expected); + done(); + }; + + env.addReporter({jasmineDone: assertions}); + env.execute(); + }); + + it('focused runnables unfocus ancestor focused suites', function() { + var actions = []; + + env.fdescribe('focused suite', function() { + env.it('unfocused spec', function() { + actions.push('unfocused spec') + }); + env.describe('inner focused suite', function() { + env.fit('focused spec', function() { + actions.push('focused spec'); + }); + }); + }); + + var assertions = function() { + var expected = ['focused spec']; expect(actions).toEqual(expected); done(); }; diff --git a/src/core/Env.js b/src/core/Env.js index 7e676d4d..c7a1c175 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -270,14 +270,16 @@ getJasmineRequireObj().Env = function(j$) { return suite; }; + var focusedRunnables = []; + this.fdescribe = function(description, specDefinitions) { var suite = suiteFactory(description); suite.isFocused = true; + + focusedRunnables.push(suite.id); + unfocusAncestor(); addSpecsToSuite(suite, specDefinitions); - if (!hasFocusedAncestor(suite.parentSuite)) { - focusedRunnables.push(suite.id); - } return suite; }; @@ -302,15 +304,27 @@ getJasmineRequireObj().Env = function(j$) { currentDeclarationSuite = parentSuite; } - function hasFocusedAncestor(suite) { + function findFocusedAncestor(suite) { while (suite) { if (suite.isFocused) { - return true; + return suite.id; } suite = suite.parentSuite; } - return false; + return null; + } + + function unfocusAncestor() { + var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); + if (focusedAncestor) { + for (var i = 0; i < focusedRunnables.length; i++) { + if (focusedRunnables[i] === focusedAncestor) { + focusedRunnables.splice(i, 1); + break; + } + } + } } var runnablesExplictlySet = false; @@ -370,14 +384,11 @@ 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); - } - + focusedRunnables.push(spec.id); + unfocusAncestor(); return spec; }; From 1290d93b6aee461d4d74d604370058b33dc2d741 Mon Sep 17 00:00:00 2001 From: Greg Cobb Date: Tue, 9 Sep 2014 11:10:30 -0700 Subject: [PATCH 40/40] Suite result status added when suite is complete - This makes it easier to detect afterAll failures, because we can rely only complete runnables having statuses [#78306786 & #73741654] --- lib/jasmine-core/jasmine.js | 12 +- spec/core/SuiteSpec.js | 2 +- spec/core/integration/EnvSpec.js | 196 ++++++++++++++++++------------- src/core/Suite.js | 2 +- 4 files changed, 121 insertions(+), 91 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index e33c4a67..ffb70fee 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -643,12 +643,14 @@ getJasmineRequireObj().Env = function(j$) { return suite; }; + var focusedRunnables = []; + this.fdescribe = function(description, specDefinitions) { var suite = suiteFactory(description); suite.isFocused = true; focusedRunnables.push(suite.id); - unfocusAncestors(); + unfocusAncestor(); addSpecsToSuite(suite, specDefinitions); return suite; @@ -686,7 +688,7 @@ getJasmineRequireObj().Env = function(j$) { return null; } - function unfocusAncestors() { + function unfocusAncestor() { var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); if (focusedAncestor) { for (var i = 0; i < focusedRunnables.length; i++) { @@ -755,13 +757,11 @@ getJasmineRequireObj().Env = function(j$) { return spec; }; - var focusedRunnables = []; - this.fit = function(description, fn ){ var spec = this.it(description, fn); focusedRunnables.push(spec.id); - unfocusAncestors(); + unfocusAncestor(); return spec; }; @@ -1877,7 +1877,6 @@ getJasmineRequireObj().Suite = function() { this.result = { id: this.id, - status: this.disabled ? 'disabled' : '', description: this.description, fullName: this.getFullName(), failedExpectations: [] @@ -1951,6 +1950,7 @@ getJasmineRequireObj().Suite = function() { }); function complete() { + self.result.status = self.disabled ? 'disabled' : 'finished'; self.resultCallback(self.result); if (onComplete) { diff --git a/spec/core/SuiteSpec.js b/spec/core/SuiteSpec.js index e0dc9649..353fb52d 100644 --- a/spec/core/SuiteSpec.js +++ b/spec/core/SuiteSpec.js @@ -260,7 +260,7 @@ describe("Suite", function() { expect(suiteResultsCallback).toHaveBeenCalledWith({ id: suite.id, - status: '', + status: 'finished', description: "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 d5c818ee..58b6fba9 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -368,110 +368,140 @@ describe("Env integration", function() { env.execute(); }); - it("reports when an afterAll fails an expectation", function(done) { - var env = new j$.Env(), - reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); + describe('suiteDone reporting', function(){ + it("reports when an afterAll fails an expectation", function(done) { + var env = new j$.Env(), + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); - reporter.jasmineDone.and.callFake(function() { - expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ - 'Expected 1 to equal 2.', - 'Expected 2 to equal 3.' - ]); - done(); - }); - - env.addReporter(reporter); - - env.describe('my suite', function() { - env.it('my spec', function() { + reporter.jasmineDone.and.callFake(function() { + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ + 'Expected 1 to equal 2.', + 'Expected 2 to equal 3.' + ]); + done(); }); - env.afterAll(function() { - env.expect(1).toEqual(2); - env.expect(2).toEqual(3); - }); - }); + env.addReporter(reporter); - env.execute(); - }); + env.describe('my suite', function() { + env.it('my spec', function() { + }); - 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','suiteDone']); - - reporter.jasmineDone.and.callFake(function() { - expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ - (/^Error: After All Exception/) - ]); - done(); - }); - - env.addReporter(reporter); - - env.describe('my suite', function() { - env.it('my spec', function() { + env.afterAll(function() { + env.expect(1).toEqual(2); + env.expect(2).toEqual(3); + }); }); - env.afterAll(function() { - throw error; - }); + env.execute(); }); - env.execute(); - }); + it("if there are no specs, it still reports correctly", function(done) { + var env = new j$.Env(), + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); - it("reports when an async afterAll fails an expectation", function(done) { - var env = new j$.Env(), - reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); + reporter.jasmineDone.and.callFake(function() { + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('outer suite', [ + 'Expected 1 to equal 2.', + 'Expected 2 to equal 3.' + ]); + done(); + }); - reporter.jasmineDone.and.callFake(function() { - expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ - 'Expected 1 to equal 2.' - ]); - done(); - }); + env.addReporter(reporter); - env.addReporter(reporter); + env.describe('outer suite', function() { + env.describe('inner suite', function() { + env.it('spec', function(){ }); + }); - env.describe('my suite', function() { - env.it('my spec', function() { + env.afterAll(function() { + env.expect(1).toEqual(2); + env.expect(2).toEqual(3); + }); + }); + + env.execute(); }); - env.afterAll(function(afterAllDone) { - env.expect(1).toEqual(2); - afterAllDone(); - }); - }); + 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','suiteDone']); - env.execute(); - }); - - 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','suiteDone']); - - - reporter.jasmineDone.and.callFake(function() { - expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ - (/^Error: After All Exception/) - ]); - done(); - }); - - env.addReporter(reporter); - - env.describe('my suite', function() { - env.it('my spec', function() { + reporter.jasmineDone.and.callFake(function() { + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ + (/^Error: After All Exception/) + ]); + done(); }); - env.afterAll(function(afterAllDone) { - throw error; + env.addReporter(reporter); + + env.describe('my suite', function() { + env.it('my spec', function() { + }); + + env.afterAll(function() { + throw error; + }); }); + + env.execute(); }); - env.execute(); + it("reports when an async afterAll fails an expectation", function(done) { + var env = new j$.Env(), + reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ + 'Expected 1 to equal 2.' + ]); + done(); + }); + + env.addReporter(reporter); + + env.describe('my suite', function() { + env.it('my spec', function() { + }); + + env.afterAll(function(afterAllDone) { + env.expect(1).toEqual(2); + afterAllDone(); + }); + }); + + env.execute(); + }); + + 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','suiteDone']); + + + reporter.jasmineDone.and.callFake(function() { + expect(reporter.suiteDone).toHaveFailedExpecationsForSuite('my suite', [ + (/^Error: After All Exception/) + ]); + done(); + }); + + env.addReporter(reporter); + + env.describe('my suite', function() { + env.it('my spec', function() { + }); + + env.afterAll(function(afterAllDone) { + throw error; + }); + }); + + env.execute(); + }); }); it("Allows specifying which specs and suites to run", function(done) { diff --git a/src/core/Suite.js b/src/core/Suite.js index bd96afad..fb83f4f5 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -21,7 +21,6 @@ getJasmineRequireObj().Suite = function() { this.result = { id: this.id, - status: this.disabled ? 'disabled' : '', description: this.description, fullName: this.getFullName(), failedExpectations: [] @@ -95,6 +94,7 @@ getJasmineRequireObj().Suite = function() { }); function complete() { + self.result.status = self.disabled ? 'disabled' : 'finished'; self.resultCallback(self.result); if (onComplete) {