Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add simulateEventDispatch to test ReactDOMEventListener #28079

Merged
merged 10 commits into from
Feb 8, 2024
38 changes: 38 additions & 0 deletions packages/internal-test-utils/ReactInternalTestUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as SchedulerMock from 'scheduler/unstable_mock';
import {diff} from 'jest-diff';
import {equals} from '@jest/expect-utils';
import enqueueTask from './enqueueTask';
import simulateBrowserEventDispatch from './simulateBrowserEventDispatch';

export {act} from './internalAct';

Expand Down Expand Up @@ -264,3 +265,40 @@ ${diff(expectedLog, actualLog)}
Error.captureStackTrace(error, assertLog);
throw error;
}

// Simulates dispatching events, waiting for microtasks in between.
// This matches the browser behavior, which will flush microtasks
// between each event handler. This will allow discrete events to
// flush between events across different event handlers.
export async function simulateEventDispatch(
node: Node,
eventType: string,
): Promise<void> {
// Ensure the node is in the document.
for (let current = node; current; current = current.parentNode) {
Copy link
Collaborator

@sebmarkbage sebmarkbage Jan 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically if you remove or move a node during this phase it'll get the events.

So really this should create a snapshot of all parent nodes into an array and then dispatch to each of those. That way if they change the DOM during the bubbling phase, all events still happen (on disconnected nodes).

I wish I knew less about the DOM.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if this doesn't end up with a parentNode that is in a document, it shouldn't dispatch anything because it means the target was detached before dispatching.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed and added a test.

if (current === document) {
break;
} else if (current.parentNode == null) {
return;
}
}

const customEvent = new Event(eventType, {
bubbles: true,
});

Object.defineProperty(customEvent, 'target', {
// Override the target to the node on which we dispatched the event.
value: node,
});

const impl = Object.getOwnPropertySymbols(node)[0];
const oldDispatch = node[impl].dispatchEvent;
try {
node[impl].dispatchEvent = simulateBrowserEventDispatch;

await node.dispatchEvent(customEvent);
} finally {
node[impl].dispatchEvent = oldDispatch;
}
}
Loading