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

Convert ReactBrowserEventEmitter to createRoot #28253

Merged
merged 4 commits into from
Feb 6, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 153 additions & 78 deletions packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
'use strict';

let React;
let ReactDOM;
let ReactDOMClient;
let ReactTestUtils;
let act;

let idCallOrder;
const recordID = function (id) {
Expand All @@ -34,6 +35,7 @@ let PARENT;
let CHILD;
let BUTTON;

let renderTree;
let putListener;
let deleteAllListeners;

Expand All @@ -47,9 +49,9 @@ describe('ReactBrowserEventEmitter', () => {
LISTENER.mockClear();

React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
ReactTestUtils = require('react-dom/test-utils');

act = require('internal-test-utils').act;
container = document.createElement('div');
document.body.appendChild(container);

Expand All @@ -68,21 +70,26 @@ describe('ReactBrowserEventEmitter', () => {
}
}

function renderTree() {
ReactDOM.render(
<div ref={c => (GRANDPARENT = c)} {...GRANDPARENT_PROPS}>
<div ref={c => (PARENT = c)} {...PARENT_PROPS}>
<ChildWrapper {...CHILD_PROPS} />
<button disabled={true} ref={c => (BUTTON = c)} {...BUTTON_PROPS} />
</div>
</div>,
container,
);
}

renderTree();
const root = ReactDOMClient.createRoot(container);

renderTree = async function () {
await act(() => {
root.render(
<div ref={c => (GRANDPARENT = c)} {...GRANDPARENT_PROPS}>
<div ref={c => (PARENT = c)} {...PARENT_PROPS}>
<ChildWrapper {...CHILD_PROPS} />
<button
disabled={true}
ref={c => (BUTTON = c)}
{...BUTTON_PROPS}
/>
</div>
</div>,
);
});
};

putListener = function (node, eventName, listener) {
putListener = async function (node, eventName, listener) {
switch (node) {
case CHILD:
CHILD_PROPS[eventName] = listener;
Expand All @@ -98,9 +105,10 @@ describe('ReactBrowserEventEmitter', () => {
break;
}
// Rerender with new event listeners
renderTree();
await renderTree();
};
deleteAllListeners = function (node) {

deleteAllListeners = async function (node) {
switch (node) {
case CHILD:
CHILD_PROPS = {};
Expand All @@ -115,7 +123,7 @@ describe('ReactBrowserEventEmitter', () => {
BUTTON_PROPS = {};
break;
}
renderTree();
await renderTree();
};

idCallOrder = [];
Expand All @@ -126,120 +134,178 @@ describe('ReactBrowserEventEmitter', () => {
container = null;
});

it('should bubble simply', () => {
putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
CHILD.click();
it('should bubble simply', async () => {
await renderTree();
await putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
await putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
await putListener(
GRANDPARENT,
ON_CLICK_KEY,
recordID.bind(null, GRANDPARENT),
);
await act(() => {
CHILD.click();
});
expect(idCallOrder.length).toBe(3);
expect(idCallOrder[0]).toBe(CHILD);
expect(idCallOrder[1]).toBe(PARENT);
expect(idCallOrder[2]).toBe(GRANDPARENT);
});

it('should bubble to the right handler after an update', () => {
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, 'GRANDPARENT'));
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, 'PARENT'));
putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, 'CHILD'));
CHILD.click();
it('should bubble to the right handler after an update', async () => {
await renderTree();
await putListener(
GRANDPARENT,
ON_CLICK_KEY,
recordID.bind(null, 'GRANDPARENT'),
);
await putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, 'PARENT'));
await putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, 'CHILD'));
await act(() => {
CHILD.click();
});
expect(idCallOrder).toEqual(['CHILD', 'PARENT', 'GRANDPARENT']);

idCallOrder = [];

// Update just the grand parent without updating the child.
putListener(
await putListener(
GRANDPARENT,
ON_CLICK_KEY,
recordID.bind(null, 'UPDATED_GRANDPARENT'),
);

CHILD.click();
await act(() => {
CHILD.click();
});
expect(idCallOrder).toEqual(['CHILD', 'PARENT', 'UPDATED_GRANDPARENT']);
});

it('should continue bubbling if an error is thrown', () => {
putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
putListener(PARENT, ON_CLICK_KEY, function () {
it('should continue bubbling if an error is thrown', async () => {
await renderTree();
await putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
await putListener(PARENT, ON_CLICK_KEY, function () {
recordID(PARENT);
throw new Error('Handler interrupted');
});
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
expect(function () {
ReactTestUtils.Simulate.click(CHILD);
}).toThrow();
await putListener(
GRANDPARENT,
ON_CLICK_KEY,
recordID.bind(null, GRANDPARENT),
);
await expect(
act(() => {
ReactTestUtils.Simulate.click(CHILD);
}),
).rejects.toThrow();
expect(idCallOrder.length).toBe(3);
expect(idCallOrder[0]).toBe(CHILD);
expect(idCallOrder[1]).toBe(PARENT);
expect(idCallOrder[2]).toBe(GRANDPARENT);
});

it('should set currentTarget', () => {
putListener(CHILD, ON_CLICK_KEY, function (event) {
it('should set currentTarget', async () => {
await renderTree();
await putListener(CHILD, ON_CLICK_KEY, function (event) {
recordID(CHILD);
expect(event.currentTarget).toBe(CHILD);
});
putListener(PARENT, ON_CLICK_KEY, function (event) {
await putListener(PARENT, ON_CLICK_KEY, function (event) {
recordID(PARENT);
expect(event.currentTarget).toBe(PARENT);
});
putListener(GRANDPARENT, ON_CLICK_KEY, function (event) {
await putListener(GRANDPARENT, ON_CLICK_KEY, function (event) {
recordID(GRANDPARENT);
expect(event.currentTarget).toBe(GRANDPARENT);
});
CHILD.click();
await act(() => {
CHILD.click();
});
expect(idCallOrder.length).toBe(3);
expect(idCallOrder[0]).toBe(CHILD);
expect(idCallOrder[1]).toBe(PARENT);
expect(idCallOrder[2]).toBe(GRANDPARENT);
});

it('should support stopPropagation()', () => {
putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
putListener(
it('should support stopPropagation()', async () => {
await renderTree();
await putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
await putListener(
PARENT,
ON_CLICK_KEY,
recordIDAndStopPropagation.bind(null, PARENT),
);
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
CHILD.click();
await putListener(
GRANDPARENT,
ON_CLICK_KEY,
recordID.bind(null, GRANDPARENT),
);
await act(() => {
CHILD.click();
});
expect(idCallOrder.length).toBe(2);
expect(idCallOrder[0]).toBe(CHILD);
expect(idCallOrder[1]).toBe(PARENT);
});

it('should support overriding .isPropagationStopped()', () => {
it('should support overriding .isPropagationStopped()', async () => {
await renderTree();
// Ew. See D4504876.
putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
putListener(PARENT, ON_CLICK_KEY, function (e) {
await putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
await putListener(PARENT, ON_CLICK_KEY, function (e) {
recordID(PARENT, e);
// This stops React bubbling but avoids touching the native event
e.isPropagationStopped = () => true;
});
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
CHILD.click();
await putListener(
GRANDPARENT,
ON_CLICK_KEY,
recordID.bind(null, GRANDPARENT),
);
await act(() => {
CHILD.click();
});
expect(idCallOrder.length).toBe(2);
expect(idCallOrder[0]).toBe(CHILD);
expect(idCallOrder[1]).toBe(PARENT);
});

it('should stop after first dispatch if stopPropagation', () => {
putListener(
it('should stop after first dispatch if stopPropagation', async () => {
await renderTree();
await putListener(
CHILD,
ON_CLICK_KEY,
recordIDAndStopPropagation.bind(null, CHILD),
);
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
CHILD.click();
await putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
await putListener(
GRANDPARENT,
ON_CLICK_KEY,
recordID.bind(null, GRANDPARENT),
);
await act(() => {
CHILD.click();
});
expect(idCallOrder.length).toBe(1);
expect(idCallOrder[0]).toBe(CHILD);
});

it('should not stopPropagation if false is returned', () => {
putListener(CHILD, ON_CLICK_KEY, recordIDAndReturnFalse.bind(null, CHILD));
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
CHILD.click();
it('should not stopPropagation if false is returned', async () => {
await renderTree();
await putListener(
CHILD,
ON_CLICK_KEY,
recordIDAndReturnFalse.bind(null, CHILD),
);
await putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
await putListener(
GRANDPARENT,
ON_CLICK_KEY,
recordID.bind(null, GRANDPARENT),
);
await act(() => {
CHILD.click();
});
expect(idCallOrder.length).toBe(3);
expect(idCallOrder[0]).toBe(CHILD);
expect(idCallOrder[1]).toBe(PARENT);
Expand All @@ -255,30 +321,39 @@ describe('ReactBrowserEventEmitter', () => {
* these new listeners.
*/

it('should invoke handlers that were removed while bubbling', () => {
it('should invoke handlers that were removed while bubbling', async () => {
await renderTree();
const handleParentClick = jest.fn();
const handleChildClick = function (event) {
deleteAllListeners(PARENT);
const handleChildClick = async function (event) {
await deleteAllListeners(PARENT);
};
putListener(CHILD, ON_CLICK_KEY, handleChildClick);
putListener(PARENT, ON_CLICK_KEY, handleParentClick);
CHILD.click();
await putListener(CHILD, ON_CLICK_KEY, handleChildClick);
await putListener(PARENT, ON_CLICK_KEY, handleParentClick);
await act(() => {
CHILD.click();
});
expect(handleParentClick).toHaveBeenCalledTimes(1);
});

it('should not invoke newly inserted handlers while bubbling', () => {
it('should not invoke newly inserted handlers while bubbling', async () => {
await renderTree();
const handleParentClick = jest.fn();
const handleChildClick = function (event) {
putListener(PARENT, ON_CLICK_KEY, handleParentClick);
const handleChildClick = async function (event) {
await putListener(PARENT, ON_CLICK_KEY, handleParentClick);
};
putListener(CHILD, ON_CLICK_KEY, handleChildClick);
CHILD.click();
await putListener(CHILD, ON_CLICK_KEY, handleChildClick);
await act(() => {
CHILD.click();
});
expect(handleParentClick).toHaveBeenCalledTimes(0);
});

it('should have mouse enter simulated by test utils', () => {
putListener(CHILD, ON_MOUSE_ENTER_KEY, recordID.bind(null, CHILD));
ReactTestUtils.Simulate.mouseEnter(CHILD);
it('should have mouse enter simulated by test utils', async () => {
await renderTree();
await putListener(CHILD, ON_MOUSE_ENTER_KEY, recordID.bind(null, CHILD));
await act(() => {
ReactTestUtils.Simulate.mouseEnter(CHILD);
});
expect(idCallOrder.length).toBe(1);
expect(idCallOrder[0]).toBe(CHILD);
});
Expand Down