From 29ee917a0ee930815c86e1f89db435413f157af7 Mon Sep 17 00:00:00 2001 From: M0Coding Date: Thu, 8 Jun 2023 01:10:44 -0700 Subject: [PATCH] Convert TouchEventDispatchTest to Kotlin (#37725) Summary: Converts TouchEventDispatchTest from Java to Kotlin, as per issue https://github.com/facebook/react-native/issues/37708 ## Changelog: [Internal] [Changed] - Convert TouchEventDispatchTest to Kotlin Pull Request resolved: https://github.com/facebook/react-native/pull/37725 Test Plan: Tests pass: `./gradlew :packages:react-native:ReactAndroid:test` Formatted with [KtFmt](https://facebook.github.io/ktfmt/) Reviewed By: cortinico Differential Revision: D46486161 Pulled By: mdvacca fbshipit-source-id: ab7872347b42871416a3d8f23b001c564ff8c18a --- .../fabric/events/TouchEventDispatchTest.java | 612 ------------------ .../fabric/events/TouchEventDispatchTest.kt | 600 +++++++++++++++++ 2 files changed, 600 insertions(+), 612 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.java create mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.java deleted file mode 100644 index 03ab6bc7810..00000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.java +++ /dev/null @@ -1,612 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.fabric.events; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; -import static org.mockito.Mockito.times; - -import android.util.DisplayMetrics; -import android.view.MotionEvent; -import android.view.MotionEvent.PointerCoords; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.JavaOnlyArray; -import com.facebook.react.bridge.JavaOnlyMap; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.fabric.FabricUIManager; -import com.facebook.react.uimanager.DisplayMetricsHolder; -import com.facebook.react.uimanager.events.TouchEvent; -import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper; -import com.facebook.react.uimanager.events.TouchEventType; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; -import org.powermock.modules.junit4.rule.PowerMockRule; -import org.robolectric.RobolectricTestRunner; - -@PrepareForTest({Arguments.class, FabricUIManager.class}) -@SuppressStaticInitializationFor("com.facebook.react.fabric.FabricUIManager") -@RunWith(RobolectricTestRunner.class) -@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "androidx.*", "android.*"}) -public class TouchEventDispatchTest { - - private static final int SURFACE_ID = 121; - private static final int TARGET_VIEW_ID = 42; - private static final int GESTURE_START_TIME = 1; - - @Rule public PowerMockRule rule = new PowerMockRule(); - - private final TouchEventCoalescingKeyHelper mTouchEventCoalescingKeyHelper = - new TouchEventCoalescingKeyHelper(); - - /** Events (1 pointer): START -> MOVE -> MOVE -> UP */ - private final TouchEvent[] mStartMoveEndSequence = - new TouchEvent[] { - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_DOWN, - 0, - new int[] {0}, - new PointerCoords[] {pointerCoords(1f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_MOVE, - 0, - new int[] {0}, - new PointerCoords[] {pointerCoords(1f, 2f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_MOVE, - 0, - new int[] {0}, - new PointerCoords[] {pointerCoords(1f, 3f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_UP, - 0, - new int[] {0}, - new PointerCoords[] {pointerCoords(1f, 3f)}) - }; - - /** Expected values for {@link #mStartMoveEndSequence} */ - private final List mStartMoveEndExpectedSequence = - listOf( - /* - * START event for touch 1: - * { - * touches: [touch1], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 1f, - GESTURE_START_TIME, - 0, - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), - /* - * MOVE event for touch 1: - * { - * touches: [touch1], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 2f, - GESTURE_START_TIME, - 0, - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0))), - /* - * MOVE event for touch 1: - * { - * touches: [touch1], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 3f, - GESTURE_START_TIME, - 0, - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0))), - /* - * END event for touch 1: - * { - * touches: [], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 3f, - GESTURE_START_TIME, - 0, - Collections.emptyList(), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0)))); - - /** Events (2 pointer): START 1st -> START 2nd -> MOVE 1st -> UP 2st -> UP 1st */ - private final TouchEvent[] mStartPointerMoveUpSequence = - new TouchEvent[] { - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_DOWN, - 0, - new int[] {0}, - new PointerCoords[] {pointerCoords(1f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_POINTER_DOWN, - 1, - new int[] {0, 1}, - new PointerCoords[] {pointerCoords(1f, 1f), pointerCoords(2f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_MOVE, - 0, - new int[] {0, 1}, - new PointerCoords[] {pointerCoords(1f, 2f), pointerCoords(2f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_POINTER_UP, - 1, - new int[] {0, 1}, - new PointerCoords[] {pointerCoords(1f, 2f), pointerCoords(2f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_POINTER_UP, - 0, - new int[] {0}, - new PointerCoords[] {pointerCoords(1f, 2f)}) - }; - - /** Expected values for {@link #mStartPointerMoveUpSequence} */ - private final List mStartPointerMoveUpExpectedSequence = - listOf( - /* - * START event for touch 1: - * { - * touch: 0, - * touches: [touch1], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 1f, - GESTURE_START_TIME, - 0, - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), - /* - * START event for touch 2: - * { - * touch: 1, - * touches: [touch0, touch1], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 2f, - 1f, - GESTURE_START_TIME, - 1, - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - /* - * MOVE event for touch 1: - * { - * touch: 0, - * touches: [touch0, touch1], - * changed: [touch0, touch1] - * } - * { - * touch: 1, - * touches: [touch0, touch1], - * changed: [touch0, touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 2f, - GESTURE_START_TIME, - 0, - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 2f, - 1f, - GESTURE_START_TIME, - 1, - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - /* - * UP event pointer 1: - * { - * touch: 1, - * touches: [touch0], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 2f, - 1f, - GESTURE_START_TIME, - 1, - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - /* - * UP event pointer 0: - * { - * touch: 0, - * touches: [], - * changed: [touch0] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 2f, - GESTURE_START_TIME, - 0, - Collections.emptyList(), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)))); - - /** Events (2 pointer): START 1st -> START 2nd -> MOVE 1st -> CANCEL */ - private final TouchEvent[] mStartMoveCancelSequence = - new TouchEvent[] { - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_DOWN, - 0, - new int[] {0}, - new PointerCoords[] {pointerCoords(1f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_POINTER_DOWN, - 1, - new int[] {0, 1}, - new PointerCoords[] {pointerCoords(1f, 1f), pointerCoords(2f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_MOVE, - 0, - new int[] {0, 1}, - new PointerCoords[] {pointerCoords(1f, 2f), pointerCoords(2f, 1f)}), - createTouchEvent( - GESTURE_START_TIME, - MotionEvent.ACTION_CANCEL, - 0, - new int[] {0, 1}, - new PointerCoords[] {pointerCoords(1f, 3f), pointerCoords(2f, 1f)}) - }; - - /** Expected values for {@link #mStartMoveCancelSequence} */ - private final List mStartMoveCancelExpectedSequence = - listOf( - /* - * START event for touch 1: - * { - * touch: 0, - * touches: [touch1], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 1f, - GESTURE_START_TIME, - 0, - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), - /* - * START event for touch 2: - * { - * touch: 1, - * touches: [touch0, touch1], - * changed: [touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 2f, - 1f, - GESTURE_START_TIME, - 1, - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), - listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - /* - * MOVE event for touch 1: - * { - * touch: 0, - * touches: [touch0, touch1], - * changed: [touch0, touch1] - * } - * { - * touch: 1, - * touches: [touch0, touch1], - * changed: [touch0, touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 2f, - GESTURE_START_TIME, - 0, - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 2f, - 1f, - GESTURE_START_TIME, - 1, - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - /* - * CANCEL event: - * { - * touch: 0, - * touches: [], - * changed: [touch0, touch1] - * } - * { - * touch: 1, - * touches: [], - * changed: [touch0, touch1] - * } - */ - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 1f, - 3f, - GESTURE_START_TIME, - 0, - Collections.emptyList(), - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), - buildGestureEvent( - SURFACE_ID, - TARGET_VIEW_ID, - 2f, - 1f, - GESTURE_START_TIME, - 1, - Collections.emptyList(), - listOf( - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0), - buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)))); - - List mDispatchedEvents; - FabricEventEmitter mEventEmitter; - - FabricUIManager mUIManager; - - @Before - public void setUp() { - PowerMockito.mockStatic(Arguments.class); - PowerMockito.mockStatic(FabricUIManager.class); - PowerMockito.when(Arguments.createArray()) - .thenAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) { - return new JavaOnlyArray(); - } - }); - PowerMockito.when(Arguments.createMap()) - .thenAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) { - return new JavaOnlyMap(); - } - }); - - DisplayMetrics metrics = new DisplayMetrics(); - metrics.xdpi = 1f; - metrics.ydpi = 1f; - metrics.density = 1f; - DisplayMetricsHolder.setWindowDisplayMetrics(metrics); - - mUIManager = Mockito.mock(FabricUIManager.class); - mEventEmitter = new FabricEventEmitter(mUIManager); - } - - @Test - public void testFabric_startMoveEnd() { - for (TouchEvent event : mStartMoveEndSequence) { - event.dispatchModern(mEventEmitter); - } - ArgumentCaptor argument = ArgumentCaptor.forClass(WritableMap.class); - verify(mUIManager, times(4)) - .receiveEvent( - anyInt(), anyInt(), anyString(), anyBoolean(), anyInt(), argument.capture(), anyInt()); - - assertEquals(mStartMoveEndExpectedSequence, argument.getAllValues()); - } - - @Test - public void testFabric_startMoveCancel() { - for (TouchEvent event : mStartMoveCancelSequence) { - event.dispatchModern(mEventEmitter); - } - ArgumentCaptor argument = ArgumentCaptor.forClass(WritableMap.class); - verify(mUIManager, times(6)) - .receiveEvent( - anyInt(), anyInt(), anyString(), anyBoolean(), anyInt(), argument.capture(), anyInt()); - - assertEquals(mStartMoveCancelExpectedSequence, argument.getAllValues()); - } - - @Test - public void testFabric_startPointerUpCancel() { - for (TouchEvent event : mStartPointerMoveUpSequence) { - event.dispatchModern(mEventEmitter); - } - ArgumentCaptor argument = ArgumentCaptor.forClass(WritableMap.class); - verify(mUIManager, times(6)) - .receiveEvent( - anyInt(), anyInt(), anyString(), anyBoolean(), anyInt(), argument.capture(), anyInt()); - - assertEquals(mStartPointerMoveUpExpectedSequence, argument.getAllValues()); - } - - private TouchEvent createTouchEvent( - int gestureTime, int action, int pointerId, int[] pointerIds, PointerCoords[] pointerCoords) { - mTouchEventCoalescingKeyHelper.addCoalescingKey(gestureTime); - action |= pointerId << MotionEvent.ACTION_POINTER_INDEX_SHIFT; - return TouchEvent.obtain( - SURFACE_ID, - TARGET_VIEW_ID, - getType(action), - MotionEvent.obtain( - gestureTime, - gestureTime, - action, - pointerIds.length, - pointerIds, - pointerCoords, - 0, - 0f, - 0f, - 0, - 0, - 0, - 0), - gestureTime, - pointerCoords[0].x, - pointerCoords[0].y, - mTouchEventCoalescingKeyHelper); - } - - private static TouchEventType getType(int action) { - action &= ~MotionEvent.ACTION_POINTER_INDEX_MASK; - switch (action) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - return TouchEventType.START; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - return TouchEventType.END; - case MotionEvent.ACTION_MOVE: - return TouchEventType.MOVE; - case MotionEvent.ACTION_CANCEL: - return TouchEventType.CANCEL; - } - - return TouchEventType.START; - } - - private static ReadableMap buildGestureEvent( - int surfaceId, - int viewTag, - float locationX, - float locationY, - int time, - int pointerId, - List touches, - List changedTouches) { - WritableMap gesture = buildGesture(surfaceId, viewTag, locationX, locationY, time, pointerId); - gesture.putArray("changedTouches", JavaOnlyArray.from(changedTouches)); - gesture.putArray("touches", JavaOnlyArray.from(touches)); - return gesture; - } - - private static WritableMap buildGesture( - int surfaceId, int viewTag, float locationX, float locationY, int time, int pointerId) { - WritableMap map = new JavaOnlyMap(); - map.putInt("targetSurface", surfaceId); - map.putInt("target", viewTag); - map.putDouble("locationX", locationX); - map.putDouble("locationY", locationY); - map.putDouble("pageX", locationX); - map.putDouble("pageY", locationY); - map.putDouble("identifier", pointerId); - map.putDouble("timestamp", time); - return map; - } - - @SafeVarargs - private static List listOf(E... args) { - return Arrays.asList(args); - } - - private static PointerCoords pointerCoords(float x, float y) { - PointerCoords pointerCoords = new PointerCoords(); - pointerCoords.x = x; - pointerCoords.y = y; - return pointerCoords; - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt new file mode 100644 index 00000000000..950ddd5ee40 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt @@ -0,0 +1,600 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.fabric.events + +import android.util.DisplayMetrics +import android.view.MotionEvent +import com.facebook.react.bridge.* +import com.facebook.react.fabric.FabricUIManager +import com.facebook.react.uimanager.DisplayMetricsHolder +import com.facebook.react.uimanager.events.TouchEvent +import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper +import com.facebook.react.uimanager.events.TouchEventType +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.* +import org.mockito.Mockito.mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.powermock.api.mockito.PowerMockito.mockStatic +import org.powermock.api.mockito.PowerMockito.`when` as whenever +import org.powermock.core.classloader.annotations.PowerMockIgnore +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor +import org.powermock.modules.junit4.rule.PowerMockRule +import org.robolectric.RobolectricTestRunner + +@PrepareForTest(Arguments::class, FabricUIManager::class) +@SuppressStaticInitializationFor("com.facebook.react.fabric.FabricUIManager") +@RunWith(RobolectricTestRunner::class) +@PowerMockIgnore("org.mockito.*", "org.robolectric.*", "androidx.*", "android.*") +class TouchEventDispatchTest { + @get:Rule var rule = PowerMockRule() + private val touchEventCoalescingKeyHelper = TouchEventCoalescingKeyHelper() + + /** Events (1 pointer): START -> MOVE -> MOVE -> UP */ + private val startMoveEndSequence = + listOf( + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_DOWN, + pointerId = 0, + pointerIds = intArrayOf(0), + pointerCoords = arrayOf(pointerCoords(1f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_MOVE, + pointerId = 0, + pointerIds = intArrayOf(0), + pointerCoords = arrayOf(pointerCoords(1f, 2f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_MOVE, + pointerId = 0, + pointerIds = intArrayOf(0), + pointerCoords = arrayOf(pointerCoords(1f, 3f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_UP, + pointerId = 0, + pointerIds = intArrayOf(0), + pointerCoords = arrayOf(pointerCoords(1f, 3f)))) + + /** Expected values for [startMoveEndSequence] */ + private val startMoveEndExpectedSequence = + listOf( + /* + * START event for touch 1: + * { + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), + /* + * MOVE event for touch 1: + * { + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 2f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0))), + /* + * MOVE event for touch 1: + * { + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 3f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0))), + /* + * END event for touch 1: + * { + * touches: [], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 3f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = emptyList(), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0)))) + + /** Events (2 pointer): START 1st -> START 2nd -> MOVE 1st -> UP 2st -> UP 1st */ + private val startPointerMoveUpSequence = + listOf( + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_DOWN, + pointerId = 0, + pointerIds = intArrayOf(0), + pointerCoords = arrayOf(pointerCoords(1f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_POINTER_DOWN, + pointerId = 1, + pointerIds = intArrayOf(0, 1), + pointerCoords = arrayOf(pointerCoords(1f, 1f), pointerCoords(2f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_MOVE, + pointerId = 0, + pointerIds = intArrayOf(0, 1), + pointerCoords = arrayOf(pointerCoords(1f, 2f), pointerCoords(2f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_POINTER_UP, + pointerId = 1, + pointerIds = intArrayOf(0, 1), + pointerCoords = arrayOf(pointerCoords(1f, 2f), pointerCoords(2f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_POINTER_UP, + pointerId = 0, + pointerIds = intArrayOf(0), + pointerCoords = arrayOf(pointerCoords(1f, 2f)))) + + /** Expected values for [startPointerMoveUpSequence] */ + private val startPointerMoveUpExpectedSequence = + listOf( + /* + * START event for touch 1: + * { + * touch: 0, + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), + /* + * START event for touch 2: + * { + * touch: 1, + * touches: [touch0, touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 2f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 1, + touches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * MOVE event for touch 1: + * { + * touch: 0, + * touches: [touch0, touch1], + * changed: [touch0, touch1] + * } + * { + * touch: 1, + * touches: [touch0, touch1], + * changed: [touch0, touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 2f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + changedTouches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 2f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 1, + touches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + changedTouches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * UP event pointer 1: + * { + * touch: 1, + * touches: [touch0], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 2f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 1, + touches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * UP event pointer 0: + * { + * touch: 0, + * touches: [], + * changed: [touch0] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 2f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = emptyList(), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0)))) + + /** Events (2 pointer): START 1st -> START 2nd -> MOVE 1st -> CANCEL */ + private val startMoveCancelSequence = + listOf( + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_DOWN, + pointerId = 0, + pointerIds = intArrayOf(0), + pointerCoords = arrayOf(pointerCoords(1f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_POINTER_DOWN, + pointerId = 1, + pointerIds = intArrayOf(0, 1), + pointerCoords = arrayOf(pointerCoords(1f, 1f), pointerCoords(2f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_MOVE, + pointerId = 0, + pointerIds = intArrayOf(0, 1), + pointerCoords = arrayOf(pointerCoords(1f, 2f), pointerCoords(2f, 1f))), + createTouchEvent( + gestureTime = GESTURE_START_TIME, + action = MotionEvent.ACTION_CANCEL, + pointerId = 0, + pointerIds = intArrayOf(0, 1), + pointerCoords = arrayOf(pointerCoords(1f, 3f), pointerCoords(2f, 1f)))) + + /** Expected values for [startMoveCancelSequence] */ + private val startMoveCancelExpectedSequence = + listOf( + /* + * START event for touch 1: + * { + * touch: 0, + * touches: [touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0))), + /* + * START event for touch 2: + * { + * touch: 1, + * touches: [touch0, touch1], + * changed: [touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 2f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 1, + touches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 1f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + changedTouches = + listOf(buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * MOVE event for touch 1: + * { + * touch: 0, + * touches: [touch0, touch1], + * changed: [touch0, touch1] + * } + * { + * touch: 1, + * touches: [touch0, touch1], + * changed: [touch0, touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 2f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + changedTouches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + buildGestureEvent( + SURFACE_ID, + TARGET_VIEW_ID, + 2f, + 1f, + GESTURE_START_TIME, + 1, + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)), + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 2f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + /* + * CANCEL event: + * { + * touch: 0, + * touches: [], + * changed: [touch0, touch1] + * } + * { + * touch: 1, + * touches: [], + * changed: [touch0, touch1] + * } + */ + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 1f, + locationY = 3f, + time = GESTURE_START_TIME, + pointerId = 0, + touches = emptyList(), + changedTouches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1))), + buildGestureEvent( + surfaceId = SURFACE_ID, + viewTag = TARGET_VIEW_ID, + locationX = 2f, + locationY = 1f, + time = GESTURE_START_TIME, + pointerId = 1, + touches = emptyList(), + changedTouches = + listOf( + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 1f, 3f, GESTURE_START_TIME, 0), + buildGesture(SURFACE_ID, TARGET_VIEW_ID, 2f, 1f, GESTURE_START_TIME, 1)))) + private var dispatchedEvents: List = emptyList() + private lateinit var eventEmitter: FabricEventEmitter + private lateinit var uiManager: FabricUIManager + + @Before + fun setUp() { + mockStatic(Arguments::class.java) + mockStatic(FabricUIManager::class.java) + whenever(Arguments.createArray()).thenAnswer { JavaOnlyArray() } + whenever(Arguments.createMap()).thenAnswer { JavaOnlyMap() } + val metrics = DisplayMetrics() + metrics.xdpi = 1f + metrics.ydpi = 1f + metrics.density = 1f + DisplayMetricsHolder.setWindowDisplayMetrics(metrics) + uiManager = mock(FabricUIManager::class.java) + eventEmitter = FabricEventEmitter(uiManager) + } + + @Test + fun testFabric_startMoveEnd() { + for (event in startMoveEndSequence) { + event.dispatchModern(eventEmitter) + } + val argument = ArgumentCaptor.forClass(WritableMap::class.java) + verify(uiManager, times(4)) + .receiveEvent( + anyInt(), anyInt(), anyString(), anyBoolean(), anyInt(), argument.capture(), anyInt()) + Assert.assertEquals(startMoveEndExpectedSequence, argument.allValues) + } + + @Test + fun testFabric_startMoveCancel() { + for (event in startMoveCancelSequence) { + event.dispatchModern(eventEmitter) + } + val argument = ArgumentCaptor.forClass(WritableMap::class.java) + verify(uiManager, times(6)) + .receiveEvent( + anyInt(), anyInt(), anyString(), anyBoolean(), anyInt(), argument.capture(), anyInt()) + Assert.assertEquals(startMoveCancelExpectedSequence, argument.allValues) + } + + @Test + fun testFabric_startPointerUpCancel() { + for (event in startPointerMoveUpSequence) { + event.dispatchModern(eventEmitter) + } + val argument = ArgumentCaptor.forClass(WritableMap::class.java) + verify(uiManager, times(6)) + .receiveEvent( + anyInt(), anyInt(), anyString(), anyBoolean(), anyInt(), argument.capture(), anyInt()) + Assert.assertEquals(startPointerMoveUpExpectedSequence, argument.allValues) + } + + private fun createTouchEvent( + gestureTime: Int, + action: Int, + pointerId: Int, + pointerIds: IntArray, + pointerCoords: Array + ): TouchEvent { + touchEventCoalescingKeyHelper.addCoalescingKey(gestureTime.toLong()) + val action = action or (pointerId shl MotionEvent.ACTION_POINTER_INDEX_SHIFT) + return TouchEvent.obtain( + SURFACE_ID, + TARGET_VIEW_ID, + getType(action), + MotionEvent.obtain( + gestureTime.toLong(), + gestureTime.toLong(), + action, + pointerIds.size, + pointerIds, + pointerCoords, + 0, + 0f, + 0f, + 0, + 0, + 0, + 0), + gestureTime.toLong(), + pointerCoords[0].x, + pointerCoords[0].y, + touchEventCoalescingKeyHelper) + } + + companion object { + private const val SURFACE_ID = 121 + private const val TARGET_VIEW_ID = 42 + private const val GESTURE_START_TIME = 1 + + private fun getType(action: Int): TouchEventType { + val action = action and MotionEvent.ACTION_POINTER_INDEX_MASK.inv() + when (action) { + MotionEvent.ACTION_DOWN, + MotionEvent.ACTION_POINTER_DOWN -> return TouchEventType.START + MotionEvent.ACTION_UP, + MotionEvent.ACTION_POINTER_UP -> return TouchEventType.END + MotionEvent.ACTION_MOVE -> return TouchEventType.MOVE + MotionEvent.ACTION_CANCEL -> return TouchEventType.CANCEL + } + return TouchEventType.START + } + + private fun buildGestureEvent( + surfaceId: Int, + viewTag: Int, + locationX: Float, + locationY: Float, + time: Int, + pointerId: Int, + touches: List, + changedTouches: List + ): ReadableMap = + buildGesture(surfaceId, viewTag, locationX, locationY, time, pointerId).apply { + putArray("changedTouches", JavaOnlyArray.from(changedTouches)) + putArray("touches", JavaOnlyArray.from(touches)) + } + + private fun buildGesture( + surfaceId: Int, + viewTag: Int, + locationX: Float, + locationY: Float, + time: Int, + pointerId: Int + ): WritableMap = + JavaOnlyMap().apply { + putInt("targetSurface", surfaceId) + putInt("target", viewTag) + putDouble("locationX", locationX.toDouble()) + putDouble("locationY", locationY.toDouble()) + putDouble("pageX", locationX.toDouble()) + putDouble("pageY", locationY.toDouble()) + putDouble("identifier", pointerId.toDouble()) + putDouble("timestamp", time.toDouble()) + } + + private fun pointerCoords(x: Float, y: Float): MotionEvent.PointerCoords = + MotionEvent.PointerCoords().apply { + this.x = x + this.y = y + } + } +}