@ -18,10 +18,10 @@
package com.google.samples.apps.nowinandroid.interests.impl
import androidx.activity.viewModels
import androidx.annotation.StringRes
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.navigation3.rememberListDetailSceneStrategy
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
@ -29,35 +29,26 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.ui.NavDisplay
import androidx.test.espresso.Espresso
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.navigation.N iaBackStackViewModel
import com.google.samples.apps.nowinandroid.core.navigation. NiaNavKey
import com.google.samples.apps.nowinandroid.core.navigation. NiaNaviga tor
import com.google.samples.apps.nowinandroid.core.navigation.N avigator
import com.google.samples.apps.nowinandroid.core.navigation. rememberNavigationState
import com.google.samples.apps.nowinandroid.core.navigation. toEnt ries
import com.google.samples.apps.nowinandroid.feature.interests.api.R
import com.google.samples.apps.nowinandroid.feature.interests.api.navigation.Interests Route
import com.google.samples.apps.nowinandroid.feature.interests.api.navigation.Interests NavKey
import com.google.samples.apps.nowinandroid.feature.interests.impl.LIST_PANE_TEST_TAG
import com.google.samples.apps.nowinandroid.feature.interests.impl.navigation.interestsEntry
import com.google.samples.apps.nowinandroid.feature.topic.impl.navigation.topicEntry
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.Module
import dagger.Provides
import dagger.hilt.EntryPoint
import dagger.hilt.EntryPoints
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.modules.PolymorphicModuleBuilder
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -65,7 +56,6 @@ import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.getValue
import kotlin.properties.ReadOnlyProperty
@ -83,13 +73,6 @@ class InterestsListDetailScreenTest {
@get : Rule ( order = 1 )
val composeTestRule = createAndroidComposeRule < HiltComponentActivity > ( )
// entry point to get the features' hilt-injected EntryProviders that are installed in ActivityComponent
@EntryPoint
@InstallIn ( ActivityComponent :: class )
interface EntryProvidersEntryPoint {
fun getEntryProviders ( ) : Set < @JvmSuppressWildcards EntryProviderScope < NiaNavKey > . ( ) -> Unit >
}
@Inject
lateinit var topicsRepository : TopicsRepository
@ -104,15 +87,9 @@ class InterestsListDetailScreenTest {
private val Topic . testTag
get ( ) = " topic: ${this.id} "
private lateinit var entryProviderBuilders : Set < EntryProviderScope < NiaNavKey > . ( ) -> Unit >
@Before
fun setup ( ) {
hiltRule . inject ( )
composeTestRule . apply {
entryProviderBuilders = EntryPoints . get ( activity , EntryProvidersEntryPoint :: class . java )
. getEntryProviders ( )
}
}
@Test
@ -121,13 +98,7 @@ class InterestsListDetailScreenTest {
composeTestRule . apply {
setContent {
NiaTheme {
NavDisplay (
backStack = listOf < NiaNavKey > ( InterestsRoute ( ) ) ,
sceneStrategy = rememberListDetailSceneStrategy ( ) ,
entryProvider = entryProvider {
entryProviderBuilders . forEach { it ( ) }
} ,
)
TestNavDisplay ( )
}
}
onNodeWithTag ( LIST _PANE _TEST _TAG ) . assertIsDisplayed ( )
@ -135,19 +106,36 @@ class InterestsListDetailScreenTest {
}
}
@Composable
private fun TestNavDisplay ( ) {
val startKey = InterestsNavKey ( null )
val navigationState = rememberNavigationState (
startKey = startKey ,
topLevelKeys = setOf ( startKey ) ,
)
val navigator = Navigator ( navigationState )
val entryProvider = entryProvider {
interestsEntry ( navigator )
topicEntry ( navigator )
}
NavDisplay (
entries = navigationState . toEntries ( entryProvider ) ,
onBack = { navigator . goBack ( ) } ,
sceneStrategy = rememberListDetailSceneStrategy ( ) ,
)
}
@Test
@Config ( qualifiers = COMPACT _WIDTH )
fun compactWidth _initialState _showsListPane ( ) {
composeTestRule . apply {
setContent {
NiaTheme {
NavDisplay (
backStack = listOf < NiaNavKey > ( InterestsRoute ( ) ) ,
sceneStrategy = rememberListDetailSceneStrategy ( ) ,
entryProvider = entryProvider {
entryProviderBuilders . forEach { it ( ) }
} ,
)
TestNavDisplay ( )
}
}
@ -161,17 +149,8 @@ class InterestsListDetailScreenTest {
fun expandedWidth _topicSelected _updatesDetailPane ( ) {
composeTestRule . apply {
setContent {
val backStackViewModel by composeTestRule . activity . viewModels < NiaBackStackViewModel > ( )
// TODO: This is broken
val backStack = backStackViewModel . niaNavigator . backStack
NiaTheme {
NavDisplay (
backStack = backStack ,
sceneStrategy = rememberListDetailSceneStrategy ( ) ,
entryProvider = entryProvider {
entryProviderBuilders . forEach { it ( ) }
} ,
)
TestNavDisplay ( )
}
}
val firstTopic = getTopics ( ) . first ( )
@ -189,16 +168,8 @@ class InterestsListDetailScreenTest {
fun compactWidth _topicSelected _showsTopicDetailPane ( ) {
composeTestRule . apply {
setContent {
val backStackViewModel by composeTestRule . activity . viewModels < NiaBackStackViewModel > ( )
val backStack = backStackViewModel . niaNavigator . backStack
NiaTheme {
NavDisplay (
backStack = backStack ,
sceneStrategy = rememberListDetailSceneStrategy ( ) ,
entryProvider = entryProvider {
entryProviderBuilders . forEach { it ( ) }
} ,
)
TestNavDisplay ( )
}
}
@ -216,16 +187,8 @@ class InterestsListDetailScreenTest {
fun compactWidth _backPressFromTopicDetail _showsListPane ( ) {
composeTestRule . apply {
setContent {
val backStackViewModel by composeTestRule . activity . viewModels < NiaBackStackViewModel > ( )
val backStack = backStackViewModel . niaNavigator . backStack
NiaTheme {
NavDisplay (
backStack = backStack ,
sceneStrategy = rememberListDetailSceneStrategy ( ) ,
entryProvider = entryProvider {
entryProviderBuilders . forEach { it ( ) }
} ,
)
TestNavDisplay ( )
}
}
@ -246,22 +209,3 @@ private fun AndroidComposeTestRule<*, *>.stringResource(
@StringRes resId : Int ,
) : ReadOnlyProperty < Any , String > =
ReadOnlyProperty { _ , _ -> activity . getString ( resId ) }
@Module
@InstallIn ( SingletonComponent :: class )
object BackStackProvider {
@Provides
@Singleton
fun provideNiaBackStack ( ) : NiaNavigator =
NiaNavigator ( startKey = InterestsRoute ( ) )
@Provides
@Singleton
fun provideSerializersModule (
polymorphicModuleBuilders : Set < @JvmSuppressWildcards PolymorphicModuleBuilder < NiaNavKey > . ( ) -> Unit > ,
) : SerializersModule = SerializersModule {
polymorphic ( NiaNavKey :: class ) {
polymorphicModuleBuilders . forEach { it ( ) }
}
}
}