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()
+ }
+ }
+ }
+ }
+}