From 57c516cb0e798443b69bab52cd6a3e72007cf5f9 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Tue, 22 Sep 2015 15:29:52 -0700 Subject: [PATCH] Don't use magic browser event dispatching for simulated events When calling ReactTestUtils.Simulate, sometimes you want to test that an exception is thrown by the event handler. This lets you do that without relying on the fact that old jsdom doesn't implement `Event`. --- .../client/eventPlugins/ChangeEventPlugin.js | 2 +- src/renderers/shared/event/EventPluginHub.js | 25 +++++++++-- .../shared/event/EventPluginUtils.js | 20 ++++++--- .../__tests__/ResponderEventPlugin-test.js | 2 +- .../reconciler/ReactEventEmitterMixin.js | 2 +- src/shared/utils/ReactErrorUtils.js | 44 +++++++++++-------- src/test/ReactTestUtils.js | 2 +- 7 files changed, 66 insertions(+), 31 deletions(-) diff --git a/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js b/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js index 992f74d29e..e33a4ccba1 100644 --- a/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js +++ b/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js @@ -96,7 +96,7 @@ function manualDispatchChangeEvent(nativeEvent) { function runEventInBatch(event) { EventPluginHub.enqueueEvents(event); - EventPluginHub.processEventQueue(); + EventPluginHub.processEventQueue(false); } function startWatchingForChangeEventIE8(target, targetID) { diff --git a/src/renderers/shared/event/EventPluginHub.js b/src/renderers/shared/event/EventPluginHub.js index ce448b701d..41b77dd948 100644 --- a/src/renderers/shared/event/EventPluginHub.js +++ b/src/renderers/shared/event/EventPluginHub.js @@ -35,17 +35,24 @@ var eventQueue = null; * Dispatches an event and releases it back into the pool, unless persistent. * * @param {?object} event Synthetic event to be dispatched. + * @param {boolean} simulated If the event is simulated (changes exn behavior) * @private */ -var executeDispatchesAndRelease = function(event) { +var executeDispatchesAndRelease = function(event, simulated) { if (event) { - EventPluginUtils.executeDispatchesInOrder(event); + EventPluginUtils.executeDispatchesInOrder(event, simulated); if (!event.isPersistent()) { event.constructor.release(event); } } }; +var executeDispatchesAndReleaseSimulated = function(e) { + return executeDispatchesAndRelease(e, true); +}; +var executeDispatchesAndReleaseTopLevel = function(e) { + return executeDispatchesAndRelease(e, false); +}; /** * - `InstanceHandle`: [required] Module that performs logical traversals of DOM @@ -266,12 +273,22 @@ var EventPluginHub = { * * @internal */ - processEventQueue: function() { + processEventQueue: function(simulated) { // Set `eventQueue` to null before processing it so that we can tell if more // events get enqueued while processing. var processingEventQueue = eventQueue; eventQueue = null; - forEachAccumulated(processingEventQueue, executeDispatchesAndRelease); + if (simulated) { + forEachAccumulated( + processingEventQueue, + executeDispatchesAndReleaseSimulated + ); + } else { + forEachAccumulated( + processingEventQueue, + executeDispatchesAndReleaseTopLevel + ); + } invariant( !eventQueue, 'processEventQueue(): Additional events were enqueued while processing ' + diff --git a/src/renderers/shared/event/EventPluginUtils.js b/src/renderers/shared/event/EventPluginUtils.js index 20b5998e7e..bff374799e 100644 --- a/src/renderers/shared/event/EventPluginUtils.js +++ b/src/renderers/shared/event/EventPluginUtils.js @@ -80,20 +80,30 @@ if (__DEV__) { /** * Dispatch the event to the listener. * @param {SyntheticEvent} event SyntheticEvent to handle + * @param {boolean} simulated If the event is simulated (changes exn behavior) * @param {function} listener Application-level callback * @param {string} domID DOM id to pass to the callback. */ -function executeDispatch(event, listener, domID) { +function executeDispatch(event, simulated, listener, domID) { var type = event.type || 'unknown-event'; event.currentTarget = injection.Mount.getNode(domID); - ReactErrorUtils.invokeGuardedCallback(type, listener, event, domID); + if (simulated) { + ReactErrorUtils.invokeGuardedCallbackWithCatch( + type, + listener, + event, + domID + ); + } else { + ReactErrorUtils.invokeGuardedCallback(type, listener, event, domID); + } event.currentTarget = null; } /** * Standard/simple iteration through an event's collected dispatches. */ -function executeDispatchesInOrder(event) { +function executeDispatchesInOrder(event, simulated) { var dispatchListeners = event._dispatchListeners; var dispatchIDs = event._dispatchIDs; if (__DEV__) { @@ -105,10 +115,10 @@ function executeDispatchesInOrder(event) { break; } // Listeners and IDs are two parallel arrays that are always in sync. - executeDispatch(event, dispatchListeners[i], dispatchIDs[i]); + executeDispatch(event, simulated, dispatchListeners[i], dispatchIDs[i]); } } else if (dispatchListeners) { - executeDispatch(event, dispatchListeners, dispatchIDs); + executeDispatch(event, simulated, dispatchListeners, dispatchIDs); } event._dispatchListeners = null; event._dispatchIDs = null; diff --git a/src/renderers/shared/event/eventPlugins/__tests__/ResponderEventPlugin-test.js b/src/renderers/shared/event/eventPlugins/__tests__/ResponderEventPlugin-test.js index 25c5e49ecc..6974cd7c41 100644 --- a/src/renderers/shared/event/eventPlugins/__tests__/ResponderEventPlugin-test.js +++ b/src/renderers/shared/event/eventPlugins/__tests__/ResponderEventPlugin-test.js @@ -306,7 +306,7 @@ var run = function(config, hierarchyConfig, nativeEventConfig) { // extraction process, but not the side effectful events. Below, we dispatch // side effectful events. EventPluginHub.enqueueEvents(extractedEvents); - EventPluginHub.processEventQueue(); + EventPluginHub.processEventQueue(true); // Ensure that every event that declared an `order`, was actually dispatched. expect( diff --git a/src/renderers/shared/reconciler/ReactEventEmitterMixin.js b/src/renderers/shared/reconciler/ReactEventEmitterMixin.js index a865fda7a4..6ab34f0050 100644 --- a/src/renderers/shared/reconciler/ReactEventEmitterMixin.js +++ b/src/renderers/shared/reconciler/ReactEventEmitterMixin.js @@ -15,7 +15,7 @@ var EventPluginHub = require('EventPluginHub'); function runEventQueueInBatch(events) { EventPluginHub.enqueueEvents(events); - EventPluginHub.processEventQueue(); + EventPluginHub.processEventQueue(false); } var ReactEventEmitterMixin = { diff --git a/src/shared/utils/ReactErrorUtils.js b/src/shared/utils/ReactErrorUtils.js index a4fdf8ce9d..2444c31f5b 100644 --- a/src/shared/utils/ReactErrorUtils.js +++ b/src/shared/utils/ReactErrorUtils.js @@ -14,25 +14,33 @@ var caughtError = null; -var ReactErrorUtils = { - /** - * Call a function while guarding against errors that happens within it. - * - * @param {?String} name of the guard to use for logging or debugging - * @param {Function} func The function to invoke - * @param {*} a First argument - * @param {*} b Second argument - */ - invokeGuardedCallback: function(name, func, a, b) { - try { - return func(a, b); - } catch (x) { - if (caughtError === null) { - caughtError = x; - } - return undefined; +/** + * Call a function while guarding against errors that happens within it. + * + * @param {?String} name of the guard to use for logging or debugging + * @param {Function} func The function to invoke + * @param {*} a First argument + * @param {*} b Second argument + */ +function invokeGuardedCallback(name, func, a, b) { + try { + return func(a, b); + } catch (x) { + if (caughtError === null) { + caughtError = x; } - }, + return undefined; + } +} + +var ReactErrorUtils = { + invokeGuardedCallback: invokeGuardedCallback, + + /** + * Invoked by ReactTestUtils.Simulate so that any errors thrown by the event + * handler are sure to be rethrown by rethrowCaughtError. + */ + invokeGuardedCallbackWithCatch: invokeGuardedCallback, /** * During execution of guarded functions we will capture the first error which diff --git a/src/test/ReactTestUtils.js b/src/test/ReactTestUtils.js index 7bc8cd0455..7a368fb540 100644 --- a/src/test/ReactTestUtils.js +++ b/src/test/ReactTestUtils.js @@ -475,7 +475,7 @@ function makeSimulator(eventType) { ReactUpdates.batchedUpdates(function() { EventPluginHub.enqueueEvents(event); - EventPluginHub.processEventQueue(); + EventPluginHub.processEventQueue(true); }); }; }