From e7884fce0b3c1f9e45ec3d8ad1b8476ca84f17a9 Mon Sep 17 00:00:00 2001 From: Nathan Hunzaker Date: Wed, 5 Sep 2018 13:08:03 -0700 Subject: [PATCH] Attach touch events locally, add tests to ensure bubble order --- .../src/__tests__/EventDispatchOrder-test.js | 124 ++++++++++++++++++ .../src/__tests__/ReactDOMComponent-test.js | 56 -------- .../src/events/ReactBrowserEventEmitter.js | 7 +- 3 files changed, 127 insertions(+), 60 deletions(-) create mode 100644 packages/react-dom/src/__tests__/EventDispatchOrder-test.js diff --git a/packages/react-dom/src/__tests__/EventDispatchOrder-test.js b/packages/react-dom/src/__tests__/EventDispatchOrder-test.js new file mode 100644 index 0000000000000..47680fe362cf6 --- /dev/null +++ b/packages/react-dom/src/__tests__/EventDispatchOrder-test.js @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +const React = require('react'); +const ReactDOM = require('react-dom'); + +describe('EventDispatchOrder', () => { + let container; + + // The order we receive here is not ideal since it is expected that the + // capture listener fire before all bubble listeners. Other React apps + // might depend on this. + // + // @see https://github.com/facebook/react/pull/12919#issuecomment-395224674 + const expectedOrder = [ + 'document capture', + 'inner capture', + 'inner bubble', + 'outer capture', + 'outer bubble', + 'document bubble', + ]; + + beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(() => { + container.remove(); + }); + + function produceDispatchOrder(event, reactEvent) { + const eventOrder = []; + const track = tag => () => eventOrder.push(tag); + const outerRef = React.createRef(); + const innerRef = React.createRef(); + + function OuterReactApp() { + return React.createElement('div', { + ref: outerRef, + [reactEvent]: track('outer bubble'), + [reactEvent + 'Capture']: track('outer capture'), + }); + } + + function InnerReactApp() { + return React.createElement('div', { + ref: innerRef, + [reactEvent]: track('inner bubble'), + [reactEvent + 'Capture']: track('inner capture'), + }); + } + + ReactDOM.unmountComponentAtNode(container); + ReactDOM.render(, container); + + ReactDOM.unmountComponentAtNode(outerRef.current); + ReactDOM.render(, outerRef.current); + + const trackDocBubble = track('document bubble'); + const trackDocCapture = track('document capture'); + + document.addEventListener(event, trackDocBubble); + document.addEventListener(event, trackDocCapture, true); + + innerRef.current.dispatchEvent(new Event(event, {bubbles: true})); + + document.removeEventListener(event, trackDocBubble); + document.removeEventListener(event, trackDocCapture, true); + + return eventOrder; + } + + it('dispatches standard events in the correct order', () => { + expect(produceDispatchOrder('click', 'onClick')).toEqual(expectedOrder); + }); + + // Scroll and Wheel are attached as captured events directly to the element + describe('scrolling events', () => { + it('dispatches scroll events in the correct order', () => { + expect(produceDispatchOrder('scroll', 'onScroll')).toEqual(expectedOrder); + }); + + it('dispatches scroll events in the correct order', () => { + expect(produceDispatchOrder('wheel', 'onWheel')).toEqual(expectedOrder); + }); + }); + + // Touch events are attached as bubbled events directly to the element + describe('touch events', () => { + it('dispatches touchstart in the correct order', () => { + expect(produceDispatchOrder('touchstart', 'onTouchStart')).toEqual( + expectedOrder, + ); + }); + + it('dispatches touchend in the correct order', () => { + expect(produceDispatchOrder('touchend', 'onTouchEnd')).toEqual( + expectedOrder, + ); + }); + + it('dispatches touchmove in the correct order', () => { + expect(produceDispatchOrder('touchmove', 'onTouchMove')).toEqual( + expectedOrder, + ); + }); + + it('dispatches touchmove in the correct order', () => { + expect(produceDispatchOrder('touchcancel', 'onTouchCancel')).toEqual( + expectedOrder, + ); + }); + }); +}); diff --git a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js index 4dc613805c94f..b83ce5d5e1f36 100644 --- a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js @@ -2603,60 +2603,4 @@ describe('ReactDOMComponent', () => { expect(node.getAttribute('onx')).toBe('bar'); }); }); - - it('receives events in specific order', () => { - let eventOrder = []; - let track = tag => () => eventOrder.push(tag); - let outerRef = React.createRef(); - let innerRef = React.createRef(); - - function OuterReactApp() { - return ( -
- ); - } - - function InnerReactApp() { - return ( -
- ); - } - - const container = document.createElement('div'); - document.body.appendChild(container); - - try { - ReactDOM.render(, container); - ReactDOM.render(, outerRef.current); - - document.addEventListener('click', track('document bubble')); - document.addEventListener('click', track('document capture'), true); - - innerRef.current.click(); - - // The order we receive here is not ideal since it is expected that the - // capture listener fire before all bubble listeners. Other React apps - // might depend on this. - // - // @see https://github.com/facebook/react/pull/12919#issuecomment-395224674 - expect(eventOrder).toEqual([ - 'document capture', - 'inner capture', - 'inner bubble', - 'outer capture', - 'outer bubble', - 'document bubble', - ]); - } finally { - document.body.removeChild(container); - } - }); }); diff --git a/packages/react-dom/src/events/ReactBrowserEventEmitter.js b/packages/react-dom/src/events/ReactBrowserEventEmitter.js index 2e000b938a20a..eeb280549e560 100644 --- a/packages/react-dom/src/events/ReactBrowserEventEmitter.js +++ b/packages/react-dom/src/events/ReactBrowserEventEmitter.js @@ -135,6 +135,7 @@ export function listenTo( ) { const mountAtListeners = getListenerTrackingFor(mountAt); const dependencies = registrationNameDependencies[registrationName]; + let elementListeners; for (let i = 0; i < dependencies.length; i++) { const dependency = dependencies[i]; @@ -142,8 +143,7 @@ export function listenTo( switch (dependency) { case TOP_SCROLL: case TOP_WHEEL: - const elementListeners = getListenerTrackingFor(element); - + elementListeners = getListenerTrackingFor(element); if (!elementListeners.hasOwnProperty(dependency)) { trapCapturedEvent(dependency, element); elementListeners[dependency] = true; @@ -153,8 +153,7 @@ export function listenTo( case TOP_TOUCH_END: case TOP_TOUCH_MOVE: case TOP_TOUCH_CANCEL: - const elementListeners = getListenerTrackingFor(element); - + elementListeners = getListenerTrackingFor(element); if (!elementListeners.hasOwnProperty(dependency)) { trapBubbledEvent(dependency, element); elementListeners[dependency] = true;