Fix flaky test timeouts: synchronous requestAnimationFrame in test setup
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 2m53s
🚀 Deploy / ʦ🔍 Typecheck & Lint (pull_request) Successful in 1m29s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped

Radix UI and @floating-ui call requestAnimationFrame for layout/positioning
on every render — Popovers, DropdownMenus, Checkboxes, etc.  In jsdom those
callbacks are deferred outside the current act() boundary, leaving pending
state updates that keep the act() settlement loop running until Vitest's
5000ms hard timeout kills the test.  Under load (full suite) there is no
slack to absorb the deferral.

Overriding requestAnimationFrame/cancelAnimationFrame with synchronous
no-ops in the global test setup ensures every Radix UI callback completes
inside the current act() cycle.  This fixes the whole class of
Radix-UI-induced flaky timeouts without touching individual test files.

https://claude.ai/code/session_012ACmUs2vAwEF9jZu7Guos2
This commit is contained in:
Claude 2026-06-10 04:15:03 +00:00
parent 40e1ba22b8
commit 15097ddcf8
No known key found for this signature in database

View file

@ -2,6 +2,22 @@ import '@testing-library/jest-dom';
import { cleanup } from '@testing-library/react';
import { afterEach, vi } from 'vitest';
// Radix UI / @floating-ui calls requestAnimationFrame for layout and
// positioning on every render (Popover, DropdownMenu, Checkbox focus rings,
// etc.). In jsdom those callbacks are deferred to a future tick, landing
// outside the current act() boundary. React queues the resulting state
// updates, which schedule more microtasks, and the act() settlement loop
// keeps running until Vitest's 5000ms hard timeout kills the test.
//
// Making rAF synchronous keeps every callback inside the current act() cycle
// so there are no pending tasks when the test finishes. This fixes the whole
// class of flaky timeouts without touching individual test files.
global.requestAnimationFrame = (cb: FrameRequestCallback): number => {
cb(performance.now());
return 0;
};
global.cancelAnimationFrame = (_id: number): void => {};
// Cleanup after each test
afterEach(() => {
cleanup();