Skip to content

Commit

Permalink
Internal test helpers: Use Node's MessageChannel to queue task
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
acdlite committed Mar 8, 2023
1 parent f36ab0e commit f3eaa76
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 38 deletions.
42 changes: 4 additions & 38 deletions packages/internal-test-utils/enqueueTask.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
7 changes: 7 additions & 0 deletions scripts/flow/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,10 @@ declare module 'async_hooks' {
enterWith(store: T): void;
}
}

declare module 'node:worker_threads' {
declare class MessageChannel {
port1: MessagePort;
port2: MessagePort;
}
}

0 comments on commit f3eaa76

Please sign in to comment.