Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React events: add delay props to Press module #15340

Merged
merged 3 commits into from
Apr 6, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 117 additions & 40 deletions packages/react-events/README.md
Original file line number Diff line number Diff line change
@@ -3,14 +3,31 @@
*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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant space 😃

high-level events for applications.


## Focus

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) => (
<Focus
onBlur={props.onBlur}
onFocus={props.onFocus}
>
<textarea></textarea>
</Focus>
);
```

```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 (
<Hover onHoverChange={setHovered}>
<a
{...props}
href={props.href}
style={{
...props.style,
textDecoration: hovered ? 'underline': 'none'
}}
/>
</Hover>
);
);
```

```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 (
<Press
onPress={props.onPress}
onPressChange={setPressed}
onLongPress={props.onLongPress}
>
<div
{...props}
role="button"
tabIndex={0}
style={
...buttonStyles,
...(pressed && pressedStyles)
}}
/>
</Press>
);
);
```

```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
36 changes: 22 additions & 14 deletions packages/react-events/src/Hover.js
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
163 changes: 112 additions & 51 deletions packages/react-events/src/Press.js
Original file line number Diff line number Diff line change
@@ -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,45 +121,93 @@ 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,
state: PressState,
): 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,
};
33 changes: 18 additions & 15 deletions packages/react-events/src/__tests__/Hover-test.internal.js
Original file line number Diff line number Diff line change
@@ -100,6 +100,24 @@ describe('Hover event responder', () => {
expect(onHoverStart).toHaveBeenCalledTimes(1);
});

it('is reset if "pointerout" is dispatched during a delay', () => {
const element = (
<Hover delayHoverStart={500} onHoverStart={onHoverStart}>
<div ref={ref} />
</Hover>
);
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 = (
<Hover delayHoverStart={0} onHoverStart={onHoverStart}>
@@ -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 = (
<Hover delayHoverStart={500} onHoverStart={onHoverStart}>
<div ref={ref} />
</Hover>
);
ReactDOM.render(element, container);

ref.current.dispatchEvent(createPointerEvent('pointerover'));
jest.advanceTimersByTime(499);
ref.current.dispatchEvent(createPointerEvent('pointerout'));
jest.advanceTimersByTime(1);
expect(onHoverStart).not.toBeCalled();
});
});
});

280 changes: 268 additions & 12 deletions packages/react-events/src/__tests__/Press-test.internal.js
Original file line number Diff line number Diff line change
@@ -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 = (
<Press delayPressStart={2000} onPressStart={onPressStart}>
<div ref={ref} />
</Press>
);
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 = (
<Press delayPressStart={2000} onPressStart={onPressStart}>
<div ref={ref} />
</Press>
);
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 = (
<Press delayPressStart={0} onPressStart={onPressStart}>
<div ref={ref} />
</Press>
);
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 = (
<Press delayPressEnd={2000} onPressStart={onPressStart}>
<div ref={ref} />
</Press>
);
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 = (
<Press delayPressEnd={2000} onPressEnd={onPressEnd}>
<div ref={ref} />
</Press>
);
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 = (
<Press delayPressEnd={500} onPressEnd={onPressEnd}>
<div ref={ref} />
</Press>
);
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 = (
<Press delayPressEnd={0} onPressEnd={onPressEnd}>
<div ref={ref} />
</Press>
);
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 = (
<Press delayPressStart={500} onPressChange={onPressChange}>
<div ref={ref} />
</Press>
);
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 = (
<Press delayPressStart={500} onPressChange={onPressChange}>
<div ref={ref} />
</Press>
);
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 = (
<Press delayPressEnd={500} onPressChange={onPressChange}>
<div ref={ref} />
</Press>
);
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 = (
<Press delayPressEnd={500} onPress={onPress}>
<div ref={ref} />
</Press>
);
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 = (
<Press delayPressEnd={500} onLongPressChange={onLongPressChange}>
<div ref={ref} />
</Press>
);
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 = (
<Press
delayPressStart={250}
delayPressEnd={250}
onLongPress={createEventHandler('onLongPress')}
onLongPressChange={createEventHandler('onLongPressChange')}
onPress={createEventHandler('onPress')}
onPressChange={createEventHandler('onPressChange')}
onPressStart={createEventHandler('onPressStart')}
onPressEnd={createEventHandler('onPressEnd')}>
<div ref={ref} />
</Press>
);

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 = [];