diff --git a/src/ReactWrapper.js b/src/ReactWrapper.js
index b2cf0fe71..20ee10882 100644
--- a/src/ReactWrapper.js
+++ b/src/ReactWrapper.js
@@ -15,6 +15,7 @@ import {
Simulate,
findDOMNode,
} from './react-compat';
+import { mapNativeEventNames } from './Utils';
/**
* Finds all nodes in the current wrapper nodes' render trees that match the provided predicate
@@ -306,7 +307,8 @@ export default class ReactWrapper {
*/
simulate(event, ...args) {
this.single(n => {
- const eventFn = Simulate[event];
+ const mappedEvent = mapNativeEventNames(event);
+ const eventFn = Simulate[mappedEvent];
if (!eventFn) {
throw new TypeError(`ReactWrapper::simulate() event '${event}' does not exist`);
}
diff --git a/src/Utils.js b/src/Utils.js
index f49b9eb98..727e5d177 100644
--- a/src/Utils.js
+++ b/src/Utils.js
@@ -4,7 +4,10 @@ import {
isDOMComponent,
findDOMNode,
} from './react-compat';
-import { REACT013 } from './version';
+import {
+ REACT013,
+ REACT014,
+} from './version';
export function propsOfNode(node) {
if (REACT013) {
@@ -168,3 +171,49 @@ export function coercePropValue(propValue) {
// coerce to boolean
return propValue === 'true' ? true : false;
}
+
+export function mapNativeEventNames(event) {
+ const nativeToReactEventMap = {
+ compositionend: 'compositionEnd',
+ compositionstart: 'compositionStart',
+ compositionupdate: 'compositionUpdate',
+ keydown: 'keyDown',
+ keyup: 'keyUp',
+ keypress: 'keyPress',
+ contextmenu: 'contextMenu',
+ doubleclick: 'doubleClick',
+ dragend: 'dragEnd',
+ dragenter: 'dragEnter',
+ dragexist: 'dragExit',
+ dragleave: 'dragLeave',
+ dragover: 'dragOver',
+ dragstart: 'dragStart',
+ mousedown: 'mouseDown',
+ mousemove: 'mouseMove',
+ mouseout: 'mouseOut',
+ mouseover: 'mouseOver',
+ mouseup: 'mouseUp',
+ touchcancel: 'touchCancel',
+ touchend: 'touchEnd',
+ touchmove: 'touchMove',
+ touchstart: 'touchStart',
+ canplay: 'canPlay',
+ canplaythrough: 'canPlayThrough',
+ durationchange: 'durationChange',
+ loadeddata: 'loadedData',
+ loadedmetadata: 'loadedMetadata',
+ loadstart: 'loadStart',
+ ratechange: 'rateChange',
+ timeupdate: 'timeUpdate',
+ volumechange: 'volumeChange',
+ };
+
+ if (REACT014) {
+ // these could not be simulated in React 0.13:
+ // https://github.com/facebook/react/issues/1297
+ nativeToReactEventMap.mouseenter = 'mouseEnter';
+ nativeToReactEventMap.mouseleave = 'mouseLeave';
+ }
+
+ return nativeToReactEventMap[event] || event;
+}
diff --git a/src/__tests__/ReactWrapper-spec.js b/src/__tests__/ReactWrapper-spec.js
index de2896939..10efa5d2b 100644
--- a/src/__tests__/ReactWrapper-spec.js
+++ b/src/__tests__/ReactWrapper-spec.js
@@ -457,6 +457,42 @@ describeWithDOM('mount', () => {
expect(wrapper.simulate.bind(wrapper, 'invalidEvent'))
.to.throw(TypeError, "ReactWrapper::simulate() event 'invalidEvent' does not exist");
});
+
+ describe('Normalizing JS event names', () => {
+ it('should convert lowercase events to React camelcase', () => {
+ const spy = sinon.spy();
+ class Foo extends React.Component {
+ render() {
+ return (
+ foo
+ );
+ }
+ }
+
+ const wrapper = mount();
+
+ wrapper.simulate('doubleclick');
+ expect(spy.calledOnce).to.equal(true);
+ });
+
+ describeIf(!REACT013, 'normalizing mouseenter', () => {
+ it('should convert lowercase events to React camelcase', () => {
+ const spy = sinon.spy();
+ class Foo extends React.Component {
+ render() {
+ return (
+ foo
+ );
+ }
+ }
+
+ const wrapper = mount();
+
+ wrapper.simulate('mouseenter');
+ expect(spy.calledOnce).to.equal(true);
+ });
+ });
+ });
});
describe('.setState(newState)', () => {
diff --git a/src/__tests__/Utils-spec.js b/src/__tests__/Utils-spec.js
index 31422439e..49a9513b8 100644
--- a/src/__tests__/Utils-spec.js
+++ b/src/__tests__/Utils-spec.js
@@ -10,6 +10,7 @@ import {
propFromEvent,
SELECTOR,
selectorType,
+ mapNativeEventNames,
} from '../Utils';
import {
describeWithDOM,
@@ -240,7 +241,30 @@ describe('Utils', () => {
expect(coercePropValue('true')).to.equal(true);
expect(coercePropValue('false')).to.equal(false);
});
+ });
+
+ describe('mapNativeEventNames', () => {
+ describe('given an event that isn\'t a mapped', () => {
+ it('returns the original event', () => {
+ const result = mapNativeEventNames('click');
+ expect(result).to.equal('click');
+ });
+
+ });
+ describe('given a React capitalised mouse event', () => {
+ it('returns the original event', () => {
+ const result = mapNativeEventNames('mouseEnter');
+ expect(result).to.equal('mouseEnter');
+ });
+ });
+
+ describe('given a native lowercase event', () => {
+ it('transforms it into the React capitalised event', () => {
+ const result = mapNativeEventNames('dragenter');
+ expect(result).to.equal('dragEnter');
+ });
+ });
});
});