From d31a431d1ff3d288a9fc8d2caf2e9f1a81e88112 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Mon, 14 Jul 2025 16:31:40 -0700 Subject: [PATCH] fix(clock): Avoid generating timers with IDs that conflict with native This commit attempts to ensure that the timers created by jasmine mock clock do not conflict with the native timers. This also retains pre-existing behavior whereby a native scheduled function cannot be cleared if it was created prior to the mock clock being installed (unless the mock clock is uninstalled first). Prior to this commit, attempting to clear a native timer would result in clearing a mocked scheduled function instead, in some scenarios where the IDs conflicted. fixes #2068 --- spec/core/DelayedFunctionSchedulerSpec.js | 29 ++++++++++++++++++++--- src/core/DelayedFunctionScheduler.js | 4 ++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/spec/core/DelayedFunctionSchedulerSpec.js b/spec/core/DelayedFunctionSchedulerSpec.js index 5175d3f5..09cf84c2 100644 --- a/spec/core/DelayedFunctionSchedulerSpec.js +++ b/spec/core/DelayedFunctionSchedulerSpec.js @@ -86,12 +86,13 @@ describe('DelayedFunctionScheduler', function() { it('increments scheduled fns ids unless one is passed', function() { const scheduler = new jasmineUnderTest.DelayedFunctionScheduler(); - expect(scheduler.scheduleFunction(function() {}, 0)).toBe(1); - expect(scheduler.scheduleFunction(function() {}, 0)).toBe(2); + const initial = scheduler.scheduleFunction(function() {}, 0); + expect(scheduler.scheduleFunction(function() {}, 0)).toBe(initial + 1); + expect(scheduler.scheduleFunction(function() {}, 0)).toBe(initial + 2); expect(scheduler.scheduleFunction(function() {}, 0, [], false, 123)).toBe( 123 ); - expect(scheduler.scheduleFunction(function() {}, 0)).toBe(3); + expect(scheduler.scheduleFunction(function() {}, 0)).toBe(initial + 3); }); it('#removeFunctionWithId removes a previously scheduled function with a given id', function() { @@ -313,6 +314,28 @@ describe('DelayedFunctionScheduler', function() { expect(tickDate).toHaveBeenCalledWith(1); }); + it('does not conflict with native timer IDs', function() { + const NODE_JS = + typeof process !== 'undefined' && + process.versions && + typeof process.versions.node === 'string'; + if (NODE_JS) { + pending('numeric timer ID conflicts only relevant for browsers.'); + } + const nativeTimeoutId = setTimeout(function() {}, 100); + + const scheduler = new jasmineUnderTest.DelayedFunctionScheduler(); + const fn = jasmine.createSpy('fn'); + + for (let i = 0; i < nativeTimeoutId; i++) { + scheduler.scheduleFunction(fn, 0, [], false); + } + scheduler.removeFunctionWithId(nativeTimeoutId); + scheduler.tick(1); + + expect(fn).toHaveBeenCalledTimes(nativeTimeoutId); + }); + describe('ticking inside a scheduled function', function() { let clock; diff --git a/src/core/DelayedFunctionScheduler.js b/src/core/DelayedFunctionScheduler.js index cca8ba46..8933866e 100644 --- a/src/core/DelayedFunctionScheduler.js +++ b/src/core/DelayedFunctionScheduler.js @@ -6,7 +6,7 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) { this.scheduledLookup_ = []; this.scheduledFunctions_ = {}; this.currentTime_ = 0; - this.delayedFnCount_ = 0; + this.delayedFnStartCount_ = 1e12; // arbitrarily large number to avoid collisions with native timer IDs; this.deletedKeys_ = []; this.tick = function(millis, tickDate) { @@ -38,7 +38,7 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) { } millis = millis || 0; - timeoutKey = timeoutKey || ++this.delayedFnCount_; + timeoutKey = timeoutKey || ++this.delayedFnStartCount_; runAtMillis = runAtMillis || this.currentTime_ + millis; const funcToSchedule = {