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'); + }); + }); }); });