1457 lines
48 KiB
JavaScript
1457 lines
48 KiB
JavaScript
/**
|
|
* Copyright (c) 2013-present, Facebook, Inc.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @emails react-core
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const {HostComponent} = require('shared/ReactTypeOfWork');
|
|
|
|
let EventPluginHub;
|
|
let ResponderEventPlugin;
|
|
|
|
const touch = function(nodeHandle, i) {
|
|
return {target: nodeHandle, identifier: i};
|
|
};
|
|
|
|
/**
|
|
* @param {NodeHandle} nodeHandle @see NodeHandle. Handle of target.
|
|
* @param {Array<Touch>} touches All active touches.
|
|
* @param {Array<Touch>} changedTouches Only the touches that have changed.
|
|
* @return {TouchEvent} Model of a touch event that is compliant with responder
|
|
* system plugin.
|
|
*/
|
|
const touchEvent = function(nodeHandle, touches, changedTouches) {
|
|
return {
|
|
target: nodeHandle,
|
|
changedTouches: changedTouches,
|
|
touches: touches,
|
|
};
|
|
};
|
|
|
|
const subsequence = function(arr, indices) {
|
|
const ret = [];
|
|
for (let i = 0; i < indices.length; i++) {
|
|
const index = indices[i];
|
|
ret.push(arr[index]);
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
const antiSubsequence = function(arr, indices) {
|
|
const ret = [];
|
|
for (let i = 0; i < arr.length; i++) {
|
|
if (indices.indexOf(i) === -1) {
|
|
ret.push(arr[i]);
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* Helper for creating touch test config data.
|
|
* @param allTouchHandles
|
|
*/
|
|
const _touchConfig = function(
|
|
topType,
|
|
targetNodeHandle,
|
|
allTouchHandles,
|
|
changedIndices,
|
|
eventTarget,
|
|
) {
|
|
const allTouchObjects = allTouchHandles.map(touch);
|
|
const changedTouchObjects = subsequence(allTouchObjects, changedIndices);
|
|
const activeTouchObjects =
|
|
topType === 'topTouchStart'
|
|
? allTouchObjects
|
|
: topType === 'topTouchMove'
|
|
? allTouchObjects
|
|
: topType === 'topTouchEnd'
|
|
? antiSubsequence(allTouchObjects, changedIndices)
|
|
: topType === 'topTouchCancel'
|
|
? antiSubsequence(allTouchObjects, changedIndices)
|
|
: null;
|
|
|
|
return {
|
|
nativeEvent: touchEvent(
|
|
targetNodeHandle,
|
|
activeTouchObjects,
|
|
changedTouchObjects,
|
|
),
|
|
topLevelType: topType,
|
|
targetInst: getInstanceFromNode(targetNodeHandle),
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Creates test data for touch events using environment agnostic "node
|
|
* handles".
|
|
*
|
|
* @param {NodeHandle} nodeHandle Environment agnostic handle to DOM node.
|
|
* @param {Array<NodeHandle>} allTouchHandles Encoding of all "touches" in the
|
|
* form of a mapping from integer (touch `identifier`) to touch target. This is
|
|
* encoded in array form. Because of this, it is possible for two separate
|
|
* touches (meaning two separate indices) to have the same touch target ID -
|
|
* this corresponds to real world cases where two separate unique touches have
|
|
* the same target. These touches don't just represent all active touches,
|
|
* rather it also includes any touches that are not active, but are in the
|
|
* process of being removed.
|
|
* @param {Array<NodeHandle>} changedIndices Indices of `allTouchHandles` that
|
|
* have changed.
|
|
* @return {object} Config data used by test cases for extracting responder
|
|
* events.
|
|
*/
|
|
const startConfig = function(nodeHandle, allTouchHandles, changedIndices) {
|
|
return _touchConfig(
|
|
'topTouchStart',
|
|
nodeHandle,
|
|
allTouchHandles,
|
|
changedIndices,
|
|
nodeHandle,
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @see `startConfig`
|
|
*/
|
|
const moveConfig = function(nodeHandle, allTouchHandles, changedIndices) {
|
|
return _touchConfig(
|
|
'topTouchMove',
|
|
nodeHandle,
|
|
allTouchHandles,
|
|
changedIndices,
|
|
nodeHandle,
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @see `startConfig`
|
|
*/
|
|
const endConfig = function(nodeHandle, allTouchHandles, changedIndices) {
|
|
return _touchConfig(
|
|
'topTouchEnd',
|
|
nodeHandle,
|
|
allTouchHandles,
|
|
changedIndices,
|
|
nodeHandle,
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Test config for events that aren't negotiation related, but rather result of
|
|
* a negotiation.
|
|
*
|
|
* Returns object of the form:
|
|
*
|
|
* {
|
|
* responderReject: {
|
|
* // Whatever "readableIDToID" was passed in.
|
|
* grandParent: {order: NA, assertEvent: null, returnVal: blah},
|
|
* ...
|
|
* child: {order: NA, assertEvent: null, returnVal: blah},
|
|
* }
|
|
* responderGrant: {
|
|
* grandParent: {order: NA, assertEvent: null, returnVal: blah},
|
|
* ...
|
|
* child: {order: NA, assertEvent: null, returnVal: blah}
|
|
* }
|
|
* ...
|
|
* }
|
|
*
|
|
* After this is created, a test case would configure specific event orderings
|
|
* and optional assertions. Anything left with an `order` of `NA` will be
|
|
* required to never be invoked (the test runner will make sure it throws if
|
|
* ever invoked).
|
|
*
|
|
*/
|
|
const NA = -1;
|
|
const oneEventLoopTestConfig = function(readableIDToID) {
|
|
const ret = {
|
|
// Negotiation
|
|
scrollShouldSetResponder: {bubbled: {}, captured: {}},
|
|
startShouldSetResponder: {bubbled: {}, captured: {}},
|
|
moveShouldSetResponder: {bubbled: {}, captured: {}},
|
|
responderTerminationRequest: {},
|
|
|
|
// Non-negotiation
|
|
responderReject: {}, // These do not bubble capture.
|
|
responderGrant: {},
|
|
responderStart: {},
|
|
responderMove: {},
|
|
responderTerminate: {},
|
|
responderEnd: {},
|
|
responderRelease: {},
|
|
};
|
|
for (const eventName in ret) {
|
|
for (const readableNodeName in readableIDToID) {
|
|
if (ret[eventName].bubbled) {
|
|
// Two phase
|
|
ret[eventName].bubbled[readableNodeName] = {
|
|
order: NA,
|
|
assertEvent: null,
|
|
returnVal: undefined,
|
|
};
|
|
ret[eventName].captured[readableNodeName] = {
|
|
order: NA,
|
|
assertEvent: null,
|
|
returnVal: undefined,
|
|
};
|
|
} else {
|
|
ret[eventName][readableNodeName] = {
|
|
order: NA,
|
|
assertEvent: null,
|
|
returnVal: undefined,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
* @param {object} eventTestConfig
|
|
* @param {object} readableIDToID
|
|
*/
|
|
const registerTestHandlers = function(eventTestConfig, readableIDToID) {
|
|
const runs = {dispatchCount: 0};
|
|
const neverFire = function(readableID, registrationName) {
|
|
runs.dispatchCount++;
|
|
expect('').toBe(
|
|
'Event type: ' +
|
|
registrationName +
|
|
'\nShould never occur on:' +
|
|
readableID +
|
|
'\nFor event test config:\n' +
|
|
JSON.stringify(eventTestConfig) +
|
|
'\n',
|
|
);
|
|
};
|
|
|
|
const registerOneEventType = function(registrationName, eventTypeTestConfig) {
|
|
for (const readableID in eventTypeTestConfig) {
|
|
const nodeConfig = eventTypeTestConfig[readableID];
|
|
const id = readableIDToID[readableID];
|
|
const handler =
|
|
nodeConfig.order === NA
|
|
? neverFire.bind(null, readableID, registrationName)
|
|
: // We partially apply readableID and nodeConfig, as they change in the
|
|
// parent closure across iterations.
|
|
function(rID, config, e) {
|
|
expect(
|
|
rID +
|
|
'->' +
|
|
registrationName +
|
|
' index:' +
|
|
runs.dispatchCount++,
|
|
).toBe(rID + '->' + registrationName + ' index:' + config.order);
|
|
if (config.assertEvent) {
|
|
config.assertEvent(e);
|
|
}
|
|
return config.returnVal;
|
|
}.bind(null, readableID, nodeConfig);
|
|
putListener(getInstanceFromNode(id), registrationName, handler);
|
|
}
|
|
};
|
|
for (const eventName in eventTestConfig) {
|
|
const oneEventTypeTestConfig = eventTestConfig[eventName];
|
|
const hasTwoPhase = !!oneEventTypeTestConfig.bubbled;
|
|
if (hasTwoPhase) {
|
|
registerOneEventType(
|
|
ResponderEventPlugin.eventTypes[eventName].phasedRegistrationNames
|
|
.bubbled,
|
|
oneEventTypeTestConfig.bubbled,
|
|
);
|
|
registerOneEventType(
|
|
ResponderEventPlugin.eventTypes[eventName].phasedRegistrationNames
|
|
.captured,
|
|
oneEventTypeTestConfig.captured,
|
|
);
|
|
} else {
|
|
registerOneEventType(
|
|
ResponderEventPlugin.eventTypes[eventName].registrationName,
|
|
oneEventTypeTestConfig,
|
|
);
|
|
}
|
|
}
|
|
return runs;
|
|
};
|
|
|
|
const run = function(config, hierarchyConfig, nativeEventConfig) {
|
|
let max = NA;
|
|
const searchForMax = function(nodeConfig) {
|
|
for (const readableID in nodeConfig) {
|
|
const order = nodeConfig[readableID].order;
|
|
max = order > max ? order : max;
|
|
}
|
|
};
|
|
for (const eventName in config) {
|
|
const eventConfig = config[eventName];
|
|
if (eventConfig.bubbled) {
|
|
searchForMax(eventConfig.bubbled);
|
|
searchForMax(eventConfig.captured);
|
|
} else {
|
|
searchForMax(eventConfig);
|
|
}
|
|
}
|
|
|
|
// Register the handlers
|
|
const runData = registerTestHandlers(config, hierarchyConfig);
|
|
|
|
// Trigger the event
|
|
const extractedEvents = ResponderEventPlugin.extractEvents(
|
|
nativeEventConfig.topLevelType,
|
|
nativeEventConfig.targetInst,
|
|
nativeEventConfig.nativeEvent,
|
|
nativeEventConfig.target,
|
|
);
|
|
|
|
// At this point the negotiation events have been dispatched as part of the
|
|
// extraction process, but not the side effectful events. Below, we dispatch
|
|
// side effectful events.
|
|
EventPluginHub.runEventsInBatch(extractedEvents, true);
|
|
|
|
// Ensure that every event that declared an `order`, was actually dispatched.
|
|
expect('number of events dispatched:' + runData.dispatchCount).toBe(
|
|
'number of events dispatched:' + (max + 1),
|
|
); // +1 for extra ++
|
|
};
|
|
|
|
const GRANDPARENT_HOST_NODE = {};
|
|
const PARENT_HOST_NODE = {};
|
|
const CHILD_HOST_NODE = {};
|
|
const CHILD_HOST_NODE2 = {};
|
|
|
|
// These intentionally look like Fibers. ReactTreeTraversal depends on their field names.
|
|
// TODO: we could test this with regular DOM nodes (and real fibers) instead.
|
|
const GRANDPARENT_INST = {
|
|
return: null,
|
|
tag: HostComponent,
|
|
stateNode: GRANDPARENT_HOST_NODE,
|
|
memoizedProps: {},
|
|
};
|
|
const PARENT_INST = {
|
|
return: GRANDPARENT_INST,
|
|
tag: HostComponent,
|
|
stateNode: PARENT_HOST_NODE,
|
|
memoizedProps: {},
|
|
};
|
|
const CHILD_INST = {
|
|
return: PARENT_INST,
|
|
tag: HostComponent,
|
|
stateNode: CHILD_HOST_NODE,
|
|
memoizedProps: {},
|
|
};
|
|
const CHILD_INST2 = {
|
|
return: PARENT_INST,
|
|
tag: HostComponent,
|
|
stateNode: CHILD_HOST_NODE2,
|
|
memoizedProps: {},
|
|
};
|
|
|
|
GRANDPARENT_HOST_NODE.testInstance = GRANDPARENT_INST;
|
|
PARENT_HOST_NODE.testInstance = PARENT_INST;
|
|
CHILD_HOST_NODE.testInstance = CHILD_INST;
|
|
CHILD_HOST_NODE2.testInstance = CHILD_INST2;
|
|
|
|
const three = {
|
|
grandParent: GRANDPARENT_HOST_NODE,
|
|
parent: PARENT_HOST_NODE,
|
|
child: CHILD_HOST_NODE,
|
|
};
|
|
|
|
const siblings = {
|
|
parent: PARENT_HOST_NODE,
|
|
childOne: CHILD_HOST_NODE,
|
|
childTwo: CHILD_HOST_NODE2,
|
|
};
|
|
|
|
function getInstanceFromNode(node) {
|
|
return node.testInstance;
|
|
}
|
|
|
|
function getNodeFromInstance(inst) {
|
|
return inst.stateNode;
|
|
}
|
|
|
|
function getFiberCurrentPropsFromNode(node) {
|
|
return node.testInstance.memoizedProps;
|
|
}
|
|
|
|
function putListener(instance, registrationName, handler) {
|
|
instance.memoizedProps[registrationName] = handler;
|
|
}
|
|
|
|
function deleteAllListeners(instance) {
|
|
instance.memoizedProps = {};
|
|
}
|
|
|
|
describe('ResponderEventPlugin', () => {
|
|
beforeEach(() => {
|
|
jest.resetModules();
|
|
|
|
const ReactDOM = require('react-dom');
|
|
const ReactDOMUnstableNativeDependencies = require('react-dom/unstable-native-dependencies');
|
|
EventPluginHub =
|
|
ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
|
.EventPluginHub;
|
|
const injectComponentTree =
|
|
ReactDOMUnstableNativeDependencies.injectComponentTree;
|
|
ResponderEventPlugin =
|
|
ReactDOMUnstableNativeDependencies.ResponderEventPlugin;
|
|
|
|
deleteAllListeners(GRANDPARENT_INST);
|
|
deleteAllListeners(PARENT_INST);
|
|
deleteAllListeners(CHILD_INST);
|
|
deleteAllListeners(CHILD_INST2);
|
|
|
|
injectComponentTree({
|
|
getInstanceFromNode,
|
|
getNodeFromInstance,
|
|
getFiberCurrentPropsFromNode,
|
|
});
|
|
});
|
|
|
|
it('should do nothing when no one wants to respond', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.child = {
|
|
order: 2,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: false};
|
|
config.startShouldSetResponder.bubbled.parent = {
|
|
order: 4,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.grandParent = {
|
|
order: 5,
|
|
returnVal: false,
|
|
};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
|
|
// Now no handlers should be called on `touchEnd`.
|
|
config = oneEventLoopTestConfig(three);
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
/**
|
|
* Simple Start Granting
|
|
* --------------------
|
|
*/
|
|
|
|
it('should grant responder grandParent while capturing', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: true,
|
|
};
|
|
config.responderGrant.grandParent = {order: 1};
|
|
config.responderStart.grandParent = {order: 2};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.grandParent),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.grandParent = {order: 0};
|
|
config.responderRelease.grandParent = {order: 1};
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
it('should grant responder parent while capturing', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: true,
|
|
};
|
|
config.responderGrant.parent = {order: 2};
|
|
config.responderStart.parent = {order: 3};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.parent),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.parent = {order: 0};
|
|
config.responderRelease.parent = {order: 1};
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
it('should grant responder child while capturing', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.child = {order: 2, returnVal: true};
|
|
config.responderGrant.child = {order: 3};
|
|
config.responderStart.child = {order: 4};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.child),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.child = {order: 0};
|
|
config.responderRelease.child = {order: 1};
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
it('should grant responder child while bubbling', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.child = {
|
|
order: 2,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: true};
|
|
config.responderGrant.child = {order: 4};
|
|
config.responderStart.child = {order: 5};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.child),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.child = {order: 0};
|
|
config.responderRelease.child = {order: 1};
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
it('should grant responder parent while bubbling', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.child = {
|
|
order: 2,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: false};
|
|
config.startShouldSetResponder.bubbled.parent = {order: 4, returnVal: true};
|
|
config.responderGrant.parent = {order: 5};
|
|
config.responderStart.parent = {order: 6};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.parent),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.parent = {order: 0};
|
|
config.responderRelease.parent = {order: 1};
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
it('should grant responder grandParent while bubbling', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.child = {
|
|
order: 2,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: false};
|
|
config.startShouldSetResponder.bubbled.parent = {
|
|
order: 4,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.grandParent = {
|
|
order: 5,
|
|
returnVal: true,
|
|
};
|
|
config.responderGrant.grandParent = {order: 6};
|
|
config.responderStart.grandParent = {order: 7};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.grandParent),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.grandParent = {order: 0};
|
|
config.responderRelease.grandParent = {order: 1};
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
/**
|
|
* Simple Move Granting
|
|
* --------------------
|
|
*/
|
|
|
|
it('should grant responder grandParent while capturing move', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
|
|
config.startShouldSetResponder.captured.grandParent = {order: 0};
|
|
config.startShouldSetResponder.captured.parent = {order: 1};
|
|
config.startShouldSetResponder.captured.child = {order: 2};
|
|
config.startShouldSetResponder.bubbled.child = {order: 3};
|
|
config.startShouldSetResponder.bubbled.parent = {order: 4};
|
|
config.startShouldSetResponder.bubbled.grandParent = {order: 5};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.moveShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: true,
|
|
};
|
|
config.responderGrant.grandParent = {order: 1};
|
|
config.responderMove.grandParent = {order: 2};
|
|
run(config, three, moveConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.grandParent),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.grandParent = {order: 0};
|
|
config.responderRelease.grandParent = {order: 1};
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
it('should grant responder parent while capturing move', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
|
|
config.startShouldSetResponder.captured.grandParent = {order: 0};
|
|
config.startShouldSetResponder.captured.parent = {order: 1};
|
|
config.startShouldSetResponder.captured.child = {order: 2};
|
|
config.startShouldSetResponder.bubbled.child = {order: 3};
|
|
config.startShouldSetResponder.bubbled.parent = {order: 4};
|
|
config.startShouldSetResponder.bubbled.grandParent = {order: 5};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.moveShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.captured.parent = {order: 1, returnVal: true};
|
|
config.responderGrant.parent = {order: 2};
|
|
config.responderMove.parent = {order: 3};
|
|
run(config, three, moveConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.parent),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.parent = {order: 0};
|
|
config.responderRelease.parent = {order: 1};
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
it('should grant responder child while capturing move', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
|
|
config.startShouldSetResponder.captured.grandParent = {order: 0};
|
|
config.startShouldSetResponder.captured.parent = {order: 1};
|
|
config.startShouldSetResponder.captured.child = {order: 2};
|
|
config.startShouldSetResponder.bubbled.child = {order: 3};
|
|
config.startShouldSetResponder.bubbled.parent = {order: 4};
|
|
config.startShouldSetResponder.bubbled.grandParent = {order: 5};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.moveShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.captured.child = {order: 2, returnVal: true};
|
|
config.responderGrant.child = {order: 3};
|
|
config.responderMove.child = {order: 4};
|
|
run(config, three, moveConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.child),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.child = {order: 0};
|
|
config.responderRelease.child = {order: 1};
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
it('should grant responder child while bubbling move', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
|
|
config.startShouldSetResponder.captured.grandParent = {order: 0};
|
|
config.startShouldSetResponder.captured.parent = {order: 1};
|
|
config.startShouldSetResponder.captured.child = {order: 2};
|
|
config.startShouldSetResponder.bubbled.child = {order: 3};
|
|
config.startShouldSetResponder.bubbled.parent = {order: 4};
|
|
config.startShouldSetResponder.bubbled.grandParent = {order: 5};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.moveShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.captured.child = {order: 2, returnVal: false};
|
|
config.moveShouldSetResponder.bubbled.child = {order: 3, returnVal: true};
|
|
config.responderGrant.child = {order: 4};
|
|
config.responderMove.child = {order: 5};
|
|
run(config, three, moveConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.child),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.child = {order: 0};
|
|
config.responderRelease.child = {order: 1};
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
it('should grant responder parent while bubbling move', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
|
|
config.startShouldSetResponder.captured.grandParent = {order: 0};
|
|
config.startShouldSetResponder.captured.parent = {order: 1};
|
|
config.startShouldSetResponder.captured.child = {order: 2};
|
|
config.startShouldSetResponder.bubbled.child = {order: 3};
|
|
config.startShouldSetResponder.bubbled.parent = {order: 4};
|
|
config.startShouldSetResponder.bubbled.grandParent = {order: 5};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.moveShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.captured.child = {order: 2, returnVal: false};
|
|
config.moveShouldSetResponder.bubbled.child = {order: 3, returnVal: false};
|
|
config.moveShouldSetResponder.bubbled.parent = {order: 4, returnVal: true};
|
|
config.responderGrant.parent = {order: 5};
|
|
config.responderMove.parent = {order: 6};
|
|
run(config, three, moveConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.parent),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.parent = {order: 0};
|
|
config.responderRelease.parent = {order: 1};
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
it('should grant responder grandParent while bubbling move', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
|
|
config.startShouldSetResponder.captured.grandParent = {order: 0};
|
|
config.startShouldSetResponder.captured.parent = {order: 1};
|
|
config.startShouldSetResponder.captured.child = {order: 2};
|
|
config.startShouldSetResponder.bubbled.child = {order: 3};
|
|
config.startShouldSetResponder.bubbled.parent = {order: 4};
|
|
config.startShouldSetResponder.bubbled.grandParent = {order: 5};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.moveShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.captured.child = {order: 2, returnVal: false};
|
|
config.moveShouldSetResponder.bubbled.child = {order: 3, returnVal: false};
|
|
config.moveShouldSetResponder.bubbled.parent = {order: 4, returnVal: false};
|
|
config.moveShouldSetResponder.bubbled.grandParent = {
|
|
order: 5,
|
|
returnVal: true,
|
|
};
|
|
config.responderGrant.grandParent = {order: 6};
|
|
config.responderMove.grandParent = {order: 7};
|
|
run(config, three, moveConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.grandParent),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.grandParent = {order: 0};
|
|
config.responderRelease.grandParent = {order: 1};
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
/**
|
|
* Common ancestor tests
|
|
* ---------------------
|
|
*/
|
|
|
|
it('should bubble negotiation to first common ancestor of responder', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: true,
|
|
};
|
|
config.responderGrant.parent = {order: 2};
|
|
config.responderStart.parent = {order: 3};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.parent),
|
|
);
|
|
|
|
// While `parent` is still responder, we create new handlers that verify
|
|
// the ordering of propagation, restarting the count at `0`.
|
|
config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
|
|
config.startShouldSetResponder.bubbled.grandParent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.responderStart.parent = {order: 2};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.parent),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.parent = {order: 0};
|
|
config.responderRelease.parent = {order: 1};
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
it('should bubble negotiation to first common ancestor of responder then transfer', () => {
|
|
let config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: true,
|
|
};
|
|
config.responderGrant.parent = {order: 2};
|
|
config.responderStart.parent = {order: 3};
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.parent),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
|
|
// Parent is responder, and responder is transferred by a second touch start
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: true,
|
|
};
|
|
config.responderGrant.grandParent = {order: 1};
|
|
config.responderTerminationRequest.parent = {order: 2, returnVal: true};
|
|
config.responderTerminate.parent = {order: 3};
|
|
config.responderStart.grandParent = {order: 4};
|
|
run(
|
|
config,
|
|
three,
|
|
startConfig(three.child, [three.child, three.child], [1]),
|
|
);
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.grandParent),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.grandParent = {order: 0};
|
|
// one remains\ /one ended \
|
|
run(config, three, endConfig(three.child, [three.child, three.child], [1]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.grandParent),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.grandParent = {order: 0};
|
|
config.responderRelease.grandParent = {order: 1};
|
|
run(config, three, endConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
/**
|
|
* If nothing is responder, then the negotiation should propagate directly to
|
|
* the deepest target in the second touch.
|
|
*/
|
|
it('should negotiate with deepest target on second touch if nothing is responder', () => {
|
|
// Initially nothing wants to become the responder
|
|
let config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.parent = {
|
|
order: 2,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.grandParent = {
|
|
order: 3,
|
|
returnVal: false,
|
|
};
|
|
|
|
run(config, three, startConfig(three.parent, [three.parent], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
|
|
// Now child wants to become responder. Negotiation should bubble as deep
|
|
// as the target is because we don't find first common ancestor (with
|
|
// current responder) because there is no current responder.
|
|
// (Even if this is the second active touch).
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.child = {
|
|
order: 2,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: true};
|
|
config.responderGrant.child = {order: 4};
|
|
config.responderStart.child = {order: 5};
|
|
// / Two active touches \ /one of them new\
|
|
run(
|
|
config,
|
|
three,
|
|
startConfig(three.child, [three.parent, three.child], [1]),
|
|
);
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.child),
|
|
);
|
|
|
|
// Now we remove the original first touch, keeping the second touch that
|
|
// started within the current responder (child). Nothing changes because
|
|
// there's still touches that started inside of the current responder.
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.child = {order: 0};
|
|
// / one ended\ /one remains \
|
|
run(
|
|
config,
|
|
three,
|
|
endConfig(three.child, [three.parent, three.child], [0]),
|
|
);
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.child),
|
|
);
|
|
|
|
// Okay, now let's add back that first touch (nothing should change) and
|
|
// then we'll try peeling back the touches in the opposite order to make
|
|
// sure that first removing the second touch instantly causes responder to
|
|
// be released.
|
|
config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.parent = {
|
|
order: 2,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.grandParent = {
|
|
order: 3,
|
|
returnVal: false,
|
|
};
|
|
// Interesting: child still gets moves even though touch target is parent!
|
|
// Current responder gets a `responderStart` for any touch while responder.
|
|
config.responderStart.child = {order: 4};
|
|
// / Two active touches \ /one of them new\
|
|
run(
|
|
config,
|
|
three,
|
|
startConfig(three.parent, [three.child, three.parent], [1]),
|
|
);
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.child),
|
|
);
|
|
|
|
// Now, move that new touch that had no effect, and did not start within
|
|
// the current responder.
|
|
config = oneEventLoopTestConfig(three);
|
|
config.moveShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.bubbled.parent = {order: 2, returnVal: false};
|
|
config.moveShouldSetResponder.bubbled.grandParent = {
|
|
order: 3,
|
|
returnVal: false,
|
|
};
|
|
// Interesting: child still gets moves even though touch target is parent!
|
|
// Current responder gets a `responderMove` for any touch while responder.
|
|
config.responderMove.child = {order: 4};
|
|
// / Two active touches \ /one of them moved\
|
|
run(
|
|
config,
|
|
three,
|
|
moveConfig(three.parent, [three.child, three.parent], [1]),
|
|
);
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.child),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.child = {order: 0};
|
|
config.responderRelease.child = {order: 1};
|
|
// /child end \ /parent remain\
|
|
run(
|
|
config,
|
|
three,
|
|
endConfig(three.child, [three.child, three.parent], [0]),
|
|
);
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
/**
|
|
* If nothing is responder, then the negotiation should propagate directly to
|
|
* the deepest target in the second touch.
|
|
*/
|
|
it('should negotiate until first common ancestor when there are siblings', () => {
|
|
// Initially nothing wants to become the responder
|
|
let config = oneEventLoopTestConfig(siblings);
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.childOne = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.childOne = {
|
|
order: 2,
|
|
returnVal: true,
|
|
};
|
|
config.responderGrant.childOne = {order: 3};
|
|
config.responderStart.childOne = {order: 4};
|
|
|
|
run(
|
|
config,
|
|
siblings,
|
|
startConfig(siblings.childOne, [siblings.childOne], [0]),
|
|
);
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(siblings.childOne),
|
|
);
|
|
|
|
// If the touch target is the sibling item, the negotiation should only
|
|
// propagate to first common ancestor of current responder and sibling (so
|
|
// the parent).
|
|
config = oneEventLoopTestConfig(siblings);
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.responderStart.childOne = {order: 2};
|
|
|
|
const touchConfig = startConfig(
|
|
siblings.childTwo,
|
|
[siblings.childOne, siblings.childTwo],
|
|
[1],
|
|
);
|
|
run(config, siblings, touchConfig);
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(siblings.childOne),
|
|
);
|
|
|
|
// move childOne
|
|
config = oneEventLoopTestConfig(siblings);
|
|
config.moveShouldSetResponder.captured.parent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.bubbled.parent = {order: 1, returnVal: false};
|
|
config.responderMove.childOne = {order: 2};
|
|
run(
|
|
config,
|
|
siblings,
|
|
moveConfig(
|
|
siblings.childOne,
|
|
[siblings.childOne, siblings.childTwo],
|
|
[0],
|
|
),
|
|
);
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(siblings.childOne),
|
|
);
|
|
|
|
// move childTwo: Only negotiates to `parent`.
|
|
config = oneEventLoopTestConfig(siblings);
|
|
config.moveShouldSetResponder.captured.parent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.bubbled.parent = {order: 1, returnVal: false};
|
|
config.responderMove.childOne = {order: 2};
|
|
run(
|
|
config,
|
|
siblings,
|
|
moveConfig(
|
|
siblings.childTwo,
|
|
[siblings.childOne, siblings.childTwo],
|
|
[1],
|
|
),
|
|
);
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(siblings.childOne),
|
|
);
|
|
});
|
|
|
|
it('should notify of being rejected. responderStart/Move happens on current responder', () => {
|
|
// Initially nothing wants to become the responder
|
|
let config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.child = {
|
|
order: 2,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: true};
|
|
config.responderGrant.child = {order: 4};
|
|
config.responderStart.child = {order: 5};
|
|
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.child),
|
|
);
|
|
|
|
// Suppose parent wants to become responder on move, and is rejected
|
|
config = oneEventLoopTestConfig(three);
|
|
config.moveShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.moveShouldSetResponder.bubbled.parent = {order: 2, returnVal: true};
|
|
config.responderGrant.parent = {order: 3};
|
|
config.responderTerminationRequest.child = {order: 4, returnVal: false};
|
|
config.responderReject.parent = {order: 5};
|
|
// The start/move should occur on the original responder if new one is rejected
|
|
config.responderMove.child = {order: 6};
|
|
|
|
let touchConfig = moveConfig(three.child, [three.child], [0]);
|
|
run(config, three, touchConfig);
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.child),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.parent = {order: 2, returnVal: true};
|
|
config.responderGrant.parent = {order: 3};
|
|
config.responderTerminationRequest.child = {order: 4, returnVal: false};
|
|
config.responderReject.parent = {order: 5};
|
|
// The start/move should occur on the original responder if new one is rejected
|
|
config.responderStart.child = {order: 6};
|
|
|
|
touchConfig = startConfig(three.child, [three.child, three.child], [1]);
|
|
run(config, three, touchConfig);
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.child),
|
|
);
|
|
});
|
|
|
|
it('should negotiate scroll', () => {
|
|
// Initially nothing wants to become the responder
|
|
let config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.child = {
|
|
order: 2,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: true};
|
|
config.responderGrant.child = {order: 4};
|
|
config.responderStart.child = {order: 5};
|
|
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.child),
|
|
);
|
|
|
|
// If the touch target is the sibling item, the negotiation should only
|
|
// propagate to first common ancestor of current responder and sibling (so
|
|
// the parent).
|
|
config = oneEventLoopTestConfig(three);
|
|
config.scrollShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.scrollShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.scrollShouldSetResponder.bubbled.parent = {
|
|
order: 2,
|
|
returnVal: true,
|
|
};
|
|
config.responderGrant.parent = {order: 3};
|
|
config.responderTerminationRequest.child = {order: 4, returnVal: false};
|
|
config.responderReject.parent = {order: 5};
|
|
|
|
run(config, three, {
|
|
topLevelType: 'topScroll',
|
|
targetInst: getInstanceFromNode(three.parent),
|
|
nativeEvent: {},
|
|
});
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.child),
|
|
);
|
|
|
|
// Now lets let the scroll take control this time.
|
|
config = oneEventLoopTestConfig(three);
|
|
config.scrollShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.scrollShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.scrollShouldSetResponder.bubbled.parent = {
|
|
order: 2,
|
|
returnVal: true,
|
|
};
|
|
config.responderGrant.parent = {order: 3};
|
|
config.responderTerminationRequest.child = {order: 4, returnVal: true};
|
|
config.responderTerminate.child = {order: 5};
|
|
|
|
run(config, three, {
|
|
topLevelType: 'topScroll',
|
|
targetInst: getInstanceFromNode(three.parent),
|
|
nativeEvent: {},
|
|
});
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.parent),
|
|
);
|
|
});
|
|
|
|
it('should cancel correctly', () => {
|
|
// Initially our child becomes responder
|
|
let config = oneEventLoopTestConfig(three);
|
|
config.startShouldSetResponder.captured.grandParent = {
|
|
order: 0,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.parent = {
|
|
order: 1,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.captured.child = {
|
|
order: 2,
|
|
returnVal: false,
|
|
};
|
|
config.startShouldSetResponder.bubbled.child = {order: 3, returnVal: true};
|
|
config.responderGrant.child = {order: 4};
|
|
config.responderStart.child = {order: 5};
|
|
|
|
run(config, three, startConfig(three.child, [three.child], [0]));
|
|
expect(ResponderEventPlugin._getResponder()).toBe(
|
|
getInstanceFromNode(three.child),
|
|
);
|
|
|
|
config = oneEventLoopTestConfig(three);
|
|
config.responderEnd.child = {order: 0};
|
|
config.responderTerminate.child = {order: 1};
|
|
|
|
const nativeEvent = _touchConfig(
|
|
'topTouchCancel',
|
|
three.child,
|
|
[three.child],
|
|
[0],
|
|
);
|
|
run(config, three, nativeEvent);
|
|
expect(ResponderEventPlugin._getResponder()).toBe(null);
|
|
});
|
|
|
|
it('should determine the first common ancestor correctly', () => {
|
|
// This test was moved here from the ReactTreeTraversal test since only the
|
|
// ResponderEventPlugin uses `getLowestCommonAncestor`
|
|
const React = require('react');
|
|
const ReactTestUtils = require('react-dom/test-utils');
|
|
const ReactTreeTraversal = require('shared/ReactTreeTraversal');
|
|
const ReactDOMComponentTree = require('../../react-dom/src/client/ReactDOMComponentTree');
|
|
|
|
class ChildComponent extends React.Component {
|
|
render() {
|
|
return (
|
|
<div ref="DIV" id={this.props.id + '__DIV'}>
|
|
<div ref="DIV_1" id={this.props.id + '__DIV_1'} />
|
|
<div ref="DIV_2" id={this.props.id + '__DIV_2'} />
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
class ParentComponent extends React.Component {
|
|
render() {
|
|
return (
|
|
<div ref="P" id="P">
|
|
<div ref="P_P1" id="P_P1">
|
|
<ChildComponent ref="P_P1_C1" id="P_P1_C1" />
|
|
<ChildComponent ref="P_P1_C2" id="P_P1_C2" />
|
|
</div>
|
|
<div ref="P_OneOff" id="P_OneOff" />
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
const parent = ReactTestUtils.renderIntoDocument(<ParentComponent />);
|
|
|
|
const ancestors = [
|
|
// Common ancestor with self is self.
|
|
{
|
|
one: parent.refs.P_P1_C1.refs.DIV_1,
|
|
two: parent.refs.P_P1_C1.refs.DIV_1,
|
|
com: parent.refs.P_P1_C1.refs.DIV_1,
|
|
},
|
|
// Common ancestor with self is self - even if topmost DOM.
|
|
{one: parent.refs.P, two: parent.refs.P, com: parent.refs.P},
|
|
// Siblings
|
|
{
|
|
one: parent.refs.P_P1_C1.refs.DIV_1,
|
|
two: parent.refs.P_P1_C1.refs.DIV_2,
|
|
com: parent.refs.P_P1_C1.refs.DIV,
|
|
},
|
|
// Common ancestor with parent is the parent.
|
|
{
|
|
one: parent.refs.P_P1_C1.refs.DIV_1,
|
|
two: parent.refs.P_P1_C1.refs.DIV,
|
|
com: parent.refs.P_P1_C1.refs.DIV,
|
|
},
|
|
// Common ancestor with grandparent is the grandparent.
|
|
{
|
|
one: parent.refs.P_P1_C1.refs.DIV_1,
|
|
two: parent.refs.P_P1,
|
|
com: parent.refs.P_P1,
|
|
},
|
|
// Grandparent across subcomponent boundaries.
|
|
{
|
|
one: parent.refs.P_P1_C1.refs.DIV_1,
|
|
two: parent.refs.P_P1_C2.refs.DIV_1,
|
|
com: parent.refs.P_P1,
|
|
},
|
|
// Something deep with something one-off.
|
|
{
|
|
one: parent.refs.P_P1_C1.refs.DIV_1,
|
|
two: parent.refs.P_OneOff,
|
|
com: parent.refs.P,
|
|
},
|
|
];
|
|
let i;
|
|
for (i = 0; i < ancestors.length; i++) {
|
|
const plan = ancestors[i];
|
|
const firstCommon = ReactTreeTraversal.getLowestCommonAncestor(
|
|
ReactDOMComponentTree.getInstanceFromNode(plan.one),
|
|
ReactDOMComponentTree.getInstanceFromNode(plan.two),
|
|
);
|
|
expect(firstCommon).toBe(
|
|
ReactDOMComponentTree.getInstanceFromNode(plan.com),
|
|
);
|
|
}
|
|
});
|
|
});
|