diff --git a/spec/core/ExceptionsSpec.js b/spec/core/ExceptionsSpec.js index ba770457..e0a141a6 100644 --- a/spec/core/ExceptionsSpec.js +++ b/spec/core/ExceptionsSpec.js @@ -5,64 +5,42 @@ describe('Exceptions:', function() { env = new jasmineUnderTest.Env(); }); - describe('with break on exception', function() { - it('should not catch the exception', function() { - env.catchExceptions(false); - env.describe('suite for break on exceptions', function() { - env.it('should break when an exception is thrown', function() { - throw new Error('I should hit a breakpoint!'); - }); + it('should handle exceptions thrown, but continue', function(done) { + var secondTest = jasmine.createSpy('second test'); + env.describe('Suite for handles exceptions', function () { + env.it('should be a test that fails because it throws an exception', function() { + throw new Error(); }); - var spy = jasmine.createSpy('spy'); - - try { - env.execute(); - spy(); - } - catch (e) {} - - expect(spy).not.toHaveBeenCalled(); + env.it('should be a passing test that runs after exceptions are thrown from a async test', secondTest); }); + + var expectations = function() { + expect(secondTest).toHaveBeenCalled(); + done(); + }; + + env.addReporter({ jasmineDone: expectations }); + env.execute(); }); - describe("with catch on exception", function() { - it('should handle exceptions thrown, but continue', function(done) { - var secondTest = jasmine.createSpy('second test'); - env.describe('Suite for handles exceptions', function () { - env.it('should be a test that fails because it throws an exception', function() { - throw new Error(); - }); - env.it('should be a passing test that runs after exceptions are thrown from a async test', secondTest); + it("should handle exceptions thrown directly in top-level describe blocks and continue", function(done) { + var secondDescribe = jasmine.createSpy("second describe"); + env.describe("a suite that throws an exception", function () { + env.it("is a test that should pass", function () { + this.expect(true).toEqual(true); }); - var expectations = function() { - expect(secondTest).toHaveBeenCalled(); - done(); - }; - - env.addReporter({ jasmineDone: expectations }); - env.execute(); + throw new Error("top level error"); }); + env.describe("a suite that doesn't throw an exception", secondDescribe); - it("should handle exceptions thrown directly in top-level describe blocks and continue", function(done) { - var secondDescribe = jasmine.createSpy("second describe"); - env.describe("a suite that throws an exception", function () { - env.it("is a test that should pass", function () { - this.expect(true).toEqual(true); - }); + var expectations = function() { + expect(secondDescribe).toHaveBeenCalled(); + done(); + }; - throw new Error("top level error"); - }); - env.describe("a suite that doesn't throw an exception", secondDescribe); - - var expectations = function() { - expect(secondDescribe).toHaveBeenCalled(); - done(); - }; - - env.addReporter({ jasmineDone: expectations }); - env.execute(); - }); + env.addReporter({ jasmineDone: expectations }); + env.execute(); }); }); diff --git a/spec/core/ReportDispatcherSpec.js b/spec/core/ReportDispatcherSpec.js index cb7a540a..43db5ffa 100644 --- a/spec/core/ReportDispatcherSpec.js +++ b/spec/core/ReportDispatcherSpec.js @@ -9,70 +9,125 @@ describe("ReportDispatcher", function() { }); it("dispatches requested methods to added reporters", function() { - var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar']), + var queueRunnerFactory = jasmine.createSpy('queueRunner'), + dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar'], queueRunnerFactory), reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']), - anotherReporter = jasmine.createSpyObj('reporter', ['foo', 'bar']); + anotherReporter = jasmine.createSpyObj('reporter', ['foo', 'bar']), + completeCallback = jasmine.createSpy('complete'); dispatcher.addReporter(reporter); dispatcher.addReporter(anotherReporter); - dispatcher.foo(123, 456); + dispatcher.foo(123, 456, completeCallback); + expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ + queueableFns: [{fn: jasmine.any(Function)}, {fn: jasmine.any(Function)}] + })); + + var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; + fns[0].fn(); expect(reporter.foo).toHaveBeenCalledWith(123, 456); + expect(reporter.foo.calls.mostRecent().object).toBe(reporter); + + fns[1].fn(); expect(anotherReporter.foo).toHaveBeenCalledWith(123, 456); + expect(anotherReporter.foo.calls.mostRecent().object).toBe(anotherReporter); - dispatcher.bar('a', 'b'); + queueRunnerFactory.calls.reset(); + dispatcher.bar('a', 'b', completeCallback); + + expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ + queueableFns: [{fn: jasmine.any(Function)}, {fn: jasmine.any(Function)}] + })); + + fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; + fns[0].fn(); expect(reporter.bar).toHaveBeenCalledWith('a', 'b'); + + fns[1].fn(); expect(anotherReporter.bar).toHaveBeenCalledWith('a', 'b'); }); it("does not dispatch to a reporter if the reporter doesn't accept the method", function() { - var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo']), + var queueRunnerFactory = jasmine.createSpy('queueRunner'), + dispatcher = new jasmineUnderTest.ReportDispatcher(['foo'], queueRunnerFactory), reporter = jasmine.createSpyObj('reporter', ['baz']); dispatcher.addReporter(reporter); - expect(function() { - dispatcher.foo(123, 456); - }).not.toThrow(); + dispatcher.foo(123, 456); + expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ + queueableFns: [] + })); }); - it("allows providing a fallback reporter in case there's no other report", function() { - var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar']), - reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']); + it("allows providing a fallback reporter in case there's no other reporter", function() { + var queueRunnerFactory = jasmine.createSpy('queueRunner'), + dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar'], queueRunnerFactory), + reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']), + completeCallback = jasmine.createSpy('complete'); dispatcher.provideFallbackReporter(reporter); - dispatcher.foo(123, 456); + dispatcher.foo(123, 456, completeCallback); + + expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ + queueableFns: [{fn: jasmine.any(Function)}] + })); + + var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; + fns[0].fn(); expect(reporter.foo).toHaveBeenCalledWith(123, 456); }); - it("does not call fallback reporting methods when another report is provided", function() { - var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar']), + it("does not call fallback reporting methods when another reporter is provided", function() { + var queueRunnerFactory = jasmine.createSpy('queueRunner'), + dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar'], queueRunnerFactory), reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']), - fallbackReporter = jasmine.createSpyObj('otherReporter', ['foo', 'bar']); + fallbackReporter = jasmine.createSpyObj('otherReporter', ['foo', 'bar']), + completeCallback = jasmine.createSpy('complete'); dispatcher.provideFallbackReporter(fallbackReporter); dispatcher.addReporter(reporter); - dispatcher.foo(123, 456); + dispatcher.foo(123, 456, completeCallback); + expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ + queueableFns: [{fn: jasmine.any(Function)}] + })); + + var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; + fns[0].fn(); expect(reporter.foo).toHaveBeenCalledWith(123, 456); expect(fallbackReporter.foo).not.toHaveBeenCalledWith(123, 456); }); it("allows registered reporters to be cleared", function() { - var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar']), + var queueRunnerFactory = jasmine.createSpy('queueRunner'), + dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar'], queueRunnerFactory), reporter1 = jasmine.createSpyObj('reporter1', ['foo', 'bar']), - reporter2 = jasmine.createSpyObj('reporter2', ['foo', 'bar']); + reporter2 = jasmine.createSpyObj('reporter2', ['foo', 'bar']), + completeCallback = jasmine.createSpy('complete'); dispatcher.addReporter(reporter1); - dispatcher.foo(123); + dispatcher.foo(123, completeCallback); + expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ + queueableFns: [{fn: jasmine.any(Function)}] + })); + + var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; + fns[0].fn(); expect(reporter1.foo).toHaveBeenCalledWith(123); dispatcher.clearReporters(); dispatcher.addReporter(reporter2); - dispatcher.bar(456); + dispatcher.bar(456, completeCallback); + expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ + queueableFns: [{fn: jasmine.any(Function)}] + })); + + fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; + fns[0].fn(); expect(reporter1.bar).not.toHaveBeenCalled(); expect(reporter2.bar).toHaveBeenCalledWith(456); }); diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index 9a32101e..d0b8d169 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -55,6 +55,7 @@ describe("Spec", function() { spec.execute(); + fakeQueueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); // TODO: due to some issue with the Pretty Printer, this line fails, but the other two pass. // This means toHaveBeenCalledWith on IE8 will always be broken. @@ -82,6 +83,7 @@ describe("Spec", function() { spec.execute(); + fakeQueueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); expect(startCallback).toHaveBeenCalled(); }); @@ -104,8 +106,8 @@ describe("Spec", function() { spec.execute(); var options = fakeQueueRunner.calls.mostRecent().args[0]; - expect(options.queueableFns).toEqual([before, queueableFn]); - expect(options.cleanupFns).toEqual([after]); + expect(options.queueableFns).toEqual([{fn: jasmine.any(Function)}, before, queueableFn]); + expect(options.cleanupFns).toEqual([after, {fn: jasmine.any(Function)}]); }); it("tells the queue runner that it's a leaf node", function() { @@ -141,8 +143,7 @@ describe("Spec", function() { }); it("can be excluded at execution time by a parent", function() { - var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner') - .and.callFake(function(attrs) { attrs.onComplete(); }), + var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'), startCallback = jasmine.createSpy('startCallback'), specBody = jasmine.createSpy('specBody'), resultCallback = jasmine.createSpy('resultCallback'), @@ -153,20 +154,26 @@ describe("Spec", function() { queueRunnerFactory: fakeQueueRunner }); - spec.execute(undefined, true); + spec.execute('cally-back', true); - expect(spec.result.status).toBe('excluded'); - - expect(fakeQueueRunner).toHaveBeenCalled(); + expect(fakeQueueRunner).toHaveBeenCalledWith(jasmine.objectContaining({ + onComplete: 'cally-back', + queueableFns: [{fn: jasmine.any(Function)}], + cleanupFns: [{fn: jasmine.any(Function)}] + })); expect(specBody).not.toHaveBeenCalled(); + var args = fakeQueueRunner.calls.mostRecent().args[0]; + args.queueableFns[0].fn(); expect(startCallback).toHaveBeenCalled(); + args.cleanupFns[0].fn(); expect(resultCallback).toHaveBeenCalled(); + + expect(spec.result.status).toBe('excluded'); }); it("can be marked pending, but still calls callbacks when executed", function() { - var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner') - .and.callFake(function(attrs) { attrs.onComplete(); }), + var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'), startCallback = jasmine.createSpy('startCallback'), resultCallback = jasmine.createSpy('resultCallback'), spec = new jasmineUnderTest.Spec({ @@ -188,7 +195,10 @@ describe("Spec", function() { expect(fakeQueueRunner).toHaveBeenCalled(); + var args = fakeQueueRunner.calls.mostRecent().args[0]; + args.queueableFns[0].fn(); expect(startCallback).toHaveBeenCalled(); + args.cleanupFns[0].fn('things'); expect(resultCallback).toHaveBeenCalledWith({ id: spec.id, status: 'pending', @@ -197,7 +207,7 @@ describe("Spec", function() { failedExpectations: [], passedExpectations: [], pendingReason: '' - }); + }, 'things'); }); it("should call the done callback on execution complete", function() { @@ -233,11 +243,12 @@ describe("Spec", function() { }); it("keeps track of passed and failed expectations", function() { - var resultCallback = jasmine.createSpy('resultCallback'), + var fakeQueueRunner = jasmine.createSpy('queueRunner'), + resultCallback = jasmine.createSpy('resultCallback'), spec = new jasmineUnderTest.Spec({ queueableFn: { fn: jasmine.createSpy("spec body") }, expectationResultFactory: function (data) { return data; }, - queueRunnerFactory: function(attrs) { attrs.onComplete(); }, + queueRunnerFactory: fakeQueueRunner, resultCallback: resultCallback }); spec.addExpectationResult(true, 'expectation1'); @@ -245,19 +256,21 @@ describe("Spec", function() { spec.execute(); + fakeQueueRunner.calls.mostRecent().args[0].cleanupFns[0].fn(); expect(resultCallback.calls.first().args[0].passedExpectations).toEqual(['expectation1']); expect(resultCallback.calls.first().args[0].failedExpectations).toEqual(['expectation2']); }); it("throws an ExpectationFailed error upon receiving a failed expectation when 'throwOnExpectationFailure' is set", function() { - var resultCallback = jasmine.createSpy('resultCallback'), + var fakeQueueRunner = jasmine.createSpy('queueRunner'), + resultCallback = jasmine.createSpy('resultCallback'), spec = new jasmineUnderTest.Spec({ - queueableFn: { fn: function() {} }, - expectationResultFactory: function(data) { return data; }, - queueRunnerFactory: function(attrs) { attrs.onComplete(); }, - resultCallback: resultCallback, - throwOnExpectationFailure: true - }); + queueableFn: { fn: function() {} }, + expectationResultFactory: function(data) { return data; }, + queueRunnerFactory: fakeQueueRunner, + resultCallback: resultCallback, + throwOnExpectationFailure: true + }); spec.addExpectationResult(true, 'passed'); expect(function() { @@ -266,6 +279,7 @@ describe("Spec", function() { spec.execute(); + fakeQueueRunner.calls.mostRecent().args[0].cleanupFns[0].fn(); expect(resultCallback.calls.first().args[0].passedExpectations).toEqual(['passed']); expect(resultCallback.calls.first().args[0].failedExpectations).toEqual(['failed']); }); @@ -332,17 +346,20 @@ describe("Spec", function() { }); it("should log a failure when handling an exception", function() { - var resultCallback = jasmine.createSpy('resultCallback'), + var fakeQueueRunner = jasmine.createSpy('queueRunner'), + resultCallback = jasmine.createSpy('resultCallback'), spec = new jasmineUnderTest.Spec({ queueableFn: { fn: function() {} }, expectationResultFactory: function(data) { return data; }, - queueRunnerFactory: function(attrs) { attrs.onComplete(); }, + queueRunnerFactory: fakeQueueRunner, resultCallback: resultCallback }); spec.onException('foo'); spec.execute(); + var args = fakeQueueRunner.calls.mostRecent().args[0]; + args.cleanupFns[0].fn(); expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([{ error: 'foo', matcherName: '', @@ -353,17 +370,20 @@ describe("Spec", function() { }); it("should not log an additional failure when handling an ExpectationFailed error", function() { - var resultCallback = jasmine.createSpy('resultCallback'), + var fakeQueueRunner = jasmine.createSpy('queueRunner'), + resultCallback = jasmine.createSpy('resultCallback'), spec = new jasmineUnderTest.Spec({ queueableFn: { fn: function() {} }, expectationResultFactory: function(data) { return data; }, - queueRunnerFactory: function(attrs) { attrs.onComplete(); }, + queueRunnerFactory: fakeQueueRunner, resultCallback: resultCallback }); spec.onException(new jasmineUnderTest.errors.ExpectationFailed()); spec.execute(); + var args = fakeQueueRunner.calls.mostRecent().args[0]; + args.cleanupFns[0].fn(); expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([]); }); }); diff --git a/spec/core/TreeProcessorSpec.js b/spec/core/TreeProcessorSpec.js index d8426144..6ab3a15e 100644 --- a/spec/core/TreeProcessorSpec.js +++ b/spec/core/TreeProcessorSpec.js @@ -284,19 +284,20 @@ describe("TreeProcessor", function() { queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(nodeDone); - expect(nodeStart).toHaveBeenCalledWith(node); expect(queueRunner).toHaveBeenCalledWith({ onComplete: jasmine.any(Function), - queueableFns: [], + queueableFns: [{ fn: jasmine.any(Function) }], userContext: { node: 'context' }, onException: jasmine.any(Function) }); + queueRunner.calls.mostRecent().args[0].queueableFns[0].fn('foo'); + expect(nodeStart).toHaveBeenCalledWith(node, 'foo'); + node.getResult.and.returnValue({ my: 'result' }); queueRunner.calls.mostRecent().args[0].onComplete(); - expect(nodeComplete).toHaveBeenCalledWith(node, { my: 'result' }); - expect(nodeDone).toHaveBeenCalled(); + expect(nodeComplete).toHaveBeenCalledWith(node, { my: 'result' }, nodeDone); }); it("runs a node with children", function() { @@ -318,12 +319,12 @@ describe("TreeProcessor", function() { queueableFns[0].fn(nodeDone); queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; - expect(queueableFns.length).toBe(2); + expect(queueableFns.length).toBe(3); - queueableFns[0].fn('foo'); + queueableFns[1].fn('foo'); expect(leaf1.execute).toHaveBeenCalledWith('foo', false); - queueableFns[1].fn('bar'); + queueableFns[2].fn('bar'); expect(leaf2.execute).toHaveBeenCalledWith('bar', false); }); @@ -348,18 +349,19 @@ describe("TreeProcessor", function() { var queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; queueableFns[0].fn(nodeDone); - expect(nodeStart).toHaveBeenCalledWith(node); - queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; - expect(queueableFns.length).toBe(1); + expect(queueableFns.length).toBe(2); - queueableFns[0].fn('foo'); + queueableFns[0].fn('bar'); + expect(nodeStart).toHaveBeenCalledWith(node, 'bar'); + + queueableFns[1].fn('foo'); expect(leaf1.execute).toHaveBeenCalledWith('foo', true); node.getResult.and.returnValue({ im: 'disabled' }); queueRunner.calls.mostRecent().args[0].onComplete(); - expect(nodeComplete).toHaveBeenCalledWith(node, { im: 'disabled' }); + expect(nodeComplete).toHaveBeenCalledWith(node, { im: 'disabled' }, nodeDone); }); it("runs beforeAlls for a node with children", function() { @@ -384,7 +386,7 @@ describe("TreeProcessor", function() { queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; - expect(queueableFns).toEqual(['beforeAll1', 'beforeAll2', { fn: jasmine.any(Function) }]); + expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }, 'beforeAll1', 'beforeAll2', { fn: jasmine.any(Function) }]); }); it("runs afterAlls for a node with children", function() { @@ -409,7 +411,7 @@ describe("TreeProcessor", function() { queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; - expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }, 'afterAll1', 'afterAll2']); + expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }, { fn: jasmine.any(Function) }, 'afterAll1', 'afterAll2']); }); it("does not run beforeAlls or afterAlls for a node with no children", function() { @@ -433,7 +435,7 @@ describe("TreeProcessor", function() { queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; - expect(queueableFns).toEqual([]); + expect(queueableFns).toEqual([{fn: jasmine.any(Function)}]); }); it("does not run beforeAlls or afterAlls for a node with only pending children", function() { @@ -460,7 +462,7 @@ describe("TreeProcessor", function() { queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; - expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }]); + expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }, { fn: jasmine.any(Function) }]); }); it("runs leaves in the order specified", function() { @@ -529,7 +531,7 @@ describe("TreeProcessor", function() { expect(specifiedLeaf.execute).not.toHaveBeenCalled(); var nodeQueueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; - nodeQueueableFns[0].fn(); + nodeQueueableFns[1].fn(); expect(childLeaf.execute).toHaveBeenCalled(); @@ -558,8 +560,8 @@ describe("TreeProcessor", function() { expect(queueableFns.length).toBe(5); queueableFns[0].fn(); - expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(leaf1.execute).toHaveBeenCalled(); queueableFns[1].fn(); @@ -567,8 +569,8 @@ describe("TreeProcessor", function() { queueableFns[2].fn(); expect(queueRunner.calls.count()).toBe(3); - expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(leaf2.execute).toHaveBeenCalled(); queueableFns[3].fn(); @@ -576,8 +578,8 @@ describe("TreeProcessor", function() { queueableFns[4].fn(); expect(queueRunner.calls.count()).toBe(4); - expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(leaf3.execute).toHaveBeenCalled(); }); @@ -603,12 +605,12 @@ describe("TreeProcessor", function() { queueableFns[0].fn(); expect(queueRunner.calls.count()).toBe(2); - expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1); + expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(queueRunner.calls.count()).toBe(3); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(leaf1.execute).toHaveBeenCalled(); queueableFns[1].fn(); @@ -616,12 +618,12 @@ describe("TreeProcessor", function() { queueableFns[2].fn(); expect(queueRunner.calls.count()).toBe(4); - expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1); + expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(queueRunner.calls.count()).toBe(5); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(leaf2.execute).toHaveBeenCalled(); queueableFns[3].fn(); @@ -629,12 +631,12 @@ describe("TreeProcessor", function() { queueableFns[4].fn(); expect(queueRunner.calls.count()).toBe(6); - expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1); + expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(queueRunner.calls.count()).toBe(7); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(leaf3.execute).toHaveBeenCalled(); }); @@ -661,11 +663,11 @@ describe("TreeProcessor", function() { queueableFns[1].fn(); var childFns = queueRunner.calls.mostRecent().args[0].queueableFns; - expect(childFns.length).toBe(2); - childFns[0].fn(); + expect(childFns.length).toBe(3); + childFns[1].fn(); expect(leaf2.execute).toHaveBeenCalled(); - childFns[1].fn(); + childFns[2].fn(); expect(leaf3.execute).toHaveBeenCalled(); }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 6e71d3c7..ac3852a7 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -412,7 +412,7 @@ describe("Env integration", function() { suiteDone: jasmine.createSpy('suiteDone').and.callFake(function(result) { expect(result.failedExpectations[0].globalErrorType).toBeFalsy(); }) - } + }; env.addReporter(reporter); @@ -480,9 +480,11 @@ describe("Env integration", function() { env.it('fails', function(specDone) { setTimeout(function() { specDone(); + setTimeout(function() { setTimeout(function() { global.onerror('fail'); }); + }); }); }); }); @@ -1002,28 +1004,35 @@ describe("Env integration", function() { }); describe("with a mock clock", function() { + var realSetTimeout; beforeEach(function() { this.originalTimeout = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL; - this.realSetTimeout = setTimeout; + realSetTimeout = setTimeout; jasmine.clock().install(); }); afterEach(function() { + jasmine.clock().tick(1); + jasmine.clock().tick(1); jasmine.clock().uninstall(); jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = this.originalTimeout; }); - it("should wait a specified interval before failing specs haven't called done yet", function(done) { + it("should wait a default interval before failing specs that haven't called done yet", function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone" ]); - reporter.specDone.and.callFake(function() { - expect(reporter.specDone).toHaveBeenCalledWith(jasmine.objectContaining({status: 'failed'})); + reporter.specDone.and.callFake(function(result) { + expect(result).toEqual(jasmine.objectContaining({status: 'failed'})); + realSetTimeout(function() { + jasmine.clock().tick(1); + }, 0); }); reporter.jasmineDone.and.callFake(function() { - expect(reporter.jasmineDone.calls.count()).toEqual(1); - done(); + expect(reporter.specDone.calls.count()).toEqual(1); + jasmine.clock().tick(1); + realSetTimeout(done); }); env.addReporter(reporter); @@ -1032,6 +1041,7 @@ describe("Env integration", function() { env.it("async spec that doesn't call done", function(underTestCallback) { env.expect(true).toBeTruthy(); jasmine.clock().tick(8416); + jasmine.clock().tick(1); }); env.execute(); @@ -1042,10 +1052,17 @@ describe("Env integration", function() { reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone" ]), clock = env.clock; + reporter.specDone.and.callFake(function() { + realSetTimeout(function() { + jasmine.clock().tick(1); + }, 0); + }); + reporter.jasmineDone.and.callFake(function() { - expect(reporter.specDone.calls.count()).toEqual(1); + expect(reporter.specDone).toHaveBeenCalledTimes(1); expect(reporter.specDone.calls.argsFor(0)[0]).toEqual(jasmine.objectContaining({status: 'passed'})); - done(); + jasmine.clock().tick(1); + realSetTimeout(done); }); env.addReporter(reporter); @@ -1062,8 +1079,7 @@ describe("Env integration", function() { env.it("spec that should not time out", function(innerDone) { clock.tick(6); expect(true).toEqual(true); - innerDone(); - jasmine.clock().tick(1); + realSetTimeout(innerDone); }); env.execute(); @@ -1072,9 +1088,20 @@ describe("Env integration", function() { it('should wait a custom interval before reporting async functions that fail to call done', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone', 'suiteDone', 'specDone']), - realSetTimeout = this.realSetTimeout, timeoutFailure = (/^Error: Timeout - Async callback was not invoked within timeout specified by jasmine\.DEFAULT_TIMEOUT_INTERVAL\./); + reporter.specDone.and.callFake(function(r) { + realSetTimeout(function() { + jasmine.clock().tick(1); + }, 0); + }); + + reporter.suiteDone.and.callFake(function(r) { + realSetTimeout(function() { + jasmine.clock().tick(1); + }, 0); + }); + reporter.jasmineDone.and.callFake(function() { expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('suite beforeAll', [ timeoutFailure ]); expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('suite afterAll', [ timeoutFailure ]); @@ -1082,18 +1109,19 @@ describe("Env integration", function() { expect(reporter.specDone).toHaveFailedExpectationsForRunnable('suite afterEach times out', [ timeoutFailure ]); expect(reporter.specDone).toHaveFailedExpectationsForRunnable('suite it times out', [ timeoutFailure ]); - done(); + jasmine.clock().tick(1); + realSetTimeout(done); }); env.addReporter(reporter); jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = 10000; env.describe('suite', function() { - env.afterAll(function() { - realSetTimeout(function() { - jasmine.clock().tick(10); - }, 100); - }); + // env.afterAll(function() { + // realSetTimeout(function() { + // jasmine.clock().tick(10); + // }, 100); + // }); env.describe('beforeAll', function() { env.beforeAll(function(innerDone) { realSetTimeout(function() { @@ -1101,7 +1129,12 @@ describe("Env integration", function() { }, 0); }, 5000); - env.it('times out', function() {}); + env.it('times out', function(innerDone) { + realSetTimeout(function() { + jasmine.clock().tick(1); + innerDone(); + }, 0); + }); }); env.describe('afterAll', function() { @@ -1111,7 +1144,12 @@ describe("Env integration", function() { }, 0); }, 2000); - env.it('times out', function() {}); + env.it('times out', function(innerDone) { + realSetTimeout(function() { + jasmine.clock().tick(1); + innerDone(); + }, 0); + }); }); env.describe('beforeEach', function() { @@ -1121,7 +1159,12 @@ describe("Env integration", function() { }, 0); }, 1000); - env.it('times out', function() {}); + env.it('times out', function(innerDone) { + realSetTimeout(function() { + jasmine.clock().tick(1); + innerDone(); + }, 0); + }); }); env.describe('afterEach', function() { @@ -1131,7 +1174,12 @@ describe("Env integration", function() { }, 0); }, 4000); - env.it('times out', function() {}); + env.it('times out', function(innerDone) { + realSetTimeout(function() { + jasmine.clock().tick(1); + innerDone(); + }, 0); + }); }); env.it('it times out', function(innerDone) { @@ -1148,6 +1196,12 @@ describe("Env integration", function() { var env = new jasmineUnderTest.Env(), specDone = jasmine.createSpy('specDone'); + specDone.and.callFake(function() { + realSetTimeout(function() { + jasmine.clock().tick(1); + }, 0); + }); + env.addReporter({ specDone: specDone, jasmineDone: function() { @@ -1175,7 +1229,9 @@ describe("Env integration", function() { message: 'Failed: error message' })] })); - done(); + + jasmine.clock().tick(1); + realSetTimeout(done); } }); @@ -1187,6 +1243,7 @@ describe("Env integration", function() { }, 1); jasmine.clock().tick(1); jasmine.clock().tick(1); + jasmine.clock().tick(1); }); env.it('specifies a message', function(innerDone) { @@ -1196,6 +1253,7 @@ describe("Env integration", function() { }, 1); jasmine.clock().tick(1); jasmine.clock().tick(1); + jasmine.clock().tick(1); }); env.it('fails via the done callback', function(innerDone) { @@ -1204,6 +1262,7 @@ describe("Env integration", function() { }, 1); jasmine.clock().tick(1); jasmine.clock().tick(1); + jasmine.clock().tick(1); }); env.it('has a message from an Error', function(innerDone) { @@ -1213,6 +1272,7 @@ describe("Env integration", function() { }, 1); jasmine.clock().tick(1); jasmine.clock().tick(1); + jasmine.clock().tick(1); }); }); diff --git a/src/core/Env.js b/src/core/Env.js index a3ce0dd3..21f7d760 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -39,59 +39,6 @@ getJasmineRequireObj().Env = function(j$) { return currentSpec || currentSuite(); }; - /** - * This represents the available reporter callback for an object passed to {@link Env#addReporter}. - * @interface Reporter - */ - var reporter = new j$.ReportDispatcher([ - /** - * `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts. - * @function - * @name Reporter#jasmineStarted - * @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run - */ - 'jasmineStarted', - /** - * When the entire suite has finished execution `jasmineDone` is called - * @function - * @name Reporter#jasmineDone - * @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running. - */ - 'jasmineDone', - /** - * `suiteStarted` is invoked when a `describe` starts to run - * @function - * @name Reporter#suiteStarted - * @param {SuiteResult} result Information about the individual {@link describe} being run - */ - 'suiteStarted', - /** - * `suiteDone` is invoked when all of the child specs and suites for a given suite have been run - * - * While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`. - * @function - * @name Reporter#suiteDone - * @param {SuiteResult} result - */ - 'suiteDone', - /** - * `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions) - * @function - * @name Reporter#specStarted - * @param {SpecResult} result Information about the individual {@link it} being run - */ - 'specStarted', - /** - * `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run. - * - * While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed. - * @function - * @name Reporter#specDone - * @param {SpecResult} result - */ - 'specDone' - ]); - var globalErrors = new j$.GlobalErrors(); globalErrors.install(); globalErrors.pushListener(function(message, filename, lineno) { @@ -260,15 +207,18 @@ getJasmineRequireObj().Env = function(j$) { handlingLoadErrors = false; }; - var queueRunnerFactory = function(options) { + var queueRunnerFactory = function(options, args) { options.catchException = catchException; options.clearStack = options.clearStack || clearStack; options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; options.fail = self.fail; options.globalErrors = globalErrors; options.completeOnFirstError = throwOnExpectationFailure && options.isLeaf; + options.onException = options.onException || function(e) { + (currentRunnable() || topSuite).onException(e); + }; - new j$.QueueRunner(options).execute(); + new j$.QueueRunner(options).execute(args); }; var topSuite = new j$.Suite({ @@ -285,6 +235,59 @@ getJasmineRequireObj().Env = function(j$) { return topSuite; }; + /** + * This represents the available reporter callback for an object passed to {@link Env#addReporter}. + * @interface Reporter + */ + var reporter = new j$.ReportDispatcher([ + /** + * `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts. + * @function + * @name Reporter#jasmineStarted + * @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run + */ + 'jasmineStarted', + /** + * When the entire suite has finished execution `jasmineDone` is called + * @function + * @name Reporter#jasmineDone + * @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running. + */ + 'jasmineDone', + /** + * `suiteStarted` is invoked when a `describe` starts to run + * @function + * @name Reporter#suiteStarted + * @param {SuiteResult} result Information about the individual {@link describe} being run + */ + 'suiteStarted', + /** + * `suiteDone` is invoked when all of the child specs and suites for a given suite have been run + * + * While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`. + * @function + * @name Reporter#suiteDone + * @param {SuiteResult} result + */ + 'suiteDone', + /** + * `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions) + * @function + * @name Reporter#specStarted + * @param {SpecResult} result Information about the individual {@link it} being run + */ + 'specStarted', + /** + * `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run. + * + * While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed. + * @function + * @name Reporter#specDone + * @param {SpecResult} result + */ + 'specDone' + ], queueRunnerFactory); + this.execute = function(runnablesToRun) { var self = this; this.suppressLoadErrors(); @@ -306,23 +309,24 @@ getJasmineRequireObj().Env = function(j$) { tree: topSuite, runnableIds: runnablesToRun, queueRunnerFactory: queueRunnerFactory, - nodeStart: function(suite) { + nodeStart: function(suite, next) { currentlyExecutingSuites.push(suite); defaultResourcesForRunnable(suite.id, suite.parentSuite.id); - reporter.suiteStarted(suite.result); + reporter.suiteStarted(suite.result, next); }, - nodeComplete: function(suite, result) { + nodeComplete: function(suite, result, next) { if (suite !== currentSuite()) { throw new Error('Tried to complete the wrong suite'); } clearResourcesForRunnable(suite.id); currentlyExecutingSuites.pop(); - reporter.suiteDone(result); if (result.status === 'failed') { hasFailures = true; } + + reporter.suiteDone(result, next); }, orderChildren: function(node) { return order.sort(node.children); @@ -345,40 +349,40 @@ getJasmineRequireObj().Env = function(j$) { reporter.jasmineStarted({ totalSpecsDefined: totalSpecsDefined, order: order - }); + }, function() { + currentlyExecutingSuites.push(topSuite); - currentlyExecutingSuites.push(topSuite); + processor.execute(function () { + clearResourcesForRunnable(topSuite.id); + currentlyExecutingSuites.pop(); + var overallStatus, incompleteReason; - processor.execute(function() { - clearResourcesForRunnable(topSuite.id); - currentlyExecutingSuites.pop(); - var overallStatus, incompleteReason; + if (hasFailures || topSuite.result.failedExpectations.length > 0) { + overallStatus = 'failed'; + } else if (focusedRunnables.length > 0) { + overallStatus = 'incomplete'; + incompleteReason = 'fit() or fdescribe() was found'; + } else if (totalSpecsDefined === 0) { + overallStatus = 'incomplete'; + incompleteReason = 'No specs found'; + } else { + overallStatus = 'passed'; + } - if (hasFailures || topSuite.result.failedExpectations.length > 0) { - overallStatus = 'failed'; - } else if (focusedRunnables.length > 0) { - overallStatus = 'incomplete'; - incompleteReason = 'fit() or fdescribe() was found'; - } else if (totalSpecsDefined === 0) { - overallStatus = 'incomplete'; - incompleteReason = 'No specs found'; - } else { - overallStatus = 'passed'; - } - - /** - * Information passed to the {@link Reporter#jasmineDone} event. - * @typedef JasmineDoneInfo - * @property {OverallStatus} - The overall result of the sute: 'passed', 'failed', or 'incomplete'. - * @property {IncompleteReason} - Explanation of why the suite was incimplete. - * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. - * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level. - */ - reporter.jasmineDone({ - overallStatus: overallStatus, - incompleteReason: incompleteReason, - order: order, - failedExpectations: topSuite.result.failedExpectations + /** + * Information passed to the {@link Reporter#jasmineDone} event. + * @typedef JasmineDoneInfo + * @property {OverallStatus} - The overall result of the sute: 'passed', 'failed', or 'incomplete'. + * @property {IncompleteReason} - Explanation of why the suite was incimplete. + * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. + * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level. + */ + reporter.jasmineDone({ + overallStatus: overallStatus, + incompleteReason: incompleteReason, + order: order, + failedExpectations: topSuite.result.failedExpectations + }, function() {}); }); }); }; @@ -581,20 +585,21 @@ getJasmineRequireObj().Env = function(j$) { return spec; - function specResultCallback(result) { + function specResultCallback(result, next) { clearResourcesForRunnable(spec.id); currentSpec = null; - reporter.specDone(result); if (result.status === 'failed') { hasFailures = true; } + + reporter.specDone(result, next); } - function specStarted(spec) { + function specStarted(spec, next) { currentSpec = spec; defaultResourcesForRunnable(spec.id, suite.id); - reporter.specStarted(spec.result); + reporter.specStarted(spec.result, next); } }; diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index c9b15a28..cc8dc1e0 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -24,6 +24,10 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.fail = attrs.fail || function() {}; this.globalErrors = attrs.globalErrors || { pushListener: function() {}, popListener: function() {} }; this.completeOnFirstError = !!attrs.completeOnFirstError; + + if (typeof(this.onComplete) !== 'function') { + throw new Error('invalid onComplete ' + JSON.stringify(this.onComplete)); + } } QueueRunner.prototype.execute = function() { @@ -53,15 +57,15 @@ getJasmineRequireObj().QueueRunner = function(j$) { QueueRunner.prototype.attempt = function attempt(iterativeIndex) { var self = this, completedSynchronously = true, - handleError = function(error) { + handleError = function handleError(error) { onException(error); next(); }, - cleanup = once(function() { + cleanup = once(function cleanup() { self.clearTimeout(timeoutId); self.globalErrors.popListener(handleError); }), - next = once(function () { + next = once(function next() { cleanup(); function runNext() { @@ -82,7 +86,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { queueableFn = self.queueableFns[iterativeIndex], timeoutId; - next.fail = function() { + next.fail = function nextFail() { self.fail.apply(null, arguments); errored = true; next(); diff --git a/src/core/ReportDispatcher.js b/src/core/ReportDispatcher.js index 4e8b29ea..a8d0d576 100644 --- a/src/core/ReportDispatcher.js +++ b/src/core/ReportDispatcher.js @@ -1,5 +1,5 @@ getJasmineRequireObj().ReportDispatcher = function(j$) { - function ReportDispatcher(methods) { + function ReportDispatcher(methods, queueRunnerFactory) { var dispatchedMethods = methods || []; @@ -33,11 +33,39 @@ getJasmineRequireObj().ReportDispatcher = function(j$) { if (reporters.length === 0 && fallbackReporter !== null) { reporters.push(fallbackReporter); } + var onComplete = args[args.length - 1]; + args = j$.util.argsToArray(args).splice(0, args.length - 1); + var fns = []; for (var i = 0; i < reporters.length; i++) { var reporter = reporters[i]; - if (reporter[method]) { - reporter[method].apply(reporter, j$.util.cloneArgs(args)); - } + addFn(fns, reporter, method, args); + } + + queueRunnerFactory({ + queueableFns: fns, + onComplete: onComplete + }); + } + + function addFn(fns, reporter, method, args) { + var fn = reporter[method]; + if (!fn) { + return; + } + + var thisArgs = j$.util.cloneArgs(args); + if (fn.length <= 1) { + fns.push({ + fn: function () { + return fn.apply(reporter, thisArgs); + } + }); + } else { + fns.push({ + fn: function (done) { + return fn.apply(reporter, thisArgs.concat([done])); + } + }); } } } diff --git a/src/core/Spec.js b/src/core/Spec.js index 7f918067..b9c9b7e8 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -58,7 +58,19 @@ getJasmineRequireObj().Spec = function(j$) { Spec.prototype.execute = function(onComplete, excluded) { var self = this; - this.onStart(this); + var onStart = { + fn: function(done) { + self.onStart(self, done); + } + }; + + var complete = { + fn: function(done) { + self.queueableFn.fn = null; + self.result.status = self.status(excluded); + self.resultCallback(self.result, done); + } + }; var fns = this.beforeAndAfterFns(); var regularFns = fns.befores.concat(this.queueableFn); @@ -67,8 +79,10 @@ getJasmineRequireObj().Spec = function(j$) { isLeaf: true, queueableFns: regularFns, cleanupFns: fns.afters, - onException: function() { self.onException.apply(self, arguments); }, - onComplete: complete, + onException: function () { + self.onException.apply(self, arguments); + }, + onComplete: onComplete, userContext: this.userContext() }; @@ -77,17 +91,10 @@ getJasmineRequireObj().Spec = function(j$) { runnerConfig.cleanupFns = []; } + runnerConfig.queueableFns.unshift(onStart); + runnerConfig.cleanupFns.push(complete); + this.queueRunnerFactory(runnerConfig); - - function complete() { - self.queueableFn.fn = null; - self.result.status = self.status(excluded); - self.resultCallback(self.result); - - if (onComplete) { - onComplete(); - } - } }; Spec.prototype.onException = function onException(e) { diff --git a/src/core/TreeProcessor.js b/src/core/TreeProcessor.js index f633094c..43ca9c5b 100644 --- a/src/core/TreeProcessor.js +++ b/src/core/TreeProcessor.js @@ -166,17 +166,20 @@ getJasmineRequireObj().TreeProcessor = function() { if (node.children) { return { fn: function(done) { - nodeStart(node); + var onStart = { + fn: function(next) { + nodeStart(node, next); + } + }; queueRunnerFactory({ - onComplete: function() { + onComplete: function () { node.cleanupBeforeAfter(); - nodeComplete(node, node.getResult()); - done(); + nodeComplete(node, node.getResult(), done); }, - queueableFns: wrapChildren(node, segmentNumber), + queueableFns: [onStart].concat(wrapChildren(node, segmentNumber)), userContext: node.sharedUserContext(), - onException: function() { + onException: function () { node.onException.apply(node, arguments); } });