Skip to content

Commit 8364377

Browse files
authored
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.
1 parent f36ab0e commit 8364377

File tree

3 files changed

+12
-39
lines changed

3 files changed

+12
-39
lines changed

packages/internal-test-utils/enqueueTask.js

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,44 +7,10 @@
77
* @flow
88
*/
99

10-
let didWarnAboutMessageChannel = false;
11-
let enqueueTaskImpl = null;
10+
const {MessageChannel} = require('node:worker_threads');
1211

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.
1512
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);
5016
}

packages/scheduler/src/__tests__/SchedulerProfiling-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ Task 1 [Normal] │ █████████
498498
taskId++;
499499
const task = scheduleCallback(NormalPriority, () => {});
500500
cancelCallback(task);
501-
await waitForAll([]);
501+
Scheduler.unstable_flushAll();
502502
}
503503

504504
expect(console.error).toHaveBeenCalledTimes(1);

scripts/flow/environment.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,10 @@ declare module 'async_hooks' {
190190
enterWith(store: T): void;
191191
}
192192
}
193+
194+
declare module 'node:worker_threads' {
195+
declare class MessageChannel {
196+
port1: MessagePort;
197+
port2: MessagePort;
198+
}
199+
}

0 commit comments

Comments
 (0)