Merge pull request #466 from android/av/test-harness

Add accompanist/testharness for checking navigation UI
remotes/c5inco/ui-polish
Alex Vanyo 2 years ago committed by GitHub
commit 5cd31c7a96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -98,8 +98,10 @@ dependencies {
androidTestImplementation(project(":core:data-test")) androidTestImplementation(project(":core:data-test"))
androidTestImplementation(project(":core:network")) androidTestImplementation(project(":core:network"))
androidTestImplementation(libs.androidx.navigation.testing) androidTestImplementation(libs.androidx.navigation.testing)
androidTestImplementation(libs.accompanist.testharness)
androidTestImplementation(kotlin("test")) androidTestImplementation(kotlin("test"))
debugImplementation(libs.androidx.compose.ui.testManifest) debugImplementation(libs.androidx.compose.ui.testManifest)
debugImplementation(project(":ui-test-hilt-manifest"))
implementation(libs.accompanist.systemuicontroller) implementation(libs.accompanist.systemuicontroller)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)

@ -0,0 +1,243 @@
/*
* Copyright 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
*
* 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 androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import com.google.accompanist.testharness.TestHarness
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import javax.inject.Inject
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
/**
* Tests that the navigation UI is rendered correctly on different screen sizes.
*/
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
@HiltAndroidTest
class NavigationUiTest {
/**
* Manages the components' state and is used to perform injection on your test
*/
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)
/**
* Create a temporary folder used to create a Data Store file. This guarantees that
* the file is removed in between each test, preventing a crash.
*/
@BindValue @get:Rule(order = 1)
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
/**
* Use a test activity to set the content on.
*/
@get:Rule(order = 2)
val composeTestRule = createAndroidComposeRule<HiltComponentActivity>()
@Inject
lateinit var networkMonitor: NetworkMonitor
@Before
fun setup() {
hiltRule.inject()
}
@Test
fun compactWidth_compactHeight_showsNavigationBar() {
composeTestRule.setContent {
TestHarness(size = DpSize(400.dp, 400.dp)) {
BoxWithConstraints {
NiaApp(
windowSizeClass = WindowSizeClass.calculateFromSize(
DpSize(maxWidth, maxHeight)
),
networkMonitor = networkMonitor
)
}
}
}
composeTestRule.onNodeWithTag("NiaBottomBar").assertIsDisplayed()
composeTestRule.onNodeWithTag("NiaNavRail").assertDoesNotExist()
}
@Test
fun mediumWidth_compactHeight_showsNavigationBar() {
composeTestRule.setContent {
TestHarness(size = DpSize(610.dp, 400.dp)) {
BoxWithConstraints {
NiaApp(
windowSizeClass = WindowSizeClass.calculateFromSize(
DpSize(maxWidth, maxHeight)
),
networkMonitor = networkMonitor
)
}
}
}
composeTestRule.onNodeWithTag("NiaBottomBar").assertIsDisplayed()
composeTestRule.onNodeWithTag("NiaNavRail").assertDoesNotExist()
}
@Test
fun expandedWidth_compactHeight_showsNavigationBar() {
composeTestRule.setContent {
TestHarness(size = DpSize(900.dp, 400.dp)) {
BoxWithConstraints {
NiaApp(
windowSizeClass = WindowSizeClass.calculateFromSize(
DpSize(maxWidth, maxHeight)
),
networkMonitor = networkMonitor
)
}
}
}
composeTestRule.onNodeWithTag("NiaBottomBar").assertIsDisplayed()
composeTestRule.onNodeWithTag("NiaNavRail").assertDoesNotExist()
}
@Test
fun compcatWidth_mediumHeight_showsNavigationBar() {
composeTestRule.setContent {
TestHarness(size = DpSize(400.dp, 500.dp)) {
BoxWithConstraints {
NiaApp(
windowSizeClass = WindowSizeClass.calculateFromSize(
DpSize(maxWidth, maxHeight)
),
networkMonitor = networkMonitor
)
}
}
}
composeTestRule.onNodeWithTag("NiaBottomBar").assertIsDisplayed()
composeTestRule.onNodeWithTag("NiaNavRail").assertDoesNotExist()
}
@Test
fun mediumWidth_mediumHeight_showsNavigationRail() {
composeTestRule.setContent {
TestHarness(size = DpSize(610.dp, 500.dp)) {
BoxWithConstraints {
NiaApp(
windowSizeClass = WindowSizeClass.calculateFromSize(
DpSize(maxWidth, maxHeight)
),
networkMonitor = networkMonitor
)
}
}
}
composeTestRule.onNodeWithTag("NiaNavRail").assertIsDisplayed()
composeTestRule.onNodeWithTag("NiaBottomBar").assertDoesNotExist()
}
@Test
fun expandedWidth_mediumHeight_showsNavigationRail() {
composeTestRule.setContent {
TestHarness(size = DpSize(900.dp, 500.dp)) {
BoxWithConstraints {
NiaApp(
windowSizeClass = WindowSizeClass.calculateFromSize(
DpSize(maxWidth, maxHeight)
),
networkMonitor = networkMonitor
)
}
}
}
composeTestRule.onNodeWithTag("NiaNavRail").assertIsDisplayed()
composeTestRule.onNodeWithTag("NiaBottomBar").assertDoesNotExist()
}
@Test
fun compactWidth_expandedHeight_showsNavigationBar() {
composeTestRule.setContent {
TestHarness(size = DpSize(400.dp, 1000.dp)) {
BoxWithConstraints {
NiaApp(
windowSizeClass = WindowSizeClass.calculateFromSize(
DpSize(maxWidth, maxHeight)
),
networkMonitor = networkMonitor
)
}
}
}
composeTestRule.onNodeWithTag("NiaBottomBar").assertIsDisplayed()
composeTestRule.onNodeWithTag("NiaNavRail").assertDoesNotExist()
}
@Test
fun mediumWidth_expandedHeight_showsNavigationRail() {
composeTestRule.setContent {
TestHarness(size = DpSize(610.dp, 1000.dp)) {
BoxWithConstraints {
NiaApp(
windowSizeClass = WindowSizeClass.calculateFromSize(
DpSize(maxWidth, maxHeight)
),
networkMonitor = networkMonitor
)
}
}
}
composeTestRule.onNodeWithTag("NiaNavRail").assertIsDisplayed()
composeTestRule.onNodeWithTag("NiaBottomBar").assertDoesNotExist()
}
@Test
fun expandedWidth_expandedHeight_showsNavigationRail() {
composeTestRule.setContent {
TestHarness(size = DpSize(900.dp, 1000.dp)) {
BoxWithConstraints {
NiaApp(
windowSizeClass = WindowSizeClass.calculateFromSize(
DpSize(maxWidth, maxHeight)
),
networkMonitor = networkMonitor
)
}
}
}
composeTestRule.onNodeWithTag("NiaNavRail").assertIsDisplayed()
composeTestRule.onNodeWithTag("NiaBottomBar").assertDoesNotExist()
}
}

@ -44,6 +44,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
@ -133,7 +134,8 @@ fun NiaApp(
NiaBottomBar( NiaBottomBar(
destinations = appState.topLevelDestinations, destinations = appState.topLevelDestinations,
onNavigateToDestination = appState::navigateToTopLevelDestination, onNavigateToDestination = appState::navigateToTopLevelDestination,
currentDestination = appState.currentDestination currentDestination = appState.currentDestination,
modifier = Modifier.testTag("NiaBottomBar")
) )
} }
} }
@ -170,7 +172,9 @@ fun NiaApp(
destinations = appState.topLevelDestinations, destinations = appState.topLevelDestinations,
onNavigateToDestination = appState::navigateToTopLevelDestination, onNavigateToDestination = appState::navigateToTopLevelDestination,
currentDestination = appState.currentDestination, currentDestination = appState.currentDestination,
modifier = Modifier.safeDrawingPadding() modifier = Modifier
.testTag("NiaNavRail")
.safeDrawingPadding()
) )
} }
@ -230,9 +234,12 @@ private fun NiaNavRail(
private fun NiaBottomBar( private fun NiaBottomBar(
destinations: List<TopLevelDestination>, destinations: List<TopLevelDestination>,
onNavigateToDestination: (TopLevelDestination) -> Unit, onNavigateToDestination: (TopLevelDestination) -> Unit,
currentDestination: NavDestination? currentDestination: NavDestination?,
modifier: Modifier = Modifier
) { ) {
NiaNavigationBar { NiaNavigationBar(
modifier = modifier
) {
destinations.forEach { destination -> destinations.forEach { destination ->
val selected = currentDestination.isTopLevelDestinationInHierarchy(destination) val selected = currentDestination.isTopLevelDestinationInHierarchy(destination)
NiaNavigationBarItem( NiaNavigationBarItem(

@ -1,5 +1,5 @@
[versions] [versions]
accompanist = "0.27.0" accompanist = "0.28.0"
androidDesugarJdkLibs = "1.2.0" androidDesugarJdkLibs = "1.2.0"
androidGradlePlugin = "7.3.1" androidGradlePlugin = "7.3.1"
androidxActivity = "1.6.1" androidxActivity = "1.6.1"
@ -49,6 +49,7 @@ turbine = "0.12.1"
[libraries] [libraries]
accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist-flowlayout", version.ref = "accompanist" } accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist-flowlayout", version.ref = "accompanist" }
accompanist-systemuicontroller = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "accompanist" } accompanist-systemuicontroller = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "accompanist" }
accompanist-testharness = { group = "com.google.accompanist", name = "accompanist-testharness", version.ref = "accompanist" }
android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" } android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" }

@ -55,3 +55,4 @@ include(":feature:settings")
include(":lint") include(":lint")
include(":sync:work") include(":sync:work")
include(":sync:sync-test") include(":sync:sync-test")
include(":ui-test-hilt-manifest")

@ -0,0 +1,25 @@
/*
* Copyright 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
*
* 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.
*/
plugins {
id("nowinandroid.android.library")
kotlin("kapt")
id("dagger.hilt.android.plugin")
}
dependencies {
implementation(libs.hilt.android)
kapt(libs.hilt.compiler)
}

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.samples.apps.nowinandroid.uitesthiltmanifest">
<application>
<activity android:name=".HiltComponentActivity" />
</application>
</manifest>

@ -0,0 +1,27 @@
/*
* Copyright 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
*
* 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.uitesthiltmanifest
import androidx.activity.ComponentActivity
import dagger.hilt.android.AndroidEntryPoint
/**
* A [ComponentActivity] annotated with [AndroidEntryPoint] for use in tests, as a workaround
* for https://github.com/google/dagger/issues/3394
*/
@AndroidEntryPoint
class HiltComponentActivity : ComponentActivity()
Loading…
Cancel
Save