-
Notifications
You must be signed in to change notification settings - Fork 4k
feat(test): add advanceTimers option to jest.useFakeTimers()
#26493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Adds support for the `advanceTimers` option in `jest.useFakeTimers()`, which automatically advances fake time in real time. This is critical for compatibility with libraries like @testing-library/user-event that wait for timers to resolve during interactions. - `advanceTimers: true` advances fake time by 20ms every 20ms (Jest default) - `advanceTimers: <number>` advances fake time by that amount every real-time interval - `advanceTimers: false` (or omitting the option) keeps the existing manual-advance behavior The implementation uses a real-time EventLoopTimer with a new `FakeTimersAutoAdvance` tag that fires fake timer callbacks while preserving the mocked time semantics. Fixes #26037 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
WalkthroughSelects per-timer time reference (real vs mocked) in timer core, adds a FakeTimersAutoAdvance tag and real-time-driven auto-advance machinery, exposes an advanceTimers option in FakeTimersConfig, and adds regression tests covering auto-advance and fake/real timer interactions. Changes
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/bun.js/api/Timer.zig (1)
291-334: Pass the correct timespec to timers that don't allow fake timers.
Line 54 always callst.fire(&now, ...), butnext()only initializesnowfor timers whereallowFakeTimers()is true. Timers likeBunTest,WTFTimer, andEventLoopDelayMonitor(which haveallowFakeTimers() == false) use thenowparameter in their fire callbacks but receive an uninitialized value.✅ Suggested fix
while (this.next(&has_set_now, &now, &has_set_real_now, &real_now)) |t| { + const fire_now = if (!t.tag.allowFakeTimers()) &real_now else &now; - t.fire(&now, vm); + t.fire(fire_now, vm); }
🤖 Fix all issues with AI agents
In `@src/bun.js/test/timers/FakeTimers.zig`:
- Around line 8-17: Make the two internal-only fields auto_advance_timer and
auto_advance_interval_ms private by renaming them to `#auto_advance_timer` and
`#auto_advance_interval_ms` (matching the existing private field pattern like
`#active`); update all internal references/usages of auto_advance_timer and
auto_advance_interval_ms within the same struct/module to the new private names,
and if external access is required create explicit accessors or methods instead
of keeping them public.
- Around line 312-329: The numeric-path for config.advanceTimers currently
converts advance_num via `@intFromFloat` after range checks but misses a finite
check, so NaN slips past (since NaN < 1 and NaN > maxInt are both false); update
the branch handling config.advanceTimers.isNumber() (using symbols advance_num,
`@intFromFloat`, auto_advance_ms) to first validate std.math.isFinite(advance_num)
and if not finite call globalObject.throwInvalidArguments("'advanceTimers' must
be a finite number", .{}), then perform the existing range checks and finally
assign auto_advance_ms = `@intFromFloat`(advance_num).
Address review feedback: - Rename auto_advance_timer to #auto_advance_timer (private) - Rename auto_advance_interval_ms to #auto_advance_interval_ms (private) - Add std.math.isFinite check to reject NaN/Infinity for advanceTimers option Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Addressed review feedback in commit 9efc218:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/bun.js/test/timers/FakeTimers.zig`:
- Around line 205-215: After running this.executeUntil(globalObject, target) we
must avoid unconditionally setting current_time back to target because callbacks
may have advanced time past target; instead, while still holding the timers.lock
and after the existing isActive check, read the current time/offset from
current_time (using the existing current_time accessors) and only call
current_time.set(globalObject, .{ .offset = &target }) if target is strictly
greater than the current offset; keep the existing timers.lock, defer unlock,
and preserve the isActive check (symbols: executeUntil, timers.lock, isActive,
current_time.set, target, globalObject).
Address review feedback: callbacks during executeUntil may manually advance time past target via advanceTimersByTime(), so we should only set current_time to target if it would actually advance time forward. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Addressed additional review feedback in commit f224b8b:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/bun.js/test/timers/FakeTimers.zig`:
- Around line 242-254: Add a defensive assertion at the start of
stopAutoAdvanceTimer to verify the required timers lock is held before
proceeding: assert the appropriate mutex/lock (the timers heap lock referenced
by this.timers or vm.timer) is currently owned/held, then continue with the
existing logic that manipulates this.#auto_advance_timer, this.timers, and
vm.timer; this prevents accidental misuse when calling stopAutoAdvanceTimer.
Add defensive assertion to verify the timers lock is held before manipulating timer state, preventing accidental misuse. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Addressed review feedback in commit a481971:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/bun.js/test/timers/FakeTimers.zig`:
- Around line 227-240: scheduleAutoAdvanceTimer currently always calls
vm.timer.insert(&this.#auto_advance_timer), which can double-insert the
intrusive timer when useFakeTimers() is called multiple times; before inserting,
check the timer state (this.#auto_advance_timer.state) and only set next/state
and call vm.timer.insert(...) if the state is not already .PENDING (or otherwise
already scheduled). Update scheduleAutoAdvanceTimer (and the similar block at
351-354) to short-circuit when the timer is already scheduled to prevent
duplicate insertions into the heap.
Add check to scheduleAutoAdvanceTimer to prevent duplicate insertion when useFakeTimers() is called multiple times. The timer is only inserted if in_heap is .none. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Addressed review feedback in commit a89dcc1:
|
Summary
advanceTimersoption tojest.useFakeTimers()for automatic timer advancementadvanceTimers: trueadvances fake time by 20ms every 20ms of real time (matching Jest's default behavior)advanceTimers: <number>advances fake time by that number of millisecondsImplementation Details
Uses a real-time
EventLoopTimerwith a newFakeTimersAutoAdvancetag that:useRealTimers()is called during timer callbacksTest plan
test/regression/issue/26037.test.tscovering:advanceTimers: trueauto-advances timersadvanceTimers: <number>advances by specified amountadvanceTimers: falsedoes not auto-advanceuseRealTimers()properly stops auto-advancingsetIntervalnowoptionuseRealTimers()called during auto-advancetest/js/bun/test/fake-timers/fake-timers.test.ts)Fixes #26037
🤖 Generated with Claude Code