diff --git a/lib/jasmine-core/boot/boot.js b/lib/jasmine-core/boot/boot.js index fb41d0c7..e3f95062 100644 --- a/lib/jasmine-core/boot/boot.js +++ b/lib/jasmine-core/boot/boot.js @@ -38,6 +38,9 @@ return env.spyOn(obj, methodName); }, + clock: function() { + return env.clock; + }, jsApiReporter: new jasmine.JsApiReporter(jasmine) }; diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index f3316038..dae085c3 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -108,8 +108,8 @@ jasmine.MessageResult.prototype.toString = function() { /** * Getter for the Jasmine environment. Ensures one gets created */ -jasmine.getEnv = function() { - var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); +jasmine.getEnv = function(options) { + var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(options); //jasmine. singletons in here (setTimeout blah blah). return env; }; @@ -519,12 +519,17 @@ jasmine.buildExpectationResult = function(params) { * @constructor */ (function() { - jasmine.Env = function() { + jasmine.Env = function(options) { + options = options || {}; var self = this; + var global = options.global || jasmine.getGlobal(); + + this.clock = new jasmine.Clock(global, new jasmine.DelayedFunctionScheduler()); + var suiteConstructor = jasmine.Suite; var isSuite = function(thing) { return thing instanceof suiteConstructor; - } + }; this.jasmine = jasmine; this.currentRunner_ = new jasmine.Runner(this, isSuite); this.spies_ = []; @@ -610,8 +615,7 @@ jasmine.buildExpectationResult = function(params) { afterFns: afterFns(suite), expectationFactory: expectationFactory, exceptionFormatter: exceptionFormatter, - //TODO: move spec creation to more appropriate level and remove this shim - resultCallback: function(result) { self.currentSpec = null; suite.specComplete(result); }, + resultCallback: specResultCallback, fullNameFactory: function(spec) { return fullNameFactory(spec, suite) }, startCallback: startCallback, description: description, @@ -625,8 +629,13 @@ jasmine.buildExpectationResult = function(params) { } return spec; - }; + function specResultCallback(result) { + self.clock.uninstall(); + self.currentSpec = null; + suite.specComplete(result); + } + }; var queueConstructor = jasmine.Queue; var queueFactory = function() { @@ -638,12 +647,6 @@ jasmine.buildExpectationResult = function(params) { }; - - jasmine.Env.prototype.setTimeout = jasmine.setTimeout; - jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; - jasmine.Env.prototype.setInterval = jasmine.setInterval; - jasmine.Env.prototype.clearInterval = jasmine.clearInterval; - //TODO: shim Spec addMatchers behavior into Env. Should be rewritten to remove globals, etc. jasmine.Env.prototype.addMatchers = function(matchersPrototype) { var parent = this.matchersClass; @@ -1480,190 +1483,6 @@ jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mis jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () { return ""; }; -// Mock setTimeout, clearTimeout -// Contributed by Pivotal Computer Systems, www.pivotalsf.com - -jasmine.FakeTimer = function() { - this.reset(); - - var self = this; - self.setTimeout = function(funcToCall, millis) { - self.timeoutsMade++; - self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); - return self.timeoutsMade; - }; - - self.setInterval = function(funcToCall, millis) { - self.timeoutsMade++; - self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); - return self.timeoutsMade; - }; - - self.clearTimeout = function(timeoutKey) { - self.scheduledFunctions[timeoutKey] = jasmine.undefined; - }; - - self.clearInterval = function(timeoutKey) { - self.scheduledFunctions[timeoutKey] = jasmine.undefined; - }; - -}; - -jasmine.FakeTimer.prototype.reset = function() { - this.timeoutsMade = 0; - this.scheduledFunctions = {}; - this.nowMillis = 0; -}; - -jasmine.FakeTimer.prototype.tick = function(millis) { - var oldMillis = this.nowMillis; - var newMillis = oldMillis + millis; - this.runFunctionsWithinRange(oldMillis, newMillis); - this.nowMillis = newMillis; -}; - -jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { - var scheduledFunc; - var funcsToRun = []; - for (var timeoutKey in this.scheduledFunctions) { - scheduledFunc = this.scheduledFunctions[timeoutKey]; - if (scheduledFunc != jasmine.undefined && - scheduledFunc.runAtMillis >= oldMillis && - scheduledFunc.runAtMillis <= nowMillis) { - funcsToRun.push(scheduledFunc); - this.scheduledFunctions[timeoutKey] = jasmine.undefined; - } - } - - if (funcsToRun.length > 0) { - funcsToRun.sort(function(a, b) { - return a.runAtMillis - b.runAtMillis; - }); - for (var i = 0; i < funcsToRun.length; ++i) { - try { - var funcToRun = funcsToRun[i]; - this.nowMillis = funcToRun.runAtMillis; - funcToRun.funcToCall(); - if (funcToRun.recurring) { - this.scheduleFunction(funcToRun.timeoutKey, - funcToRun.funcToCall, - funcToRun.millis, - true); - } - } catch(e) { - } - } - this.runFunctionsWithinRange(oldMillis, nowMillis); - } -}; - -jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { - this.scheduledFunctions[timeoutKey] = { - runAtMillis: this.nowMillis + millis, - funcToCall: funcToCall, - recurring: recurring, - timeoutKey: timeoutKey, - millis: millis - }; -}; - -/** - * @namespace - */ -jasmine.Clock = { - defaultFakeTimer: new jasmine.FakeTimer(), - - reset: function() { - jasmine.Clock.assertInstalled(); - jasmine.Clock.defaultFakeTimer.reset(); - }, - - tick: function(millis) { - jasmine.Clock.assertInstalled(); - jasmine.Clock.defaultFakeTimer.tick(millis); - }, - - runFunctionsWithinRange: function(oldMillis, nowMillis) { - jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); - }, - - scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { - jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); - }, - - useMock: function() { - if (!jasmine.Clock.isInstalled()) { - //TODO: this is using an interface that doesn't exist. - var spec = jasmine.getEnv().currentSpec; - spec.after(jasmine.Clock.uninstallMock); - - jasmine.Clock.installMock(); - } - }, - - installMock: function() { - jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; - }, - - uninstallMock: function() { - jasmine.Clock.assertInstalled(); - jasmine.Clock.installed = jasmine.Clock.real; - }, - - real: { - setTimeout: jasmine.getGlobal().setTimeout, - clearTimeout: jasmine.getGlobal().clearTimeout, - setInterval: jasmine.getGlobal().setInterval, - clearInterval: jasmine.getGlobal().clearInterval - }, - - assertInstalled: function() { - if (!jasmine.Clock.isInstalled()) { - throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); - } - }, - - isInstalled: function() { - return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; - }, - - installed: null -}; -jasmine.Clock.installed = jasmine.Clock.real; - -//else for IE support -jasmine.getGlobal().setTimeout = function(funcToCall, millis) { - if (jasmine.Clock.installed.setTimeout.apply) { - return jasmine.Clock.installed.setTimeout.apply(this, arguments); - } else { - return jasmine.Clock.installed.setTimeout(funcToCall, millis); - } -}; - -jasmine.getGlobal().setInterval = function(funcToCall, millis) { - if (jasmine.Clock.installed.setInterval.apply) { - return jasmine.Clock.installed.setInterval.apply(this, arguments); - } else { - return jasmine.Clock.installed.setInterval(funcToCall, millis); - } -}; - -jasmine.getGlobal().clearTimeout = function(timeoutKey) { - if (jasmine.Clock.installed.clearTimeout.apply) { - return jasmine.Clock.installed.clearTimeout.apply(this, arguments); - } else { - return jasmine.Clock.installed.clearTimeout(timeoutKey); - } -}; - -jasmine.getGlobal().clearInterval = function(timeoutKey) { - if (jasmine.Clock.installed.clearTimeout.apply) { - return jasmine.Clock.installed.clearInterval.apply(this, arguments); - } else { - return jasmine.Clock.installed.clearInterval(timeoutKey); - } -}; - /** * @constructor */ @@ -2288,6 +2107,201 @@ jasmine.Suite.prototype.execute = function(onComplete) { self.finish(onComplete); }); }; +jasmine.Clock = function(global, delayedFunctionScheduler) { + var self = this, + realTimingFunctions = { + setTimeout: global.setTimeout, + clearTimeout: global.clearTimeout, + setInterval: global.setInterval, + clearInterval: global.clearInterval + }, + fakeTimingFunctions = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval + }, + timer = realTimingFunctions, + installed = false; + + self.install = function() { + installed = true; + timer = fakeTimingFunctions; + }; + + self.uninstall = function() { + delayedFunctionScheduler.reset(); + installed = false; + timer = realTimingFunctions; + }; + + self.setTimeout = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error("IE < 9 cannot support extra params to setTimeout without a polyfill"); + } + return timer.setTimeout(fn, delay); + } + return timer.setTimeout.apply(null, arguments); + }; + + self.setInterval = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error("IE < 9 cannot support extra params to setInterval without a polyfill"); + } + return timer.setInterval(fn, delay); + } + return timer.setInterval.apply(null, arguments); + }; + + self.clearTimeout = function(id) { + return timer.clearTimeout(id); + }; + + self.clearInterval = function(id) { + return timer.clearInterval(id); + }; + + self.tick = function(millis) { + if (installed) { + delayedFunctionScheduler.tick(millis) + } else { + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); + } + }; + + return self; + + function legacyIE() { + //if these methods are polyfilled, apply will be present + //TODO: it may be difficult to load the polyfill before jasmine loads + //(env should be new-ed inside of onload) + return !(global.setTimeout || global.setInterval).apply; + } + + function setTimeout(fn, delay) { + return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments,2)); + } + + function clearTimeout(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function setInterval(fn, interval) { + return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); + } + + function clearInterval(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function argSlice(argsObj, n) { + return Array.prototype.slice.call(argsObj, 2); + } +}; +jasmine.DelayedFunctionScheduler = function() { + var self = this; + var scheduledFunctions = {}; + var currentTime = 0; + var delayedFnCount = 0; + + self.tick = function(millis) { + runFunctionsWithinRange(currentTime, currentTime + millis); + currentTime = currentTime + millis; + }; + + self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { + timeoutKey = timeoutKey || ++delayedFnCount; + runAtMillis = runAtMillis || (currentTime + millis); + scheduledFunctions[timeoutKey] = { + runAtMillis: runAtMillis, + funcToCall: funcToCall, + recurring: recurring, + params: params, + timeoutKey: timeoutKey, + millis: millis + }; + return timeoutKey; + }; + + self.removeFunctionWithId = function(timeoutKey) { + delete scheduledFunctions[timeoutKey]; + }; + + self.reset = function() { + currentTime = 0; + scheduledFunctions = {}; + delayedFnCount = 0; + }; + + return self; + + + //finds/dupes functions within range and removes them. + function functionsWithinRange(startMillis, endMillis) { + var fnsToRun = []; + for (var timeoutKey in scheduledFunctions) { + var scheduledFunc = scheduledFunctions[timeoutKey]; + if (scheduledFunc && + scheduledFunc.runAtMillis >= startMillis && + scheduledFunc.runAtMillis <= endMillis) { + //remove fn -- we'll reschedule later if it is recurring. + self.removeFunctionWithId(timeoutKey); + if (!scheduledFunc.recurring) { + fnsToRun.push(scheduledFunc); // schedules each function only once + } else { + fnsToRun.push(buildNthInstanceOf(scheduledFunc, 0)); + var additionalTimesFnRunsInRange = + Math.floor((endMillis - scheduledFunc.runAtMillis) / scheduledFunc.millis); + for (var i = 0; i < additionalTimesFnRunsInRange; i++) { + fnsToRun.push(buildNthInstanceOf(scheduledFunc, i + 1)); + } + reschedule(buildNthInstanceOf(scheduledFunc, additionalTimesFnRunsInRange)); + } + } + } + + return fnsToRun; + } + + function buildNthInstanceOf(scheduledFunc, n) { + return { + runAtMillis: scheduledFunc.runAtMillis + (scheduledFunc.millis * n), + funcToCall: scheduledFunc.funcToCall, + params: scheduledFunc.params, + millis: scheduledFunc.millis, + recurring: scheduledFunc.recurring, + timeoutKey: scheduledFunc.timeoutKey + }; + } + + function reschedule(scheduledFn) { + self.scheduleFunction(scheduledFn.funcToCall, + scheduledFn.millis, + scheduledFn.params, + true, + scheduledFn.timeoutKey, + scheduledFn.runAtMillis + scheduledFn.millis); + } + + + function runFunctionsWithinRange(startMillis, endMillis) { + var funcsToRun = functionsWithinRange(startMillis, endMillis); + if (funcsToRun.length === 0) { + return; + } + + funcsToRun.sort(function(a, b) { + return a.runAtMillis - b.runAtMillis; + }); + + for (var i = 0; i < funcsToRun.length; ++i) { + var funcToRun = funcsToRun[i]; + funcToRun.funcToCall.apply(null, funcToRun.params); + } + } +}; jasmine.version_= { "major": 1, diff --git a/spec/core/ClockSpec.js b/spec/core/ClockSpec.js new file mode 100644 index 00000000..f5135c31 --- /dev/null +++ b/spec/core/ClockSpec.js @@ -0,0 +1,296 @@ +describe("Clock", function() { + +// TODO: fullName/SpecFilter is broken, so don't nest describes you want to filter + + it("calls the global setTimeout directly if Clock is not installed", function() { + var setTimeout = jasmine.createSpy('setTimeout'), + delayedFunctionScheduler = jasmine.createSpyObj('delayedFunctionScheduler', ['scheduleFunction']), + global = { setTimeout: setTimeout }, + delayedFn = jasmine.createSpy('delayedFn'), + clock = new jasmine.Clock(global, delayedFunctionScheduler); + + clock.setTimeout(delayedFn, 0); + + expect(delayedFunctionScheduler.scheduleFunction).not.toHaveBeenCalled(); + expect(setTimeout).toHaveBeenCalledWith(delayedFn, 0); + }); + + it("schedules the delayed function with the fake timer", function() { + var setTimeout = jasmine.createSpy('setTimeout'), + scheduleFunction = jasmine.createSpy('scheduleFunction'), + delayedFunctionScheduler = {scheduleFunction: scheduleFunction}, + global = { setTimeout: setTimeout }, + delayedFn = jasmine.createSpy('delayedFn'), + clock = new jasmine.Clock(global, delayedFunctionScheduler); + + clock.install(); + clock.setTimeout(delayedFn, 0, 'a', 'b'); + + expect(setTimeout).not.toHaveBeenCalled(); + expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(delayedFn, 0, ['a', 'b']); + }); + + it("returns an id for the delayed function", function() { + var setTimeout = jasmine.createSpy('setTimeout'), + scheduleId = 123, + scheduleFunction = jasmine.createSpy('scheduleFunction').andReturn(scheduleId), + delayedFunctionScheduler = {scheduleFunction: scheduleFunction}, + global = { setTimeout: setTimeout }, + delayedFn = jasmine.createSpy('delayedFn'), + clock = new jasmine.Clock(global, delayedFunctionScheduler), + timeoutId; + + clock.install(); + timeoutId = clock.setTimeout(delayedFn, 0); + + expect(timeoutId).toEqual(123); + }); + + it("calls the global clearTimeout directly if Clock is not installed", function() { + var clearTimeout = jasmine.createSpy('clearTimeout'), + delayedFunctionScheduler = jasmine.createSpyObj('delayedFunctionScheduler', ['clearTimeout']), + global = { clearTimeout: clearTimeout }, + clock = new jasmine.Clock(global, delayedFunctionScheduler); + + clock.clearTimeout(123); + + expect(clearTimeout).toHaveBeenCalledWith(123); + }); + + it("clears the scheduled function with the scheduler", function() { + var clearTimeout = jasmine.createSpy('clearTimeout'), + delayedFunctionScheduler = jasmine.createSpyObj('delayedFunctionScheduler', ['removeFunctionWithId']), + global = { setTimeout: clearTimeout }, + delayedFn = jasmine.createSpy('delayedFn'), + clock = new jasmine.Clock(global, delayedFunctionScheduler); + + clock.install(); + clock.clearTimeout(123); + + expect(clearTimeout).not.toHaveBeenCalled(); + expect(delayedFunctionScheduler.removeFunctionWithId).toHaveBeenCalledWith(123); + }); + + it("calls the global setInterval directly if Clock is not installed", function() { + var setInterval = jasmine.createSpy('setInterval'), + delayedFunctionScheduler = jasmine.createSpyObj('delayedFunctionScheduler', ['scheduleFunction']), + global = { setInterval: setInterval }, + delayedFn = jasmine.createSpy('delayedFn'), + clock = new jasmine.Clock(global, delayedFunctionScheduler); + + clock.setInterval(delayedFn, 0); + + expect(delayedFunctionScheduler.scheduleFunction).not.toHaveBeenCalled(); + expect(setInterval).toHaveBeenCalledWith(delayedFn, 0); + }); + + it("schedules the delayed function with the fake timer", function() { + var setInterval = jasmine.createSpy('setInterval'), + scheduleFunction = jasmine.createSpy('scheduleFunction'), + delayedFunctionScheduler = {scheduleFunction: scheduleFunction}, + global = { setInterval: setInterval }, + delayedFn = jasmine.createSpy('delayedFn'), + clock = new jasmine.Clock(global, delayedFunctionScheduler); + + clock.install(); + clock.setInterval(delayedFn, 0, 'a', 'b'); + + expect(setInterval).not.toHaveBeenCalled(); + expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(delayedFn, 0, ['a', 'b'], true); + }); + + it("returns an id for the delayed function", function() { + var setInterval = jasmine.createSpy('setInterval'), + scheduleId = 123, + scheduleFunction = jasmine.createSpy('scheduleFunction').andReturn(scheduleId), + delayedFunctionScheduler = {scheduleFunction: scheduleFunction}, + global = { setInterval: setInterval }, + delayedFn = jasmine.createSpy('delayedFn'), + clock = new jasmine.Clock(global, delayedFunctionScheduler), + intervalId; + + clock.install(); + intervalId = clock.setInterval(delayedFn, 0); + + expect(intervalId).toEqual(123); + }); + + it("calls the global clearInterval directly if Clock is not installed", function() { + var clearInterval = jasmine.createSpy('clearInterval'), + delayedFunctionScheduler = jasmine.createSpyObj('delayedFunctionScheduler', ['clearInterval']), + global = { clearInterval: clearInterval }, + clock = new jasmine.Clock(global, delayedFunctionScheduler); + + clock.clearInterval(123); + + expect(clearInterval).toHaveBeenCalledWith(123); + }); + + it("clears the scheduled function with the scheduler", function() { + var clearInterval = jasmine.createSpy('clearInterval'), + delayedFunctionScheduler = jasmine.createSpyObj('delayedFunctionScheduler', ['removeFunctionWithId']), + global = { setInterval: clearInterval }, + delayedFn = jasmine.createSpy('delayedFn'), + clock = new jasmine.Clock(global, delayedFunctionScheduler); + + clock.install(); + clock.clearInterval(123); + + expect(clearInterval).not.toHaveBeenCalled(); + expect(delayedFunctionScheduler.removeFunctionWithId).toHaveBeenCalledWith(123); + }); + + it("gives you a friendly reminder if the Clock is not installed and you tick", function() { + var clock = new jasmine.Clock({}, jasmine.createSpyObj('delayedFunctionScheduler', ['tick'])); + expect(function() { + clock.tick(50); + }).toThrow(); + }); + + it("can be uninstalled", function() { + var setTimeout = jasmine.createSpy('setTimeout'), + setInterval = jasmine.createSpy('setInterval'), + delayedFunctionScheduler = jasmine.createSpyObj('delayedFunctionScheduler', ['scheduleFunction', 'tick', 'reset']), + global = { setTimeout: setTimeout, setInterval: setInterval }, + delayedFn = jasmine.createSpy('delayedFn'), + clock = new jasmine.Clock(global, delayedFunctionScheduler); + + clock.install(); + clock.setTimeout(delayedFn, 0); + expect(setTimeout).not.toHaveBeenCalled(); + + clock.setInterval(delayedFn, 0); + expect(setInterval).not.toHaveBeenCalled(); + + expect(function() { + clock.tick(0); + }).not.toThrow(); + + clock.uninstall(); + + expect(delayedFunctionScheduler.reset).toHaveBeenCalled(); + + clock.setTimeout(delayedFn, 0); + + expect(setTimeout).toHaveBeenCalled(); + + clock.setInterval(delayedFn, 0); + expect(setInterval).toHaveBeenCalled(); + + expect(function() { + clock.tick(0); + }).toThrow(); + }); + + + it("on IE < 9, fails if extra args are passed to fake clock", function() { + //fail, because this would break in IE9. + var setTimeout = jasmine.createSpy('setTimeout'), + setInterval = jasmine.createSpy('setInterval'), + delayedFunctionScheduler = jasmine.createSpyObj('delayedFunctionScheduler', ['scheduleFunction']), + fn = jasmine.createSpy('fn'), + global = { setTimeout: setTimeout, setInterval: setInterval }, + clock = new jasmine.Clock(global, delayedFunctionScheduler); + + setTimeout.apply = null; + setInterval.apply = null; + + clock.install(); + + clock.setTimeout(fn, 0); + expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(fn, 0, []); + expect(function() { + clock.setTimeout(fn, 0, 'extra'); + }).toThrow(); + + clock.setInterval(fn, 0); + expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(fn, 0, [], true); + expect(function() { + clock.setInterval(fn, 0, 'extra'); + }).toThrow(); + }); + +}); + +describe("Clock (acceptance)", function() { + it("can run setTimeouts/setIntervals synchronously", function() { + var delayedFn1 = jasmine.createSpy('delayedFn1'), + delayedFn2 = jasmine.createSpy('delayedFn2'), + delayedFn3 = jasmine.createSpy('delayedFn3'), + recurring1 = jasmine.createSpy('recurring1'), + delayedFunctionScheduler = new jasmine.DelayedFunctionScheduler(), + clock = new jasmine.Clock({setTimeout: setTimeout}, delayedFunctionScheduler); + + clock.install(); + + clock.setTimeout(delayedFn1, 0, 'some', 'arg'); + var intervalId = clock.setInterval(recurring1, 50, 'some', 'other', 'args'); + clock.setTimeout(delayedFn2, 100); + clock.setTimeout(delayedFn3, 200); + + expect(delayedFn1).not.toHaveBeenCalled(); + expect(delayedFn2).not.toHaveBeenCalled(); + expect(delayedFn3).not.toHaveBeenCalled(); + + clock.tick(0); + + expect(delayedFn1).toHaveBeenCalledWith('some', 'arg'); + expect(delayedFn2).not.toHaveBeenCalled(); + expect(delayedFn3).not.toHaveBeenCalled(); + + clock.tick(50); + + expect(recurring1).toHaveBeenCalledWith('some', 'other', 'args'); + expect(recurring1.callCount).toBe(1); + expect(delayedFn2).not.toHaveBeenCalled(); + expect(delayedFn3).not.toHaveBeenCalled(); + + clock.tick(50); + + expect(recurring1.callCount).toBe(2); + expect(delayedFn2).toHaveBeenCalled(); + expect(delayedFn3).not.toHaveBeenCalled(); + + clock.tick(100); + + expect(recurring1.callCount).toBe(4); + expect(delayedFn3).toHaveBeenCalled(); + + clock.clearInterval(intervalId); + clock.tick(50); + + expect(recurring1.callCount).toBe(4); + }); + + it("can clear a previously set timeout", function() { + var clearedFn = jasmine.createSpy('clearedFn'), + delayedFunctionScheduler = new jasmine.DelayedFunctionScheduler(), + clock = new jasmine.Clock({setTimeout: function() {}}, delayedFunctionScheduler), + timeoutId; + + clock.install(); + + timeoutId = clock.setTimeout(clearedFn, 100); + expect(clearedFn).not.toHaveBeenCalled(); + + clock.clearTimeout(timeoutId); + clock.tick(100); + + expect(clearedFn).not.toHaveBeenCalled(); + }); + + it("correctly schedules functions after the Clock has advanced", function() { + var delayedFn1 = jasmine.createSpy('delayedFn1'), + delayedFunctionScheduler = new jasmine.DelayedFunctionScheduler(), + clock = new jasmine.Clock({setTimeout: function(){}}, delayedFunctionScheduler); + + clock.install(); + + clock.tick(100); + clock.setTimeout(delayedFn1, 10, ['some', 'arg']); + clock.tick(5); + expect(delayedFn1).not.toHaveBeenCalled(); + clock.tick(5); + expect(delayedFn1).toHaveBeenCalled(); + }); +}); diff --git a/spec/core/DelayedFunctionSchedulerSpec.js b/spec/core/DelayedFunctionSchedulerSpec.js new file mode 100644 index 00000000..0615f493 --- /dev/null +++ b/spec/core/DelayedFunctionSchedulerSpec.js @@ -0,0 +1,142 @@ +describe("DelayedFunctionScheduler", function() { + it("schedules a function for later execution", function() { + var scheduler = new jasmine.DelayedFunctionScheduler(), + fn = jasmine.createSpy('fn'); + + scheduler.scheduleFunction(fn, 0); + + expect(fn).not.toHaveBeenCalled(); + + scheduler.tick(0); + + expect(fn).toHaveBeenCalled(); + }); + + it("optionally passes params to scheduled functions", function() { + var scheduler = new jasmine.DelayedFunctionScheduler(), + fn = jasmine.createSpy('fn'); + + scheduler.scheduleFunction(fn, 0, ['foo', 'bar']); + + expect(fn).not.toHaveBeenCalled(); + + scheduler.tick(0); + + expect(fn).toHaveBeenCalledWith('foo', 'bar'); + }); + + it("scheduled fns can optionally reoccur", function() { + var scheduler = new jasmine.DelayedFunctionScheduler(), + fn = jasmine.createSpy('fn'); + + scheduler.scheduleFunction(fn, 20, [], true); + + expect(fn).not.toHaveBeenCalled(); + + scheduler.tick(20); + + expect(fn.callCount).toBe(1); + + scheduler.tick(40); + + expect(fn.callCount).toBe(3); + + scheduler.tick(21); + + expect(fn.callCount).toBe(4); + + }); + + it("increments scheduled fns ids unless one is passed", function() { + var scheduler = new jasmine.DelayedFunctionScheduler(); + + expect(scheduler.scheduleFunction(function() { + }, 0)).toBe(1); + expect(scheduler.scheduleFunction(function() { + }, 0)).toBe(2); + expect(scheduler.scheduleFunction(function() { + }, 0, [], false, 123)).toBe(123); + expect(scheduler.scheduleFunction(function() { + }, 0)).toBe(3); + }); + + it("#removeFunctionWithId removes a previously scheduled function with a given id", function() { + var scheduler = new jasmine.DelayedFunctionScheduler(), + fn = jasmine.createSpy('fn'), + timeoutKey; + + timeoutKey = scheduler.scheduleFunction(fn, 0); + + expect(fn).not.toHaveBeenCalled(); + + scheduler.removeFunctionWithId(timeoutKey); + + scheduler.tick(0); + + expect(fn).not.toHaveBeenCalled(); + }); + + it("reset removes scheduled functions", function() { + var scheduler = new jasmine.DelayedFunctionScheduler(), + fn = jasmine.createSpy('fn'); + + scheduler.scheduleFunction(fn, 0); + + expect(fn).not.toHaveBeenCalled(); + + scheduler.reset(); + + scheduler.tick(0); + + expect(fn).not.toHaveBeenCalled(); + }); + + it("reset resets the returned ids", function() { + var scheduler = new jasmine.DelayedFunctionScheduler(); + expect(scheduler.scheduleFunction(function() { }, 0)).toBe(1); + expect(scheduler.scheduleFunction(function() { }, 0, [], false, 123)).toBe(123); + + scheduler.reset(); + expect(scheduler.scheduleFunction(function() { }, 0)).toBe(1); + expect(scheduler.scheduleFunction(function() { }, 0, [], false, 123)).toBe(123); + }); + + it("reset resets the current tick time", function() { + var scheduler = new jasmine.DelayedFunctionScheduler(), + fn = jasmine.createSpy('fn'); + + expect(fn).not.toHaveBeenCalled(); + + scheduler.tick(15); + scheduler.reset(); + + scheduler.scheduleFunction(fn, 20, [], false, 1, 20); + + scheduler.tick(5); + + expect(fn).not.toHaveBeenCalled(); + }); + + it("executes recurring functions interleaved with regular functions in the correct order", function() { + var scheduler = new jasmine.DelayedFunctionScheduler(), + fn = jasmine.createSpy('fn'), + recurringCallCount = 0, + recurring = jasmine.createSpy('recurring').andCallFake(function() { + recurringCallCount++; + if (recurringCallCount < 5) { + expect(fn).not.toHaveBeenCalled(); + } + }); + + scheduler.scheduleFunction(recurring, 10, [], true); + scheduler.scheduleFunction(fn, 50); + + scheduler.tick(60); + + expect(recurring).toHaveBeenCalled(); + expect(recurring.callCount).toBe(6); + expect(fn).toHaveBeenCalled(); + }); + +}); + diff --git a/spec/core/EnvSpec.js b/spec/core/EnvSpec.js index 7211618a..3190a6dd 100644 --- a/spec/core/EnvSpec.js +++ b/spec/core/EnvSpec.js @@ -157,3 +157,33 @@ describe("jasmine.Env", function() { }); }); }); + +describe("jasmine Env", function() { + + it("Mock clock can be installed and used in tests", function() { + var setTimeout = jasmine.createSpy('setTimeout'), + globalTimeoutFn = jasmine.createSpy('globalTimeoutFn'), + fakeTimeoutFn = jasmine.createSpy('fakeTimeoutFn'), + env = new jasmine.Env({global: { setTimeout: setTimeout }}); + + env.describe("tests", function() { + env.it("test with mock clock", function() { + env.clock.install(); + env.clock.setTimeout(fakeTimeoutFn, 100); + env.clock.tick(100); + }); + env.it("test without mock clock", function() { + env.clock.setTimeout(globalTimeoutFn, 100); + }); + }); + + expect(setTimeout).not.toHaveBeenCalled(); + expect(fakeTimeoutFn).not.toHaveBeenCalled(); + + env.execute(); + + expect(fakeTimeoutFn).toHaveBeenCalled(); + expect(setTimeout).toHaveBeenCalledWith(globalTimeoutFn, 100); + }); + +}); diff --git a/spec/core/MockClockSpec.js b/spec/core/MockClockSpec.js deleted file mode 100644 index 3aca2621..00000000 --- a/spec/core/MockClockSpec.js +++ /dev/null @@ -1,40 +0,0 @@ -//// TODO: Disabling b/c this spec isn't testing what it thinks it is. -//// Make a proper unit and intergration tests for this object -//describe("MockClock", function () { -// -// beforeEach(function() { -// jasmine.Clock.useMock(); -// }); -// -// describe("setTimeout", function () { -// it("should mock the clock when useMock is in a beforeEach", function() { -// var expected = false; -// setTimeout(function() { -// expected = true; -// }, 30000); -// expect(expected).toBe(false); -// jasmine.Clock.tick(30001); -// expect(expected).toBe(true); -// }); -// }); -// -// describe("setInterval", function () { -// it("should mock the clock when useMock is in a beforeEach", function() { -// var interval = 0; -// setInterval(function() { -// interval++; -// }, 30000); -// expect(interval).toEqual(0); -// jasmine.Clock.tick(30001); -// expect(interval).toEqual(1); -// jasmine.Clock.tick(30001); -// expect(interval).toEqual(2); -// jasmine.Clock.tick(1); -// expect(interval).toEqual(2); -// }); -// }); -// -// it("shouldn't complain if you call jasmine.Clock.useMock() more than once", function() { -// jasmine.Clock.useMock(); -// }); -//}); diff --git a/spec/core/RunnerSpec.js b/spec/core/RunnerSpec.js index 95cd4451..36984a3d 100644 --- a/spec/core/RunnerSpec.js +++ b/spec/core/RunnerSpec.js @@ -5,12 +5,6 @@ describe('RunnerTest', function() { beforeEach(function() { env = new jasmine.Env(); env.updateInterval = 0; - - fakeTimer = new jasmine.FakeTimer(); - env.setTimeout = fakeTimer.setTimeout; - env.clearTimeout = fakeTimer.clearTimeout; - env.setInterval = fakeTimer.setInterval; - env.clearInterval = fakeTimer.clearInterval; }); describe('beforeEach', function() { diff --git a/spec/core/SpecRunningSpec.js b/spec/core/SpecRunningSpec.js index 76841b40..a33efe18 100644 --- a/spec/core/SpecRunningSpec.js +++ b/spec/core/SpecRunningSpec.js @@ -5,12 +5,6 @@ describe("jasmine spec running", function () { beforeEach(function() { env = new jasmine.Env(); env.updateInterval = 0; - - fakeTimer = new originalJasmine.FakeTimer(); - env.setTimeout = fakeTimer.setTimeout; - env.clearTimeout = fakeTimer.clearTimeout; - env.setInterval = fakeTimer.setInterval; - env.clearInterval = fakeTimer.clearInterval; }); it('should assign spec ids sequentially', function() { diff --git a/spec/core/SuiteSpec.js b/spec/core/SuiteSpec.js index 84141dec..36ef4683 100644 --- a/spec/core/SuiteSpec.js +++ b/spec/core/SuiteSpec.js @@ -1,16 +1,9 @@ describe('Suite', function() { - var fakeTimer; var env; beforeEach(function() { env = new jasmine.Env(); env.updateInterval = 0; - - fakeTimer = new originalJasmine.FakeTimer(); - env.setTimeout = fakeTimer.setTimeout; - env.clearTimeout = fakeTimer.clearTimeout; - env.setInterval = fakeTimer.setInterval; - env.clearInterval = fakeTimer.clearInterval; }); describe('Specs', function () { @@ -42,7 +35,7 @@ describe('Suite', function() { }); }); }); - + it('#specs should return all immediate children that are specs.', function () { var suiteSpecs = suite.specs(); expect(suiteSpecs.length).toEqual(3); diff --git a/spec/html/HTMLReporterSpec.js b/spec/html/HTMLReporterSpec.js index 3eb473fe..69232388 100644 --- a/spec/html/HTMLReporterSpec.js +++ b/spec/html/HTMLReporterSpec.js @@ -71,12 +71,6 @@ describe("HtmlReporter", function() { var runner, spec, fakeTimer; beforeEach(function() { - fakeTimer = new jasmine.FakeTimer(); - env.setTimeout = fakeTimer.setTimeout; - env.clearTimeout = fakeTimer.clearTimeout; - env.setInterval = fakeTimer.setInterval; - env.clearInterval = fakeTimer.clearInterval; - fakeDocument.location.search = "?"; env.addReporter(htmlReporter); }); @@ -89,7 +83,6 @@ describe("HtmlReporter", function() { }); env.execute(); - fakeTimer.tick(0); var resultEl = getResultMessageDiv(body); expect(resultEl.innerHTML).toMatch(/foo/); diff --git a/spec/jasmine.yml b/spec/jasmine.yml index 847c9171..134bd2c2 100644 --- a/spec/jasmine.yml +++ b/spec/jasmine.yml @@ -11,7 +11,6 @@ src_files: - 'core/Env.js' - 'core/JsApiReporter.js' - 'core/Matchers.js' - - 'core/mock-timeout.js' - 'core/MultiReporter.js' - 'core/NestedResults.js' - 'core/PrettyPrinter.js' diff --git a/spec/node_performance_suite.js b/spec/node_performance_suite.js new file mode 100644 index 00000000..7797cec4 --- /dev/null +++ b/spec/node_performance_suite.js @@ -0,0 +1,176 @@ +var fs = require('fs'); +var util = require('util'); +var path = require('path'); + +// yes, really keep this here to keep us honest, but only for jasmine's own runner! [xw] +// undefined = "diz be undefined yo"; + + +var jasmineGlobals = require('../lib/jasmine-core/jasmine.js'); +for (var k in jasmineGlobals) { + global[k] = jasmineGlobals[k]; +} +var env = jasmine.getEnv(); + +var jasmineInterface = { + describe: function(description, specDefinitions) { + return env.describe(description, specDefinitions); + }, + + xdescribe: function(description, specDefinitions) { + return env.xdescribe(description, specDefinitions); + }, + + it: function(desc, func) { + return env.it(desc, func); + }, + + xit: function(desc, func) { + return env.xit(desc, func); + }, + + beforeEach: function(beforeEachFunction) { + return env.beforeEach(beforeEachFunction); + }, + + afterEach: function(afterEachFunction) { + return env.afterEach(afterEachFunction); + }, + + expect: function(actual) { + return env.expect(actual); + }, + + addMatchers: function(matchers) { + return env.addMatchers(matchers); + }, + + spyOn: function(obj, methodName) { + return env.spyOn(obj, methodName); + }, + + + jsApiReporter: new jasmine.JsApiReporter(jasmine) +}; + +for (var k in jasmineInterface) { + global[k] = jasmineInterface[k]; +} + +require('../src/console/ConsoleReporter.js'); + +/* + Pulling in code from jasmine-node. + + We can't just depend on jasmine-node because it has its own jasmine that it uses. + */ + +global.window = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval +}; + +delete global.window; + +function noop() { +} + +jasmine.executeSpecs = function(specs, done, isVerbose, showColors) { + global.originalJasmine = jasmine; + + for (var i = 0, len = specs.length; i < len; ++i) { + var filename = specs[i]; + require(filename.replace(/\.\w+$/, "")); + } + + var jasmineEnv = jasmine.getEnv(); + var consoleReporter = new jasmine.ConsoleReporter(util.print, done, showColors); + + jasmineEnv.addReporter(consoleReporter); + jasmineEnv.execute(); +}; + +jasmine.getAllSpecFiles = function(dir, matcher) { + var specs = []; + + if (fs.statSync(dir).isFile() && dir.match(matcher)) { + specs.push(dir); + } else { + var files = fs.readdirSync(dir); + for (var i = 0, len = files.length; i < len; ++i) { + var filename = dir + '/' + files[i]; + if (fs.statSync(filename).isFile() && filename.match(matcher)) { + specs.push(filename); + } else if (fs.statSync(filename).isDirectory()) { + var subfiles = this.getAllSpecFiles(filename, matcher); + subfiles.forEach(function(result) { + specs.push(result); + }); + } + } + } + + return specs; +}; + +function now() { + return new Date().getTime(); +} + +jasmine.asyncSpecWait = function() { + var wait = jasmine.asyncSpecWait; + wait.start = now(); + wait.done = false; + (function innerWait() { + waits(10); + runs(function() { + if (wait.start + wait.timeout < now()) { + expect('timeout waiting for spec').toBeNull(); + } else if (wait.done) { + wait.done = false; + } else { + innerWait(); + } + }); + })(); +}; +jasmine.asyncSpecWait.timeout = 4 * 1000; +jasmine.asyncSpecDone = function() { + jasmine.asyncSpecWait.done = true; +}; + +for (var key in jasmine) { + exports[key] = jasmine[key]; +} + +/* + End jasmine-node runner + */ + +var isVerbose = false; +var showColors = true; +process.argv.forEach(function(arg) { + switch (arg) { + case '--color': showColors = true; break; + case '--noColor': showColors = false; break; + case '--verbose': isVerbose = true; break; + } +}); + +var specs = jasmine.getAllSpecFiles(__dirname + '/smoke/', new RegExp("test.js$")); +var domIndependentSpecs = []; +for (var i = 0; i < specs.length; i++) { + if (fs.readFileSync(specs[i], "utf8").indexOf("document.createElement") < 0) { + domIndependentSpecs.push(specs[i]); + } +} + +jasmine.executeSpecs(domIndependentSpecs, function(passed) { + if (passed) { + process.exit(0); + } else { + process.exit(1); + } +}, isVerbose, showColors); diff --git a/spec/runner.html b/spec/runner.html index 79dfa0d6..5df02a83 100644 --- a/spec/runner.html +++ b/spec/runner.html @@ -34,7 +34,8 @@ - + + diff --git a/src/core/Clock.js b/src/core/Clock.js new file mode 100644 index 00000000..47608b89 --- /dev/null +++ b/src/core/Clock.js @@ -0,0 +1,93 @@ +jasmine.Clock = function(global, delayedFunctionScheduler) { + var self = this, + realTimingFunctions = { + setTimeout: global.setTimeout, + clearTimeout: global.clearTimeout, + setInterval: global.setInterval, + clearInterval: global.clearInterval + }, + fakeTimingFunctions = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval + }, + timer = realTimingFunctions, + installed = false; + + self.install = function() { + installed = true; + timer = fakeTimingFunctions; + }; + + self.uninstall = function() { + delayedFunctionScheduler.reset(); + installed = false; + timer = realTimingFunctions; + }; + + self.setTimeout = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error("IE < 9 cannot support extra params to setTimeout without a polyfill"); + } + return timer.setTimeout(fn, delay); + } + return timer.setTimeout.apply(null, arguments); + }; + + self.setInterval = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error("IE < 9 cannot support extra params to setInterval without a polyfill"); + } + return timer.setInterval(fn, delay); + } + return timer.setInterval.apply(null, arguments); + }; + + self.clearTimeout = function(id) { + return timer.clearTimeout(id); + }; + + self.clearInterval = function(id) { + return timer.clearInterval(id); + }; + + self.tick = function(millis) { + if (installed) { + delayedFunctionScheduler.tick(millis) + } else { + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); + } + }; + + return self; + + function legacyIE() { + //if these methods are polyfilled, apply will be present + //TODO: it may be difficult to load the polyfill before jasmine loads + //(env should be new-ed inside of onload) + return !(global.setTimeout || global.setInterval).apply; + } + + function setTimeout(fn, delay) { + return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments,2)); + } + + function clearTimeout(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function setInterval(fn, interval) { + return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); + } + + function clearInterval(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function argSlice(argsObj, n) { + return Array.prototype.slice.call(argsObj, 2); + } +}; diff --git a/src/core/DelayedFunctionScheduler.js b/src/core/DelayedFunctionScheduler.js new file mode 100644 index 00000000..15cb3f37 --- /dev/null +++ b/src/core/DelayedFunctionScheduler.js @@ -0,0 +1,102 @@ +jasmine.DelayedFunctionScheduler = function() { + var self = this; + var scheduledFunctions = {}; + var currentTime = 0; + var delayedFnCount = 0; + + self.tick = function(millis) { + runFunctionsWithinRange(currentTime, currentTime + millis); + currentTime = currentTime + millis; + }; + + self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { + timeoutKey = timeoutKey || ++delayedFnCount; + runAtMillis = runAtMillis || (currentTime + millis); + scheduledFunctions[timeoutKey] = { + runAtMillis: runAtMillis, + funcToCall: funcToCall, + recurring: recurring, + params: params, + timeoutKey: timeoutKey, + millis: millis + }; + return timeoutKey; + }; + + self.removeFunctionWithId = function(timeoutKey) { + delete scheduledFunctions[timeoutKey]; + }; + + self.reset = function() { + currentTime = 0; + scheduledFunctions = {}; + delayedFnCount = 0; + }; + + return self; + + + //finds/dupes functions within range and removes them. + function functionsWithinRange(startMillis, endMillis) { + var fnsToRun = []; + for (var timeoutKey in scheduledFunctions) { + var scheduledFunc = scheduledFunctions[timeoutKey]; + if (scheduledFunc && + scheduledFunc.runAtMillis >= startMillis && + scheduledFunc.runAtMillis <= endMillis) { + //remove fn -- we'll reschedule later if it is recurring. + self.removeFunctionWithId(timeoutKey); + if (!scheduledFunc.recurring) { + fnsToRun.push(scheduledFunc); // schedules each function only once + } else { + fnsToRun.push(buildNthInstanceOf(scheduledFunc, 0)); + var additionalTimesFnRunsInRange = + Math.floor((endMillis - scheduledFunc.runAtMillis) / scheduledFunc.millis); + for (var i = 0; i < additionalTimesFnRunsInRange; i++) { + fnsToRun.push(buildNthInstanceOf(scheduledFunc, i + 1)); + } + reschedule(buildNthInstanceOf(scheduledFunc, additionalTimesFnRunsInRange)); + } + } + } + + return fnsToRun; + } + + function buildNthInstanceOf(scheduledFunc, n) { + return { + runAtMillis: scheduledFunc.runAtMillis + (scheduledFunc.millis * n), + funcToCall: scheduledFunc.funcToCall, + params: scheduledFunc.params, + millis: scheduledFunc.millis, + recurring: scheduledFunc.recurring, + timeoutKey: scheduledFunc.timeoutKey + }; + } + + function reschedule(scheduledFn) { + self.scheduleFunction(scheduledFn.funcToCall, + scheduledFn.millis, + scheduledFn.params, + true, + scheduledFn.timeoutKey, + scheduledFn.runAtMillis + scheduledFn.millis); + } + + + function runFunctionsWithinRange(startMillis, endMillis) { + var funcsToRun = functionsWithinRange(startMillis, endMillis); + if (funcsToRun.length === 0) { + return; + } + + funcsToRun.sort(function(a, b) { + return a.runAtMillis - b.runAtMillis; + }); + + for (var i = 0; i < funcsToRun.length; ++i) { + var funcToRun = funcsToRun[i]; + funcToRun.funcToCall.apply(null, funcToRun.params); + } + } +}; diff --git a/src/core/Env.js b/src/core/Env.js index 52cbf446..15eef30f 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -4,12 +4,17 @@ * @constructor */ (function() { - jasmine.Env = function() { + jasmine.Env = function(options) { + options = options || {}; var self = this; + var global = options.global || jasmine.getGlobal(); + + this.clock = new jasmine.Clock(global, new jasmine.DelayedFunctionScheduler()); + var suiteConstructor = jasmine.Suite; var isSuite = function(thing) { return thing instanceof suiteConstructor; - } + }; this.jasmine = jasmine; this.currentRunner_ = new jasmine.Runner(this, isSuite); this.spies_ = []; @@ -95,8 +100,7 @@ afterFns: afterFns(suite), expectationFactory: expectationFactory, exceptionFormatter: exceptionFormatter, - //TODO: move spec creation to more appropriate level and remove this shim - resultCallback: function(result) { self.currentSpec = null; suite.specComplete(result); }, + resultCallback: specResultCallback, fullNameFactory: function(spec) { return fullNameFactory(spec, suite) }, startCallback: startCallback, description: description, @@ -110,8 +114,13 @@ } return spec; - }; + function specResultCallback(result) { + self.clock.uninstall(); + self.currentSpec = null; + suite.specComplete(result); + } + }; var queueConstructor = jasmine.Queue; var queueFactory = function() { @@ -123,12 +132,6 @@ }; - - jasmine.Env.prototype.setTimeout = jasmine.setTimeout; - jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; - jasmine.Env.prototype.setInterval = jasmine.setInterval; - jasmine.Env.prototype.clearInterval = jasmine.clearInterval; - //TODO: shim Spec addMatchers behavior into Env. Should be rewritten to remove globals, etc. jasmine.Env.prototype.addMatchers = function(matchersPrototype) { var parent = this.matchersClass; diff --git a/src/core/base.js b/src/core/base.js index 8e411879..255288f1 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -108,8 +108,8 @@ jasmine.MessageResult.prototype.toString = function() { /** * Getter for the Jasmine environment. Ensures one gets created */ -jasmine.getEnv = function() { - var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); +jasmine.getEnv = function(options) { + var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(options); //jasmine. singletons in here (setTimeout blah blah). return env; }; diff --git a/src/core/mock-timeout.js b/src/core/mock-timeout.js deleted file mode 100644 index bd1251aa..00000000 --- a/src/core/mock-timeout.js +++ /dev/null @@ -1,184 +0,0 @@ -// Mock setTimeout, clearTimeout -// Contributed by Pivotal Computer Systems, www.pivotalsf.com - -jasmine.FakeTimer = function() { - this.reset(); - - var self = this; - self.setTimeout = function(funcToCall, millis) { - self.timeoutsMade++; - self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); - return self.timeoutsMade; - }; - - self.setInterval = function(funcToCall, millis) { - self.timeoutsMade++; - self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); - return self.timeoutsMade; - }; - - self.clearTimeout = function(timeoutKey) { - self.scheduledFunctions[timeoutKey] = jasmine.undefined; - }; - - self.clearInterval = function(timeoutKey) { - self.scheduledFunctions[timeoutKey] = jasmine.undefined; - }; - -}; - -jasmine.FakeTimer.prototype.reset = function() { - this.timeoutsMade = 0; - this.scheduledFunctions = {}; - this.nowMillis = 0; -}; - -jasmine.FakeTimer.prototype.tick = function(millis) { - var oldMillis = this.nowMillis; - var newMillis = oldMillis + millis; - this.runFunctionsWithinRange(oldMillis, newMillis); - this.nowMillis = newMillis; -}; - -jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { - var scheduledFunc; - var funcsToRun = []; - for (var timeoutKey in this.scheduledFunctions) { - scheduledFunc = this.scheduledFunctions[timeoutKey]; - if (scheduledFunc != jasmine.undefined && - scheduledFunc.runAtMillis >= oldMillis && - scheduledFunc.runAtMillis <= nowMillis) { - funcsToRun.push(scheduledFunc); - this.scheduledFunctions[timeoutKey] = jasmine.undefined; - } - } - - if (funcsToRun.length > 0) { - funcsToRun.sort(function(a, b) { - return a.runAtMillis - b.runAtMillis; - }); - for (var i = 0; i < funcsToRun.length; ++i) { - try { - var funcToRun = funcsToRun[i]; - this.nowMillis = funcToRun.runAtMillis; - funcToRun.funcToCall(); - if (funcToRun.recurring) { - this.scheduleFunction(funcToRun.timeoutKey, - funcToRun.funcToCall, - funcToRun.millis, - true); - } - } catch(e) { - } - } - this.runFunctionsWithinRange(oldMillis, nowMillis); - } -}; - -jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { - this.scheduledFunctions[timeoutKey] = { - runAtMillis: this.nowMillis + millis, - funcToCall: funcToCall, - recurring: recurring, - timeoutKey: timeoutKey, - millis: millis - }; -}; - -/** - * @namespace - */ -jasmine.Clock = { - defaultFakeTimer: new jasmine.FakeTimer(), - - reset: function() { - jasmine.Clock.assertInstalled(); - jasmine.Clock.defaultFakeTimer.reset(); - }, - - tick: function(millis) { - jasmine.Clock.assertInstalled(); - jasmine.Clock.defaultFakeTimer.tick(millis); - }, - - runFunctionsWithinRange: function(oldMillis, nowMillis) { - jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); - }, - - scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { - jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); - }, - - useMock: function() { - if (!jasmine.Clock.isInstalled()) { - //TODO: this is using an interface that doesn't exist. - var spec = jasmine.getEnv().currentSpec; - spec.after(jasmine.Clock.uninstallMock); - - jasmine.Clock.installMock(); - } - }, - - installMock: function() { - jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; - }, - - uninstallMock: function() { - jasmine.Clock.assertInstalled(); - jasmine.Clock.installed = jasmine.Clock.real; - }, - - real: { - setTimeout: jasmine.getGlobal().setTimeout, - clearTimeout: jasmine.getGlobal().clearTimeout, - setInterval: jasmine.getGlobal().setInterval, - clearInterval: jasmine.getGlobal().clearInterval - }, - - assertInstalled: function() { - if (!jasmine.Clock.isInstalled()) { - throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); - } - }, - - isInstalled: function() { - return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; - }, - - installed: null -}; -jasmine.Clock.installed = jasmine.Clock.real; - -//else for IE support -jasmine.getGlobal().setTimeout = function(funcToCall, millis) { - if (jasmine.Clock.installed.setTimeout.apply) { - return jasmine.Clock.installed.setTimeout.apply(this, arguments); - } else { - return jasmine.Clock.installed.setTimeout(funcToCall, millis); - } -}; - -jasmine.getGlobal().setInterval = function(funcToCall, millis) { - if (jasmine.Clock.installed.setInterval.apply) { - return jasmine.Clock.installed.setInterval.apply(this, arguments); - } else { - return jasmine.Clock.installed.setInterval(funcToCall, millis); - } -}; - -jasmine.getGlobal().clearTimeout = function(timeoutKey) { - if (jasmine.Clock.installed.clearTimeout.apply) { - return jasmine.Clock.installed.clearTimeout.apply(this, arguments); - } else { - return jasmine.Clock.installed.clearTimeout(timeoutKey); - } -}; - -jasmine.getGlobal().clearInterval = function(timeoutKey) { - if (jasmine.Clock.installed.clearTimeout.apply) { - return jasmine.Clock.installed.clearInterval.apply(this, arguments); - } else { - return jasmine.Clock.installed.clearInterval(timeoutKey); - } -}; - diff --git a/tasks/jasmine_dev/sources.rb b/tasks/jasmine_dev/sources.rb index 06b65c10..dfd3e611 100644 --- a/tasks/jasmine_dev/sources.rb +++ b/tasks/jasmine_dev/sources.rb @@ -8,7 +8,6 @@ class JasmineDev < Thor "Reporter.js", "JsApiReporter.js", "Matchers.js", - "mock-timeout.js", "MultiReporter.js", "NestedResults.js", "PrettyPrinter.js", @@ -16,6 +15,8 @@ class JasmineDev < Thor "Runner.js", "Spec.js", "Suite.js", + "Clock.js", + "DelayedFunctionScheduler.js" ], :html => [