mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[react-events] DOM event testing library (#16433)
This patch formalizes the mock native events and event sequences used in unit tests.
The `createEventTarget` function returns an object that can be used to dispatch native event sequences on the target without having to manually do so across all the scenarios we need to account for. Unit tests can be written as if we were only working with PointerEvent, but they will dispatch realistic native event sequences based on the execution environment (e.g., is PointerEvent supported?) and pointer type.
```
describe.each(environments)('Suite', (hasPointerEvents) => {
beforeEach(() => {
// setup
});
test.each(pointerTypes)('Test', (pointerType) => {
const target = createEventTarget(node);
target.pointerdown({pointerType});
expect(callback).toBeCalled();
});
});
```
Every native event that is dispatched now includes a complete object by default. The properties of the events can be customized. Properties that shouldn't be relied on in responder implementations are excluded from the mock native events to ensure tests will fail. Equivalent properties are normalized across different event types, e.g., 'pointerId' is converted to 'identifier' before a TouchEvent is dispatched.
This commit is contained in:
+2
@@ -520,6 +520,7 @@ const pressResponderImpl = {
|
||||
const isPressed = state.isPressed;
|
||||
|
||||
handleStopPropagation(props, context, nativeEvent);
|
||||
|
||||
switch (type) {
|
||||
// START
|
||||
case 'pointerdown':
|
||||
@@ -632,6 +633,7 @@ const pressResponderImpl = {
|
||||
const previousPointerType = state.pointerType;
|
||||
|
||||
handleStopPropagation(props, context, nativeEvent);
|
||||
|
||||
switch (type) {
|
||||
// MOVE
|
||||
case 'pointermove':
|
||||
|
||||
+18
-8
@@ -62,14 +62,16 @@ type ScrollEvent = {|
|
||||
y: null | number,
|
||||
|};
|
||||
|
||||
const targetEventTypes = [
|
||||
'scroll',
|
||||
'pointerdown',
|
||||
'touchstart',
|
||||
'keyup',
|
||||
'wheel',
|
||||
];
|
||||
const rootEventTypes = ['touchcancel', 'touchend'];
|
||||
const hasPointerEvents =
|
||||
typeof window !== 'undefined' && window.PointerEvent !== undefined;
|
||||
|
||||
const targetEventTypes = hasPointerEvents
|
||||
? ['scroll', 'pointerdown', 'keyup', 'wheel']
|
||||
: ['scroll', 'mousedown', 'touchstart', 'keyup', 'wheel'];
|
||||
|
||||
const rootEventTypes = hasPointerEvents
|
||||
? ['pointercancel', 'pointerup']
|
||||
: ['touchcancel', 'touchend'];
|
||||
|
||||
function isFunction(obj): boolean {
|
||||
return typeof obj === 'function';
|
||||
@@ -237,17 +239,23 @@ const scrollResponderImpl = {
|
||||
state.pointerType = pointerType;
|
||||
break;
|
||||
}
|
||||
case 'mousedown':
|
||||
case 'wheel': {
|
||||
state.pointerType = 'mouse';
|
||||
break;
|
||||
}
|
||||
case 'pointerdown': {
|
||||
state.pointerType = pointerType;
|
||||
if (pointerType === 'touch' && !state.isTouching) {
|
||||
state.isTouching = true;
|
||||
context.addRootEventTypes(rootEventTypes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'touchstart': {
|
||||
if (!state.isTouching) {
|
||||
state.isTouching = true;
|
||||
state.pointerType = 'touch';
|
||||
context.addRootEventTypes(rootEventTypes);
|
||||
}
|
||||
}
|
||||
@@ -262,6 +270,8 @@ const scrollResponderImpl = {
|
||||
const {type} = event;
|
||||
|
||||
switch (type) {
|
||||
case 'pointercancel':
|
||||
case 'pointerup':
|
||||
case 'touchcancel':
|
||||
case 'touchend': {
|
||||
if (state.isTouching) {
|
||||
|
||||
@@ -9,13 +9,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
dispatchLongPressContextMenu,
|
||||
dispatchRightClickContextMenu,
|
||||
dispatchModifiedClickContextMenu,
|
||||
platform,
|
||||
setPointerEvent,
|
||||
} from '../test-utils';
|
||||
import {createEventTarget, platform, setPointerEvent} from '../testing-library';
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
@@ -62,7 +56,8 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
dispatchRightClickContextMenu(ref.current, {preventDefault});
|
||||
const target = createEventTarget(ref.current);
|
||||
target.contextmenu({preventDefault});
|
||||
expect(preventDefault).toHaveBeenCalledTimes(1);
|
||||
expect(onContextMenu).toHaveBeenCalledTimes(1);
|
||||
expect(onContextMenu).toHaveBeenCalledWith(
|
||||
@@ -80,7 +75,8 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
dispatchLongPressContextMenu(ref.current, {preventDefault});
|
||||
const target = createEventTarget(ref.current);
|
||||
target.contextmenu({preventDefault}, {pointerType: 'touch'});
|
||||
expect(preventDefault).toHaveBeenCalledTimes(1);
|
||||
expect(onContextMenu).toHaveBeenCalledTimes(1);
|
||||
expect(onContextMenu).toHaveBeenCalledWith(
|
||||
@@ -100,7 +96,8 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
dispatchRightClickContextMenu(ref.current);
|
||||
const target = createEventTarget(ref.current);
|
||||
target.contextmenu();
|
||||
expect(onContextMenu).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
@@ -117,7 +114,8 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
dispatchRightClickContextMenu(ref.current, {preventDefault});
|
||||
const target = createEventTarget(ref.current);
|
||||
target.contextmenu({preventDefault});
|
||||
expect(preventDefault).toHaveBeenCalledTimes(0);
|
||||
expect(onContextMenu).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -142,7 +140,8 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
dispatchModifiedClickContextMenu(ref.current);
|
||||
const target = createEventTarget(ref.current);
|
||||
target.contextmenu({}, {modified: true});
|
||||
expect(onContextMenu).toHaveBeenCalledTimes(1);
|
||||
expect(onContextMenu).toHaveBeenCalledWith(
|
||||
expect.objectContaining({pointerType: 'mouse', type: 'contextmenu'}),
|
||||
@@ -169,7 +168,8 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
dispatchModifiedClickContextMenu(ref.current);
|
||||
const target = createEventTarget(ref.current);
|
||||
target.contextmenu({}, {modified: true});
|
||||
expect(onContextMenu).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,15 +9,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
blur,
|
||||
focus,
|
||||
keydown,
|
||||
setPointerEvent,
|
||||
platform,
|
||||
dispatchPointerDown,
|
||||
dispatchPointerUp,
|
||||
} from '../test-utils';
|
||||
import {createEventTarget, setPointerEvent, platform} from '../testing-library';
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
@@ -73,9 +65,9 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
});
|
||||
|
||||
it('does not call callbacks', () => {
|
||||
const dispatch = arg => ref.current.dispatchEvent(arg);
|
||||
dispatch(focus());
|
||||
dispatch(blur());
|
||||
const target = createEventTarget(ref.current);
|
||||
target.focus();
|
||||
target.blur();
|
||||
expect(onFocus).not.toBeCalled();
|
||||
expect(onBlur).not.toBeCalled();
|
||||
});
|
||||
@@ -97,9 +89,9 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
});
|
||||
|
||||
it('is called after "blur" event', () => {
|
||||
const dispatch = arg => ref.current.dispatchEvent(arg);
|
||||
dispatch(focus());
|
||||
dispatch(blur());
|
||||
const target = createEventTarget(ref.current);
|
||||
target.focus();
|
||||
target.blur();
|
||||
expect(onBlur).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -127,19 +119,21 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
beforeEach(componentInit);
|
||||
|
||||
it('is called after "focus" event', () => {
|
||||
ref.current.dispatchEvent(focus());
|
||||
const target = createEventTarget(ref.current);
|
||||
target.focus();
|
||||
expect(onFocus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is not called if descendants of target receive focus', () => {
|
||||
innerRef.current.dispatchEvent(focus());
|
||||
const target = createEventTarget(innerRef.current);
|
||||
target.focus();
|
||||
expect(onFocus).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('is called with the correct pointerType: mouse', () => {
|
||||
const target = ref.current;
|
||||
dispatchPointerDown(target, {pointerType: 'mouse'});
|
||||
dispatchPointerUp(target, {pointerType: 'mouse'});
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown();
|
||||
target.pointerup();
|
||||
expect(onFocus).toHaveBeenCalledTimes(1);
|
||||
expect(onFocus).toHaveBeenCalledWith(
|
||||
expect.objectContaining({pointerType: 'mouse'}),
|
||||
@@ -147,9 +141,10 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
});
|
||||
|
||||
it('is called with the correct pointerType: touch', () => {
|
||||
const target = ref.current;
|
||||
dispatchPointerDown(target, {pointerType: 'touch'});
|
||||
dispatchPointerUp(target, {pointerType: 'touch'});
|
||||
const target = createEventTarget(ref.current);
|
||||
const pointerType = 'touch';
|
||||
target.pointerdown({pointerType});
|
||||
target.pointerup({pointerType});
|
||||
expect(onFocus).toHaveBeenCalledTimes(1);
|
||||
expect(onFocus).toHaveBeenCalledWith(
|
||||
expect.objectContaining({pointerType: 'touch'}),
|
||||
@@ -158,9 +153,10 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
|
||||
if (hasPointerEvents) {
|
||||
it('is called with the correct pointerType: pen', () => {
|
||||
const target = ref.current;
|
||||
dispatchPointerDown(target, {pointerType: 'pen'});
|
||||
dispatchPointerUp(target, {pointerType: 'pen'});
|
||||
const target = createEventTarget(ref.current);
|
||||
const pointerType = 'pen';
|
||||
target.pointerdown({pointerType});
|
||||
target.pointerup({pointerType});
|
||||
expect(onFocus).toHaveBeenCalledTimes(1);
|
||||
expect(onFocus).toHaveBeenCalledWith(
|
||||
expect.objectContaining({pointerType: 'pen'}),
|
||||
@@ -169,10 +165,9 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
}
|
||||
|
||||
it('is called with the correct pointerType using a keyboard', () => {
|
||||
const target = ref.current;
|
||||
// Keyboard tab
|
||||
target.dispatchEvent(keydown({key: 'Tab'}));
|
||||
target.dispatchEvent(focus());
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown({key: 'Tab'});
|
||||
target.focus();
|
||||
expect(onFocus).toHaveBeenCalledTimes(1);
|
||||
expect(onFocus).toHaveBeenCalledWith(
|
||||
expect.objectContaining({pointerType: 'keyboard'}),
|
||||
@@ -184,10 +179,11 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
jest.resetModules();
|
||||
initializeModules();
|
||||
componentInit();
|
||||
const target = ref.current;
|
||||
|
||||
target.dispatchEvent(keydown({key: 'Tab', altKey: true}));
|
||||
target.dispatchEvent(focus());
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown({key: 'Tab', altKey: true});
|
||||
target.focus();
|
||||
|
||||
expect(onFocus).toHaveBeenCalledTimes(1);
|
||||
expect(onFocus).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -220,20 +216,20 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
});
|
||||
|
||||
it('is called after "blur" and "focus" events', () => {
|
||||
const target = ref.current;
|
||||
target.dispatchEvent(focus());
|
||||
const target = createEventTarget(ref.current);
|
||||
target.focus();
|
||||
expect(onFocusChange).toHaveBeenCalledTimes(1);
|
||||
expect(onFocusChange).toHaveBeenCalledWith(true);
|
||||
target.dispatchEvent(blur());
|
||||
target.blur();
|
||||
expect(onFocusChange).toHaveBeenCalledTimes(2);
|
||||
expect(onFocusChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('is not called after "blur" and "focus" events on descendants', () => {
|
||||
const target = innerRef.current;
|
||||
target.dispatchEvent(focus());
|
||||
const target = createEventTarget(innerRef.current);
|
||||
target.focus();
|
||||
expect(onFocusChange).toHaveBeenCalledTimes(0);
|
||||
target.dispatchEvent(blur());
|
||||
target.blur();
|
||||
expect(onFocusChange).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
@@ -259,48 +255,52 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
});
|
||||
|
||||
it('is called after "focus" and "blur" if keyboard navigation is active', () => {
|
||||
const target = ref.current;
|
||||
const target = createEventTarget(ref.current);
|
||||
const containerTarget = createEventTarget(container);
|
||||
// use keyboard first
|
||||
container.dispatchEvent(keydown({key: 'Tab'}));
|
||||
target.dispatchEvent(focus());
|
||||
containerTarget.keydown({key: 'Tab'});
|
||||
target.focus();
|
||||
expect(onFocusVisibleChange).toHaveBeenCalledTimes(1);
|
||||
expect(onFocusVisibleChange).toHaveBeenCalledWith(true);
|
||||
target.dispatchEvent(blur({relatedTarget: container}));
|
||||
target.blur({relatedTarget: container});
|
||||
expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
|
||||
expect(onFocusVisibleChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('is called if non-keyboard event is dispatched on target previously focused with keyboard', () => {
|
||||
const target = ref.current;
|
||||
const target = createEventTarget(ref.current);
|
||||
const containerTarget = createEventTarget(container);
|
||||
// use keyboard first
|
||||
container.dispatchEvent(keydown({key: 'Tab'}));
|
||||
target.dispatchEvent(focus());
|
||||
containerTarget.keydown({key: 'Tab'});
|
||||
target.focus();
|
||||
expect(onFocusVisibleChange).toHaveBeenCalledTimes(1);
|
||||
expect(onFocusVisibleChange).toHaveBeenCalledWith(true);
|
||||
// then use pointer on the target, focus should no longer be visible
|
||||
dispatchPointerDown(target);
|
||||
target.pointerdown();
|
||||
expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
|
||||
expect(onFocusVisibleChange).toHaveBeenCalledWith(false);
|
||||
// onFocusVisibleChange should not be called again
|
||||
target.dispatchEvent(blur({relatedTarget: container}));
|
||||
target.blur({relatedTarget: container});
|
||||
expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('is not called after "focus" and "blur" events without keyboard', () => {
|
||||
const target = ref.current;
|
||||
dispatchPointerDown(target);
|
||||
dispatchPointerUp(target);
|
||||
dispatchPointerDown(container);
|
||||
target.dispatchEvent(blur({relatedTarget: container}));
|
||||
const target = createEventTarget(ref.current);
|
||||
const containerTarget = createEventTarget(container);
|
||||
target.pointerdown();
|
||||
target.pointerup();
|
||||
containerTarget.pointerdown();
|
||||
target.blur({relatedTarget: container});
|
||||
expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('is not called after "blur" and "focus" events on descendants', () => {
|
||||
const target = innerRef.current;
|
||||
container.dispatchEvent(keydown({key: 'Tab'}));
|
||||
target.dispatchEvent(focus());
|
||||
const innerTarget = createEventTarget(innerRef.current);
|
||||
const containerTarget = createEventTarget(container);
|
||||
containerTarget.keydown({key: 'Tab'});
|
||||
innerTarget.focus();
|
||||
expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
|
||||
target.dispatchEvent(blur({relatedTarget: container}));
|
||||
innerTarget.blur({relatedTarget: container});
|
||||
expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
@@ -338,10 +338,13 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
|
||||
ReactDOM.render(<Outer />, container);
|
||||
|
||||
outerRef.current.dispatchEvent(focus());
|
||||
outerRef.current.dispatchEvent(blur());
|
||||
innerRef.current.dispatchEvent(focus());
|
||||
innerRef.current.dispatchEvent(blur());
|
||||
const innerTarget = createEventTarget(innerRef.current);
|
||||
const outerTarget = createEventTarget(outerRef.current);
|
||||
|
||||
outerTarget.focus();
|
||||
outerTarget.blur();
|
||||
innerTarget.focus();
|
||||
innerTarget.blur();
|
||||
expect(events).toEqual([
|
||||
'outer: onFocus',
|
||||
'outer: onFocusChange',
|
||||
|
||||
@@ -9,14 +9,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
blur,
|
||||
focus,
|
||||
keydown,
|
||||
setPointerEvent,
|
||||
dispatchPointerDown,
|
||||
dispatchPointerUp,
|
||||
} from '../test-utils';
|
||||
import {createEventTarget, setPointerEvent} from '../testing-library';
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
@@ -73,9 +66,9 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
});
|
||||
|
||||
it('prevents custom events being dispatched', () => {
|
||||
const target = ref.current;
|
||||
target.dispatchEvent(focus());
|
||||
target.dispatchEvent(blur());
|
||||
const target = createEventTarget(ref.current);
|
||||
target.focus();
|
||||
target.blur();
|
||||
expect(onFocusWithinChange).not.toBeCalled();
|
||||
expect(onFocusWithinVisibleChange).not.toBeCalled();
|
||||
});
|
||||
@@ -104,42 +97,46 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
});
|
||||
|
||||
it('is called after "blur" and "focus" events on focus target', () => {
|
||||
const target = ref.current;
|
||||
target.dispatchEvent(focus());
|
||||
const target = createEventTarget(ref.current);
|
||||
target.focus();
|
||||
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
|
||||
expect(onFocusWithinChange).toHaveBeenCalledWith(true);
|
||||
target.dispatchEvent(blur({relatedTarget: container}));
|
||||
target.blur({relatedTarget: container});
|
||||
expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
|
||||
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('is called after "blur" and "focus" events on descendants', () => {
|
||||
const target = innerRef.current;
|
||||
target.dispatchEvent(focus());
|
||||
const target = createEventTarget(innerRef.current);
|
||||
target.focus();
|
||||
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
|
||||
expect(onFocusWithinChange).toHaveBeenCalledWith(true);
|
||||
target.dispatchEvent(blur({relatedTarget: container}));
|
||||
target.blur({relatedTarget: container});
|
||||
expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
|
||||
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('is only called once when focus moves within and outside the subtree', () => {
|
||||
const target = ref.current;
|
||||
const innerTarget1 = innerRef.current;
|
||||
const innerTarget2 = innerRef2.current;
|
||||
const node = ref.current;
|
||||
const innerNode1 = innerRef.current;
|
||||
const innerNode2 = innerRef.current;
|
||||
const target = createEventTarget(node);
|
||||
const innerTarget1 = createEventTarget(innerNode1);
|
||||
const innerTarget2 = createEventTarget(innerNode2);
|
||||
|
||||
// focus shifts into subtree
|
||||
innerTarget1.dispatchEvent(focus());
|
||||
innerTarget1.focus();
|
||||
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
|
||||
expect(onFocusWithinChange).toHaveBeenCalledWith(true);
|
||||
// focus moves around subtree
|
||||
innerTarget1.dispatchEvent(blur({relatedTarget: innerTarget2}));
|
||||
innerTarget2.dispatchEvent(focus());
|
||||
innerTarget2.dispatchEvent(blur({relatedTarget: target}));
|
||||
target.dispatchEvent(focus());
|
||||
target.dispatchEvent(blur({relatedTarget: innerTarget1}));
|
||||
innerTarget1.blur({relatedTarget: innerNode2});
|
||||
innerTarget2.focus();
|
||||
innerTarget2.blur({relatedTarget: node});
|
||||
target.focus();
|
||||
target.blur({relatedTarget: innerNode1});
|
||||
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
|
||||
// focus shifts outside subtree
|
||||
innerTarget1.dispatchEvent(blur({relatedTarget: container}));
|
||||
innerTarget1.blur({relatedTarget: container});
|
||||
expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
|
||||
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
@@ -168,87 +165,96 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
});
|
||||
|
||||
it('is called after "focus" and "blur" on focus target if keyboard was used', () => {
|
||||
const target = ref.current;
|
||||
const target = createEventTarget(ref.current);
|
||||
const containerTarget = createEventTarget(container);
|
||||
// use keyboard first
|
||||
container.dispatchEvent(keydown({key: 'Tab'}));
|
||||
target.dispatchEvent(focus());
|
||||
containerTarget.keydown({key: 'Tab'});
|
||||
target.focus();
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
|
||||
target.dispatchEvent(blur({relatedTarget: container}));
|
||||
target.blur({relatedTarget: container});
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('is called after "focus" and "blur" on descendants if keyboard was used', () => {
|
||||
const innerTarget = innerRef.current;
|
||||
const innerTarget = createEventTarget(innerRef.current);
|
||||
const containerTarget = createEventTarget(container);
|
||||
// use keyboard first
|
||||
container.dispatchEvent(keydown({key: 'Tab'}));
|
||||
innerTarget.dispatchEvent(focus());
|
||||
containerTarget.keydown({key: 'Tab'});
|
||||
innerTarget.focus();
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
|
||||
innerTarget.dispatchEvent(blur({relatedTarget: container}));
|
||||
innerTarget.blur({relatedTarget: container});
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('is called if non-keyboard event is dispatched on target previously focused with keyboard', () => {
|
||||
const target = ref.current;
|
||||
const innerTarget1 = innerRef.current;
|
||||
const innerTarget2 = innerRef2.current;
|
||||
const node = ref.current;
|
||||
const innerNode1 = innerRef.current;
|
||||
const innerNode2 = innerRef2.current;
|
||||
|
||||
const target = createEventTarget(node);
|
||||
const innerTarget1 = createEventTarget(innerNode1);
|
||||
const innerTarget2 = createEventTarget(innerNode2);
|
||||
// use keyboard first
|
||||
target.dispatchEvent(focus());
|
||||
target.dispatchEvent(keydown({key: 'Tab'}));
|
||||
target.dispatchEvent(blur({relatedTarget: innerTarget1}));
|
||||
innerTarget1.dispatchEvent(focus());
|
||||
target.focus();
|
||||
target.keydown({key: 'Tab'});
|
||||
target.blur({relatedTarget: innerNode1});
|
||||
innerTarget1.focus();
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
|
||||
// then use pointer on the next target, focus should no longer be visible
|
||||
dispatchPointerDown(innerTarget2);
|
||||
innerTarget1.dispatchEvent(blur({relatedTarget: innerTarget2}));
|
||||
innerTarget2.dispatchEvent(focus());
|
||||
innerTarget2.pointerdown();
|
||||
innerTarget1.blur({relatedTarget: innerNode2});
|
||||
innerTarget2.focus();
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
|
||||
// then use keyboard again
|
||||
innerTarget2.dispatchEvent(keydown({key: 'Tab', shiftKey: true}));
|
||||
innerTarget2.dispatchEvent(blur({relatedTarget: innerTarget1}));
|
||||
innerTarget1.dispatchEvent(focus());
|
||||
innerTarget2.keydown({key: 'Tab', shiftKey: true});
|
||||
innerTarget2.blur({relatedTarget: innerNode1});
|
||||
innerTarget1.focus();
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(3);
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
|
||||
// then use pointer on the target, focus should no longer be visible
|
||||
dispatchPointerDown(innerTarget1);
|
||||
innerTarget1.pointerdown();
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4);
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
|
||||
// onFocusVisibleChange should not be called again
|
||||
innerTarget1.dispatchEvent(blur({relatedTarget: container}));
|
||||
innerTarget1.blur({relatedTarget: container});
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it('is not called after "focus" and "blur" events without keyboard', () => {
|
||||
const innerTarget = innerRef.current;
|
||||
dispatchPointerDown(innerTarget);
|
||||
dispatchPointerUp(innerTarget);
|
||||
innerTarget.dispatchEvent(blur({relatedTarget: container}));
|
||||
const innerTarget = createEventTarget(innerRef.current);
|
||||
innerTarget.pointerdown();
|
||||
innerTarget.pointerup();
|
||||
innerTarget.blur({relatedTarget: container});
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('is only called once when focus moves within and outside the subtree', () => {
|
||||
const target = ref.current;
|
||||
const innerTarget1 = innerRef.current;
|
||||
const innerTarget2 = innerRef2.current;
|
||||
const node = ref.current;
|
||||
const innerNode1 = innerRef.current;
|
||||
const innerNode2 = innerRef2.current;
|
||||
const target = createEventTarget(node);
|
||||
const innerTarget1 = createEventTarget(innerNode1);
|
||||
const innerTarget2 = createEventTarget(innerNode2);
|
||||
|
||||
// focus shifts into subtree
|
||||
innerTarget1.dispatchEvent(focus());
|
||||
innerTarget1.focus();
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
|
||||
// focus moves around subtree
|
||||
innerTarget1.dispatchEvent(blur({relatedTarget: innerTarget2}));
|
||||
innerTarget2.dispatchEvent(focus());
|
||||
innerTarget2.dispatchEvent(blur({relatedTarget: target}));
|
||||
target.dispatchEvent(focus());
|
||||
target.dispatchEvent(blur({relatedTarget: innerTarget1}));
|
||||
innerTarget1.blur({relatedTarget: innerNode2});
|
||||
innerTarget2.focus();
|
||||
innerTarget2.blur({relatedTarget: node});
|
||||
target.focus();
|
||||
target.blur({relatedTarget: innerNode1});
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
|
||||
// focus shifts outside subtree
|
||||
innerTarget1.dispatchEvent(blur({relatedTarget: container}));
|
||||
innerTarget1.blur({relatedTarget: container});
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
|
||||
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
@@ -9,14 +9,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
dispatchPointerCancel,
|
||||
dispatchPointerHoverEnter,
|
||||
dispatchPointerHoverExit,
|
||||
dispatchPointerHoverMove,
|
||||
dispatchTouchTap,
|
||||
setPointerEvent,
|
||||
} from '../test-utils';
|
||||
import {createEventTarget, setPointerEvent} from '../testing-library';
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
@@ -77,9 +70,9 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
});
|
||||
|
||||
it('does not call callbacks', () => {
|
||||
const target = ref.current;
|
||||
dispatchPointerHoverEnter(target);
|
||||
dispatchPointerHoverExit(target);
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerenter();
|
||||
target.pointerexit();
|
||||
expect(onHoverChange).not.toBeCalled();
|
||||
expect(onHoverStart).not.toBeCalled();
|
||||
expect(onHoverMove).not.toBeCalled();
|
||||
@@ -103,21 +96,23 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
});
|
||||
|
||||
it('is called for mouse pointers', () => {
|
||||
const target = ref.current;
|
||||
dispatchPointerHoverEnter(target);
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerenter();
|
||||
expect(onHoverStart).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('is not called for touch pointers', () => {
|
||||
const target = ref.current;
|
||||
dispatchTouchTap(target);
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({pointerType: 'touch'});
|
||||
target.pointerup({pointerType: 'touch'});
|
||||
expect(onHoverStart).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('is called if a mouse pointer is used after a touch pointer', () => {
|
||||
const target = ref.current;
|
||||
dispatchTouchTap(target);
|
||||
dispatchPointerHoverEnter(target);
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({pointerType: 'touch'});
|
||||
target.pointerup({pointerType: 'touch'});
|
||||
target.pointerenter();
|
||||
expect(onHoverStart).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -138,18 +133,19 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
});
|
||||
|
||||
it('is called for mouse pointers', () => {
|
||||
const target = ref.current;
|
||||
dispatchPointerHoverEnter(target);
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerenter();
|
||||
expect(onHoverChange).toHaveBeenCalledTimes(1);
|
||||
expect(onHoverChange).toHaveBeenCalledWith(true);
|
||||
dispatchPointerHoverExit(target);
|
||||
target.pointerexit();
|
||||
expect(onHoverChange).toHaveBeenCalledTimes(2);
|
||||
expect(onHoverChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('is not called for touch pointers', () => {
|
||||
const target = ref.current;
|
||||
dispatchTouchTap(target);
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({pointerType: 'touch'});
|
||||
target.pointerup({pointerType: 'touch'});
|
||||
expect(onHoverChange).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -170,31 +166,32 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
});
|
||||
|
||||
it('is called for mouse pointers', () => {
|
||||
const target = ref.current;
|
||||
dispatchPointerHoverEnter(target);
|
||||
dispatchPointerHoverExit(target);
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerenter();
|
||||
target.pointerexit();
|
||||
expect(onHoverEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
if (hasPointerEvents) {
|
||||
it('is called once for cancelled mouse pointers', () => {
|
||||
const target = ref.current;
|
||||
dispatchPointerHoverEnter(target);
|
||||
dispatchPointerCancel(target);
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerenter();
|
||||
target.pointercancel();
|
||||
expect(onHoverEnd).toHaveBeenCalledTimes(1);
|
||||
|
||||
// only called once if cancel follows exit
|
||||
onHoverEnd.mockReset();
|
||||
dispatchPointerHoverEnter(target);
|
||||
dispatchPointerHoverExit(target);
|
||||
dispatchPointerCancel(target);
|
||||
target.pointerenter();
|
||||
target.pointerexit();
|
||||
target.pointercancel();
|
||||
expect(onHoverEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
}
|
||||
|
||||
it('is not called for touch pointers', () => {
|
||||
const target = ref.current;
|
||||
dispatchTouchTap(target);
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({pointerType: 'touch'});
|
||||
target.pointerup({pointerType: 'touch'});
|
||||
expect(onHoverEnd).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -211,10 +208,10 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
const target = ref.current;
|
||||
dispatchPointerHoverEnter(target);
|
||||
dispatchPointerHoverMove(target, {x: 0, y: 0});
|
||||
dispatchPointerHoverMove(target, {x: 1, y: 1});
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerenter();
|
||||
target.pointerhover({x: 0, y: 0});
|
||||
target.pointerhover({x: 1, y: 1});
|
||||
expect(onHoverMove).toHaveBeenCalledTimes(2);
|
||||
expect(onHoverMove).toHaveBeenCalledWith(
|
||||
expect.objectContaining({type: 'hovermove'}),
|
||||
@@ -254,15 +251,17 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
};
|
||||
ReactDOM.render(<Outer />, container);
|
||||
|
||||
const innerTarget = innerRef.current;
|
||||
const outerTarget = outerRef.current;
|
||||
const innerNode = innerRef.current;
|
||||
const outerNode = outerRef.current;
|
||||
const innerTarget = createEventTarget(innerNode);
|
||||
const outerTarget = createEventTarget(outerNode);
|
||||
|
||||
dispatchPointerHoverEnter(outerTarget, {relatedTarget: container});
|
||||
dispatchPointerHoverExit(outerTarget, {relatedTarget: innerTarget});
|
||||
dispatchPointerHoverEnter(innerTarget, {relatedTarget: outerTarget});
|
||||
dispatchPointerHoverExit(innerTarget, {relatedTarget: outerTarget});
|
||||
dispatchPointerHoverEnter(outerTarget, {relatedTarget: innerTarget});
|
||||
dispatchPointerHoverExit(outerTarget, {relatedTarget: container});
|
||||
outerTarget.pointerenter({relatedTarget: container});
|
||||
outerTarget.pointerexit({relatedTarget: innerNode});
|
||||
innerTarget.pointerenter({relatedTarget: outerNode});
|
||||
innerTarget.pointerexit({relatedTarget: outerNode});
|
||||
outerTarget.pointerenter({relatedTarget: innerNode});
|
||||
outerTarget.pointerexit({relatedTarget: container});
|
||||
|
||||
expect(events).toEqual([
|
||||
'outer: onHoverStart',
|
||||
@@ -315,12 +314,13 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
const target = ref.current;
|
||||
const node = ref.current;
|
||||
const target = createEventTarget(node);
|
||||
|
||||
dispatchPointerHoverEnter(target, {x: 10, y: 10});
|
||||
dispatchPointerHoverMove(target, {x: 10, y: 10});
|
||||
dispatchPointerHoverMove(target, {x: 20, y: 20});
|
||||
dispatchPointerHoverExit(target, {x: 20, y: 20});
|
||||
target.pointerenter({x: 10, y: 10});
|
||||
target.pointerhover({x: 10, y: 10});
|
||||
target.pointerhover({x: 20, y: 20});
|
||||
target.pointerexit({x: 20, y: 20});
|
||||
|
||||
expect(eventLog).toEqual([
|
||||
{
|
||||
@@ -330,7 +330,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
pageY: 10,
|
||||
clientX: 10,
|
||||
clientY: 10,
|
||||
target,
|
||||
target: node,
|
||||
timeStamp: timeStamps[0],
|
||||
type: 'hoverstart',
|
||||
pointerType: 'mouse',
|
||||
@@ -342,7 +342,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
pageY: 10,
|
||||
clientX: 10,
|
||||
clientY: 10,
|
||||
target,
|
||||
target: node,
|
||||
timeStamp: timeStamps[1],
|
||||
type: 'hovermove',
|
||||
pointerType: 'mouse',
|
||||
@@ -354,7 +354,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
pageY: 20,
|
||||
clientX: 20,
|
||||
clientY: 20,
|
||||
target,
|
||||
target: node,
|
||||
timeStamp: timeStamps[2],
|
||||
type: 'hovermove',
|
||||
pointerType: 'mouse',
|
||||
@@ -366,7 +366,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
pageY: 20,
|
||||
clientX: 20,
|
||||
clientY: 20,
|
||||
target,
|
||||
target: node,
|
||||
timeStamp: timeStamps[3],
|
||||
type: 'hoverend',
|
||||
pointerType: 'mouse',
|
||||
|
||||
@@ -14,7 +14,7 @@ let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let useKeyboardResponder;
|
||||
|
||||
import {keydown, keyup} from '../test-utils';
|
||||
import {createEventTarget} from '../testing-library';
|
||||
|
||||
function initializeModules(hasPointerEvents) {
|
||||
jest.resetModules();
|
||||
@@ -59,9 +59,9 @@ describe('Keyboard event responder', () => {
|
||||
});
|
||||
|
||||
it('prevents custom events being dispatched', () => {
|
||||
const target = ref.current;
|
||||
target.dispatchEvent(keydown());
|
||||
target.dispatchEvent(keyup());
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown();
|
||||
target.keyup();
|
||||
expect(onKeyDown).not.toBeCalled();
|
||||
expect(onKeyUp).not.toBeCalled();
|
||||
});
|
||||
@@ -83,7 +83,8 @@ describe('Keyboard event responder', () => {
|
||||
});
|
||||
|
||||
it('is called after "keydown" event', () => {
|
||||
ref.current.dispatchEvent(keydown({key: 'Q'}));
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown({key: 'Q'});
|
||||
expect(onKeyDown).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyDown).toHaveBeenCalledWith(
|
||||
expect.objectContaining({key: 'Q', type: 'keydown'}),
|
||||
@@ -109,9 +110,9 @@ describe('Keyboard event responder', () => {
|
||||
});
|
||||
|
||||
it('is called after "keydown" event', () => {
|
||||
const target = ref.current;
|
||||
target.dispatchEvent(keydown({key: 'Q'}));
|
||||
target.dispatchEvent(keyup({key: 'Q'}));
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown({key: 'Q'});
|
||||
target.keyup({key: 'Q'});
|
||||
expect(onKeyDown).toHaveBeenCalledTimes(1);
|
||||
expect(onKeyDown).toHaveBeenCalledWith(
|
||||
expect.objectContaining({key: 'Q', type: 'keydown'}),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,33 +9,31 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {createEventTarget, setPointerEvent} from '../testing-library';
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let useScrollResponder;
|
||||
|
||||
const createEvent = (type, data) => {
|
||||
const event = document.createEvent('CustomEvent');
|
||||
event.initCustomEvent(type, true, true);
|
||||
if (data != null) {
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
event[key] = value;
|
||||
});
|
||||
}
|
||||
return event;
|
||||
const forcePointerEvents = true;
|
||||
const table = [[forcePointerEvents], [!forcePointerEvents]];
|
||||
|
||||
const initializeModules = hasPointerEvents => {
|
||||
setPointerEvent(hasPointerEvents);
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableFlareAPI = true;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
useScrollResponder = require('react-events/scroll').useScrollResponder;
|
||||
};
|
||||
|
||||
describe('Scroll event responder', () => {
|
||||
describe.each(table)('Scroll responder', hasPointerEvents => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableFlareAPI = true;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
useScrollResponder = require('react-events/scroll').useScrollResponder;
|
||||
|
||||
initializeModules(hasPointerEvents);
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
@@ -63,7 +61,8 @@ describe('Scroll event responder', () => {
|
||||
});
|
||||
|
||||
it('prevents custom events being dispatched', () => {
|
||||
ref.current.dispatchEvent(createEvent('scroll'));
|
||||
const target = createEventTarget(ref.current);
|
||||
target.scroll();
|
||||
expect(onScroll).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -84,129 +83,49 @@ describe('Scroll event responder', () => {
|
||||
});
|
||||
|
||||
describe('is called after "scroll" event', () => {
|
||||
it('with a mouse pointerType', () => {
|
||||
ref.current.dispatchEvent(
|
||||
createEvent('pointerdown', {
|
||||
pointerType: 'mouse',
|
||||
}),
|
||||
);
|
||||
ref.current.dispatchEvent(createEvent('scroll'));
|
||||
const pointerTypesTable = hasPointerEvents
|
||||
? [['mouse'], ['touch'], ['pen']]
|
||||
: [['mouse'], ['touch']];
|
||||
it.each(pointerTypesTable)('with pointerType: %s', pointerType => {
|
||||
const node = ref.current;
|
||||
const target = createEventTarget(node);
|
||||
target.pointerdown({pointerType});
|
||||
target.scroll();
|
||||
expect(onScroll).toHaveBeenCalledTimes(1);
|
||||
expect(onScroll).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pointerType: 'mouse',
|
||||
pointerType,
|
||||
type: 'scroll',
|
||||
direction: '',
|
||||
}),
|
||||
);
|
||||
onScroll.mockReset();
|
||||
ref.current.scrollTop = -1;
|
||||
ref.current.dispatchEvent(createEvent('scroll'));
|
||||
node.scrollTop = -1;
|
||||
target.scroll();
|
||||
expect(onScroll).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pointerType: 'mouse',
|
||||
pointerType,
|
||||
type: 'scroll',
|
||||
direction: 'up',
|
||||
}),
|
||||
);
|
||||
onScroll.mockReset();
|
||||
ref.current.scrollTop = 1;
|
||||
ref.current.dispatchEvent(createEvent('scroll'));
|
||||
node.scrollTop = 1;
|
||||
target.scroll();
|
||||
expect(onScroll).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pointerType: 'mouse',
|
||||
pointerType,
|
||||
type: 'scroll',
|
||||
direction: 'down',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('with a touch pointerType', () => {
|
||||
ref.current.dispatchEvent(
|
||||
createEvent('pointerdown', {
|
||||
pointerType: 'touch',
|
||||
}),
|
||||
);
|
||||
ref.current.dispatchEvent(createEvent('scroll'));
|
||||
expect(onScroll).toHaveBeenCalledTimes(1);
|
||||
expect(onScroll).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pointerType: 'touch',
|
||||
type: 'scroll',
|
||||
direction: '',
|
||||
}),
|
||||
);
|
||||
onScroll.mockReset();
|
||||
ref.current.scrollTop = -1;
|
||||
ref.current.dispatchEvent(createEvent('scroll'));
|
||||
expect(onScroll).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pointerType: 'touch',
|
||||
type: 'scroll',
|
||||
direction: 'up',
|
||||
}),
|
||||
);
|
||||
onScroll.mockReset();
|
||||
ref.current.scrollTop = 1;
|
||||
ref.current.dispatchEvent(createEvent('scroll'));
|
||||
expect(onScroll).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pointerType: 'touch',
|
||||
type: 'scroll',
|
||||
direction: 'down',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('with a pen pointerType', () => {
|
||||
ref.current.dispatchEvent(
|
||||
createEvent('pointerdown', {
|
||||
pointerType: 'pen',
|
||||
}),
|
||||
);
|
||||
ref.current.dispatchEvent(createEvent('scroll'));
|
||||
expect(onScroll).toHaveBeenCalledTimes(1);
|
||||
expect(onScroll).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pointerType: 'pen',
|
||||
type: 'scroll',
|
||||
direction: '',
|
||||
}),
|
||||
);
|
||||
onScroll.mockReset();
|
||||
ref.current.scrollTop = -1;
|
||||
ref.current.dispatchEvent(createEvent('scroll'));
|
||||
expect(onScroll).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pointerType: 'pen',
|
||||
type: 'scroll',
|
||||
direction: 'up',
|
||||
}),
|
||||
);
|
||||
onScroll.mockReset();
|
||||
ref.current.scrollTop = 1;
|
||||
ref.current.dispatchEvent(createEvent('scroll'));
|
||||
expect(onScroll).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pointerType: 'pen',
|
||||
type: 'scroll',
|
||||
direction: 'down',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('with a keyboard pointerType', () => {
|
||||
ref.current.dispatchEvent(
|
||||
createEvent('keydown', {
|
||||
key: 'A',
|
||||
}),
|
||||
);
|
||||
ref.current.dispatchEvent(
|
||||
createEvent('keyup', {
|
||||
key: 'A',
|
||||
}),
|
||||
);
|
||||
ref.current.dispatchEvent(createEvent('scroll'));
|
||||
it('with pointerType: keyboard', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.keydown({key: 'A'});
|
||||
target.keyup({key: 'A'});
|
||||
target.scroll();
|
||||
expect(onScroll).toHaveBeenCalledTimes(1);
|
||||
expect(onScroll).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -235,13 +154,9 @@ describe('Scroll event responder', () => {
|
||||
});
|
||||
|
||||
it('works as expected with touch events', () => {
|
||||
ref.current.dispatchEvent(
|
||||
createEvent('pointerdown', {
|
||||
pointerType: 'touch',
|
||||
}),
|
||||
);
|
||||
ref.current.dispatchEvent(createEvent('touchstart'));
|
||||
ref.current.dispatchEvent(createEvent('scroll'));
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({pointerType: 'touch'});
|
||||
target.scroll();
|
||||
expect(onScrollDragStart).toHaveBeenCalledTimes(1);
|
||||
expect(onScrollDragStart).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -269,14 +184,10 @@ describe('Scroll event responder', () => {
|
||||
});
|
||||
|
||||
it('works as expected with touch events', () => {
|
||||
ref.current.dispatchEvent(
|
||||
createEvent('pointerdown', {
|
||||
pointerType: 'touch',
|
||||
}),
|
||||
);
|
||||
ref.current.dispatchEvent(createEvent('touchstart'));
|
||||
ref.current.dispatchEvent(createEvent('scroll'));
|
||||
ref.current.dispatchEvent(createEvent('touchend'));
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({pointerType: 'touch'});
|
||||
target.scroll();
|
||||
target.pointerup({pointerType: 'touch'});
|
||||
expect(onScrollDragEnd).toHaveBeenCalledTimes(1);
|
||||
expect(onScrollDragEnd).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
||||
-481
@@ -1,481 +0,0 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
/**
|
||||
* Change environment support for PointerEvent.
|
||||
*/
|
||||
|
||||
function hasPointerEvent() {
|
||||
return global != null && global.PointerEvent != null;
|
||||
}
|
||||
|
||||
function setPointerEvent(bool) {
|
||||
global.PointerEvent = bool ? function() {} : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change environment host platform.
|
||||
*/
|
||||
|
||||
const platformGetter = jest.spyOn(global.navigator, 'platform', 'get');
|
||||
|
||||
const platform = {
|
||||
clear() {
|
||||
platformGetter.mockClear();
|
||||
},
|
||||
get() {
|
||||
return global.navigator.platform === 'MacIntel' ? 'mac' : 'windows';
|
||||
},
|
||||
set(name: 'mac' | 'windows') {
|
||||
switch (name) {
|
||||
case 'mac': {
|
||||
platformGetter.mockReturnValue('MacIntel');
|
||||
break;
|
||||
}
|
||||
case 'windows': {
|
||||
platformGetter.mockReturnValue('Win32');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock native events
|
||||
*/
|
||||
|
||||
function createEvent(type, data = {}) {
|
||||
const event = document.createEvent('CustomEvent');
|
||||
event.initCustomEvent(type, true, true);
|
||||
event.clientX = data.x || 0;
|
||||
event.clientY = data.y || 0;
|
||||
event.x = data.x || 0;
|
||||
event.y = data.y || 0;
|
||||
if (data != null) {
|
||||
Object.keys(data).forEach(key => {
|
||||
const value = data[key];
|
||||
Object.defineProperty(event, key, {value});
|
||||
});
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
function createTouchEvent(type, data = {}, id) {
|
||||
return createEvent(type, {
|
||||
changedTouches: [
|
||||
{
|
||||
identifier: id,
|
||||
clientX: data.x || 0,
|
||||
clientY: data.y || 0,
|
||||
...data,
|
||||
},
|
||||
],
|
||||
targetTouches: [
|
||||
{
|
||||
identifier: id,
|
||||
clientX: 0 || data.x,
|
||||
clientY: 0 || data.y,
|
||||
...data,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const createKeyboardEvent = (type, data) => {
|
||||
return new KeyboardEvent(type, {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
...data,
|
||||
});
|
||||
};
|
||||
|
||||
function blur(data) {
|
||||
return createEvent('blur', data);
|
||||
}
|
||||
|
||||
function click(data) {
|
||||
return createEvent('click', data);
|
||||
}
|
||||
|
||||
function contextmenu(data) {
|
||||
return createEvent('contextmenu', data);
|
||||
}
|
||||
|
||||
function dragstart(data) {
|
||||
return createEvent('dragstart', data);
|
||||
}
|
||||
|
||||
function focus(data) {
|
||||
return createEvent('focus', data);
|
||||
}
|
||||
|
||||
function gotpointercapture(data) {
|
||||
return createEvent('gotpointercapture', data);
|
||||
}
|
||||
|
||||
function keydown(data) {
|
||||
return createEvent('keydown', data);
|
||||
}
|
||||
|
||||
function keyup(data) {
|
||||
return createEvent('keyup', data);
|
||||
}
|
||||
|
||||
function lostpointercapture(data) {
|
||||
return createEvent('lostpointercapture', data);
|
||||
}
|
||||
|
||||
function mousedown(data) {
|
||||
return createEvent('mousedown', data);
|
||||
}
|
||||
|
||||
function mouseenter(data) {
|
||||
return createEvent('mouseenter', data);
|
||||
}
|
||||
|
||||
function mouseleave(data) {
|
||||
return createEvent('mouseleave', data);
|
||||
}
|
||||
|
||||
function mousemove(data) {
|
||||
return createEvent('mousemove', data);
|
||||
}
|
||||
|
||||
function mouseout(data) {
|
||||
return createEvent('mouseout', data);
|
||||
}
|
||||
|
||||
function mouseover(data) {
|
||||
return createEvent('mouseover', data);
|
||||
}
|
||||
|
||||
function mouseup(data) {
|
||||
return createEvent('mouseup', data);
|
||||
}
|
||||
|
||||
function pointercancel(data) {
|
||||
return createEvent('pointercancel', data);
|
||||
}
|
||||
|
||||
function pointerdown(data) {
|
||||
return createEvent('pointerdown', data);
|
||||
}
|
||||
|
||||
function pointerenter(data) {
|
||||
return createEvent('pointerenter', data);
|
||||
}
|
||||
|
||||
function pointerleave(data) {
|
||||
return createEvent('pointerleave', data);
|
||||
}
|
||||
|
||||
function pointermove(data) {
|
||||
return createEvent('pointermove', data);
|
||||
}
|
||||
|
||||
function pointerout(data) {
|
||||
return createEvent('pointerout', data);
|
||||
}
|
||||
|
||||
function pointerover(data) {
|
||||
return createEvent('pointerover', data);
|
||||
}
|
||||
|
||||
function pointerup(data) {
|
||||
return createEvent('pointerup', data);
|
||||
}
|
||||
|
||||
function scroll(data) {
|
||||
return createEvent('scroll', data);
|
||||
}
|
||||
|
||||
function touchcancel(data, id) {
|
||||
return createTouchEvent('touchcancel', data, id);
|
||||
}
|
||||
|
||||
function touchend(data, id) {
|
||||
return createTouchEvent('touchend', data, id);
|
||||
}
|
||||
|
||||
function touchmove(data, id) {
|
||||
return createTouchEvent('touchmove', data, id);
|
||||
}
|
||||
|
||||
function touchstart(data, id) {
|
||||
return createTouchEvent('touchstart', data, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch high-level event sequences
|
||||
*/
|
||||
|
||||
function emptyFunction() {}
|
||||
|
||||
function dispatchLongPressContextMenu(
|
||||
target,
|
||||
{preventDefault = emptyFunction} = {},
|
||||
) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const button = 0;
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(pointerdown({button, pointerType: 'touch'}));
|
||||
}
|
||||
dispatch(touchstart());
|
||||
dispatch(contextmenu({button, preventDefault}));
|
||||
}
|
||||
|
||||
function dispatchRightClickContextMenu(
|
||||
target,
|
||||
{preventDefault = emptyFunction} = {},
|
||||
) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const button = 2;
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(pointerdown({button, pointerType: 'mouse'}));
|
||||
}
|
||||
dispatch(mousedown({button}));
|
||||
dispatch(contextmenu({button, preventDefault}));
|
||||
}
|
||||
|
||||
function dispatchModifiedClickContextMenu(
|
||||
target,
|
||||
{preventDefault = emptyFunction} = {},
|
||||
) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const button = 0;
|
||||
const ctrlKey = true;
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(pointerdown({button, ctrlKey, pointerType: 'mouse'}));
|
||||
}
|
||||
dispatch(mousedown({button, ctrlKey}));
|
||||
if (platform.get() === 'mac') {
|
||||
dispatch(contextmenu({button, ctrlKey, preventDefault}));
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchPointerHoverEnter(target, {relatedTarget, x, y} = {}) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const button = -1;
|
||||
const pointerType = 'mouse';
|
||||
const event = {
|
||||
button,
|
||||
relatedTarget,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
pageX: x,
|
||||
pageY: y,
|
||||
};
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(pointerover({pointerType, ...event}));
|
||||
dispatch(pointerenter({pointerType, ...event}));
|
||||
}
|
||||
dispatch(mouseover(event));
|
||||
dispatch(mouseenter(event));
|
||||
}
|
||||
|
||||
function dispatchPointerHoverMove(target, {x, y} = {}) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const button = -1;
|
||||
const pointerId = 1;
|
||||
const pointerType = 'mouse';
|
||||
function dispatchMove() {
|
||||
const event = {
|
||||
button,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
pageX: x,
|
||||
pageY: y,
|
||||
};
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(pointermove({pointerId, pointerType, ...event}));
|
||||
}
|
||||
dispatch(mousemove(event));
|
||||
}
|
||||
dispatchMove();
|
||||
}
|
||||
|
||||
function dispatchPointerHoverExit(target, {relatedTarget, x, y} = {}) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const button = -1;
|
||||
const pointerType = 'mouse';
|
||||
const event = {
|
||||
button,
|
||||
relatedTarget,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
pageX: x,
|
||||
pageY: y,
|
||||
};
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(pointerout({pointerType, ...event}));
|
||||
dispatch(pointerleave({pointerType, ...event}));
|
||||
}
|
||||
dispatch(mouseout(event));
|
||||
dispatch(mouseleave(event));
|
||||
}
|
||||
|
||||
function dispatchPointerCancel(target, {pointerType = 'mouse', ...rest} = {}) {
|
||||
const dispatchEvent = arg => target.dispatchEvent(arg);
|
||||
if (hasPointerEvent()) {
|
||||
dispatchEvent(pointercancel({pointerType, ...rest}));
|
||||
} else {
|
||||
if (pointerType === 'mouse') {
|
||||
dispatchEvent(dragstart({...rest}));
|
||||
} else {
|
||||
dispatchEvent(touchcancel({...rest}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchPointerDown(
|
||||
target,
|
||||
{button = 0, pointerType = 'mouse', ...rest} = {},
|
||||
) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerId = 1;
|
||||
const pointerEvent = {button, pointerId, pointerType, ...rest};
|
||||
const mouseEvent = {button, ...rest};
|
||||
const touch = {...rest};
|
||||
|
||||
if (pointerType === 'mouse') {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(pointerover(pointerEvent));
|
||||
dispatch(pointerenter(pointerEvent));
|
||||
}
|
||||
dispatch(mouseover(mouseEvent));
|
||||
dispatch(mouseenter(mouseEvent));
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(pointerdown(pointerEvent));
|
||||
}
|
||||
dispatch(mousedown(mouseEvent));
|
||||
if (document.activeElement !== target) {
|
||||
dispatch(focus());
|
||||
}
|
||||
} else {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(pointerover(pointerEvent));
|
||||
dispatch(pointerenter(pointerEvent));
|
||||
dispatch(pointerdown(pointerEvent));
|
||||
}
|
||||
dispatch(touchstart(touch, pointerId));
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(gotpointercapture(pointerEvent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchPointerUp(
|
||||
target,
|
||||
{button = 0, pointerType = 'mouse', ...rest} = {},
|
||||
) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerId = 1;
|
||||
const pointerEvent = {button, pointerId, pointerType, ...rest};
|
||||
const mouseEvent = {button, ...rest};
|
||||
const touch = {...rest};
|
||||
|
||||
if (pointerType === 'mouse') {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(pointerup(pointerEvent));
|
||||
}
|
||||
dispatch(mouseup(mouseEvent));
|
||||
dispatch(click(mouseEvent));
|
||||
} else {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(pointerup(pointerEvent));
|
||||
dispatch(lostpointercapture(pointerEvent));
|
||||
dispatch(pointerout(pointerEvent));
|
||||
dispatch(pointerleave(pointerEvent));
|
||||
}
|
||||
dispatch(touchend(touch, pointerId));
|
||||
dispatch(mouseover(mouseEvent));
|
||||
dispatch(mousemove(mouseEvent));
|
||||
dispatch(mousedown(mouseEvent));
|
||||
if (document.activeElement !== target) {
|
||||
dispatch(focus());
|
||||
}
|
||||
dispatch(mouseup(mouseEvent));
|
||||
dispatch(click(mouseEvent));
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchPointerMove(
|
||||
target,
|
||||
{button = 0, pointerType = 'mouse', ...rest} = {},
|
||||
) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerId = 1;
|
||||
const pointerEvent = {
|
||||
button,
|
||||
pointerId,
|
||||
pointerType,
|
||||
...rest,
|
||||
};
|
||||
const mouseEvent = {
|
||||
button,
|
||||
...rest,
|
||||
};
|
||||
const touch = {
|
||||
...rest,
|
||||
};
|
||||
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(pointermove(pointerEvent));
|
||||
}
|
||||
if (pointerType === 'mouse') {
|
||||
dispatch(mousemove(mouseEvent));
|
||||
}
|
||||
if (pointerType === 'touch') {
|
||||
dispatch(touchmove(touch, pointerId));
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchTouchTap(target) {
|
||||
dispatchPointerDown(target, {pointerType: 'touch'});
|
||||
dispatchPointerUp(target, {pointerType: 'touch'});
|
||||
}
|
||||
|
||||
function dispatchMouseTap(target) {
|
||||
dispatchPointerDown(target, {pointerType: 'mouse'});
|
||||
dispatchPointerUp(target, {pointerType: 'mouse'});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
blur,
|
||||
click,
|
||||
focus,
|
||||
createEvent,
|
||||
dispatchLongPressContextMenu,
|
||||
dispatchRightClickContextMenu,
|
||||
dispatchModifiedClickContextMenu,
|
||||
dispatchPointerCancel,
|
||||
dispatchPointerHoverEnter,
|
||||
dispatchPointerHoverExit,
|
||||
dispatchPointerHoverMove,
|
||||
dispatchPointerMove,
|
||||
dispatchPointerDown,
|
||||
dispatchPointerUp,
|
||||
dispatchTouchTap,
|
||||
dispatchMouseTap,
|
||||
keydown,
|
||||
keyup,
|
||||
scroll,
|
||||
pointerdown,
|
||||
pointerup,
|
||||
platform,
|
||||
hasPointerEvent,
|
||||
setPointerEvent,
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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';
|
||||
/**
|
||||
* Change environment support for PointerEvent.
|
||||
*/
|
||||
|
||||
export function hasPointerEvent() {
|
||||
return global != null && global.PointerEvent != null;
|
||||
}
|
||||
|
||||
export function setPointerEvent(bool) {
|
||||
global.PointerEvent = bool ? function() {} : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change environment host platform.
|
||||
*/
|
||||
|
||||
const platformGetter = jest.spyOn(global.navigator, 'platform', 'get');
|
||||
|
||||
export const platform = {
|
||||
clear() {
|
||||
platformGetter.mockClear();
|
||||
},
|
||||
get() {
|
||||
return global.navigator.platform === 'MacIntel' ? 'mac' : 'windows';
|
||||
},
|
||||
set(name: 'mac' | 'windows') {
|
||||
switch (name) {
|
||||
case 'mac': {
|
||||
platformGetter.mockReturnValue('MacIntel');
|
||||
break;
|
||||
}
|
||||
case 'windows': {
|
||||
platformGetter.mockReturnValue('Win32');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
import * as domEvents from './domEvents';
|
||||
import {hasPointerEvent, platform} from './domEnvironment';
|
||||
|
||||
function emptyFunction() {}
|
||||
|
||||
function getPointerType(payload) {
|
||||
let pointerType = 'mouse';
|
||||
if (payload != null && payload.pointerType != null) {
|
||||
pointerType = payload.pointerType;
|
||||
}
|
||||
return pointerType;
|
||||
}
|
||||
|
||||
export function contextmenu(
|
||||
target,
|
||||
{preventDefault = emptyFunction} = {},
|
||||
{pointerType = 'mouse', modified} = {},
|
||||
) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
if (pointerType === 'touch') {
|
||||
const button = 0;
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerdown({button, pointerType}));
|
||||
}
|
||||
dispatch(domEvents.touchstart());
|
||||
dispatch(domEvents.contextmenu({button, preventDefault}));
|
||||
} else if (pointerType === 'mouse') {
|
||||
if (modified === true) {
|
||||
const button = 0;
|
||||
const ctrlKey = true;
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerdown({button, ctrlKey, pointerType}));
|
||||
}
|
||||
dispatch(domEvents.mousedown({button, ctrlKey}));
|
||||
if (platform.get() === 'mac') {
|
||||
dispatch(domEvents.contextmenu({button, ctrlKey, preventDefault}));
|
||||
}
|
||||
} else {
|
||||
const button = 2;
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerdown({button, pointerType}));
|
||||
}
|
||||
dispatch(domEvents.mousedown({button}));
|
||||
dispatch(domEvents.contextmenu({button, preventDefault}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function pointercancel(target, payload) {
|
||||
const dispatchEvent = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(payload);
|
||||
if (hasPointerEvent()) {
|
||||
dispatchEvent(domEvents.pointercancel(payload));
|
||||
} else {
|
||||
if (pointerType === 'mouse') {
|
||||
dispatchEvent(domEvents.dragstart(payload));
|
||||
} else {
|
||||
dispatchEvent(domEvents.touchcancel(payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function pointerdown(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(defaultPayload);
|
||||
const payload = {button: 0, buttons: 2, ...defaultPayload};
|
||||
|
||||
if (pointerType === 'mouse') {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerover(payload));
|
||||
dispatch(domEvents.pointerenter(payload));
|
||||
}
|
||||
dispatch(domEvents.mouseover(payload));
|
||||
dispatch(domEvents.mouseenter(payload));
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerdown(payload));
|
||||
}
|
||||
dispatch(domEvents.mousedown(payload));
|
||||
if (document.activeElement !== target) {
|
||||
dispatch(domEvents.focus());
|
||||
}
|
||||
} else {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerover(payload));
|
||||
dispatch(domEvents.pointerenter(payload));
|
||||
dispatch(domEvents.pointerdown(payload));
|
||||
}
|
||||
dispatch(domEvents.touchstart(payload));
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.gotpointercapture(payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function pointerenter(target, payload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerover(payload));
|
||||
dispatch(domEvents.pointerenter(payload));
|
||||
}
|
||||
dispatch(domEvents.mouseover(payload));
|
||||
dispatch(domEvents.mouseenter(payload));
|
||||
}
|
||||
|
||||
export function pointerexit(target, payload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerout(payload));
|
||||
dispatch(domEvents.pointerleave(payload));
|
||||
}
|
||||
dispatch(domEvents.mouseout(payload));
|
||||
dispatch(domEvents.mouseleave(payload));
|
||||
}
|
||||
|
||||
export function pointerhover(target, payload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointermove(payload));
|
||||
}
|
||||
dispatch(domEvents.mousemove(payload));
|
||||
}
|
||||
|
||||
export function pointermove(target, payload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(payload);
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointermove(payload));
|
||||
}
|
||||
if (pointerType === 'mouse') {
|
||||
dispatch(domEvents.mousemove(payload));
|
||||
} else {
|
||||
dispatch(domEvents.touchmove(payload));
|
||||
}
|
||||
}
|
||||
|
||||
export function pointerup(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(defaultPayload);
|
||||
const payload = {button: 0, buttons: 2, ...defaultPayload};
|
||||
|
||||
if (pointerType === 'mouse') {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerup(payload));
|
||||
}
|
||||
dispatch(domEvents.mouseup(payload));
|
||||
dispatch(domEvents.click(payload));
|
||||
} else {
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerup(payload));
|
||||
dispatch(domEvents.lostpointercapture(payload));
|
||||
dispatch(domEvents.pointerout(payload));
|
||||
dispatch(domEvents.pointerleave(payload));
|
||||
}
|
||||
dispatch(domEvents.touchend(payload));
|
||||
dispatch(domEvents.mouseover(payload));
|
||||
dispatch(domEvents.mousemove(payload));
|
||||
dispatch(domEvents.mousedown(payload));
|
||||
if (document.activeElement !== target) {
|
||||
dispatch(domEvents.focus());
|
||||
}
|
||||
dispatch(domEvents.mouseup(payload));
|
||||
dispatch(domEvents.click(payload));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* Native event object mocks for higher-level events.
|
||||
*
|
||||
* 1. Each event type defines the exact object that it accepts. This ensures
|
||||
* that no arbitrary properties can be assigned to events, and the properties
|
||||
* that don't exist on specific event types (e.g., 'pointerType') are not added
|
||||
* to the respective native event.
|
||||
*
|
||||
* 2. Properties that cannot be relied on due to inconsistent browser support (e.g., 'x' and 'y') are not
|
||||
* added to the native event. Others that shouldn't be arbitrarily customized (e.g., 'screenX')
|
||||
* are automatically inferred from associated values.
|
||||
*
|
||||
* 3. PointerEvent and TouchEvent fields are normalized (e.g., 'rotationAngle' -> 'twist')
|
||||
*/
|
||||
|
||||
function emptyFunction() {}
|
||||
|
||||
function createEvent(type, data = {}) {
|
||||
const event = document.createEvent('CustomEvent');
|
||||
event.initCustomEvent(type, true, true);
|
||||
if (data != null) {
|
||||
Object.keys(data).forEach(key => {
|
||||
const value = data[key];
|
||||
Object.defineProperty(event, key, {value});
|
||||
});
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
function createGetModifierState(keyArg, data) {
|
||||
if (keyArg === 'Alt') {
|
||||
return data.altKey || false;
|
||||
}
|
||||
if (keyArg === 'Control') {
|
||||
return data.ctrlKey || false;
|
||||
}
|
||||
if (keyArg === 'Meta') {
|
||||
return data.metaKey || false;
|
||||
}
|
||||
if (keyArg === 'Shift') {
|
||||
return data.shiftKey || false;
|
||||
}
|
||||
}
|
||||
|
||||
function createPointerEvent(
|
||||
type,
|
||||
{
|
||||
altKey = false,
|
||||
button = -1,
|
||||
buttons = 0,
|
||||
ctrlKey = false,
|
||||
height,
|
||||
metaKey = false,
|
||||
movementX = 0,
|
||||
movementY = 0,
|
||||
offsetX = 0,
|
||||
offsetY = 0,
|
||||
pageX,
|
||||
pageY,
|
||||
pointerId = 1,
|
||||
pressure = 0,
|
||||
preventDefault = emptyFunction,
|
||||
pointerType = 'mouse',
|
||||
shiftKey = false,
|
||||
tangentialPressure = 0,
|
||||
tiltX = 0,
|
||||
tiltY = 0,
|
||||
twist = 0,
|
||||
width,
|
||||
x = 0,
|
||||
y = 0,
|
||||
} = {},
|
||||
) {
|
||||
const modifierState = {altKey, ctrlKey, metaKey, shiftKey};
|
||||
|
||||
return createEvent(type, {
|
||||
altKey,
|
||||
button,
|
||||
buttons,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
ctrlKey,
|
||||
getModifierState(keyArg) {
|
||||
createGetModifierState(keyArg, modifierState);
|
||||
},
|
||||
height: pointerType === 'mouse' ? 1 : height != null ? height : 11.5,
|
||||
metaKey,
|
||||
movementX,
|
||||
movementY,
|
||||
offsetX,
|
||||
offsetY,
|
||||
pageX: pageX || x,
|
||||
pageY: pageY || y,
|
||||
pointerId,
|
||||
pointerType,
|
||||
pressure,
|
||||
preventDefault,
|
||||
screenX: x,
|
||||
screenY: y + 50, // arbitrary value to emulate browser chrome, etc
|
||||
shiftKey,
|
||||
tangentialPressure,
|
||||
tiltX,
|
||||
tiltY,
|
||||
twist,
|
||||
width: pointerType === 'mouse' ? 1 : width != null ? width : 11.5,
|
||||
});
|
||||
}
|
||||
|
||||
function createKeyboardEvent(
|
||||
type,
|
||||
{
|
||||
altKey = false,
|
||||
ctrlKey = false,
|
||||
key = '',
|
||||
metaKey = false,
|
||||
preventDefault = emptyFunction,
|
||||
shiftKey = false,
|
||||
} = {},
|
||||
) {
|
||||
const modifierState = {altKey, ctrlKey, metaKey, shiftKey};
|
||||
|
||||
return createEvent(type, {
|
||||
altKey,
|
||||
ctrlKey,
|
||||
getModifierState(keyArg) {
|
||||
createGetModifierState(keyArg, modifierState);
|
||||
},
|
||||
key,
|
||||
metaKey,
|
||||
preventDefault,
|
||||
shiftKey,
|
||||
});
|
||||
}
|
||||
|
||||
function createMouseEvent(
|
||||
type,
|
||||
{
|
||||
altKey = false,
|
||||
button = -1,
|
||||
buttons = 0,
|
||||
ctrlKey = false,
|
||||
metaKey = false,
|
||||
movementX = 0,
|
||||
movementY = 0,
|
||||
offsetX = 0,
|
||||
offsetY = 0,
|
||||
pageX,
|
||||
pageY,
|
||||
preventDefault = emptyFunction,
|
||||
shiftKey = false,
|
||||
x = 0,
|
||||
y = 0,
|
||||
} = {},
|
||||
) {
|
||||
const modifierState = {altKey, ctrlKey, metaKey, shiftKey};
|
||||
|
||||
return createEvent(type, {
|
||||
altKey,
|
||||
button,
|
||||
buttons,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
ctrlKey,
|
||||
getModifierState(keyArg) {
|
||||
createGetModifierState(keyArg, modifierState);
|
||||
},
|
||||
metaKey,
|
||||
movementX,
|
||||
movementY,
|
||||
offsetX,
|
||||
offsetY,
|
||||
pageX: pageX || x,
|
||||
pageY: pageY || y,
|
||||
preventDefault,
|
||||
screenX: x,
|
||||
screenY: y + 50, // arbitrary value to emulate browser chrome, etc
|
||||
shiftKey,
|
||||
});
|
||||
}
|
||||
|
||||
function createTouchEvent(
|
||||
type,
|
||||
{
|
||||
altKey = false,
|
||||
ctrlKey = false,
|
||||
height = 11.5,
|
||||
metaKey = false,
|
||||
pageX,
|
||||
pageY,
|
||||
pointerId = 1,
|
||||
preventDefault = emptyFunction,
|
||||
shiftKey = false,
|
||||
twist = 0,
|
||||
width = 11.5,
|
||||
x = 0,
|
||||
y = 0,
|
||||
} = {},
|
||||
) {
|
||||
const touch = {
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
force: 1,
|
||||
identifier: pointerId,
|
||||
pageX: pageX || x,
|
||||
pageY: pageY || y,
|
||||
radiusX: width,
|
||||
radiusY: height,
|
||||
rotationAngle: twist,
|
||||
screenX: x,
|
||||
screenY: y + 50, // arbitrary value to emulate browser chrome, etc
|
||||
};
|
||||
|
||||
return createEvent(type, {
|
||||
altKey,
|
||||
changedTouches: [touch],
|
||||
ctrlKey,
|
||||
metaKey,
|
||||
preventDefault,
|
||||
shiftKey,
|
||||
targetTouches: type !== 'touchend' ? [touch] : null,
|
||||
touches: [touch],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock event objects
|
||||
*/
|
||||
|
||||
export function blur({relatedTarget} = {}) {
|
||||
return createEvent('blur', {relatedTarget});
|
||||
}
|
||||
|
||||
export function click(payload) {
|
||||
return createMouseEvent('click', payload);
|
||||
}
|
||||
|
||||
export function contextmenu(payload) {
|
||||
return createMouseEvent('contextmenu', payload);
|
||||
}
|
||||
|
||||
export function dragstart(payload) {
|
||||
return createMouseEvent('dragstart', payload);
|
||||
}
|
||||
|
||||
export function focus({relatedTarget} = {}) {
|
||||
return createEvent('focus', {relatedTarget});
|
||||
}
|
||||
|
||||
export function scroll() {
|
||||
return createEvent('scroll');
|
||||
}
|
||||
|
||||
/**
|
||||
* Key events
|
||||
*/
|
||||
|
||||
export function keydown(payload) {
|
||||
return createKeyboardEvent('keydown', payload);
|
||||
}
|
||||
|
||||
export function keyup(payload) {
|
||||
return createKeyboardEvent('keyup', payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pointer events
|
||||
*/
|
||||
|
||||
export function gotpointercapture(payload) {
|
||||
return createPointerEvent('gotpointercapture', payload);
|
||||
}
|
||||
|
||||
export function lostpointercapture(payload) {
|
||||
return createPointerEvent('lostpointercapture', payload);
|
||||
}
|
||||
|
||||
export function pointercancel(payload) {
|
||||
return createPointerEvent('pointercancel', payload);
|
||||
}
|
||||
|
||||
export function pointerdown(payload) {
|
||||
return createPointerEvent('pointerdown', {button: 0, buttons: 2, ...payload});
|
||||
}
|
||||
|
||||
export function pointerenter(payload) {
|
||||
return createPointerEvent('pointerenter', payload);
|
||||
}
|
||||
|
||||
export function pointerleave(payload) {
|
||||
return createPointerEvent('pointerleave', payload);
|
||||
}
|
||||
|
||||
export function pointermove(payload) {
|
||||
return createPointerEvent('pointermove', payload);
|
||||
}
|
||||
|
||||
export function pointerout(payload) {
|
||||
return createPointerEvent('pointerout', payload);
|
||||
}
|
||||
|
||||
export function pointerover(payload) {
|
||||
return createPointerEvent('pointerover', payload);
|
||||
}
|
||||
|
||||
export function pointerup(payload) {
|
||||
return createPointerEvent('pointerup', {button: 0, buttons: 2, ...payload});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mouse events
|
||||
*/
|
||||
|
||||
export function mousedown(payload) {
|
||||
return createMouseEvent('mousedown', {button: 0, buttons: 2, ...payload});
|
||||
}
|
||||
|
||||
export function mouseenter(payload) {
|
||||
return createMouseEvent('mouseenter', payload);
|
||||
}
|
||||
|
||||
export function mouseleave(payload) {
|
||||
return createMouseEvent('mouseleave', payload);
|
||||
}
|
||||
|
||||
export function mousemove(payload) {
|
||||
return createMouseEvent('mousemove', payload);
|
||||
}
|
||||
|
||||
export function mouseout(payload) {
|
||||
return createMouseEvent('mouseout', payload);
|
||||
}
|
||||
|
||||
export function mouseover(payload) {
|
||||
return createMouseEvent('mouseover', payload);
|
||||
}
|
||||
|
||||
export function mouseup(payload) {
|
||||
return createMouseEvent('mouseup', {button: 0, buttons: 2, ...payload});
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch events
|
||||
*/
|
||||
|
||||
export function touchcancel(payload) {
|
||||
return createTouchEvent('touchcancel', payload);
|
||||
}
|
||||
|
||||
export function touchend(payload) {
|
||||
return createTouchEvent('touchend', payload);
|
||||
}
|
||||
|
||||
export function touchmove(payload) {
|
||||
return createTouchEvent('touchmove', payload);
|
||||
}
|
||||
|
||||
export function touchstart(payload) {
|
||||
return createTouchEvent('touchstart', payload);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
import * as domEvents from './domEvents';
|
||||
import * as domEventSequences from './domEventSequences';
|
||||
import {hasPointerEvent, setPointerEvent, platform} from './domEnvironment';
|
||||
|
||||
const createEventTarget = node => ({
|
||||
node,
|
||||
/**
|
||||
* General events abstraction.
|
||||
*/
|
||||
blur(payload) {
|
||||
node.dispatchEvent(domEvents.blur(payload));
|
||||
},
|
||||
click(payload) {
|
||||
node.dispatchEvent(domEvents.click(payload));
|
||||
},
|
||||
contextmenu(payload, options) {
|
||||
domEventSequences.contextmenu(node, payload, options);
|
||||
},
|
||||
focus(payload) {
|
||||
node.dispatchEvent(domEvents.focus(payload));
|
||||
},
|
||||
/**
|
||||
* KeyboardEvent abstraction.
|
||||
*/
|
||||
keydown(payload) {
|
||||
node.dispatchEvent(domEvents.keydown(payload));
|
||||
},
|
||||
keyup(payload) {
|
||||
node.dispatchEvent(domEvents.keyup(payload));
|
||||
},
|
||||
scroll(payload) {
|
||||
node.dispatchEvent(domEvents.scroll(payload));
|
||||
},
|
||||
/**
|
||||
* PointerEvent abstraction.
|
||||
* Dispatches the expected sequence of PointerEvents, MouseEvents, and
|
||||
* TouchEvents for a given environment.
|
||||
*/
|
||||
// node no longer receives events for the pointer
|
||||
pointercancel(payload) {
|
||||
domEventSequences.pointercancel(node, payload);
|
||||
},
|
||||
// node dispatches down events
|
||||
pointerdown(payload) {
|
||||
domEventSequences.pointerdown(node, payload);
|
||||
},
|
||||
// node dispatches move events (pointer is not down)
|
||||
pointerhover(payload) {
|
||||
domEventSequences.pointerhover(node, payload);
|
||||
},
|
||||
// node dispatches move events (pointer is down)
|
||||
pointermove(payload) {
|
||||
domEventSequences.pointermove(node, payload);
|
||||
},
|
||||
// node dispatches enter & over events
|
||||
pointerenter(payload) {
|
||||
domEventSequences.pointerenter(node, payload);
|
||||
},
|
||||
// node dispatches exit & out events
|
||||
pointerexit(payload) {
|
||||
domEventSequences.pointerexit(node, payload);
|
||||
},
|
||||
// node dispatches up events
|
||||
pointerup(payload) {
|
||||
domEventSequences.pointerup(node, payload);
|
||||
},
|
||||
/**
|
||||
* Gesture abstractions.
|
||||
* Helpers for event sequences expected in a gesture.
|
||||
* target.tap({ pointerType: 'touch' })
|
||||
*/
|
||||
tap(payload) {
|
||||
domEventSequences.pointerdown(payload);
|
||||
domEventSequences.pointerup(payload);
|
||||
},
|
||||
/**
|
||||
* Utilities
|
||||
*/
|
||||
setBoundingClientRect({x, y, width, height}) {
|
||||
node.getBoundingClientRect = function() {
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
left: x,
|
||||
right: x + width,
|
||||
top: y,
|
||||
bottom: y + height,
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export {createEventTarget, platform, hasPointerEvent, setPointerEvent};
|
||||
Reference in New Issue
Block a user