Skip to content

Commit 1a70c20

Browse files
committed
Break navigation tests into multiple dedicated files
1 parent aba0268 commit 1a70c20

File tree

12 files changed

+1031
-12
lines changed

12 files changed

+1031
-12
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package io.homeassistant.companion.android.onboarding
2+
3+
import android.content.pm.PackageManager
4+
import androidx.activity.compose.LocalActivityResultRegistryOwner
5+
import androidx.activity.result.ActivityResultRegistry
6+
import androidx.activity.result.ActivityResultRegistryOwner
7+
import androidx.compose.runtime.CompositionLocalProvider
8+
import androidx.compose.ui.platform.LocalContext
9+
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
10+
import androidx.compose.ui.test.junit4.createAndroidComposeRule
11+
import androidx.core.content.ContextCompat
12+
import androidx.navigation.NavController
13+
import androidx.navigation.compose.ComposeNavigator
14+
import androidx.navigation.compose.NavHost
15+
import androidx.navigation.testing.TestNavHostController
16+
import dagger.hilt.android.testing.HiltAndroidRule
17+
import io.homeassistant.companion.android.HiltComponentActivity
18+
import io.homeassistant.companion.android.testing.unit.ConsoleLogRule
19+
import io.homeassistant.companion.android.util.LocationPermissionActivityResultRegistry
20+
import io.homeassistant.companion.android.util.compose.navigateToUri
21+
import io.mockk.Runs
22+
import io.mockk.every
23+
import io.mockk.just
24+
import io.mockk.mockkStatic
25+
import kotlinx.coroutines.test.runTest
26+
import org.junit.Before
27+
import org.junit.Rule
28+
29+
/**
30+
* Base class for onboarding navigation tests providing shared setup, mocks, and utilities.
31+
*
32+
* Subclasses should extend this class and use [testNavigation] to set up the navigation
33+
* test environment with the onboarding flow.
34+
*/
35+
internal abstract class BaseOnboardingNavigationTest {
36+
37+
@get:Rule(order = 0)
38+
var consoleLog = ConsoleLogRule()
39+
40+
@get:Rule(order = 1)
41+
val hiltRule = HiltAndroidRule(this)
42+
43+
@get:Rule(order = 2)
44+
val composeTestRule = createAndroidComposeRule<HiltComponentActivity>()
45+
46+
protected lateinit var navController: TestNavHostController
47+
48+
protected var onboardingDone = false
49+
50+
@Before
51+
fun baseSetup() {
52+
mockkStatic(NavController::navigateToUri)
53+
every { any<NavController>().navigateToUri(any()) } just Runs
54+
}
55+
56+
protected fun setContent(
57+
urlToOnboard: String? = null,
58+
hideExistingServers: Boolean = false,
59+
skipWelcome: Boolean = false,
60+
hasLocationTracking: Boolean = true,
61+
) {
62+
composeTestRule.setContent {
63+
navController = TestNavHostController(LocalContext.current)
64+
navController.navigatorProvider.addNavigator(ComposeNavigator())
65+
66+
CompositionLocalProvider(
67+
LocalActivityResultRegistryOwner provides object : ActivityResultRegistryOwner {
68+
override val activityResultRegistry: ActivityResultRegistry =
69+
LocationPermissionActivityResultRegistry(true)
70+
},
71+
) {
72+
NavHost(
73+
navController = navController,
74+
startDestination = OnboardingRoute(hasLocationTracking = true),
75+
) {
76+
onboarding(
77+
navController,
78+
onShowSnackbar = { _, _ -> true },
79+
onOnboardingDone = {
80+
onboardingDone = true
81+
},
82+
urlToOnboard = urlToOnboard,
83+
hideExistingServers = hideExistingServers,
84+
skipWelcome = skipWelcome,
85+
hasLocationTracking = hasLocationTracking,
86+
)
87+
}
88+
}
89+
}
90+
}
91+
92+
protected fun testNavigation(
93+
urlToOnboard: String? = null,
94+
hideExistingServers: Boolean = false,
95+
skipWelcome: Boolean = false,
96+
hasLocationTracking: Boolean = true,
97+
testContent: suspend AndroidComposeTestRule<*, *>.() -> Unit,
98+
) {
99+
setContent(
100+
urlToOnboard = urlToOnboard,
101+
hideExistingServers = hideExistingServers,
102+
skipWelcome = skipWelcome,
103+
hasLocationTracking = hasLocationTracking,
104+
)
105+
runTest {
106+
composeTestRule.testContent()
107+
}
108+
}
109+
110+
protected fun mockCheckPermission(grant: Boolean) {
111+
mockkStatic(ContextCompat::class)
112+
every {
113+
ContextCompat.checkSelfPermission(any(), any())
114+
} returns if (grant) PackageManager.PERMISSION_GRANTED else PackageManager.PERMISSION_DENIED
115+
}
116+
}

app/src/test/kotlin/io/homeassistant/companion/android/onboarding/WearOnboardingNavigationTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ import kotlinx.coroutines.test.runTest
7373
import org.junit.Before
7474
import org.junit.Rule
7575
import org.junit.Test
76-
import org.junit.jupiter.api.Assertions
7776
import org.junit.jupiter.api.Assertions.assertEquals
7877
import org.junit.jupiter.api.Assertions.assertFalse
7978
import org.junit.runner.RunWith
@@ -201,7 +200,7 @@ internal class WearOnboardingNavigationTest {
201200
@Test
202201
fun `Given server onboard when starting the navigation then opens ConnectionScreen`() {
203202
testNavigation("http://ha") {
204-
Assertions.assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<ConnectionRoute>() == true)
203+
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<ConnectionRoute>() == true)
205204
onNodeWithTag(HA_WEBVIEW_TAG).assertIsDisplayed()
206205
}
207206
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package io.homeassistant.companion.android.onboarding.connection.navigation
2+
3+
import androidx.compose.ui.test.assertIsDisplayed
4+
import androidx.compose.ui.test.assertIsNotDisplayed
5+
import androidx.compose.ui.test.onNodeWithContentDescription
6+
import androidx.compose.ui.test.onNodeWithTag
7+
import androidx.compose.ui.test.onNodeWithText
8+
import androidx.compose.ui.test.performClick
9+
import androidx.navigation.NavDestination.Companion.hasRoute
10+
import androidx.navigation.toRoute
11+
import dagger.hilt.android.testing.HiltAndroidTest
12+
import dagger.hilt.android.testing.HiltTestApplication
13+
import io.homeassistant.companion.android.common.R as commonR
14+
import io.homeassistant.companion.android.onboarding.BaseOnboardingNavigationTest
15+
import io.homeassistant.companion.android.onboarding.connection.CONNECTION_SCREEN_TAG
16+
import io.homeassistant.companion.android.onboarding.welcome.navigation.WelcomeRoute
17+
import io.homeassistant.companion.android.testing.unit.stringResource
18+
import junit.framework.TestCase.assertTrue
19+
import org.junit.Test
20+
import org.junit.jupiter.api.Assertions.assertEquals
21+
import org.junit.runner.RunWith
22+
import org.robolectric.RobolectricTestRunner
23+
import org.robolectric.annotation.Config
24+
25+
/**
26+
* Navigation tests for the Connection screen in the onboarding flow.
27+
*/
28+
@RunWith(RobolectricTestRunner::class)
29+
@Config(application = HiltTestApplication::class)
30+
@HiltAndroidTest
31+
internal class ConnectionNavigationTest : BaseOnboardingNavigationTest() {
32+
33+
@Test
34+
fun `Given skipWelcome and urlToOnboard when starting then show Connection screen and no back arrow`() {
35+
val url = "http://ha.org"
36+
testNavigation(skipWelcome = true, urlToOnboard = url) {
37+
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<ConnectionRoute>() == true)
38+
assertEquals(url, navController.currentBackStackEntry?.toRoute<ConnectionRoute>()?.url)
39+
40+
onNodeWithContentDescription(stringResource(commonR.string.navigate_up)).assertIsNotDisplayed()
41+
}
42+
}
43+
44+
@Test
45+
fun `Given clicking on connect button with server to onboard when starting the onboarding then show Connection screen then back goes to Welcome`() {
46+
testNavigation("http://homeassistant.local") {
47+
onNodeWithText(stringResource(commonR.string.welcome_connect_to_ha))
48+
.assertIsDisplayed()
49+
.performClick()
50+
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<ConnectionRoute>() == true)
51+
52+
onNodeWithTag(CONNECTION_SCREEN_TAG).assertIsDisplayed()
53+
54+
composeTestRule.activity.onBackPressedDispatcher.onBackPressed()
55+
56+
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<WelcomeRoute>() == true)
57+
}
58+
}
59+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package io.homeassistant.companion.android.onboarding.localfirst.navigation
2+
3+
import androidx.compose.ui.test.onNodeWithText
4+
import androidx.compose.ui.test.performClick
5+
import androidx.compose.ui.test.performScrollTo
6+
import androidx.navigation.NavDestination.Companion.hasRoute
7+
import dagger.hilt.android.testing.HiltAndroidTest
8+
import dagger.hilt.android.testing.HiltTestApplication
9+
import io.homeassistant.companion.android.common.R as commonR
10+
import io.homeassistant.companion.android.onboarding.BaseOnboardingNavigationTest
11+
import io.homeassistant.companion.android.onboarding.locationforsecureconnection.navigation.LocationForSecureConnectionRoute
12+
import io.homeassistant.companion.android.onboarding.locationsharing.navigation.LocationSharingRoute
13+
import io.homeassistant.companion.android.onboarding.sethomenetwork.navigation.SetHomeNetworkRoute
14+
import io.homeassistant.companion.android.onboarding.welcome.navigation.WelcomeRoute
15+
import io.homeassistant.companion.android.testing.unit.stringResource
16+
import junit.framework.TestCase.assertTrue
17+
import org.junit.Test
18+
import org.junit.runner.RunWith
19+
import org.robolectric.RobolectricTestRunner
20+
import org.robolectric.annotation.Config
21+
22+
/**
23+
* Navigation tests for the Local First screen in the onboarding flow.
24+
*/
25+
@RunWith(RobolectricTestRunner::class)
26+
@Config(application = HiltTestApplication::class)
27+
@HiltAndroidTest
28+
internal class LocalFirstNavigationTest : BaseOnboardingNavigationTest() {
29+
30+
@Test
31+
fun `Given LocalFirst when pressing next then show LocationSharing then goes back stop the app`() {
32+
testNavigation {
33+
navController.navigateToLocalFirst(42, true)
34+
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<LocalFirstRoute>() == true)
35+
onNodeWithText(stringResource(commonR.string.local_first_next))
36+
.performScrollTo()
37+
.performClick()
38+
39+
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<LocationSharingRoute>() == true)
40+
41+
composeTestRule.activity.onBackPressedDispatcher.onBackPressed()
42+
43+
// In the test scenario since we never opened NameYourDevice the stack still contains Welcome
44+
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<WelcomeRoute>() == true)
45+
}
46+
}
47+
48+
@Test
49+
fun `Given no location tracking from LocalFirst with HTTP and no permission when next clicked then show LocationForSecureConnection`() {
50+
testNavigation(hasLocationTracking = false) {
51+
mockCheckPermission(false)
52+
navController.navigateToLocalFirst(serverId = 42, hasPlainTextAccess = true)
53+
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<LocalFirstRoute>() == true)
54+
55+
onNodeWithText(stringResource(commonR.string.local_first_next))
56+
.performScrollTo()
57+
.performClick()
58+
59+
assertTrue(
60+
navController.currentBackStackEntry?.destination?.hasRoute<LocationForSecureConnectionRoute>() == true,
61+
)
62+
}
63+
}
64+
65+
@Test
66+
fun `Given no location tracking from LocalFirst with HTTP and has permission when next clicked then show SetHomeNetwork`() {
67+
testNavigation(hasLocationTracking = false) {
68+
mockCheckPermission(true)
69+
navController.navigateToLocalFirst(serverId = 42, hasPlainTextAccess = true)
70+
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<LocalFirstRoute>() == true)
71+
72+
onNodeWithText(stringResource(commonR.string.local_first_next))
73+
.performScrollTo()
74+
.performClick()
75+
76+
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<SetHomeNetworkRoute>() == true)
77+
}
78+
}
79+
80+
@Test
81+
fun `Given no location tracking from LocalFirst with HTTPS when next clicked then onboarding completes`() {
82+
testNavigation(hasLocationTracking = false) {
83+
navController.navigateToLocalFirst(serverId = 42, hasPlainTextAccess = false)
84+
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<LocalFirstRoute>() == true)
85+
86+
onNodeWithText(stringResource(commonR.string.local_first_next))
87+
.performScrollTo()
88+
.performClick()
89+
90+
assertTrue(onboardingDone)
91+
}
92+
}
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package io.homeassistant.companion.android.onboarding.locationforsecureconnection.navigation
2+
3+
import androidx.compose.ui.test.onNodeWithText
4+
import androidx.compose.ui.test.performClick
5+
import androidx.compose.ui.test.performScrollTo
6+
import androidx.navigation.NavDestination.Companion.hasRoute
7+
import dagger.hilt.android.testing.HiltAndroidTest
8+
import dagger.hilt.android.testing.HiltTestApplication
9+
import io.homeassistant.companion.android.common.R as commonR
10+
import io.homeassistant.companion.android.onboarding.BaseOnboardingNavigationTest
11+
import io.homeassistant.companion.android.onboarding.sethomenetwork.navigation.SetHomeNetworkRoute
12+
import io.homeassistant.companion.android.testing.unit.stringResource
13+
import junit.framework.TestCase.assertTrue
14+
import org.junit.Test
15+
import org.junit.runner.RunWith
16+
import org.robolectric.RobolectricTestRunner
17+
import org.robolectric.annotation.Config
18+
19+
/**
20+
* Navigation tests for the Location For Secure Connection screen in the onboarding flow.
21+
*/
22+
@RunWith(RobolectricTestRunner::class)
23+
@Config(application = HiltTestApplication::class)
24+
@HiltAndroidTest
25+
internal class LocationForSecureConnectionNavigationTest : BaseOnboardingNavigationTest() {
26+
27+
@Test
28+
fun `Given LocationForSecureConnection when agreeing to share then show SetHomeNetwork`() {
29+
testNavigation {
30+
navController.navigateToLocationForSecureConnection(42)
31+
assertTrue(
32+
navController.currentBackStackEntry?.destination?.hasRoute<LocationForSecureConnectionRoute>() == true,
33+
)
34+
35+
onNodeWithText(stringResource(commonR.string.connection_security_most_secure))
36+
.performScrollTo()
37+
.performClick()
38+
onNodeWithText(stringResource(commonR.string.location_secure_connection_next))
39+
.performScrollTo()
40+
.performClick()
41+
42+
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<SetHomeNetworkRoute>() == true)
43+
}
44+
}
45+
46+
@Test
47+
fun `Given LocationForSecureConnection when choosing less secure option then onboarding completes`() {
48+
testNavigation {
49+
navController.navigateToLocationForSecureConnection(42)
50+
assertTrue(
51+
navController.currentBackStackEntry?.destination?.hasRoute<LocationForSecureConnectionRoute>() == true,
52+
)
53+
54+
onNodeWithText(stringResource(commonR.string.connection_security_less_secure))
55+
.performScrollTo()
56+
.performClick()
57+
onNodeWithText(stringResource(commonR.string.location_secure_connection_next))
58+
.performScrollTo()
59+
.performClick()
60+
61+
assertTrue(onboardingDone)
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)