Repository: facebook/react
Pull request: Internal test helpers: Use Node's MessageChannel to queue task

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.

Changed files:
filename: packages/internal-test-utils/enqueueTask.js
patch: @@ -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);
 }

filename: packages/scheduler/src/__tests__/SchedulerProfiling-test.js
patch: @@ -498,7 +498,7 @@ Task 1 [Normal]              │                    █████████
       taskId++;
       const task = scheduleCallback(NormalPriority, () => {});
       cancelCallback(task);
-      await waitForAll([]);
+      Scheduler.unstable_flushAll();
     }
 
     expect(console.error).toHaveBeenCalledTimes(1);

filename: scripts/flow/environment.js
patch: @@ -190,3 +190,10 @@ declare module 'async_hooks' {
     enterWith(store: T): void;
   }
 }
+
+declare module 'node:worker_threads' {
+  declare class MessageChannel {
+    port1: MessagePort;
+    port2: MessagePort;
+  }
+}