diff --git a/src/browser/ReactEventEmitter.js b/src/browser/ReactEventEmitter.js index 56de0dcb25637..11697d0caf303 100644 --- a/src/browser/ReactEventEmitter.js +++ b/src/browser/ReactEventEmitter.js @@ -318,6 +318,8 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, { } }, + eventNameDispatchConfigs: EventPluginHub.eventNameDispatchConfigs, + registrationNameModules: EventPluginHub.registrationNameModules, putListener: EventPluginHub.putListener, diff --git a/src/browser/__tests__/ReactEventEmitter-test.js b/src/browser/__tests__/ReactEventEmitter-test.js index 6c1ca8e3c05f1..2aeaecbedfb19 100644 --- a/src/browser/__tests__/ReactEventEmitter-test.js +++ b/src/browser/__tests__/ReactEventEmitter-test.js @@ -142,10 +142,10 @@ describe('ReactEventEmitter', function() { it('should not invoke handlers if ReactEventEmitter is disabled', function() { registerSimpleTestHandler(); ReactEventEmitter.setEnabled(false); - ReactTestUtils.Simulate.click(CHILD); + ReactTestUtils.SimulateNative.click(CHILD); expect(LISTENER.mock.calls.length).toBe(0); ReactEventEmitter.setEnabled(true); - ReactTestUtils.Simulate.click(CHILD); + ReactTestUtils.SimulateNative.click(CHILD); expect(LISTENER.mock.calls.length).toBe(1); }); @@ -312,11 +312,11 @@ describe('ReactEventEmitter', function() { ON_TOUCH_TAP_KEY, recordID.bind(null, getID(CHILD)) ); - ReactTestUtils.Simulate.touchStart( + ReactTestUtils.SimulateNative.touchStart( CHILD, ReactTestUtils.nativeTouchData(0, 0) ); - ReactTestUtils.Simulate.touchEnd( + ReactTestUtils.SimulateNative.touchEnd( CHILD, ReactTestUtils.nativeTouchData(0, 0) ); @@ -330,11 +330,11 @@ describe('ReactEventEmitter', function() { ON_TOUCH_TAP_KEY, recordID.bind(null, getID(CHILD)) ); - ReactTestUtils.Simulate.touchStart( + ReactTestUtils.SimulateNative.touchStart( CHILD, ReactTestUtils.nativeTouchData(0, 0) ); - ReactTestUtils.Simulate.touchEnd( + ReactTestUtils.SimulateNative.touchEnd( CHILD, ReactTestUtils.nativeTouchData(0, tapMoveThreshold - 1) ); @@ -348,11 +348,11 @@ describe('ReactEventEmitter', function() { ON_TOUCH_TAP_KEY, recordID.bind(null, getID(CHILD)) ); - ReactTestUtils.Simulate.touchStart( + ReactTestUtils.SimulateNative.touchStart( CHILD, ReactTestUtils.nativeTouchData(0, 0) ); - ReactTestUtils.Simulate.touchEnd( + ReactTestUtils.SimulateNative.touchEnd( CHILD, ReactTestUtils.nativeTouchData(0, tapMoveThreshold + 1) ); @@ -415,11 +415,11 @@ describe('ReactEventEmitter', function() { ON_TOUCH_TAP_KEY, recordID.bind(null, getID(GRANDPARENT)) ); - ReactTestUtils.Simulate.touchStart( + ReactTestUtils.SimulateNative.touchStart( CHILD, ReactTestUtils.nativeTouchData(0, 0) ); - ReactTestUtils.Simulate.touchEnd( + ReactTestUtils.SimulateNative.touchEnd( CHILD, ReactTestUtils.nativeTouchData(0, 0) ); diff --git a/src/browser/dom/components/__tests__/ReactDOMInput-test.js b/src/browser/dom/components/__tests__/ReactDOMInput-test.js index 88e91f2b9d949..d2049a6d517c9 100644 --- a/src/browser/dom/components/__tests__/ReactDOMInput-test.js +++ b/src/browser/dom/components/__tests__/ReactDOMInput-test.js @@ -125,7 +125,7 @@ describe('ReactDOMInput', function() { var node = renderTextInput(stub); node.value = 'giraffe'; - ReactTestUtils.Simulate.input(node); + ReactTestUtils.Simulate.change(node); expect(node.value).toBe('0'); }); @@ -171,9 +171,8 @@ describe('ReactDOMInput', function() { aNode.checked = false; expect(cNode.checked).toBe(true); - // Now let's run the actual ReactDOMInput change event handler (on radio - // inputs, ChangeEventPlugin listens for the `click` event so trigger that) - ReactTestUtils.Simulate.click(bNode); + // Now let's run the actual ReactDOMInput change event handler + ReactTestUtils.Simulate.change(bNode); // The original state should have been restored expect(aNode.checked).toBe(true); @@ -192,7 +191,7 @@ describe('ReactDOMInput', function() { expect(link.requestChange.mock.calls.length).toBe(0); instance.getDOMNode().value = 'test'; - ReactTestUtils.Simulate.input(instance.getDOMNode()); + ReactTestUtils.Simulate.change(instance.getDOMNode()); expect(link.requestChange.mock.calls.length).toBe(1); expect(link.requestChange.mock.calls[0][0]).toEqual('test'); @@ -265,7 +264,7 @@ describe('ReactDOMInput', function() { expect(link.requestChange.mock.calls.length).toBe(0); instance.getDOMNode().checked = false; - ReactTestUtils.Simulate.click(instance.getDOMNode()); + ReactTestUtils.Simulate.change(instance.getDOMNode()); expect(link.requestChange.mock.calls.length).toBe(1); expect(link.requestChange.mock.calls[0][0]).toEqual(false); diff --git a/src/browser/dom/components/__tests__/ReactDOMTextarea-test.js b/src/browser/dom/components/__tests__/ReactDOMTextarea-test.js index 955e3c13f2f2b..576f21997ca7e 100644 --- a/src/browser/dom/components/__tests__/ReactDOMTextarea-test.js +++ b/src/browser/dom/components/__tests__/ReactDOMTextarea-test.js @@ -139,7 +139,7 @@ describe('ReactDOMTextarea', function() { var node = renderTextarea(stub); node.value = 'giraffe'; - ReactTestUtils.Simulate.input(node); + ReactTestUtils.Simulate.change(node); expect(node.value).toBe('0'); }); @@ -216,7 +216,7 @@ describe('ReactDOMTextarea', function() { expect(link.requestChange.mock.calls.length).toBe(0); instance.getDOMNode().value = 'test'; - ReactTestUtils.Simulate.input(instance.getDOMNode()); + ReactTestUtils.Simulate.change(instance.getDOMNode()); expect(link.requestChange.mock.calls.length).toBe(1); expect(link.requestChange.mock.calls[0][0]).toEqual('test'); diff --git a/src/browser/eventPlugins/__tests__/AnalyticsEventPlugin-test.js b/src/browser/eventPlugins/__tests__/AnalyticsEventPlugin-test.js index 14059dec8665a..770d73db6ec02 100644 --- a/src/browser/eventPlugins/__tests__/AnalyticsEventPlugin-test.js +++ b/src/browser/eventPlugins/__tests__/AnalyticsEventPlugin-test.js @@ -99,14 +99,14 @@ describe('AnalyticsEventPlugin', function() { // Simulate some clicks for (var i = 0; i < numClickEvents; i++) { - ReactTestUtils.Simulate.click(renderedComponent.refs.testDiv); + ReactTestUtils.SimulateNative.click(renderedComponent.refs.testDiv); } // Simulate some double clicks for (i = 0; i < numDoubleClickEvents; i++) { - ReactTestUtils.Simulate.doubleClick(renderedComponent.refs.testDiv); + ReactTestUtils.SimulateNative.doubleClick(renderedComponent.refs.testDiv); } // Simulate some other events not being tracked for analytics - ReactTestUtils.Simulate.focus(renderedComponent.refs.testDiv); + ReactTestUtils.SimulateNative.focus(renderedComponent.refs.testDiv); window.mockRunTimersOnce(); expect(cb).toBeCalled(); @@ -143,7 +143,7 @@ describe('AnalyticsEventPlugin', function() { var error = false; try { - ReactTestUtils.Simulate.click(renderedComponent.refs.testDiv); + ReactTestUtils.SimulateNative.click(renderedComponent.refs.testDiv); } catch(e) { error = true; } diff --git a/src/core/__tests__/ReactBind-test.js b/src/core/__tests__/ReactBind-test.js index 000303a353a1e..00743a04f64a5 100644 --- a/src/core/__tests__/ReactBind-test.js +++ b/src/core/__tests__/ReactBind-test.js @@ -52,8 +52,8 @@ describe('autobinding', function() { render: function() { return (
); diff --git a/src/event/EventPluginHub.js b/src/event/EventPluginHub.js index cdee4f710f01b..74494c66a60b5 100644 --- a/src/event/EventPluginHub.js +++ b/src/event/EventPluginHub.js @@ -141,6 +141,8 @@ var EventPluginHub = { }, + eventNameDispatchConfigs: EventPluginRegistry.eventNameDispatchConfigs, + registrationNameModules: EventPluginRegistry.registrationNameModules, /** diff --git a/src/event/EventPluginRegistry.js b/src/event/EventPluginRegistry.js index a81e9614f7a85..730c1879e652f 100644 --- a/src/event/EventPluginRegistry.js +++ b/src/event/EventPluginRegistry.js @@ -85,6 +85,14 @@ function recomputePluginOrdering() { * @private */ function publishEventForPlugin(dispatchConfig, PluginModule, eventName) { + invariant( + !EventPluginRegistry.eventNameDispatchConfigs[eventName], + 'EventPluginHub: More than one plugin attempted to publish the same ' + + 'event name, `%s`.', + eventName + ); + EventPluginRegistry.eventNameDispatchConfigs[eventName] = dispatchConfig; + var phasedRegistrationNames = dispatchConfig.phasedRegistrationNames; if (phasedRegistrationNames) { for (var phaseName in phasedRegistrationNames) { @@ -142,7 +150,12 @@ var EventPluginRegistry = { plugins: [], /** - * Mapping from registration names to plugin modules. + * Mapping from event name to dispatch config + */ + eventNameDispatchConfigs: {}, + + /** + * Mapping from registration name to plugin module */ registrationNameModules: {}, @@ -243,6 +256,14 @@ var EventPluginRegistry = { } } EventPluginRegistry.plugins.length = 0; + + var eventNameDispatchConfigs = EventPluginRegistry.eventNameDispatchConfigs; + for (var eventName in eventNameDispatchConfigs) { + if (eventNameDispatchConfigs.hasOwnProperty(eventName)) { + delete eventNameDispatchConfigs[eventName]; + } + } + var registrationNameModules = EventPluginRegistry.registrationNameModules; for (var registrationName in registrationNameModules) { if (registrationNameModules.hasOwnProperty(registrationName)) { diff --git a/src/test/ReactTestUtils.js b/src/test/ReactTestUtils.js index c7d94c7b2cbe8..8d34272703b1e 100644 --- a/src/test/ReactTestUtils.js +++ b/src/test/ReactTestUtils.js @@ -16,13 +16,19 @@ * @providesModule ReactTestUtils */ +"use strict"; + var EventConstants = require('EventConstants'); +var EventPluginHub = require('EventPluginHub'); +var EventPropagators = require('EventPropagators'); var React = require('React'); var ReactComponent = require('ReactComponent'); var ReactDOM = require('ReactDOM'); var ReactEventEmitter = require('ReactEventEmitter'); -var ReactTextComponent = require('ReactTextComponent'); var ReactMount = require('ReactMount'); +var ReactTextComponent = require('ReactTextComponent'); +var ReactUpdates = require('ReactUpdates'); +var SyntheticEvent = require('SyntheticEvent'); var mergeInto = require('mergeInto'); var copyProperties = require('copyProperties'); @@ -228,12 +234,12 @@ var ReactTestUtils = { /** * Simulates a top level event being dispatched from a raw event that occured - * on and `Element` node. + * on an `Element` node. * @param topLevelType {Object} A type from `EventConstants.topLevelTypes` * @param {!Element} node The dom to simulate an event occurring on. * @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent. */ - simulateEventOnNode: function(topLevelType, node, fakeNativeEvent) { + simulateNativeEventOnNode: function(topLevelType, node, fakeNativeEvent) { var virtualHandler = ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback( topLevelType @@ -249,20 +255,15 @@ var ReactTestUtils = { * @param comp {!ReactDOMComponent} * @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent. */ - simulateEventOnDOMComponent: function(topLevelType, comp, fakeNativeEvent) { - var reactRootID = comp._rootNodeID || comp._rootDomId; - if (!reactRootID) { - throw new Error('Simulating event on non-rendered component'); - } - var virtualHandler = - ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback( - topLevelType - ); - var node = ReactMount.getNode(reactRootID); - fakeNativeEvent.target = node; - /* jsdom is returning nodes without id's - fixing that issue here. */ - ReactMount.setID(node, reactRootID); - virtualHandler(fakeNativeEvent); + simulateNativeEventOnDOMComponent: function( + topLevelType, + comp, + fakeNativeEvent) { + ReactTestUtils.simulateNativeEventOnNode( + topLevelType, + comp.getDOMNode(), + fakeNativeEvent + ); }, nativeTouchData: function(x, y) { @@ -273,7 +274,8 @@ var ReactTestUtils = { }; }, - Simulate: null // Will populate + Simulate: null, + SimulateNative: {} }; /** @@ -281,33 +283,93 @@ var ReactTestUtils = { * * - `ReactTestUtils.Simulate.click(Element/ReactDOMComponent)` * - `ReactTestUtils.Simulate.mouseMove(Element/ReactDOMComponent)` - * - `ReactTestUtils.Simulate.mouseIn/ReactDOMComponent)` - * - `ReactTestUtils.Simulate.mouseOut(Element/ReactDOMComponent)` + * - `ReactTestUtils.Simulate.change(Element/ReactDOMComponent)` + * - ... (All keys from event plugin `eventTypes` objects) + */ +function makeSimulator(eventType) { + return function(domComponentOrNode, eventData) { + var node; + if (ReactTestUtils.isDOMComponent(domComponentOrNode)) { + node = domComponentOrNode.getDOMNode(); + } else if (domComponentOrNode.tagName) { + node = domComponentOrNode; + } + + var fakeNativeEvent = new Event(); + fakeNativeEvent.target = node; + // We don't use SyntheticEvent.getPooled in order to not have to worry about + // properly destroying any properties assigned from `eventData` upon release + var event = new SyntheticEvent( + ReactEventEmitter.eventNameDispatchConfigs[eventType], + ReactMount.getID(node), + fakeNativeEvent + ); + mergeInto(event, eventData); + EventPropagators.accumulateTwoPhaseDispatches(event); + + ReactUpdates.batchedUpdates(function() { + EventPluginHub.enqueueEvents(event); + EventPluginHub.processEventQueue(); + }); + }; +} + +function buildSimulators() { + ReactTestUtils.Simulate = {}; + + var eventType; + for (eventType in ReactEventEmitter.eventNameDispatchConfigs) { + /** + * @param {!Element || ReactDOMComponent} domComponentOrNode + * @param {?object} eventData Fake event data to use in SyntheticEvent. + */ + ReactTestUtils.Simulate[eventType] = makeSimulator(eventType); + } +} + +// Rebuild ReactTestUtils.Simulate whenever event plugins are injected +var oldInjectEventPluginOrder = EventPluginHub.injection.injectEventPluginOrder; +EventPluginHub.injection.injectEventPluginOrder = function() { + oldInjectEventPluginOrder.apply(this, arguments); + buildSimulators(); +}; +var oldInjectEventPlugins = EventPluginHub.injection.injectEventPluginsByName; +EventPluginHub.injection.injectEventPluginsByName = function() { + oldInjectEventPlugins.apply(this, arguments); + buildSimulators(); +}; + +buildSimulators(); + +/** + * Exports: + * + * - `ReactTestUtils.SimulateNative.click(Element/ReactDOMComponent)` + * - `ReactTestUtils.SimulateNative.mouseMove(Element/ReactDOMComponent)` + * - `ReactTestUtils.SimulateNative.mouseIn/ReactDOMComponent)` + * - `ReactTestUtils.SimulateNative.mouseOut(Element/ReactDOMComponent)` * - ... (All keys from `EventConstants.topLevelTypes`) * * Note: Top level event types are a subset of the entire set of handler types * (which include a broader set of "synthetic" events). For example, onDragDone - * is a synthetic event. You certainly may write test cases for these event - * types, but it doesn't make sense to simulate them at this low of a level. In - * this case, the way you test an `onDragDone` event is by simulating a series - * of `mouseMove`/ `mouseDown`/`mouseUp` events - Then, a synthetic event of - * type `onDragDone` will be constructed and dispached through your system - * automatically. + * is a synthetic event. Except when testing an event plugin or React's event + * handling code specifically, you probably want to use ReactTestUtils.Simulate + * to dispatch synthetic events. */ -function makeSimulator(eventType) { +function makeNativeSimulator(eventType) { return function(domComponentOrNode, nativeEventData) { var fakeNativeEvent = new Event(eventType); mergeInto(fakeNativeEvent, nativeEventData); if (ReactTestUtils.isDOMComponent(domComponentOrNode)) { - ReactTestUtils.simulateEventOnDOMComponent( + ReactTestUtils.simulateNativeEventOnDOMComponent( eventType, domComponentOrNode, fakeNativeEvent ); } else if (!!domComponentOrNode.tagName) { // Will allow on actual dom nodes. - ReactTestUtils.simulateEventOnNode( + ReactTestUtils.simulateNativeEventOnNode( eventType, domComponentOrNode, fakeNativeEvent @@ -316,7 +378,6 @@ function makeSimulator(eventType) { }; } -ReactTestUtils.Simulate = {}; var eventType; for (eventType in topLevelTypes) { // Event type is stored as 'topClick' - we transform that to 'click' @@ -326,7 +387,8 @@ for (eventType in topLevelTypes) { * @param {!Element || ReactDOMComponent} domComponentOrNode * @param {?Event} nativeEventData Fake native event to use in SyntheticEvent. */ - ReactTestUtils.Simulate[convenienceName] = makeSimulator(eventType); + ReactTestUtils.SimulateNative[convenienceName] = + makeNativeSimulator(eventType); } module.exports = ReactTestUtils;