From c75abefea6d553ba90a2d63ea6351845236712a5 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Wed, 27 Dec 2023 08:52:29 -0800 Subject: [PATCH] Convert the last Unit Tests to Kotlin (#42078) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/42078 Resubmit of D51891716 and D52033328 I'm doing a pass and converting the last Java Unit Tests we had to Kotlin I've also re-enabled multiple tests that were disabled in the past. Changelog: [Internal] [Changed] - Convert the last Unit Tests to Kotlin Reviewed By: rshest Differential Revision: D52430728 fbshipit-source-id: e6b4a6ed88d852024d959cf5148e992e97a84434 --- .../runtime/BridgelessReactContextTest.java | 71 ----- .../runtime/BridgelessReactContextTest.kt | 70 +++++ .../facebook/react/runtime/ReactHostTest.java | 187 ------------- .../facebook/react/runtime/ReactHostTest.kt | 203 ++++++++++++++ .../react/runtime/ReactSurfaceTest.java | 253 ------------------ .../react/runtime/ReactSurfaceTest.kt | 215 +++++++++++++++ .../configuration/MockitoConfiguration.java | 24 -- .../configuration/MockitoConfiguration.kt | 17 ++ 8 files changed, 505 insertions(+), 535 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/BridgelessReactContextTest.java create mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/BridgelessReactContextTest.kt delete mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostTest.java create mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostTest.kt delete mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactSurfaceTest.java create mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactSurfaceTest.kt delete mode 100644 packages/react-native/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.java create mode 100644 packages/react-native/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.kt diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/BridgelessReactContextTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/BridgelessReactContextTest.java deleted file mode 100644 index e5e773c8c2c..00000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/BridgelessReactContextTest.java +++ /dev/null @@ -1,71 +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.runtime; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.doReturn; - -import android.app.Activity; -import android.content.Context; -import com.facebook.react.bridge.JSIModuleType; -import com.facebook.react.uimanager.UIManagerModule; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; - -/** Tests {@link BridgelessReactContext} */ -@RunWith(RobolectricTestRunner.class) -public class BridgelessReactContextTest { - - private Context mContext; - private ReactHostImpl mReactHost; - private BridgelessReactContext mBridgelessReactContext; - - @Before - public void setUp() { - mContext = Robolectric.buildActivity(Activity.class).create().get(); - mReactHost = Mockito.mock(ReactHostImpl.class); - mBridgelessReactContext = new BridgelessReactContext(mContext, mReactHost); - } - - @Test - public void getNativeModuleTest() { - UIManagerModule mUiManagerModule = Mockito.mock(UIManagerModule.class); - doReturn(mUiManagerModule) - .when(mReactHost) - .getNativeModule(ArgumentMatchers.>any()); - - UIManagerModule uiManagerModule = - mBridgelessReactContext.getNativeModule(UIManagerModule.class); - - assertThat(uiManagerModule).isEqualTo(mUiManagerModule); - } - - @Test(expected = UnsupportedOperationException.class) - public void getJSIModule_throwsException() { - mBridgelessReactContext.getJSIModule(JSIModuleType.TurboModuleManager); - } - - // Disable this test for now due to mocking FabricUIManager fails - // @Test - // public void getJSIModuleTest() { - // FabricUIManager fabricUiManager = Mockito.mock(FabricUIManager.class); - // doReturn(fabricUiManager).when(mReactHost).getUIManager(); - // assertThat(mBridgelessReactContext.getJSIModule(JSIModuleType.UIManager)) - // .isEqualTo(fabricUiManager); - // } - - @Test(expected = UnsupportedOperationException.class) - public void getCatalystInstance_throwsException() { - mBridgelessReactContext.getCatalystInstance(); - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/BridgelessReactContextTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/BridgelessReactContextTest.kt new file mode 100644 index 00000000000..7d19faeddc9 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/BridgelessReactContextTest.kt @@ -0,0 +1,70 @@ +/* + * 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.runtime + +import android.app.Activity +import android.content.Context +import com.facebook.react.bridge.JSIModuleType +import com.facebook.react.fabric.FabricUIManager +import com.facebook.react.uimanager.UIManagerModule +import com.facebook.testutils.shadows.ShadowSoLoader +import org.assertj.core.api.Assertions +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.Mockito +import org.mockito.Mockito.doReturn +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** Tests [BridgelessReactContext] */ +@RunWith(RobolectricTestRunner::class) +@Config(shadows = [ShadowSoLoader::class]) +class BridgelessReactContextTest { + private lateinit var context: Context + private lateinit var reactHost: ReactHostImpl + private lateinit var bridgelessReactContext: BridgelessReactContext + + @Before + fun setUp() { + context = Robolectric.buildActivity(Activity::class.java).create().get() + reactHost = Mockito.mock(ReactHostImpl::class.java) + bridgelessReactContext = BridgelessReactContext(context, reactHost) + } + + @Test + fun getNativeModuleTest() { + val mUiManagerModule = Mockito.mock(UIManagerModule::class.java) + doReturn(mUiManagerModule) + .`when`(reactHost) + .getNativeModule(ArgumentMatchers.any>()) + val uiManagerModule = bridgelessReactContext.getNativeModule(UIManagerModule::class.java) + Assertions.assertThat(uiManagerModule).isEqualTo(mUiManagerModule) + } + + @Test(expected = UnsupportedOperationException::class) + fun getJSIModule_throwsException() { + bridgelessReactContext.getJSIModule(JSIModuleType.TurboModuleManager) + } + + @Test + fun getJSIModuleTest() { + val fabricUiManager = Mockito.mock(FabricUIManager::class.java) + doReturn(fabricUiManager).`when`(reactHost).uiManager + Assertions.assertThat(bridgelessReactContext.getJSIModule(JSIModuleType.UIManager)) + .isEqualTo(fabricUiManager) + } + + @Test(expected = UnsupportedOperationException::class) + fun getCatalystInstance_throwsException() { + // Disable this test for now due to mocking FabricUIManager fails + bridgelessReactContext.catalystInstance + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostTest.java deleted file mode 100644 index a72e51adee5..00000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostTest.java +++ /dev/null @@ -1,187 +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.runtime; - -import static android.os.Looper.getMainLooper; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.MockitoAnnotations.initMocks; -import static org.robolectric.Shadows.shadowOf; - -import android.app.Activity; -import com.facebook.react.MemoryPressureRouter; -import com.facebook.react.bridge.JSBundleLoader; -import com.facebook.react.bridge.MemoryPressureListener; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.UIManager; -import com.facebook.react.common.LifecycleState; -import com.facebook.react.devsupport.interfaces.PackagerStatusCallback; -import com.facebook.react.fabric.ComponentFactory; -import com.facebook.react.interfaces.TaskInterface; -import com.facebook.react.runtime.internal.bolts.TaskCompletionSource; -import com.facebook.react.uimanager.events.BlackHoleEventDispatcher; -import com.facebook.react.uimanager.events.EventDispatcher; -import com.facebook.testutils.shadows.ShadowSoLoader; -import java.util.concurrent.TimeUnit; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.android.controller.ActivityController; -import org.robolectric.annotation.Config; -import org.robolectric.annotation.LooperMode; - -/** Tests {@linkcom.facebook.react.runtime.ReactHostImpl} */ -@Ignore("Ignore for now as these tests fail in OSS only") -@RunWith(RobolectricTestRunner.class) -@Config(shadows = ShadowSoLoader.class) -@LooperMode(LooperMode.Mode.PAUSED) -public class ReactHostTest { - - private ReactHostDelegate mReactHostDelegate; - private ReactInstance mReactInstance; - private MemoryPressureRouter mMemoryPressureRouter; - private BridgelessDevSupportManager mDevSupportManager; - private JSBundleLoader mJSBundleLoader; - private ReactHostImpl mReactHost; - private ActivityController mActivityController; - private ComponentFactory mComponentFactory; - private BridgelessReactContext mBridgelessReactContext; - - @Before - public void setUp() throws Exception { - initMocks(this); - - mActivityController = Robolectric.buildActivity(Activity.class).create().start().resume(); - - mReactHostDelegate = mock(ReactHostDelegate.class); - mReactInstance = mock(ReactInstance.class); - mMemoryPressureRouter = mock(MemoryPressureRouter.class); - mDevSupportManager = mock(BridgelessDevSupportManager.class); - mJSBundleLoader = mock(JSBundleLoader.class); - mComponentFactory = mock(ComponentFactory.class); - mBridgelessReactContext = mock(BridgelessReactContext.class); - - // TODO This should be replaced with proper mocking once this test is un-ignored - // whenNew(ReactInstance.class).withAnyArguments().thenReturn(mReactInstance); - // - // whenNew(BridgelessReactContext.class).withAnyArguments().thenReturn(mBridgelessReactContext); - // whenNew(MemoryPressureRouter.class).withAnyArguments().thenReturn(mMemoryPressureRouter); - // - // whenNew(BridgelessDevSupportManager.class).withAnyArguments().thenReturn(mDevSupportManager); - - doReturn(mJSBundleLoader).when(mReactHostDelegate).getJsBundleLoader(); - - mReactHost = - new ReactHostImpl( - mActivityController.get().getApplication(), - mReactHostDelegate, - mComponentFactory, - false, - null, - false); - - TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - taskCompletionSource.setResult(true); - // TODO This should be replaced with proper mocking once this test is un-ignored - // whenNew(TaskCompletionSource.class).withAnyArguments().thenReturn(taskCompletionSource); - } - - @Test - public void getEventDispatcher_returnsBlackHoleEventDispatcher() { - EventDispatcher eventDispatcher = mReactHost.getEventDispatcher(); - assertThat(eventDispatcher).isInstanceOf(BlackHoleEventDispatcher.class); - } - - @Test - public void getUIManager_returnsNullIfNoInstance() { - UIManager uiManager = mReactHost.getUIManager(); - assertThat(uiManager).isNull(); - } - - @Test - public void testGetDevSupportManager() { - assertThat(mReactHost.getDevSupportManager()).isEqualTo(mDevSupportManager); - } - - @Test - public void testStart() throws Exception { - doNothing().when(mDevSupportManager).isPackagerRunning(any(PackagerStatusCallback.class)); - assertThat(mReactHost.isInstanceInitialized()).isFalse(); - - waitForTaskUIThread(mReactHost.start()); - - assertThat(mReactHost.isInstanceInitialized()).isTrue(); - assertThat(mReactHost.getCurrentReactContext()).isNotNull(); - verify(mMemoryPressureRouter).addMemoryPressureListener((MemoryPressureListener) any()); - } - - private void startReactHost() throws Exception { - waitForTaskUIThread(mReactHost.start()); - } - - @Test - public void testDestroy() throws Exception { - startReactHost(); - - waitForTaskUIThread(mReactHost.destroy("Destroying from testing infra", null)); - assertThat(mReactHost.isInstanceInitialized()).isFalse(); - assertThat(mReactHost.getCurrentReactContext()).isNull(); - } - - @Test - public void testReload() throws Exception { - startReactHost(); - - ReactContext oldReactContext = mReactHost.getCurrentReactContext(); - BridgelessReactContext newReactContext = mock(BridgelessReactContext.class); - assertThat(newReactContext).isNotEqualTo(oldReactContext); - // TODO This should be replaced with proper mocking once this test is un-ignored - // whenNew(BridgelessReactContext.class).withAnyArguments().thenReturn(newReactContext); - - waitForTaskUIThread(mReactHost.reload("Reload from testing infra")); - - assertThat(mReactHost.isInstanceInitialized()).isTrue(); - assertThat(mReactHost.getCurrentReactContext()).isNotNull(); - assertThat(mReactHost.getCurrentReactContext()).isEqualTo(newReactContext); - assertThat(mReactHost.getCurrentReactContext()).isNotEqualTo(oldReactContext); - } - - @Test - public void testLifecycleStateChanges() throws Exception { - startReactHost(); - - assertThat(mReactHost.getLifecycleState()).isEqualTo(LifecycleState.BEFORE_CREATE); - mReactHost.onHostResume(mActivityController.get()); - assertThat(mReactHost.getLifecycleState()).isEqualTo(LifecycleState.RESUMED); - mReactHost.onHostPause(mActivityController.get()); - assertThat(mReactHost.getLifecycleState()).isEqualTo(LifecycleState.BEFORE_RESUME); - mReactHost.onHostDestroy(mActivityController.get()); - assertThat(mReactHost.getLifecycleState()).isEqualTo(LifecycleState.BEFORE_CREATE); - } - - private static void waitForTaskUIThread(TaskInterface task) throws InterruptedException { - boolean isTaskCompleted = false; - while (!isTaskCompleted) { - if (!task.waitForCompletion(4, TimeUnit.MILLISECONDS)) { - shadowOf(getMainLooper()).idle(); - } else { - if (task.isCancelled() || task.isFaulted()) { - throw new RuntimeException("Task was cancelled or faulted. Error: " + task.getError()); - } - isTaskCompleted = true; - } - } - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostTest.kt new file mode 100644 index 00000000000..44825dd09ea --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostTest.kt @@ -0,0 +1,203 @@ +/* + * 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.runtime + +import android.app.Activity +import android.os.Looper +import com.facebook.react.MemoryPressureRouter +import com.facebook.react.bridge.JSBundleLoader +import com.facebook.react.bridge.MemoryPressureListener +import com.facebook.react.bridge.UIManager +import com.facebook.react.common.LifecycleState +import com.facebook.react.common.annotations.UnstableReactNativeAPI +import com.facebook.react.devsupport.DisabledDevSupportManager +import com.facebook.react.devsupport.interfaces.PackagerStatusCallback +import com.facebook.react.fabric.ComponentFactory +import com.facebook.react.interfaces.TaskInterface +import com.facebook.react.runtime.internal.bolts.TaskCompletionSource +import com.facebook.react.uimanager.events.BlackHoleEventDispatcher +import com.facebook.testutils.shadows.ShadowSoLoader +import java.util.concurrent.TimeUnit +import org.assertj.core.api.Assertions +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.MockedConstruction +import org.mockito.Mockito +import org.mockito.Mockito.withSettings +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner +import org.robolectric.Shadows +import org.robolectric.android.controller.ActivityController +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode + +/** Tests [ReactHostImpl] */ +@RunWith(RobolectricTestRunner::class) +@Config(shadows = [ShadowSoLoader::class]) +@LooperMode(LooperMode.Mode.PAUSED) +@OptIn(UnstableReactNativeAPI::class) +class ReactHostTest { + private lateinit var reactHostDelegate: ReactHostDelegate + private lateinit var reactInstance: ReactInstance + private lateinit var memoryPressureRouter: MemoryPressureRouter + private lateinit var jSBundleLoader: JSBundleLoader + private lateinit var reactHost: ReactHostImpl + private lateinit var activityController: ActivityController + private lateinit var componentFactory: ComponentFactory + private lateinit var bridgelessReactContext: BridgelessReactContext + + private lateinit var mockedReactInstanceCtor: MockedConstruction + private lateinit var mockedDevSupportManagerCtor: MockedConstruction + private lateinit var mockedBridgelessReactContextCtor: MockedConstruction + private lateinit var mockedMemoryPressureRouterCtor: MockedConstruction + private lateinit var mockedTaskCompletionSourceCtor: MockedConstruction> + + @Before + fun setUp() { + activityController = Robolectric.buildActivity(Activity::class.java).create().start().resume() + + reactHostDelegate = Mockito.mock(ReactHostDelegate::class.java) + reactInstance = Mockito.mock(ReactInstance::class.java) + memoryPressureRouter = Mockito.mock(MemoryPressureRouter::class.java) + jSBundleLoader = Mockito.mock(JSBundleLoader::class.java) + componentFactory = Mockito.mock(ComponentFactory::class.java) + bridgelessReactContext = Mockito.mock(BridgelessReactContext::class.java) + + mockedReactInstanceCtor = Mockito.mockConstruction(ReactInstance::class.java) + mockedDevSupportManagerCtor = Mockito.mockConstruction(BridgelessDevSupportManager::class.java) + mockedBridgelessReactContextCtor = Mockito.mockConstruction(BridgelessReactContext::class.java) + mockedMemoryPressureRouterCtor = Mockito.mockConstruction(MemoryPressureRouter::class.java) + + Mockito.doReturn(jSBundleLoader).`when`(reactHostDelegate).jsBundleLoader + reactHost = + ReactHostImpl( + activityController.get().application, + reactHostDelegate, + componentFactory, + false, + {}, + false) + val taskCompletionSource = TaskCompletionSource().apply { setResult(true) } + mockedTaskCompletionSourceCtor = + Mockito.mockConstruction( + TaskCompletionSource::class.java, withSettings().useConstructor(taskCompletionSource)) + } + + @After + fun tearDown() { + mockedReactInstanceCtor.close() + mockedDevSupportManagerCtor.close() + mockedBridgelessReactContextCtor.close() + mockedMemoryPressureRouterCtor.close() + mockedTaskCompletionSourceCtor.close() + } + + @Test + fun getEventDispatcher_returnsBlackHoleEventDispatcher() { + val eventDispatcher = reactHost.eventDispatcher + Assertions.assertThat(eventDispatcher).isInstanceOf(BlackHoleEventDispatcher::class.java) + } + + @Test + fun getUIManager_returnsNullIfNoInstance() { + val uiManager: UIManager? = reactHost.uiManager + Assertions.assertThat(uiManager).isNull() + } + + @Test + fun testGetDevSupportManager() { + // BridgelessDevSupportManager is created only for debug + // we check if it was instantiated or if DisabledDevSupportManager was created (for release). + if (mockedDevSupportManagerCtor.constructed().isNotEmpty()) { + val devSupportManager = mockedDevSupportManagerCtor.constructed().first() + Assertions.assertThat(reactHost.devSupportManager).isEqualTo(devSupportManager) + } else { + Assertions.assertThat(reactHost.devSupportManager) + .isInstanceOf(DisabledDevSupportManager::class.java) + } + } + + @Test + @Ignore("Test is currently failing in OSS and needs to be looked into") + fun testStart() { + val devSupportManager = mockedDevSupportManagerCtor.constructed().first() + + Mockito.doNothing() + .`when`(devSupportManager) + .isPackagerRunning(ArgumentMatchers.any(PackagerStatusCallback::class.java)) + Assertions.assertThat(reactHost.isInstanceInitialized).isFalse() + waitForTaskUIThread(reactHost.start()) + Assertions.assertThat(reactHost.isInstanceInitialized).isTrue() + Assertions.assertThat(reactHost.currentReactContext).isNotNull() + Mockito.verify(memoryPressureRouter) + .addMemoryPressureListener(ArgumentMatchers.any() as MemoryPressureListener) + } + + @Test + @Ignore("Test is currently failing in OSS and needs to be looked into") + fun testDestroy() { + startReactHost() + waitForTaskUIThread(reactHost.destroy("Destroying from testing infra", null)) + Assertions.assertThat(reactHost.isInstanceInitialized).isFalse() + Assertions.assertThat(reactHost.currentReactContext).isNull() + } + + @Test + @Ignore("Test is currently failing in OSS and needs to be looked into") + fun testReload() { + startReactHost() + val oldReactContext = reactHost.currentReactContext + val newReactContext = Mockito.mock(BridgelessReactContext::class.java) + Assertions.assertThat(newReactContext).isNotEqualTo(oldReactContext) + // TODO This should be replaced with proper mocking once this test is un-ignored + // whenNew(BridgelessReactContext.class).withAnyArguments().thenReturn(newReactContext); + waitForTaskUIThread(reactHost.reload("Reload from testing infra")) + Assertions.assertThat(reactHost.isInstanceInitialized).isTrue() + Assertions.assertThat(reactHost.currentReactContext).isNotNull() + Assertions.assertThat(reactHost.currentReactContext).isEqualTo(newReactContext) + Assertions.assertThat(reactHost.currentReactContext).isNotEqualTo(oldReactContext) + } + + @Test + @Ignore("Test is currently failing in OSS and needs to be looked into") + fun testLifecycleStateChanges() { + startReactHost() + Assertions.assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.BEFORE_CREATE) + reactHost.onHostResume(activityController.get()) + Assertions.assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.RESUMED) + reactHost.onHostPause(activityController.get()) + Assertions.assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.BEFORE_RESUME) + reactHost.onHostDestroy(activityController.get()) + Assertions.assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.BEFORE_CREATE) + } + + private fun startReactHost() { + waitForTaskUIThread(reactHost.start()) + } + + companion object { + @Throws(InterruptedException::class) + private fun waitForTaskUIThread(task: TaskInterface) { + var isTaskCompleted = false + while (!isTaskCompleted) { + if (!task.waitForCompletion(4, TimeUnit.MILLISECONDS)) { + Shadows.shadowOf(Looper.getMainLooper()).idle() + } else { + if (task.isCancelled() || task.isFaulted()) { + throw RuntimeException("Task was cancelled or faulted. Error: " + task.getError()) + } + isTaskCompleted = true + } + } + } + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactSurfaceTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactSurfaceTest.java deleted file mode 100644 index 429e5c0350e..00000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactSurfaceTest.java +++ /dev/null @@ -1,253 +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.runtime; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.robolectric.shadows.ShadowInstrumentation.getInstrumentation; - -import android.app.Activity; -import android.content.Context; -import android.view.View; -import com.facebook.react.bridge.NativeMap; -import com.facebook.react.interfaces.fabric.SurfaceHandler; -import com.facebook.react.runtime.internal.bolts.Task; -import com.facebook.react.uimanager.events.EventDispatcher; -import java.util.concurrent.Callable; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; - -@RunWith(RobolectricTestRunner.class) -public class ReactSurfaceTest { - @Mock ReactHostDelegate mReactHostDelegate; - @Mock EventDispatcher mEventDispatcher; - - private ReactHostImpl mReactHost; - private Context mContext; - private ReactSurfaceImpl mReactSurface; - private TestSurfaceHandler mSurfaceHandler; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mContext = Robolectric.buildActivity(Activity.class).create().get(); - - mReactHost = spy(new ReactHostImpl(mContext, mReactHostDelegate, null, false, null, false)); - doAnswer(mockedStartSurface()).when(mReactHost).startSurface(any(ReactSurfaceImpl.class)); - doAnswer(mockedStartSurface()).when(mReactHost).prerenderSurface(any(ReactSurfaceImpl.class)); - doAnswer(mockedStopSurface()).when(mReactHost).stopSurface(any(ReactSurfaceImpl.class)); - doReturn(mEventDispatcher).when(mReactHost).getEventDispatcher(); - - mSurfaceHandler = new TestSurfaceHandler(); - mReactSurface = new ReactSurfaceImpl(mSurfaceHandler, mContext); - mReactSurface.attachView(new ReactSurfaceView(mContext, mReactSurface)); - } - - @Test - public void testAttach() { - assertThat(mReactSurface.getReactHost()).isNull(); - mReactSurface.attach(mReactHost); - assertThat(mReactSurface.getReactHost()).isEqualTo(mReactHost); - assertThat(mReactSurface.isAttached()).isTrue(); - } - - @Test(expected = IllegalStateException.class) - public void testAttachThrowExeption() { - mReactSurface.attach(mReactHost); - mReactSurface.attach(mReactHost); - } - - @Test - public void testPrerender() throws InterruptedException { - mReactSurface.attach(mReactHost); - Task task = (Task) mReactSurface.prerender(); - task.waitForCompletion(); - - verify(mReactHost).prerenderSurface(mReactSurface); - assertThat(mSurfaceHandler.isRunning).isTrue(); - } - - @Test - public void testStart() throws InterruptedException { - mReactSurface.attach(mReactHost); - assertThat(mReactHost.isSurfaceAttached(mReactSurface)).isFalse(); - Task task = (Task) mReactSurface.start(); - task.waitForCompletion(); - - verify(mReactHost).startSurface(mReactSurface); - assertThat(mSurfaceHandler.isRunning).isTrue(); - } - - @Test - public void testStop() throws InterruptedException { - mReactSurface.attach(mReactHost); - - Task task = (Task) mReactSurface.start(); - task.waitForCompletion(); - - task = (Task) mReactSurface.stop(); - task.waitForCompletion(); - - verify(mReactHost).stopSurface(mReactSurface); - } - - @Test - public void testClear() { - mReactSurface.getView().addView(new View(mContext)); - - mReactSurface.clear(); - - getInstrumentation().waitForIdleSync(); - - assertThat(mReactSurface.getView().getId()).isEqualTo(View.NO_ID); - assertThat(mReactSurface.getView().getChildCount()).isEqualTo(0); - } - - @Test - public void testGetLayoutSpecs() { - int measureSpecWidth = Integer.MAX_VALUE; - int measureSpecHeight = Integer.MIN_VALUE; - - assertThat(mSurfaceHandler.mWidthMeasureSpec).isNotEqualTo(measureSpecWidth); - assertThat(mSurfaceHandler.mHeightMeasureSpec).isNotEqualTo(measureSpecHeight); - - mReactSurface.attach(mReactHost); - mReactSurface.updateLayoutSpecs(measureSpecWidth, measureSpecHeight, 2, 3); - - assertThat(mSurfaceHandler.mWidthMeasureSpec).isEqualTo(measureSpecWidth); - assertThat(mSurfaceHandler.mHeightMeasureSpec).isEqualTo(measureSpecHeight); - } - - @Test - public void testGetEventDispatcher() { - mReactSurface.attach(mReactHost); - assertThat(mReactSurface.getEventDispatcher()).isEqualTo(mEventDispatcher); - } - - @Test - public void testStartStopHandlerCalls() throws InterruptedException { - mReactSurface.attach(mReactHost); - - assertThat(mReactSurface.isRunning()).isFalse(); - - Task task = (Task) mReactSurface.start(); - task.waitForCompletion(); - - assertThat(mReactSurface.isRunning()).isTrue(); - - task = (Task) mReactSurface.stop(); - task.waitForCompletion(); - - assertThat(mReactSurface.isRunning()).isFalse(); - } - - private Answer> mockedStartSurface() { - return new Answer>() { - @Override - public Task answer(InvocationOnMock invocation) { - return Task.call( - new Callable() { - @Override - public Void call() { - mSurfaceHandler.start(); - return null; - } - }); - } - }; - } - - private Answer> mockedStopSurface() { - return new Answer>() { - @Override - public Task answer(InvocationOnMock invocation) { - return Task.call( - new Callable() { - @Override - public Boolean call() { - mSurfaceHandler.stop(); - return true; - } - }); - } - }; - } - - static class TestSurfaceHandler implements SurfaceHandler { - private boolean isRunning = false; - private int mSurfaceId = 0; - - private int mHeightMeasureSpec = 0; - private int mWidthMeasureSpec = 0; - - @Override - public void start() { - isRunning = true; - } - - @Override - public void stop() { - isRunning = false; - } - - @Override - public int getSurfaceId() { - return mSurfaceId; - } - - @Override - public void setSurfaceId(int surfaceId) { - mSurfaceId = surfaceId; - } - - @Override - public boolean isRunning() { - return isRunning; - } - - @Override - public String getModuleName() { - return null; - } - - @Override - public void setMountable(boolean mountable) { - // no-op - } - - @Override - public void setLayoutConstraints( - int widthMeasureSpec, - int heightMeasureSpec, - int offsetX, - int offsetY, - boolean doLeftAndRightSwapInRTL, - boolean isRTL, - float pixelDensity) { - mWidthMeasureSpec = widthMeasureSpec; - mHeightMeasureSpec = heightMeasureSpec; - } - - @Override - public void setProps(NativeMap props) { - // no-op - } - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactSurfaceTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactSurfaceTest.kt new file mode 100644 index 00000000000..4d224410203 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactSurfaceTest.kt @@ -0,0 +1,215 @@ +/* + * 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.runtime + +import android.app.Activity +import android.content.Context +import android.view.View +import com.facebook.react.bridge.NativeMap +import com.facebook.react.common.annotations.UnstableReactNativeAPI +import com.facebook.react.fabric.ComponentFactory +import com.facebook.react.interfaces.fabric.SurfaceHandler +import com.facebook.react.runtime.internal.bolts.Task +import com.facebook.react.uimanager.events.EventDispatcher +import com.facebook.testutils.shadows.ShadowSoLoader +import org.assertj.core.api.Assertions +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.Mockito +import org.mockito.Mockito.mock +import org.mockito.stubbing.Answer +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.robolectric.shadows.ShadowInstrumentation + +@RunWith(RobolectricTestRunner::class) +@OptIn(UnstableReactNativeAPI::class) +@Config(shadows = [ShadowSoLoader::class]) +class ReactSurfaceTest { + private lateinit var reactHostDelegate: ReactHostDelegate + private lateinit var eventDispatcher: EventDispatcher + private lateinit var reactHost: ReactHostImpl + private lateinit var context: Context + private lateinit var reactSurface: ReactSurfaceImpl + private lateinit var surfaceHandler: TestSurfaceHandler + + @Before + fun setUp() { + reactHostDelegate = mock(ReactHostDelegate::class.java) + eventDispatcher = mock(EventDispatcher::class.java) + context = Robolectric.buildActivity(Activity::class.java).create().get() + val componentFactory = Mockito.mock(ComponentFactory::class.java) + reactHost = + Mockito.spy(ReactHostImpl(context, reactHostDelegate, componentFactory, false, {}, false)) + Mockito.doAnswer(mockedStartSurface()) + .`when`(reactHost) + .startSurface(ArgumentMatchers.any(ReactSurfaceImpl::class.java)) + Mockito.doAnswer(mockedStartSurface()) + .`when`(reactHost) + .prerenderSurface(ArgumentMatchers.any(ReactSurfaceImpl::class.java)) + Mockito.doAnswer(mockedStopSurface()) + .`when`(reactHost) + .stopSurface(ArgumentMatchers.any(ReactSurfaceImpl::class.java)) + Mockito.doReturn(eventDispatcher).`when`(reactHost).eventDispatcher + surfaceHandler = TestSurfaceHandler() + reactSurface = ReactSurfaceImpl(surfaceHandler, context) + reactSurface.attachView(ReactSurfaceView(context, reactSurface)) + } + + @Test + fun testAttach() { + Assertions.assertThat(reactSurface.reactHost).isNull() + reactSurface.attach(reactHost) + Assertions.assertThat(reactSurface.reactHost).isEqualTo(reactHost) + Assertions.assertThat(reactSurface.isAttached).isTrue() + } + + @Test(expected = IllegalStateException::class) + fun testAttachThrowException() { + reactSurface.attach(reactHost) + reactSurface.attach(reactHost) + } + + @Test + @Throws(InterruptedException::class) + fun testPrerender() { + reactSurface.attach(reactHost) + val task = reactSurface.prerender() as Task + task.waitForCompletion() + Mockito.verify(reactHost).prerenderSurface(reactSurface) + Assertions.assertThat(surfaceHandler.isRunning).isTrue() + } + + @Test + @Throws(InterruptedException::class) + fun testStart() { + reactSurface.attach(reactHost) + Assertions.assertThat(reactHost.isSurfaceAttached(reactSurface)).isFalse() + val task = reactSurface.start() as Task + task.waitForCompletion() + Mockito.verify(reactHost).startSurface(reactSurface) + Assertions.assertThat(surfaceHandler.isRunning).isTrue() + } + + @Test + @Throws(InterruptedException::class) + fun testStop() { + reactSurface.attach(reactHost) + var task = reactSurface.start() as Task<*> + task.waitForCompletion() + task = reactSurface.stop() as Task<*> + task.waitForCompletion() + Mockito.verify(reactHost).stopSurface(reactSurface) + } + + @Test + fun testClear() { + reactSurface.view!!.addView(View(context)) + reactSurface.clear() + ShadowInstrumentation.getInstrumentation().waitForIdleSync() + Assertions.assertThat(reactSurface.view!!.id).isEqualTo(View.NO_ID) + Assertions.assertThat(reactSurface.view!!.childCount).isEqualTo(0) + } + + @Test + fun testGetLayoutSpecs() { + val measureSpecWidth = Int.MAX_VALUE + val measureSpecHeight = Int.MIN_VALUE + Assertions.assertThat(surfaceHandler.widthMeasureSpec).isNotEqualTo(measureSpecWidth) + Assertions.assertThat(surfaceHandler.heightMeasureSpec).isNotEqualTo(measureSpecHeight) + reactSurface.attach(reactHost) + reactSurface.updateLayoutSpecs(measureSpecWidth, measureSpecHeight, 2, 3) + Assertions.assertThat(surfaceHandler.widthMeasureSpec).isEqualTo(measureSpecWidth) + Assertions.assertThat(surfaceHandler.heightMeasureSpec).isEqualTo(measureSpecHeight) + } + + @Test + fun testGetEventDispatcher() { + reactSurface.attach(reactHost) + Assertions.assertThat(reactSurface.eventDispatcher).isEqualTo(eventDispatcher) + } + + @Test + @Throws(InterruptedException::class) + fun testStartStopHandlerCalls() { + reactSurface.attach(reactHost) + Assertions.assertThat(reactSurface.isRunning).isFalse() + var task = reactSurface.start() as Task<*> + task.waitForCompletion() + Assertions.assertThat(reactSurface.isRunning).isTrue() + task = reactSurface.stop() as Task<*> + task.waitForCompletion() + Assertions.assertThat(reactSurface.isRunning).isFalse() + } + + private fun mockedStartSurface(): Answer> { + return Answer { + Task.call { + surfaceHandler.start() + null + } + } + } + + private fun mockedStopSurface(): Answer> { + return Answer { + Task.call { + surfaceHandler.stop() + true + } + } + } + + internal class TestSurfaceHandler : SurfaceHandler { + override var isRunning = false + override val moduleName: String = "TestSurfaceHandler" + override val surfaceId + get() = _surfaceId + + private var _surfaceId = 0 + + var heightMeasureSpec = 0 + var widthMeasureSpec = 0 + + override fun start() { + isRunning = true + } + + override fun stop() { + isRunning = false + } + + override fun setMountable(mountable: Boolean) { + // no-op + } + + override fun setLayoutConstraints( + widthMeasureSpec: Int, + heightMeasureSpec: Int, + offsetX: Int, + offsetY: Int, + doLeftAndRightSwapInRTL: Boolean, + isRTL: Boolean, + pixelDensity: Float + ) { + this.widthMeasureSpec = widthMeasureSpec + this.heightMeasureSpec = heightMeasureSpec + } + + override fun setProps(props: NativeMap) { + // no-op + } + + override fun setSurfaceId(surfaceId: Int) { + _surfaceId = surfaceId + } + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.java b/packages/react-native/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.java deleted file mode 100644 index ab68d427ceb..00000000000 --- a/packages/react-native/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.java +++ /dev/null @@ -1,24 +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 org.mockito.configuration; - -/** - * Disables the Mockito cache to prevent Mockito & Robolectric bugs. - * - *

Mockito loads this with reflection, so this class might appear unused. - */ -@SuppressWarnings("unused") -public class MockitoConfiguration extends DefaultMockitoConfiguration { - - /* (non-Javadoc) - * @see org.mockito.configuration.IMockitoConfiguration#enableClassCache() - */ - public boolean enableClassCache() { - return false; - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.kt b/packages/react-native/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.kt new file mode 100644 index 00000000000..bf61c2bf98a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.kt @@ -0,0 +1,17 @@ +/* + * 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 org.mockito.configuration + +/** + * Disables the Mockito cache to prevent Mockito & Robolectric bugs. Mockito loads this with + * reflection, so this class might appear unused. + */ +@Suppress("unused") +class MockitoConfiguration : DefaultMockitoConfiguration() { + override fun enableClassCache(): Boolean = false +}