From 83643778bd5805504c87cce90ca997d13bf528d1 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Wed, 8 Mar 2023 15:04:38 -0500 Subject: [PATCH] Internal test helpers: Use Node's MessageChannel to queue task (#26345) To wait for the microtask queue to empty, our internal test helpers schedule an arbitrary task using `setImmediate`. It doesn't matter what kind of task it is, only that it's a separate task from the current one, because by the time it fires, the microtasks for the current event will have already been processed. The issue with `setImmediate` is that Jest mocks it. Which can lead to weird behavior. I've changed it to instead use a message event, via the MessageChannel implementation exposed by the `node:worker_threads` module. We should consider doing this in the public implementation of `act`, too. --- packages/internal-test-utils/enqueueTask.js | 42 ++----------------- .../src/__tests__/SchedulerProfiling-test.js | 2 +- scripts/flow/environment.js | 7 ++++ 3 files changed, 12 insertions(+), 39 deletions(-) diff --git a/packages/internal-test-utils/enqueueTask.js b/packages/internal-test-utils/enqueueTask.js index 6f7f00fee68ca..99b680d5cdd17 100644 --- a/packages/internal-test-utils/enqueueTask.js +++ b/packages/internal-test-utils/enqueueTask.js @@ -7,44 +7,10 @@ * @flow */ -let didWarnAboutMessageChannel = false; -let enqueueTaskImpl = null; +const {MessageChannel} = require('node:worker_threads'); -// Same as shared/enqeuueTask, but while that one used by the public -// implementation of `act`, this is only used by our internal testing helpers. export default function enqueueTask(task: () => void): void { - if (enqueueTaskImpl === null) { - try { - // read require off the module object to get around the bundlers. - // we don't want them to detect a require and bundle a Node polyfill. - const requireString = ('require' + Math.random()).slice(0, 7); - const nodeRequire = module && module[requireString]; - // assuming we're in node, let's try to get node's - // version of setImmediate, bypassing fake timers if any. - enqueueTaskImpl = nodeRequire.call(module, 'timers').setImmediate; - } catch (_err) { - // we're in a browser - // we can't use regular timers because they may still be faked - // so we try MessageChannel+postMessage instead - enqueueTaskImpl = function (callback: () => void) { - if (__DEV__) { - if (didWarnAboutMessageChannel === false) { - didWarnAboutMessageChannel = true; - if (typeof MessageChannel === 'undefined') { - console['error']( - 'This browser does not have a MessageChannel implementation, ' + - 'so enqueuing tasks via await act(async () => ...) will fail. ' + - 'Please file an issue at https://github.com/facebook/react/issues ' + - 'if you encounter this warning.', - ); - } - } - } - const channel = new MessageChannel(); - channel.port1.onmessage = callback; - channel.port2.postMessage(undefined); - }; - } - } - return enqueueTaskImpl(task); + const channel = new MessageChannel(); + channel.port1.onmessage = task; + channel.port2.postMessage(undefined); } diff --git a/packages/scheduler/src/__tests__/SchedulerProfiling-test.js b/packages/scheduler/src/__tests__/SchedulerProfiling-test.js index f0a88cead2334..eef602d90ac3d 100644 --- a/packages/scheduler/src/__tests__/SchedulerProfiling-test.js +++ b/packages/scheduler/src/__tests__/SchedulerProfiling-test.js @@ -498,7 +498,7 @@ Task 1 [Normal] │ █████████ taskId++; const task = scheduleCallback(NormalPriority, () => {}); cancelCallback(task); - await waitForAll([]); + Scheduler.unstable_flushAll(); } expect(console.error).toHaveBeenCalledTimes(1); diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js index 6ebf107207bd5..046e0a143542a 100644 --- a/scripts/flow/environment.js +++ b/scripts/flow/environment.js @@ -190,3 +190,10 @@ declare module 'async_hooks' { enterWith(store: T): void; } } + +declare module 'node:worker_threads' { + declare class MessageChannel { + port1: MessagePort; + port2: MessagePort; + } +}