diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 33827f673b..09bac9e57b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -169,6 +169,7 @@ dependencies { implementation(project(":features:rageshake")) implementation(project(":features:preferences")) implementation(project(":libraries:di")) + implementation(project(":tests:uitests")) implementation(project(":anvilannotations")) anvil(project(":anvilcodegen")) @@ -186,7 +187,4 @@ dependencies { implementation(libs.dagger) kapt(libs.dagger.compiler) - - implementation(libs.showkase) - ksp(libs.showkase.processor) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 2e1ed9af84..af4fe223dd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -50,6 +50,7 @@ include(":features:rageshake") include(":features:preferences") include(":libraries:designsystem") include(":libraries:di") +include(":tests:uitests") include(":anvilannotations") include(":anvilcodegen") include(":libraries:architecture") diff --git a/tests/uitests/.gitignore b/tests/uitests/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/tests/uitests/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/tests/uitests/build.gradle.kts b/tests/uitests/build.gradle.kts new file mode 100644 index 0000000000..ff21a715ca --- /dev/null +++ b/tests/uitests/build.gradle.kts @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * 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. + */ + +plugins { + id("io.element.android-compose-library") + alias(libs.plugins.ksp) + // TODO Create alias + id("app.cash.paparazzi") version "1.0.0" +} + +android { + namespace = "io.element.android.x.tests.uitests" +} + +dependencies { + testImplementation(libs.test.junit) + androidTestImplementation(libs.test.junitext) + ksp(libs.showkase.processor) + kspTest(libs.showkase.processor) + + // TODO Move to libs + testImplementation("com.airbnb.android:showkase-screenshot-testing:$1.0.0-beta14") + testImplementation("com.google.testparameterinjector:test-parameter-injector:1.8") + + implementation(project(":libraries:designsystem")) + + implementation(libs.showkase) + ksp(libs.showkase.processor) +} diff --git a/tests/uitests/consumer-rules.pro b/tests/uitests/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/uitests/proguard-rules.pro b/tests/uitests/proguard-rules.pro new file mode 100644 index 0000000000..ff59496d81 --- /dev/null +++ b/tests/uitests/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle.kts. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/tests/uitests/src/main/AndroidManifest.xml b/tests/uitests/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..122869829c --- /dev/null +++ b/tests/uitests/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/kotlin/io/element/android/x/ElementRootModule.kt b/tests/uitests/src/main/java/io/element/android/x/tests/uitests/ElementXShowkaseRootModule.kt similarity index 88% rename from app/src/main/kotlin/io/element/android/x/ElementRootModule.kt rename to tests/uitests/src/main/java/io/element/android/x/tests/uitests/ElementXShowkaseRootModule.kt index ab59ae72fe..6250bcaba8 100644 --- a/app/src/main/kotlin/io/element/android/x/ElementRootModule.kt +++ b/tests/uitests/src/main/java/io/element/android/x/tests/uitests/ElementXShowkaseRootModule.kt @@ -14,10 +14,10 @@ * limitations under the License. */ -package io.element.android.x +package io.element.android.x.tests.uitests import com.airbnb.android.showkase.annotation.ShowkaseRoot import com.airbnb.android.showkase.annotation.ShowkaseRootModule @ShowkaseRoot -class ElementRootModule : ShowkaseRootModule +class ElementXShowkaseRootModule : ShowkaseRootModule diff --git a/tests/uitests/src/main/java/io/element/android/x/tests/uitests/ShowkaseButton.kt b/tests/uitests/src/main/java/io/element/android/x/tests/uitests/ShowkaseButton.kt new file mode 100644 index 0000000000..a6dfd59cb9 --- /dev/null +++ b/tests/uitests/src/main/java/io/element/android/x/tests/uitests/ShowkaseButton.kt @@ -0,0 +1,42 @@ +package io.element.android.x.tests.uitests + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun ShowkaseButton( + onClick: () -> Unit, +) { + var isShowkaseButtonVisible by remember { mutableStateOf(BuildConfig.DEBUG) } + + if (isShowkaseButtonVisible) { + Button( + modifier = Modifier + .padding(top = 32.dp, start = 16.dp), + onClick = onClick + ) { + Text(text = "Showkase Browser") + IconButton( + modifier = Modifier + .padding(start = 8.dp) + .size(16.dp), + onClick = { isShowkaseButtonVisible = false }, + ) { + Icon(imageVector = Icons.Filled.Close, contentDescription = "") + } + } + } +} diff --git a/tests/uitests/src/main/java/io/element/android/x/tests/uitests/ShowkaseNavigation.kt b/tests/uitests/src/main/java/io/element/android/x/tests/uitests/ShowkaseNavigation.kt new file mode 100644 index 0000000000..4ed7328ad3 --- /dev/null +++ b/tests/uitests/src/main/java/io/element/android/x/tests/uitests/ShowkaseNavigation.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * 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 io.element.android.x.tests.uitests + +import android.app.Activity +import com.airbnb.android.showkase.models.Showkase + +fun openShowkase(activity: Activity) { + activity.startActivity(Showkase.getBrowserIntent(activity)) +} diff --git a/tests/uitests/src/test/java/io/element/android/x/tests/uitests/ComposePapparazziTest.kt b/tests/uitests/src/test/java/io/element/android/x/tests/uitests/ComposePapparazziTest.kt new file mode 100644 index 0000000000..d1fcff0de3 --- /dev/null +++ b/tests/uitests/src/test/java/io/element/android/x/tests/uitests/ComposePapparazziTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * 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 io.element.android.x.tests.uitests + +import android.os.Build +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.unit.Density +import app.cash.paparazzi.DeviceConfig.Companion.PIXEL_5 +import app.cash.paparazzi.Paparazzi +import app.cash.paparazzi.androidHome +import app.cash.paparazzi.detectEnvironment +import com.airbnb.android.showkase.models.Showkase +import com.airbnb.android.showkase.models.ShowkaseBrowserComponent +import com.google.testing.junit.testparameterinjector.TestParameter +import com.google.testing.junit.testparameterinjector.TestParameterInjector +import io.element.android.x.designsystem.ElementXTheme +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +class ComponentPreview( + private val showkaseBrowserComponent: ShowkaseBrowserComponent +) { + val content: @Composable () -> Unit = showkaseBrowserComponent.component + override fun toString(): String = + showkaseBrowserComponent.group + ":" + showkaseBrowserComponent.componentName +} + +@RunWith(TestParameterInjector::class) +class ComposePaparazziTests { + + object PreviewProvider : TestParameter.TestParameterValuesProvider { + override fun provideValues(): List = + Showkase.getMetadata().componentList.map(::ComponentPreview) + } + + @get:Rule + val paparazzi = Paparazzi( + // Apply trick from https://github.com/cashapp/paparazzi/issues/489#issuecomment-1195674603 + environment = detectEnvironment().copy( + platformDir = "${androidHome()}/platforms/android-32", + compileSdkVersion = Build.VERSION_CODES.S_V2 /* 32 */ + ), + maxPercentDifference = 0.0, + deviceConfig = PIXEL_5.copy(softButtons = false), + ) + + @Test + fun preview_tests( + @TestParameter(valuesProvider = PreviewProvider::class) componentPreview: ComponentPreview, + @TestParameter(value = ["1.0", "1.5"]) fontScale: Float, + @TestParameter(value = ["light", "dark"]) theme: String, + // TODO Test other languages + ) { + paparazzi.snapshot { + CompositionLocalProvider( + LocalInspectionMode provides true, + LocalDensity provides Density( + density = LocalDensity.current.density, + fontScale = fontScale + ) + ) { + ElementXTheme(darkTheme = (theme == "dark")) { + componentPreview.content() + } + } + } + } +}