diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java index 17086f58261f78..1ae42cbe7920e4 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java @@ -137,7 +137,7 @@ public class ReactHostImpl implements ReactHost { public ReactHostImpl( Context context, ReactHostDelegate delegate, - @Nullable ComponentFactory componentFactory, + ComponentFactory componentFactory, boolean allowPackagerServerAccess, ReactJsExceptionHandler reactJsExceptionHandler, boolean useDevSupport) { @@ -155,7 +155,7 @@ public ReactHostImpl( public ReactHostImpl( Context context, ReactHostDelegate delegate, - @Nullable ComponentFactory componentFactory, + ComponentFactory componentFactory, Executor bgExecutor, Executor uiExecutor, ReactJsExceptionHandler reactJsExceptionHandler, 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 new file mode 100644 index 00000000000000..e5e773c8c2c8e6 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/BridgelessReactContextTest.java @@ -0,0 +1,71 @@ +/* + * 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 deleted file mode 100644 index 7d19faeddc9c25..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/BridgelessReactContextTest.kt +++ /dev/null @@ -1,70 +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 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 new file mode 100644 index 00000000000000..a72e51adee5f38 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostTest.java @@ -0,0 +1,187 @@ +/* + * 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 deleted file mode 100644 index 44825dd09eae8a..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostTest.kt +++ /dev/null @@ -1,203 +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 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 new file mode 100644 index 00000000000000..429e5c0350ee6a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactSurfaceTest.java @@ -0,0 +1,253 @@ +/* + * 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 deleted file mode 100644 index 94d73a817e2888..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactSurfaceTest.kt +++ /dev/null @@ -1,212 +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 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.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() - reactHost = Mockito.spy(ReactHostImpl(context, reactHostDelegate, null, 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 new file mode 100644 index 00000000000000..ab68d427ceb032 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.java @@ -0,0 +1,24 @@ +/* + * 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 deleted file mode 100644 index bf61c2bf98aa3e..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.kt +++ /dev/null @@ -1,17 +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. - */ -@Suppress("unused") -class MockitoConfiguration : DefaultMockitoConfiguration() { - override fun enableClassCache(): Boolean = false -}