@samitouri / QOS-React-2 / commits / a146c1f9ed

[Flare] Refactor of Press to fix various issues (#15878)

Dominic Gannaway committed Jun 14, 2019 at 22:52 UTC a146c1f9ed2f13bb885a5f49623d45f03c0bb424
9 files changed +649 -310
packages/react-dom/src/events/DOMEventResponderSystem.js
+19 -22
@@ -394,26 +394,6 @@ const eventResponderContext: ReactResponderContext = {
394 },
395 getActiveDocument,
396 objectAssign: Object.assign,
397 - getEventPointerType(
398 - event: ReactResponderEvent,
399 - ): '' | 'mouse' | 'keyboard' | 'pen' | 'touch' {
400 - validateResponderContext();
401 - const nativeEvent: any = event.nativeEvent;
402 - const {type, pointerType} = nativeEvent;
403 - if (pointerType != null) {
404 - return pointerType;
405 - }
406 - if (type.indexOf('mouse') === 0) {
407 - return 'mouse';
408 - }
409 - if (type.indexOf('touch') === 0) {
410 - return 'touch';
411 - }
412 - if (type.indexOf('key') === 0) {
413 - return 'keyboard';
414 - }
415 - return '';
416 - },
397 getEventCurrentTarget(event: ReactResponderEvent): Element {
398 validateResponderContext();
399 const target = event.target;
@@ -583,12 +563,29 @@ function createResponderEvent(
563 passive: boolean,
564 passiveSupported: boolean,
565 ): ReactResponderEvent {
566 + const {pointerType} = (nativeEvent: any);
567 + let eventPointerType = '';
568 + let pointerId = null;
569 +
570 + if (pointerType !== undefined) {
571 + eventPointerType = pointerType;
572 + pointerId = (nativeEvent: any).pointerId;
573 + } else if (nativeEvent.key !== undefined) {
574 + eventPointerType = 'keyboard';
575 + } else if (nativeEvent.button !== undefined) {
576 + eventPointerType = 'mouse';
577 + } else if ((nativeEvent: any).changedTouches !== undefined) {
578 + eventPointerType = 'touch';
579 + }
580 +
581 const responderEvent = {
582 nativeEvent: nativeEvent,
588 - target: nativeEventTarget,
589 - type: topLevelType,
583 passive,
584 passiveSupported,
585 + pointerId,
586 + pointerType: eventPointerType,
587 + target: nativeEventTarget,
588 + type: topLevelType,
589 };
590 if (__DEV__) {
591 Object.freeze(responderEvent);
packages/react-events/src/Focus.js
+1 -1
@@ -8,6 +8,7 @@
8 */
9
10 import type {
11 + PointerType,
12 ReactResponderEvent,
13 ReactResponderContext,
14 } from 'shared/ReactTypes';
@@ -30,7 +31,6 @@ type FocusState = {
31 pointerType: PointerType,
32 };
33
33 -type PointerType = '' | 'mouse' | 'keyboard' | 'pen' | 'touch';
34 type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange';
35
36 type FocusEvent = {|
packages/react-events/src/Hover.js
+1 -2
@@ -294,7 +294,7 @@ const HoverResponder = {
294 props: HoverProps,
295 state: HoverState,
296 ): void {
297 - const {type} = event;
297 + const {pointerType, type} = event;
298
299 if (props.disabled) {
300 if (state.isHovered) {
@@ -306,7 +306,6 @@ const HoverResponder = {
306 }
307 return;
308 }
309 - const pointerType = context.getEventPointerType(event);
309
310 switch (type) {
311 // START
packages/react-events/src/Press.js
+174 -131
@@ -8,6 +8,7 @@
8 */
9
10 import type {
11 + PointerType,
12 ReactResponderEvent,
13 ReactResponderContext,
14 } from 'shared/ReactTypes';
@@ -40,8 +41,6 @@ type PressProps = {
41 stopPropagation: boolean,
42 };
43
43 -type PointerType = '' | 'mouse' | 'keyboard' | 'pen' | 'touch';
44 -
44 type PressState = {
45 activationPosition: null | $ReadOnly<{|
46 x: number,
@@ -71,7 +70,7 @@ type PressState = {
70 top: number,
71 |}>,
72 ignoreEmulatedMouseEvents: boolean,
74 - allowPressReentry: boolean,
73 + activePointerId: null | number,
74 };
75
76 type PressEventType =
@@ -176,7 +175,7 @@ function createPressEvent(
175 let eventObject;
176 if (nativeEvent.clientX !== undefined) {
177 eventObject = (nativeEvent: any);
179 - } else if (isTouchEvent(nativeEvent)) {
178 + } else if (isNativeTouchEvent(nativeEvent)) {
179 eventObject = getTouchFromPressEvent(nativeEvent);
180 }
181 if (eventObject) {
@@ -482,7 +481,7 @@ function calculateResponderRegion(
481 };
482 }
483
485 -function isTouchEvent(nativeEvent: Event): boolean {
484 +function isNativeTouchEvent(nativeEvent: Event): boolean {
485 const changedTouches = ((nativeEvent: any): TouchEvent).changedTouches;
486 return changedTouches && typeof changedTouches.length === 'number';
487 }
@@ -500,7 +499,7 @@ function getEventViewportCoords(
499 nativeEvent: Event,
500 ): {x: null | number, y: null | number} {
501 let eventObject = (nativeEvent: any);
503 - if (isTouchEvent(eventObject)) {
502 + if (isNativeTouchEvent(eventObject)) {
503 eventObject = getTouchFromPressEvent(eventObject);
504 }
505 const x = eventObject.clientX;
@@ -511,39 +510,6 @@ function getEventViewportCoords(
510 };
511 }
512
514 -function isPressWithinResponderRegion(
515 - nativeEvent: $PropertyType<ReactResponderEvent, 'nativeEvent'>,
516 - state: PressState,
517 -): boolean {
518 - const {responderRegionOnActivation, responderRegionOnDeactivation} = state;
519 - let left, top, right, bottom;
520 -
521 - if (responderRegionOnActivation != null) {
522 - left = responderRegionOnActivation.left;
523 - top = responderRegionOnActivation.top;
524 - right = responderRegionOnActivation.right;
525 - bottom = responderRegionOnActivation.bottom;
526 -
527 - if (responderRegionOnDeactivation != null) {
528 - left = Math.min(left, responderRegionOnDeactivation.left);
529 - top = Math.min(top, responderRegionOnDeactivation.top);
530 - right = Math.max(right, responderRegionOnDeactivation.right);
531 - bottom = Math.max(bottom, responderRegionOnDeactivation.bottom);
532 - }
533 - }
534 - const {x, y} = getEventViewportCoords(((nativeEvent: any): Event));
535 -
536 - return (
537 - left != null &&
538 - right != null &&
539 - top != null &&
540 - bottom != null &&
541 - x !== null &&
542 - y !== null &&
543 - (x >= left && x <= right && y >= top && y <= bottom)
544 - );
545 -}
546 -
513 function unmountResponder(
514 context: ReactResponderContext,
515 props: PressProps,
@@ -571,11 +537,78 @@ function removeRootEventTypes(
537 ): void {
538 if (state.addedRootEvents) {
539 state.addedRootEvents = false;
574 - state.allowPressReentry = false;
540 context.removeRootEventTypes(rootEventTypes);
541 }
542 }
543
544 +function getTouchById(
545 + nativeEvent: TouchEvent,
546 + pointerId: null | number,
547 +): null | Touch {
548 + const changedTouches = nativeEvent.changedTouches;
549 + for (let i = 0; i < changedTouches.length; i++) {
550 + const touch = changedTouches[i];
551 + if (touch.identifier === pointerId) {
552 + return touch;
553 + }
554 + }
555 + return null;
556 +}
557 +
558 +function getTouchTarget(context: ReactResponderContext, touchEvent: Touch) {
559 + const doc = context.getActiveDocument();
560 + return doc.elementFromPoint(touchEvent.clientX, touchEvent.clientY);
561 +}
562 +
563 +function updateIsPressWithinResponderRegion(
564 + target: Element | Document,
565 + nativeEventOrTouchEvent: Event | Touch,
566 + context: ReactResponderContext,
567 + props: PressProps,
568 + state: PressState,
569 +): void {
570 + if (
571 + state.pressTarget != null &&
572 + !context.isTargetWithinElement(target, state.pressTarget)
573 + ) {
574 + // Calculate the responder region we use for deactivation if not
575 + // already done during move event.
576 + if (state.responderRegionOnDeactivation == null) {
577 + state.responderRegionOnDeactivation = calculateResponderRegion(
578 + context,
579 + state.pressTarget,
580 + props,
581 + );
582 + }
583 + const {responderRegionOnActivation, responderRegionOnDeactivation} = state;
584 + let left, top, right, bottom;
585 +
586 + if (responderRegionOnActivation != null) {
587 + left = responderRegionOnActivation.left;
588 + top = responderRegionOnActivation.top;
589 + right = responderRegionOnActivation.right;
590 + bottom = responderRegionOnActivation.bottom;
591 +
592 + if (responderRegionOnDeactivation != null) {
593 + left = Math.min(left, responderRegionOnDeactivation.left);
594 + top = Math.min(top, responderRegionOnDeactivation.top);
595 + right = Math.max(right, responderRegionOnDeactivation.right);
596 + bottom = Math.max(bottom, responderRegionOnDeactivation.bottom);
597 + }
598 + }
599 + const {clientX: x, clientY: y} = (nativeEventOrTouchEvent: any);
600 +
601 + state.isPressWithinResponderRegion =
602 + left != null &&
603 + right != null &&
604 + top != null &&
605 + bottom != null &&
606 + x !== null &&
607 + y !== null &&
608 + (x >= left && x <= right && y >= top && y <= bottom);
609 + }
610 +}
611 +
612 const PressResponder = {
613 targetEventTypes,
614 createInitialState(): PressState {
@@ -596,7 +629,7 @@ const PressResponder = {
629 responderRegionOnActivation: null,
630 responderRegionOnDeactivation: null,
631 ignoreEmulatedMouseEvents: false,
599 - allowPressReentry: false,
632 + activePointerId: null,
633 };
634 },
635 allowMultipleHostChildren: false,
@@ -607,7 +640,7 @@ const PressResponder = {
640 props: PressProps,
641 state: PressState,
642 ): void {
610 - const {type} = event;
643 + const {pointerId, pointerType, type} = event;
644
645 if (props.disabled) {
646 removeRootEventTypes(context, state);
@@ -616,7 +649,7 @@ const PressResponder = {
649 return;
650 }
651 const nativeEvent: any = event.nativeEvent;
619 - const pointerType = context.getEventPointerType(event);
652 + const isPressed = state.isPressed;
653
654 if (props.stopPropagation === true) {
655 nativeEvent.stopPropagation();
@@ -627,26 +660,27 @@ const PressResponder = {
660 case 'keydown':
661 case 'mousedown':
662 case 'touchstart': {
630 - if (!state.isPressed) {
631 - if (type === 'pointerdown' || type === 'touchstart') {
663 + if (!isPressed) {
664 + const isTouchEvent = type === 'touchstart';
665 + const isPointerEvent = type === 'pointerdown';
666 + const isKeyboardEvent = pointerType === 'keyboard';
667 + const isMouseEvent = pointerType === 'mouse';
668 + const isPenEvent = pointerType === 'pen';
669 +
670 + if (isPointerEvent || isTouchEvent) {
671 state.ignoreEmulatedMouseEvents = true;
633 - }
634 -
635 - // Ignore unrelated key events
636 - if (pointerType === 'keyboard') {
672 + } else if (type === 'mousedown' && state.ignoreEmulatedMouseEvents) {
673 + // Ignore emulated mouse events
674 + return;
675 + } else if (isKeyboardEvent) {
676 + // Ignore unrelated key events
677 if (!isValidKeyboardEvent(nativeEvent)) {
678 return;
679 }
680 }
641 -
642 - // Ignore emulated mouse events
643 - if (type === 'mousedown' && state.ignoreEmulatedMouseEvents) {
644 - return;
645 - }
681 // Ignore mouse/pen pressing on touch hit target area
647 - const isMouseType = pointerType === 'mouse';
682 if (
649 - (isMouseType || pointerType === 'pen') &&
683 + (isMouseEvent || isPenEvent) &&
684 context.isEventWithinTouchHitTarget(event)
685 ) {
686 // We need to prevent the native event to block the focus
@@ -659,18 +693,23 @@ const PressResponder = {
693 // data around for handling of the context menu
694 state.pointerType = pointerType;
695 state.pressTarget = context.getEventCurrentTarget(event);
696 + if (isPointerEvent) {
697 + state.activePointerId = pointerId;
698 + } else if (isTouchEvent) {
699 + const touchEvent = getTouchFromPressEvent(nativeEvent);
700 + state.activePointerId = touchEvent.identifier;
701 + }
702
703 // Ignore any device buttons except left-mouse and touch/pen contact.
704 // Additionally we ignore left-mouse + ctrl-key with Macs as that
705 // acts like right-click and opens the contextmenu.
706 if (
707 nativeEvent.button > 0 ||
668 - (isMac && isMouseType && nativeEvent.ctrlKey)
708 + (isMac && isMouseEvent && nativeEvent.ctrlKey)
709 ) {
710 return;
711 }
712
673 - state.allowPressReentry = true;
713 state.responderRegionOnActivation = calculateResponderRegion(
714 context,
715 state.pressTarget,
@@ -689,13 +728,13 @@ const PressResponder = {
728 }
729
730 case 'contextmenu': {
692 - if (state.isPressed) {
693 - dispatchCancel(event, context, props, state);
731 + if (isPressed) {
732 if (props.preventDefault !== false) {
733 // Skip dispatching of onContextMenu below
734 nativeEvent.preventDefault();
735 return;
736 }
737 + dispatchCancel(event, context, props, state);
738 }
739 if (props.onContextMenu) {
740 dispatchEvent(
@@ -719,10 +758,11 @@ const PressResponder = {
758 props: PressProps,
759 state: PressState,
760 ): void {
722 - const {target, type} = event;
761 + let {pointerId, pointerType, target, type} = event;
762
763 const nativeEvent: any = event.nativeEvent;
725 - const pointerType = context.getEventPointerType(event);
764 + const isPressed = state.isPressed;
765 + const activePointerId = state.activePointerId;
766
767 if (props.stopPropagation === true) {
768 nativeEvent.stopPropagation();
@@ -732,61 +772,62 @@ const PressResponder = {
772 case 'pointermove':
773 case 'mousemove':
774 case 'touchmove': {
735 - if (state.isPressed || state.allowPressReentry) {
736 - // Ignore emulated events (pointermove will dispatch touch and mouse events)
737 - // Ignore pointermove events during a keyboard press.
738 - if (state.pointerType !== pointerType) {
775 + let touchEvent;
776 + // Ignore emulated events (pointermove will dispatch touch and mouse events)
777 + // Ignore pointermove events during a keyboard press.
778 + if (state.pointerType !== pointerType) {
779 + return;
780 + }
781 + if (type === 'pointermove' && activePointerId !== pointerId) {
782 + return;
783 + } else if (type === 'touchmove') {
784 + touchEvent = getTouchById(nativeEvent, activePointerId);
785 + if (touchEvent === null) {
786 return;
787 }
788 + target = getTouchTarget(context, touchEvent);
789 + }
790
742 - // Calculate the responder region we use for deactivation, as the
743 - // element dimensions may have changed since activation.
744 - if (
745 - state.pressTarget !== null &&
746 - state.responderRegionOnDeactivation == null
747 - ) {
748 - state.responderRegionOnDeactivation = calculateResponderRegion(
749 - context,
750 - state.pressTarget,
751 - props,
752 - );
753 - }
754 - state.isPressWithinResponderRegion = isPressWithinResponderRegion(
755 - nativeEvent,
756 - state,
757 - );
758 -
759 - if (state.isPressWithinResponderRegion) {
760 - if (state.isPressed) {
761 - if (props.onPressMove) {
762 - dispatchEvent(
763 - event,
764 - context,
765 - state,
766 - 'pressmove',
767 - props.onPressMove,
768 - UserBlockingEvent,
769 - );
770 - }
791 + // Calculate the responder region we use for deactivation, as the
792 + // element dimensions may have changed since activation.
793 + updateIsPressWithinResponderRegion(
794 + target,
795 + touchEvent || nativeEvent,
796 + context,
797 + props,
798 + state,
799 + );
800 +
801 + if (state.isPressWithinResponderRegion) {
802 + if (isPressed) {
803 + if (props.onPressMove) {
804 + dispatchEvent(
805 + event,
806 + context,
807 + state,
808 + 'pressmove',
809 + props.onPressMove,
810 + UserBlockingEvent,
811 + );
812 + }
813 + if (
814 + state.activationPosition != null &&
815 + state.longPressTimeout != null
816 + ) {
817 + const deltaX = state.activationPosition.x - nativeEvent.clientX;
818 + const deltaY = state.activationPosition.y - nativeEvent.clientY;
819 if (
772 - state.activationPosition != null &&
820 + Math.hypot(deltaX, deltaY) > 10 &&
821 state.longPressTimeout != null
822 ) {
775 - const deltaX = state.activationPosition.x - nativeEvent.clientX;
776 - const deltaY = state.activationPosition.y - nativeEvent.clientY;
777 - if (
778 - Math.hypot(deltaX, deltaY) > 10 &&
779 - state.longPressTimeout != null
780 - ) {
781 - context.clearTimeout(state.longPressTimeout);
782 - }
823 + context.clearTimeout(state.longPressTimeout);
824 }
784 - } else {
785 - dispatchPressStartEvents(event, context, props, state);
825 }
826 } else {
788 - dispatchPressEndEvents(event, context, props, state);
827 + dispatchPressStartEvents(event, context, props, state);
828 }
829 + } else {
830 + dispatchPressEndEvents(event, context, props, state);
831 }
832 break;
833 }
@@ -796,39 +837,41 @@ const PressResponder = {
837 case 'keyup':
838 case 'mouseup':
839 case 'touchend': {
799 - if (state.isPressed) {
800 - // Ignore unrelated keyboard events and verify press is within
801 - // responder region for non-keyboard events.
802 - if (pointerType === 'keyboard') {
803 - if (!isValidKeyboardEvent(nativeEvent)) {
840 + if (isPressed) {
841 + let isKeyboardEvent = false;
842 + let touchEvent;
843 + if (type === 'pointerup' && activePointerId !== pointerId) {
844 + return;
845 + } else if (type === 'touchend') {
846 + touchEvent = getTouchById(nativeEvent, activePointerId);
847 + if (touchEvent === null) {
848 return;
849 }
806 - // If the event target isn't within the press target, check if we're still
807 - // within the responder region. The region may have changed if the
808 - // element's layout was modified after activation.
809 - } else if (
810 - state.pressTarget != null &&
811 - !context.isTargetWithinElement(target, state.pressTarget)
812 - ) {
813 - // Calculate the responder region we use for deactivation if not
814 - // already done during move event.
815 - if (state.responderRegionOnDeactivation == null) {
816 - state.responderRegionOnDeactivation = calculateResponderRegion(
817 - context,
818 - state.pressTarget,
819 - props,
820 - );
850 + target = getTouchTarget(context, touchEvent);
851 + } else if (type === 'keyup') {
852 + // Ignore unrelated keyboard events
853 + if (!isValidKeyboardEvent(nativeEvent)) {
854 + return;
855 }
822 - state.isPressWithinResponderRegion = isPressWithinResponderRegion(
823 - nativeEvent,
824 - state,
825 - );
856 + isKeyboardEvent = true;
857 }
858
859 const wasLongPressed = state.isLongPressed;
860 dispatchPressEndEvents(event, context, props, state);
861
862 if (state.pressTarget !== null && props.onPress) {
863 + if (!isKeyboardEvent) {
864 + // If the event target isn't within the press target, check if we're still
865 + // within the responder region. The region may have changed if the
866 + // element's layout was modified after activation.
867 + updateIsPressWithinResponderRegion(
868 + target,
869 + touchEvent || nativeEvent,
870 + context,
871 + props,
872 + state,
873 + );
874 + }
875 if (state.isPressWithinResponderRegion) {
876 if (
877 !(
packages/react-events/src/Scroll.js
+3 -6
@@ -8,6 +8,7 @@
8 */
9
10 import type {
11 + PointerType,
12 ReactResponderEvent,
13 ReactResponderContext,
14 } from 'shared/ReactTypes';
@@ -38,8 +39,6 @@ type ScrollEventType =
39 | 'scrollmomentumstart'
40 | 'scrollmomentumend';
41
41 -type PointerType = '' | 'mouse' | 'keyboard' | 'pen' | 'touch';
42 -
42 type ScrollDirection = '' | 'up' | 'down' | 'left' | 'right';
43
44 type ScrollEvent = {|
@@ -134,7 +133,7 @@ const ScrollResponder = {
133 props: ScrollProps,
134 state: ScrollState,
135 ): void {
137 - const {target, type} = event;
136 + const {pointerType, target, type} = event;
137
138 if (props.disabled) {
139 if (state.isPointerDown) {
@@ -144,7 +143,6 @@ const ScrollResponder = {
143 }
144 return;
145 }
147 - const pointerType = context.getEventPointerType(event);
146
147 switch (type) {
148 case 'scroll': {
@@ -181,8 +179,7 @@ const ScrollResponder = {
179 props: ScrollProps,
180 state: ScrollState,
181 ) {
184 - const {type} = event;
185 - const pointerType = context.getEventPointerType(event);
182 + const {pointerType, type} = event;
183
184 switch (type) {
185 case 'pointercancel':
packages/react-events/src/__tests__/Hover-test.internal.js
+110 -70
@@ -16,7 +16,7 @@ let TestUtils;
16 let Scheduler;
17 let Hover;
18
19 -const createPointerEvent = (type, data) => {
19 +const createEvent = (type, data) => {
20 const event = document.createEvent('CustomEvent');
21 event.initCustomEvent(type, true, true);
22 if (data != null) {
@@ -27,6 +27,17 @@ const createPointerEvent = (type, data) => {
27 return event;
28 };
29
30 +function createTouchEvent(type, id, data) {
31 + return createEvent(type, {
32 + changedTouches: [
33 + {
34 + ...data,
35 + identifier: id,
36 + },
37 + ],
38 + });
39 +}
40 +
41 describe('Hover event responder', () => {
42 let container;
43
@@ -67,8 +78,8 @@ describe('Hover event responder', () => {
78 });
79
80 it('prevents custom events being dispatched', () => {
70 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
71 - ref.current.dispatchEvent(createPointerEvent('pointerout'));
81 + ref.current.dispatchEvent(createEvent('pointerover'));
82 + ref.current.dispatchEvent(createEvent('pointerout'));
83 expect(onHoverStart).not.toBeCalled();
84 expect(onHoverEnd).not.toBeCalled();
85 });
@@ -89,44 +100,65 @@ describe('Hover event responder', () => {
100 });
101
102 it('is called after "pointerover" event', () => {
92 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
103 + ref.current.dispatchEvent(createEvent('pointerover'));
104 expect(onHoverStart).toHaveBeenCalledTimes(1);
105 });
106
107 it('is not called if "pointerover" pointerType is touch', () => {
97 - const event = createPointerEvent('pointerover', {pointerType: 'touch'});
108 + const event = createEvent('pointerover', {pointerType: 'touch'});
109 ref.current.dispatchEvent(event);
110 expect(onHoverStart).not.toBeCalled();
111 });
112
113 it('is called if valid "pointerover" follows touch', () => {
114 ref.current.dispatchEvent(
104 - createPointerEvent('pointerover', {pointerType: 'touch'}),
115 + createEvent('pointerover', {pointerType: 'touch'}),
116 );
117 ref.current.dispatchEvent(
107 - createPointerEvent('pointerout', {pointerType: 'touch'}),
118 + createEvent('pointerout', {pointerType: 'touch'}),
119 );
120 ref.current.dispatchEvent(
110 - createPointerEvent('pointerover', {pointerType: 'mouse'}),
121 + createEvent('pointerover', {pointerType: 'mouse'}),
122 );
123 expect(onHoverStart).toHaveBeenCalledTimes(1);
124 });
125
126 it('ignores browser emulated "mouseover" event', () => {
116 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
117 - ref.current.dispatchEvent(createPointerEvent('mouseover'));
127 + ref.current.dispatchEvent(createEvent('pointerover'));
128 + ref.current.dispatchEvent(
129 + createEvent('mouseover', {
130 + button: 0,
131 + }),
132 + );
133 expect(onHoverStart).toHaveBeenCalledTimes(1);
134 });
135
136 // No PointerEvent fallbacks
137 it('is called after "mouseover" event', () => {
123 - ref.current.dispatchEvent(createPointerEvent('mouseover'));
138 + ref.current.dispatchEvent(
139 + createEvent('mouseover', {
140 + button: 0,
141 + }),
142 + );
143 expect(onHoverStart).toHaveBeenCalledTimes(1);
144 });
145 +
146 it('is not called after "touchstart"', () => {
127 - ref.current.dispatchEvent(createPointerEvent('touchstart'));
128 - ref.current.dispatchEvent(createPointerEvent('touchend'));
129 - ref.current.dispatchEvent(createPointerEvent('mouseover'));
147 + ref.current.dispatchEvent(
148 + createTouchEvent('touchstart', 0, {
149 + target: ref.current,
150 + }),
151 + );
152 + ref.current.dispatchEvent(
153 + createTouchEvent('touchend', 0, {
154 + target: ref.current,
155 + }),
156 + );
157 + ref.current.dispatchEvent(
158 + createEvent('mouseover', {
159 + button: 0,
160 + }),
161 + );
162 expect(onHoverStart).not.toBeCalled();
163 });
164
@@ -139,7 +171,7 @@ describe('Hover event responder', () => {
171 );
172 ReactDOM.render(element, container);
173
142 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
174 + ref.current.dispatchEvent(createEvent('pointerover'));
175 jest.advanceTimersByTime(1999);
176 expect(onHoverStart).not.toBeCalled();
177 jest.advanceTimersByTime(1);
@@ -154,12 +186,12 @@ describe('Hover event responder', () => {
186 );
187 ReactDOM.render(element, container);
188
157 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
189 + ref.current.dispatchEvent(createEvent('pointerover'));
190 jest.advanceTimersByTime(499);
159 - ref.current.dispatchEvent(createPointerEvent('pointerout'));
191 + ref.current.dispatchEvent(createEvent('pointerout'));
192 jest.advanceTimersByTime(1);
193 expect(onHoverStart).not.toBeCalled();
162 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
194 + ref.current.dispatchEvent(createEvent('pointerover'));
195 jest.runAllTimers();
196 expect(onHoverStart).toHaveBeenCalledTimes(1);
197 });
@@ -172,7 +204,7 @@ describe('Hover event responder', () => {
204 );
205 ReactDOM.render(element, container);
206
175 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
207 + ref.current.dispatchEvent(createEvent('pointerover'));
208 expect(onHoverStart).toHaveBeenCalledTimes(1);
209 });
210
@@ -187,12 +219,12 @@ describe('Hover event responder', () => {
219 );
220 ReactDOM.render(element, container);
221
190 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
222 + ref.current.dispatchEvent(createEvent('pointerover'));
223 jest.advanceTimersByTime(500);
224 expect(onHoverStart).toHaveBeenCalledTimes(1);
193 - ref.current.dispatchEvent(createPointerEvent('pointerout'));
225 + ref.current.dispatchEvent(createEvent('pointerout'));
226 jest.advanceTimersByTime(10);
195 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
227 + ref.current.dispatchEvent(createEvent('pointerover'));
228 jest.runAllTimers();
229 expect(onHoverStart).toHaveBeenCalledTimes(1);
230 });
@@ -214,20 +246,20 @@ describe('Hover event responder', () => {
246 });
247
248 it('is called after "pointerover" and "pointerout" events', () => {
217 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
249 + ref.current.dispatchEvent(createEvent('pointerover'));
250 expect(onHoverChange).toHaveBeenCalledTimes(1);
251 expect(onHoverChange).toHaveBeenCalledWith(true);
220 - ref.current.dispatchEvent(createPointerEvent('pointerout'));
252 + ref.current.dispatchEvent(createEvent('pointerout'));
253 expect(onHoverChange).toHaveBeenCalledTimes(2);
254 expect(onHoverChange).toHaveBeenCalledWith(false);
255 });
256
257 // No PointerEvent fallbacks
258 it('is called after "mouseover" and "mouseout" events', () => {
227 - ref.current.dispatchEvent(createPointerEvent('mouseover'));
259 + ref.current.dispatchEvent(createEvent('mouseover'));
260 expect(onHoverChange).toHaveBeenCalledTimes(1);
261 expect(onHoverChange).toHaveBeenCalledWith(true);
230 - ref.current.dispatchEvent(createPointerEvent('mouseout'));
262 + ref.current.dispatchEvent(createEvent('mouseout'));
263 expect(onHoverChange).toHaveBeenCalledTimes(2);
264 expect(onHoverChange).toHaveBeenCalledWith(false);
265 });
@@ -266,7 +298,7 @@ describe('Hover event responder', () => {
298 expect(newContainer.textContent).toEqual('not hovered');
299
300 await act(async () => {
269 - target.current.dispatchEvent(createPointerEvent('mouseover'));
301 + target.current.dispatchEvent(createEvent('mouseover'));
302
303 // 3s should be enough to expire the updates
304 Scheduler.advanceTime(3000);
@@ -290,49 +322,57 @@ describe('Hover event responder', () => {
322 });
323
324 it('is called after "pointerout" event', () => {
293 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
294 - ref.current.dispatchEvent(createPointerEvent('pointerout'));
325 + ref.current.dispatchEvent(createEvent('pointerover'));
326 + ref.current.dispatchEvent(createEvent('pointerout'));
327 expect(onHoverEnd).toHaveBeenCalledTimes(1);
328 });
329
330 it('is not called if "pointerover" pointerType is touch', () => {
299 - const event = createPointerEvent('pointerover');
331 + const event = createEvent('pointerover');
332 event.pointerType = 'touch';
333 ref.current.dispatchEvent(event);
302 - ref.current.dispatchEvent(createPointerEvent('pointerout'));
334 + ref.current.dispatchEvent(createEvent('pointerout'));
335 expect(onHoverEnd).not.toBeCalled();
336 });
337
338 it('ignores browser emulated "mouseout" event', () => {
307 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
308 - ref.current.dispatchEvent(createPointerEvent('pointerout'));
309 - ref.current.dispatchEvent(createPointerEvent('mouseout'));
339 + ref.current.dispatchEvent(createEvent('pointerover'));
340 + ref.current.dispatchEvent(createEvent('pointerout'));
341 + ref.current.dispatchEvent(createEvent('mouseout'));
342 expect(onHoverEnd).toHaveBeenCalledTimes(1);
343 });
344
345 it('is called after "pointercancel" event', () => {
314 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
315 - ref.current.dispatchEvent(createPointerEvent('pointercancel'));
346 + ref.current.dispatchEvent(createEvent('pointerover'));
347 + ref.current.dispatchEvent(createEvent('pointercancel'));
348 expect(onHoverEnd).toHaveBeenCalledTimes(1);
349 });
350
351 it('is not called again after "pointercancel" event if it follows "pointerout"', () => {
320 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
321 - ref.current.dispatchEvent(createPointerEvent('pointerout'));
322 - ref.current.dispatchEvent(createPointerEvent('pointercancel'));
352 + ref.current.dispatchEvent(createEvent('pointerover'));
353 + ref.current.dispatchEvent(createEvent('pointerout'));
354 + ref.current.dispatchEvent(createEvent('pointercancel'));
355 expect(onHoverEnd).toHaveBeenCalledTimes(1);
356 });
357
358 // No PointerEvent fallbacks
359 it('is called after "mouseout" event', () => {
328 - ref.current.dispatchEvent(createPointerEvent('mouseover'));
329 - ref.current.dispatchEvent(createPointerEvent('mouseout'));
360 + ref.current.dispatchEvent(createEvent('mouseover'));
361 + ref.current.dispatchEvent(createEvent('mouseout'));
362 expect(onHoverEnd).toHaveBeenCalledTimes(1);
363 });
364 it('is not called after "touchend"', () => {
333 - ref.current.dispatchEvent(createPointerEvent('touchstart'));
334 - ref.current.dispatchEvent(createPointerEvent('touchend'));
335 - ref.current.dispatchEvent(createPointerEvent('mouseout'));
365 + ref.current.dispatchEvent(
366 + createTouchEvent('touchstart', 0, {
367 + target: ref.current,
368 + }),
369 + );
370 + ref.current.dispatchEvent(
371 + createTouchEvent('touchend', 0, {
372 + target: ref.current,
373 + }),
374 + );
375 + ref.current.dispatchEvent(createEvent('mouseout'));
376 expect(onHoverEnd).not.toBeCalled();
377 });
378
@@ -345,8 +385,8 @@ describe('Hover event responder', () => {
385 );
386 ReactDOM.render(element, container);
387
348 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
349 - ref.current.dispatchEvent(createPointerEvent('pointerout'));
388 + ref.current.dispatchEvent(createEvent('pointerover'));
389 + ref.current.dispatchEvent(createEvent('pointerout'));
390 jest.advanceTimersByTime(1999);
391 expect(onHoverEnd).not.toBeCalled();
392 jest.advanceTimersByTime(1);
@@ -361,8 +401,8 @@ describe('Hover event responder', () => {
401 );
402 ReactDOM.render(element, container);
403
364 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
365 - ref.current.dispatchEvent(createPointerEvent('pointerout'));
404 + ref.current.dispatchEvent(createEvent('pointerover'));
405 + ref.current.dispatchEvent(createEvent('pointerout'));
406 expect(onHoverEnd).toHaveBeenCalledTimes(1);
407 });
408
@@ -374,12 +414,12 @@ describe('Hover event responder', () => {
414 );
415 ReactDOM.render(element, container);
416
377 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
378 - ref.current.dispatchEvent(createPointerEvent('pointerout'));
417 + ref.current.dispatchEvent(createEvent('pointerover'));
418 + ref.current.dispatchEvent(createEvent('pointerout'));
419 jest.advanceTimersByTime(499);
380 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
420 + ref.current.dispatchEvent(createEvent('pointerover'));
421 jest.advanceTimersByTime(100);
382 - ref.current.dispatchEvent(createPointerEvent('pointerout'));
422 + ref.current.dispatchEvent(createEvent('pointerout'));
423 jest.runAllTimers();
424 expect(onHoverEnd).toHaveBeenCalledTimes(1);
425 });
@@ -392,10 +432,10 @@ describe('Hover event responder', () => {
432 );
433 ReactDOM.render(element, container);
434
395 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
396 - ref.current.dispatchEvent(createPointerEvent('pointerout'));
435 + ref.current.dispatchEvent(createEvent('pointerover'));
436 + ref.current.dispatchEvent(createEvent('pointerout'));
437 jest.advanceTimersByTime(499);
398 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
438 + ref.current.dispatchEvent(createEvent('pointerover'));
439 jest.advanceTimersByTime(1);
440 expect(onHoverEnd).not.toBeCalled();
441 });
@@ -411,8 +451,8 @@ describe('Hover event responder', () => {
451 );
452 ReactDOM.render(element, container);
453
414 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
415 - ref.current.dispatchEvent(createPointerEvent('pointerout'));
454 + ref.current.dispatchEvent(createEvent('pointerover'));
455 + ref.current.dispatchEvent(createEvent('pointerout'));
456 jest.runAllTimers();
457 expect(onHoverEnd).not.toBeCalled();
458 });
@@ -436,12 +476,12 @@ describe('Hover event responder', () => {
476 bottom: 500,
477 right: 500,
478 });
439 - ref.current.dispatchEvent(createPointerEvent('pointerover'));
479 + ref.current.dispatchEvent(createEvent('pointerover'));
480 ref.current.dispatchEvent(
441 - createPointerEvent('pointermove', {pointerType: 'mouse'}),
481 + createEvent('pointermove', {pointerType: 'mouse'}),
482 );
443 - ref.current.dispatchEvent(createPointerEvent('touchmove'));
444 - ref.current.dispatchEvent(createPointerEvent('mousemove'));
483 + ref.current.dispatchEvent(createEvent('touchmove'));
484 + ref.current.dispatchEvent(createEvent('mousemove'));
485 expect(onHoverMove).toHaveBeenCalledTimes(1);
486 expect(onHoverMove).toHaveBeenCalledWith(
487 expect.objectContaining({type: 'hovermove'}),
@@ -476,18 +516,18 @@ describe('Hover event responder', () => {
516
517 ReactDOM.render(element, container);
518
479 - outerRef.current.dispatchEvent(createPointerEvent('pointerover'));
519 + outerRef.current.dispatchEvent(createEvent('pointerover'));
520 outerRef.current.dispatchEvent(
481 - createPointerEvent('pointerout', {relatedTarget: innerRef.current}),
521 + createEvent('pointerout', {relatedTarget: innerRef.current}),
522 );
483 - innerRef.current.dispatchEvent(createPointerEvent('pointerover'));
523 + innerRef.current.dispatchEvent(createEvent('pointerover'));
524 innerRef.current.dispatchEvent(
485 - createPointerEvent('pointerout', {relatedTarget: outerRef.current}),
525 + createEvent('pointerout', {relatedTarget: outerRef.current}),
526 );
527 outerRef.current.dispatchEvent(
488 - createPointerEvent('pointerover', {relatedTarget: innerRef.current}),
528 + createEvent('pointerover', {relatedTarget: innerRef.current}),
529 );
490 - outerRef.current.dispatchEvent(createPointerEvent('pointerout'));
530 + outerRef.current.dispatchEvent(createEvent('pointerout'));
531 expect(events).toEqual([
532 'outer: onHoverStart',
533 'outer: onHoverChange',
@@ -547,7 +587,7 @@ describe('Hover event responder', () => {
587 });
588
589 ref.current.dispatchEvent(
550 - createPointerEvent('pointerover', {
590 + createEvent('pointerover', {
591 pointerType: 'mouse',
592 pageX: 15,
593 pageY: 16,
@@ -558,7 +598,7 @@ describe('Hover event responder', () => {
598 }),
599 );
600 ref.current.dispatchEvent(
561 - createPointerEvent('pointermove', {
601 + createEvent('pointermove', {
602 pointerType: 'mouse',
603 pageX: 16,
604 pageY: 17,
@@ -569,7 +609,7 @@ describe('Hover event responder', () => {
609 }),
610 );
611 ref.current.dispatchEvent(
572 - createPointerEvent('pointerout', {
612 + createEvent('pointerout', {
613 pointerType: 'mouse',
614 pageX: 17,
615 pageY: 18,
packages/react-events/src/__tests__/Press-test.internal.js
+319 -71
@@ -28,6 +28,17 @@ const createEvent = (type, data) => {
28 return event;
29 };
30
31 +function createTouchEvent(type, id, data) {
32 + return createEvent(type, {
33 + changedTouches: [
34 + {
35 + ...data,
36 + identifier: id,
37 + },
38 + ],
39 + });
40 +}
41 +
42 const createKeyboardEvent = (type, data) => {
43 return new KeyboardEvent(type, {
44 bubbles: true,
@@ -108,6 +119,11 @@ describe('Event responder: Press', () => {
119 ref.current.dispatchEvent(
120 createEvent('pointerdown', {pointerType: 'pen'}),
121 );
122 + ref.current.dispatchEvent(
123 + createTouchEvent('touchstart', 0, {
124 + target: ref.current,
125 + }),
126 + );
127 expect(onPressStart).toHaveBeenCalledTimes(1);
128 expect(onPressStart).toHaveBeenCalledWith(
129 expect.objectContaining({pointerType: 'pen', type: 'pressstart'}),
@@ -156,14 +172,23 @@ describe('Event responder: Press', () => {
172
173 // No PointerEvent fallbacks
174 it('is called after "mousedown" event', () => {
159 - ref.current.dispatchEvent(createEvent('mousedown'));
175 + ref.current.dispatchEvent(
176 + createEvent('mousedown', {
177 + button: 0,
178 + }),
179 + );
180 expect(onPressStart).toHaveBeenCalledTimes(1);
181 expect(onPressStart).toHaveBeenCalledWith(
182 expect.objectContaining({pointerType: 'mouse', type: 'pressstart'}),
183 );
184 });
185 +
186 it('is called after "touchstart" event', () => {
166 - ref.current.dispatchEvent(createEvent('touchstart'));
187 + ref.current.dispatchEvent(
188 + createTouchEvent('touchstart', 0, {
189 + target: ref.current,
190 + }),
191 + );
192 expect(onPressStart).toHaveBeenCalledTimes(1);
193 expect(onPressStart).toHaveBeenCalledWith(
194 expect.objectContaining({pointerType: 'touch', type: 'pressstart'}),
@@ -266,7 +291,17 @@ describe('Event responder: Press', () => {
291 ref.current.dispatchEvent(
292 createEvent('pointerdown', {pointerType: 'pen'}),
293 );
269 - ref.current.dispatchEvent(createEvent('pointerup'));
294 + ref.current.dispatchEvent(
295 + createTouchEvent('touchstart', 0, {
296 + target: ref.current,
297 + }),
298 + );
299 + ref.current.dispatchEvent(
300 + createTouchEvent('touchend', 0, {
301 + target: ref.current,
302 + }),
303 + );
304 + ref.current.dispatchEvent(createEvent('pointerup', {pointerType: 'pen'}));
305 expect(onPressEnd).toHaveBeenCalledTimes(1);
306 expect(onPressEnd).toHaveBeenCalledWith(
307 expect.objectContaining({pointerType: 'pen', type: 'pressend'}),
@@ -277,10 +312,20 @@ describe('Event responder: Press', () => {
312 ref.current.dispatchEvent(
313 createEvent('pointerdown', {pointerType: 'touch'}),
314 );
280 - ref.current.dispatchEvent(createEvent('touchstart'));
281 - ref.current.dispatchEvent(createEvent('pointerup'));
282 - ref.current.dispatchEvent(createEvent('touchend'));
315 + ref.current.dispatchEvent(
316 + createTouchEvent('touchstart', 0, {
317 + target: ref.current,
318 + }),
319 + );
320 ref.current.dispatchEvent(createEvent('mousedown'));
321 + ref.current.dispatchEvent(
322 + createEvent('pointerup', {pointerType: 'touch'}),
323 + );
324 + ref.current.dispatchEvent(
325 + createTouchEvent('touchend', 0, {
326 + target: ref.current,
327 + }),
328 + );
329 ref.current.dispatchEvent(createEvent('mouseup'));
330 expect(onPressEnd).toHaveBeenCalledTimes(1);
331 expect(onPressEnd).toHaveBeenCalledWith(
@@ -337,16 +382,33 @@ describe('Event responder: Press', () => {
382
383 // No PointerEvent fallbacks
384 it('is called after "mouseup" event', () => {
340 - ref.current.dispatchEvent(createEvent('mousedown'));
341 - ref.current.dispatchEvent(createEvent('mouseup'));
385 + ref.current.dispatchEvent(
386 + createEvent('mousedown', {
387 + button: 0,
388 + }),
389 + );
390 + ref.current.dispatchEvent(
391 + createEvent('mouseup', {
392 + button: 0,
393 + }),
394 + );
395 expect(onPressEnd).toHaveBeenCalledTimes(1);
396 expect(onPressEnd).toHaveBeenCalledWith(
397 expect.objectContaining({pointerType: 'mouse', type: 'pressend'}),
398 );
399 });
400 it('is called after "touchend" event', () => {
348 - ref.current.dispatchEvent(createEvent('touchstart'));
349 - ref.current.dispatchEvent(createEvent('touchend'));
401 + document.elementFromPoint = () => ref.current;
402 + ref.current.dispatchEvent(
403 + createTouchEvent('touchstart', 0, {
404 + target: ref.current,
405 + }),
406 + );
407 + ref.current.dispatchEvent(
408 + createTouchEvent('touchend', 0, {
409 + target: ref.current,
410 + }),
411 + );
412 expect(onPressEnd).toHaveBeenCalledTimes(1);
413 expect(onPressEnd).toHaveBeenCalledWith(
414 expect.objectContaining({pointerType: 'touch', type: 'pressend'}),
@@ -510,10 +572,18 @@ describe('Event responder: Press', () => {
572 expect(onPressChange).toHaveBeenCalledWith(false);
573 });
574 it('is called after "touchstart" and "touchend" events', () => {
513 - ref.current.dispatchEvent(createEvent('touchstart'));
575 + ref.current.dispatchEvent(
576 + createTouchEvent('touchstart', 0, {
577 + target: ref.current,
578 + }),
579 + );
580 expect(onPressChange).toHaveBeenCalledTimes(1);
581 expect(onPressChange).toHaveBeenCalledWith(true);
516 - ref.current.dispatchEvent(createEvent('touchend'));
582 + ref.current.dispatchEvent(
583 + createTouchEvent('touchend', 0, {
584 + target: ref.current,
585 + }),
586 + );
587 expect(onPressChange).toHaveBeenCalledTimes(2);
588 expect(onPressChange).toHaveBeenCalledWith(false);
589 });
@@ -544,8 +614,16 @@ describe('Event responder: Press', () => {
614 createEvent('pointerdown', {pointerType: 'pen'}),
615 );
616 ref.current.dispatchEvent(
547 - createEvent('pointerup', {clientX: 10, clientY: 10}),
617 + createTouchEvent('touchstart', 0, {
618 + target: ref.current,
619 + }),
620 );
621 + ref.current.dispatchEvent(
622 + createTouchEvent('touchend', 0, {
623 + target: ref.current,
624 + }),
625 + );
626 + ref.current.dispatchEvent(createEvent('pointerup', {pointerType: 'pen'}));
627 expect(onPress).toHaveBeenCalledTimes(1);
628 expect(onPress).toHaveBeenCalledWith(
629 expect.objectContaining({pointerType: 'pen', type: 'press'}),
@@ -624,6 +702,11 @@ describe('Event responder: Press', () => {
702 ref.current.dispatchEvent(
703 createEvent('pointerdown', {pointerType: 'pen'}),
704 );
705 + ref.current.dispatchEvent(
706 + createTouchEvent('touchstart', 0, {
707 + target: ref.current,
708 + }),
709 + );
710 jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY - 1);
711 expect(onLongPress).not.toBeCalled();
712 jest.advanceTimersByTime(1);
@@ -815,18 +898,18 @@ describe('Event responder: Press', () => {
898 right: 100,
899 });
900 ref.current.dispatchEvent(
818 - createEvent('pointerdown', {pointerType: 'touch'}),
901 + createEvent('pointerdown', {pointerType: 'mouse'}),
902 );
903 ref.current.dispatchEvent(
904 createEvent('pointermove', {
822 - pointerType: 'touch',
905 + pointerType: 'mouse',
906 clientX: 10,
907 clientY: 10,
908 }),
909 );
910 expect(onPressMove).toHaveBeenCalledTimes(1);
911 expect(onPressMove).toHaveBeenCalledWith(
829 - expect.objectContaining({pointerType: 'touch', type: 'pressmove'}),
912 + expect.objectContaining({pointerType: 'mouse', type: 'pressmove'}),
913 );
914 });
915
@@ -876,7 +959,11 @@ describe('Event responder: Press', () => {
959 ref.current.dispatchEvent(
960 createEvent('pointerdown', {pointerType: 'touch'}),
961 );
879 - ref.current.dispatchEvent(createEvent('touchstart'));
962 + ref.current.dispatchEvent(
963 + createTouchEvent('touchstart', 0, {
964 + target: ref.current,
965 + }),
966 + );
967 ref.current.dispatchEvent(
968 createEvent('pointermove', {
969 pointerType: 'touch',
@@ -884,7 +971,13 @@ describe('Event responder: Press', () => {
971 clientY: 10,
972 }),
973 );
887 - ref.current.dispatchEvent(createEvent('touchmove'));
974 + ref.current.dispatchEvent(
975 + createTouchEvent('touchmove', 0, {
976 + target: ref.current,
977 + clientX: 10,
978 + clientY: 10,
979 + }),
980 + );
981 ref.current.dispatchEvent(createEvent('mousemove'));
982 expect(onPressMove).toHaveBeenCalledTimes(1);
983 });
@@ -1344,24 +1437,53 @@ describe('Event responder: Press', () => {
1437 pointerType: 'touch',
1438 }),
1439 );
1440 + ref.current.dispatchEvent(
1441 + createTouchEvent('touchstart', 0, {
1442 + target: ref.current,
1443 + }),
1444 + );
1445 ref.current.dispatchEvent(
1446 createEvent('pointermove', {
1447 ...coordinatesInside,
1448 pointerType: 'touch',
1449 }),
1450 );
1451 + ref.current.dispatchEvent(
1452 + createTouchEvent('touchmove', 0, {
1453 + ...coordinatesInside,
1454 + target: ref.current,
1455 + }),
1456 + );
1457 container.dispatchEvent(
1458 createEvent('pointermove', {
1459 ...coordinatesOutside,
1460 pointerType: 'touch',
1461 }),
1462 );
1463 + ref.current.dispatchEvent(
1464 + createTouchEvent('touchmove', 0, {
1465 + ...coordinatesOutside,
1466 + target: ref.current,
1467 + }),
1468 + );
1469 container.dispatchEvent(
1470 createEvent('pointermove', {
1471 ...coordinatesInside,
1472 pointerType: 'touch',
1473 }),
1474 );
1475 + ref.current.dispatchEvent(
1476 + createTouchEvent('touchmove', 0, {
1477 + ...coordinatesInside,
1478 + target: ref.current,
1479 + }),
1480 + );
1481 + ref.current.dispatchEvent(
1482 + createTouchEvent('touchend', 0, {
1483 + ...coordinatesInside,
1484 + target: ref.current,
1485 + }),
1486 + );
1487 container.dispatchEvent(
1488 createEvent('pointerup', {
1489 ...coordinatesInside,
@@ -1397,20 +1519,12 @@ describe('Event responder: Press', () => {
1519 const pressRectOffset = 20;
1520 const getBoundingClientRectMock = () => rectMock;
1521 const coordinatesInside = {
1400 - changedTouches: [
1401 - {
1402 - clientX: rectMock.left - pressRectOffset,
1403 - clientY: rectMock.top - pressRectOffset,
1404 - },
1405 - ],
1522 + clientX: rectMock.left - pressRectOffset,
1523 + clientY: rectMock.top - pressRectOffset,
1524 };
1525 const coordinatesOutside = {
1408 - changedTouches: [
1409 - {
1410 - clientX: rectMock.left - pressRectOffset - 1,
1411 - clientY: rectMock.top - pressRectOffset - 1,
1412 - },
1413 - ],
1526 + clientX: rectMock.left - pressRectOffset - 1,
1527 + clientY: rectMock.top - pressRectOffset - 1,
1528 };
1529
1530 describe('within bounds of hit rect', () => {
@@ -1441,10 +1555,25 @@ describe('Event responder: Press', () => {
1555
1556 ReactDOM.render(element, container);
1557
1558 + document.elementFromPoint = () => ref.current;
1559 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1445 - ref.current.dispatchEvent(createEvent('touchstart'));
1446 - ref.current.dispatchEvent(createEvent('touchmove', coordinatesInside));
1447 - ref.current.dispatchEvent(createEvent('touchend', coordinatesInside));
1560 + ref.current.dispatchEvent(
1561 + createTouchEvent('touchstart', 0, {
1562 + target: ref.current,
1563 + }),
1564 + );
1565 + ref.current.dispatchEvent(
1566 + createTouchEvent('touchmove', 0, {
1567 + ...coordinatesInside,
1568 + target: ref.current,
1569 + }),
1570 + );
1571 + ref.current.dispatchEvent(
1572 + createTouchEvent('touchend', 0, {
1573 + ...coordinatesInside,
1574 + target: ref.current,
1575 + }),
1576 + );
1577 jest.runAllTimers();
1578
1579 expect(events).toEqual([
@@ -1478,9 +1607,19 @@ describe('Event responder: Press', () => {
1607
1608 ReactDOM.render(element, container);
1609
1610 + document.elementFromPoint = () => ref.current;
1611 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1482 - ref.current.dispatchEvent(createEvent('touchstart'));
1483 - ref.current.dispatchEvent(createEvent('touchmove', coordinatesInside));
1612 + ref.current.dispatchEvent(
1613 + createTouchEvent('touchstart', 0, {
1614 + target: ref.current,
1615 + }),
1616 + );
1617 + ref.current.dispatchEvent(
1618 + createTouchEvent('touchmove', 0, {
1619 + ...coordinatesInside,
1620 + target: ref.current,
1621 + }),
1622 + );
1623 jest.advanceTimersByTime(499);
1624 expect(events).toEqual(['onPressMove']);
1625 events = [];
@@ -1489,7 +1628,12 @@ describe('Event responder: Press', () => {
1628 expect(events).toEqual(['onPressStart', 'onPressChange']);
1629 events = [];
1630
1492 - ref.current.dispatchEvent(createEvent('touchend', coordinatesInside));
1631 + ref.current.dispatchEvent(
1632 + createTouchEvent('touchend', 0, {
1633 + ...coordinatesInside,
1634 + target: ref.current,
1635 + }),
1636 + );
1637 expect(events).toEqual(['onPressEnd', 'onPressChange', 'onPress']);
1638 });
1639
@@ -1514,15 +1658,27 @@ describe('Event responder: Press', () => {
1658 );
1659
1660 ReactDOM.render(element, container);
1661 +
1662 + document.elementFromPoint = () => ref.current;
1663 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1518 - ref.current.dispatchEvent(createEvent('touchstart'));
1664 ref.current.dispatchEvent(
1520 - createEvent('touchmove', {
1665 + createTouchEvent('touchstart', 0, {
1666 + target: ref.current,
1667 + }),
1668 + );
1669 + ref.current.dispatchEvent(
1670 + createTouchEvent('touchmove', 0, {
1671 clientX: rectMock.left - pressRetentionOffset.left,
1672 clientY: rectMock.top - pressRetentionOffset.top,
1673 + target: ref.current,
1674 + }),
1675 + );
1676 + ref.current.dispatchEvent(
1677 + createTouchEvent('touchend', 0, {
1678 + ...coordinatesInside,
1679 + target: ref.current,
1680 }),
1681 );
1525 - ref.current.dispatchEvent(createEvent('touchend', coordinatesInside));
1682 expect(events).toEqual([
1683 'onPressStart',
1684 'onPressChange',
@@ -1550,8 +1706,14 @@ describe('Event responder: Press', () => {
1706 );
1707
1708 ReactDOM.render(element, container);
1709 +
1710 + document.elementFromPoint = () => ref.current;
1711 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1554 - ref.current.dispatchEvent(createEvent('touchstart'));
1712 + ref.current.dispatchEvent(
1713 + createTouchEvent('touchstart', 0, {
1714 + target: ref.current,
1715 + }),
1716 + );
1717 // emulate smaller dimensions change on activation
1718 ref.current.getBoundingClientRect = () => ({
1719 width: 80,
@@ -1566,8 +1728,18 @@ describe('Event responder: Press', () => {
1728 clientY: rectMock.top,
1729 };
1730 // move to an area within the pre-activation region
1569 - ref.current.dispatchEvent(createEvent('touchmove', coordinates));
1570 - ref.current.dispatchEvent(createEvent('touchend', coordinates));
1731 + ref.current.dispatchEvent(
1732 + createTouchEvent('touchmove', 0, {
1733 + ...coordinates,
1734 + target: ref.current,
1735 + }),
1736 + );
1737 + ref.current.dispatchEvent(
1738 + createTouchEvent('touchend', 0, {
1739 + ...coordinates,
1740 + target: ref.current,
1741 + }),
1742 + );
1743 expect(events).toEqual(['onPressStart', 'onPressEnd', 'onPress']);
1744 });
1745
@@ -1588,8 +1760,14 @@ describe('Event responder: Press', () => {
1760 );
1761
1762 ReactDOM.render(element, container);
1763 +
1764 + document.elementFromPoint = () => ref.current;
1765 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1592 - ref.current.dispatchEvent(createEvent('touchstart'));
1766 + ref.current.dispatchEvent(
1767 + createTouchEvent('touchstart', 0, {
1768 + target: ref.current,
1769 + }),
1770 + );
1771 // emulate larger dimensions change on activation
1772 ref.current.getBoundingClientRect = () => ({
1773 width: 200,
@@ -1604,8 +1782,18 @@ describe('Event responder: Press', () => {
1782 clientY: rectMock.top - 50,
1783 };
1784 // move to an area within the post-activation region
1607 - ref.current.dispatchEvent(createEvent('touchmove', coordinates));
1608 - ref.current.dispatchEvent(createEvent('touchend', coordinates));
1785 + ref.current.dispatchEvent(
1786 + createTouchEvent('touchmove', 0, {
1787 + ...coordinates,
1788 + target: ref.current,
1789 + }),
1790 + );
1791 + ref.current.dispatchEvent(
1792 + createTouchEvent('touchend', 0, {
1793 + ...coordinates,
1794 + target: ref.current,
1795 + }),
1796 + );
1797 expect(events).toEqual(['onPressStart', 'onPressEnd', 'onPress']);
1798 });
1799 });
@@ -1640,11 +1828,32 @@ describe('Event responder: Press', () => {
1828
1829 ReactDOM.render(element, container);
1830
1831 + document.elementFromPoint = () => ref.current;
1832 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1644 - ref.current.dispatchEvent(createEvent('touchstart'));
1645 - ref.current.dispatchEvent(createEvent('touchmove', coordinatesInside));
1646 - container.dispatchEvent(createEvent('touchmove', coordinatesOutside));
1647 - container.dispatchEvent(createEvent('touchend', coordinatesOutside));
1833 + ref.current.dispatchEvent(
1834 + createTouchEvent('touchstart', 0, {
1835 + target: ref.current,
1836 + }),
1837 + );
1838 + ref.current.dispatchEvent(
1839 + createTouchEvent('touchmove', 0, {
1840 + ...coordinatesInside,
1841 + target: ref.current,
1842 + }),
1843 + );
1844 + document.elementFromPoint = () => container;
1845 + ref.current.dispatchEvent(
1846 + createTouchEvent('touchmove', 0, {
1847 + ...coordinatesOutside,
1848 + target: ref.current,
1849 + }),
1850 + );
1851 + ref.current.dispatchEvent(
1852 + createTouchEvent('touchend', 0, {
1853 + ...coordinatesOutside,
1854 + target: ref.current,
1855 + }),
1856 + );
1857 jest.runAllTimers();
1858
1859 expect(events).toEqual([
@@ -1679,14 +1888,35 @@ describe('Event responder: Press', () => {
1888
1889 ReactDOM.render(element, container);
1890
1891 + document.elementFromPoint = () => ref.current;
1892 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1683 - ref.current.dispatchEvent(createEvent('touchstart'));
1684 - ref.current.dispatchEvent(createEvent('touchmove', coordinatesInside));
1685 - container.dispatchEvent(createEvent('touchmove', coordinatesOutside));
1893 + ref.current.dispatchEvent(
1894 + createTouchEvent('touchstart', 0, {
1895 + target: ref.current,
1896 + }),
1897 + );
1898 + ref.current.dispatchEvent(
1899 + createTouchEvent('touchmove', 0, {
1900 + ...coordinatesInside,
1901 + target: ref.current,
1902 + }),
1903 + );
1904 + document.elementFromPoint = () => container;
1905 + container.dispatchEvent(
1906 + createTouchEvent('touchmove', 0, {
1907 + ...coordinatesOutside,
1908 + target: container,
1909 + }),
1910 + );
1911 jest.runAllTimers();
1912 expect(events).toEqual(['onPressMove']);
1913 events = [];
1689 - container.dispatchEvent(createEvent('touchend', coordinatesOutside));
1914 + ref.current.dispatchEvent(
1915 + createTouchEvent('touchend', 0, {
1916 + ...coordinatesOutside,
1917 + target: ref.current,
1918 + }),
1919 + );
1920 jest.runAllTimers();
1921 expect(events).toEqual([]);
1922 });
@@ -1712,34 +1942,36 @@ describe('Event responder: Press', () => {
1942
1943 ReactDOM.render(element, container);
1944
1945 + document.elementFromPoint = () => ref.current;
1946 ref.current.getBoundingClientRect = getBoundingClientRectMock;
1947 ref.current.dispatchEvent(
1717 - createEvent('touchstart', {
1718 - pointerType: 'touch',
1948 + createTouchEvent('touchstart', 0, {
1949 + target: ref.current,
1950 }),
1951 );
1952 ref.current.dispatchEvent(
1722 - createEvent('touchmove', {
1953 + createTouchEvent('touchmove', 0, {
1954 ...coordinatesInside,
1724 - pointerType: 'touch',
1955 + target: ref.current,
1956 }),
1957 );
1727 - container.dispatchEvent(
1728 - createEvent('touchmove', {
1958 + document.elementFromPoint = () => container;
1959 + ref.current.dispatchEvent(
1960 + createTouchEvent('touchmove', 0, {
1961 ...coordinatesOutside,
1730 - pointerType: 'touch',
1962 + target: ref.current,
1963 }),
1964 );
1733 - container.dispatchEvent(
1734 - createEvent('touchmove', {
1965 + ref.current.dispatchEvent(
1966 + createTouchEvent('touchmove', 0, {
1967 ...coordinatesInside,
1736 - pointerType: 'touch',
1968 + target: ref.current,
1969 }),
1970 );
1739 - container.dispatchEvent(
1740 - createEvent('touchend', {
1741 - ...coordinatesInside,
1742 - pointerType: 'touch',
1971 + document.elementFromPoint = () => ref.current;
1972 + ref.current.dispatchEvent(
1973 + createTouchEvent('touchend', 0, {
1974 + target: ref.current,
1975 }),
1976 );
1977 jest.runAllTimers();
@@ -2094,8 +2326,16 @@ describe('Event responder: Press', () => {
2326 onPressEnd.mockReset();
2327
2328 // When pointer events are supported
2097 - ref.current.dispatchEvent(createEvent('pointerdown'));
2098 - ref.current.dispatchEvent(createEvent('pointercancel'));
2329 + ref.current.dispatchEvent(
2330 + createEvent('pointerdown', {
2331 + pointerType: 'mouse',
2332 + }),
2333 + );
2334 + ref.current.dispatchEvent(
2335 + createEvent('pointercancel', {
2336 + pointerType: 'mouse',
2337 + }),
2338 + );
2339 expect(onPressEnd).toHaveBeenCalledTimes(1);
2340 jest.runAllTimers();
2341 expect(onLongPress).not.toBeCalled();
@@ -2104,8 +2344,16 @@ describe('Event responder: Press', () => {
2344 onPressEnd.mockReset();
2345
2346 // Touch fallback
2107 - ref.current.dispatchEvent(createEvent('touchstart'));
2108 - ref.current.dispatchEvent(createEvent('touchcancel'));
2347 + ref.current.dispatchEvent(
2348 + createTouchEvent('touchstart', 0, {
2349 + target: ref.current,
2350 + }),
2351 + );
2352 + ref.current.dispatchEvent(
2353 + createTouchEvent('touchcancel', 0, {
2354 + target: ref.current,
2355 + }),
2356 + );
2357 expect(onPressEnd).toHaveBeenCalledTimes(1);
2358 jest.runAllTimers();
2359 expect(onLongPress).not.toBeCalled();
packages/react-events/src/__tests__/Scroll-test.internal.js
+10 -2
@@ -121,8 +121,16 @@ describe('Scroll event responder', () => {
121 });
122
123 it('with a keyboard pointerType', () => {
124 - ref.current.dispatchEvent(createEvent('keydown'));
125 - ref.current.dispatchEvent(createEvent('keyup'));
124 + ref.current.dispatchEvent(
125 + createEvent('keydown', {
126 + key: 'A',
127 + }),
128 + );
129 + ref.current.dispatchEvent(
130 + createEvent('keyup', {
131 + key: 'A',
132 + }),
133 + );
134 ref.current.dispatchEvent(createEvent('scroll'));
135 expect(onScroll).toHaveBeenCalledTimes(1);
136 expect(onScroll).toHaveBeenCalledWith(
packages/shared/ReactTypes.js
+12 -5
@@ -150,12 +150,22 @@ export type ReactEventTarget = {|
150
151 type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | Touch;
152
153 +export type PointerType =
154 + | ''
155 + | 'mouse'
156 + | 'keyboard'
157 + | 'pen'
158 + | 'touch'
159 + | 'trackpad';
160 +
161 export type ReactResponderEvent = {
162 nativeEvent: AnyNativeEvent,
155 - target: Element | Document,
156 - type: string,
163 passive: boolean,
164 passiveSupported: boolean,
165 + pointerId: null | number,
166 + pointerType: PointerType,
167 + target: Element | Document,
168 + type: string,
169 };
170
171 export opaque type EventPriority = 0 | 1 | 2;
@@ -192,9 +202,6 @@ export type ReactResponderContext = {
202 getFocusableElementsInScope(): Array<HTMLElement>,
203 getActiveDocument(): Document,
204 objectAssign: Function,
195 - getEventPointerType(
196 - event: ReactResponderEvent,
197 - ): '' | 'mouse' | 'keyboard' | 'pen' | 'touch',
205 getEventCurrentTarget(event: ReactResponderEvent): Element,
206 getTimeStamp: () => number,
207 isTargetWithinHostComponent: (