diff --git a/spec/core/ClearStackSpec.js b/spec/core/ClearStackSpec.js new file mode 100644 index 00000000..4630d36e --- /dev/null +++ b/spec/core/ClearStackSpec.js @@ -0,0 +1,73 @@ +describe("ClearStack", function() { + it("works in an integrationy way", function(done) { + var global = { + setTimeout: typeof setTimeout === 'undefined' ? undefined : setTimeout, + setImmediate: typeof setImmediate === 'undefined' ? undefined : setImmediate, + MessageChannel: typeof MessageChannel === 'undefined' ? undefined : MessageChannel, + process: typeof process === 'undefined' ? undefined : process + }, + clearStack = jasmineUnderTest.getClearStack(global); + + clearStack(function() { + done(); + }); + }); + + it("uses nextTick when available", function() { + var nextTick = jasmine.createSpy('nextTick').and.callFake(function(fn) { fn() }), + global = { process: { nextTick: nextTick } }, + clearStack = jasmineUnderTest.getClearStack(global), + called = false; + + clearStack(function() { + called = true; + }); + + expect(called).toBe(true); + expect(nextTick).toHaveBeenCalled(); + }); + + it("uses setImmediate when available", function() { + var setImmediate = jasmine.createSpy('setImmediate').and.callFake(function(fn) { fn() }), + global = { setImmediate: setImmediate }, + clearStack = jasmineUnderTest.getClearStack(global), + called = false; + + clearStack(function() { + called = true; + }); + + expect(called).toBe(true); + expect(setImmediate).toHaveBeenCalled(); + }); + + it("uses MessageChannels when available", function() { + var fakeChannel = { + port1: {}, + port2: { postMessage: function() { fakeChannel.port1.onmessage(); } } + }, + global = { MessageChannel: function() { return fakeChannel; } }, + clearStack = jasmineUnderTest.getClearStack(global), + called = false; + + clearStack(function() { + called = true; + }); + + expect(called).toBe(true); + }); + + it("falls back to setTimeout", function() { + var setTimeout = jasmine.createSpy('setTimeout').and.callFake(function(fn) { fn() }), + global = { setTimeout: setTimeout }, + clearStack = jasmineUnderTest.getClearStack(global), + called = false; + + clearStack(function() { + called = true; + }); + + expect(called).toBe(true); + expect(setTimeout).toHaveBeenCalledWith(jasmine.any(Function), 0); + }); +}); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index b223d4ae..29d418bd 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -1130,6 +1130,7 @@ describe("Env integration", function() { env.fail(); innerDone(); }, 1); + jasmine.clock().tick(1); }); env.it('specifies a message', function(innerDone) { @@ -1137,12 +1138,14 @@ describe("Env integration", function() { env.fail('messy message'); innerDone(); }, 1); + jasmine.clock().tick(1); }); env.it('fails via the done callback', function(innerDone) { setTimeout(function() { innerDone.fail('done failed'); }, 1); + jasmine.clock().tick(1); }); env.it('has a message from an Error', function(innerDone) { @@ -1150,14 +1153,11 @@ describe("Env integration", function() { env.fail(new Error('error message')); innerDone(); }, 1); + jasmine.clock().tick(1); }); }); env.execute(); - jasmine.clock().tick(1); - jasmine.clock().tick(1); - jasmine.clock().tick(1); - jasmine.clock().tick(1); }); }); @@ -1186,7 +1186,7 @@ describe("Env integration", function() { env.execute(); }); - it('should only run focused suites', function(){ + it('should only run focused suites', function(done){ var env = new jasmineUnderTest.Env(), calls = []; @@ -1572,14 +1572,17 @@ describe("Env integration", function() { }); it("produces an understandable error message when an 'expect' is used outside of a current spec", function(done) { - var env = new jasmineUnderTest.Env(); + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('fakeReporter', ['jasmineDone']); + + reporter.jasmineDone.and.callFake(done); + env.addReporter(reporter); env.describe("A Suite", function() { env.it("an async spec that is actually synchronous", function(underTestCallback) { underTestCallback(); - expect(function() { env.expect('a').toEqual('a'); }).toThrowError(/'expect' was used when there was no current spec/); - done(); }); + expect(function() { env.expect('a').toEqual('a'); }).toThrowError(/'expect' was used when there was no current spec/); }); env.execute(); diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index e70732d1..d3317fcb 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -475,7 +475,7 @@ describe("jasmine spec running", function () { env.execute(); }); - it('focused runnables unfocus ancestor focused suites', function() { + it('focused runnables unfocus ancestor focused suites', function(done) { var actions = []; env.fdescribe('focused suite', function() { @@ -518,7 +518,7 @@ describe("jasmine spec running", function () { env.execute(); }); - it("should allow top level suites to be disabled", function() { + it("should allow top level suites to be disabled", function(done) { var specInADisabledSuite = jasmine.createSpy("specInADisabledSuite"), otherSpec = jasmine.createSpy("otherSpec"); diff --git a/src/core/ClearStack.js b/src/core/ClearStack.js new file mode 100644 index 00000000..8fd9b912 --- /dev/null +++ b/src/core/ClearStack.js @@ -0,0 +1,36 @@ +getJasmineRequireObj().clearStack = function(j$) { + function messageChannelImpl(global) { + var channel = new global.MessageChannel(), + head = {}, + tail = head; + + channel.port1.onmessage = function() { + head = head.next; + var task = head.task; + delete head.task; + task(); + }; + + return function clearStack(fn) { + tail = tail.next = { task: fn }; + channel.port2.postMessage(0); + }; + } + + function getClearStack(global) { + if (global && global.process && j$.isFunction_(global.process.nextTick)) { + return global.process.nextTick; + } else if (j$.isFunction_(global.setImmediate)) { + return global.setImmediate; + } else if (!j$.util.isUndefined(global.MessageChannel)) { + return messageChannelImpl(global); + } else if (j$.isFunction_(global.setTimeout)) { + var realSetTimeout = global.setTimeout; + return function clearStack(fn) { + realSetTimeout(fn, 0); + } + } + } + + return getClearStack; +}; diff --git a/src/core/Env.js b/src/core/Env.js index 6dafef1b..ef5582b4 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -11,6 +11,7 @@ getJasmineRequireObj().Env = function(j$) { var realSetTimeout = j$.getGlobal().setTimeout; var realClearTimeout = j$.getGlobal().clearTimeout; + var clearStack = j$.getClearStack(j$.getGlobal()); this.clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global)); var runnableResources = {}; @@ -154,16 +155,6 @@ getJasmineRequireObj().Env = function(j$) { var maximumSpecCallbackDepth = 20; var currentSpecCallbackDepth = 0; - function clearStack(fn) { - currentSpecCallbackDepth++; - if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { - currentSpecCallbackDepth = 0; - realSetTimeout(fn, 0); - } else { - fn(); - } - } - var catchException = function(e) { return j$.Spec.isPendingSpecException(e) || catchExceptions; }; diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index 94e647de..e93bceea 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -42,11 +42,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { } } - var runnerDone = iterativeIndex >= length; - - if (runnerDone) { - this.clearStack(this.onComplete); - } + this.clearStack(this.onComplete); function attemptSync(queueableFn) { try { diff --git a/src/core/requireCore.js b/src/core/requireCore.js index 08660781..50d07359 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -30,6 +30,7 @@ var getJasmineRequireObj = (function (jasmineGlobal) { j$.Anything = jRequire.Anything(j$); j$.CallTracker = jRequire.CallTracker(j$); j$.MockDate = jRequire.MockDate(); + j$.getClearStack = jRequire.clearStack(j$); j$.Clock = jRequire.Clock(); j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); j$.Env = jRequire.Env(j$);