Skip to content

Commit

Permalink
Replaced symlinks with actual files in hilt-test because they were ca…
Browse files Browse the repository at this point in the history
…using headaches
  • Loading branch information
isaac-udy committed Oct 18, 2022
1 parent 77a4513 commit 37b7afe
Show file tree
Hide file tree
Showing 5 changed files with 605 additions and 5 deletions.

This file was deleted.

16 changes: 16 additions & 0 deletions enro/hilt-test/src/androidTest/java/dev/enro/TestApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package dev.enro

import android.app.Application
import dev.enro.annotations.NavigationComponent
import dev.enro.core.controller.NavigationApplication
import dev.enro.core.controller.navigationController
import dev.enro.core.plugins.EnroLogger

@NavigationComponent
open class TestApplication : Application(), NavigationApplication {
override val navigationController = navigationController {
plugin(EnroLogger())
plugin(TestPlugin)
}
}

This file was deleted.

44 changes: 44 additions & 0 deletions enro/hilt-test/src/androidTest/java/dev/enro/TestDestinations.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package dev.enro

import androidx.compose.runtime.Composable
import dev.enro.annotations.NavigationDestination
import dev.enro.core.NavigationKey
import dev.enro.core.navigationHandle
import kotlinx.parcelize.Parcelize

@Parcelize
data class DefaultActivityKey(val id: String) : NavigationKey

@NavigationDestination(DefaultActivityKey::class)
class DefaultActivity : TestActivity() {
private val navigation by navigationHandle<DefaultActivityKey> {
defaultKey(defaultKey)
}

companion object {
val defaultKey = DefaultActivityKey("default")
}
}

@Parcelize
data class GenericActivityKey(val id: String) : NavigationKey

@NavigationDestination(GenericActivityKey::class)
class GenericActivity : TestActivity()

@Parcelize
data class GenericFragmentKey(val id: String) : NavigationKey, NavigationKey.SupportsPush

@NavigationDestination(GenericFragmentKey::class)
class GenericFragment : TestFragment()

@Parcelize
data class GenericComposableKey(val id: String) : NavigationKey

@Composable
@NavigationDestination(GenericComposableKey::class)
fun GenericComposableDestination() = TestComposable(name = "GenericComposableDestination")

class UnboundActivity : TestActivity()

class UnboundFragment : TestFragment()

This file was deleted.

259 changes: 259 additions & 0 deletions enro/hilt-test/src/androidTest/java/dev/enro/TestExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
package dev.enro

import android.app.Activity
import android.app.Application
import android.os.Debug
import androidx.activity.ComponentActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.test.core.app.ActivityScenario
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
import androidx.test.runner.lifecycle.Stage
import dev.enro.core.*
import dev.enro.core.compose.ComposableDestination
import dev.enro.core.controller.NavigationController
import dev.enro.core.controller.navigationController
import dev.enro.core.result.EnroResultChannel
import kotlin.reflect.KClass

private val isDebugging: Boolean get() = Debug.isDebuggerConnected()

inline fun <reified T: NavigationKey> ActivityScenario<out ComponentActivity>.getNavigationHandle(): TypedNavigationHandle<T> {
var result: NavigationHandle? = null
onActivity{
result = it.getNavigationHandle()
}

val handle = result ?: throw IllegalStateException("Could not retrieve NavigationHandle from Activity")
handle.key as? T
?: throw IllegalStateException("Handle was of incorrect type. Expected ${T::class.java.name} but was ${handle.key::class.java.name}")
return handle.asTyped()
}

class TestNavigationContext<Context: Any, KeyType: NavigationKey>(
val context: Context,
val navigation: TypedNavigationHandle<KeyType>
) {
val navigationContext = kotlin.run {
navigation.getPrivate<NavigationHandle>("navigationHandle")
.getPrivate<NavigationContext<*>>("navigationContext")
}
}

inline fun <reified KeyType: NavigationKey> expectComposableContext(
noinline selector: (TestNavigationContext<ComposableDestination, KeyType>) -> Boolean = { true }
): TestNavigationContext<ComposableDestination, KeyType> {
return expectContext(selector)
}

inline fun <reified KeyType: NavigationKey> expectFragmentContext(
noinline selector: (TestNavigationContext<Fragment, KeyType>) -> Boolean = { true }
): TestNavigationContext<Fragment, KeyType> {
return expectContext(selector)
}

inline fun <reified ContextType: Any, reified KeyType: NavigationKey> findContextFrom(
rootContext: NavigationContext<*>?,
noinline selector: (TestNavigationContext<ContextType, KeyType>) -> Boolean = { true }
): TestNavigationContext<ContextType, KeyType>? = findContextFrom(ContextType::class, KeyType::class, rootContext, selector)

fun <ContextType: Any, KeyType: NavigationKey> findContextFrom(
contextType: KClass<ContextType>,
keyType: KClass<KeyType>,
rootContext: NavigationContext<*>?,
selector: (TestNavigationContext<ContextType, KeyType>) -> Boolean = { true }
): TestNavigationContext<ContextType, KeyType>? {
var activeContext = rootContext
while(activeContext != null) {
if (
keyType.java.isAssignableFrom(activeContext.getNavigationHandle().key::class.java)
&& contextType.java.isAssignableFrom(activeContext.contextReference::class.java)
) {
val context = TestNavigationContext(
activeContext.contextReference as ContextType,
activeContext.getNavigationHandle().asTyped(keyType)
)
if (selector(context)) return context
}

activeContext.containerManager.containers
.filter { it.acceptsDirection(NavigationDirection.Present) }
.forEach { presentationContainer ->
presentationContainer.activeContext
?.let {
findContextFrom(contextType, keyType, it, selector)
}
?.let {
return it
}
}

activeContext = activeContext.containerManager.activeContainer?.activeContext
?: when(val reference = activeContext.contextReference) {
is FragmentActivity -> reference.supportFragmentManager.primaryNavigationFragment?.navigationContext
is Fragment -> reference.childFragmentManager.primaryNavigationFragment?.navigationContext
else -> null
}
}
return null
}

inline fun <reified ContextType: Any, reified KeyType: NavigationKey> expectContext(
noinline selector: (TestNavigationContext<ContextType, KeyType>) -> Boolean = { true }
): TestNavigationContext<ContextType, KeyType> {

return when {
ComposableDestination::class.java.isAssignableFrom(ContextType::class.java) ||
Fragment::class.java.isAssignableFrom(ContextType::class.java) -> {
waitOnMain {
val activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED)
val activity = activities.firstOrNull() as? ComponentActivity ?: return@waitOnMain null

return@waitOnMain findContextFrom(activity.navigationContext, selector)
}
}
ComponentActivity::class.java.isAssignableFrom(ContextType::class.java) -> waitOnMain {
val activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED)
val activity = activities.firstOrNull()
if(activity !is ComponentActivity) return@waitOnMain null
if(activity !is ContextType) return@waitOnMain null

val context = TestNavigationContext(
activity as ContextType,
activity.getNavigationHandle().asTyped<KeyType>()
)
return@waitOnMain if(selector(context)) context else null
}
else -> throw RuntimeException("Failed to get context type ${ContextType::class.java.name}")
}
}


fun getActiveActivity(): Activity? {
val activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED)
return activities.firstOrNull()
}

fun expectActivityHostForAnyInstruction(): FragmentActivity {
return expectActivity { it::class.java.simpleName == "ActivityHostForAnyInstruction" }
}

fun expectFragmentHostForPresentableFragment(): Fragment {
return expectFragment { it::class.java.simpleName == "FragmentHostForPresentableFragment" }
}

inline fun <reified T: ComponentActivity> expectActivity(crossinline selector: (ComponentActivity) -> Boolean = { it is T }): T {
return expectContext<T, NavigationKey> {
selector(it.context)
}.context
}

internal inline fun <reified T: Fragment> expectFragment(crossinline selector: (T) -> Boolean = { true }): T {
return expectContext<T, NavigationKey> {
selector(it.context)
}.context
}

internal inline fun <reified T: Fragment> expectNoFragment(crossinline selector: (Fragment) -> Boolean = { it is T }): Boolean {
waitFor {
runCatching { expectFragment<T>(selector) }.isFailure
}
return true
}

fun expectNoActivity() {
waitOnMain {
val activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.PRE_ON_CREATE).toList() +
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.CREATED).toList() +
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.STARTED).toList() +
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED).toList() +
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.PAUSED).toList() +
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.STOPPED).toList() +
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESTARTED).toList()
return@waitOnMain if(activities.isEmpty()) true else null
}
}

fun waitFor(block: () -> Boolean) {
val maximumTime = 7_000
val startTime = System.currentTimeMillis()

while(true) {
if(block()) return
Thread.sleep(33)
if(System.currentTimeMillis() - startTime > maximumTime) throw IllegalStateException("Took too long waiting")
}
}

fun <T: Any> waitOnMain(block: () -> T?): T {
if(isDebugging) { Thread.sleep(2000) }

val maximumTime = 7_000
val startTime = System.currentTimeMillis()
var currentResponse: T? = null

while(true) {
if (System.currentTimeMillis() - startTime > maximumTime) throw IllegalStateException("Took too long waiting")
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
InstrumentationRegistry.getInstrumentation().runOnMainSync {
currentResponse = block()
}
currentResponse?.let { return it }
Thread.sleep(33)
}
}

fun getActiveEnroResultChannels(): List<EnroResultChannel<*, *>> {
val enroResultClass = Class.forName("dev.enro.core.result.EnroResult")
val getEnroResult = enroResultClass.getDeclaredMethod("from", NavigationController::class.java)
getEnroResult.isAccessible = true
val enroResult = getEnroResult.invoke(null, application.navigationController)
getEnroResult.isAccessible = false

requireNotNull(enroResult)
val channels = enroResult.getPrivate<Map<Any, EnroResultChannel<*, * >>>("channels")
return channels.values.toList()
}

fun clearAllEnroResultChannels() {
val enroResultClass = Class.forName("dev.enro.core.result.EnroResult")
val getEnroResult = enroResultClass.getDeclaredMethod("from", NavigationController::class.java)
getEnroResult.isAccessible = true
val enroResult = getEnroResult.invoke(null, application.navigationController)
getEnroResult.isAccessible = false

requireNotNull(enroResult)
val channels = enroResult.getPrivate<MutableMap<Any, EnroResultChannel<*, * >>>("channels")
channels.clear()
}

@Suppress("unused")
fun <T> Any.callPrivate(methodName: String, vararg args: Any): T {
val method = this::class.java.declaredMethods.first { it.name.startsWith(methodName) }
method.isAccessible = true
val result = method.invoke(this, *args)
method.isAccessible = false

@Suppress("UNCHECKED_CAST")
return result as T
}

fun <T> Any.getPrivate(methodName: String): T {
val method = this::class.java.declaredFields.first { it.name.startsWith(methodName) }
method.isAccessible = true
val result = method.get(this)
method.isAccessible = false

@Suppress("UNCHECKED_CAST")
return result as T
}

val application: Application get() =
InstrumentationRegistry.getInstrumentation().context.applicationContext as Application

val ComponentActivity.navigationContext get() =
getNavigationHandle().getPrivate<NavigationContext<*>>("navigationContext")

val Fragment.navigationContext get() =
getNavigationHandle().getPrivate<NavigationContext<*>>("navigationContext")
1 change: 0 additions & 1 deletion enro/hilt-test/src/androidTest/java/dev/enro/TestPlugin.kt

This file was deleted.

13 changes: 13 additions & 0 deletions enro/hilt-test/src/androidTest/java/dev/enro/TestPlugin.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package dev.enro

import dev.enro.core.NavigationHandle
import dev.enro.core.NavigationKey
import dev.enro.core.plugins.EnroPlugin

object TestPlugin : EnroPlugin() {
var activeKey: NavigationKey? = null

override fun onActive(navigationHandle: NavigationHandle) {
activeKey = navigationHandle.key
}
}
1 change: 0 additions & 1 deletion enro/hilt-test/src/androidTest/java/dev/enro/TestViews.kt

This file was deleted.

Loading

0 comments on commit 37b7afe

Please sign in to comment.