Skip to content

Commit

Permalink
Add test cases and revise traversal
Browse files Browse the repository at this point in the history
Fix
  • Loading branch information
trueadm committed Feb 20, 2020
1 parent 8582b99 commit 4bd5fd2
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 5 deletions.
12 changes: 10 additions & 2 deletions packages/react-dom/src/events/DOMModernPluginEventSystem.js
Expand Up @@ -56,7 +56,7 @@ import {
import {trapEventForPluginEventSystem} from './ReactDOMEventListener';
import getEventTarget from './getEventTarget';
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
import {HostRoot} from 'shared/ReactWorkTags';
import {HostRoot, HostPortal} from 'shared/ReactWorkTags';

const capturePhaseEvents = new Set([
TOP_FOCUS,
Expand Down Expand Up @@ -120,15 +120,23 @@ function getContainerAncestorInstance(
let node = targetInst;

while (node !== null) {
const tag = node.tag;
if (node === containerInst || node === containerInstAlt) {
return ancestor;
} else if (node.tag === HostRoot) {
} else if (tag === HostRoot) {
node = getHostRootClosestInstance(node);
if (node === null) {
return ancestor;
}
ancestor = node;
continue;
} else if (tag === HostPortal) {
const portalInst = getHostRootClosestInstance(node);
if (portalInst === containerInst || portalInst === containerInstAlt) {
return ancestor;
}
const parent = node.return;
ancestor = parent;
}
node = node.return;
}
Expand Down
Expand Up @@ -136,7 +136,6 @@ describe('DOMModernPluginEventSystem', () => {

const portalElement = document.createElement('div');
document.body.appendChild(portalElement);
portalElement.isPortal = true;

function Child() {
return (
Expand Down Expand Up @@ -178,6 +177,117 @@ describe('DOMModernPluginEventSystem', () => {
document.body.removeChild(portalElement);
});

it('handle click events on document.body portals', () => {
const log = [];

function Child({label}) {
return <div onClick={() => log.push(label)}>{label}</div>;
}

function Parent() {
return (
<>
{ReactDOM.createPortal(<Child label={'first'} />, document.body)}
{ReactDOM.createPortal(<Child label={'second'} />, document.body)}
</>
);
}

ReactDOM.render(<Parent />, container);

const second = document.body.lastChild;
expect(second.textContent).toEqual('second');
dispatchClickEvent(second);

expect(log).toEqual(['second']);

const first = second.previousSibling;
expect(first.textContent).toEqual('first');
dispatchClickEvent(first);

expect(log).toEqual(['second', 'first']);
});

it('handle click events on document.body dynamic portals', () => {
const log = [];

function Parent() {
const ref = React.useRef(null);
const [portal, setPortal] = React.useState(null);

React.useEffect(() => {
setPortal(
ReactDOM.createPortal(
<span onClick={() => log.push('child')} id="child" />,
ref.current,
),
);
});

return (
<div ref={ref} onClick={() => log.push('parent')} id="parent">
{portal}
</div>
);
}

ReactDOM.render(<Parent />, container);

const parent = container.lastChild;
expect(parent.id).toEqual('parent');
dispatchClickEvent(parent);

expect(log).toEqual(['parent']);

const child = parent.lastChild;
expect(child.id).toEqual('child');
dispatchClickEvent(child);

// we add both 'child' and 'parent' due to bubbling
expect(log).toEqual(['parent', 'child', 'parent']);
});

// Slight alteration to the last test, to catch
// a subtle difference in traversal.
it('handle click events on document.body dynamic portals #2', () => {
const log = [];

function Parent() {
const ref = React.useRef(null);
const [portal, setPortal] = React.useState(null);

React.useEffect(() => {
setPortal(
ReactDOM.createPortal(
<span onClick={() => log.push('child')} id="child" />,
ref.current,
),
);
});

return (
<div ref={ref} onClick={() => log.push('parent')} id="parent">
<div>{portal}</div>
</div>
);
}

ReactDOM.render(<Parent />, container);

const parent = container.lastChild;
expect(parent.id).toEqual('parent');
dispatchClickEvent(parent);

expect(log).toEqual(['parent']);

const child = parent.lastChild;
expect(child.id).toEqual('child');
dispatchClickEvent(child);

// we add both 'child' and 'parent' due to bubbling
expect(log).toEqual(['parent', 'child', 'parent']);
});

it('native stopPropagation on click events between portals', () => {
const buttonRef = React.createRef();
const divRef = React.createRef();
Expand Down Expand Up @@ -338,7 +448,6 @@ describe('DOMModernPluginEventSystem', () => {

const portalElement = document.createElement('div');
document.body.appendChild(portalElement);
portalElement.isPortal = true;

function Child() {
return (
Expand Down
29 changes: 28 additions & 1 deletion packages/shared/ReactTreeTraversal.js
Expand Up @@ -5,9 +5,36 @@
* LICENSE file in the root directory of this source tree.
*/

import {HostComponent} from './ReactWorkTags';
import {HostComponent, HostPortal, HostRoot} from './ReactWorkTags';
import {enableModernEventSystem} from './ReactFeatureFlags';

function getParent(inst) {
if (enableModernEventSystem) {
let node = inst.return;

while (node !== null) {
if (node.tag === HostPortal) {
let grandNode = node;
const portalNode = node.stateNode.containerInfo;
while (grandNode !== null) {
// If we find a root that is actually a parent in the DOM tree
// then we don't continue with getting the parent, as that root
// will have its own event listener.
if (
grandNode.tag === HostRoot &&
grandNode.stateNode.containerInfo.contains(portalNode)
) {
return null;
}
grandNode = grandNode.return;
}
} else if (node.tag === HostComponent) {
return node;
}
node = node.return;
}
return null;
}
do {
inst = inst.return;
// TODO: If this is a HostRoot we might want to bail out.
Expand Down

0 comments on commit 4bd5fd2

Please sign in to comment.