Override WindowMetrics in InterestsListDetailScreenTest

Change-Id: Id1b0566f4c1705372699c69741dc1016ca6e169d
jdk/fake_window_metrics
Jonathan Koren 5 months ago
parent 9e4532f0eb
commit f49dda4ddc

@ -126,6 +126,7 @@ dependencies {
androidTestImplementation(libs.androidx.test.espresso.core)
androidTestImplementation(libs.androidx.navigation.testing)
androidTestImplementation(libs.androidx.compose.ui.test)
androidTestImplementation(libs.androidx.window.testing)
androidTestImplementation(libs.hilt.android.testing)
baselineProfile(projects.benchmarks)

@ -0,0 +1,82 @@
/*
* Copyright 2024 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
*
* https://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.google.samples.apps.nowinandroid.ui
import android.app.Activity
import android.content.Context
import android.graphics.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.unit.toIntSize
import androidx.window.layout.WindowMetrics
import androidx.window.layout.WindowMetricsCalculator
import androidx.window.layout.WindowMetricsCalculatorDecorator
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
/**
* A test rule which allows overriding the reported WindowMetrics with an arbitrary size, defaulting
* to [Size.Zero]. The size is not persisted and will be reset after each test.
*/
class FakeWindowMetricsCalculatorRule : TestRule {
private val calculator = FakeWindowMetricsCalculator()
fun setWindowSize(size: Size) {
calculator.windowSize = size
}
override fun apply(base: Statement?, description: Description?): Statement {
return object : Statement() {
override fun evaluate() {
WindowMetricsCalculator.overrideDecorator(
object : WindowMetricsCalculatorDecorator {
override fun decorate(calculator: WindowMetricsCalculator): WindowMetricsCalculator =
calculator
},
)
try {
base?.evaluate()
} finally {
WindowMetricsCalculator.reset()
calculator.resetWindowSize()
}
}
}
}
}
internal class FakeWindowMetricsCalculator : WindowMetricsCalculator {
var windowSize = Size.Zero
fun resetWindowSize() {
windowSize = Size.Zero
}
override fun computeCurrentWindowMetrics(context: Context) = compute()
override fun computeMaximumWindowMetrics(context: Context) = compute()
override fun computeCurrentWindowMetrics(activity: Activity) = compute()
override fun computeMaximumWindowMetrics(activity: Activity) = compute()
private fun compute(): WindowMetrics = windowSize.toIntSize().run {
WindowMetrics(Rect(0, 0, width, height))
}
}

@ -17,8 +17,8 @@
package com.google.samples.apps.nowinandroid.ui.interests2pane
import androidx.activity.compose.BackHandler
import androidx.compose.material3.adaptive.Posture
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.test.DeviceConfigurationOverride
import androidx.compose.ui.test.ForcedSize
import androidx.compose.ui.test.assertIsDisplayed
@ -30,10 +30,10 @@ import androidx.compose.ui.test.performClick
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.test.espresso.Espresso
import androidx.window.core.layout.WindowSizeClass
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.ui.FakeWindowMetricsCalculatorRule
import com.google.samples.apps.nowinandroid.ui.stringResource
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.BindValue
@ -61,6 +61,9 @@ class InterestsListDetailScreenTest {
@get:Rule(order = 2)
val composeTestRule = createAndroidComposeRule<HiltComponentActivity>()
@get:Rule(order = 3)
val windowMetricsCalculatorRule = FakeWindowMetricsCalculatorRule()
@Inject
lateinit var topicsRepository: TopicsRepository
@ -72,16 +75,9 @@ class InterestsListDetailScreenTest {
get() = "topic:${this.id}"
// Overrides for device sizes.
private enum class TestDeviceConfig(widthDp: Float, heightDp: Float) {
Compact(412f, 915f),
Expanded(1200f, 840f),
;
val sizeOverride = DeviceConfigurationOverride.ForcedSize(DpSize(widthDp.dp, heightDp.dp))
val adaptiveInfo = WindowAdaptiveInfo(
windowSizeClass = WindowSizeClass.compute(widthDp, heightDp),
windowPosture = Posture(),
)
private enum class TestDeviceConfig(val dpSize: DpSize) {
Compact(DpSize(412.dp, 915.dp)),
Expanded(DpSize(1200.dp, 840.dp)),
}
@Before
@ -94,15 +90,28 @@ class InterestsListDetailScreenTest {
topicsRepository.getTopics().first()
}
/**
* Sets up a ForcedSize override with a matching size in the window metrics calculator rule.
*/
@Composable
private fun TestDeviceConfig.Override(
content: @Composable () -> Unit
) {
DeviceConfigurationOverride(override = DeviceConfigurationOverride.ForcedSize(dpSize)) {
with(LocalDensity.current) {
windowMetricsCalculatorRule.setWindowSize(dpSize.toSize())
}
content()
}
}
@Test
fun expandedWidth_initialState_showsTwoPanesWithPlaceholder() {
composeTestRule.apply {
setContent {
with(TestDeviceConfig.Expanded) {
DeviceConfigurationOverride(override = sizeOverride) {
TestDeviceConfig.Expanded.Override {
NiaTheme {
InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo)
}
InterestsListDetailScreen()
}
}
}
@ -116,11 +125,9 @@ class InterestsListDetailScreenTest {
fun compactWidth_initialState_showsListPane() {
composeTestRule.apply {
setContent {
with(TestDeviceConfig.Compact) {
DeviceConfigurationOverride(override = sizeOverride) {
TestDeviceConfig.Compact.Override {
NiaTheme {
InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo)
}
InterestsListDetailScreen()
}
}
}
@ -134,11 +141,9 @@ class InterestsListDetailScreenTest {
fun expandedWidth_topicSelected_updatesDetailPane() {
composeTestRule.apply {
setContent {
with(TestDeviceConfig.Expanded) {
DeviceConfigurationOverride(override = sizeOverride) {
TestDeviceConfig.Expanded.Override {
NiaTheme {
InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo)
}
InterestsListDetailScreen()
}
}
}
@ -156,11 +161,9 @@ class InterestsListDetailScreenTest {
fun compactWidth_topicSelected_showsTopicDetailPane() {
composeTestRule.apply {
setContent {
with(TestDeviceConfig.Compact) {
DeviceConfigurationOverride(override = sizeOverride) {
TestDeviceConfig.Compact.Override {
NiaTheme {
InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo)
}
InterestsListDetailScreen()
}
}
}
@ -179,16 +182,14 @@ class InterestsListDetailScreenTest {
var unhandledBackPress = false
composeTestRule.apply {
setContent {
with(TestDeviceConfig.Expanded) {
DeviceConfigurationOverride(override = sizeOverride) {
TestDeviceConfig.Expanded.Override {
NiaTheme {
// Back press should not be handled by the two pane layout, and thus
// "fall through" to this BackHandler.
BackHandler {
unhandledBackPress = true
}
InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo)
}
InterestsListDetailScreen()
}
}
}
@ -206,11 +207,9 @@ class InterestsListDetailScreenTest {
fun compactWidth_backPressFromTopicDetail_showsListPane() {
composeTestRule.apply {
setContent {
with(TestDeviceConfig.Compact) {
DeviceConfigurationOverride(override = sizeOverride) {
TestDeviceConfig.Compact.Override {
NiaTheme {
InterestsListDetailScreen(windowAdaptiveInfo = adaptiveInfo)
}
InterestsListDetailScreen()
}
}
}

@ -18,14 +18,11 @@ package com.google.samples.apps.nowinandroid.ui.interests2pane
import androidx.activity.compose.BackHandler
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective
import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.Composable
@ -74,13 +71,11 @@ fun NavGraphBuilder.interestsListDetailScreen() {
@Composable
internal fun InterestsListDetailScreen(
viewModel: Interests2PaneViewModel = hiltViewModel(),
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(),
) {
val selectedTopicId by viewModel.selectedTopicId.collectAsStateWithLifecycle()
InterestsListDetailScreen(
selectedTopicId = selectedTopicId,
onTopicClick = viewModel::onTopicClick,
windowAdaptiveInfo = windowAdaptiveInfo,
)
}
@ -89,10 +84,8 @@ internal fun InterestsListDetailScreen(
internal fun InterestsListDetailScreen(
selectedTopicId: String?,
onTopicClick: (String) -> Unit,
windowAdaptiveInfo: WindowAdaptiveInfo,
) {
val listDetailNavigator = rememberListDetailPaneScaffoldNavigator(
scaffoldDirective = calculatePaneScaffoldDirective(windowAdaptiveInfo),
initialDestinationHistory = listOfNotNull(
ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
ThreePaneScaffoldDestinationItem<Nothing>(ListDetailPaneScaffoldRole.Detail).takeIf {

@ -107,6 +107,7 @@ androidx-test-runner = { group = "androidx.test", name = "runner", version.ref =
androidx-test-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "androidxUiAutomator" }
androidx-tracing-ktx = { group = "androidx.tracing", name = "tracing-ktx", version.ref = "androidxTracing" }
androidx-window-core = { group = "androidx.window", name = "window-core", version.ref = "androidxWindowManager" }
androidx-window-testing = { group = "androidx.window", name = "window-testing", version.ref = "androidxWindowManager" }
androidx-work-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "androidxWork" }
androidx-work-testing = { group = "androidx.work", name = "work-testing", version.ref = "androidxWork" }
coil-kt = { group = "io.coil-kt", name = "coil", version.ref = "coil" }

Loading…
Cancel
Save