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
This commit is contained in:
Nicola Corti
2023-12-27 08:52:29 -08:00
committed by Facebook GitHub Bot
parent ac9b87cd57
commit c75abefea6
8 changed files with 505 additions and 535 deletions
@@ -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.<Class<UIManagerModule>>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();
}
}
@@ -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<Class<UIManagerModule>>())
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
}
}
@@ -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<Activity> 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<Boolean> 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 <T> void waitForTaskUIThread(TaskInterface<T> 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;
}
}
}
}
@@ -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<Activity>
private lateinit var componentFactory: ComponentFactory
private lateinit var bridgelessReactContext: BridgelessReactContext
private lateinit var mockedReactInstanceCtor: MockedConstruction<ReactInstance>
private lateinit var mockedDevSupportManagerCtor: MockedConstruction<BridgelessDevSupportManager>
private lateinit var mockedBridgelessReactContextCtor: MockedConstruction<BridgelessReactContext>
private lateinit var mockedMemoryPressureRouterCtor: MockedConstruction<MemoryPressureRouter>
private lateinit var mockedTaskCompletionSourceCtor: MockedConstruction<TaskCompletionSource<*>>
@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<Boolean>().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<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 <T> waitForTaskUIThread(task: TaskInterface<T>) {
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
}
}
}
}
}
@@ -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<Void> task = (Task<Void>) 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<Void> task = (Task<Void>) mReactSurface.start();
task.waitForCompletion();
verify(mReactHost).startSurface(mReactSurface);
assertThat(mSurfaceHandler.isRunning).isTrue();
}
@Test
public void testStop() throws InterruptedException {
mReactSurface.attach(mReactHost);
Task<Void> task = (Task<Void>) mReactSurface.start();
task.waitForCompletion();
task = (Task<Void>) 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<Void> task = (Task<Void>) mReactSurface.start();
task.waitForCompletion();
assertThat(mReactSurface.isRunning()).isTrue();
task = (Task<Void>) mReactSurface.stop();
task.waitForCompletion();
assertThat(mReactSurface.isRunning()).isFalse();
}
private Answer<Task<Void>> mockedStartSurface() {
return new Answer<Task<Void>>() {
@Override
public Task<Void> answer(InvocationOnMock invocation) {
return Task.call(
new Callable<Void>() {
@Override
public Void call() {
mSurfaceHandler.start();
return null;
}
});
}
};
}
private Answer<Task<Boolean>> mockedStopSurface() {
return new Answer<Task<Boolean>>() {
@Override
public Task<Boolean> answer(InvocationOnMock invocation) {
return Task.call(
new Callable<Boolean>() {
@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
}
}
}
@@ -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<Void>
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<Void>
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<Task<Void>> {
return Answer {
Task.call {
surfaceHandler.start()
null
}
}
}
private fun mockedStopSurface(): Answer<Task<Boolean>> {
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
}
}
}
@@ -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.
*
* <p>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;
}
}
@@ -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
}