diff --git a/packages/react-events/README.md b/packages/react-events/README.md
index b90e166156412..9173084e54a5c 100644
--- a/packages/react-events/README.md
+++ b/packages/react-events/README.md
@@ -3,6 +3,10 @@
*This package is experimental. It is intended for use with the experimental React
events API that is not available in open source builds.*
+Event components do not render a host node. They listen to native browser events
+dispatched on the host node of their child and transform those events into
+high-level events for applications.
+
## Focus
@@ -10,7 +14,20 @@ The `Focus` module responds to focus and blur events on the element it wraps.
Focus events are dispatched for `mouse`, `pen`, `touch`, and `keyboard`
pointer types.
+```js
+// Example
+const TextField = (props) => (
+
+
+
+);
```
+
+```js
+// Types
type FocusEvent = {}
```
@@ -38,69 +55,124 @@ The `Hover` module responds to hover events on the element it wraps. Hover
events are only dispatched for `mouse` pointer types. Hover begins when the
pointer enters the element's bounds and ends when the pointer leaves.
+```js
+// Example
+const Link = (props) => (
+ const [ hovered, setHovered ] = useState(false);
+ return (
+
+
+
+ );
+);
```
+
+```js
+// Types
type HoverEvent = {}
```
-### disabled: boolean
+### delayHoverEnd: number
-Disables all `Hover` events.
+The duration of the delay between when hover ends and when `onHoverEnd` is
+called.
-### onHoverStart: (e: HoverEvent) => void
+### delayHoverStart: number
-Called once the element is hovered. It will not be called if the pointer leaves
-the element before the `delayHoverStart` threshold is exceeded. And it will not
-be called more than once before `onHoverEnd` is called.
+The duration of the delay between when hover starts and when `onHoverStart` is
+called.
-### onHoverEnd: (e: HoverEvent) => void
+### disabled: boolean
-Called once the element is no longer hovered. It will be cancelled if the
-pointer leaves the element before the `delayHoverStart` threshold is exceeded.
+Disables all `Hover` events.
### onHoverChange: boolean => void
Called when the element changes hover state (i.e., after `onHoverStart` and
`onHoverEnd`).
-### delayHoverStart: number
+### onHoverEnd: (e: HoverEvent) => void
-The duration of the delay between when hover starts and when `onHoverStart` is
-called.
+Called once the element is no longer hovered. It will be cancelled if the
+pointer leaves the element before the `delayHoverStart` threshold is exceeded.
-### delayHoverEnd: number
+### onHoverStart: (e: HoverEvent) => void
-The duration of the delay between when hover ends and when `onHoverEnd` is
-called.
+Called once the element is hovered. It will not be called if the pointer leaves
+the element before the `delayHoverStart` threshold is exceeded. And it will not
+be called more than once before `onHoverEnd` is called.
## Press
The `Press` module responds to press events on the element it wraps. Press
events are dispatched for `mouse`, `pen`, `touch`, and `keyboard` pointer types.
-
+Press events are only dispatched for keyboards when pressing the Enter or
+Spacebar keys. If neither `onPress` nor `onLongPress` are called, this signifies
+that the press ended outside of the element hit bounds (i.e., the user aborted
+the press).
+
+```js
+// Example
+const Button = (props) => (
+ const [ pressed, setPressed ] = useState(false);
+ return (
+
+
+
+ );
+);
```
+
+```js
+// Types
type PressEvent = {}
+
+type PressOffset = {
+ top: number,
+ right: number,
+ bottom: number,
+ right: number
+};
```
-### disabled: boolean
+### delayLongPress: number = 500ms
-Disables all `Press` events.
+The duration of a press before `onLongPress` and `onLongPressChange` are called.
-### onPressStart: (e: PressEvent) => void
+### delayPressEnd: number
-Called once the element is pressed down. If the press is released before the
-`delayPressStart` threshold is exceeded then the delay is cut short and
-`onPressStart` is called immediately.
+The duration of the delay between when the press ends and when `onPressEnd` is
+called.
-### onPressEnd: (e: PressEvent) => void
+### delayPressStart: number
-Called once the element is no longer pressed. It will be cancelled if the press
-starts again before the `delayPressEnd` threshold is exceeded.
+The duration of a delay between when the press starts and when `onPressStart` is
+called. This delay is cut short (and `onPressStart` is called) if the press is
+released before the threshold is exceeded.
-### onPressChange: boolean => void
+### disabled: boolean
-Called when the element changes press state (i.e., after `onPressStart` and
-`onPressEnd`).
+Disables all `Press` events.
### onLongPress: (e: PressEvent) => void
@@ -117,25 +189,30 @@ Determines whether calling `onPress` should be cancelled if `onLongPress` or
### onPress: (e: PressEvent) => void
-Called after `onPressEnd` only if `onLongPressShouldCancelPress` returns
-`false`.
+Called immediately after a press is released, unless either 1) the press is
+released outside the hit bounds of the element (accounting for
+`pressRetentionOffset` and `TouchHitTarget`), or 2) the press was a long press,
+and `onLongPress` or `onLongPressChange` props are provided, and
+`onLongPressCancelsPress()` is `true`.
-### delayPressStart: number
+### onPressChange: boolean => void
-The duration of a delay between when the press starts and when `onPressStart` is
-called. This delay is cut short if the press ends released before the threshold
-is exceeded.
+Called when the element changes press state (i.e., after `onPressStart` and
+`onPressEnd`).
-### delayPressEnd: number
+### onPressEnd: (e: PressEvent) => void
-The duration of the delay between when the press ends and when `onPressEnd` is
-called.
+Called once the element is no longer pressed. If the press starts again before
+the `delayPressEnd` threshold is exceeded then the delay is reset to prevent
+`onPressEnd` being called during a press.
-### delayLongPress: number = 500ms
+### onPressStart: (e: PressEvent) => void
-The duration of a press before `onLongPress` and `onLongPressChange` are called.
+Called once the element is pressed down. If the press is released before the
+`delayPressStart` threshold is exceeded then the delay is cut short and
+`onPressStart` is called immediately.
-### pressRententionOffset: { top: number, right: number, bottom: number, right: number }
+### pressRententionOffset: PressOffset
Defines how far the pointer (while held down) may move outside the bounds of the
element before it is deactivated. Once deactivated, the pointer (still held
diff --git a/packages/react-events/src/Hover.js b/packages/react-events/src/Hover.js
index 0668880268476..bc438d9c74ad8 100644
--- a/packages/react-events/src/Hover.js
+++ b/packages/react-events/src/Hover.js
@@ -36,8 +36,8 @@ type HoverEvent = {|
type: HoverEventType,
|};
-// const DEFAULT_HOVER_END_DELAY_MS = 0;
-// const DEFAULT_HOVER_START_DELAY_MS = 0;
+const DEFAULT_HOVER_END_DELAY_MS = 0;
+const DEFAULT_HOVER_START_DELAY_MS = 0;
const targetEventTypes = [
'pointerover',
@@ -98,7 +98,7 @@ function dispatchHoverStartEvents(
state.hoverEndTimeout = null;
}
- const dispatch = () => {
+ const activate = () => {
state.isActiveHovered = true;
if (props.onHoverStart) {
@@ -115,14 +115,18 @@ function dispatchHoverStartEvents(
};
if (!state.isActiveHovered) {
- const delay = calculateDelayMS(props.delayHoverStart, 0, 0);
- if (delay > 0) {
+ const delayHoverStart = calculateDelayMS(
+ props.delayHoverStart,
+ 0,
+ DEFAULT_HOVER_START_DELAY_MS,
+ );
+ if (delayHoverStart > 0) {
state.hoverStartTimeout = context.setTimeout(() => {
state.hoverStartTimeout = null;
- dispatch();
- }, delay);
+ activate();
+ }, delayHoverStart);
} else {
- dispatch();
+ activate();
}
}
}
@@ -145,7 +149,7 @@ function dispatchHoverEndEvents(
state.hoverStartTimeout = null;
}
- const dispatch = () => {
+ const deactivate = () => {
state.isActiveHovered = false;
if (props.onHoverEnd) {
@@ -162,13 +166,17 @@ function dispatchHoverEndEvents(
};
if (state.isActiveHovered) {
- const delay = calculateDelayMS(props.delayHoverEnd, 0, 0);
- if (delay > 0) {
+ const delayHoverEnd = calculateDelayMS(
+ props.delayHoverEnd,
+ 0,
+ DEFAULT_HOVER_END_DELAY_MS,
+ );
+ if (delayHoverEnd > 0) {
state.hoverEndTimeout = context.setTimeout(() => {
- dispatch();
- }, delay);
+ deactivate();
+ }, delayHoverEnd);
} else {
- dispatch();
+ deactivate();
}
}
}
diff --git a/packages/react-events/src/Press.js b/packages/react-events/src/Press.js
index 00db07c72c423..5aacbbb44b018 100644
--- a/packages/react-events/src/Press.js
+++ b/packages/react-events/src/Press.js
@@ -27,11 +27,15 @@ type PressProps = {
type PressState = {
defaultPrevented: boolean,
+ isActivePressed: boolean,
+ isActivePressStart: boolean,
isAnchorTouched: boolean,
isLongPressed: boolean,
isPressed: boolean,
longPressTimeout: null | TimeoutID,
pressTarget: null | Element | Document,
+ pressEndTimeout: null | TimeoutID,
+ pressStartTimeout: null | TimeoutID,
shouldSkipMouseAfterTouch: boolean,
};
@@ -49,9 +53,8 @@ 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_PRESS_END_DELAY_MS = 0;
+const DEFAULT_PRESS_START_DELAY_MS = 0;
const DEFAULT_LONG_PRESS_DELAY_MS = 500;
const targetEventTypes = [
@@ -102,7 +105,7 @@ function dispatchPressChangeEvent(
state: PressState,
): void {
const listener = () => {
- props.onPressChange(state.isPressed);
+ props.onPressChange(state.isActivePressed);
};
dispatchEvent(context, state, 'presschange', listener);
}
@@ -118,6 +121,34 @@ function dispatchLongPressChangeEvent(
dispatchEvent(context, state, 'longpresschange', listener);
}
+function activate(context, props, state) {
+ const wasActivePressed = state.isActivePressed;
+ state.isActivePressed = true;
+
+ if (props.onPressStart) {
+ dispatchEvent(context, state, 'pressstart', props.onPressStart);
+ }
+ if (!wasActivePressed && props.onPressChange) {
+ dispatchPressChangeEvent(context, props, state);
+ }
+}
+
+function deactivate(context, props, state) {
+ const wasLongPressed = state.isLongPressed;
+ state.isActivePressed = false;
+ state.isLongPressed = false;
+
+ if (props.onPressEnd) {
+ dispatchEvent(context, state, 'pressend', props.onPressEnd);
+ }
+ if (props.onPressChange) {
+ dispatchPressChangeEvent(context, props, state);
+ }
+ if (wasLongPressed && props.onLongPressChange) {
+ dispatchLongPressChangeEvent(context, props, state);
+ }
+}
+
function dispatchPressStartEvents(
context: ResponderContext,
props: PressProps,
@@ -125,38 +156,58 @@ function dispatchPressStartEvents(
): void {
state.isPressed = true;
- if (props.onPressStart) {
- dispatchEvent(context, state, 'pressstart', props.onPressStart);
+ if (state.pressEndTimeout !== null) {
+ clearTimeout(state.pressEndTimeout);
+ state.pressEndTimeout = null;
}
- if (props.onPressChange) {
- dispatchPressChangeEvent(context, props, state);
- }
- if ((props.onLongPress || props.onLongPressChange) && !state.isLongPressed) {
- const delayLongPress = calculateDelayMS(
- props.delayLongPress,
- 10,
- DEFAULT_LONG_PRESS_DELAY_MS,
- );
- state.longPressTimeout = context.setTimeout(() => {
- state.isLongPressed = true;
- state.longPressTimeout = null;
-
- if (props.onLongPress) {
- const listener = e => {
- props.onLongPress(e);
- // TODO address this again at some point
- // if (e.nativeEvent.defaultPrevented) {
- // state.defaultPrevented = true;
- // }
- };
- dispatchEvent(context, state, 'longpress', listener);
- }
+ const dispatch = () => {
+ state.isActivePressStart = true;
+ activate(context, props, state);
+
+ if (
+ (props.onLongPress || props.onLongPressChange) &&
+ !state.isLongPressed
+ ) {
+ const delayLongPress = calculateDelayMS(
+ props.delayLongPress,
+ 10,
+ DEFAULT_LONG_PRESS_DELAY_MS,
+ );
+ state.longPressTimeout = context.setTimeout(() => {
+ state.isLongPressed = true;
+ state.longPressTimeout = null;
+ if (props.onLongPress) {
+ const listener = e => {
+ props.onLongPress(e);
+ // TODO address this again at some point
+ // if (e.nativeEvent.defaultPrevented) {
+ // state.defaultPrevented = true;
+ // }
+ };
+ dispatchEvent(context, state, 'longpress', listener);
+ }
+ if (props.onLongPressChange) {
+ dispatchLongPressChangeEvent(context, props, state);
+ }
+ }, delayLongPress);
+ }
+ };
- if (props.onLongPressChange) {
- dispatchLongPressChangeEvent(context, props, state);
- }
- }, delayLongPress);
+ if (!state.isActivePressStart) {
+ const delayPressStart = calculateDelayMS(
+ props.delayPressStart,
+ 0,
+ DEFAULT_PRESS_START_DELAY_MS,
+ );
+ if (delayPressStart > 0) {
+ state.pressStartTimeout = context.setTimeout(() => {
+ state.pressStartTimeout = null;
+ dispatch();
+ }, delayPressStart);
+ } else {
+ dispatch();
+ }
}
}
@@ -165,25 +216,36 @@ function dispatchPressEndEvents(
props: PressProps,
state: PressState,
): void {
+ const wasActivePressStart = state.isActivePressStart;
+
+ state.isActivePressStart = false;
+ state.isPressed = false;
+
if (state.longPressTimeout !== null) {
clearTimeout(state.longPressTimeout);
state.longPressTimeout = null;
}
- if (props.onPressEnd) {
- dispatchEvent(context, state, 'pressend', props.onPressEnd);
- }
- if (state.isPressed) {
- state.isPressed = false;
- if (props.onPressChange) {
- dispatchPressChangeEvent(context, props, state);
- }
+ if (!wasActivePressStart && state.pressStartTimeout !== null) {
+ clearTimeout(state.pressStartTimeout);
+ state.pressStartTimeout = null;
+ // if we haven't yet activated (due to delays), activate now
+ activate(context, props, state);
}
- if (state.isLongPressed) {
- state.isLongPressed = false;
- if (props.onLongPressChange) {
- dispatchLongPressChangeEvent(context, props, state);
+ if (state.isActivePressed) {
+ const delayPressEnd = calculateDelayMS(
+ props.delayPressEnd,
+ 0,
+ DEFAULT_PRESS_END_DELAY_MS,
+ );
+ if (delayPressEnd > 0) {
+ state.pressEndTimeout = context.setTimeout(() => {
+ state.pressEndTimeout = null;
+ deactivate(context, props, state);
+ }, delayPressEnd);
+ } else {
+ deactivate(context, props, state);
}
}
}
@@ -208,13 +270,8 @@ function unmountResponder(
state: PressState,
): void {
if (state.isPressed) {
- state.isPressed = false;
- context.removeRootEventTypes(rootEventTypes);
dispatchPressEndEvents(context, props, state);
- if (state.longPressTimeout !== null) {
- clearTimeout(state.longPressTimeout);
- state.longPressTimeout = null;
- }
+ context.removeRootEventTypes(rootEventTypes);
}
}
@@ -223,10 +280,14 @@ const PressResponder = {
createInitialState(): PressState {
return {
defaultPrevented: false,
+ isActivePressed: false,
+ isActivePressStart: false,
isAnchorTouched: false,
isLongPressed: false,
isPressed: false,
longPressTimeout: null,
+ pressEndTimeout: null,
+ pressStartTimeout: null,
pressTarget: null,
shouldSkipMouseAfterTouch: false,
};
diff --git a/packages/react-events/src/__tests__/Hover-test.internal.js b/packages/react-events/src/__tests__/Hover-test.internal.js
index c7cb373a1626b..0d29036f988f3 100644
--- a/packages/react-events/src/__tests__/Hover-test.internal.js
+++ b/packages/react-events/src/__tests__/Hover-test.internal.js
@@ -100,6 +100,24 @@ describe('Hover event responder', () => {
expect(onHoverStart).toHaveBeenCalledTimes(1);
});
+ it('is reset if "pointerout" is dispatched during a delay', () => {
+ const element = (
+
+
+
+ );
+ ReactDOM.render(element, container);
+
+ ref.current.dispatchEvent(createPointerEvent('pointerover'));
+ jest.advanceTimersByTime(499);
+ ref.current.dispatchEvent(createPointerEvent('pointerout'));
+ jest.advanceTimersByTime(1);
+ expect(onHoverStart).not.toBeCalled();
+ ref.current.dispatchEvent(createPointerEvent('pointerover'));
+ jest.runAllTimers();
+ expect(onHoverStart).toHaveBeenCalledTimes(1);
+ });
+
it('onHoverStart is called synchronously if delay is 0ms', () => {
const element = (
@@ -132,21 +150,6 @@ describe('Hover event responder', () => {
jest.runAllTimers();
expect(onHoverStart).toHaveBeenCalledTimes(1);
});
-
- it('onHoverStart is not called if "pointerout" is dispatched during a delay', () => {
- const element = (
-
-
-
- );
- ReactDOM.render(element, container);
-
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
- jest.advanceTimersByTime(499);
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
- jest.advanceTimersByTime(1);
- expect(onHoverStart).not.toBeCalled();
- });
});
});
diff --git a/packages/react-events/src/__tests__/Press-test.internal.js b/packages/react-events/src/__tests__/Press-test.internal.js
index 504f7e49d8d85..2c83bcaafd338 100644
--- a/packages/react-events/src/__tests__/Press-test.internal.js
+++ b/packages/react-events/src/__tests__/Press-test.internal.js
@@ -104,8 +104,70 @@ describe('Event responder: Press', () => {
expect(onPressStart).toHaveBeenCalledTimes(1);
});
- // TODO: complete delayPressStart tests
- // describe('delayPressStart', () => {});
+ describe('delayPressStart', () => {
+ it('can be configured', () => {
+ const element = (
+
+
+
+ );
+ ReactDOM.render(element, container);
+
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ jest.advanceTimersByTime(1999);
+ expect(onPressStart).not.toBeCalled();
+ jest.advanceTimersByTime(1);
+ expect(onPressStart).toHaveBeenCalledTimes(1);
+ });
+
+ it('is cut short if the press is released during a delay', () => {
+ const element = (
+
+
+
+ );
+ ReactDOM.render(element, container);
+
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ jest.advanceTimersByTime(499);
+ expect(onPressStart).toHaveBeenCalledTimes(0);
+ ref.current.dispatchEvent(createPointerEvent('pointerup'));
+ expect(onPressStart).toHaveBeenCalledTimes(1);
+ jest.runAllTimers();
+ expect(onPressStart).toHaveBeenCalledTimes(1);
+ });
+
+ it('onPressStart is called synchronously if delay is 0ms', () => {
+ const element = (
+
+
+
+ );
+ ReactDOM.render(element, container);
+
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ expect(onPressStart).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('delayPressEnd', () => {
+ it('onPressStart called each time a press is initiated', () => {
+ // This test makes sure that onPressStart is called each time a press
+ // starts, even if a delayPressEnd is delaying the deactivation of the
+ // previous press.
+ const element = (
+
+
+
+ );
+ ReactDOM.render(element, container);
+
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ ref.current.dispatchEvent(createPointerEvent('pointerup'));
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ expect(onPressStart).toHaveBeenCalledTimes(2);
+ });
+ });
});
describe('onPressEnd', () => {
@@ -165,8 +227,55 @@ describe('Event responder: Press', () => {
expect(onPressEnd).toHaveBeenCalledTimes(1);
});
- // TODO: complete delayPressStart tests
- // describe('delayPressStart', () => {});
+ describe('delayPressEnd', () => {
+ it('can be configured', () => {
+ const element = (
+
+
+
+ );
+ ReactDOM.render(element, container);
+
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ ref.current.dispatchEvent(createPointerEvent('pointerup'));
+ jest.advanceTimersByTime(1999);
+ expect(onPressEnd).not.toBeCalled();
+ jest.advanceTimersByTime(1);
+ expect(onPressEnd).toHaveBeenCalledTimes(1);
+ });
+
+ it('is reset if "pointerdown" is dispatched during a delay', () => {
+ const element = (
+
+
+
+ );
+ ReactDOM.render(element, container);
+
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ ref.current.dispatchEvent(createPointerEvent('pointerup'));
+ jest.advanceTimersByTime(499);
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ jest.advanceTimersByTime(1);
+ expect(onPressEnd).not.toBeCalled();
+ ref.current.dispatchEvent(createPointerEvent('pointerup'));
+ jest.runAllTimers();
+ expect(onPressEnd).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ it('onPressEnd is called synchronously if delay is 0ms', () => {
+ const element = (
+
+
+
+ );
+ ReactDOM.render(element, container);
+
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ ref.current.dispatchEvent(createPointerEvent('pointerup'));
+ expect(onPressEnd).toHaveBeenCalledTimes(1);
+ });
});
describe('onPressChange', () => {
@@ -201,6 +310,55 @@ describe('Event responder: Press', () => {
expect(onPressChange).toHaveBeenCalledWith(false);
});
+ it('is called after delayed onPressStart', () => {
+ const element = (
+
+
+
+ );
+ ReactDOM.render(element, container);
+
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ jest.advanceTimersByTime(499);
+ expect(onPressChange).not.toBeCalled();
+ jest.advanceTimersByTime(1);
+ expect(onPressChange).toHaveBeenCalledTimes(1);
+ expect(onPressChange).toHaveBeenCalledWith(true);
+ });
+
+ it('is called after delayPressStart is cut short', () => {
+ const element = (
+
+
+
+ );
+ ReactDOM.render(element, container);
+
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ jest.advanceTimersByTime(100);
+ ref.current.dispatchEvent(createPointerEvent('pointerup'));
+ expect(onPressChange).toHaveBeenCalledTimes(2);
+ });
+
+ it('is called after delayed onPressEnd', () => {
+ const element = (
+
+
+
+ );
+ ReactDOM.render(element, container);
+
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ expect(onPressChange).toHaveBeenCalledTimes(1);
+ expect(onPressChange).toHaveBeenCalledWith(true);
+ ref.current.dispatchEvent(createPointerEvent('pointerup'));
+ jest.advanceTimersByTime(499);
+ expect(onPressChange).toHaveBeenCalledTimes(1);
+ jest.advanceTimersByTime(1);
+ expect(onPressChange).toHaveBeenCalledTimes(2);
+ expect(onPressChange).toHaveBeenCalledWith(false);
+ });
+
// No PointerEvent fallbacks
it('is called after "mousedown" and "mouseup" events', () => {
ref.current.dispatchEvent(createPointerEvent('mousedown'));
@@ -246,13 +404,26 @@ describe('Event responder: Press', () => {
expect(onPress).toHaveBeenCalledTimes(1);
});
+ it('is always called immediately after press is released', () => {
+ const element = (
+
+
+
+ );
+ ReactDOM.render(element, container);
+
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ ref.current.dispatchEvent(createPointerEvent('pointerup'));
+ 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);
- //});
+ // it('is called after "touchend" event', () => {
+ // ref.current.dispatchEvent(createPointerEvent('touchstart'));
+ // ref.current.dispatchEvent(createPointerEvent('touchend'));
+ // expect(onPress).toHaveBeenCalledTimes(1);
+ // });
});
describe('onLongPress', () => {
@@ -332,7 +503,6 @@ describe('Event responder: Press', () => {
expect(onLongPress).toHaveBeenCalledTimes(1);
});
- /*
it('compounds with "delayPressStart"', () => {
const delayPressStart = 100;
const element = (
@@ -343,12 +513,13 @@ describe('Event responder: Press', () => {
ReactDOM.render(element, container);
ref.current.dispatchEvent(createPointerEvent('pointerdown'));
- jest.advanceTimersByTime(delayPressStart + DEFAULT_LONG_PRESS_DELAY - 1);
+ jest.advanceTimersByTime(
+ delayPressStart + DEFAULT_LONG_PRESS_DELAY - 1,
+ );
expect(onLongPress).not.toBeCalled();
jest.advanceTimersByTime(1);
expect(onLongPress).toHaveBeenCalledTimes(1);
});
- */
});
});
@@ -371,6 +542,28 @@ describe('Event responder: Press', () => {
expect(onLongPressChange).toHaveBeenCalledTimes(2);
expect(onLongPressChange).toHaveBeenCalledWith(false);
});
+
+ it('is called after delayed onPressEnd', () => {
+ const onLongPressChange = jest.fn();
+ const ref = React.createRef();
+ const element = (
+
+
+
+ );
+ ReactDOM.render(element, container);
+
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY);
+ expect(onLongPressChange).toHaveBeenCalledTimes(1);
+ expect(onLongPressChange).toHaveBeenCalledWith(true);
+ ref.current.dispatchEvent(createPointerEvent('pointerup'));
+ jest.advanceTimersByTime(499);
+ expect(onLongPressChange).toHaveBeenCalledTimes(1);
+ jest.advanceTimersByTime(1);
+ expect(onLongPressChange).toHaveBeenCalledTimes(2);
+ expect(onLongPressChange).toHaveBeenCalledWith(false);
+ });
});
describe('onLongPressShouldCancelPress', () => {
@@ -430,6 +623,69 @@ describe('Event responder: Press', () => {
//});
//});
+ describe('delayed and multiple events', () => {
+ it('dispatches in the correct order', () => {
+ let events;
+ const ref = React.createRef();
+ const createEventHandler = msg => () => {
+ events.push(msg);
+ };
+
+ const element = (
+
+
+
+ );
+
+ ReactDOM.render(element, container);
+
+ // 1
+ events = [];
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ ref.current.dispatchEvent(createPointerEvent('pointerup'));
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ ref.current.dispatchEvent(createPointerEvent('pointerup'));
+ jest.runAllTimers();
+
+ expect(events).toEqual([
+ 'onPressStart',
+ 'onPressChange',
+ 'onPress',
+ 'onPressStart',
+ 'onPress',
+ 'onPressEnd',
+ 'onPressChange',
+ ]);
+
+ // 2
+ events = [];
+ ref.current.dispatchEvent(createPointerEvent('pointerdown'));
+ jest.advanceTimersByTime(250);
+ jest.advanceTimersByTime(500);
+ ref.current.dispatchEvent(createPointerEvent('pointerup'));
+ jest.runAllTimers();
+
+ expect(events).toEqual([
+ 'onPressStart',
+ 'onPressChange',
+ 'onLongPress',
+ 'onLongPressChange',
+ 'onPress',
+ 'onPressEnd',
+ 'onPressChange',
+ 'onLongPressChange',
+ ]);
+ });
+ });
+
describe('nested responders', () => {
it('dispatch events in the correct order', () => {
const events = [];