mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
fix(test): make fake timers work with testing-library/react (#25915)
## Summary Fixes #25869 Two fixes to enable `jest.useFakeTimers()` to work with `@testing-library/react` and `@testing-library/user-event`: - Set `setTimeout.clock = true` when fake timers are enabled. testing-library/react's `jestFakeTimersAreEnabled()` checks for this property to determine if `jest.advanceTimersByTime()` should be called when draining the microtask queue. Without this, testing-library never advances timers. - Make `advanceTimersByTime(0)` fire `setTimeout(fn, 0)` timers. `setTimeout(fn, 0)` is internally scheduled with a 1ms delay per HTML spec. Jest/testing-library expect `advanceTimersByTime(0)` to fire such "immediate" timers, but we were advancing by 0ms so they never fired. ## Test plan - [x] All 30 existing fake timer tests pass - [x] New regression test validates both fixes - [x] Original user-event reproduction now works (test completes instead of hanging) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
81
test/regression/issue/25869.test.ts
Normal file
81
test/regression/issue/25869.test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
// https://github.com/oven-sh/bun/issues/25869
|
||||
// useFakeTimers with testing-library/react hangs when using user-event
|
||||
import { expect, jest, test } from "bun:test";
|
||||
|
||||
// Test that jestFakeTimersAreEnabled() detection works properly.
|
||||
// testing-library/react checks for setTimeout.clock or setTimeout._isMockFunction
|
||||
// to determine if fake timers are enabled.
|
||||
function jestFakeTimersAreEnabled(): boolean {
|
||||
// @ts-expect-error - checking for Jest fake timers markers
|
||||
if (typeof jest !== "undefined" && jest !== null) {
|
||||
return (
|
||||
// @ts-expect-error - checking for mock function marker
|
||||
(globalThis.setTimeout as any)._isMockFunction === true ||
|
||||
Object.prototype.hasOwnProperty.call(globalThis.setTimeout, "clock")
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
test("setTimeout.clock is not set before useFakeTimers", () => {
|
||||
expect(jestFakeTimersAreEnabled()).toBe(false);
|
||||
expect(Object.prototype.hasOwnProperty.call(globalThis.setTimeout, "clock")).toBe(false);
|
||||
});
|
||||
|
||||
test("setTimeout.clock is set after useFakeTimers", () => {
|
||||
jest.useFakeTimers();
|
||||
try {
|
||||
expect(jestFakeTimersAreEnabled()).toBe(true);
|
||||
expect(Object.prototype.hasOwnProperty.call(globalThis.setTimeout, "clock")).toBe(true);
|
||||
} finally {
|
||||
jest.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
test("setTimeout.clock is set to false after useRealTimers", () => {
|
||||
jest.useFakeTimers();
|
||||
jest.useRealTimers();
|
||||
// Note: The clock property remains on setTimeout but is set to false.
|
||||
// This differs from Jest/Sinon which removes the property entirely.
|
||||
// The value being false is sufficient for most use cases.
|
||||
expect((globalThis.setTimeout as any).clock).toBe(false);
|
||||
});
|
||||
|
||||
test("advanceTimersByTime(0) fires setTimeout(fn, 0) timers", async () => {
|
||||
jest.useFakeTimers();
|
||||
try {
|
||||
let called = false;
|
||||
setTimeout(() => {
|
||||
called = true;
|
||||
}, 0);
|
||||
|
||||
expect(called).toBe(false);
|
||||
jest.advanceTimersByTime(0);
|
||||
expect(called).toBe(true);
|
||||
} finally {
|
||||
jest.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
test("user-event style wait pattern does not hang", async () => {
|
||||
jest.useFakeTimers();
|
||||
try {
|
||||
// This is the pattern used by @testing-library/user-event in wait.js
|
||||
// It was hanging before the fix because:
|
||||
// 1. advanceTimersByTime(0) didn't fire setTimeout(fn, 0) timers
|
||||
// 2. jestFakeTimersAreEnabled() returned false, so advanceTimers wasn't called
|
||||
const delay = 0;
|
||||
|
||||
const result = await Promise.all([
|
||||
new Promise<string>(resolve => globalThis.setTimeout(() => resolve("timeout"), delay)),
|
||||
Promise.resolve().then(() => {
|
||||
jest.advanceTimersByTime(delay);
|
||||
return "advanced";
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(result).toEqual(["timeout", "advanced"]);
|
||||
} finally {
|
||||
jest.useRealTimers();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user