Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.Nullsafe;
Expand Down Expand Up @@ -1773,7 +1774,9 @@ private Task<Void> getOrCreateDestroyTask(final String reason, @Nullable Excepti
return mDestroyTask;
}

private @Nullable ReactHostInspectorTarget getOrCreateReactHostInspectorTarget() {
@VisibleForTesting
/* package */ @Nullable
ReactHostInspectorTarget getOrCreateReactHostInspectorTarget() {
if (mReactHostInspectorTarget == null && InspectorFlags.getFuseboxEnabled()) {
// NOTE: ReactHostInspectorTarget only retains a weak reference to `this`.
mReactHostInspectorTarget = new ReactHostInspectorTarget(this);
Expand All @@ -1783,7 +1786,8 @@ private Task<Void> getOrCreateDestroyTask(final String reason, @Nullable Excepti
}

@ThreadConfined(UI)
private void unregisterInstanceFromInspector(final @Nullable ReactInstance reactInstance) {
@VisibleForTesting
/* package */ void unregisterInstanceFromInspector(final @Nullable ReactInstance reactInstance) {
if (reactInstance != null) {
if (InspectorFlags.getFuseboxEnabled()) {
Assertions.assertCondition(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,113 +8,103 @@
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.ReleaseDevSupportManager
import com.facebook.react.fabric.ComponentFactory
import com.facebook.react.interfaces.TaskInterface
import com.facebook.react.runtime.internal.bolts.TaskCompletionSource
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlagsDefaults
import com.facebook.react.runtime.internal.bolts.Task
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.assertj.core.api.Assertions.assertThat
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.ArgumentMatchers.any
import org.mockito.MockedConstruction
import org.mockito.Mockito
import org.mockito.Mockito.withSettings
import org.mockito.Mockito.mockConstruction
import org.mockito.kotlin.doNothing
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
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 mockedReactHostInspectorTargetCtor:
MockedConstruction<ReactHostInspectorTarget>
private lateinit var mockedBridgelessReactContextCtor: MockedConstruction<BridgelessReactContext>
private lateinit var mockedMemoryPressureRouterCtor: MockedConstruction<MemoryPressureRouter>
private lateinit var mockedTaskCompletionSourceCtor: MockedConstruction<TaskCompletionSource<*>>

@Before
fun setUp() {
ReactNativeFeatureFlagsForTests.setUp()
ReactNativeFeatureFlags.override(ReactNativeNewArchitectureFeatureFlagsDefaults())

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 = mockConstruction(ReactInstance::class.java)
mockedBridgelessReactContextCtor = mockConstruction(BridgelessReactContext::class.java)
mockedMemoryPressureRouterCtor = mockConstruction(MemoryPressureRouter::class.java)

mockedReactInstanceCtor = Mockito.mockConstruction(ReactInstance::class.java)
mockedReactHostInspectorTargetCtor =
Mockito.mockConstruction(ReactHostInspectorTarget::class.java)
mockedBridgelessReactContextCtor = Mockito.mockConstruction(BridgelessReactContext::class.java)
mockedMemoryPressureRouterCtor = Mockito.mockConstruction(MemoryPressureRouter::class.java)
reactHostDelegate = mock()
whenever(reactHostDelegate.jsBundleLoader).thenReturn(mock())

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))
spy(
ReactHostImpl(
activityController.get().application,
reactHostDelegate,
mock(),
Task.IMMEDIATE_EXECUTOR,
Task.IMMEDIATE_EXECUTOR,
false /* allowPackagerServerAccess */,
false /* useDevSupport */,
))
doReturn(null).whenever(reactHost).getOrCreateReactHostInspectorTarget()
doNothing().whenever(reactHost).unregisterInstanceFromInspector(any())
}

@After
fun tearDown() {
mockedReactInstanceCtor.close()
mockedReactHostInspectorTargetCtor.close()
mockedBridgelessReactContextCtor.close()
mockedMemoryPressureRouterCtor.close()
mockedTaskCompletionSourceCtor.close()
ReactNativeFeatureFlags.dangerouslyReset()
}

@Test
fun getEventDispatcher_returnsBlackHoleEventDispatcher() {
val eventDispatcher = reactHost.eventDispatcher
Assertions.assertThat(eventDispatcher).isInstanceOf(BlackHoleEventDispatcher::class.java)
assertThat(eventDispatcher).isInstanceOf(BlackHoleEventDispatcher::class.java)
}

@Test
fun getUIManager_returnsNullIfNoInstance() {
val uiManager: UIManager? = reactHost.uiManager
Assertions.assertThat(uiManager).isNull()
assertThat(uiManager).isNull()
}

@Test
fun testGetDevSupportManager_withRelease() {
// BridgelessDevSupportManager is created only for debug
// we check if it was instantiated or if ReleaseDevSupportManager was created (for release).
Assertions.assertThat(reactHost.devSupportManager)
.isInstanceOf(ReleaseDevSupportManager::class.java)
assertThat(reactHost.devSupportManager).isInstanceOf(ReleaseDevSupportManager::class.java)
}

@Test
Expand All @@ -123,80 +113,56 @@ class ReactHostTest {
ReactHostImpl(
activityController.get().application,
reactHostDelegate,
componentFactory,
false,
mock(),
Task.IMMEDIATE_EXECUTOR,
Task.IMMEDIATE_EXECUTOR,
false /* allowPackagerServerAccess */,
true /* useDevSupport */)
Assertions.assertThat(reactHost.devSupportManager)
.isNotInstanceOf(ReleaseDevSupportManager::class.java)
assertThat(reactHost.devSupportManager).isNotInstanceOf(ReleaseDevSupportManager::class.java)
}

@Test
@Ignore("Test is currently failing in OSS and needs to be looked into")
fun testStart() {
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)
assertThat(reactHost.isInstanceInitialized).isFalse()
reactHost.start()
assertThat(reactHost.isInstanceInitialized).isTrue()
assertThat(reactHost.currentReactContext).isNotNull()

val memoryPressureRouter = mockedMemoryPressureRouterCtor.constructed().first()
verify(memoryPressureRouter).addMemoryPressureListener(any())
}

@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()
reactHost.start()
reactHost.destroy("Destroying from testing infra", null)
assertThat(reactHost.isInstanceInitialized).isFalse()
assertThat(reactHost.currentReactContext).isNull()
}

@Test
@Ignore("Test is currently failing in OSS and needs to be looked into")
fun testReload() {
startReactHost()
reactHost.start()
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)
reactHost.reload("Reload from testing infra")
assertThat(mockedBridgelessReactContextCtor.constructed().count()).isEqualTo(2)
assertThat(mockedBridgelessReactContextCtor.constructed().first()).isEqualTo(oldReactContext)
val newReactContext = mockedBridgelessReactContextCtor.constructed().last()
assertThat(reactHost.isInstanceInitialized).isTrue()
assertThat(reactHost.currentReactContext).isNotNull()
assertThat(reactHost.currentReactContext).isEqualTo(newReactContext)
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.start()
assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.BEFORE_CREATE)
reactHost.onHostResume(activityController.get())
Assertions.assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.RESUMED)
assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.RESUMED)
reactHost.onHostPause(activityController.get())
Assertions.assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.BEFORE_RESUME)
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
}
}
}
assertThat(reactHost.lifecycleState).isEqualTo(LifecycleState.BEFORE_CREATE)
}
}