Skip to content

Commit 2f06fbe

Browse files
authored
Merge pull request #1753 from airbnb/pointer
[New] add pointer events support
2 parents 9b06f58 + 8e7e170 commit 2f06fbe

File tree

10 files changed

+189
-22
lines changed

10 files changed

+189
-22
lines changed

packages/enzyme-adapter-react-15.4/src/ReactFifteenFourAdapter.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ function instanceToTree(inst) {
103103
};
104104
}
105105

106+
const eventOptions = { animation: true };
107+
106108
class ReactFifteenFourAdapter extends EnzymeAdapter {
107109
constructor() {
108110
super();
@@ -153,7 +155,7 @@ class ReactFifteenFourAdapter extends EnzymeAdapter {
153155
return instance ? instanceToTree(instance._reactInternalInstance).rendered : null;
154156
},
155157
simulateEvent(node, event, mock) {
156-
const mappedEvent = mapNativeEventNames(event, { animation: true });
158+
const mappedEvent = mapNativeEventNames(event, eventOptions);
157159
const eventFn = TestUtils.Simulate[mappedEvent];
158160
if (!eventFn) {
159161
throw new TypeError(`ReactWrapper::simulate() event '${event}' does not exist`);
@@ -201,7 +203,7 @@ class ReactFifteenFourAdapter extends EnzymeAdapter {
201203
};
202204
},
203205
simulateEvent(node, event, ...args) {
204-
const handler = node.props[propFromEvent(event)];
206+
const handler = node.props[propFromEvent(event, eventOptions)];
205207
if (handler) {
206208
withSetStateAllowed(() => {
207209
// TODO(lmr): create/use synthetic events

packages/enzyme-adapter-react-15/src/ReactFifteenAdapter.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ function instanceToTree(inst) {
103103
};
104104
}
105105

106+
const eventOptions = { animation: true };
107+
106108
class ReactFifteenAdapter extends EnzymeAdapter {
107109
constructor() {
108110
super();
@@ -153,7 +155,7 @@ class ReactFifteenAdapter extends EnzymeAdapter {
153155
return instance ? instanceToTree(instance._reactInternalInstance).rendered : null;
154156
},
155157
simulateEvent(node, event, mock) {
156-
const mappedEvent = mapNativeEventNames(event, { animation: true });
158+
const mappedEvent = mapNativeEventNames(event, eventOptions);
157159
const eventFn = TestUtils.Simulate[mappedEvent];
158160
if (!eventFn) {
159161
throw new TypeError(`ReactWrapper::simulate() event '${event}' does not exist`);
@@ -201,7 +203,7 @@ class ReactFifteenAdapter extends EnzymeAdapter {
201203
};
202204
},
203205
simulateEvent(node, event, ...args) {
204-
const handler = node.props[propFromEvent(event)];
206+
const handler = node.props[propFromEvent(event, eventOptions)];
205207
if (handler) {
206208
withSetStateAllowed(() => {
207209
// TODO(lmr): create/use synthetic events

packages/enzyme-adapter-react-16.1/src/ReactSixteenOneAdapter.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ function nodeToHostNode(_node) {
166166
return ReactDOM.findDOMNode(node.instance);
167167
}
168168

169+
const eventOptions = { animation: true };
170+
169171
class ReactSixteenOneAdapter extends EnzymeAdapter {
170172
constructor() {
171173
super();
@@ -218,7 +220,7 @@ class ReactSixteenOneAdapter extends EnzymeAdapter {
218220
return instance ? toTree(instance._reactInternalFiber).rendered : null;
219221
},
220222
simulateEvent(node, event, mock) {
221-
const mappedEvent = mapNativeEventNames(event, { animation: true });
223+
const mappedEvent = mapNativeEventNames(event, eventOptions);
222224
const eventFn = TestUtils.Simulate[mappedEvent];
223225
if (!eventFn) {
224226
throw new TypeError(`ReactWrapper::simulate() event '${event}' does not exist`);
@@ -283,7 +285,7 @@ class ReactSixteenOneAdapter extends EnzymeAdapter {
283285
};
284286
},
285287
simulateEvent(node, event, ...args) {
286-
const handler = node.props[propFromEvent(event)];
288+
const handler = node.props[propFromEvent(event, eventOptions)];
287289
if (handler) {
288290
withSetStateAllowed(() => {
289291
// TODO(lmr): create/use synthetic events

packages/enzyme-adapter-react-16.2/src/ReactSixteenTwoAdapter.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ function nodeToHostNode(_node) {
167167
return ReactDOM.findDOMNode(node.instance);
168168
}
169169

170+
const eventOptions = { animation: true };
171+
170172
class ReactSixteenTwoAdapter extends EnzymeAdapter {
171173
constructor() {
172174
super();
@@ -220,7 +222,7 @@ class ReactSixteenTwoAdapter extends EnzymeAdapter {
220222
return instance ? toTree(instance._reactInternalFiber).rendered : null;
221223
},
222224
simulateEvent(node, event, mock) {
223-
const mappedEvent = mapNativeEventNames(event, { animation: true });
225+
const mappedEvent = mapNativeEventNames(event, eventOptions);
224226
const eventFn = TestUtils.Simulate[mappedEvent];
225227
if (!eventFn) {
226228
throw new TypeError(`ReactWrapper::simulate() event '${event}' does not exist`);
@@ -285,7 +287,7 @@ class ReactSixteenTwoAdapter extends EnzymeAdapter {
285287
};
286288
},
287289
simulateEvent(node, event, ...args) {
288-
const handler = node.props[propFromEvent(event)];
290+
const handler = node.props[propFromEvent(event, eventOptions)];
289291
if (handler) {
290292
withSetStateAllowed(() => {
291293
// TODO(lmr): create/use synthetic events

packages/enzyme-adapter-react-16.3/src/ReactSixteenThreeAdapter.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ function nodeToHostNode(_node) {
185185
return ReactDOM.findDOMNode(node.instance);
186186
}
187187

188+
const eventOptions = { animation: true };
189+
188190
class ReactSixteenThreeAdapter extends EnzymeAdapter {
189191
constructor() {
190192
super();
@@ -238,7 +240,7 @@ class ReactSixteenThreeAdapter extends EnzymeAdapter {
238240
return instance ? toTree(instance._reactInternalFiber).rendered : null;
239241
},
240242
simulateEvent(node, event, mock) {
241-
const mappedEvent = mapNativeEventNames(event, { animation: true });
243+
const mappedEvent = mapNativeEventNames(event, eventOptions);
242244
const eventFn = TestUtils.Simulate[mappedEvent];
243245
if (!eventFn) {
244246
throw new TypeError(`ReactWrapper::simulate() event '${event}' does not exist`);
@@ -303,7 +305,7 @@ class ReactSixteenThreeAdapter extends EnzymeAdapter {
303305
};
304306
},
305307
simulateEvent(node, event, ...args) {
306-
const handler = node.props[propFromEvent(event)];
308+
const handler = node.props[propFromEvent(event, eventOptions)];
307309
if (handler) {
308310
withSetStateAllowed(() => {
309311
// TODO(lmr): create/use synthetic events

packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@ function nodeToHostNode(_node) {
186186
return ReactDOM.findDOMNode(node.instance);
187187
}
188188

189+
const eventOptions = {
190+
animation: true,
191+
pointerEvents: true, // 16.4+
192+
};
193+
189194
class ReactSixteenAdapter extends EnzymeAdapter {
190195
constructor() {
191196
super();
@@ -239,12 +244,11 @@ class ReactSixteenAdapter extends EnzymeAdapter {
239244
return instance ? toTree(instance._reactInternalFiber).rendered : null;
240245
},
241246
simulateEvent(node, event, mock) {
242-
const mappedEvent = mapNativeEventNames(event, { animation: true });
247+
const mappedEvent = mapNativeEventNames(event, eventOptions);
243248
const eventFn = TestUtils.Simulate[mappedEvent];
244249
if (!eventFn) {
245250
throw new TypeError(`ReactWrapper::simulate() event '${event}' does not exist`);
246251
}
247-
// eslint-disable-next-line react/no-find-dom-node
248252
eventFn(nodeToHostNode(node), mock);
249253
},
250254
batchedUpdates(fn) {
@@ -304,7 +308,7 @@ class ReactSixteenAdapter extends EnzymeAdapter {
304308
};
305309
},
306310
simulateEvent(node, event, ...args) {
307-
const handler = node.props[propFromEvent(event)];
311+
const handler = node.props[propFromEvent(event, eventOptions)];
308312
if (handler) {
309313
withSetStateAllowed(() => {
310314
// TODO(lmr): create/use synthetic events
@@ -340,7 +344,7 @@ class ReactSixteenAdapter extends EnzymeAdapter {
340344

341345
// Provided a bag of options, return an `EnzymeRenderer`. Some options can be implementation
342346
// specific, like `attach` etc. for React, but not part of this interface explicitly.
343-
// eslint-disable-next-line class-methods-use-this, no-unused-vars
347+
// eslint-disable-next-line class-methods-use-this
344348
createRenderer(options) {
345349
switch (options.mode) {
346350
case EnzymeAdapter.MODES.MOUNT: return this.createMountRenderer(options);
@@ -354,7 +358,7 @@ class ReactSixteenAdapter extends EnzymeAdapter {
354358
// converts an RSTNode to the corresponding JSX Pragma Element. This will be needed
355359
// in order to implement the `Wrapper.mount()` and `Wrapper.shallow()` methods, but should
356360
// be pretty straightforward for people to implement.
357-
// eslint-disable-next-line class-methods-use-this, no-unused-vars
361+
// eslint-disable-next-line class-methods-use-this
358362
nodeToElement(node) {
359363
if (!node || typeof node !== 'object') return null;
360364
return React.createElement(node.type, propsWithKeysAndRef(node));

packages/enzyme-adapter-utils/src/Utils.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export { createMountWrapper, createRenderWrapper };
66

77
export function mapNativeEventNames(event, {
88
animation = false, // should be true for React 15+
9+
pointerEvents = false, // should be true for React 16.4+
910
} = {}) {
1011
const nativeToReactEventMap = {
1112
compositionend: 'compositionEnd',
@@ -50,15 +51,27 @@ export function mapNativeEventNames(event, {
5051
animationiteration: 'animationIteration',
5152
animationend: 'animationEnd',
5253
}),
54+
...(pointerEvents && {
55+
pointerdown: 'pointerDown',
56+
pointermove: 'pointerMove',
57+
pointerup: 'pointerUp',
58+
pointercancel: 'pointerCancel',
59+
gotpointercapture: 'gotPointerCapture',
60+
lostpointercapture: 'lostPointerCapture',
61+
pointerenter: 'pointerEnter',
62+
pointerleave: 'pointerLeave',
63+
pointerover: 'pointerOver',
64+
pointerout: 'pointerOut',
65+
}),
5366
};
5467

5568
return nativeToReactEventMap[event] || event;
5669
}
5770

5871
// 'click' => 'onClick'
5972
// 'mouseEnter' => 'onMouseEnter'
60-
export function propFromEvent(event) {
61-
const nativeEvent = mapNativeEventNames(event);
73+
export function propFromEvent(event, eventOptions = {}) {
74+
const nativeEvent = mapNativeEventNames(event, eventOptions);
6275
return `on${nativeEvent[0].toUpperCase()}${nativeEvent.slice(1)}`;
6376
}
6477

packages/enzyme-test-suite/test/ReactWrapper-spec.jsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2061,6 +2061,66 @@ describeWithDOM('mount', () => {
20612061
expect(spy).to.have.property('callCount', 1);
20622062
});
20632063
});
2064+
2065+
describeIf(is('>= 15'), 'animation events', () => {
2066+
it('should convert lowercase events to React camelcase', () => {
2067+
const spy = sinon.spy();
2068+
class Foo extends React.Component {
2069+
render() {
2070+
return (
2071+
<a onAnimationIteration={spy}>foo</a>
2072+
);
2073+
}
2074+
}
2075+
2076+
const wrapper = mount(<Foo />);
2077+
2078+
wrapper.simulate('animationiteration');
2079+
expect(spy).to.have.property('callCount', 1);
2080+
});
2081+
2082+
it('should convert lowercase events to React camelcase in stateless components', () => {
2083+
const spy = sinon.spy();
2084+
const Foo = () => (
2085+
<a onAnimationIteration={spy}>foo</a>
2086+
);
2087+
2088+
const wrapper = mount(<Foo />);
2089+
2090+
wrapper.simulate('animationiteration');
2091+
expect(spy).to.have.property('callCount', 1);
2092+
});
2093+
});
2094+
2095+
describeIf(is('>= 16.4'), 'pointer events', () => {
2096+
it('should convert lowercase events to React camelcase', () => {
2097+
const spy = sinon.spy();
2098+
class Foo extends React.Component {
2099+
render() {
2100+
return (
2101+
<a onGotPointerCapture={spy}>foo</a>
2102+
);
2103+
}
2104+
}
2105+
2106+
const wrapper = mount(<Foo />);
2107+
2108+
wrapper.simulate('gotpointercapture');
2109+
expect(spy).to.have.property('callCount', 1);
2110+
});
2111+
2112+
it('should convert lowercase events to React camelcase in stateless components', () => {
2113+
const spy = sinon.spy();
2114+
const Foo = () => (
2115+
<a onGotPointerCapture={spy}>foo</a>
2116+
);
2117+
2118+
const wrapper = mount(<Foo />);
2119+
2120+
wrapper.simulate('gotpointercapture');
2121+
expect(spy).to.have.property('callCount', 1);
2122+
});
2123+
});
20642124
});
20652125

20662126
it('should be batched updates', () => {

packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1982,6 +1982,66 @@ describe('shallow', () => {
19821982
expect(spy).to.have.property('callCount', 1);
19831983
});
19841984
});
1985+
1986+
describeIf(is('>= 15'), 'animation events', () => {
1987+
it('should convert lowercase events to React camelcase', () => {
1988+
const spy = sinon.spy();
1989+
class Foo extends React.Component {
1990+
render() {
1991+
return (
1992+
<a onAnimationIteration={spy}>foo</a>
1993+
);
1994+
}
1995+
}
1996+
1997+
const wrapper = shallow(<Foo />);
1998+
1999+
wrapper.simulate('animationiteration');
2000+
expect(spy).to.have.property('callCount', 1);
2001+
});
2002+
2003+
it('should convert lowercase events to React camelcase in stateless components', () => {
2004+
const spy = sinon.spy();
2005+
const Foo = () => (
2006+
<a onAnimationIteration={spy}>foo</a>
2007+
);
2008+
2009+
const wrapper = shallow(<Foo />);
2010+
2011+
wrapper.simulate('animationiteration');
2012+
expect(spy).to.have.property('callCount', 1);
2013+
});
2014+
});
2015+
2016+
describeIf(is('>= 16.4'), 'pointer events', () => {
2017+
it('should convert lowercase events to React camelcase', () => {
2018+
const spy = sinon.spy();
2019+
class Foo extends React.Component {
2020+
render() {
2021+
return (
2022+
<a onGotPointerCapture={spy}>foo</a>
2023+
);
2024+
}
2025+
}
2026+
2027+
const wrapper = shallow(<Foo />);
2028+
2029+
wrapper.simulate('gotpointercapture');
2030+
expect(spy).to.have.property('callCount', 1);
2031+
});
2032+
2033+
it('should convert lowercase events to React camelcase in stateless components', () => {
2034+
const spy = sinon.spy();
2035+
const Foo = () => (
2036+
<a onGotPointerCapture={spy}>foo</a>
2037+
);
2038+
2039+
const wrapper = shallow(<Foo />);
2040+
2041+
wrapper.simulate('gotpointercapture');
2042+
expect(spy).to.have.property('callCount', 1);
2043+
});
2044+
});
19852045
});
19862046

19872047
itIf(BATCHING, 'should be batched updates', () => {

0 commit comments

Comments
 (0)