From 15097ddcf8c43b65a60c43f3f190ddfd0549c16c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 10 Jun 2026 04:15:03 +0000 Subject: [PATCH] Fix flaky test timeouts: synchronous requestAnimationFrame in test setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- app/test/setup.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/test/setup.ts b/app/test/setup.ts index 255906e..f39c595 100644 --- a/app/test/setup.ts +++ b/app/test/setup.ts @@ -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();