Fix captured/bubbled in ReactNativeTreeTraversal (#8019)

Follow-up to #7741. Added a test for RN event bubbling that fails before the fix.
(cherry picked from commit a8beab3341)
This commit is contained in:
Ben Alpert
2016-10-20 11:17:06 -07:00
committed by Paul O’Shannessy
parent ac0c3e60fb
commit fbe19e90b9
3 changed files with 137 additions and 2 deletions
@@ -82,10 +82,10 @@ function traverseTwoPhase(inst, fn, arg) {
}
var i;
for (i = path.length; i-- > 0;) {
fn(path[i], false, arg);
fn(path[i], 'captured', arg);
}
for (i = 0; i < path.length; i++) {
fn(path[i], true, arg);
fn(path[i], 'bubbled', arg);
}
}
@@ -18,6 +18,52 @@ var RCTUIManager = {
updateView: jest.fn(),
removeSubviewsFromContainerWithID: jest.fn(),
replaceExistingNonRootView: jest.fn(),
customBubblingEventTypes: {
topBlur: {
phasedRegistrationNames: {
bubbled: 'onBlur',
captured: 'onBlurCapture',
},
},
topFocus: {
phasedRegistrationNames: {
bubbled: 'onFocus',
captured: 'onFocusCapture',
},
},
topTouchCancel: {
phasedRegistrationNames: {
bubbled: 'onTouchCancel',
captured: 'onTouchCancelCapture',
},
},
topTouchEnd: {
phasedRegistrationNames: {
bubbled: 'onTouchEnd',
captured: 'onTouchEndCapture',
},
},
topTouchMove: {
phasedRegistrationNames: {
bubbled: 'onTouchMove',
captured: 'onTouchMoveCapture',
},
},
topTouchStart: {
phasedRegistrationNames: {
bubbled: 'onTouchStart',
captured: 'onTouchStartCapture',
},
},
},
customDirectEventTypes: {
topAccessibilityTap: {
registrationName: 'onAccessibilityTap',
},
topTextLayout: {
registrationName: 'onTextLayout',
},
},
};
module.exports = RCTUIManager;
@@ -0,0 +1,89 @@
/**
* 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 RCTEventEmitter;
var React;
var ReactErrorUtils;
var ReactNative;
var UIManager;
var createReactNativeComponentClass;
beforeEach(() => {
jest.resetModuleRegistry();
RCTEventEmitter = require('RCTEventEmitter');
React = require('React');
ReactErrorUtils = require('ReactErrorUtils');
ReactNative = require('ReactNative');
UIManager = require('UIManager');
createReactNativeComponentClass = require('createReactNativeComponentClass');
// Ensure errors from event callbacks are properly surfaced (otherwise,
// jest/jsdom swallows them when we do the .dispatchEvent call)
ReactErrorUtils.invokeGuardedCallback =
ReactErrorUtils.invokeGuardedCallbackWithCatch;
});
it('handles events', () => {
expect(RCTEventEmitter.register.mock.calls.length).toBe(1);
var EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
var View = createReactNativeComponentClass({
validAttributes: { foo: true },
uiViewClassName: 'View',
});
var log = [];
ReactNative.render(
<View
foo="outer"
onTouchEnd={() => log.push('outer touchend')}
onTouchEndCapture={() => log.push('outer touchend capture')}
onTouchStart={() => log.push('outer touchstart')}
onTouchStartCapture={() => log.push('outer touchstart capture')}>
<View
foo="inner"
onTouchEndCapture={() => log.push('inner touchend capture')}
onTouchEnd={() => log.push('inner touchend')}
onTouchStartCapture={() => log.push('inner touchstart capture')}
onTouchStart={() => log.push('inner touchstart')}
/>
</View>,
1
);
expect(UIManager.createView.mock.calls.length).toBe(2);
var innerTag = UIManager.createView.mock.calls[1][0];
EventEmitter.receiveTouches(
'topTouchStart',
[{target: innerTag, identifier: 17}],
[0]
);
EventEmitter.receiveTouches(
'topTouchEnd',
[{target: innerTag, identifier: 17}],
[0]
);
expect(log).toEqual([
'outer touchstart capture',
'inner touchstart capture',
'inner touchstart',
'outer touchstart',
'outer touchend capture',
'inner touchend capture',
'inner touchend',
'outer touchend',
]);
});