Add tests of BrowseActivity
Bug: 256582545 Test: unit-test & local build gallery Change-Id: If2c258b307250ac0e77fb7e0978eeef6fecac3bc
This commit is contained in:
@@ -19,6 +19,7 @@ package com.android.settingslib.spa.framework
|
||||
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 androidx.navigation.compose.rememberNavController
|
||||
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 @@ open class BrowseActivity : ComponentActivity() {
|
||||
|
||||
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 @@ open class BrowseActivity : ComponentActivity() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +156,15 @@ data class SettingsPage(
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -51,12 +51,3 @@ interface SettingsPageProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
|
||||
return SettingsPage.create(
|
||||
name = name,
|
||||
displayName = displayName,
|
||||
parameter = parameter,
|
||||
arguments = arguments
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import com.android.settingslib.spa.framework.common.SettingsPageProviderReposito
|
||||
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 @@ object SppLayer1 : SettingsPageProvider {
|
||||
|
||||
fun buildInject(): SettingsEntryBuilder {
|
||||
return SettingsEntryBuilder.createInject(this.createSettingsPage())
|
||||
.setMacro {
|
||||
SimplePreferenceMacro(
|
||||
title = "SppHome to Layer1",
|
||||
clickRoute = name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
|
||||
|
||||
Reference in New Issue
Block a user