diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 2f24ab240..20a060f8d 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -698,6 +698,7 @@ const TextInput = React.createClass({ accessible={props.accessible} accessibilityLabel={props.accessibilityLabel} accessibilityTraits={props.accessibilityTraits} + nativeID={this.props.nativeID} testID={props.testID}> {textContainer} @@ -748,6 +749,7 @@ const TextInput = React.createClass({ accessible={this.props.accessible} accessibilityLabel={this.props.accessibilityLabel} accessibilityComponentType={this.props.accessibilityComponentType} + nativeID={this.props.nativeID} testID={this.props.testID}> {textContainer} diff --git a/Libraries/Components/Touchable/TouchableBounce.js b/Libraries/Components/Touchable/TouchableBounce.js index 333cf6cbf..ae861f5e1 100644 --- a/Libraries/Components/Touchable/TouchableBounce.js +++ b/Libraries/Components/Touchable/TouchableBounce.js @@ -141,6 +141,7 @@ var TouchableBounce = React.createClass({ accessibilityLabel={this.props.accessibilityLabel} accessibilityComponentType={this.props.accessibilityComponentType} accessibilityTraits={this.props.accessibilityTraits} + nativeID={this.props.nativeID} testID={this.props.testID} hitSlop={this.props.hitSlop} onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder} diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js index ee0d056b2..df821100c 100644 --- a/Libraries/Components/Touchable/TouchableHighlight.js +++ b/Libraries/Components/Touchable/TouchableHighlight.js @@ -22,7 +22,6 @@ var TimerMixin = require('react-timer-mixin'); var Touchable = require('Touchable'); var TouchableWithoutFeedback = require('TouchableWithoutFeedback'); var View = require('View'); - const ViewPropTypes = require('ViewPropTypes'); var ensureComponentIsNative = require('ensureComponentIsNative'); @@ -267,6 +266,7 @@ var TouchableHighlight = React.createClass({ onResponderMove={this.touchableHandleResponderMove} onResponderRelease={this.touchableHandleResponderRelease} onResponderTerminate={this.touchableHandleResponderTerminate} + nativeID={this.props.nativeID} testID={this.props.testID}> {React.cloneElement( React.Children.only(this.props.children), diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js index 40079f445..0138fea92 100644 --- a/Libraries/Components/Touchable/TouchableOpacity.js +++ b/Libraries/Components/Touchable/TouchableOpacity.js @@ -165,7 +165,7 @@ var TouchableOpacity = React.createClass({ _opacityFocused: function() { this.setOpacityTo(this.props.focusedOpacity); }, - + _getChildStyleOpacityWithDefault: function() { var childStyle = flattenStyle(this.props.style) || {}; return childStyle.opacity == undefined ? 1 : childStyle.opacity; @@ -179,6 +179,7 @@ var TouchableOpacity = React.createClass({ accessibilityComponentType={this.props.accessibilityComponentType} accessibilityTraits={this.props.accessibilityTraits} style={[this.props.style, {opacity: this.state.anim}]} + nativeID={this.props.nativeID} testID={this.props.testID} onLayout={this.props.onLayout} isTVSelectable={true} diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js index 0886c577e..cd1f855ff 100755 --- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js +++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js @@ -176,6 +176,8 @@ const TouchableWithoutFeedback = React.createClass({ accessibilityComponentType: this.props.accessibilityComponentType, accessibilityTraits: this.props.accessibilityTraits, // $FlowFixMe(>=0.41.0) + nativeID: this.props.nativeID, + // $FlowFixMe(>=0.41.0) testID: this.props.testID, onLayout: this.props.onLayout, hitSlop: this.props.hitSlop, diff --git a/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap index 29f7478ff..12896cafc 100644 --- a/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap +++ b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap @@ -9,6 +9,7 @@ exports[`TouchableHighlight renders correctly 1`] = ` hasTVPreferredFocus={undefined} hitSlop={undefined} isTVSelectable={true} + nativeID={undefined} onLayout={undefined} onResponderGrant={[Function]} onResponderMove={[Function]} diff --git a/Libraries/Components/View/ReactNativeViewAttributes.js b/Libraries/Components/View/ReactNativeViewAttributes.js index e5e83a40d..3a769ca5e 100644 --- a/Libraries/Components/View/ReactNativeViewAttributes.js +++ b/Libraries/Components/View/ReactNativeViewAttributes.js @@ -23,6 +23,7 @@ ReactNativeViewAttributes.UIView = { accessibilityLiveRegion: true, accessibilityTraits: true, importantForAccessibility: true, + nativeID: true, testID: true, renderToHardwareTextureAndroid: true, shouldRasterizeIOS: true, diff --git a/Libraries/Components/View/ViewPropTypes.js b/Libraries/Components/View/ViewPropTypes.js index 86f9f8787..a0f8b8ff5 100644 --- a/Libraries/Components/View/ViewPropTypes.js +++ b/Libraries/Components/View/ViewPropTypes.js @@ -173,6 +173,15 @@ module.exports = { */ testID: PropTypes.string, + /** + * Used to locate this view from native classes. + * + * > This disables the 'layout-only view removal' optimization for this view! + * + * @platform android + */ + nativeID: PropTypes.string, + /** * For most touch interactions, you'll simply want to wrap your component in * `TouchableHighlight` or `TouchableOpacity`. Check out `Touchable.js`, diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js index 2401589e9..986f69590 100644 --- a/Libraries/Text/Text.js +++ b/Libraries/Text/Text.js @@ -21,9 +21,9 @@ const StyleSheetPropType = require('StyleSheetPropType'); const TextStylePropTypes = require('TextStylePropTypes'); const Touchable = require('Touchable'); -const processColor = require('processColor'); const createReactNativeComponentClass = require('createReactNativeComponentClass'); const mergeFast = require('mergeFast'); +const processColor = require('processColor'); const { PropTypes } = React; @@ -177,6 +177,11 @@ const Text = React.createClass({ * Used to locate this view in end-to-end tests. */ testID: PropTypes.string, + /** + * Used to locate this view from native code. + * @platform android + */ + nativeID: PropTypes.string, /** * Specifies whether fonts should scale to respect Text Size accessibility settings. The * default is `true`. diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK index 992351f62..1a7646351 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK @@ -13,13 +13,14 @@ deps = [ react_native_target("java/com/facebook/react/modules/appstate:appstate"), react_native_target("java/com/facebook/react/modules/core:core"), react_native_target("java/com/facebook/react/modules/datepicker:datepicker"), + react_native_target("java/com/facebook/react/modules/deviceinfo:deviceinfo"), react_native_target("java/com/facebook/react/modules/share:share"), react_native_target("java/com/facebook/react/modules/systeminfo:systeminfo"), - react_native_target("java/com/facebook/react/modules/deviceinfo:deviceinfo"), react_native_target("java/com/facebook/react/modules/timepicker:timepicker"), react_native_target("java/com/facebook/react/touch:touch"), react_native_target("java/com/facebook/react/uimanager:uimanager"), react_native_target("java/com/facebook/react/uimanager/annotations:annotations"), + react_native_target("java/com/facebook/react/uimanager/util:util"), react_native_target("java/com/facebook/react/views/picker:picker"), react_native_target("java/com/facebook/react/views/progressbar:progressbar"), react_native_target("java/com/facebook/react/views/scroll:scroll"), diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/NativeIdTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/NativeIdTestCase.java new file mode 100644 index 000000000..cccec2ae1 --- /dev/null +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/NativeIdTestCase.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2014-present, 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. + */ + +package com.facebook.react.tests; + +import java.util.Arrays; +import java.util.List; + +import android.view.View; + +import com.facebook.react.testing.ReactAppInstrumentationTestCase; +import com.facebook.react.uimanager.util.ReactFindViewUtil; + +/** + * Tests that the 'nativeID' property can be set on various views. + * The 'nativeID' property is used to reference react managed views from native code. + */ +public class NativeIdTestCase extends ReactAppInstrumentationTestCase { + + @Override + protected String getReactApplicationKeyUnderTest() { + return "NativeIdTestApp"; + } + + private final List viewTags = Arrays.asList( + "Image", + "Text", + "TouchableBounce", + "TouchableHighlight", + "TouchableOpacity", + "TouchableWithoutFeedback", + "TextInput", + "View"); + + public void testPropertyIsSetForViews() { + for (String nativeId : viewTags) { + View viewWithTag = ReactFindViewUtil.findViewByNativeId( + getActivity().getRootView(), + nativeId); + assertNotNull( + "View with nativeID " + nativeId + " was not found. Check NativeIdTestModule.js.", + viewWithTag); + } + } +} diff --git a/ReactAndroid/src/androidTest/js/NativeIdTestModule.js b/ReactAndroid/src/androidTest/js/NativeIdTestModule.js new file mode 100644 index 000000000..d1bf8c57d --- /dev/null +++ b/ReactAndroid/src/androidTest/js/NativeIdTestModule.js @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2013-present, 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 NativeIdTestModule + * @flow + */ + +'use strict'; + +const Image = require('Image'); +const React = require('React'); +const StyleSheet = require('StyleSheet'); +const Text = require('Text'); +const TextInput = require('TextInput'); +const TouchableBounce = require('TouchableBounce'); +const TouchableHighlight = require('TouchableHighlight'); +const TouchableOpacity = require('TouchableOpacity'); +const TouchableWithoutFeedback = require('TouchableWithoutFeedback'); +const View = require('View'); + +/** + * All the views implemented on Android, each with the nativeID property set. + * We test that: + * - The app renders fine + * - The nativeID property is passed to the native views + */ +class NativeIdTestApp extends React.Component { + render() { + const uri = 'data:image/gif;base64,' + + 'R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapy' + + 'uvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/' + + 'TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5' + + 'iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97V' + + 'riy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7'; + return ( + + + text + + + TouchableBounce + + + TouchableHighlight + + + TouchableOpacity + + + + TouchableWithoutFeedback + + + + + ); + } +} + +const styles = StyleSheet.create({ + base: { + width: 150, + height: 50, + }, +}); + +module.exports = { + NativeIdTestApp: NativeIdTestApp, +}; diff --git a/ReactAndroid/src/androidTest/js/TestBundle.js b/ReactAndroid/src/androidTest/js/TestBundle.js index 1d0242558..5cbfea239 100644 --- a/ReactAndroid/src/androidTest/js/TestBundle.js +++ b/ReactAndroid/src/androidTest/js/TestBundle.js @@ -69,6 +69,10 @@ var apps = [ appKey: 'MultitouchHandlingTestAppModule', component: () => require('MultitouchHandlingTestAppModule') }, +{ + appKey: 'NativeIdTestApp', + component: () => require('NativeIdTestModule').NativeIdTestApp +}, { appKey: 'PickerAndroidTestApp', component: () => require('PickerAndroidTestModule').PickerAndroidTestApp, diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index 16c85690a..271b126b3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -41,6 +41,7 @@ public abstract class BaseViewManager + + +