@samitouri / QOS-React-2 / commits / 83643778bd

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.

Andrew Clark committed Mar 8, 2023 at 15:04 UTC 83643778bd5805504c87cce90ca997d13bf528d1
3 files changed +12 -39
packages/internal-test-utils/enqueueTask.js
+4 -38
@@ -7,44 +7,10 @@
7 * @flow
8 */
9
10 -let didWarnAboutMessageChannel = false;
11 -let enqueueTaskImpl = null;
10 +const {MessageChannel} = require('node:worker_threads');
11
13 -// Same as shared/enqeuueTask, but while that one used by the public
14 -// implementation of `act`, this is only used by our internal testing helpers.
12 export default function enqueueTask(task: () => void): void {
16 - if (enqueueTaskImpl === null) {
17 - try {
18 - // read require off the module object to get around the bundlers.
19 - // we don't want them to detect a require and bundle a Node polyfill.
20 - const requireString = ('require' + Math.random()).slice(0, 7);
21 - const nodeRequire = module && module[requireString];
22 - // assuming we're in node, let's try to get node's
23 - // version of setImmediate, bypassing fake timers if any.
24 - enqueueTaskImpl = nodeRequire.call(module, 'timers').setImmediate;
25 - } catch (_err) {
26 - // we're in a browser
27 - // we can't use regular timers because they may still be faked
28 - // so we try MessageChannel+postMessage instead
29 - enqueueTaskImpl = function (callback: () => void) {
30 - if (__DEV__) {
31 - if (didWarnAboutMessageChannel === false) {
32 - didWarnAboutMessageChannel = true;
33 - if (typeof MessageChannel === 'undefined') {
34 - console['error'](
35 - 'This browser does not have a MessageChannel implementation, ' +
36 - 'so enqueuing tasks via await act(async () => ...) will fail. ' +
37 - 'Please file an issue at https://github.com/facebook/react/issues ' +
38 - 'if you encounter this warning.',
39 - );
40 - }
41 - }
42 - }
43 - const channel = new MessageChannel();
44 - channel.port1.onmessage = callback;
45 - channel.port2.postMessage(undefined);
46 - };
47 - }
48 - }
49 - return enqueueTaskImpl(task);
13 + const channel = new MessageChannel();
14 + channel.port1.onmessage = task;
15 + channel.port2.postMessage(undefined);
16 }
packages/scheduler/src/__tests__/SchedulerProfiling-test.js
+1 -1
@@ -498,7 +498,7 @@ Task 1 [Normal] │ █████████
498 taskId++;
499 const task = scheduleCallback(NormalPriority, () => {});
500 cancelCallback(task);
501 - await waitForAll([]);
501 + Scheduler.unstable_flushAll();
502 }
503
504 expect(console.error).toHaveBeenCalledTimes(1);
scripts/flow/environment.js
+7
@@ -190,3 +190,10 @@ declare module 'async_hooks' {
190 enterWith(store: T): void;
191 }
192 }
193 +
194 +declare module 'node:worker_threads' {
195 + declare class MessageChannel {
196 + port1: MessagePort;
197 + port2: MessagePort;
198 + }
199 +}