diff --git a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt index 52c337521..e2c1175e7 100644 --- a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt @@ -41,6 +41,8 @@ class AndroidFeatureConventionPlugin : Plugin { dependencies { add("implementation", project(":core:ui")) add("implementation", project(":core:designsystem")) + add("testImplementation", project(":core:testing")) + add("androidTestImplementation", project(":core:testing")) add("implementation", libs.findLibrary("androidx.hilt.navigation.compose").get()) add("implementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get()) diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts index bbb4ab97b..59cb45898 100644 --- a/core/designsystem/build.gradle.kts +++ b/core/designsystem/build.gradle.kts @@ -45,8 +45,6 @@ dependencies { testImplementation(libs.androidx.compose.ui.testManifest) testImplementation(libs.hilt.android.testing) - testImplementation(libs.robolectric) - testImplementation(libs.roborazzi) testImplementation(projects.core.screenshotTesting) testImplementation(projects.core.testing) diff --git a/core/screenshot-testing/build.gradle.kts b/core/screenshot-testing/build.gradle.kts index fb23cf057..49cb2507d 100644 --- a/core/screenshot-testing/build.gradle.kts +++ b/core/screenshot-testing/build.gradle.kts @@ -25,10 +25,9 @@ android { dependencies { api(libs.roborazzi) - implementation(libs.androidx.compose.ui.test) + api(libs.robolectric) implementation(libs.androidx.activity.compose) implementation(libs.androidx.compose.ui.test) - implementation(libs.robolectric) implementation(projects.core.common) implementation(projects.core.designsystem) } diff --git a/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt b/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt index f11651220..8b0ba7c77 100644 --- a/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt +++ b/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.test.DarkMode import androidx.compose.ui.test.DeviceConfigurationOverride import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onRoot import androidx.test.ext.junit.rules.ActivityScenarioRule import com.github.takahirom.roborazzi.RoborazziOptions @@ -50,6 +51,7 @@ enum class DefaultTestDevices(val description: String, val spec: String) { FOLDABLE("foldable", "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480"), TABLET("tablet", "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480"), } + fun AndroidComposeTestRule, A>.captureMultiDevice( screenshotName: String, body: @Composable () -> Unit, @@ -90,6 +92,27 @@ fun AndroidComposeTestRule, A>.c ) } +fun AndroidComposeTestRule, A>.captureDialog( + screenshotName: String, + nodeTag: String, + roborazziOptions: RoborazziOptions = DefaultRoborazziOptions, + body: @Composable () -> Unit, +) { + this.activity.setContent { + CompositionLocalProvider( + LocalInspectionMode provides true, + ) { + body() + } + } + + this.onNodeWithTag(nodeTag) + .captureRoboImage( + filePath = "src/test/screenshots/$screenshotName.png", + roborazziOptions = roborazziOptions, + ) +} + /** * Takes six screenshots combining light/dark and default/Android themes and whether dynamic color * is enabled. diff --git a/feature/bookmarks/build.gradle.kts b/feature/bookmarks/build.gradle.kts index 4e97176a2..deed3bc53 100644 --- a/feature/bookmarks/build.gradle.kts +++ b/feature/bookmarks/build.gradle.kts @@ -26,8 +26,4 @@ android { dependencies { implementation(projects.core.data) - - testImplementation(projects.core.testing) - - androidTestImplementation(projects.core.testing) } diff --git a/feature/foryou/build.gradle.kts b/feature/foryou/build.gradle.kts index fd41d9a13..930b601a6 100644 --- a/feature/foryou/build.gradle.kts +++ b/feature/foryou/build.gradle.kts @@ -31,10 +31,7 @@ dependencies { implementation(projects.core.domain) testImplementation(libs.hilt.android.testing) - testImplementation(libs.robolectric) - testImplementation(projects.core.testing) - testImplementation(projects.core.screenshotTesting) testDemoImplementation(libs.roborazzi) - androidTestImplementation(projects.core.testing) + testImplementation(projects.core.screenshotTesting) } diff --git a/feature/interests/build.gradle.kts b/feature/interests/build.gradle.kts index ee6aaf122..a6b0fe26d 100644 --- a/feature/interests/build.gradle.kts +++ b/feature/interests/build.gradle.kts @@ -26,8 +26,4 @@ android { dependencies { implementation(projects.core.data) implementation(projects.core.domain) - - testImplementation(projects.core.testing) - - androidTestImplementation(projects.core.testing) } diff --git a/feature/search/build.gradle.kts b/feature/search/build.gradle.kts index 98052e9ab..283a1d835 100644 --- a/feature/search/build.gradle.kts +++ b/feature/search/build.gradle.kts @@ -28,9 +28,5 @@ dependencies { implementation(projects.core.data) implementation(projects.core.domain) implementation(projects.core.ui) - - testImplementation(projects.core.testing) - - androidTestImplementation(projects.core.testing) } diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts index 4b9a72bdd..a0aaa013e 100644 --- a/feature/settings/build.gradle.kts +++ b/feature/settings/build.gradle.kts @@ -18,6 +18,7 @@ plugins { alias(libs.plugins.nowinandroid.android.feature) alias(libs.plugins.nowinandroid.android.library.compose) alias(libs.plugins.nowinandroid.android.library.jacoco) + alias(libs.plugins.roborazzi) } android { @@ -29,7 +30,8 @@ dependencies { implementation(libs.google.oss.licenses) implementation(projects.core.data) - testImplementation(projects.core.testing) + testImplementation(libs.hilt.android.testing) + testDemoImplementation(libs.roborazzi) - androidTestImplementation(projects.core.testing) + testImplementation(projects.core.screenshotTesting) } diff --git a/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt b/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt index db60a6447..a33b30164 100644 --- a/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt +++ b/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt @@ -89,11 +89,12 @@ fun SettingsDialog( @Composable fun SettingsDialog( settingsUiState: SettingsUiState, - supportDynamicColor: Boolean = supportsDynamicTheming(), onDismiss: () -> Unit, onChangeThemeBrand: (themeBrand: ThemeBrand) -> Unit, onChangeDynamicColorPreference: (useDynamicColor: Boolean) -> Unit, onChangeDarkThemeConfig: (darkThemeConfig: DarkThemeConfig) -> Unit, + modifier: Modifier = Modifier, + supportDynamicColor: Boolean = supportsDynamicTheming(), ) { val configuration = LocalConfiguration.current @@ -106,7 +107,7 @@ fun SettingsDialog( */ AlertDialog( properties = DialogProperties(usePlatformDefaultWidth = false), - modifier = Modifier.widthIn(max = configuration.screenWidthDp.dp - 80.dp), + modifier = modifier.widthIn(max = configuration.screenWidthDp.dp - 80.dp), onDismissRequest = { onDismiss() }, title = { Text( diff --git a/feature/settings/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogScreenshotTests.kt b/feature/settings/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogScreenshotTests.kt new file mode 100644 index 000000000..39d13a7be --- /dev/null +++ b/feature/settings/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogScreenshotTests.kt @@ -0,0 +1,202 @@ +/* + * 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.feature.settings + +import androidx.activity.ComponentActivity +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme +import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig.DARK +import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig.LIGHT +import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand.ANDROID +import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand.DEFAULT +import com.google.samples.apps.nowinandroid.core.testing.util.captureDialog +import com.google.samples.apps.nowinandroid.feature.settings.SettingsUiState.Success +import dagger.hilt.android.testing.HiltTestApplication +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.robolectric.annotation.GraphicsMode +import org.robolectric.annotation.LooperMode +@RunWith(RobolectricTestRunner::class) +@GraphicsMode(GraphicsMode.Mode.NATIVE) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = HiltTestApplication::class, qualifiers = "w480dp-h960dp-480dpi") +class SettingsDialogScreenshotTests { + + @get:Rule + val composeTestRule = createAndroidComposeRule() + + private val defaultSettingsUiState = Success( + UserEditableSettings( + brand = DEFAULT, + darkThemeConfig = LIGHT, + useDynamicColor = false, + ), + ) + + @Test + fun settingsDialogLoading() { + composeTestRule.captureDialog( + screenshotName = "SettingsDialogLoading", + nodeTag = "SettingsDialog", + ) { + SettingsDialogDefaultTheme( + settingsUiState = SettingsUiState.Loading, + ) + } + } + + @Test + fun settingsDialogLoadingDark() { + composeTestRule.captureDialog( + screenshotName = "SettingsDialogLoadingDark", + nodeTag = "SettingsDialog", + ) { + SettingsDialogDefaultTheme( + darkMode = true, + settingsUiState = SettingsUiState.Loading, + ) + } + } + + @Test + fun settingsDialogDefaultTheme() { + composeTestRule.captureDialog( + screenshotName = "SettingsDialogDefaultTheme", + nodeTag = "SettingsDialog", + ) { + SettingsDialogDefaultTheme() + } + } + + @Test + fun settingsDialogDefaultThemeDark() { + composeTestRule.captureDialog( + screenshotName = "SettingsDialogDefaultThemeDark", + nodeTag = "SettingsDialog", + ) { + SettingsDialogDefaultTheme( + darkMode = true, + settingsUiState = Success( + defaultSettingsUiState.settings.copy( + darkThemeConfig = DARK, + ), + ), + ) + } + } + + @Test + fun settingsDialogDynamicColor() { + composeTestRule.captureDialog( + screenshotName = "SettingsDialogDynamicColor", + nodeTag = "SettingsDialog", + ) { + SettingsDialogDefaultTheme( + disableDynamicTheming = false, + settingsUiState = Success( + defaultSettingsUiState.settings.copy( + useDynamicColor = true, + ), + ), + ) + } + } + + @Test + fun settingsDialogDynamicColorDark() { + composeTestRule.captureDialog( + screenshotName = "SettingsDialogDynamicColorDark", + nodeTag = "SettingsDialog", + ) { + SettingsDialogDefaultTheme( + darkMode = true, + disableDynamicTheming = false, + settingsUiState = Success( + defaultSettingsUiState.settings.copy( + darkThemeConfig = DARK, + useDynamicColor = true, + ), + ), + ) + } + } + + @Test + fun settingsDialogAndroidTheme() { + composeTestRule.captureDialog( + screenshotName = "SettingsDialogAndroidTheme", + nodeTag = "SettingsDialog", + ) { + SettingsDialogDefaultTheme( + androidTheme = true, + settingsUiState = Success( + defaultSettingsUiState.settings.copy( + brand = ANDROID, + ), + ), + ) + } + } + + @Test + fun settingsDialogAndroidThemeDark() { + composeTestRule.captureDialog( + screenshotName = "SettingsDialogAndroidThemeDark", + nodeTag = "SettingsDialog", + ) { + SettingsDialogDefaultTheme( + darkMode = true, + androidTheme = true, + settingsUiState = Success( + defaultSettingsUiState.settings.copy( + darkThemeConfig = DARK, + brand = ANDROID, + ), + ), + ) + } + } + + @Composable + private fun SettingsDialogDefaultTheme( + darkMode: Boolean = false, + androidTheme: Boolean = false, + disableDynamicTheming: Boolean = true, + settingsUiState: SettingsUiState = defaultSettingsUiState, + ) { + NiaTheme( + darkTheme = darkMode, + androidTheme = androidTheme, + disableDynamicTheming = disableDynamicTheming, + ) { + SettingsDialog( + modifier = Modifier.testTag("SettingsDialog"), + onDismiss = {}, + settingsUiState = settingsUiState, + onChangeThemeBrand = {}, + onChangeDynamicColorPreference = {}, + onChangeDarkThemeConfig = {}, + ) + } + } +} diff --git a/feature/settings/src/test/screenshots/SettingsDialogAndroidTheme.png b/feature/settings/src/test/screenshots/SettingsDialogAndroidTheme.png new file mode 100644 index 000000000..352d65052 Binary files /dev/null and b/feature/settings/src/test/screenshots/SettingsDialogAndroidTheme.png differ diff --git a/feature/settings/src/test/screenshots/SettingsDialogAndroidThemeDark.png b/feature/settings/src/test/screenshots/SettingsDialogAndroidThemeDark.png new file mode 100644 index 000000000..a2ac0c1ac Binary files /dev/null and b/feature/settings/src/test/screenshots/SettingsDialogAndroidThemeDark.png differ diff --git a/feature/settings/src/test/screenshots/SettingsDialogDefaultTheme.png b/feature/settings/src/test/screenshots/SettingsDialogDefaultTheme.png new file mode 100644 index 000000000..a794943d8 Binary files /dev/null and b/feature/settings/src/test/screenshots/SettingsDialogDefaultTheme.png differ diff --git a/feature/settings/src/test/screenshots/SettingsDialogDefaultThemeDark.png b/feature/settings/src/test/screenshots/SettingsDialogDefaultThemeDark.png new file mode 100644 index 000000000..33dbd9b4b Binary files /dev/null and b/feature/settings/src/test/screenshots/SettingsDialogDefaultThemeDark.png differ diff --git a/feature/settings/src/test/screenshots/SettingsDialogDynamicColor.png b/feature/settings/src/test/screenshots/SettingsDialogDynamicColor.png new file mode 100644 index 000000000..3e048063d Binary files /dev/null and b/feature/settings/src/test/screenshots/SettingsDialogDynamicColor.png differ diff --git a/feature/settings/src/test/screenshots/SettingsDialogDynamicColorDark.png b/feature/settings/src/test/screenshots/SettingsDialogDynamicColorDark.png new file mode 100644 index 000000000..93744d489 Binary files /dev/null and b/feature/settings/src/test/screenshots/SettingsDialogDynamicColorDark.png differ diff --git a/feature/settings/src/test/screenshots/SettingsDialogLoading.png b/feature/settings/src/test/screenshots/SettingsDialogLoading.png new file mode 100644 index 000000000..c418a11b6 Binary files /dev/null and b/feature/settings/src/test/screenshots/SettingsDialogLoading.png differ diff --git a/feature/settings/src/test/screenshots/SettingsDialogLoadingDark.png b/feature/settings/src/test/screenshots/SettingsDialogLoadingDark.png new file mode 100644 index 000000000..20eaf2c19 Binary files /dev/null and b/feature/settings/src/test/screenshots/SettingsDialogLoadingDark.png differ diff --git a/feature/topic/build.gradle.kts b/feature/topic/build.gradle.kts index d457b2f73..8722fd67c 100644 --- a/feature/topic/build.gradle.kts +++ b/feature/topic/build.gradle.kts @@ -26,8 +26,4 @@ android { dependencies { implementation(projects.core.data) - - testImplementation(projects.core.testing) - - androidTestImplementation(projects.core.testing) -} \ No newline at end of file +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e0b938e75..f6e4a781b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -55,7 +55,7 @@ protobufPlugin = "0.9.4" retrofit = "2.9.0" retrofitKotlinxSerializationJson = "1.0.0" robolectric = "4.11.1" -roborazzi = "1.7.0" +roborazzi = "1.12.0" room = "2.6.1" secrets = "2.0.1" truth = "1.4.2"