From a526ebf26179afaad719e73de198c265031e5241 Mon Sep 17 00:00:00 2001 From: "Davis W. Frank & Rajan Agaskar" Date: Fri, 7 Dec 2012 16:30:32 -0800 Subject: [PATCH] Re-add async support (achieved via done callbacks) - TODO: pull out queueRunner into a new object. --- lib/jasmine-core/boot/boot.js | 8 +- lib/jasmine-core/jasmine.js | 71 +++++++----- spec/core/SpecSpec.js | 212 +++++++++++++++++++++------------- spec/node_suite.js | 5 + src/core/Spec.js | 71 +++++++----- 5 files changed, 226 insertions(+), 141 deletions(-) diff --git a/lib/jasmine-core/boot/boot.js b/lib/jasmine-core/boot/boot.js index e3f95062..550544be 100644 --- a/lib/jasmine-core/boot/boot.js +++ b/lib/jasmine-core/boot/boot.js @@ -38,9 +38,11 @@ return env.spyOn(obj, methodName); }, - clock: function() { - return env.clock; - }, + clock: env.clock, + setTimeout: env.clock.setTimeout, + clearTimeout: env.clock.clearTimeout, + setInterval: env.clock.setInterval, + clearInterval: env.clock.clearInterval, jsApiReporter: new jasmine.JsApiReporter(jasmine) }; diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 49a7dc06..c7ba5ed7 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -1958,47 +1958,60 @@ jasmine.Spec.prototype.expect = function(actual) { }; jasmine.Spec.prototype.execute = function() { + var self = this; if (this.disabled) { - resultCallback.call(this); + resultCallback(); return; } var befores = this.beforeFns() || [], afters = this.afterFns() || []; this.startCallback(this); - try { - for (var i = 0; i < befores.length; i++) { - befores[i].call(this); - } - this.fn.call(this); - for (i = 0; i < afters.length; i++) { - afters[i].call(this); - } - } catch (e) { - //TODO: weird. buildExpectationResult is really a presenter for expectations - //so this should take an expectation object. - this.addExpectationResult(false, this.expectationResultFactory({ - matcherName: "", - passed: false, - expected: "", - actual: "", - message: this.exceptionFormatter(e), - trace: e - })); - if (!this.catchExceptions) { - throw e; + var allFns = befores.concat(this.fn).concat(afters); + + queueRunner(allFns, 0); + + function attempt(fn) { + try { + fn(); + } catch (e) { + //TODO: weird. buildExpectationResult is really a presenter for expectations + //so this should take an expectation object. + self.addExpectationResult(false, self.expectationResultFactory({ + matcherName: "", + passed: false, + expected: "", + actual: "", + message: self.exceptionFormatter(e), + trace: e + })); + if (!self.catchExceptions) { + resultCallback(); + throw e; + } } } - finally { - resultCallback.call(this); + + function queueRunner(allFns, index) { + if (index >= allFns.length) { + resultCallback(); + return; + } + var fn = allFns[index]; + if (fn.length > 0) { + attempt(function() { fn.call(self, function() { queueRunner(allFns, index + 1) }) }); + } else { + attempt(function() { fn.call(self); }); + queueRunner(allFns, index + 1); + } } function resultCallback() { - this.resultCallback({ - id: this.id, - status: this.status(), - description: this.description, - failedExpectations: this.failedExpectations + self.resultCallback({ + id: self.id, + status: self.status(), + description: self.description, + failedExpectations: self.failedExpectations }); } }; diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index 0b5c6ab0..68ce2f87 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -1,23 +1,23 @@ -describe("Spec (integration)", function() { +describe("Spec", function() { it("reports results for passing tests", function() { - var resultCallback = jasmine.createSpy('resultCallback'), - expectationFactory = function(actual, spec) { - expect(actual).toBe('some-actual'); - return { - pass: function() { - spec.addExpectationResult(true); - } + var resultCallback = originalJasmine.createSpy('resultCallback'), + expectationFactory = function(actual, spec) { + expect(actual).toBe('some-actual'); + return { + pass: function() { + spec.addExpectationResult(true); } + } + }, + spec = new jasmine.Spec({ + description: 'my test', + id: 'some-id', + fn: function() { + this.expect('some-actual').pass(); }, - spec = new jasmine.Spec({ - description: 'my test', - id: 'some-id', - fn: function() { - this.expect('some-actual').pass(); - }, - resultCallback: resultCallback, - expectationFactory: expectationFactory - }); + resultCallback: resultCallback, + expectationFactory: expectationFactory + }); spec.execute(); @@ -29,24 +29,24 @@ describe("Spec (integration)", function() { }); }); it("reports results for failing tests", function() { - var resultCallback = jasmine.createSpy('resultCallback'), - expectationFactory = function(actual, spec) { - expect(actual).toBe('some-actual'); - return { - fail: function() { - spec.addExpectationResult(true); - } + var resultCallback = originalJasmine.createSpy('resultCallback'), + expectationFactory = function(actual, spec) { + expect(actual).toBe('some-actual'); + return { + fail: function() { + spec.addExpectationResult(true); } + } + }, + spec = new jasmine.Spec({ + description: 'my test', + id: 'some-id', + fn: function() { + this.expect('some-actual').fail(); }, - spec = new jasmine.Spec({ - description: 'my test', - id: 'some-id', - fn: function() { - this.expect('some-actual').fail(); - }, - resultCallback: resultCallback, - expectationFactory: expectationFactory - }); + resultCallback: resultCallback, + expectationFactory: expectationFactory + }); spec.execute(); @@ -58,14 +58,13 @@ describe("Spec (integration)", function() { }); }); - //TODO: test order of befores, spec, after. it("executes before fns, after fns", function() { - var before = jasmine.createSpy('before'), - after = jasmine.createSpy('after'), - fn = originalJasmine.createSpy('test body').andCallFake(function() { - expect(before).toHaveBeenCalled(); - expect(after).not.toHaveBeenCalled(); - }); + var before = originalJasmine.createSpy('before'), + after = originalJasmine.createSpy('after'), + fn = originalJasmine.createSpy('test body').andCallFake(function() { + expect(before).toHaveBeenCalled(); + expect(after).not.toHaveBeenCalled(); + }); spec = new jasmine.Spec({ fn: fn, beforeFns: function() { @@ -86,9 +85,61 @@ describe("Spec (integration)", function() { expect(after).toHaveBeenCalled(); expect(after.mostRecentCall.object).toBe(spec); }); -}); -describe("Spec (real-ish unit tests)", function() { + it("passes an optional callback to spec bodies, befores, and afters", function() { + //TODO: it would be nice if spy arity could match the fake, so we could do something like: + //createSpy('asyncfn').andCallFake(function(done) {}); + var resultCallback = originalJasmine.createSpy('resultCallback'), + beforeCallback = originalJasmine.createSpy('beforeCallback'), + fnCallback = originalJasmine.createSpy('fnCallback'), + afterCallback = originalJasmine.createSpy('afterCallback'), + before = function(done) { + beforeCallback(); + setTimeout(function() { done() }, 100); + }, + fn = function(done) { + fnCallback(); + setTimeout(function() { done() }, 100); + }, + after = function(done) { + afterCallback(); + setTimeout(function() { done() }, 100); + }, + spec = new jasmine.Spec({ + resultCallback: resultCallback, + fn: fn, + beforeFns: function() { + return [before] + }, + afterFns: function() { + return [after] + } + }); + clock.install(); + + spec.execute(); + + expect(beforeCallback).toHaveBeenCalled(); + expect(fnCallback).not.toHaveBeenCalled(); + expect(afterCallback).not.toHaveBeenCalled(); + expect(resultCallback).not.toHaveBeenCalled(); + + clock.tick(100); + + expect(fnCallback).toHaveBeenCalled(); + expect(afterCallback).not.toHaveBeenCalled(); + expect(resultCallback).not.toHaveBeenCalled(); + + clock.tick(100); + + expect(afterCallback).toHaveBeenCalled(); + expect(resultCallback).not.toHaveBeenCalled(); + + clock.tick(100); + + expect(resultCallback).toHaveBeenCalled(); + }); + it("status returns null by default", function() { var spec = new jasmine.Spec({}); expect(spec.status()).toBeNull(); @@ -110,13 +161,13 @@ describe("Spec (real-ish unit tests)", function() { it("calls the resultCallback with a failure when an exception occurs in the spec fn", function() { //TODO: one day we should pass a stack with this. var resultCallback = originalJasmine.createSpy('resultCallback'), - spec = new jasmine.Spec({ - fn: function() { - throw new Error(); - }, - catchExceptions: true, - resultCallback: resultCallback - }); + spec = new jasmine.Spec({ + fn: function() { + throw new Error(); + }, + catchExceptions: true, + resultCallback: resultCallback + }); expect(resultCallback).not.toHaveBeenCalled(); spec.execute(); @@ -129,13 +180,13 @@ describe("Spec (real-ish unit tests)", function() { it("throws when an exception occurs in the spec fn if catchExceptions is false", function() { //TODO: one day we should pass a stack with this. var resultCallback = originalJasmine.createSpy('resultCallback'), - spec = new jasmine.Spec({ - fn: function() { - throw new Error(); - }, - catchExceptions: false, - resultCallback: resultCallback - }); + spec = new jasmine.Spec({ + fn: function() { + throw new Error(); + }, + catchExceptions: false, + resultCallback: resultCallback + }); expect(function() { spec.execute(); @@ -144,22 +195,22 @@ describe("Spec (real-ish unit tests)", function() { it("should call the start callback before any befores are called", function() { var beforesWereCalled = false, - startCallback = originalJasmine.createSpy('start-callback').andCallFake(function() { - expect(beforesWereCalled).toBe(false); - }), - spec = new jasmine.Spec({ - fn: function() { - }, - beforeFns: function() { - return [function() { - beforesWereCalled = true - }] - }, - startCallback: startCallback, - catchExceptions: false, - resultCallback: function() { - } - }); + startCallback = originalJasmine.createSpy('start-callback').andCallFake(function() { + expect(beforesWereCalled).toBe(false); + }), + spec = new jasmine.Spec({ + fn: function() { + }, + beforeFns: function() { + return [function() { + beforesWereCalled = true + }] + }, + startCallback: startCallback, + catchExceptions: false, + resultCallback: function() { + } + }); spec.execute(); expect(startCallback).toHaveBeenCalled(); @@ -177,15 +228,15 @@ describe("Spec (real-ish unit tests)", function() { }); it("can be disabled", function() { - var startCallback = jasmine.createSpy('startCallback'), - specBody = jasmine.createSpy('specBody'), - resultCallback = jasmine.createSpy('resultCallback'), - spec = new jasmine.Spec({ - startCallback: startCallback, - fn: specBody, - resultCallback: resultCallback + var startCallback = originalJasmine.createSpy('startCallback'), + specBody = originalJasmine.createSpy('specBody'), + resultCallback = originalJasmine.createSpy('resultCallback'), + spec = new jasmine.Spec({ + startCallback: startCallback, + fn: specBody, + resultCallback: resultCallback - }); + }); spec.disable(); expect(spec.status()).toBe('disabled'); @@ -198,3 +249,4 @@ describe("Spec (real-ish unit tests)", function() { expect(resultCallback).toHaveBeenCalled(); }); }); + diff --git a/spec/node_suite.js b/spec/node_suite.js index 5ae018fe..17c66512 100644 --- a/spec/node_suite.js +++ b/spec/node_suite.js @@ -49,6 +49,11 @@ var jasmineInterface = { return env.spyOn(obj, methodName); }, + clock: env.clock, + setTimeout: env.clock.setTimeout, + clearTimeout: env.clock.clearTimeout, + setInterval: env.clock.setInterval, + clearInterval: env.clock.clearInterval, jsApiReporter: new jasmine.JsApiReporter(jasmine) }; diff --git a/src/core/Spec.js b/src/core/Spec.js index 65817175..b95158ca 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -27,47 +27,60 @@ jasmine.Spec.prototype.expect = function(actual) { }; jasmine.Spec.prototype.execute = function() { + var self = this; if (this.disabled) { - resultCallback.call(this); + resultCallback(); return; } var befores = this.beforeFns() || [], afters = this.afterFns() || []; this.startCallback(this); - try { - for (var i = 0; i < befores.length; i++) { - befores[i].call(this); - } - this.fn.call(this); - for (i = 0; i < afters.length; i++) { - afters[i].call(this); - } - } catch (e) { - //TODO: weird. buildExpectationResult is really a presenter for expectations - //so this should take an expectation object. - this.addExpectationResult(false, this.expectationResultFactory({ - matcherName: "", - passed: false, - expected: "", - actual: "", - message: this.exceptionFormatter(e), - trace: e - })); - if (!this.catchExceptions) { - throw e; + var allFns = befores.concat(this.fn).concat(afters); + + queueRunner(allFns, 0); + + function attempt(fn) { + try { + fn(); + } catch (e) { + //TODO: weird. buildExpectationResult is really a presenter for expectations + //so this should take an expectation object. + self.addExpectationResult(false, self.expectationResultFactory({ + matcherName: "", + passed: false, + expected: "", + actual: "", + message: self.exceptionFormatter(e), + trace: e + })); + if (!self.catchExceptions) { + resultCallback(); + throw e; + } } } - finally { - resultCallback.call(this); + + function queueRunner(allFns, index) { + if (index >= allFns.length) { + resultCallback(); + return; + } + var fn = allFns[index]; + if (fn.length > 0) { + attempt(function() { fn.call(self, function() { queueRunner(allFns, index + 1) }) }); + } else { + attempt(function() { fn.call(self); }); + queueRunner(allFns, index + 1); + } } function resultCallback() { - this.resultCallback({ - id: this.id, - status: this.status(), - description: this.description, - failedExpectations: this.failedExpectations + self.resultCallback({ + id: self.id, + status: self.status(), + description: self.description, + failedExpectations: self.failedExpectations }); } };