From eea8f04ff8d91d7784946c27ceda54c1df86f8b9 Mon Sep 17 00:00:00 2001 From: sangwook Date: Sun, 19 Apr 2026 15:10:46 +0900 Subject: [PATCH] test_runner: align mock timeout api Expose Timeout.close() and Timeout[Symbol.dispose]() from MockTimers. This keeps fake setTimeout() handles aligned with the public Timeout API so disposal-based patterns do not throw. Add targeted coverage for Timeout.close() and Timeout[Symbol.dispose](). --- lib/internal/test_runner/mock/mock_timers.js | 13 ++++++++++ test/parallel/test-runner-mock-timers.js | 26 ++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/lib/internal/test_runner/mock/mock_timers.js b/lib/internal/test_runner/mock/mock_timers.js index 643128c3f0031f..d8372368467827 100644 --- a/lib/internal/test_runner/mock/mock_timers.js +++ b/lib/internal/test_runner/mock/mock_timers.js @@ -69,12 +69,15 @@ const TIMERS_DEFAULT_INTERVAL = { }; class Timeout { + #clear; + constructor(opts) { this.id = opts.id; this.callback = opts.callback; this.runAt = opts.runAt; this.interval = opts.interval; this.args = opts.args; + this.#clear = opts.clear; } hasRef() { @@ -92,6 +95,15 @@ class Timeout { refresh() { return this; } + + close() { + this.#clear(this); + return this; + } + + [SymbolDispose]() { + this.#clear(this); + } } class MockTimers { @@ -310,6 +322,7 @@ class MockTimers { runAt: this.#now + delay, interval: isInterval ? delay : undefined, args, + clear: this.#clearTimeout, }; const timer = new Timeout(opts); diff --git a/test/parallel/test-runner-mock-timers.js b/test/parallel/test-runner-mock-timers.js index 17602e4eb90163..09075cf3a58fa8 100644 --- a/test/parallel/test-runner-mock-timers.js +++ b/test/parallel/test-runner-mock-timers.js @@ -266,6 +266,32 @@ describe('Mock Timers Test Suite', () => { assert.deepStrictEqual(fn.mock.calls[0].arguments, args); }); + it('should expose Timeout.prototype[Symbol.dispose]', (t) => { + t.mock.timers.enable({ apis: ['setTimeout'] }); + const fn = t.mock.fn(); + const timeout = globalThis.setTimeout(fn, 2000); + + assert.strictEqual(typeof timeout[Symbol.dispose], 'function'); + + timeout[Symbol.dispose](); + t.mock.timers.tick(2000); + + assert.strictEqual(fn.mock.callCount(), 0); + }); + + it('should expose Timeout.prototype.close()', (t) => { + t.mock.timers.enable({ apis: ['setTimeout'] }); + const fn = t.mock.fn(); + const timeout = globalThis.setTimeout(fn, 2000); + + assert.strictEqual(typeof timeout.close, 'function'); + assert.strictEqual(timeout.close(), timeout); + + t.mock.timers.tick(2000); + + assert.strictEqual(fn.mock.callCount(), 0); + }); + it('should keep setTimeout working if timers are disabled', (t, done) => { const now = Date.now(); const timeout = 2;