eventHandler(e.currentTarget.id, 'captured', e.type)}
+ onClick={e => eventHandler(e.currentTarget.id, 'bubbled', e.type)}
+ onMouseEnter={e => eventHandler(e.currentTarget.id, e.type)}
+ onMouseLeave={e => eventHandler(e.currentTarget.id, e.type)}
+ />
+
eventHandler(e.currentTarget.id, 'captured', e.type)}
+ onClick={e => eventHandler(e.currentTarget.id, 'bubbled', e.type)}
+ onMouseEnter={e => eventHandler(e.currentTarget.id, e.type)}
+ onMouseLeave={e => eventHandler(e.currentTarget.id, e.type)}
+ />
+
eventHandler(e.currentTarget.id, 'captured', e.type)}
+ onClick={e => eventHandler(e.currentTarget.id, 'bubbled', e.type)}
+ onMouseEnter={e => eventHandler(e.currentTarget.id, e.type)}
+ onMouseLeave={e => eventHandler(e.currentTarget.id, e.type)}>
+
eventHandler(e.currentTarget.id, 'captured', e.type)}
+ onClick={e => eventHandler(e.currentTarget.id, 'bubbled', e.type)}
+ onMouseEnter={e => eventHandler(e.currentTarget.id, e.type)}
+ onMouseLeave={e => eventHandler(e.currentTarget.id, e.type)}>
+
+
+
+
eventHandler(e.currentTarget.id, 'captured', e.type)}
+ onClick={e => eventHandler(e.currentTarget.id, 'bubbled', e.type)}
+ onMouseEnter={e => eventHandler(e.currentTarget.id, e.type)}
+ onMouseLeave={e => eventHandler(e.currentTarget.id, e.type)}
+ />
+
+);
+
+describe('ReactTreeTraversal', () => {
+ var mockFn = jest.fn();
+ var container;
+ var outerNode1;
+ var outerNode2;
+
+ beforeEach(() => {
+ React = require('react');
+ ReactDOM = require('react-dom');
+
+ mockFn.mockReset();
+
+ container = document.createElement('div');
+ outerNode1 = document.createElement('div');
+ outerNode2 = document.createElement('div');
+ document.body.appendChild(container);
+ document.body.appendChild(outerNode1);
+ document.body.appendChild(outerNode2);
+
+ ReactDOM.render(
, container);
+ });
+
+ afterEach(() => {
+ document.body.removeChild(container);
+ document.body.removeChild(outerNode1);
+ document.body.removeChild(outerNode2);
+ container = null;
+ outerNode1 = null;
+ outerNode2 = null;
+ });
+
+ describe('Two phase traversal', () => {
+ it('should not traverse when target is outside component boundary', () => {
+ outerNode1.dispatchEvent(
+ new MouseEvent('click', {bubbles: true, cancelable: true}),
+ );
+
+ expect(mockFn).not.toHaveBeenCalled();
+ });
+
+ it('should traverse two phase across component boundary', () => {
+ var expectedCalls = [
+ ['P', 'captured', 'click'],
+ ['P_P1', 'captured', 'click'],
+ ['P_P1_C1__DIV', 'captured', 'click'],
+ ['P_P1_C1__DIV_1', 'captured', 'click'],
+
+ ['P_P1_C1__DIV_1', 'bubbled', 'click'],
+ ['P_P1_C1__DIV', 'bubbled', 'click'],
+ ['P_P1', 'bubbled', 'click'],
+ ['P', 'bubbled', 'click'],
+ ];
+
+ var node = document.getElementById('P_P1_C1__DIV_1');
+ node.dispatchEvent(
+ new MouseEvent('click', {bubbles: true, cancelable: true}),
+ );
+
+ expect(mockFn.mock.calls).toEqual(expectedCalls);
+ });
+
+ it('should traverse two phase at shallowest node', () => {
+ var node = document.getElementById('P');
+ node.dispatchEvent(
+ new MouseEvent('click', {bubbles: true, cancelable: true}),
+ );
+
+ var expectedCalls = [
+ ['P', 'captured', 'click'],
+ ['P', 'bubbled', 'click'],
+ ];
+ expect(mockFn.mock.calls).toEqual(expectedCalls);
+ });
+ });
+
+ describe('Enter leave traversal', () => {
+ it('should not traverse when enter/leaving outside DOM', () => {
+ outerNode1.dispatchEvent(
+ new MouseEvent('mouseout', {
+ bubbles: true,
+ cancelable: true,
+ relatedTarget: outerNode2,
+ }),
+ );
+
+ expect(mockFn).not.toHaveBeenCalled();
+ });
+
+ it('should not traverse if enter/leave the same node', () => {
+ var leaveNode = document.getElementById('P_P1_C1__DIV_1');
+ var enterNode = document.getElementById('P_P1_C1__DIV_1');
+
+ leaveNode.dispatchEvent(
+ new MouseEvent('mouseout', {
+ bubbles: true,
+ cancelable: true,
+ relatedTarget: enterNode,
+ }),
+ );
+
+ expect(mockFn).not.toHaveBeenCalled();
+ });
+
+ it('should traverse enter/leave to sibling - avoids parent', () => {
+ var leaveNode = document.getElementById('P_P1_C1__DIV_1');
+ var enterNode = document.getElementById('P_P1_C1__DIV_2');
+
+ var expectedCalls = [
+ ['P_P1_C1__DIV_1', 'mouseleave'],
+ // enter/leave shouldn't fire anything on the parent
+ ['P_P1_C1__DIV_2', 'mouseenter'],
+ ];
+
+ leaveNode.dispatchEvent(
+ new MouseEvent('mouseout', {
+ bubbles: true,
+ cancelable: true,
+ relatedTarget: enterNode,
+ }),
+ );
+
+ expect(mockFn.mock.calls).toEqual(expectedCalls);
+ });
+
+ it('should traverse enter/leave to parent - avoids parent', () => {
+ var leaveNode = document.getElementById('P_P1_C1__DIV_1');
+ var enterNode = document.getElementById('P_P1_C1__DIV');
+
+ var expectedCalls = [['P_P1_C1__DIV_1', 'mouseleave']];
+
+ leaveNode.dispatchEvent(
+ new MouseEvent('mouseout', {
+ bubbles: true,
+ cancelable: true,
+ relatedTarget: enterNode,
+ }),
+ );
+
+ expect(mockFn.mock.calls).toEqual(expectedCalls);
+ });
+
+ it('should enter from the window', () => {
+ var enterNode = document.getElementById('P_P1_C1__DIV');
+
+ var expectedCalls = [
+ ['P', 'mouseenter'],
+ ['P_P1', 'mouseenter'],
+ ['P_P1_C1__DIV', 'mouseenter'],
+ ];
+
+ outerNode1.dispatchEvent(
+ new MouseEvent('mouseout', {
+ bubbles: true,
+ cancelable: true,
+ relatedTarget: enterNode,
+ }),
+ );
+
+ expect(mockFn.mock.calls).toEqual(expectedCalls);
+ });
+
+ it('should enter from the window to the shallowest', () => {
+ var enterNode = document.getElementById('P');
+
+ var expectedCalls = [['P', 'mouseenter']];
+
+ outerNode1.dispatchEvent(
+ new MouseEvent('mouseout', {
+ bubbles: true,
+ cancelable: true,
+ relatedTarget: enterNode,
+ }),
+ );
+
+ expect(mockFn.mock.calls).toEqual(expectedCalls);
+ });
+
+ it('should leave to the window', () => {
+ var leaveNode = document.getElementById('P_P1_C1__DIV');
+
+ var expectedCalls = [
+ ['P_P1_C1__DIV', 'mouseleave'],
+ ['P_P1', 'mouseleave'],
+ ['P', 'mouseleave'],
+ ];
+
+ leaveNode.dispatchEvent(
+ new MouseEvent('mouseout', {
+ bubbles: true,
+ cancelable: true,
+ relatedTarget: outerNode1,
+ }),
+ );
+
+ expect(mockFn.mock.calls).toEqual(expectedCalls);
+ });
+
+ it('should leave to the window from the shallowest', () => {
+ var leaveNode = document.getElementById('P');
+
+ var expectedCalls = [['P', 'mouseleave']];
+
+ leaveNode.dispatchEvent(
+ new MouseEvent('mouseout', {
+ bubbles: true,
+ cancelable: true,
+ relatedTarget: outerNode1,
+ }),
+ );
+
+ expect(mockFn.mock.calls).toEqual(expectedCalls);
+ });
+ });
+});