mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
React events: keyboard press, types, tests (#15314)
* Add HoverProps type * Add more Hover event module tests * Add more Press event module tests * Change default longPress delay from 1000 to 500 * Rename dispatchPressEvent -> dispatchEvent * Consolidate state updates in Press event module * Add keyboard support for Press events * Add FocusProps type and unit tests
This commit is contained in:
Vendored
+23
-10
@@ -10,10 +10,12 @@
|
||||
import type {EventResponderContext} from 'events/EventTypes';
|
||||
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
const targetEventTypes = [
|
||||
{name: 'focus', passive: true, capture: true},
|
||||
{name: 'blur', passive: true, capture: true},
|
||||
];
|
||||
type FocusProps = {
|
||||
disabled: boolean,
|
||||
onBlur: (e: FocusEvent) => void,
|
||||
onFocus: (e: FocusEvent) => void,
|
||||
onFocusChange: boolean => void,
|
||||
};
|
||||
|
||||
type FocusState = {
|
||||
isFocused: boolean,
|
||||
@@ -27,6 +29,11 @@ type FocusEvent = {|
|
||||
type: FocusEventType,
|
||||
|};
|
||||
|
||||
const targetEventTypes = [
|
||||
{name: 'focus', passive: true, capture: true},
|
||||
{name: 'blur', passive: true, capture: true},
|
||||
];
|
||||
|
||||
function createFocusEvent(
|
||||
type: FocusEventType,
|
||||
target: Element | Document,
|
||||
@@ -39,7 +46,10 @@ function createFocusEvent(
|
||||
};
|
||||
}
|
||||
|
||||
function dispatchFocusInEvents(context: EventResponderContext, props: Object) {
|
||||
function dispatchFocusInEvents(
|
||||
context: EventResponderContext,
|
||||
props: FocusProps,
|
||||
) {
|
||||
const {event, eventTarget} = context;
|
||||
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
|
||||
return;
|
||||
@@ -53,19 +63,22 @@ function dispatchFocusInEvents(context: EventResponderContext, props: Object) {
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
if (props.onFocusChange) {
|
||||
const focusChangeEventListener = () => {
|
||||
const listener = () => {
|
||||
props.onFocusChange(true);
|
||||
};
|
||||
const syntheticEvent = createFocusEvent(
|
||||
'focuschange',
|
||||
eventTarget,
|
||||
focusChangeEventListener,
|
||||
listener,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchFocusOutEvents(context: EventResponderContext, props: Object) {
|
||||
function dispatchFocusOutEvents(
|
||||
context: EventResponderContext,
|
||||
props: FocusProps,
|
||||
) {
|
||||
const {event, eventTarget} = context;
|
||||
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
|
||||
return;
|
||||
@@ -75,13 +88,13 @@ function dispatchFocusOutEvents(context: EventResponderContext, props: Object) {
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
if (props.onFocusChange) {
|
||||
const focusChangeEventListener = () => {
|
||||
const listener = () => {
|
||||
props.onFocusChange(false);
|
||||
};
|
||||
const syntheticEvent = createFocusEvent(
|
||||
'focuschange',
|
||||
eventTarget,
|
||||
focusChangeEventListener,
|
||||
listener,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
|
||||
Vendored
+41
-24
@@ -10,12 +10,14 @@
|
||||
import type {EventResponderContext} from 'events/EventTypes';
|
||||
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
const targetEventTypes = [
|
||||
'pointerover',
|
||||
'pointermove',
|
||||
'pointerout',
|
||||
'pointercancel',
|
||||
];
|
||||
type HoverProps = {
|
||||
disabled: boolean,
|
||||
delayHoverEnd: number,
|
||||
delayHoverStart: number,
|
||||
onHoverChange: boolean => void,
|
||||
onHoverEnd: (e: HoverEvent) => void,
|
||||
onHoverStart: (e: HoverEvent) => void,
|
||||
};
|
||||
|
||||
type HoverState = {
|
||||
isHovered: boolean,
|
||||
@@ -31,6 +33,21 @@ type HoverEvent = {|
|
||||
type: HoverEventType,
|
||||
|};
|
||||
|
||||
// const DEFAULT_HOVER_END_DELAY_MS = 0;
|
||||
// const DEFAULT_HOVER_START_DELAY_MS = 0;
|
||||
|
||||
const targetEventTypes = [
|
||||
'pointerover',
|
||||
'pointermove',
|
||||
'pointerout',
|
||||
'pointercancel',
|
||||
];
|
||||
|
||||
// If PointerEvents is not supported (e.g., Safari), also listen to touch and mouse events.
|
||||
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
|
||||
targetEventTypes.push('touchstart', 'mouseover', 'mouseout');
|
||||
}
|
||||
|
||||
function createHoverEvent(
|
||||
type: HoverEventType,
|
||||
target: Element | Document,
|
||||
@@ -43,16 +60,9 @@ function createHoverEvent(
|
||||
};
|
||||
}
|
||||
|
||||
// In the case we don't have PointerEvents (Safari), we listen to touch events
|
||||
// too
|
||||
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
|
||||
targetEventTypes.push('touchstart', 'mouseover', 'mouseout');
|
||||
}
|
||||
|
||||
function dispatchHoverStartEvents(
|
||||
context: EventResponderContext,
|
||||
props: Object,
|
||||
state: HoverState,
|
||||
props: HoverProps,
|
||||
): void {
|
||||
const {event, eventTarget} = context;
|
||||
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
|
||||
@@ -67,19 +77,22 @@ function dispatchHoverStartEvents(
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
if (props.onHoverChange) {
|
||||
const hoverChangeEventListener = () => {
|
||||
const listener = () => {
|
||||
props.onHoverChange(true);
|
||||
};
|
||||
const syntheticEvent = createHoverEvent(
|
||||
'hoverchange',
|
||||
eventTarget,
|
||||
hoverChangeEventListener,
|
||||
listener,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchHoverEndEvents(context: EventResponderContext, props: Object) {
|
||||
function dispatchHoverEndEvents(
|
||||
context: EventResponderContext,
|
||||
props: HoverProps,
|
||||
) {
|
||||
const {event, eventTarget} = context;
|
||||
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
|
||||
return;
|
||||
@@ -93,13 +106,13 @@ function dispatchHoverEndEvents(context: EventResponderContext, props: Object) {
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
if (props.onHoverChange) {
|
||||
const hoverChangeEventListener = () => {
|
||||
const listener = () => {
|
||||
props.onHoverChange(false);
|
||||
};
|
||||
const syntheticEvent = createHoverEvent(
|
||||
'hoverchange',
|
||||
eventTarget,
|
||||
hoverChangeEventListener,
|
||||
listener,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
@@ -116,18 +129,22 @@ const HoverResponder = {
|
||||
},
|
||||
handleEvent(
|
||||
context: EventResponderContext,
|
||||
props: Object,
|
||||
props: HoverProps,
|
||||
state: HoverState,
|
||||
): void {
|
||||
const {eventType, eventTarget, event} = context;
|
||||
|
||||
switch (eventType) {
|
||||
case 'touchstart':
|
||||
// Touch devices don't have hover support
|
||||
/**
|
||||
* Prevent hover events when touch is being used.
|
||||
*/
|
||||
case 'touchstart': {
|
||||
if (!state.isTouched) {
|
||||
state.isTouched = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'pointerover':
|
||||
case 'mouseover': {
|
||||
if (
|
||||
@@ -148,7 +165,7 @@ const HoverResponder = {
|
||||
state.isInHitSlop = true;
|
||||
return;
|
||||
}
|
||||
dispatchHoverStartEvents(context, props, state);
|
||||
dispatchHoverStartEvents(context, props);
|
||||
state.isHovered = true;
|
||||
}
|
||||
break;
|
||||
@@ -172,7 +189,7 @@ const HoverResponder = {
|
||||
(event: any).y,
|
||||
)
|
||||
) {
|
||||
dispatchHoverStartEvents(context, props, state);
|
||||
dispatchHoverStartEvents(context, props);
|
||||
state.isHovered = true;
|
||||
state.isInHitSlop = false;
|
||||
}
|
||||
|
||||
Vendored
+182
-157
@@ -10,27 +10,6 @@
|
||||
import type {EventResponderContext} from 'events/EventTypes';
|
||||
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
// const DEFAULT_PRESS_DELAY_MS = 0;
|
||||
// const DEFAULT_PRESS_END_DELAY_MS = 0;
|
||||
// const DEFAULT_PRESS_START_DELAY_MS = 0;
|
||||
const DEFAULT_LONG_PRESS_DELAY_MS = 1000;
|
||||
|
||||
const targetEventTypes = [
|
||||
{name: 'click', passive: false},
|
||||
{name: 'keydown', passive: false},
|
||||
'pointerdown',
|
||||
'pointercancel',
|
||||
'contextmenu',
|
||||
];
|
||||
const rootEventTypes = [{name: 'pointerup', passive: false}, 'scroll'];
|
||||
|
||||
// In the case we don't have PointerEvents (Safari), we listen to touch events
|
||||
// too
|
||||
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
|
||||
targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
|
||||
rootEventTypes.push({name: 'mouseup', passive: false});
|
||||
}
|
||||
|
||||
type PressProps = {
|
||||
disabled: boolean,
|
||||
delayLongPress: number,
|
||||
@@ -70,6 +49,30 @@ type PressEvent = {|
|
||||
type: PressEventType,
|
||||
|};
|
||||
|
||||
// const DEFAULT_PRESS_DELAY_MS = 0;
|
||||
// const DEFAULT_PRESS_END_DELAY_MS = 0;
|
||||
// const DEFAULT_PRESS_START_DELAY_MS = 0;
|
||||
const DEFAULT_LONG_PRESS_DELAY_MS = 500;
|
||||
|
||||
const targetEventTypes = [
|
||||
{name: 'click', passive: false},
|
||||
{name: 'keydown', passive: false},
|
||||
'pointerdown',
|
||||
'pointercancel',
|
||||
'contextmenu',
|
||||
];
|
||||
const rootEventTypes = [
|
||||
{name: 'keyup', passive: false},
|
||||
{name: 'pointerup', passive: false},
|
||||
'scroll',
|
||||
];
|
||||
|
||||
// If PointerEvents is not supported (e.g., Safari), also listen to touch and mouse events.
|
||||
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
|
||||
targetEventTypes.push('touchstart', 'touchend', 'mousedown', 'touchcancel');
|
||||
rootEventTypes.push({name: 'mouseup', passive: false});
|
||||
}
|
||||
|
||||
function createPressEvent(
|
||||
type: PressEventType,
|
||||
target: Element | Document,
|
||||
@@ -82,7 +85,7 @@ function createPressEvent(
|
||||
};
|
||||
}
|
||||
|
||||
function dispatchPressEvent(
|
||||
function dispatchEvent(
|
||||
context: EventResponderContext,
|
||||
state: PressState,
|
||||
name: PressEventType,
|
||||
@@ -93,23 +96,40 @@ function dispatchPressEvent(
|
||||
context.dispatchEvent(syntheticEvent, {discrete: true});
|
||||
}
|
||||
|
||||
function dispatchPressChangeEvent(
|
||||
context: EventResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
const listener = () => {
|
||||
props.onPressChange(state.isPressed);
|
||||
};
|
||||
dispatchEvent(context, state, 'presschange', listener);
|
||||
}
|
||||
|
||||
function dispatchLongPressChangeEvent(
|
||||
context: EventResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
const listener = () => {
|
||||
props.onLongPressChange(state.isLongPressed);
|
||||
};
|
||||
dispatchEvent(context, state, 'longpresschange', listener);
|
||||
}
|
||||
|
||||
function dispatchPressStartEvents(
|
||||
context: EventResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
): void {
|
||||
function dispatchPressChangeEvent(bool) {
|
||||
const pressChangeEventListener = () => {
|
||||
props.onPressChange(bool);
|
||||
};
|
||||
dispatchPressEvent(context, state, 'presschange', pressChangeEventListener);
|
||||
}
|
||||
state.isPressed = true;
|
||||
|
||||
if (props.onPressStart) {
|
||||
dispatchPressEvent(context, state, 'pressstart', props.onPressStart);
|
||||
dispatchEvent(context, state, 'pressstart', props.onPressStart);
|
||||
}
|
||||
if (props.onPressChange) {
|
||||
dispatchPressChangeEvent(true);
|
||||
dispatchPressChangeEvent(context, props, state);
|
||||
}
|
||||
if ((props.onLongPress || props.onLongPressChange) && !state.isLongPressed) {
|
||||
const delayLongPress = calculateDelayMS(
|
||||
@@ -125,31 +145,18 @@ function dispatchPressStartEvents(
|
||||
state.longPressTimeout = null;
|
||||
|
||||
if (props.onLongPress) {
|
||||
const longPressEventListener = e => {
|
||||
const listener = e => {
|
||||
props.onLongPress(e);
|
||||
// TODO address this again at some point
|
||||
// if (e.nativeEvent.defaultPrevented) {
|
||||
// state.defaultPrevented = true;
|
||||
// }
|
||||
};
|
||||
dispatchPressEvent(
|
||||
context,
|
||||
state,
|
||||
'longpress',
|
||||
longPressEventListener,
|
||||
);
|
||||
dispatchEvent(context, state, 'longpress', listener);
|
||||
}
|
||||
|
||||
if (props.onLongPressChange) {
|
||||
const longPressChangeEventListener = () => {
|
||||
props.onLongPressChange(true);
|
||||
};
|
||||
dispatchPressEvent(
|
||||
context,
|
||||
state,
|
||||
'longpresschange',
|
||||
longPressChangeEventListener,
|
||||
);
|
||||
dispatchLongPressChangeEvent(context, props, state);
|
||||
}
|
||||
}),
|
||||
delayLongPress,
|
||||
@@ -167,24 +174,21 @@ function dispatchPressEndEvents(
|
||||
state.longPressTimeout = null;
|
||||
}
|
||||
if (props.onPressEnd) {
|
||||
dispatchPressEvent(context, state, 'pressend', props.onPressEnd);
|
||||
dispatchEvent(context, state, 'pressend', props.onPressEnd);
|
||||
}
|
||||
if (props.onPressChange) {
|
||||
const pressChangeEventListener = () => {
|
||||
props.onPressChange(false);
|
||||
};
|
||||
dispatchPressEvent(context, state, 'presschange', pressChangeEventListener);
|
||||
|
||||
if (state.isPressed) {
|
||||
state.isPressed = false;
|
||||
if (props.onPressChange) {
|
||||
dispatchPressChangeEvent(context, props, state);
|
||||
}
|
||||
}
|
||||
if (props.onLongPressChange && state.isLongPressed) {
|
||||
const longPressChangeEventListener = () => {
|
||||
props.onLongPressChange(false);
|
||||
};
|
||||
dispatchPressEvent(
|
||||
context,
|
||||
state,
|
||||
'longpresschange',
|
||||
longPressChangeEventListener,
|
||||
);
|
||||
|
||||
if (state.isLongPressed) {
|
||||
state.isLongPressed = false;
|
||||
if (props.onLongPressChange) {
|
||||
dispatchLongPressChangeEvent(context, props, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,78 +227,6 @@ const PressResponder = {
|
||||
const {eventTarget, eventType, event} = context;
|
||||
|
||||
switch (eventType) {
|
||||
case 'keydown': {
|
||||
if (
|
||||
!props.onPress ||
|
||||
context.isTargetOwned(eventTarget) ||
|
||||
!isValidKeyPress((event: any).key)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
dispatchPressEvent(context, state, 'press', props.onPress);
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch event implementations are only needed for Safari, which lacks
|
||||
* support for pointer events.
|
||||
*/
|
||||
case 'touchstart':
|
||||
if (!state.isPressed && !context.isTargetOwned(eventTarget)) {
|
||||
// We bail out of polyfilling anchor tags, given the same heuristics
|
||||
// explained above in regards to needing to use click events.
|
||||
if (isAnchorTagElement(eventTarget)) {
|
||||
state.isAnchorTouched = true;
|
||||
return;
|
||||
}
|
||||
state.pressTarget = eventTarget;
|
||||
dispatchPressStartEvents(context, props, state);
|
||||
state.isPressed = true;
|
||||
context.addRootEventTypes(rootEventTypes);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'touchend': {
|
||||
if (state.isAnchorTouched) {
|
||||
return;
|
||||
}
|
||||
if (state.isPressed) {
|
||||
dispatchPressEndEvents(context, props, state);
|
||||
if (
|
||||
eventType !== 'touchcancel' &&
|
||||
(props.onPress || props.onLongPress)
|
||||
) {
|
||||
// Find if the X/Y of the end touch is still that of the original target
|
||||
const changedTouch = (event: any).changedTouches[0];
|
||||
const doc = (eventTarget: any).ownerDocument;
|
||||
const target = doc.elementFromPoint(
|
||||
changedTouch.screenX,
|
||||
changedTouch.screenY,
|
||||
);
|
||||
if (
|
||||
target !== null &&
|
||||
context.isTargetWithinEventComponent(target)
|
||||
) {
|
||||
if (
|
||||
props.onPress &&
|
||||
!(
|
||||
state.isLongPressed &&
|
||||
props.onLongPressShouldCancelPress &&
|
||||
props.onLongPressShouldCancelPress()
|
||||
)
|
||||
) {
|
||||
dispatchPressEvent(context, state, 'press', props.onPress);
|
||||
}
|
||||
}
|
||||
}
|
||||
state.isPressed = false;
|
||||
state.isLongPressed = false;
|
||||
state.shouldSkipMouseAfterTouch = true;
|
||||
context.removeRootEventTypes(rootEventTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to pointer events and fall back to mouse.
|
||||
*/
|
||||
@@ -309,8 +241,11 @@ const PressResponder = {
|
||||
(event: any).pointerType === 'mouse' ||
|
||||
eventType === 'mousedown'
|
||||
) {
|
||||
// Ignore if we are pressing on hit slop area with mouse
|
||||
if (
|
||||
// Ignore right- and middle-clicks
|
||||
event.button === 1 ||
|
||||
event.button === 2 ||
|
||||
// Ignore pressing on hit slop area with mouse
|
||||
context.isPositionWithinTouchHitTarget(
|
||||
(event: any).x,
|
||||
(event: any).y,
|
||||
@@ -318,14 +253,9 @@ const PressResponder = {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// Ignore middle- and right-clicks
|
||||
if (event.button === 2 || event.button === 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
state.pressTarget = eventTarget;
|
||||
dispatchPressStartEvents(context, props, state);
|
||||
state.isPressed = true;
|
||||
context.addRootEventTypes(rootEventTypes);
|
||||
}
|
||||
break;
|
||||
@@ -337,52 +267,147 @@ const PressResponder = {
|
||||
state.shouldSkipMouseAfterTouch = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const wasLongPressed = state.isLongPressed;
|
||||
|
||||
dispatchPressEndEvents(context, props, state);
|
||||
if (
|
||||
state.pressTarget !== null &&
|
||||
(props.onPress || props.onLongPress)
|
||||
) {
|
||||
|
||||
if (state.pressTarget !== null && props.onPress) {
|
||||
if (context.isTargetWithinElement(eventTarget, state.pressTarget)) {
|
||||
if (
|
||||
props.onPress &&
|
||||
!(
|
||||
state.isLongPressed &&
|
||||
wasLongPressed &&
|
||||
props.onLongPressShouldCancelPress &&
|
||||
props.onLongPressShouldCancelPress()
|
||||
)
|
||||
) {
|
||||
const pressEventListener = e => {
|
||||
const listener = e => {
|
||||
props.onPress(e);
|
||||
// TODO address this again at some point
|
||||
// if (e.nativeEvent.defaultPrevented) {
|
||||
// state.defaultPrevented = true;
|
||||
// }
|
||||
};
|
||||
dispatchPressEvent(context, state, 'press', pressEventListener);
|
||||
dispatchEvent(context, state, 'press', listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
state.isPressed = false;
|
||||
state.isLongPressed = false;
|
||||
context.removeRootEventTypes(rootEventTypes);
|
||||
}
|
||||
state.isAnchorTouched = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'scroll':
|
||||
case 'touchcancel':
|
||||
case 'contextmenu':
|
||||
case 'pointercancel': {
|
||||
/**
|
||||
* Touch event implementations are only needed for Safari, which lacks
|
||||
* support for pointer events.
|
||||
*/
|
||||
case 'touchstart': {
|
||||
if (!state.isPressed && !context.isTargetOwned(eventTarget)) {
|
||||
// We bail out of polyfilling anchor tags, given the same heuristics
|
||||
// explained above in regards to needing to use click events.
|
||||
if (isAnchorTagElement(eventTarget)) {
|
||||
state.isAnchorTouched = true;
|
||||
return;
|
||||
}
|
||||
state.pressTarget = eventTarget;
|
||||
dispatchPressStartEvents(context, props, state);
|
||||
context.addRootEventTypes(rootEventTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'touchend': {
|
||||
if (state.isAnchorTouched) {
|
||||
state.isAnchorTouched = false;
|
||||
return;
|
||||
}
|
||||
if (state.isPressed) {
|
||||
state.shouldSkipMouseAfterTouch = false;
|
||||
const wasLongPressed = state.isLongPressed;
|
||||
|
||||
dispatchPressEndEvents(context, props, state);
|
||||
state.isPressed = false;
|
||||
state.isLongPressed = false;
|
||||
|
||||
if (eventType !== 'touchcancel' && props.onPress) {
|
||||
// Find if the X/Y of the end touch is still that of the original target
|
||||
const changedTouch = (event: any).changedTouches[0];
|
||||
const doc = (eventTarget: any).ownerDocument;
|
||||
const target = doc.elementFromPoint(
|
||||
changedTouch.screenX,
|
||||
changedTouch.screenY,
|
||||
);
|
||||
if (
|
||||
target !== null &&
|
||||
context.isTargetWithinEventComponent(target)
|
||||
) {
|
||||
if (
|
||||
!(
|
||||
wasLongPressed &&
|
||||
props.onLongPressShouldCancelPress &&
|
||||
props.onLongPressShouldCancelPress()
|
||||
)
|
||||
) {
|
||||
dispatchEvent(context, state, 'press', props.onPress);
|
||||
}
|
||||
}
|
||||
}
|
||||
state.shouldSkipMouseAfterTouch = true;
|
||||
context.removeRootEventTypes(rootEventTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keyboard interaction support
|
||||
* TODO: determine UX for metaKey + validKeyPress interactions
|
||||
*/
|
||||
case 'keydown': {
|
||||
if (
|
||||
!state.isPressed &&
|
||||
!state.isLongPressed &&
|
||||
!context.isTargetOwned(eventTarget) &&
|
||||
isValidKeyPress((event: any).key)
|
||||
) {
|
||||
// Prevent spacebar press from scrolling the window
|
||||
if ((event: any).key === ' ') {
|
||||
(event: any).preventDefault();
|
||||
}
|
||||
state.pressTarget = eventTarget;
|
||||
dispatchPressStartEvents(context, props, state);
|
||||
context.addRootEventTypes(rootEventTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'keyup': {
|
||||
if (state.isPressed && isValidKeyPress((event: any).key)) {
|
||||
const wasLongPressed = state.isLongPressed;
|
||||
dispatchPressEndEvents(context, props, state);
|
||||
if (state.pressTarget !== null && props.onPress) {
|
||||
if (
|
||||
!(
|
||||
wasLongPressed &&
|
||||
props.onLongPressShouldCancelPress &&
|
||||
props.onLongPressShouldCancelPress()
|
||||
)
|
||||
) {
|
||||
dispatchEvent(context, state, 'press', props.onPress);
|
||||
}
|
||||
}
|
||||
context.removeRootEventTypes(rootEventTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'contextmenu':
|
||||
case 'pointercancel':
|
||||
case 'scroll':
|
||||
case 'touchcancel': {
|
||||
if (state.isPressed) {
|
||||
state.shouldSkipMouseAfterTouch = false;
|
||||
dispatchPressEndEvents(context, props, state);
|
||||
context.removeRootEventTypes(rootEventTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'click': {
|
||||
if (state.defaultPrevented) {
|
||||
(event: any).preventDefault();
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* 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';
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let Focus;
|
||||
|
||||
const createFocusEvent = type => {
|
||||
const event = document.createEvent('Event');
|
||||
event.initEvent(type, true, true);
|
||||
return event;
|
||||
};
|
||||
|
||||
describe('Focus event responder', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableEventAPI = true;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
Focus = require('react-events/focus');
|
||||
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(container);
|
||||
container = null;
|
||||
});
|
||||
|
||||
describe('onBlur', () => {
|
||||
let onBlur, ref;
|
||||
|
||||
beforeEach(() => {
|
||||
onBlur = jest.fn();
|
||||
ref = React.createRef();
|
||||
const element = (
|
||||
<Focus onBlur={onBlur}>
|
||||
<div ref={ref} />
|
||||
</Focus>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
});
|
||||
|
||||
it('is called after "blur" event', () => {
|
||||
ref.current.dispatchEvent(createFocusEvent('focus'));
|
||||
ref.current.dispatchEvent(createFocusEvent('blur'));
|
||||
expect(onBlur).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onFocus', () => {
|
||||
let onFocus, ref;
|
||||
|
||||
beforeEach(() => {
|
||||
onFocus = jest.fn();
|
||||
ref = React.createRef();
|
||||
const element = (
|
||||
<Focus onFocus={onFocus}>
|
||||
<div ref={ref} />
|
||||
</Focus>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
});
|
||||
|
||||
it('is called after "focus" event', () => {
|
||||
ref.current.dispatchEvent(createFocusEvent('focus'));
|
||||
expect(onFocus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onFocusChange', () => {
|
||||
let onFocusChange, ref;
|
||||
|
||||
beforeEach(() => {
|
||||
onFocusChange = jest.fn();
|
||||
ref = React.createRef();
|
||||
const element = (
|
||||
<Focus onFocusChange={onFocusChange}>
|
||||
<div ref={ref} />
|
||||
</Focus>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
});
|
||||
|
||||
it('is called after "blur" and "focus" events', () => {
|
||||
ref.current.dispatchEvent(createFocusEvent('focus'));
|
||||
expect(onFocusChange).toHaveBeenCalledTimes(1);
|
||||
expect(onFocusChange).toHaveBeenCalledWith(true);
|
||||
ref.current.dispatchEvent(createFocusEvent('blur'));
|
||||
expect(onFocusChange).toHaveBeenCalledTimes(2);
|
||||
expect(onFocusChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('expect displayName to show up for event component', () => {
|
||||
expect(Focus.displayName).toBe('Focus');
|
||||
});
|
||||
});
|
||||
@@ -14,6 +14,12 @@ let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let Hover;
|
||||
|
||||
const createPointerEvent = type => {
|
||||
const event = document.createEvent('Event');
|
||||
event.initEvent(type, true, true);
|
||||
return event;
|
||||
};
|
||||
|
||||
describe('Hover event responder', () => {
|
||||
let container;
|
||||
|
||||
@@ -34,69 +40,154 @@ describe('Hover event responder', () => {
|
||||
container = null;
|
||||
});
|
||||
|
||||
it('should support onHover', () => {
|
||||
let divRef = React.createRef();
|
||||
let events = [];
|
||||
describe('onHoverStart', () => {
|
||||
let onHoverStart, ref;
|
||||
|
||||
function handleOnHover(e) {
|
||||
if (e) {
|
||||
events.push('hover in');
|
||||
} else {
|
||||
events.push('hover out');
|
||||
}
|
||||
}
|
||||
|
||||
function Component() {
|
||||
return (
|
||||
<Hover onHoverChange={handleOnHover}>
|
||||
<div ref={divRef}>Hover me!</div>
|
||||
beforeEach(() => {
|
||||
onHoverStart = jest.fn();
|
||||
ref = React.createRef();
|
||||
const element = (
|
||||
<Hover onHoverStart={onHoverStart}>
|
||||
<div ref={ref} />
|
||||
</Hover>
|
||||
);
|
||||
}
|
||||
ReactDOM.render(element, container);
|
||||
});
|
||||
|
||||
ReactDOM.render(<Component />, container);
|
||||
it('is called after "pointerover" event', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerover'));
|
||||
expect(onHoverStart).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
const mouseOverEvent = document.createEvent('Event');
|
||||
mouseOverEvent.initEvent('mouseover', true, true);
|
||||
divRef.current.dispatchEvent(mouseOverEvent);
|
||||
it('is not called if "pointerover" pointerType is touch', () => {
|
||||
const event = createPointerEvent('pointerover');
|
||||
event.pointerType = 'touch';
|
||||
ref.current.dispatchEvent(event);
|
||||
expect(onHoverStart).not.toBeCalled();
|
||||
});
|
||||
|
||||
const mouseOutEvent = document.createEvent('Event');
|
||||
mouseOutEvent.initEvent('mouseout', true, true);
|
||||
divRef.current.dispatchEvent(mouseOutEvent);
|
||||
it('ignores browser emulated "mouseover" event', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerover'));
|
||||
ref.current.dispatchEvent(createPointerEvent('mouseover'));
|
||||
expect(onHoverStart).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
expect(events).toEqual(['hover in', 'hover out']);
|
||||
// No PointerEvent fallbacks
|
||||
it('is called after "mouseover" event', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('mouseover'));
|
||||
expect(onHoverStart).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('is not called after "touchstart"', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('touchstart'));
|
||||
ref.current.dispatchEvent(createPointerEvent('touchend'));
|
||||
ref.current.dispatchEvent(createPointerEvent('mouseover'));
|
||||
expect(onHoverStart).not.toBeCalled();
|
||||
});
|
||||
|
||||
// TODO: complete delayHoverStart tests
|
||||
// describe('delayHoverStart', () => {});
|
||||
});
|
||||
|
||||
it('should support onHoverStart and onHoverEnd', () => {
|
||||
let divRef = React.createRef();
|
||||
let events = [];
|
||||
describe('onHoverChange', () => {
|
||||
let onHoverChange, ref;
|
||||
|
||||
function handleOnHoverStart() {
|
||||
events.push('onHoverStart');
|
||||
}
|
||||
|
||||
function handleOnHoverEnd() {
|
||||
events.push('onHoverEnd');
|
||||
}
|
||||
|
||||
function Component() {
|
||||
return (
|
||||
<Hover onHoverStart={handleOnHoverStart} onHoverEnd={handleOnHoverEnd}>
|
||||
<div ref={divRef}>Hover me!</div>
|
||||
beforeEach(() => {
|
||||
onHoverChange = jest.fn();
|
||||
ref = React.createRef();
|
||||
const element = (
|
||||
<Hover onHoverChange={onHoverChange}>
|
||||
<div ref={ref} />
|
||||
</Hover>
|
||||
);
|
||||
}
|
||||
ReactDOM.render(element, container);
|
||||
});
|
||||
|
||||
ReactDOM.render(<Component />, container);
|
||||
it('is called after "pointerover" and "pointerout" events', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerover'));
|
||||
expect(onHoverChange).toHaveBeenCalledTimes(1);
|
||||
expect(onHoverChange).toHaveBeenCalledWith(true);
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerout'));
|
||||
expect(onHoverChange).toHaveBeenCalledTimes(2);
|
||||
expect(onHoverChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
const mouseOverEvent = document.createEvent('Event');
|
||||
mouseOverEvent.initEvent('mouseover', true, true);
|
||||
divRef.current.dispatchEvent(mouseOverEvent);
|
||||
// No PointerEvent fallbacks
|
||||
it('is called after "mouseover" and "mouseout" events', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('mouseover'));
|
||||
expect(onHoverChange).toHaveBeenCalledTimes(1);
|
||||
expect(onHoverChange).toHaveBeenCalledWith(true);
|
||||
ref.current.dispatchEvent(createPointerEvent('mouseout'));
|
||||
expect(onHoverChange).toHaveBeenCalledTimes(2);
|
||||
expect(onHoverChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
const mouseOutEvent = document.createEvent('Event');
|
||||
mouseOutEvent.initEvent('mouseout', true, true);
|
||||
divRef.current.dispatchEvent(mouseOutEvent);
|
||||
describe('onHoverEnd', () => {
|
||||
let onHoverEnd, ref;
|
||||
|
||||
expect(events).toEqual(['onHoverStart', 'onHoverEnd']);
|
||||
beforeEach(() => {
|
||||
onHoverEnd = jest.fn();
|
||||
ref = React.createRef();
|
||||
const element = (
|
||||
<Hover onHoverEnd={onHoverEnd}>
|
||||
<div ref={ref} />
|
||||
</Hover>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
});
|
||||
|
||||
it('is called after "pointerout" event', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerover'));
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerout'));
|
||||
expect(onHoverEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is not called if "pointerover" pointerType is touch', () => {
|
||||
const event = createPointerEvent('pointerover');
|
||||
event.pointerType = 'touch';
|
||||
ref.current.dispatchEvent(event);
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerout'));
|
||||
expect(onHoverEnd).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('ignores browser emulated "mouseout" event', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerover'));
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerout'));
|
||||
ref.current.dispatchEvent(createPointerEvent('mouseout'));
|
||||
expect(onHoverEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is called after "pointercancel" event', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerover'));
|
||||
ref.current.dispatchEvent(createPointerEvent('pointercancel'));
|
||||
expect(onHoverEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is not called again after "pointercancel" event if it follows "pointerout"', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerover'));
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerout'));
|
||||
ref.current.dispatchEvent(createPointerEvent('pointercancel'));
|
||||
expect(onHoverEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// No PointerEvent fallbacks
|
||||
it('is called after "mouseout" event', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('mouseover'));
|
||||
ref.current.dispatchEvent(createPointerEvent('mouseout'));
|
||||
expect(onHoverEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('is not called after "touchend"', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('touchstart'));
|
||||
ref.current.dispatchEvent(createPointerEvent('touchend'));
|
||||
ref.current.dispatchEvent(createPointerEvent('mouseout'));
|
||||
expect(onHoverEnd).not.toBeCalled();
|
||||
});
|
||||
|
||||
// TODO: complete delayHoverStart tests
|
||||
// describe('delayHoverEnd', () => {});
|
||||
});
|
||||
|
||||
it('expect displayName to show up for event component', () => {
|
||||
expect(Hover.displayName).toBe('Hover');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let Press;
|
||||
|
||||
const DEFAULT_LONG_PRESS_DELAY = 1000;
|
||||
const DEFAULT_LONG_PRESS_DELAY = 500;
|
||||
|
||||
const createPointerEvent = type => {
|
||||
const event = document.createEvent('Event');
|
||||
@@ -69,12 +69,31 @@ describe('Event responder: Press', () => {
|
||||
expect(onPressStart).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('ignores emulated "mousedown" event', () => {
|
||||
it('ignores browser emulated "mousedown" event', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerdown'));
|
||||
ref.current.dispatchEvent(createPointerEvent('mousedown'));
|
||||
expect(onPressStart).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is called once after "keydown" events for Enter', () => {
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Enter'}));
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Enter'}));
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Enter'}));
|
||||
expect(onPressStart).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is called once after "keydown" events for Spacebar', () => {
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: ' '}));
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: ' '}));
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: ' '}));
|
||||
expect(onPressStart).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is not called after "keydown" for other keys', () => {
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'a'}));
|
||||
expect(onPressStart).not.toBeCalled();
|
||||
});
|
||||
|
||||
// No PointerEvent fallbacks
|
||||
it('is called after "mousedown" event', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('mousedown'));
|
||||
@@ -109,20 +128,37 @@ describe('Event responder: Press', () => {
|
||||
expect(onPressEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('ignores emulated "mouseup" event', () => {
|
||||
it('ignores browser emulated "mouseup" event', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('touchstart'));
|
||||
ref.current.dispatchEvent(createPointerEvent('touchend'));
|
||||
ref.current.dispatchEvent(createPointerEvent('mouseup'));
|
||||
expect(onPressEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is called after "keyup" event for Enter', () => {
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Enter'}));
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keyup', {key: 'Enter'}));
|
||||
expect(onPressEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is called after "keyup" event for Spacebar', () => {
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: ' '}));
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keyup', {key: ' '}));
|
||||
expect(onPressEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is not called after "keyup" event for other keys', () => {
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Enter'}));
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keyup', {key: 'a'}));
|
||||
expect(onPressEnd).not.toBeCalled();
|
||||
});
|
||||
|
||||
// No PointerEvent fallbacks
|
||||
it('is called after "mouseup" event', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('mousedown'));
|
||||
ref.current.dispatchEvent(createPointerEvent('mouseup'));
|
||||
expect(onPressEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is called after "touchend" event', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('touchstart'));
|
||||
ref.current.dispatchEvent(createPointerEvent('touchend'));
|
||||
@@ -155,6 +191,33 @@ describe('Event responder: Press', () => {
|
||||
expect(onPressChange).toHaveBeenCalledTimes(2);
|
||||
expect(onPressChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('is called after valid "keydown" and "keyup" events', () => {
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Enter'}));
|
||||
expect(onPressChange).toHaveBeenCalledTimes(1);
|
||||
expect(onPressChange).toHaveBeenCalledWith(true);
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keyup', {key: 'Enter'}));
|
||||
expect(onPressChange).toHaveBeenCalledTimes(2);
|
||||
expect(onPressChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
// No PointerEvent fallbacks
|
||||
it('is called after "mousedown" and "mouseup" events', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('mousedown'));
|
||||
expect(onPressChange).toHaveBeenCalledTimes(1);
|
||||
expect(onPressChange).toHaveBeenCalledWith(true);
|
||||
ref.current.dispatchEvent(createPointerEvent('mouseup'));
|
||||
expect(onPressChange).toHaveBeenCalledTimes(2);
|
||||
expect(onPressChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
it('is called after "touchstart" and "touchend" events', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('touchstart'));
|
||||
expect(onPressChange).toHaveBeenCalledTimes(1);
|
||||
expect(onPressChange).toHaveBeenCalledWith(true);
|
||||
ref.current.dispatchEvent(createPointerEvent('touchend'));
|
||||
expect(onPressChange).toHaveBeenCalledTimes(2);
|
||||
expect(onPressChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onPress', () => {
|
||||
@@ -176,6 +239,20 @@ describe('Event responder: Press', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerup'));
|
||||
expect(onPress).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is called after valid "keyup" event', () => {
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Enter'}));
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keyup', {key: 'Enter'}));
|
||||
expect(onPress).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// No PointerEvent fallbacks
|
||||
// TODO: jsdom missing APIs
|
||||
//it('is called after "touchend" event', () => {
|
||||
//ref.current.dispatchEvent(createPointerEvent('touchstart'));
|
||||
//ref.current.dispatchEvent(createPointerEvent('touchend'));
|
||||
//expect(onPress).toHaveBeenCalledTimes(1);
|
||||
//});
|
||||
});
|
||||
|
||||
describe('onLongPress', () => {
|
||||
@@ -192,7 +269,7 @@ describe('Event responder: Press', () => {
|
||||
ReactDOM.render(element, container);
|
||||
});
|
||||
|
||||
it('is called if press lasts default delay', () => {
|
||||
it('is called if "pointerdown" lasts default delay', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerdown'));
|
||||
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY - 1);
|
||||
expect(onLongPress).not.toBeCalled();
|
||||
@@ -200,7 +277,7 @@ describe('Event responder: Press', () => {
|
||||
expect(onLongPress).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is not called if press is released before delay', () => {
|
||||
it('is not called if "pointerup" is dispatched before delay', () => {
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerdown'));
|
||||
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY - 1);
|
||||
ref.current.dispatchEvent(createPointerEvent('pointerup'));
|
||||
@@ -208,6 +285,22 @@ describe('Event responder: Press', () => {
|
||||
expect(onLongPress).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('is called if valid "keydown" lasts default delay', () => {
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Enter'}));
|
||||
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY - 1);
|
||||
expect(onLongPress).not.toBeCalled();
|
||||
jest.advanceTimersByTime(1);
|
||||
expect(onLongPress).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is not called if valid "keyup" is dispatched before delay', () => {
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Enter'}));
|
||||
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY - 1);
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keyup', {key: 'Enter'}));
|
||||
jest.advanceTimersByTime(1);
|
||||
expect(onLongPress).not.toBeCalled();
|
||||
});
|
||||
|
||||
describe('delayLongPress', () => {
|
||||
it('can be configured', () => {
|
||||
const element = (
|
||||
@@ -339,7 +432,7 @@ describe('Event responder: Press', () => {
|
||||
|
||||
describe('nested responders', () => {
|
||||
it('dispatch events in the correct order', () => {
|
||||
let events = [];
|
||||
const events = [];
|
||||
const ref = React.createRef();
|
||||
const createEventHandler = msg => () => {
|
||||
events.push(msg);
|
||||
@@ -385,13 +478,6 @@ describe('Event responder: Press', () => {
|
||||
'outer: onPressChange',
|
||||
'outer: onPress',
|
||||
]);
|
||||
|
||||
events = [];
|
||||
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Enter'}));
|
||||
// TODO update this test once we have a form of stopPropagation in
|
||||
// the responder system again. This test had to be updated because
|
||||
// we have removed stopPropagation() from synthetic events.
|
||||
expect(events).toEqual(['keydown', 'inner: onPress', 'outer: onPress']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user