Add tests of BrowseActivity
Bug: 256582545
Test: unit-test & local build gallery
Change-Id: If2c258b307250ac0e77fb7e0978eeef6fecac3bc
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index c3c90ab..6ed7481 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -19,6 +19,7 @@
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
@@ -37,6 +38,7 @@
import com.android.settingslib.spa.R
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.LocalNavController
@@ -74,80 +76,13 @@
setContent {
SettingsTheme {
- MainContent()
- }
- }
- }
-
- @Composable
- private fun MainContent() {
- val sppRepository by spaEnvironment.pageProviderRepository
- val navController = rememberNavController()
- val nullPage = SettingsPage.createNull()
- CompositionLocalProvider(navController.localNavController()) {
- NavHost(
- navController = navController,
- startDestination = nullPage.sppName,
- ) {
- composable(nullPage.sppName) {}
- for (spp in sppRepository.getAllProviders()) {
- composable(
- route = spp.name + spp.parameter.navRoute(),
- arguments = spp.parameter,
- ) { navBackStackEntry ->
- PageLogger(remember(navBackStackEntry.arguments) {
- spp.createSettingsPage(arguments = navBackStackEntry.arguments)
- })
-
- spp.Page(navBackStackEntry.arguments)
- }
- }
- }
- InitialDestinationNavigator()
- }
- }
-
- @Composable
- private fun PageLogger(settingsPage: SettingsPage) {
- val lifecycleOwner = LocalLifecycleOwner.current
- DisposableEffect(lifecycleOwner) {
- val observer = LifecycleEventObserver { _, event ->
- if (event == Lifecycle.Event.ON_START) {
- settingsPage.enterPage()
- } else if (event == Lifecycle.Event.ON_STOP) {
- settingsPage.leavePage()
- }
- }
-
- // Add the observer to the lifecycle
- lifecycleOwner.lifecycle.addObserver(observer)
-
- // When the effect leaves the Composition, remove the observer
- onDispose {
- lifecycleOwner.lifecycle.removeObserver(observer)
- }
- }
- }
-
- @Composable
- private fun InitialDestinationNavigator() {
- val sppRepository by spaEnvironment.pageProviderRepository
- val destinationNavigated = rememberSaveable { mutableStateOf(false) }
- if (destinationNavigated.value) return
- destinationNavigated.value = true
- val controller = LocalNavController.current as NavControllerWrapperImpl
- LaunchedEffect(Unit) {
- val destination =
- intent?.getStringExtra(KEY_DESTINATION) ?: sppRepository.getDefaultStartPage()
- val highlightEntryId = intent?.getStringExtra(KEY_HIGHLIGHT_ENTRY)
- if (destination.isNotEmpty()) {
- controller.highlightId = highlightEntryId
- val navController = controller.navController
- navController.navigate(destination) {
- popUpTo(navController.graph.findStartDestination().id) {
- inclusive = true
- }
- }
+ val sppRepository by spaEnvironment.pageProviderRepository
+ BrowseContent(
+ allProviders = sppRepository.getAllProviders(),
+ initialDestination = intent?.getStringExtra(KEY_DESTINATION)
+ ?: sppRepository.getDefaultStartPage(),
+ initialEntryId = intent?.getStringExtra(KEY_HIGHLIGHT_ENTRY)
+ )
}
}
}
@@ -157,3 +92,81 @@
const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
}
}
+
+@VisibleForTesting
+@Composable
+fun BrowseContent(
+ allProviders: Collection<SettingsPageProvider>,
+ initialDestination: String,
+ initialEntryId: String?
+) {
+ val navController = rememberNavController()
+ CompositionLocalProvider(navController.localNavController()) {
+ val controller = LocalNavController.current as NavControllerWrapperImpl
+ controller.NavContent(allProviders)
+ controller.InitialDestination(initialDestination, initialEntryId)
+ }
+}
+
+@Composable
+private fun SettingsPageProvider.PageEvents(arguments: Bundle? = null) {
+ val page = remember(arguments) { createSettingsPage(arguments) }
+ val lifecycleOwner = LocalLifecycleOwner.current
+ DisposableEffect(lifecycleOwner) {
+ val observer = LifecycleEventObserver { _, event ->
+ if (event == Lifecycle.Event.ON_START) {
+ page.enterPage()
+ } else if (event == Lifecycle.Event.ON_STOP) {
+ page.leavePage()
+ }
+ }
+
+ // Add the observer to the lifecycle
+ lifecycleOwner.lifecycle.addObserver(observer)
+
+ // When the effect leaves the Composition, remove the observer
+ onDispose {
+ lifecycleOwner.lifecycle.removeObserver(observer)
+ }
+ }
+}
+
+@Composable
+private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<SettingsPageProvider>) {
+ val nullPage = SettingsPage.createNull()
+ NavHost(
+ navController = navController,
+ startDestination = nullPage.sppName,
+ ) {
+ composable(nullPage.sppName) {}
+ for (spp in allProvider) {
+ composable(
+ route = spp.name + spp.parameter.navRoute(),
+ arguments = spp.parameter,
+ ) { navBackStackEntry ->
+ spp.PageEvents(navBackStackEntry.arguments)
+ spp.Page(navBackStackEntry.arguments)
+ }
+ }
+ }
+}
+
+@Composable
+private fun NavControllerWrapperImpl.InitialDestination(
+ destination: String,
+ highlightEntryId: String?
+) {
+ val destinationNavigated = rememberSaveable { mutableStateOf(false) }
+ if (destinationNavigated.value) return
+ destinationNavigated.value = true
+
+ if (destination.isEmpty()) return
+ LaunchedEffect(Unit) {
+ highlightId = highlightEntryId
+ navController.navigate(destination) {
+ popUpTo(navController.graph.findStartDestination().id) {
+ inclusive = true
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index a372bbd..82e05a5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -156,6 +156,15 @@
}
}
+fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
+ return SettingsPage.create(
+ name = name,
+ displayName = displayName,
+ parameter = parameter,
+ arguments = arguments
+ )
+}
+
fun String.toHashId(): String {
return this.hashCode().toUInt().toString(36)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 60599d4..940005d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -51,12 +51,3 @@
}
}
}
-
-fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
- return SettingsPage.create(
- name = name,
- displayName = displayName,
- parameter = parameter,
- arguments = arguments
- )
-}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
new file mode 100644
index 0000000..bde3bba
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework
+
+import android.content.Context
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.LogEvent
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
+import com.android.settingslib.spa.testutils.waitUntil
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val WAIT_UNTIL_TIMEOUT = 1000L
+
+@RunWith(AndroidJUnit4::class)
+class BrowseActivityTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val spaLogger = SpaLoggerForTest()
+ private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger)
+
+ @Test
+ fun testBrowsePage() {
+ spaLogger.reset()
+ SpaEnvironmentFactory.reset(spaEnvironment)
+
+ val sppRepository by spaEnvironment.pageProviderRepository
+ val sppHome = sppRepository.getProviderOrNull("SppHome")!!
+ val pageHome = sppHome.createSettingsPage()
+ val sppLayer1 = sppRepository.getProviderOrNull("SppLayer1")!!
+ val pageLayer1 = sppLayer1.createSettingsPage()
+
+ composeTestRule.setContent {
+ BrowseContent(
+ allProviders = listOf(sppHome, sppLayer1),
+ initialDestination = pageHome.buildRoute(),
+ initialEntryId = null
+ )
+ }
+
+ composeTestRule.onNodeWithText(sppHome.getTitle(null)).assertIsDisplayed()
+ spaLogger.verifyPageEvent(pageHome.id, 1, 0)
+ spaLogger.verifyPageEvent(pageLayer1.id, 0, 0)
+
+ // click to layer1 page
+ composeTestRule.onNodeWithText("SppHome to Layer1").assertIsDisplayed().performClick()
+ waitUntil(WAIT_UNTIL_TIMEOUT) {
+ composeTestRule.onAllNodesWithText(sppLayer1.getTitle(null))
+ .fetchSemanticsNodes().size == 1
+ }
+ spaLogger.verifyPageEvent(pageHome.id, 1, 1)
+ spaLogger.verifyPageEvent(pageLayer1.id, 1, 0)
+ }
+}
+
+private fun SpaLoggerForTest.verifyPageEvent(id: String, entryCount: Int, leaveCount: Int) {
+ Truth.assertThat(getEventCount(id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
+ .isEqualTo(entryCount)
+ Truth.assertThat(getEventCount(id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
+ .isEqualTo(leaveCount)
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index a404dd3..ab269f2 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -35,6 +35,7 @@
import com.android.settingslib.spa.framework.common.SpaEnvironment
import com.android.settingslib.spa.framework.common.SpaLogger
import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
class SpaLoggerForTest : SpaLogger {
data class MsgCountKey(val msg: String, val category: LogCategory)
@@ -98,6 +99,12 @@
fun buildInject(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(this.createSettingsPage())
+ .setMacro {
+ SimplePreferenceMacro(
+ title = "SppHome to Layer1",
+ clickRoute = name
+ )
+ }
}
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {