From f152aee181de2b07c2aa7663c81fe049821ff9c5 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Tue, 10 Feb 2026 04:21:31 -0800 Subject: [PATCH] Fix flaky Modal-itest "MessageQueue is not empty" failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: The Modal integration test ` › ref › instance › uses the "RN:ModalHostView" tag name` was flaky on CI, intermittently failing with "MessageQueue is not empty" during the global afterEach `validateEmptyMessageQueue` hook. The root cause is that when Modal renders in __DEV__ mode, AppContainer's `useEffect` schedules passive effects as a deferred task on the RuntimeScheduler. Due to how microtask scheduling interacts with the RuntimeScheduler drain loop in Hermes, `flushMessageQueue()` inside `runTask` occasionally returns before this task is fully processed, leaving it on the queue for `validateEmptyMessageQueue` to find. The fix adds an `afterEach` hook at the top-level `describe('')` block that calls `Fantom.runWorkLoop()`. This provides a second drain pass before the global `validateEmptyMessageQueue` check runs (inner afterEach hooks execute before outer ones). The call is a no-op when the queue is already empty, so it adds no overhead to tests where the initial drain was complete. This is consistent with patterns in Fantom-itest.js where Modal tests that call additional `runWorkLoop()` (for `enqueueModalSizeUpdate`) never exhibit this flakiness, precisely because that second drain catches any residual passive effects work. Differential Revision: D92828076 --- .../react-native/Libraries/Modal/__tests__/Modal-itest.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/react-native/Libraries/Modal/__tests__/Modal-itest.js b/packages/react-native/Libraries/Modal/__tests__/Modal-itest.js index 9cf86470d4f7..30ed74e0c2b2 100644 --- a/packages/react-native/Libraries/Modal/__tests__/Modal-itest.js +++ b/packages/react-native/Libraries/Modal/__tests__/Modal-itest.js @@ -28,6 +28,14 @@ const DEFAULT_MODAL_CHILD_VIEW = ( ); describe('', () => { + afterEach(() => { + // Flush any remaining tasks that may have been scheduled during the + // render (e.g. passive effects from AppContainer in __DEV__ mode). + // This prevents flaky "MessageQueue is not empty" failures from the + // global afterEach validation hook. + Fantom.runWorkLoop(); + }); + describe('props', () => { it('renders a Modal with the default values when no props are passed', () => { const root = Fantom.createRoot();