diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 18ddf049..65ed7782 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2008-2017 Pivotal Labs +Copyright (c) 2008-2018 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -2066,6 +2066,7 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() { var scheduledFunctions = {}; var currentTime = 0; var delayedFnCount = 0; + var deletedKeys = []; self.tick = function(millis, tickDate) { millis = millis || 0; @@ -2112,6 +2113,8 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() { }; self.removeFunctionWithId = function(timeoutKey) { + deletedKeys.push(timeoutKey); + for (var runAtMillis in scheduledFunctions) { var funcs = scheduledFunctions[runAtMillis]; var i = indexOfFirstToPass(funcs, function (func) { @@ -2188,6 +2191,7 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() { currentTime = newCurrentTime; var funcsToRun = scheduledFunctions[currentTime]; + delete scheduledFunctions[currentTime]; forEachFunction(funcsToRun, function(funcToRun) { @@ -2197,8 +2201,13 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() { }); forEachFunction(funcsToRun, function(funcToRun) { + if (deletedKeys.indexOf(funcToRun.timeoutKey) !== -1) { + // skip a timeoutKey deleted whilst we were running + return; + } funcToRun.funcToCall.apply(null, funcToRun.params || []); }); + deletedKeys = []; } while (scheduledLookup.length > 0 && // checking first if we're out of time prevents setTimeout(0) // scheduled in a funcToRun from forcing an extra iteration diff --git a/spec/core/ClockSpec.js b/spec/core/ClockSpec.js index bcce2fa1..2a29e3b5 100644 --- a/spec/core/ClockSpec.js +++ b/spec/core/ClockSpec.js @@ -714,4 +714,41 @@ describe("Clock (acceptance)", function() { expect(actualTimes).toEqual([baseTime.getTime(), baseTime.getTime() + 1, baseTime.getTime() + 3]); }) + + it('correctly clears a scheduled timeout while the Clock is advancing', function () { + var delayedFunctionScheduler = new jasmineUnderTest.DelayedFunctionScheduler(), + global = {Date: Date, setTimeout: undefined}, + mockDate = new jasmineUnderTest.MockDate(global), + clock = new jasmineUnderTest.Clock(global, function () { return delayedFunctionScheduler; }, mockDate); + + clock.install(); + + var timerId2; + + global.setTimeout(function () { + global.clearTimeout(timerId2); + }, 100); + + timerId2 = global.setTimeout(fail, 100); + + clock.tick(100); + }); + + it('correctly clears a scheduled interval while the Clock is advancing', function () { + var delayedFunctionScheduler = new jasmineUnderTest.DelayedFunctionScheduler(), + global = {Date: Date, setTimeout: undefined}, + mockDate = new jasmineUnderTest.MockDate(global), + clock = new jasmineUnderTest.Clock(global, function () { return delayedFunctionScheduler; }, mockDate); + + clock.install(); + + var timerId2; + var timerId1 = global.setInterval(function () { + global.clearInterval(timerId2); + }, 100); + + timerId2 = global.setInterval(fail, 100); + + clock.tick(400); + }); }); diff --git a/spec/core/DelayedFunctionSchedulerSpec.js b/spec/core/DelayedFunctionSchedulerSpec.js index c26252ef..fa846d6f 100644 --- a/spec/core/DelayedFunctionSchedulerSpec.js +++ b/spec/core/DelayedFunctionSchedulerSpec.js @@ -216,21 +216,23 @@ describe("DelayedFunctionScheduler", function() { it("removes functions during a tick that runs the function", function() { var scheduler = new jasmineUnderTest.DelayedFunctionScheduler(), - fn = jasmine.createSpy('fn'), + spy = jasmine.createSpy('fn'), + spyAndRemove = jasmine.createSpy('fn'), fnDelay = 10, timeoutKey; - timeoutKey = scheduler.scheduleFunction(fn, fnDelay, [], true); - scheduler.scheduleFunction(function () { + spyAndRemove.and.callFake(function() { scheduler.removeFunctionWithId(timeoutKey); - }, 2 * fnDelay); + }); - expect(fn).not.toHaveBeenCalled(); + scheduler.scheduleFunction(spyAndRemove, fnDelay); - scheduler.tick(3 * fnDelay); + timeoutKey = scheduler.scheduleFunction(spy, fnDelay, [], true); - expect(fn).toHaveBeenCalled(); - expect(fn.calls.count()).toBe(2); + scheduler.tick(2 * fnDelay); + + expect(spy).not.toHaveBeenCalled(); + expect(spyAndRemove).toHaveBeenCalled(); }); it("removes functions during the first tick that runs the function", function() { diff --git a/src/core/DelayedFunctionScheduler.js b/src/core/DelayedFunctionScheduler.js index 4756b323..73001cc3 100644 --- a/src/core/DelayedFunctionScheduler.js +++ b/src/core/DelayedFunctionScheduler.js @@ -5,6 +5,7 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() { var scheduledFunctions = {}; var currentTime = 0; var delayedFnCount = 0; + var deletedKeys = []; self.tick = function(millis, tickDate) { millis = millis || 0; @@ -51,6 +52,8 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() { }; self.removeFunctionWithId = function(timeoutKey) { + deletedKeys.push(timeoutKey); + for (var runAtMillis in scheduledFunctions) { var funcs = scheduledFunctions[runAtMillis]; var i = indexOfFirstToPass(funcs, function (func) { @@ -127,6 +130,7 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() { currentTime = newCurrentTime; var funcsToRun = scheduledFunctions[currentTime]; + delete scheduledFunctions[currentTime]; forEachFunction(funcsToRun, function(funcToRun) { @@ -136,8 +140,13 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() { }); forEachFunction(funcsToRun, function(funcToRun) { + if (deletedKeys.indexOf(funcToRun.timeoutKey) !== -1) { + // skip a timeoutKey deleted whilst we were running + return; + } funcToRun.funcToCall.apply(null, funcToRun.params || []); }); + deletedKeys = []; } while (scheduledLookup.length > 0 && // checking first if we're out of time prevents setTimeout(0) // scheduled in a funcToRun from forcing an extra iteration