From e31123e224149c15aa318e4512d17de771b57191 Mon Sep 17 00:00:00 2001 From: jsdevel Date: Wed, 21 Sep 2016 18:30:45 -0700 Subject: [PATCH] feat: Adding full support for testID with uiautomator. * Calling view.setId() with the matching resource-id of an id found in R.class. Added TestIdUtil to facilitate this. * Updating the android sample project to include a testID example. Updating the e2e test to use it. * Changing the signature for virtually all Event Classes to require the View instead of the viewTag. This reduces the number of locations where TestIdUtil.getOriginalReactTag is called. * Minimizing the impact in non __DEV__ environments where testID should not be set by simply returning view.getId() in TestIdUtil.getOriginalReactTag. * This closes #9777. --- Libraries/Components/View/View.js | 24 ++++ .../react/testing/ReactAppTestActivity.java | 2 +- .../react/testing/ReactTestHelper.java | 34 +---- .../tests/CatalystUIManagerTestCase.java | 13 +- .../react/tests/TextInputTestCase.java | 14 +- .../react/XReactInstanceManagerImpl.java | 5 +- .../com/facebook/react/common/TestIdUtil.java | 55 ++++++++ .../react/common/ViewMethodsUtil.java | 16 +++ .../main/java/com/facebook/react/touch/BUCK | 1 + .../react/touch/JSResponderHandler.java | 4 +- .../react/uimanager/BaseViewManager.java | 4 +- .../uimanager/NativeViewHierarchyManager.java | 38 ++++-- .../react/uimanager/TouchTargetHelper.java | 12 +- .../drawer/ReactDrawerLayoutManager.java | 10 +- .../react/views/image/ReactImageView.java | 16 ++- .../views/modal/ReactModalHostManager.java | 6 +- .../react/views/modal/ReactModalHostView.java | 4 +- .../views/picker/ReactPickerManager.java | 4 +- .../RecyclerViewBackedScrollView.java | 9 +- .../views/scroll/ReactScrollViewHelper.java | 4 +- .../views/slider/ReactSliderManager.java | 6 +- .../SwipeRefreshLayoutManager.java | 3 +- .../views/switchview/ReactSwitchManager.java | 4 +- .../react/views/text/ReactTextView.java | 4 +- .../textinput/ReactTextInputManager.java | 19 +-- .../views/toolbar/ReactToolbarManager.java | 6 +- .../react/views/viewpager/ReactViewPager.java | 12 +- .../views/webview/ReactWebViewManager.java | 14 +- .../java/com/facebook/react/RootViewTest.java | 2 +- .../NativeAnimatedNodeTraversalTest.java | 29 ++-- .../react/uimanager/BaseViewManagerTest.java | 125 ++++++++++++++++++ .../react/uimanager/UIManagerModuleTest.java | 13 +- 32 files changed, 383 insertions(+), 129 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/common/TestIdUtil.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/common/ViewMethodsUtil.java create mode 100644 ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.java diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index ccad6cefac72a9..cbe791c0d19854 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -259,6 +259,30 @@ const View = React.createClass({ * Used to locate this view in end-to-end tests. * * > This disables the 'layout-only view removal' optimization for this view! + * + * ### Android Specifics + * + * While React Native does *not* utilize XML based layouts for android Views it + * is still possible to add [android:id](https://developer.android.com/reference/android/view/View.html#attr_android:id) + * to the underlying View in order to support + * [findViewById](https://developer.android.com/reference/android/app/Activity.html#findViewById(int)). + * + * This is achieved by: + * + * 1. Defining a resource id in your android project's `res` folder (typically at + * `./android/app/src/main/res/values/ids.xml`). + * + * 2. Adding your resource ids to `ids.xml` e.g. + * + * ```xml + * + * + * + * + * + * ``` + * 3. Using the resource id as `testID` e.g. ``. + * */ testID: PropTypes.string, diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java index 4e84fdab239ca3..dc0200e3cc97a6 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java @@ -58,7 +58,7 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(rootView); mScreenshotingFrameLayout = new ScreenshotingFrameLayout(this); - mScreenshotingFrameLayout.setId(ROOT_VIEW_ID); + mScreenshotingFrameLayout.setTag(ROOT_VIEW_ID); rootView.addView(mScreenshotingFrameLayout); mReactRootView = new ReactRootView(this); diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java index 8a65a51f61908d..245536fcddd96b 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java @@ -29,6 +29,7 @@ import com.facebook.react.bridge.NativeModuleCallExceptionHandler; import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec; +import com.facebook.react.common.TestIdUtil; import com.facebook.react.cxxbridge.CatalystInstanceImpl; import com.facebook.react.cxxbridge.JSBundleLoader; import com.facebook.react.cxxbridge.JSCJavaScriptExecutor; @@ -179,37 +180,6 @@ public static T getViewAtPath(ViewGroup rootView, int... path) * propagated into view content description. */ public static View getViewWithReactTestId(View rootView, String testId) { - return findChild(rootView, hasTagValue(testId)); - } - - public static String getTestId(View view) { - return view.getTag() instanceof String ? (String) view.getTag() : null; - } - - private static View findChild(View root, Predicate predicate) { - if (predicate.apply(root)) { - return root; - } - if (root instanceof ViewGroup) { - ViewGroup viewGroup = (ViewGroup) root; - for (int i = 0; i < viewGroup.getChildCount(); i++) { - View child = viewGroup.getChildAt(i); - View result = findChild(child, predicate); - if (result != null) { - return result; - } - } - } - return null; - } - - private static Predicate hasTagValue(final String tagValue) { - return new Predicate() { - @Override - public boolean apply(View view) { - Object tag = view.getTag(); - return tag != null && tag.equals(tagValue); - } - }; + return rootView.findViewById(TestIdUtil.getTestId(testId)); } } diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystUIManagerTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystUIManagerTestCase.java index f73efaf4677864..7c362e4a2d96c1 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystUIManagerTestCase.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystUIManagerTestCase.java @@ -23,7 +23,6 @@ import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.modules.systeminfo.AndroidInfoModule; import com.facebook.react.uimanager.PixelUtil; -import com.facebook.react.uimanager.UIImplementation; import com.facebook.react.uimanager.UIImplementationProvider; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewManager; @@ -34,6 +33,8 @@ import com.facebook.react.testing.ReactIntegrationTestCase; import com.facebook.react.testing.ReactTestHelper; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * Test case for basic {@link UIManagerModule} functionality. */ @@ -102,7 +103,7 @@ public void run() { public void testFlexUIRendered() { FrameLayout rootView = createRootView(); - jsModule.renderFlexTestApplication(rootView.getId()); + jsModule.renderFlexTestApplication(reactTagFor(rootView)); waitForBridgeAndUIIdle(); assertEquals(1, rootView.getChildCount()); @@ -126,7 +127,7 @@ public void testFlexUIRendered() { // Find what could be different and make the test independent of env // public void testFlexWithTextViews() { // FrameLayout rootView = createRootView(); - // jsModule.renderFlexWithTextApplication(rootView.getId()); + // jsModule.renderFlexWithTextApplication(reactTagFor(rootView)); // waitForBridgeAndUIIdle(); // // assertEquals(1, rootView.getChildCount()); @@ -164,7 +165,7 @@ public void testFlexUIRendered() { public void testAbsolutePositionUIRendered() { FrameLayout rootView = createRootView(); - jsModule.renderAbsolutePositionTestApplication(rootView.getId()); + jsModule.renderAbsolutePositionTestApplication(reactTagFor(rootView)); waitForBridgeAndUIIdle(); assertEquals(1, rootView.getChildCount()); @@ -178,7 +179,7 @@ public void testAbsolutePositionUIRendered() { public void testUpdatePositionInList() { FrameLayout rootView = createRootView(); - jsModule.renderUpdatePositionInListTestApplication(rootView.getId()); + jsModule.renderUpdatePositionInListTestApplication(reactTagFor(rootView)); waitForBridgeAndUIIdle(); ViewGroup containerView = getViewByTestId(rootView, "container"); @@ -207,7 +208,7 @@ public void testUpdatePositionInList() { public void testAbsolutePositionBottomRightUIRendered() { FrameLayout rootView = createRootView(); - jsModule.renderAbsolutePositionBottomRightTestApplication(rootView.getId()); + jsModule.renderAbsolutePositionBottomRightTestApplication(reactTagFor(rootView)); waitForBridgeAndUIIdle(); assertEquals(1, rootView.getChildCount()); diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TextInputTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TextInputTestCase.java index 40aa97813554a2..20e2a73d93c901 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TextInputTestCase.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/TextInputTestCase.java @@ -25,6 +25,8 @@ import com.facebook.react.views.textinput.ReactTextChangedEvent; import com.facebook.react.views.textinput.ReactTextInputEvent; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * Test to verify that TextInput renders correctly */ @@ -114,7 +116,7 @@ public void testMetionsInputColors() throws Throwable { eventDispatcher.dispatchEvent( new ReactTextChangedEvent( - reactEditText.getId(), + reactTagFor(reactEditText), newText.toString(), (int) PixelUtil.toDIPFromPixel(contentWidth), (int) PixelUtil.toDIPFromPixel(contentHeight), @@ -122,7 +124,7 @@ public void testMetionsInputColors() throws Throwable { eventDispatcher.dispatchEvent( new ReactTextInputEvent( - reactEditText.getId(), + reactTagFor(reactEditText), newText.toString(), "", start, @@ -146,7 +148,7 @@ public void testMetionsInputColors() throws Throwable { eventDispatcher.dispatchEvent( new ReactTextChangedEvent( - reactEditText.getId(), + reactTagFor(reactEditText), newText.toString(), (int) PixelUtil.toDIPFromPixel(contentWidth), (int) PixelUtil.toDIPFromPixel(contentHeight), @@ -154,7 +156,7 @@ public void testMetionsInputColors() throws Throwable { eventDispatcher.dispatchEvent( new ReactTextInputEvent( - reactEditText.getId(), + reactTagFor(reactEditText), moreText, "", start, @@ -178,7 +180,7 @@ public void testMetionsInputColors() throws Throwable { eventDispatcher.dispatchEvent( new ReactTextChangedEvent( - reactEditText.getId(), + reactTagFor(reactEditText), newText.toString(), (int) PixelUtil.toDIPFromPixel(contentWidth), (int) PixelUtil.toDIPFromPixel(contentHeight), @@ -186,7 +188,7 @@ public void testMetionsInputColors() throws Throwable { eventDispatcher.dispatchEvent( new ReactTextInputEvent( - reactEditText.getId(), + reactTagFor(reactEditText), moreText, "", start, diff --git a/ReactAndroid/src/main/java/com/facebook/react/XReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/XReactInstanceManagerImpl.java index 2a396cb09f2055..85b280b5c2090c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/XReactInstanceManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/XReactInstanceManagerImpl.java @@ -88,6 +88,7 @@ import static com.facebook.react.bridge.ReactMarkerConstants.PROCESS_PACKAGES_START; import static com.facebook.react.bridge.ReactMarkerConstants.SETUP_REACT_CONTEXT_END; import static com.facebook.react.bridge.ReactMarkerConstants.SETUP_REACT_CONTEXT_START; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE; /** @@ -797,7 +798,7 @@ private void attachMeasuredRootViewToInstance( // Reset view content as it's going to be populated by the application content from JS rootView.removeAllViews(); - rootView.setId(View.NO_ID); + rootView.setTag(View.NO_ID); UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class); int rootTag = uiManagerModule.addMeasuredRootView(rootView); @@ -818,7 +819,7 @@ private void detachViewFromInstance( CatalystInstance catalystInstance) { UiThreadUtil.assertOnUiThread(); catalystInstance.getJSModule(AppRegistry.class) - .unmountApplicationComponentAtRootTag(rootView.getId()); + .unmountApplicationComponentAtRootTag(reactTagFor(rootView)); } private void tearDownReactContext(ReactContext reactContext) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/TestIdUtil.java b/ReactAndroid/src/main/java/com/facebook/react/common/TestIdUtil.java new file mode 100644 index 00000000000000..38b8010b4fcfc9 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/common/TestIdUtil.java @@ -0,0 +1,55 @@ +package com.facebook.react.common; + +import android.view.View; + +import com.facebook.react.common.annotations.VisibleForTesting; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +public class TestIdUtil { + private static final ConcurrentHashMap mTestIds = new ConcurrentHashMap<>(); + // Integer values in R.class are typically large. To avoid colliding with R.class we + // use smaller values for ids when no resource id exists. + private static final int mStartingInternalId = 1; + private static final AtomicInteger mInternalId = new AtomicInteger(mStartingInternalId); + + /** + * Looks for defined resource IDs in R.class by the name of testId and if a matching resource ID is + * found it is passed to the view's setId method. If the given testId cannot be found in R.class, + * an increment value is assigned instead. + */ + public static void setTestId(T view, String testId) { + int mappedTestId; + if (!mTestIds.containsKey(testId)) { + mappedTestId = view.getResources().getIdentifier(testId, "id", view.getContext().getPackageName()); + final boolean idNotFoundInResources = mappedTestId <= 0; + if (idNotFoundInResources) { + mappedTestId = mInternalId.getAndIncrement(); + } + mTestIds.put(testId, mappedTestId); + } else { + mappedTestId = mTestIds.get(testId); + } + + if (mappedTestId != 0 && view.getId() != mappedTestId) { + view.setId(mappedTestId); + } + } + + /** + * Used for e2e tests that do not yet have testIDs stored in ids.xml. It is strongly + * advised that you reference ids that have been generated in R.class to avoid collisions and + * to properly support UIAutomatorViewer. + */ + @VisibleForTesting + public static int getTestId(String testId) { + return mTestIds.containsKey(testId) ? mTestIds.get(testId) : View.NO_ID; + } + + @VisibleForTesting + public static void resetStateInTest() { + mTestIds.clear(); + mInternalId.set(mStartingInternalId); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/ViewMethodsUtil.java b/ReactAndroid/src/main/java/com/facebook/react/common/ViewMethodsUtil.java new file mode 100644 index 00000000000000..24f4794bbc1392 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/common/ViewMethodsUtil.java @@ -0,0 +1,16 @@ +package com.facebook.react.common; + +import android.view.View; + +public class ViewMethodsUtil { + + /** + * Returns the react tag for the view. If no react tag has been set then {@link View#NO_ID} is + * returned. + */ + public static int reactTagFor(View view) { + return view == null || view.getTag() == null ? + View.NO_ID : + (int) view.getTag(); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/touch/BUCK b/ReactAndroid/src/main/java/com/facebook/react/touch/BUCK index 44cea8fdce54f4..fa495f2e5afa5b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/touch/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/touch/BUCK @@ -6,6 +6,7 @@ android_library( deps = [ react_native_dep('third-party/java/infer-annotations:infer-annotations'), react_native_dep('third-party/java/jsr-305:jsr-305'), + react_native_target('java/com/facebook/react/common:common'), ], visibility = [ 'PUBLIC' diff --git a/ReactAndroid/src/main/java/com/facebook/react/touch/JSResponderHandler.java b/ReactAndroid/src/main/java/com/facebook/react/touch/JSResponderHandler.java index 7140e2c303fea8..72f0f3cc0c35ce 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/touch/JSResponderHandler.java +++ b/ReactAndroid/src/main/java/com/facebook/react/touch/JSResponderHandler.java @@ -15,6 +15,8 @@ import android.view.ViewGroup; import android.view.ViewParent; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * This class coordinates JSResponder commands for {@link UIManagerModule}. It should be set as * OnInterceptTouchEventListener for all newly created native views that implements @@ -70,7 +72,7 @@ public boolean onInterceptTouchEvent(ViewGroup v, MotionEvent event) { // Therefore since "UP" event is the last event in a gesture, we should just let it reach the // original target that is a child view of {@param v}. // http://developer.android.com/reference/android/view/ViewGroup.html#onInterceptTouchEvent(android.view.MotionEvent) - return v.getId() == currentJSResponder; + return reactTagFor(v) == currentJSResponder; } return false; } 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 815bad0faddcc5..02accaf536ecfa 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -5,9 +5,9 @@ import android.graphics.Color; import android.os.Build; import android.view.View; -import android.view.ViewGroup; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.common.TestIdUtil; import com.facebook.react.uimanager.annotations.ReactProp; /** @@ -84,7 +84,7 @@ public void setRenderToHardwareTexture(T view, boolean useHWTexture) { @ReactProp(name = PROP_TEST_ID) public void setTestId(T view, String testId) { - view.setTag(testId); + TestIdUtil.setTestId(view, testId); } @ReactProp(name = PROP_ACCESSIBILITY_LABEL) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index a5b157d0f1029f..4b64dde817a2ca 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -31,6 +31,7 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.SoftAssertions; import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.common.ViewMethodsUtil; import com.facebook.react.touch.JSResponderHandler; import com.facebook.react.uimanager.layoutanimation.LayoutAnimationController; import com.facebook.react.uimanager.layoutanimation.LayoutAnimationListener; @@ -40,6 +41,8 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * Delegate of {@link UIManagerModule} that owns the native view hierarchy and mapping between * native view names used in JS and corresponding instances of {@link ViewManager}. The @@ -200,6 +203,14 @@ private void updateLayout(View viewToUpdate, int x, int y, int width, int height } } + /** + * Creates a view and adds it to a corresponding {@link ViewManager}. + * + * The tag (a.k.a. React Tag) is stored with {@link View#setTag(Object)}. It is necessary to use + * {@link ViewMethodsUtil#reactTagFor(View)} wherever the original + * tag is needed e.g. when communicating with the Shadow DOM or with JS about a particular react + * tag. + */ public void createView( ThemedReactContext themedContext, int tag, @@ -219,10 +230,10 @@ public void createView( mTagsToViews.put(tag, view); mTagsToViewManagers.put(tag, viewManager); - // Use android View id field to store React tag. This is possible since we don't inflate + // Use android View tag field to store React tag. This is possible since we don't inflate // React views from layout xmls. Thus it is easier to just reuse that field instead of // creating another (potentially much more expensive) mapping from view to React tag - view.setId(tag); + view.setTag(tag); if (initialProps != null) { viewManager.updateProperties(view, initialProps); } @@ -240,13 +251,13 @@ private static String constructManageChildrenErrorMessage( StringBuilder stringBuilder = new StringBuilder(); if (null != viewToManage) { - stringBuilder.append("View tag:" + viewToManage.getId() + "\n"); + stringBuilder.append("View tag:" + reactTagFor(viewToManage) + "\n"); stringBuilder.append(" children(" + viewManager.getChildCount(viewToManage) + "): [\n"); for (int index=0; index= 0; i--) { View child = viewGroupManager.getChildAt(viewGroup, i); - if (mTagsToViews.get(child.getId()) != null) { + if (mTagsToViews.get(reactTagFor(child)) != null) { dropView(child); } } viewGroupManager.removeAllViews(viewGroup); } - mTagsToViews.remove(view.getId()); - mTagsToViewManagers.remove(view.getId()); + mTagsToViews.remove(reactTag); + mTagsToViewManagers.remove(reactTag); } public void removeRootView(int rootViewTag) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java index 4ef74fafc941f5..9662ba295d3260 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java @@ -22,6 +22,8 @@ import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.touch.ReactHitSlopView; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * Class responsible for identifying which react view should handle a given {@link MotionEvent}. * It uses the event coordinates to traverse the view hierarchy and return a suitable view. @@ -87,7 +89,7 @@ public static int findTargetTagAndCoordinatesForTouch( float[] viewCoords, @Nullable int[] nativeViewTag) { UiThreadUtil.assertOnUiThread(); - int targetTag = viewGroup.getId(); + int targetTag = reactTagFor(viewGroup); // Store eventCoords in array so that they are modified to be relative to the targetView found. viewCoords[0] = eventX; viewCoords[1] = eventY; @@ -96,7 +98,7 @@ public static int findTargetTagAndCoordinatesForTouch( View reactTargetView = findClosestReactAncestor(nativeTargetView); if (reactTargetView != null) { if (nativeViewTag != null) { - nativeViewTag[0] = reactTargetView.getId(); + nativeViewTag[0] = reactTagFor(reactTargetView); } targetTag = getTouchTargetForView(reactTargetView, viewCoords[0], viewCoords[1]); } @@ -105,7 +107,7 @@ public static int findTargetTagAndCoordinatesForTouch( } private static View findClosestReactAncestor(View view) { - while (view != null && view.getId() <= 0) { + while (view != null && reactTagFor(view) <= 0) { view = (View) view.getParent(); } return view; @@ -224,7 +226,7 @@ private static boolean isTransformedTouchPointInView( // ViewGroup). if (view instanceof ReactCompoundView) { int reactTag = ((ReactCompoundView)view).reactTagForTouch(eventCoords[0], eventCoords[1]); - if (reactTag != view.getId()) { + if (reactTag != reactTagFor(view)) { // make sure we exclude the View itself because of the PointerEvents.BOX_NONE return view; } @@ -256,7 +258,7 @@ private static int getTouchTargetForView(View targetView, float eventX, float ev // {@link #findTouchTargetView()}. return ((ReactCompoundView) targetView).reactTagForTouch(eventX, eventY); } - return targetView.getId(); + return reactTagFor(targetView); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java index e61d1266e1f29e..05e88008ddf134 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java @@ -36,6 +36,8 @@ import com.facebook.react.views.drawer.events.DrawerSlideEvent; import com.facebook.react.views.drawer.events.DrawerStateChangedEvent; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * View Manager for {@link ReactDrawerLayout} components. */ @@ -187,25 +189,25 @@ public DrawerEventEmitter(DrawerLayout drawerLayout, EventDispatcher eventDispat @Override public void onDrawerSlide(View view, float v) { mEventDispatcher.dispatchEvent( - new DrawerSlideEvent(mDrawerLayout.getId(), v)); + new DrawerSlideEvent(reactTagFor(mDrawerLayout), v)); } @Override public void onDrawerOpened(View view) { mEventDispatcher.dispatchEvent( - new DrawerOpenedEvent(mDrawerLayout.getId())); + new DrawerOpenedEvent(reactTagFor(mDrawerLayout))); } @Override public void onDrawerClosed(View view) { mEventDispatcher.dispatchEvent( - new DrawerClosedEvent(mDrawerLayout.getId())); + new DrawerClosedEvent(reactTagFor(mDrawerLayout))); } @Override public void onDrawerStateChanged(int i) { mEventDispatcher.dispatchEvent( - new DrawerStateChangedEvent(mDrawerLayout.getId(), i)); + new DrawerStateChangedEvent(reactTagFor(mDrawerLayout), i)); } } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java index f519627b634f1c..62b045d5f72dfb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java @@ -59,6 +59,8 @@ import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper; import com.facebook.react.views.imagehelper.MultiSourceHelper.MultiSourceResult; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * Wrapper class around Fresco's GenericDraweeView, enabling persisting props across multiple view * update and consistent processing of both static and network images. @@ -192,7 +194,7 @@ public void setShouldNotifyLoadEvents(boolean shouldNotify) { @Override public void onSubmit(String id, Object callerContext) { mEventDispatcher.dispatchEvent( - new ImageLoadEvent(getId(), ImageLoadEvent.ON_LOAD_START)); + new ImageLoadEvent(getReactTag(), ImageLoadEvent.ON_LOAD_START)); } @Override @@ -202,19 +204,19 @@ public void onFinalImageSet( @Nullable Animatable animatable) { if (imageInfo != null) { mEventDispatcher.dispatchEvent( - new ImageLoadEvent(getId(), ImageLoadEvent.ON_LOAD, + new ImageLoadEvent(getReactTag(), ImageLoadEvent.ON_LOAD, mImageSource.getSource(), imageInfo.getWidth(), imageInfo.getHeight())); mEventDispatcher.dispatchEvent( - new ImageLoadEvent(getId(), ImageLoadEvent.ON_LOAD_END)); + new ImageLoadEvent(getReactTag(), ImageLoadEvent.ON_LOAD_END)); } } @Override public void onFailure(String id, Throwable throwable) { mEventDispatcher.dispatchEvent( - new ImageLoadEvent(getId(), ImageLoadEvent.ON_ERROR)); + new ImageLoadEvent(getReactTag(), ImageLoadEvent.ON_ERROR)); mEventDispatcher.dispatchEvent( - new ImageLoadEvent(getId(), ImageLoadEvent.ON_LOAD_END)); + new ImageLoadEvent(getReactTag(), ImageLoadEvent.ON_LOAD_END)); } }; } @@ -222,6 +224,10 @@ public void onFailure(String id, Throwable throwable) { mIsDirty = true; } + private int getReactTag() { + return reactTagFor(this); + } + public void setBorderColor(int borderColor) { mBorderColor = borderColor; mIsDirty = true; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.java index 220284ef3dd870..113701e3dfa5c0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.java @@ -22,6 +22,8 @@ import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.events.EventDispatcher; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * View manager for {@link ReactModalHostView} components. */ @@ -76,14 +78,14 @@ protected void addEventEmitters( new ReactModalHostView.OnRequestCloseListener() { @Override public void onRequestClose(DialogInterface dialog) { - dispatcher.dispatchEvent(new RequestCloseEvent(view.getId())); + dispatcher.dispatchEvent(new RequestCloseEvent(reactTagFor(view))); } }); view.setOnShowListener( new DialogInterface.OnShowListener() { @Override public void onShow(DialogInterface dialog) { - dispatcher.dispatchEvent(new ShowEvent(view.getId())); + dispatcher.dispatchEvent(new ShowEvent(reactTagFor(view))); } }); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java index c2a5c933dbec4c..54b2a3978ab943 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java @@ -36,6 +36,8 @@ import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.views.view.ReactViewGroup; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * ReactModalHostView is a view that sits in the view hierarchy representing a Modal view. * @@ -299,7 +301,7 @@ protected void onSizeChanged(final int w, final int h, int oldw, int oldh) { @Override public void run() { ((ReactContext) getContext()).getNativeModule(UIManagerModule.class) - .updateNodeSize(getChildAt(0).getId(), w, h); + .updateNodeSize(reactTagFor(getChildAt(0)), w, h); } }); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java index 5e1e9ccf33a236..a6d2af492c3dce 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java @@ -30,6 +30,8 @@ import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.views.picker.events.PickerItemSelectEvent; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * {@link ViewManager} for the {@link ReactPicker} view. This is abstract because the * {@link Spinner} doesn't support setting the mode (dropdown/dialog) outside the constructor, so @@ -156,7 +158,7 @@ public PickerEventEmitter(ReactPicker reactPicker, EventDispatcher eventDispatch @Override public void onItemSelected(int position) { mEventDispatcher.dispatchEvent( new PickerItemSelectEvent( - mReactPicker.getId(), position)); + reactTagFor(mReactPicker), position)); } } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java index d823ac88bf3938..d96aaf72ef0c62 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java @@ -15,12 +15,15 @@ import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.ReactContext; import com.facebook.react.common.annotations.VisibleForTesting; +import com.facebook.react.uimanager.NativeViewHierarchyManager; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.events.ContentSizeChangeEvent; import com.facebook.react.uimanager.events.NativeGestureUtil; import com.facebook.react.views.scroll.ScrollEvent; import com.facebook.react.views.scroll.ScrollEventType; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * Wraps {@link RecyclerView} providing interface similar to `ScrollView.js` where each children * will be rendered as a separate {@link RecyclerView} row. @@ -294,7 +297,7 @@ public int getItemCount() { @Override public long getItemId(int position) { - return mViews.get(position).getId(); + return reactTagFor(mViews.get(position)); } public View getView(int index) { @@ -342,7 +345,7 @@ protected void onScrollChanged(int l, int t, int oldl, int oldt) { ((ReactContext) getContext()).getNativeModule(UIManagerModule.class).getEventDispatcher() .dispatchEvent(ScrollEvent.obtain( - getId(), + reactTagFor(this), ScrollEventType.SCROLL, 0, /* offsetX = 0, horizontal scrolling only */ calculateAbsoluteOffset(), @@ -356,7 +359,7 @@ private void onTotalChildrenHeightChange(int newTotalChildrenHeight) { if (mSendContentSizeChangeEvents) { ((ReactContext) getContext()).getNativeModule(UIManagerModule.class).getEventDispatcher() .dispatchEvent(new ContentSizeChangeEvent( - getId(), + reactTagFor(this), getWidth(), newTotalChildrenHeight)); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java index 966c50bb30a797..5f955a0d9e2de2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java @@ -15,6 +15,8 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.uimanager.UIManagerModule; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * Helper class that deals with emitting Scroll Events. */ @@ -55,7 +57,7 @@ private static void emitScrollEvent(ViewGroup scrollView, ScrollEventType scroll ReactContext reactContext = (ReactContext) scrollView.getContext(); reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( ScrollEvent.obtain( - scrollView.getId(), + reactTagFor(scrollView), scrollEventType, scrollView.getScrollX(), scrollView.getScrollY(), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java index 87f930ab46db97..61df272940ee83 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java @@ -27,6 +27,8 @@ import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.annotations.ReactProp; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * Manages instances of {@code ReactSlider}. * @@ -79,7 +81,7 @@ public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { ReactContext reactContext = (ReactContext) seekbar.getContext(); reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( new ReactSliderEvent( - seekbar.getId(), + reactTagFor(seekbar), ((ReactSlider)seekbar).toRealProgress(progress), fromUser)); } @@ -93,7 +95,7 @@ public void onStopTrackingTouch(SeekBar seekbar) { ReactContext reactContext = (ReactContext) seekbar.getContext(); reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( new ReactSlidingCompleteEvent( - seekbar.getId(), + reactTagFor(seekbar), ((ReactSlider)seekbar).toRealProgress(seekbar.getProgress()))); } }; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java index 99b62b4a96b67e..ae815bb08a5680 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java @@ -27,6 +27,7 @@ import com.facebook.react.uimanager.annotations.ReactProp; import static com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager.REACT_CLASS; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; /** * ViewManager for {@link ReactSwipeRefreshLayout} which allows the user to "pull to refresh" a @@ -94,7 +95,7 @@ protected void addEventEmitters( @Override public void onRefresh() { reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher() - .dispatchEvent(new RefreshEvent(view.getId())); + .dispatchEvent(new RefreshEvent(reactTagFor(view))); } }); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java index d05f8bae33fd3f..8f52ca107de078 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java @@ -25,6 +25,8 @@ import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.annotations.ReactProp; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * View manager for {@link ReactSwitch} components. */ @@ -76,7 +78,7 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { ReactContext reactContext = (ReactContext) buttonView.getContext(); reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( new ReactSwitchEvent( - buttonView.getId(), + reactTagFor(buttonView), isChecked)); } }; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index 50828a80ba6818..29d63f54d50379 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -26,6 +26,8 @@ import com.facebook.react.uimanager.ViewDefaults; import com.facebook.react.views.view.ReactViewBackgroundDrawable; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + public class ReactTextView extends TextView implements ReactCompoundView { private static final ViewGroup.LayoutParams EMPTY_LAYOUT_PARAMS = @@ -74,7 +76,7 @@ public void setText(ReactTextUpdate update) { @Override public int reactTagForTouch(float touchX, float touchY) { Spanned text = (Spanned) getText(); - int target = getId(); + int target = reactTagFor(this); int x = (int) touchX; int y = (int) touchY; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java index 2a2cf446c70de9..dd510419b8ac44 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java @@ -50,9 +50,10 @@ import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper; import com.facebook.react.views.text.DefaultStyleValuesUtil; import com.facebook.react.views.text.ReactTextUpdate; -import com.facebook.react.views.text.ReactTextView; import com.facebook.react.views.text.TextInlineImageSpan; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * Manages instances of TextInput. */ @@ -645,7 +646,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { // TODO: t7936714 merge these events mEventDispatcher.dispatchEvent( new ReactTextChangedEvent( - mEditText.getId(), + reactTagFor(mEditText), s.toString(), PixelUtil.toDIPFromPixel(contentWidth), PixelUtil.toDIPFromPixel(contentHeight), @@ -653,7 +654,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { mEventDispatcher.dispatchEvent( new ReactTextInputEvent( - mEditText.getId(), + reactTagFor(mEditText), newText, oldText, start, @@ -678,15 +679,15 @@ public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { eventDispatcher.dispatchEvent( new ReactTextInputFocusEvent( - editText.getId())); + reactTagFor(editText))); } else { eventDispatcher.dispatchEvent( new ReactTextInputBlurEvent( - editText.getId())); + reactTagFor(editText))); eventDispatcher.dispatchEvent( new ReactTextInputEndEditingEvent( - editText.getId(), + reactTagFor(editText), editText.getText().toString())); } } @@ -703,7 +704,7 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent keyEvent) { reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); eventDispatcher.dispatchEvent( new ReactTextInputSubmitEditingEvent( - editText.getId(), + reactTagFor(editText), editText.getText().toString())); } if (actionId == EditorInfo.IME_ACTION_NEXT || @@ -749,7 +750,7 @@ public void onLayout() { mEventDispatcher.dispatchEvent( new ReactContentSizeChangedEvent( - mEditText.getId(), + reactTagFor(mEditText), PixelUtil.toDIPFromPixel(contentWidth), PixelUtil.toDIPFromPixel(contentHeight))); } @@ -777,7 +778,7 @@ public void onSelectionChanged(int start, int end) { if (mPreviousSelectionStart != start || mPreviousSelectionEnd != end) { mEventDispatcher.dispatchEvent( new ReactTextInputSelectionEvent( - mReactEditText.getId(), + reactTagFor(mReactEditText), start, end )); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java index b37040a22f3ed8..264bfb0bb6ea8f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java @@ -33,6 +33,8 @@ import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.views.toolbar.events.ToolbarClickEvent; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * Manages instances of ReactToolbar. */ @@ -130,7 +132,7 @@ protected void addEventEmitters(final ThemedReactContext reactContext, final Rea @Override public void onClick(View v) { mEventDispatcher.dispatchEvent( - new ToolbarClickEvent(view.getId(), -1)); + new ToolbarClickEvent(reactTagFor(view), -1)); } }); @@ -140,7 +142,7 @@ public void onClick(View v) { public boolean onMenuItemClick(MenuItem menuItem) { mEventDispatcher.dispatchEvent( new ToolbarClickEvent( - view.getId(), + reactTagFor(view), menuItem.getOrder())); return true; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java index c5a2ea12f71557..e542cdc6aea543 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java @@ -23,6 +23,8 @@ import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.events.NativeGestureUtil; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * Wrapper view for {@link ViewPager}. It's forwarding calls to {@link ViewGroup#addView} to add * views to custom {@link PagerAdapter} instance which is used by {@link NativeViewHierarchyManager} @@ -123,14 +125,14 @@ private class PageChangeListener implements OnPageChangeListener { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { mEventDispatcher.dispatchEvent( - new PageScrollEvent(getId(), position, positionOffset)); + new PageScrollEvent(getReactTag(), position, positionOffset)); } @Override public void onPageSelected(int position) { if (!mIsCurrentItemFromJs) { mEventDispatcher.dispatchEvent( - new PageSelectedEvent(getId(), position)); + new PageSelectedEvent(getReactTag(), position)); } } @@ -151,10 +153,14 @@ public void onPageScrollStateChanged(int state) { throw new IllegalStateException("Unsupported pageScrollState"); } mEventDispatcher.dispatchEvent( - new PageScrollStateChangedEvent(getId(), pageScrollState)); + new PageScrollStateChangedEvent(getReactTag(), pageScrollState)); } } + private int getReactTag() { + return reactTagFor(this); + } + private final EventDispatcher mEventDispatcher; private boolean mIsCurrentItemFromJs; private boolean mScrollEnabled = true; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java index 20951b621291fe..bd8475c7e3157d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java @@ -57,6 +57,8 @@ import org.json.JSONObject; import org.json.JSONException; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; + /** * Manages instances of {@link WebView} * @@ -126,7 +128,7 @@ public void onPageStarted(WebView webView, String url, Bitmap favicon) { dispatchEvent( webView, new TopLoadingStartEvent( - webView.getId(), + reactTagFor(webView), createWebViewEvent(webView, url))); } @@ -162,7 +164,7 @@ public void onReceivedError( dispatchEvent( webView, - new TopLoadingErrorEvent(webView.getId(), eventData)); + new TopLoadingErrorEvent(reactTagFor(webView), eventData)); } @Override @@ -172,7 +174,7 @@ public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload dispatchEvent( webView, new TopLoadingStartEvent( - webView.getId(), + reactTagFor(webView), createWebViewEvent(webView, url))); } @@ -180,13 +182,13 @@ private void emitFinishEvent(WebView webView, String url) { dispatchEvent( webView, new TopLoadingFinishEvent( - webView.getId(), + reactTagFor(webView), createWebViewEvent(webView, url))); } private WritableMap createWebViewEvent(WebView webView, String url) { WritableMap event = Arguments.createMap(); - event.putDouble("target", webView.getId()); + event.putDouble("target", reactTagFor(webView)); // Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks // like onPageFinished event.putString("url", url); @@ -512,7 +514,7 @@ public void onNewPicture(WebView webView, Picture picture) { dispatchEvent( webView, new ContentSizeChangeEvent( - webView.getId(), + reactTagFor(webView), webView.getWidth(), webView.getContentHeight())); } diff --git a/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java b/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java index 3bcdab6acdd5cb..a66c858dbbf30d 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java @@ -112,7 +112,7 @@ public void testTouchEmitter() { int rootViewId = 7; ReactRootView rootView = new ReactRootView(mReactContext); - rootView.setId(rootViewId); + rootView.setTag(rootViewId); rootView.startReactApplication(instanceManager, ""); rootView.simulateAttachForTesting(); diff --git a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java index 9f4db9d6fd1945..95c3726e3ea6a6 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java @@ -9,6 +9,8 @@ package com.facebook.react.animated; +import android.view.View; + import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.JavaOnlyArray; @@ -27,6 +29,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.powermock.api.mockito.PowerMockito; @@ -35,6 +39,7 @@ import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.RobolectricTestRunner; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; import static org.fest.assertions.api.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; @@ -45,6 +50,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; /** * Tests the animated nodes graph traversal algorithm from {@link NativeAnimatedNodesManager}. @@ -54,6 +60,9 @@ @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) public class NativeAnimatedNodeTraversalTest { + @Mock + private View view; + private final int viewTag = 1000; private static long FRAME_LEN_NANOS = 1000000000L / 60L; private static long INITIAL_FRAME_TIME_NANOS = 14599233201256L; /* random */ @@ -72,6 +81,8 @@ private long nextFrameTime() { @Before public void setUp() { + MockitoAnnotations.initMocks(this); + when(reactTagFor(view)).thenReturn(viewTag); PowerMockito.mockStatic(Arguments.class); PowerMockito.when(Arguments.createArray()).thenAnswer(new Answer() { @Override @@ -726,8 +737,8 @@ public void testInterpolationNode() { verifyNoMoreInteractions(mUIImplementationMock); } - private Event createScrollEvent(final int tag, final double value) { - return new Event(tag) { + private Event createScrollEvent(final View view, final double value) { + return new Event(reactTagFor(view)) { @Override public String getEventName() { return "topScroll"; @@ -735,7 +746,7 @@ public String getEventName() { @Override public void dispatch(RCTEventEmitter rctEventEmitter) { - rctEventEmitter.receiveEvent(tag, "topScroll", JavaOnlyMap.of( + rctEventEmitter.receiveEvent(getViewTag(), "topScroll", JavaOnlyMap.of( "contentOffset", JavaOnlyMap.of("y", value))); } }; @@ -743,15 +754,13 @@ public void dispatch(RCTEventEmitter rctEventEmitter) { @Test public void testNativeAnimatedEventDoUpdate() { - int viewTag = 1000; - createSimpleAnimatedViewWithOpacity(viewTag, 0d); mNativeAnimatedNodesManager.addAnimatedEventToView(viewTag, "topScroll", JavaOnlyMap.of( "animatedValueTag", 1, "nativeEventPath", JavaOnlyArray.of("contentOffset", "y"))); - mNativeAnimatedNodesManager.onEventDispatch(createScrollEvent(viewTag, 10)); + mNativeAnimatedNodesManager.onEventDispatch(createScrollEvent(view, 10)); ArgumentCaptor stylesCaptor = ArgumentCaptor.forClass(ReactStylesDiffMap.class); @@ -764,8 +773,6 @@ public void testNativeAnimatedEventDoUpdate() { @Test public void testNativeAnimatedEventDoNotUpdate() { - int viewTag = 1000; - createSimpleAnimatedViewWithOpacity(viewTag, 0d); mNativeAnimatedNodesManager.addAnimatedEventToView(viewTag, "otherEvent", JavaOnlyMap.of( @@ -776,7 +783,7 @@ public void testNativeAnimatedEventDoNotUpdate() { "animatedValueTag", 1, "nativeEventPath", JavaOnlyArray.of("contentOffset", "y"))); - mNativeAnimatedNodesManager.onEventDispatch(createScrollEvent(viewTag, 10)); + mNativeAnimatedNodesManager.onEventDispatch(createScrollEvent(view, 10)); ArgumentCaptor stylesCaptor = ArgumentCaptor.forClass(ReactStylesDiffMap.class); @@ -789,8 +796,6 @@ public void testNativeAnimatedEventDoNotUpdate() { @Test public void testNativeAnimatedEventCustomMapping() { - int viewTag = 1000; - PowerMockito.when(mUIManagerMock.getConstants()).thenAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { @@ -807,7 +812,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { "animatedValueTag", 1, "nativeEventPath", JavaOnlyArray.of("contentOffset", "y"))); - mNativeAnimatedNodesManager.onEventDispatch(createScrollEvent(viewTag, 10)); + mNativeAnimatedNodesManager.onEventDispatch(createScrollEvent(view, 10)); ArgumentCaptor stylesCaptor = ArgumentCaptor.forClass(ReactStylesDiffMap.class); diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.java new file mode 100644 index 00000000000000..dae27a5026c8b2 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.java @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2015-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.uimanager; + +import android.content.Context; +import android.content.res.Resources; +import android.view.View; + +import com.facebook.csslayout.Spacing; +import com.facebook.react.common.TestIdUtil; + +import org.junit.After; +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.verification.VerificationMode; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.junit.Assert.assertEquals; +import static org.mockito.AdditionalMatchers.not; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyFloat; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import static org.mockito.internal.verification.VerificationModeFactory.calls; + +@Config(manifest= Config.NONE) +@RunWith(RobolectricTestRunner.class) +@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) +public class BaseViewManagerTest { + + @Mock + Resources resources; + @Mock + Context context; + @Mock + private View view; + private BaseViewManager sut; + + private final String testID = "some-test-id"; + private final int mappedTestID = 23457897; + private final String myPackage = "com.myApp"; + + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(view.getContext()).thenReturn(context); + when(view.getResources()).thenReturn(resources); + when(resources.getIdentifier(eq(testID), eq("id"), eq(myPackage))).thenReturn(mappedTestID); + when(resources.getIdentifier(eq(testID), eq("id"), not(eq(myPackage)))).thenReturn(0); + sut = new ViewManagerStub(); + } + + @After + public void teardown() { + TestIdUtil.resetStateInTest(); + } + + @Test + public void testUsingATestIdNotFoundInResources() { + when(context.getPackageName()).thenReturn("com.foo"); + sut.setTestId(view, testID); + verify(view).setId(eq(1)); + } + + @Test + public void testUsingATestIdFoundInResources() { + InOrder inOrder = inOrder(view); + when(context.getPackageName()).thenReturn(myPackage); + sut.setTestId(view, testID); + inOrder.verify(view, calls(1)).setId(mappedTestID); + when(view.getId()).thenReturn((mappedTestID)); + sut.setTestId(view, testID); + inOrder.verify(view, never()).setId(anyInt()); + } + + private static class ViewManagerStub extends BaseViewManager { + @Override + public String getName() { + return null; + } + + @Override + public LayoutShadowNode createShadowNodeInstance() { + return null; + } + + @Override + public Class getShadowNodeClass() { + return null; + } + + @Override + protected View createViewInstance(ThemedReactContext reactContext) { + return null; + } + + @Override + public void updateExtraData(View root, Object extraData) { + + } + } +} diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java index 7cdcabf53932e8..ec644032489556 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java @@ -48,6 +48,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import static com.facebook.react.common.ViewMethodsUtil.reactTagFor; import static org.fest.assertions.api.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doAnswer; @@ -483,22 +484,22 @@ public void testTagsAssignment() { TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManager); View view0 = hierarchy.nativeRootView.getChildAt(0); - assertThat(view0.getId()).isEqualTo(hierarchy.view0); + assertThat(reactTagFor(view0)).isEqualTo(hierarchy.view0); View viewWithChildren1 = hierarchy.nativeRootView.getChildAt(1); - assertThat(viewWithChildren1.getId()).isEqualTo(hierarchy.viewWithChildren1); + assertThat(reactTagFor(viewWithChildren1)).isEqualTo(hierarchy.viewWithChildren1); View childView0 = ((ViewGroup) viewWithChildren1).getChildAt(0); - assertThat(childView0.getId()).isEqualTo(hierarchy.childView0); + assertThat(reactTagFor(childView0)).isEqualTo(hierarchy.childView0); View childView1 = ((ViewGroup) viewWithChildren1).getChildAt(1); - assertThat(childView1.getId()).isEqualTo(hierarchy.childView1); + assertThat(reactTagFor(childView1)).isEqualTo(hierarchy.childView1); View view2 = hierarchy.nativeRootView.getChildAt(2); - assertThat(view2.getId()).isEqualTo(hierarchy.view2); + assertThat(reactTagFor(view2)).isEqualTo(hierarchy.view2); View view3 = hierarchy.nativeRootView.getChildAt(3); - assertThat(view3.getId()).isEqualTo(hierarchy.view3); + assertThat(reactTagFor(view3)).isEqualTo(hierarchy.view3); } @Test