Merge pull request #3555 from spicyj/native-overrides

Import ResponderEventPlugin from react-native
This commit is contained in:
Ben Alpert
2015-04-02 14:13:35 -07:00
10 changed files with 1823 additions and 811 deletions
@@ -1,309 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ResponderEventPlugin
*/
'use strict';
var EventConstants = require('EventConstants');
var EventPluginUtils = require('EventPluginUtils');
var EventPropagators = require('EventPropagators');
var SyntheticEvent = require('SyntheticEvent');
var accumulateInto = require('accumulateInto');
var keyOf = require('keyOf');
var isStartish = EventPluginUtils.isStartish;
var isMoveish = EventPluginUtils.isMoveish;
var isEndish = EventPluginUtils.isEndish;
var executeDirectDispatch = EventPluginUtils.executeDirectDispatch;
var hasDispatches = EventPluginUtils.hasDispatches;
var executeDispatchesInOrderStopAtTrue =
EventPluginUtils.executeDispatchesInOrderStopAtTrue;
/**
* ID of element that should respond to touch/move types of interactions, as
* indicated explicitly by relevant callbacks.
*/
var responderID = null;
var isPressing = false;
var eventTypes = {
/**
* On a `touchStart`/`mouseDown`, is it desired that this element become the
* responder?
*/
startShouldSetResponder: {
phasedRegistrationNames: {
bubbled: keyOf({onStartShouldSetResponder: null}),
captured: keyOf({onStartShouldSetResponderCapture: null})
}
},
/**
* On a `scroll`, is it desired that this element become the responder? This
* is usually not needed, but should be used to retroactively infer that a
* `touchStart` had occured during momentum scroll. During a momentum scroll,
* a touch start will be immediately followed by a scroll event if the view is
* currently scrolling.
*/
scrollShouldSetResponder: {
phasedRegistrationNames: {
bubbled: keyOf({onScrollShouldSetResponder: null}),
captured: keyOf({onScrollShouldSetResponderCapture: null})
}
},
/**
* On a `touchMove`/`mouseMove`, is it desired that this element become the
* responder?
*/
moveShouldSetResponder: {
phasedRegistrationNames: {
bubbled: keyOf({onMoveShouldSetResponder: null}),
captured: keyOf({onMoveShouldSetResponderCapture: null})
}
},
/**
* Direct responder events dispatched directly to responder. Do not bubble.
*/
responderMove: {registrationName: keyOf({onResponderMove: null})},
responderRelease: {registrationName: keyOf({onResponderRelease: null})},
responderTerminationRequest: {
registrationName: keyOf({onResponderTerminationRequest: null})
},
responderGrant: {registrationName: keyOf({onResponderGrant: null})},
responderReject: {registrationName: keyOf({onResponderReject: null})},
responderTerminate: {registrationName: keyOf({onResponderTerminate: null})}
};
/**
* Performs negotiation between any existing/current responder, checks to see if
* any new entity is interested in becoming responder, performs that handshake
* and returns any events that must be emitted to notify the relevant parties.
*
* A note about event ordering in the `EventPluginHub`.
*
* Suppose plugins are injected in the following order:
*
* `[R, S, C]`
*
* To help illustrate the example, assume `S` is `SimpleEventPlugin` (for
* `onClick` etc) and `R` is `ResponderEventPlugin`.
*
* "Deferred-Dispatched Events":
*
* - The current event plugin system will traverse the list of injected plugins,
* in order, and extract events by collecting the plugin's return value of
* `extractEvents()`.
* - These events that are returned from `extractEvents` are "deferred
* dispatched events".
* - When returned from `extractEvents`, deferred-dispatched events contain an
* "accumulation" of deferred dispatches.
* - These deferred dispatches are accumulated/collected before they are
* returned, but processed at a later time by the `EventPluginHub` (hence the
* name deferred).
*
* In the process of returning their deferred-dispatched events, event plugins
* themselves can dispatch events on-demand without returning them from
* `extractEvents`. Plugins might want to do this, so that they can use event
* dispatching as a tool that helps them decide which events should be extracted
* in the first place.
*
* "On-Demand-Dispatched Events":
*
* - On-demand-dispatched events are not returned from `extractEvents`.
* - On-demand-dispatched events are dispatched during the process of returning
* the deferred-dispatched events.
* - They should not have side effects.
* - They should be avoided, and/or eventually be replaced with another
* abstraction that allows event plugins to perform multiple "rounds" of event
* extraction.
*
* Therefore, the sequence of event dispatches becomes:
*
* - `R`s on-demand events (if any) (dispatched by `R` on-demand)
* - `S`s on-demand events (if any) (dispatched by `S` on-demand)
* - `C`s on-demand events (if any) (dispatched by `C` on-demand)
* - `R`s extracted events (if any) (dispatched by `EventPluginHub`)
* - `S`s extracted events (if any) (dispatched by `EventPluginHub`)
* - `C`s extracted events (if any) (dispatched by `EventPluginHub`)
*
* In the case of `ResponderEventPlugin`: If the `startShouldSetResponder`
* on-demand dispatch returns `true` (and some other details are satisfied) the
* `onResponderGrant` deferred dispatched event is returned from
* `extractEvents`. The sequence of dispatch executions in this case
* will appear as follows:
*
* - `startShouldSetResponder` (`ResponderEventPlugin` dispatches on-demand)
* - `touchStartCapture` (`EventPluginHub` dispatches as usual)
* - `touchStart` (`EventPluginHub` dispatches as usual)
* - `responderGrant/Reject` (`EventPluginHub` dispatches as usual)
*
* @param {string} topLevelType Record from `EventConstants`.
* @param {string} topLevelTargetID ID of deepest React rendered element.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of synthetic events.
*/
function setResponderAndExtractTransfer(
topLevelType,
topLevelTargetID,
nativeEvent) {
var shouldSetEventType =
isStartish(topLevelType) ? eventTypes.startShouldSetResponder :
isMoveish(topLevelType) ? eventTypes.moveShouldSetResponder :
eventTypes.scrollShouldSetResponder;
var bubbleShouldSetFrom = responderID || topLevelTargetID;
var shouldSetEvent = SyntheticEvent.getPooled(
shouldSetEventType,
bubbleShouldSetFrom,
nativeEvent
);
EventPropagators.accumulateTwoPhaseDispatches(shouldSetEvent);
var wantsResponderID = executeDispatchesInOrderStopAtTrue(shouldSetEvent);
if (!shouldSetEvent.isPersistent()) {
shouldSetEvent.constructor.release(shouldSetEvent);
}
if (!wantsResponderID || wantsResponderID === responderID) {
return null;
}
var extracted;
var grantEvent = SyntheticEvent.getPooled(
eventTypes.responderGrant,
wantsResponderID,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(grantEvent);
if (responderID) {
var terminationRequestEvent = SyntheticEvent.getPooled(
eventTypes.responderTerminationRequest,
responderID,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(terminationRequestEvent);
var shouldSwitch = !hasDispatches(terminationRequestEvent) ||
executeDirectDispatch(terminationRequestEvent);
if (!terminationRequestEvent.isPersistent()) {
terminationRequestEvent.constructor.release(terminationRequestEvent);
}
if (shouldSwitch) {
var terminateType = eventTypes.responderTerminate;
var terminateEvent = SyntheticEvent.getPooled(
terminateType,
responderID,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(terminateEvent);
extracted = accumulateInto(extracted, [grantEvent, terminateEvent]);
responderID = wantsResponderID;
} else {
var rejectEvent = SyntheticEvent.getPooled(
eventTypes.responderReject,
wantsResponderID,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(rejectEvent);
extracted = accumulateInto(extracted, rejectEvent);
}
} else {
extracted = accumulateInto(extracted, grantEvent);
responderID = wantsResponderID;
}
return extracted;
}
/**
* A transfer is a negotiation between a currently set responder and the next
* element to claim responder status. Any start event could trigger a transfer
* of responderID. Any move event could trigger a transfer, so long as there is
* currently a responder set (in other words as long as the user is pressing
* down).
*
* @param {string} topLevelType Record from `EventConstants`.
* @return {boolean} True if a transfer of responder could possibly occur.
*/
function canTriggerTransfer(topLevelType) {
return topLevelType === EventConstants.topLevelTypes.topScroll ||
isStartish(topLevelType) ||
(isPressing && isMoveish(topLevelType));
}
/**
* Event plugin for formalizing the negotiation between claiming locks on
* receiving touches.
*/
var ResponderEventPlugin = {
getResponderID: function() {
return responderID;
},
eventTypes: eventTypes,
/**
* @param {string} topLevelType Record from `EventConstants`.
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of synthetic events.
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
var extracted;
// Must have missed an end event - reset the state here.
if (responderID && isStartish(topLevelType)) {
responderID = null;
}
if (isStartish(topLevelType)) {
isPressing = true;
} else if (isEndish(topLevelType)) {
isPressing = false;
}
if (canTriggerTransfer(topLevelType)) {
var transfer = setResponderAndExtractTransfer(
topLevelType,
topLevelTargetID,
nativeEvent
);
if (transfer) {
extracted = accumulateInto(extracted, transfer);
}
}
// Now that we know the responder is set correctly, we can dispatch
// responder type events (directly to the responder).
var type = isMoveish(topLevelType) ? eventTypes.responderMove :
isEndish(topLevelType) ? eventTypes.responderRelease :
isStartish(topLevelType) ? eventTypes.responderStart : null;
if (type) {
var gesture = SyntheticEvent.getPooled(
type,
responderID || '',
nativeEvent
);
EventPropagators.accumulateDirectDispatches(gesture);
extracted = accumulateInto(extracted, gesture);
}
if (type === eventTypes.responderRelease) {
responderID = null;
}
return extracted;
}
};
module.exports = ResponderEventPlugin;
@@ -1,494 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails react-core
*/
'use strict';
var EventPluginHub;
var EventConstants;
var EventPropagators;
var ReactInstanceHandles;
var ResponderEventPlugin;
var SyntheticEvent;
var GRANDPARENT_ID = '.0';
var PARENT_ID = '.0.0';
var CHILD_ID = '.0.0.0';
var topLevelTypes;
var responderEventTypes;
var spies;
var DUMMY_NATIVE_EVENT = {};
var DUMMY_RENDERED_TARGET = {};
var onStartShouldSetResponder = function(id, cb, capture) {
var registrationNames = responderEventTypes
.startShouldSetResponder
.phasedRegistrationNames;
EventPluginHub.putListener(
id,
capture ? registrationNames.captured : registrationNames.bubbled,
cb
);
};
var onScrollShouldSetResponder = function(id, cb, capture) {
var registrationNames = responderEventTypes
.scrollShouldSetResponder
.phasedRegistrationNames;
EventPluginHub.putListener(
id,
capture ? registrationNames.captured : registrationNames.bubbled,
cb
);
};
var onMoveShouldSetResponder = function(id, cb, capture) {
var registrationNames = responderEventTypes
.moveShouldSetResponder
.phasedRegistrationNames;
EventPluginHub.putListener(
id,
capture ? registrationNames.captured : registrationNames.bubbled,
cb
);
};
var onResponderGrant = function(id, cb) {
EventPluginHub.putListener(
id,
responderEventTypes.responderGrant.registrationName,
cb
);
};
var extractForTouchStart = function(renderedTargetID) {
return ResponderEventPlugin.extractEvents(
topLevelTypes.topTouchStart,
DUMMY_NATIVE_EVENT,
renderedTargetID,
DUMMY_RENDERED_TARGET
);
};
var extractForTouchMove = function(renderedTargetID) {
return ResponderEventPlugin.extractEvents(
topLevelTypes.topTouchMove,
DUMMY_NATIVE_EVENT,
renderedTargetID,
DUMMY_RENDERED_TARGET
);
};
var extractForTouchEnd = function(renderedTargetID) {
return ResponderEventPlugin.extractEvents(
topLevelTypes.topTouchEnd,
DUMMY_NATIVE_EVENT,
renderedTargetID,
DUMMY_RENDERED_TARGET
);
};
var extractForMouseDown = function(renderedTargetID) {
return ResponderEventPlugin.extractEvents(
topLevelTypes.topMouseDown,
DUMMY_NATIVE_EVENT,
renderedTargetID,
DUMMY_RENDERED_TARGET
);
};
var extractForMouseMove = function(renderedTargetID) {
return ResponderEventPlugin.extractEvents(
topLevelTypes.topMouseMove,
DUMMY_NATIVE_EVENT,
renderedTargetID,
DUMMY_RENDERED_TARGET
);
};
var extractForMouseUp = function(renderedTargetID) {
return ResponderEventPlugin.extractEvents(
topLevelTypes.topMouseUp,
DUMMY_NATIVE_EVENT,
renderedTargetID,
DUMMY_RENDERED_TARGET
);
};
var extractForScroll = function(renderedTargetID) {
return ResponderEventPlugin.extractEvents(
topLevelTypes.topScroll,
DUMMY_NATIVE_EVENT,
renderedTargetID,
DUMMY_RENDERED_TARGET
);
};
var onGrantChild;
var onGrantParent;
var onGrantGrandParent;
var existsInExtraction = function(extracted, test) {
if (Array.isArray(extracted)) {
for (var i = 0; i < extracted.length; i++) {
if (test(extracted[i])) {
return true;
}
}
} else if (extracted) {
return test(extracted);
}
return false;
};
/**
* Helper validators.
*/
function assertGrantEvent(id, extracted) {
var test = function(event) {
return event instanceof SyntheticEvent &&
event.dispatchConfig === responderEventTypes.responderGrant &&
event.dispatchMarker === id;
};
expect(ResponderEventPlugin.getResponderID()).toBe(id);
expect(existsInExtraction(extracted, test)).toBe(true);
}
function assertResponderMoveEvent(id, extracted) {
var test = function(event) {
return event instanceof SyntheticEvent &&
event.dispatchConfig === responderEventTypes.responderMove &&
event.dispatchMarker === id;
};
expect(ResponderEventPlugin.getResponderID()).toBe(id);
expect(existsInExtraction(extracted, test)).toBe(true);
}
function assertTerminateEvent(id, extracted) {
var test = function(event) {
return event instanceof SyntheticEvent &&
event.dispatchConfig === responderEventTypes.responderTerminate &&
event.dispatchMarker === id;
};
expect(ResponderEventPlugin.getResponderID()).not.toBe(id);
expect(existsInExtraction(extracted, test)).toBe(true);
}
function assertRelease(id, extracted) {
var test = function(event) {
return event instanceof SyntheticEvent &&
event.dispatchConfig === responderEventTypes.responderRelease &&
event.dispatchMarker === id;
};
expect(ResponderEventPlugin.getResponderID()).toBe(null);
expect(existsInExtraction(extracted, test)).toBe(true);
}
function assertNothingExtracted(extracted) {
expect(Array.isArray(extracted)).toBe(false); // No grant events.
expect(Array.isArray(extracted)).toBeFalsy();
}
/**
* TODO:
* - Test that returning false from `responderTerminationRequest` will never
* cause the responder to be lost.
* - Automate some of this testing by providing config data - generalize.
*/
describe('ResponderEventPlugin', function() {
beforeEach(function() {
require('mock-modules').dumpCache();
EventPluginHub = require('EventPluginHub');
EventConstants = require('EventConstants');
EventPropagators = require('EventPropagators');
ReactInstanceHandles = require('ReactInstanceHandles');
ResponderEventPlugin = require('ResponderEventPlugin');
SyntheticEvent = require('SyntheticEvent');
EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles);
// dumpCache, in open-source tests, only resets existing mocks. It does not
// reset module-state though -- so we need to do this explicitly in the test
// for now. Once that's no longer the case, we can delete this line.
EventPluginHub.__purge();
topLevelTypes = EventConstants.topLevelTypes;
responderEventTypes = ResponderEventPlugin.eventTypes;
spies = {
onStartShouldSetResponderChild: function() {},
onStartShouldSetResponderParent: function() {},
onStartShouldSetResponderParentCapture: function() {},
onStartShouldSetResponderGrandParent: function() {},
onMoveShouldSetResponderParent: function() {},
onScrollShouldSetResponderParent: function() {}
};
onGrantChild = function() {};
onGrantParent = function() {};
onGrantGrandParent = function() {};
});
it('should not auto-set responder on touch start', function() {
// Notice we're not registering the startShould* handler.
var extracted = extractForTouchStart(CHILD_ID);
assertNothingExtracted(extracted);
expect(ResponderEventPlugin.getResponderID()).toBe(null);
});
it('should not auto-set responder on mouse down', function() {
// Notice we're not registering the startShould* handler.
var extracted = extractForMouseDown(CHILD_ID);
assertNothingExtracted(extracted);
expect(ResponderEventPlugin.getResponderID()).toBe(null);
extractForMouseUp(CHILD_ID); // Let up!
expect(ResponderEventPlugin.getResponderID()).toBe(null);
// Register `onMoveShould*` handler.
spyOn(spies, 'onMoveShouldSetResponderParent').andReturn(true);
onMoveShouldSetResponder(PARENT_ID, spies.onMoveShouldSetResponderParent);
onResponderGrant(PARENT_ID, onGrantParent);
// Move mouse while not pressing down
extracted = extractForMouseMove(CHILD_ID);
assertNothingExtracted(extracted);
// Not going to call `onMoveShould`* if not touching.
expect(spies.onMoveShouldSetResponderParent.calls.length).toBe(0);
expect(ResponderEventPlugin.getResponderID()).toBe(null);
// Now try the move extraction again, this time while holding down, and not
// letting up.
extracted = extractForMouseDown(CHILD_ID);
assertNothingExtracted(extracted);
expect(ResponderEventPlugin.getResponderID()).toBe(null);
// Now moving can set the responder, if pressing down, even if there is no
// current responder.
extracted = extractForMouseMove(CHILD_ID);
expect(spies.onMoveShouldSetResponderParent.calls.length).toBe(1);
expect(ResponderEventPlugin.getResponderID()).toBe(PARENT_ID);
assertGrantEvent(PARENT_ID, extracted);
extractForMouseUp(CHILD_ID);
expect(ResponderEventPlugin.getResponderID()).toBe(null);
});
it('should not extract a grant/release event if double start', function() {
// Return true - we should become the responder.
var extracted;
spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
onResponderGrant(CHILD_ID, onGrantChild);
extracted = extractForTouchStart(CHILD_ID);
assertGrantEvent(CHILD_ID, extracted);
expect(spies.onStartShouldSetResponderChild.calls.length).toBe(1);
// Now we do *not* clear out the touch via a simulated touch end. This mocks
// out an environment that likely will never happen, but could in some odd
// error state so it's nice to make sure we recover gracefully.
// extractForTouchEnd(CHILD_ID); // Clear the responder
extracted = extractForTouchStart(CHILD_ID);
assertNothingExtracted();
expect(spies.onStartShouldSetResponderChild.calls.length).toBe(2);
});
it('should bubble/capture responder on start', function() {
// Return true - we should become the responder.
var extracted;
spyOn(spies, 'onStartShouldSetResponderParent').andReturn(true);
spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
onStartShouldSetResponder(PARENT_ID, spies.onStartShouldSetResponderParent);
onResponderGrant(CHILD_ID, onGrantChild);
onResponderGrant(PARENT_ID, onGrantParent);
// Nothing extracted if no responder.
extracted = extractForTouchMove(GRANDPARENT_ID);
assertNothingExtracted(extracted);
extracted = extractForTouchStart(CHILD_ID);
assertGrantEvent(CHILD_ID, extracted);
expect(spies.onStartShouldSetResponderChild.calls.length).toBe(1);
expect(spies.onStartShouldSetResponderParent.calls.length).toBe(0);
// Even if moving on the grandparent, the child will receive responder moves
// (This is even true for mouse interactions - which we should absolutely
// test)
extracted = extractForTouchMove(GRANDPARENT_ID);
assertResponderMoveEvent(CHILD_ID, extracted);
extracted = extractForTouchMove(CHILD_ID); // Test move on child node too.
assertResponderMoveEvent(CHILD_ID, extracted);
// Reset the responder - id passed here shouldn't matter:
// TODO: Test varying the id here.
extracted = extractForTouchEnd(GRANDPARENT_ID); // Clear the responder
assertRelease(CHILD_ID, extracted);
// Now make sure the parent requests responder on capture.
spyOn(spies, 'onStartShouldSetResponderParentCapture').andReturn(true);
onStartShouldSetResponder(
PARENT_ID,
spies.onStartShouldSetResponderParent,
true // Capture
);
onResponderGrant(PARENT_ID, onGrantGrandParent);
extracted = extractForTouchStart(PARENT_ID);
expect(ResponderEventPlugin.getResponderID()).toBe(PARENT_ID);
assertGrantEvent(PARENT_ID, extracted);
// Now move on various nodes, ensuring that the responder move is emitted to
// the parent node.
extracted = extractForTouchMove(GRANDPARENT_ID);
assertResponderMoveEvent(PARENT_ID, extracted);
extracted = extractForTouchMove(CHILD_ID); // Test move on child node too.
assertResponderMoveEvent(PARENT_ID, extracted);
// Reset the responder - id passed here shouldn't matter:
// TODO: Test varying the id here.
extracted = extractForTouchEnd(GRANDPARENT_ID); // Clear the responder
assertRelease(PARENT_ID, extracted);
});
it('should invoke callback to ask if responder is desired', function() {
// Return true - we should become the responder.
spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
var extracted = extractForTouchStart(CHILD_ID);
assertNothingExtracted(extracted);
expect(spies.onStartShouldSetResponderChild.calls.length).toBe(1);
expect(ResponderEventPlugin.getResponderID()).toBe(CHILD_ID);
extractForTouchEnd(CHILD_ID); // Clear the responder
// Now try returning false - we should not become the responder.
spies.onStartShouldSetResponderChild.andReturn(false);
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
extracted = extractForTouchStart(CHILD_ID);
assertNothingExtracted(extracted);
expect(spies.onStartShouldSetResponderChild.calls.length).toBe(2);
expect(ResponderEventPlugin.getResponderID()).toBe(null);
extractForTouchEnd(CHILD_ID);
expect(ResponderEventPlugin.getResponderID()).toBe(null); // Still null
// Same thing as before but return true from "shouldSet".
spies.onStartShouldSetResponderChild.andReturn(true);
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
onResponderGrant(CHILD_ID, onGrantChild);
extracted = extractForTouchStart(CHILD_ID);
expect(spies.onStartShouldSetResponderChild.calls.length).toBe(3);
assertGrantEvent(CHILD_ID, extracted);
extracted = extractForTouchEnd(CHILD_ID); // Clear the responder
assertRelease(CHILD_ID, extracted);
});
it('should give up responder to parent on move iff allowed', function() {
// Return true - we should become the responder.
var extracted;
spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
spyOn(spies, 'onMoveShouldSetResponderParent').andReturn(true);
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
onMoveShouldSetResponder(PARENT_ID, spies.onMoveShouldSetResponderParent);
onResponderGrant(CHILD_ID, onGrantChild);
onResponderGrant(PARENT_ID, onGrantParent);
spies.onStartShouldSetResponderChild.andReturn(true);
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
extracted = extractForTouchStart(CHILD_ID);
expect(spies.onStartShouldSetResponderChild.calls.length).toBe(1);
expect(spies.onMoveShouldSetResponderParent.calls.length).toBe(0); // none yet
assertGrantEvent(CHILD_ID, extracted); // Child is the current responder
extracted = extractForTouchMove(CHILD_ID);
expect(spies.onMoveShouldSetResponderParent.calls.length).toBe(1);
assertGrantEvent(PARENT_ID, extracted);
assertTerminateEvent(CHILD_ID, extracted);
extracted = extractForTouchEnd(CHILD_ID); // Clear the responder
assertRelease(PARENT_ID, extracted);
});
it('should responder move only on direct responder', function() {
// Return true - we should become the responder.
spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
var extracted = extractForTouchStart(CHILD_ID);
assertNothingExtracted(extracted);
expect(spies.onStartShouldSetResponderChild.calls.length).toBe(1);
expect(ResponderEventPlugin.getResponderID()).toBe(CHILD_ID);
extractForTouchEnd(CHILD_ID); // Clear the responder
expect(ResponderEventPlugin.getResponderID()).toBe(null);
// Now try returning false - we should not become the responder.
spies.onStartShouldSetResponderChild.andReturn(false);
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
extracted = extractForTouchStart(CHILD_ID);
assertNothingExtracted(extracted);
expect(spies.onStartShouldSetResponderChild.calls.length).toBe(2);
expect(ResponderEventPlugin.getResponderID()).toBe(null);
extractForTouchEnd(CHILD_ID); // Clear the responder
// Same thing as before but return true from "shouldSet".
spies.onStartShouldSetResponderChild.andReturn(true);
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
onResponderGrant(CHILD_ID, onGrantChild);
extracted = extractForTouchStart(CHILD_ID);
expect(spies.onStartShouldSetResponderChild.calls.length).toBe(3);
assertGrantEvent(CHILD_ID, extracted);
extracted = extractForTouchEnd(CHILD_ID); // Clear the responder
assertRelease(CHILD_ID, extracted);
});
it('should give up responder to parent on scroll iff allowed', function() {
// Return true - we should become the responder.
var extracted;
spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
spyOn(spies, 'onMoveShouldSetResponderParent').andReturn(false);
spyOn(spies, 'onScrollShouldSetResponderParent').andReturn(true);
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
onMoveShouldSetResponder(PARENT_ID, spies.onMoveShouldSetResponderParent);
onScrollShouldSetResponder(
PARENT_ID,
spies.onScrollShouldSetResponderParent
);
onResponderGrant(CHILD_ID, onGrantChild);
onResponderGrant(PARENT_ID, onGrantParent);
spies.onStartShouldSetResponderChild.andReturn(true);
onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
extracted = extractForTouchStart(CHILD_ID);
expect(spies.onStartShouldSetResponderChild.calls.length).toBe(1);
expect(spies.onMoveShouldSetResponderParent.calls.length).toBe(0); // none yet
assertGrantEvent(CHILD_ID, extracted); // Child is the current responder
extracted = extractForTouchMove(CHILD_ID);
expect(spies.onMoveShouldSetResponderParent.calls.length).toBe(1);
assertNothingExtracted(extracted);
extracted = extractForScroll(CHILD_ID); // Could have been parent here too.
expect(spies.onScrollShouldSetResponderParent.calls.length).toBe(1);
assertGrantEvent(PARENT_ID, extracted);
assertTerminateEvent(CHILD_ID, extracted);
extracted = extractForTouchEnd(CHILD_ID); // Clear the responder
assertRelease(PARENT_ID, extracted);
});
});