diff --git a/lib/internal/test_runner/mock/mock_timers.js b/lib/internal/test_runner/mock/mock_timers.js index 85512cc4b66a51..00fe29d43c97ad 100644 --- a/lib/internal/test_runner/mock/mock_timers.js +++ b/lib/internal/test_runner/mock/mock_timers.js @@ -622,8 +622,12 @@ class MockTimers { if (timer.runAt > this.#now) break; FunctionPrototypeApply(timer.callback, undefined, timer.args); - this.#executionQueue.shift(); - timer.priorityQueuePosition = undefined; + // Check if the timeout was cleared by calling clearTimeout inside its own callback + const afterCallback = this.#executionQueue.peek(); + if (afterCallback.id === timer.id) { + this.#executionQueue.shift(); + timer.priorityQueuePosition = undefined; + } if (timer.interval !== undefined) { timer.runAt += timer.interval; diff --git a/test/parallel/test-runner-mock-timers.js b/test/parallel/test-runner-mock-timers.js index eb5cec84e122f2..7e519e8a2ad2fb 100644 --- a/test/parallel/test-runner-mock-timers.js +++ b/test/parallel/test-runner-mock-timers.js @@ -532,6 +532,22 @@ describe('Mock Timers Test Suite', () => { await nodeTimersPromises.setImmediate(); // let promises settle assert.strictEqual(f2.mock.callCount(), 1); }); + + it('should not affect other timers when clearing timeout inside own callback', (t) => { + t.mock.timers.enable({ apis: ['setTimeout'] }); + const f = t.mock.fn(); + + const timer = nodeTimers.setTimeout(() => { + f(); + // Clearing the already-expired timeout should do nothing + nodeTimers.clearTimeout(timer); + }, 50); + nodeTimers.setTimeout(f, 50); + nodeTimers.setTimeout(f, 50); + + t.mock.timers.runAll(); + assert.strictEqual(f.mock.callCount(), 3); + }); }); describe('setInterval Suite', () => {