/** * 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. */ package com.facebook.react.tests; import android.view.View; import com.facebook.react.testing.ReactInstanceSpecForTest; import com.facebook.react.testing.ReactAppInstrumentationTestCase; import com.facebook.react.testing.SingleTouchGestureGenerator; import com.facebook.react.testing.StringRecordingModule; /** * This test is to verify that touch events bubbles up to the right handler. We emulate couple * of different gestures on top of the application reflecting following layout: * * +---------------------------------------------------------------------------------------+ * | | * | +----------------------------------------------------------------------------------+ | * | | +-------------+ +----------------+ | | * | | | +---+ | | | | | * | | | | A | | | | | | * | | | +---+ | | C | | | * | | | {B} | | | | | * | | | | {D} | | | | * | | +-------------+ +----------------+ | | * | | | | * | | | | * | +----------------------------------------------------------------------------------+ | * | * | +----------------------------------------------------------------------------------+ | * | | | | * | | | | * | | | | * | | {E} | | * | | | | * | | | | * | +----------------------------------------------------------------------------------+ | * +---------------------------------------------------------------------------------------+ * * Then in each test case we eiter tap the center of a particular view (from A to E) or we start * a gesture in one view and end it with another. * View with names in brackets (e.g. {D}) have touch handlers set whereas all other views are not * declared to handler touch events. */ public class CatalystTouchBubblingTestCase extends ReactAppInstrumentationTestCase { private final StringRecordingModule mRecordingModule = new StringRecordingModule(); @Override protected String getReactApplicationKeyUnderTest() { return "TouchBubblingTestAppModule"; } /** * 1) Simulate touch event at view A, expect {B} touch handler to fire * 2) Simulate touch event at view C, expect {D} touch handler to fire */ public void testSimpleClickAtInnerElements() { mRecordingModule.reset(); View innerButton = getViewByTestId("A"); assertNotNull(innerButton); createGestureGenerator().startGesture(innerButton).endGesture(); waitForBridgeAndUIIdle(); assertEquals(1, mRecordingModule.getCalls().size()); assertEquals("inner", mRecordingModule.getCalls().get(0)); mRecordingModule.reset(); innerButton = getViewByTestId("C"); assertNotNull(innerButton); createGestureGenerator().startGesture(innerButton).endGesture(); waitForBridgeAndUIIdle(); assertEquals(1, mRecordingModule.getCalls().size()); assertEquals("outer", mRecordingModule.getCalls().get(0)); } /** * 1) Start touch at view A, then drag and release on view {B} (but outside of A), expect {B}'s * touch handler to fire * 2) Do the same with view C and {D} */ public void testDownOnInnerUpOnTouchableParent() { View innerButton = getViewByTestId("A"); View touchableParent = getViewByTestId("B"); SingleTouchGestureGenerator gestureGenerator = createGestureGenerator(); gestureGenerator.startGesture(innerButton); // wait for tapped view measurements waitForBridgeAndUIIdle(); gestureGenerator.dragTo(touchableParent, 15).endGesture(); waitForBridgeAndUIIdle(); assertEquals(1, mRecordingModule.getCalls().size()); assertEquals("inner", mRecordingModule.getCalls().get(0)); // Do same with second inner view mRecordingModule.reset(); touchableParent = getViewByTestId("D"); innerButton = getViewByTestId("C"); gestureGenerator = createGestureGenerator(); gestureGenerator.startGesture(innerButton); // wait for tapped view measurements waitForBridgeAndUIIdle(); gestureGenerator.dragTo(touchableParent, 15).endGesture(); waitForBridgeAndUIIdle(); assertEquals(1, mRecordingModule.getCalls().size()); assertEquals("outer", mRecordingModule.getCalls().get(0)); } /** * Start gesture at view A, then drag and release on view {E}. Expect no touch handlers to fire */ public void testDragOutOfTouchable() { View outsideView = getViewByTestId("E"); View innerButton = getViewByTestId("A"); SingleTouchGestureGenerator gestureGenerator = createGestureGenerator(); gestureGenerator.startGesture(innerButton); // wait for tapped view measurements waitForBridgeAndUIIdle(); gestureGenerator.dragTo(outsideView, 15).endGesture(); waitForBridgeAndUIIdle(); assertTrue(mRecordingModule.getCalls().isEmpty()); } /** * In this scenario we start gesture at view A (has two touchable parents {B} and {D}) then we * drag and release gesture on view {D}, but outside of {B}. We expect no touch handler to fire */ public void testNoEventWhenDragOutOfFirstTouchableParentToItsTouchableParent() { View topLevelTouchable = getViewByTestId("C"); View innerButton = getViewByTestId("A"); SingleTouchGestureGenerator gestureGenerator = createGestureGenerator(); gestureGenerator.startGesture(innerButton); // wait for tapped view measurements waitForBridgeAndUIIdle(); gestureGenerator.dragTo(topLevelTouchable, 15).endGesture(); waitForBridgeAndUIIdle(); assertTrue(mRecordingModule.getCalls().isEmpty()); } @Override protected ReactInstanceSpecForTest createReactInstanceSpecForTest() { return new ReactInstanceSpecForTest() .addNativeModule(mRecordingModule); } }