mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge pull request #3555 from spicyj/native-overrides
Import ResponderEventPlugin from react-native
This commit is contained in:
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user