@ -18,10 +18,10 @@
package com.google.samples.apps.nowinandroid.interests.impl
package com.google.samples.apps.nowinandroid.interests.impl
import androidx.activity.viewModels
import androidx.annotation.StringRes
import androidx.annotation.StringRes
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.navigation3.rememberListDetailSceneStrategy
import androidx.compose.material3.adaptive.navigation3.rememberListDetailSceneStrategy
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
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.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performClick
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.ui.NavDisplay
import androidx.navigation3.ui.NavDisplay
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
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.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.model.data.Topic
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.N avigator
import com.google.samples.apps.nowinandroid.core.navigation. NiaNavKey
import com.google.samples.apps.nowinandroid.core.navigation. rememberNavigationState
import com.google.samples.apps.nowinandroid.core.navigation. NiaNaviga tor
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.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.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 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.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import dagger.hilt.android.testing.HiltTestApplication
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
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.Before
import org.junit.Rule
import org.junit.Rule
import org.junit.Test
import org.junit.Test
@ -65,7 +56,6 @@ import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.annotation.Config
import javax.inject.Inject
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.getValue
import kotlin.getValue
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadOnlyProperty
@ -83,13 +73,6 @@ class InterestsListDetailScreenTest {
@get : Rule ( order = 1 )
@get : Rule ( order = 1 )
val composeTestRule = createAndroidComposeRule < HiltComponentActivity > ( )
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
@Inject
lateinit var topicsRepository : TopicsRepository
lateinit var topicsRepository : TopicsRepository
@ -104,15 +87,9 @@ class InterestsListDetailScreenTest {
private val Topic . testTag
private val Topic . testTag
get ( ) = " topic: ${this.id} "
get ( ) = " topic: ${this.id} "
private lateinit var entryProviderBuilders : Set < EntryProviderScope < NiaNavKey > . ( ) -> Unit >
@Before
@Before
fun setup ( ) {
fun setup ( ) {
hiltRule . inject ( )
hiltRule . inject ( )
composeTestRule . apply {
entryProviderBuilders = EntryPoints . get ( activity , EntryProvidersEntryPoint :: class . java )
. getEntryProviders ( )
}
}
}
@Test
@Test
@ -121,13 +98,7 @@ class InterestsListDetailScreenTest {
composeTestRule . apply {
composeTestRule . apply {
setContent {
setContent {
NiaTheme {
NiaTheme {
NavDisplay (
TestNavDisplay ( )
backStack = listOf < NiaNavKey > ( InterestsRoute ( ) ) ,
sceneStrategy = rememberListDetailSceneStrategy ( ) ,
entryProvider = entryProvider {
entryProviderBuilders . forEach { it ( ) }
} ,
)
}
}
}
}
onNodeWithTag ( LIST _PANE _TEST _TAG ) . assertIsDisplayed ( )
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
@Test
@Config ( qualifiers = COMPACT _WIDTH )
@Config ( qualifiers = COMPACT _WIDTH )
fun compactWidth _initialState _showsListPane ( ) {
fun compactWidth _initialState _showsListPane ( ) {
composeTestRule . apply {
composeTestRule . apply {
setContent {
setContent {
NiaTheme {
NiaTheme {
NavDisplay (
TestNavDisplay ( )
backStack = listOf < NiaNavKey > ( InterestsRoute ( ) ) ,
sceneStrategy = rememberListDetailSceneStrategy ( ) ,
entryProvider = entryProvider {
entryProviderBuilders . forEach { it ( ) }
} ,
)
}
}
}
}
@ -161,17 +149,8 @@ class InterestsListDetailScreenTest {
fun expandedWidth _topicSelected _updatesDetailPane ( ) {
fun expandedWidth _topicSelected _updatesDetailPane ( ) {
composeTestRule . apply {
composeTestRule . apply {
setContent {
setContent {
val backStackViewModel by composeTestRule . activity . viewModels < NiaBackStackViewModel > ( )
// TODO: This is broken
val backStack = backStackViewModel . niaNavigator . backStack
NiaTheme {
NiaTheme {
NavDisplay (
TestNavDisplay ( )
backStack = backStack ,
sceneStrategy = rememberListDetailSceneStrategy ( ) ,
entryProvider = entryProvider {
entryProviderBuilders . forEach { it ( ) }
} ,
)
}
}
}
}
val firstTopic = getTopics ( ) . first ( )
val firstTopic = getTopics ( ) . first ( )
@ -189,16 +168,8 @@ class InterestsListDetailScreenTest {
fun compactWidth _topicSelected _showsTopicDetailPane ( ) {
fun compactWidth _topicSelected _showsTopicDetailPane ( ) {
composeTestRule . apply {
composeTestRule . apply {
setContent {
setContent {
val backStackViewModel by composeTestRule . activity . viewModels < NiaBackStackViewModel > ( )
val backStack = backStackViewModel . niaNavigator . backStack
NiaTheme {
NiaTheme {
NavDisplay (
TestNavDisplay ( )
backStack = backStack ,
sceneStrategy = rememberListDetailSceneStrategy ( ) ,
entryProvider = entryProvider {
entryProviderBuilders . forEach { it ( ) }
} ,
)
}
}
}
}
@ -216,16 +187,8 @@ class InterestsListDetailScreenTest {
fun compactWidth _backPressFromTopicDetail _showsListPane ( ) {
fun compactWidth _backPressFromTopicDetail _showsListPane ( ) {
composeTestRule . apply {
composeTestRule . apply {
setContent {
setContent {
val backStackViewModel by composeTestRule . activity . viewModels < NiaBackStackViewModel > ( )
val backStack = backStackViewModel . niaNavigator . backStack
NiaTheme {
NiaTheme {
NavDisplay (
TestNavDisplay ( )
backStack = backStack ,
sceneStrategy = rememberListDetailSceneStrategy ( ) ,
entryProvider = entryProvider {
entryProviderBuilders . forEach { it ( ) }
} ,
)
}
}
}
}
@ -246,22 +209,3 @@ private fun AndroidComposeTestRule<*, *>.stringResource(
@StringRes resId : Int ,
@StringRes resId : Int ,
) : ReadOnlyProperty < Any , String > =
) : ReadOnlyProperty < Any , String > =
ReadOnlyProperty { _ , _ -> activity . getString ( resId ) }
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 ( ) }
}
}
}