Improve screenshot testing with ComposablePreviewScanner (#3125)

* Use ComposablePreviewScanner to rework how screenshot testing works
* Add test sharding
* Update screenshots
* Fixes for Element Gallery

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
Co-authored-by: Benoit Marty <benoit@matrix.org>
This commit is contained in:
Jorge Martin Espinosa
2024-07-03 13:05:06 +02:00
committed by GitHub
parent 41e3568a5a
commit 950e502ca6
2063 changed files with 1607 additions and 1768 deletions

View File

@@ -65,7 +65,7 @@ echo "Deleting previous screenshots"
./gradlew removeOldSnapshots --stacktrace --warn ./gradlew removeOldSnapshots --stacktrace --warn
echo "Record screenshots" echo "Record screenshots"
./gradlew recordPaparazziDebug --stacktrace --warn ./gradlew recordPaparazziDebug --stacktrace
echo "Committing changes" echo "Committing changes"
git config http.sslVerify false git config http.sslVerify false

View File

@@ -31,7 +31,6 @@ plugins {
id("io.element.android-compose-application") id("io.element.android-compose-application")
alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.android)
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
alias(libs.plugins.kapt) alias(libs.plugins.kapt)
// When using precompiled plugins, we need to apply the firebase plugin like this // When using precompiled plugins, we need to apply the firebase plugin like this
id(libs.plugins.firebaseAppDistribution.get().pluginId) id(libs.plugins.firebaseAppDistribution.get().pluginId)
@@ -283,6 +282,5 @@ dependencies {
testImplementation(libs.test.turbine) testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
ksp(libs.showkase.processor)
koverDependencies() koverDependencies()
} }

View File

@@ -16,7 +16,6 @@
plugins { plugins {
id("io.element.android-library") id("io.element.android-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
} }
android { android {

View File

@@ -21,7 +21,6 @@ import extension.allFeaturesApi
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
alias(libs.plugins.kapt) alias(libs.plugins.kapt)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -78,6 +77,4 @@ dependencies {
testImplementation(projects.services.analytics.test) testImplementation(projects.services.analytics.test)
testImplementation(libs.test.appyx.junit) testImplementation(libs.test.appyx.junit)
testImplementation(libs.test.arch.core) testImplementation(libs.test.arch.core)
ksp(libs.showkase.processor)
} }

View File

@@ -160,15 +160,15 @@ allprojects {
// Record all the languages? // Record all the languages?
if (project.hasProperty("allLanguagesNoEnglish")) { if (project.hasProperty("allLanguagesNoEnglish")) {
// Do not record English language // Do not record English language
exclude("ui/S.class") exclude("ui/*.class")
} else if (project.hasProperty("allLanguages").not()) { } else if (project.hasProperty("allLanguages").not()) {
// Do not record other languages // Do not record other languages
exclude("ui/T.class") exclude("translations/*.class")
} }
} else { } else {
// Disable screenshot tests by default // Disable screenshot tests by default
exclude("ui/S.class") exclude("ui/*.class")
exclude("ui/T.class") exclude("translations/*.class")
} }
} }
} }

View File

@@ -13,7 +13,7 @@
## Overview ## Overview
- Screenshot tests are tests which record the content of a rendered screen and verify subsequent runs to check if the screen renders differently. - Screenshot tests are tests which record the content of a rendered screen and verify subsequent runs to check if the screen renders differently.
- Element X uses [Paparazzi](https://github.com/cashapp/paparazzi) to render, record and verify Composable. All Composable Preview will be use to make screenshot test, thanks to the usage of [Showkase](https://github.com/airbnb/Showkase). - Element X uses [Paparazzi](https://github.com/cashapp/paparazzi) to render, record and verify Composables. All internal/public Composable Preview will be used for screenshot tests, thanks to the usage of [ComposablePreviewScanner](https://github.com/sergio-sastre/ComposablePreviewScanner).
- The screenshot verification occurs on every pull request as part of the `tests.yml` workflow. - The screenshot verification occurs on every pull request as part of the `tests.yml` workflow.
## Setup ## Setup

View File

@@ -15,7 +15,6 @@
*/ */
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
} }
android { android {
@@ -27,6 +26,4 @@ dependencies {
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.androidutils) implementation(projects.libraries.androidutils)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -42,7 +41,6 @@ dependencies {
implementation(projects.appconfig) implementation(projects.appconfig)
implementation(libs.androidx.datastore.preferences) implementation(libs.androidx.datastore.preferences)
implementation(libs.androidx.browser) implementation(libs.androidx.browser)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
} }
@@ -57,7 +56,6 @@ dependencies {
implementation(libs.network.retrofit) implementation(libs.network.retrofit)
implementation(libs.serialization.json) implementation(libs.serialization.json)
api(projects.features.call.api) api(projects.features.call.api)
ksp(libs.showkase.processor)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime) testImplementation(libs.molecule.runtime)

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -71,6 +70,4 @@ dependencies {
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -64,6 +63,4 @@ dependencies {
testImplementation(projects.features.lockscreen.test) testImplementation(projects.features.lockscreen.test)
testImplementation(projects.services.toolbox.test) testImplementation(projects.services.toolbox.test)
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -52,6 +51,4 @@ dependencies {
testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.push.test)
testImplementation(projects.services.analytics.test) testImplementation(projects.services.analytics.test)
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -59,6 +58,4 @@ dependencies {
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
ksp(libs.showkase.processor)
} }

View File

@@ -15,7 +15,6 @@
*/ */
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
} }
android { android {
@@ -27,5 +26,4 @@ dependencies {
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api)
ksp(libs.showkase.processor)
} }

View File

@@ -18,7 +18,6 @@ import java.util.Properties
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -66,7 +65,6 @@ dependencies {
implementation(projects.libraries.matrixui) implementation(projects.libraries.matrixui)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
implementation(libs.coil.compose) implementation(libs.coil.compose)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.test.truth) testImplementation(libs.test.truth)

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
} }
android { android {
@@ -51,7 +50,6 @@ dependencies {
implementation(libs.dagger) implementation(libs.dagger)
implementation(projects.anvilannotations) implementation(projects.anvilannotations)
anvil(projects.anvilcodegen) anvil(projects.anvilcodegen)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -30,7 +29,6 @@ anvil {
} }
dependencies { dependencies {
ksp(libs.showkase.processor)
implementation(projects.anvilannotations) implementation(projects.anvilannotations)
anvil(projects.anvilcodegen) anvil(projects.anvilcodegen)
api(projects.features.lockscreen.api) api(projects.features.lockscreen.api)

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
} }
@@ -56,7 +55,6 @@ dependencies {
implementation(libs.network.retrofit) implementation(libs.network.retrofit)
implementation(libs.serialization.json) implementation(libs.serialization.json)
api(projects.features.login.api) api(projects.features.login.api)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)

View File

@@ -15,7 +15,6 @@
*/ */
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
} }
android { android {
@@ -26,6 +25,4 @@ dependencies {
implementation(projects.libraries.architecture) implementation(projects.libraries.architecture)
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
} }
android { android {
@@ -47,7 +46,6 @@ dependencies {
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
implementation(projects.libraries.dateformatter.api) implementation(projects.libraries.dateformatter.api)
api(projects.features.logout.api) api(projects.features.logout.api)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -104,6 +103,4 @@ dependencies {
testImplementation(projects.features.poll.impl) testImplementation(projects.features.poll.impl)
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
} }
android { android {
@@ -35,8 +34,6 @@ dependencies {
implementation(projects.libraries.sessionStorage.api) implementation(projects.libraries.sessionStorage.api)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime) testImplementation(libs.molecule.runtime)

View File

@@ -16,7 +16,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
} }
android { android {
@@ -27,6 +26,4 @@ dependencies {
implementation(libs.coroutines.core) implementation(libs.coroutines.core)
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -48,7 +47,6 @@ dependencies {
implementation(projects.libraries.testtags) implementation(projects.libraries.testtags)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
api(projects.features.onboarding.api) api(projects.features.onboarding.api)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)

View File

@@ -16,7 +16,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
} }
android { android {
@@ -28,6 +27,4 @@ dependencies {
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -62,6 +61,4 @@ dependencies {
testImplementation(projects.features.poll.test) testImplementation(projects.features.poll.test)
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -72,7 +71,6 @@ dependencies {
implementation(libs.androidx.browser) implementation(libs.androidx.browser)
implementation(libs.androidx.datastore.preferences) implementation(libs.androidx.datastore.preferences)
api(projects.features.preferences.api) api(projects.features.preferences.api)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)

View File

@@ -15,7 +15,6 @@
*/ */
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
} }
android { android {
@@ -27,6 +26,4 @@ dependencies {
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.androidutils) implementation(projects.libraries.androidutils)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -55,7 +54,6 @@ dependencies {
implementation(libs.network.okhttp.okhttp) implementation(libs.network.okhttp.okhttp)
implementation(libs.coil) implementation(libs.coil)
implementation(libs.coil.compose) implementation(libs.coil.compose)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.test.robolectric) testImplementation(libs.test.robolectric)

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -56,6 +55,4 @@ dependencies {
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -82,6 +81,4 @@ dependencies {
testImplementation(projects.services.analytics.test) testImplementation(projects.services.analytics.test)
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -58,6 +57,4 @@ dependencies {
testImplementation(libs.test.turbine) testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -62,7 +61,6 @@ dependencies {
implementation(projects.services.analytics.api) implementation(projects.services.analytics.api)
implementation(libs.androidx.datastore.preferences) implementation(libs.androidx.datastore.preferences)
api(projects.features.roomlist.api) api(projects.features.roomlist.api)
ksp(libs.showkase.processor)
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)

View File

@@ -16,7 +16,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -61,6 +60,4 @@ dependencies {
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
ksp(libs.showkase.processor)
} }

View File

@@ -16,7 +16,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -65,6 +64,4 @@ dependencies {
testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.mediaupload.test)
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -48,6 +47,4 @@ dependencies {
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.sessionStorage.implMemory) testImplementation(projects.libraries.sessionStorage.implMemory)
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -65,6 +64,4 @@ dependencies {
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -66,6 +65,4 @@ dependencies {
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
ksp(libs.showkase.processor)
} }

View File

@@ -16,7 +16,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
} }
@@ -58,6 +57,4 @@ dependencies {
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -38,7 +37,6 @@ dependencies {
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
api(projects.features.viewfolder.api) api(projects.features.viewfolder.api)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.test.robolectric) testImplementation(libs.test.robolectric)

View File

@@ -16,7 +16,6 @@
plugins { plugins {
id("io.element.android-library") id("io.element.android-library")
alias(libs.plugins.ksp)
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
} }

View File

@@ -46,7 +46,6 @@ android {
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
ksp(libs.showkase.processor) ksp(libs.showkase.processor)
kspTest(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-library") id("io.element.android-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -33,5 +32,4 @@ dependencies {
implementation(projects.anvilannotations) implementation(projects.anvilannotations)
anvil(projects.anvilcodegen) anvil(projects.anvilcodegen)
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -49,8 +48,6 @@ dependencies {
implementation(libs.coil.gif) implementation(libs.coil.gif)
implementation(libs.jsoup) implementation(libs.jsoup)
ksp(libs.showkase.processor)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-library") id("io.element.android-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
} }
android { android {

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-library") id("io.element.android-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
} }
android { android {

View File

@@ -16,7 +16,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -67,6 +66,4 @@ dependencies {
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
ksp(libs.showkase.processor)
} }

View File

@@ -15,7 +15,6 @@
*/ */
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
} }
android { android {
@@ -26,6 +25,4 @@ dependencies {
implementation(projects.libraries.architecture) implementation(projects.libraries.architecture)
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
} }
android { android {
@@ -61,6 +60,4 @@ dependencies {
testImplementation(projects.libraries.permissions.test) testImplementation(projects.libraries.permissions.test)
testImplementation(projects.services.toolbox.test) testImplementation(projects.services.toolbox.test)
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
ksp(libs.showkase.processor)
} }

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -40,7 +39,6 @@ dependencies {
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
api(projects.libraries.roomselect.api) api(projects.libraries.roomselect.api)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)

View File

@@ -16,7 +16,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
@@ -54,8 +53,6 @@ dependencies {
debugApi(libs.matrix.richtexteditor.compose) debugApi(libs.matrix.richtexteditor.compose)
} }
ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime) testImplementation(libs.molecule.runtime)

View File

@@ -16,7 +16,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
} }
@@ -44,7 +43,6 @@ dependencies {
api(projects.libraries.troubleshoot.api) api(projects.libraries.troubleshoot.api)
api(projects.libraries.push.api) api(projects.libraries.push.api)
implementation(projects.services.analytics.api) implementation(projects.services.analytics.api)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.test.robolectric) testImplementation(libs.test.robolectric)

File diff suppressed because it is too large Load Diff

View File

@@ -183,7 +183,7 @@ function getNiceName(name) {
for(var i = 0; i < name.length; i++) { for(var i = 0; i < name.length; i++) {
if (name[i] === "_") indices.push(i); if (name[i] === "_") indices.push(i);
} }
return name.substring(indices[2] + 1, indices[3]); return name.substring(indices[0] + 1, indices[1]);
} }
function createMissingImageElement() { function createMissingImageElement() {
@@ -276,7 +276,7 @@ function addTable() {
} else if(modifiedDayTime >= minModifiedDayTime) { } else if(modifiedDayTime >= minModifiedDayTime) {
hasTranslatedFiles = true; hasTranslatedFiles = true;
// Foreign file is the same as the english file, replacing the language // Foreign file is the same as the english file, replacing the language
const foreignFile = englishFile.replace("en]", `${dataLanguages[languageIndex]}]`).replace("_S_", "_T_") const foreignFile = englishFile.substring(0, englishFile.length-2) + dataLanguages[languageIndex];
const fullForeignFile = `${dataLanguages[languageIndex]}/${foreignFile}.png`; const fullForeignFile = `${dataLanguages[languageIndex]}/${foreignFile}.png`;
td.appendChild(createImageElement(fullForeignFile, modifiedDayTime)); td.appendChild(createImageElement(fullForeignFile, modifiedDayTime));
} else { } else {

View File

@@ -17,7 +17,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize") id("kotlin-parcelize")
} }

View File

@@ -16,7 +16,6 @@
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
} }
@@ -46,6 +45,4 @@ dependencies {
testImplementation(libs.coroutines.test) testImplementation(libs.coroutines.test)
testImplementation(libs.test.turbine) testImplementation(libs.test.turbine)
testImplementation(libs.test.truth) testImplementation(libs.test.truth)
ksp(libs.showkase.processor)
} }

View File

@@ -16,7 +16,6 @@
plugins { plugins {
id("io.element.android-library") id("io.element.android-library")
alias(libs.plugins.ksp)
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
} }

View File

@@ -27,6 +27,13 @@ pluginManagement {
dependencyResolutionManagement { dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories { repositories {
maven {
url = URI("https://jitpack.io")
content {
includeModule("com.github.sergio-sastre.ComposablePreviewScanner", "android")
includeModule("com.github.sergio-sastre.ComposablePreviewScanner", "core")
}
}
// Snapshot versions // Snapshot versions
maven { maven {
url = URI("https://s01.oss.sonatype.org/content/repositories/snapshots") url = URI("https://s01.oss.sonatype.org/content/repositories/snapshots")

View File

@@ -15,7 +15,6 @@
*/ */
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
} }
android { android {

View File

@@ -20,7 +20,6 @@ import extension.allServicesImpl
plugins { plugins {
id("io.element.android-compose-library") id("io.element.android-compose-library")
alias(libs.plugins.ksp)
alias(libs.plugins.paparazzi) alias(libs.plugins.paparazzi)
} }
@@ -58,9 +57,6 @@ dependencies {
) )
} }
ksp(libs.showkase.processor)
kspTest(libs.showkase.processor)
implementation(libs.showkase) implementation(libs.showkase)
// TODO There is a Resources.NotFoundException maybe due to the mipmap, even if we have // TODO There is a Resources.NotFoundException maybe due to the mipmap, even if we have
@@ -70,4 +66,6 @@ dependencies {
allLibrariesImpl() allLibrariesImpl()
allServicesImpl() allServicesImpl()
allFeaturesImpl(rootDir, logger) allFeaturesImpl(rootDir, logger)
testImplementation("com.github.sergio-sastre.ComposablePreviewScanner:android:0.1.2")
} }

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package ui package base
import app.cash.paparazzi.DeviceConfig import app.cash.paparazzi.DeviceConfig

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2024 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.
*/
@file:Suppress("DEPRECATION")
package base
import com.google.testing.junit.testparameterinjector.TestParameter
import sergio.sastre.composable.preview.scanner.android.AndroidComposablePreviewScanner
import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo
import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview
object ComposablePreviewProvider : TestParameter.TestParameterValuesProvider {
private val values: List<IndexedValue<ComposablePreview<AndroidPreviewInfo>>> by lazy {
AndroidComposablePreviewScanner()
.scanPackageTrees(
"io.element.android.features",
"io.element.android.libraries",
"io.element.android.services",
"io.element.android.appnav",
"io.element.android.x",
// Make sure we don't import Compound previews by mistake
)
.getPreviews()
.withIndex()
.toList()
}
override fun provideValues(): List<IndexedValue<ComposablePreview<AndroidPreviewInfo>>> = values
}
object Shard1ComposablePreviewProvider : TestParameter.TestParameterValuesProvider {
override fun provideValues(): List<ComposablePreview<AndroidPreviewInfo>> =
ComposablePreviewProvider.provideValues().filter { it.index % 4 == 0 }.map { it.value }
}
object Shard2ComposablePreviewProvider : TestParameter.TestParameterValuesProvider {
override fun provideValues(): List<ComposablePreview<AndroidPreviewInfo>> =
ComposablePreviewProvider.provideValues().filter { it.index % 4 == 1 }.map { it.value }
}
object Shard3ComposablePreviewProvider : TestParameter.TestParameterValuesProvider {
override fun provideValues(): List<ComposablePreview<AndroidPreviewInfo>> =
ComposablePreviewProvider.provideValues().filter { it.index % 4 == 2 }.map { it.value }
}
object Shard4ComposablePreviewProvider : TestParameter.TestParameterValuesProvider {
override fun provideValues(): List<ComposablePreview<AndroidPreviewInfo>> =
ComposablePreviewProvider.provideValues().filter { it.index % 4 == 3 }.map { it.value }
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (c) 2024 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 base
import android.content.res.Configuration
import android.os.LocaleList
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.unit.Density
import app.cash.paparazzi.DeviceConfig
import app.cash.paparazzi.Paparazzi
import app.cash.paparazzi.TestName
import com.android.resources.NightMode
import io.element.android.compound.theme.ElementTheme
import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo
import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview
import java.util.Locale
object ScreenshotTest {
val defaultDeviceConfig = BaseDeviceConfig.NEXUS_5.deviceConfig
fun runTest(
paparazzi: Paparazzi,
preview: ComposablePreview<AndroidPreviewInfo>,
localeStr: String,
) {
val locale = localeStr.toLocale()
// Needed for regional settings, as first day of week
Locale.setDefault(locale)
paparazzi.fixScreenshotName(preview, localeStr)
paparazzi.snapshot {
CompositionLocalProvider(
LocalInspectionMode provides true,
LocalDensity provides Density(
density = LocalDensity.current.density,
fontScale = 1.0f,
),
LocalConfiguration provides Configuration().apply {
setLocales(LocaleList(locale))
uiMode = preview.previewInfo.uiMode
},
) {
ElementTheme {
Box(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
) {
preview()
}
}
}
}
}
}
private val testNameField = Paparazzi::class.java.getDeclaredField("testName").apply {
isAccessible = true
}
private fun Paparazzi.fixScreenshotName(preview: ComposablePreview<AndroidPreviewInfo>, locale: String) {
val id = listOf(createScreenshotIdFor(preview), locale)
.filter { it.isNotEmpty() }
.joinToString("_")
val packageName = preview.declaringClass
// Remove common prefix
.replace("io.element.android.", "")
.split(".")
// Remove class name
.dropLast(1)
.joinToString(".")
val testName = TestName(
packageName = packageName,
className = preview.methodName.replace("Preview", ""),
methodName = id
)
testNameField.set(this, testName)
}
private fun String.toLocale(): Locale {
return when (this) {
"en" -> Locale.US
"fr" -> Locale.FRANCE
"de" -> Locale.GERMAN
else -> Locale.Builder().setLanguage(this).build()
}
}
fun createScreenshotIdFor(preview: ComposablePreview<AndroidPreviewInfo>) = buildList {
// `name` here can be `Day`, `Night`, or nothing at all
if (preview.previewInfo.name.isNotEmpty()) {
add(preview.previewInfo.name)
}
if (preview.previewInfo.group.isNotEmpty()) {
add(preview.previewInfo.group)
}
// If it's a day/night preview, we should add an index to be consistent even if there is only version of this composable
val needsIndex = preview.previewInfo.name == "Day" || preview.previewInfo.name == "Night"
if (preview.previewIndex != null || needsIndex) {
add((preview.previewIndex ?: 0).toString())
}
}.joinToString("_")
object PaparazziPreviewRule {
fun createFor(preview: ComposablePreview<AndroidPreviewInfo>, locale: String, deviceConfig: DeviceConfig = ScreenshotTest.defaultDeviceConfig): Paparazzi {
val densityScale = deviceConfig.density.dpiValue / 160f
val customScreenHeight = preview.previewInfo.heightDp.takeIf { it >= 0 }?.let { it * densityScale }?.toInt()
return Paparazzi(
deviceConfig = deviceConfig.copy(
nightMode = when (preview.previewInfo.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) {
true -> NightMode.NIGHT
false -> NightMode.NOTNIGHT
},
locale = locale,
softButtons = false,
screenHeight = customScreenHeight ?: deviceConfig.screenHeight,
),
maxPercentDifference = 0.01
)
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2024 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 translations
import android.content.res.Configuration
import base.ComposablePreviewProvider
import base.PaparazziPreviewRule
import base.ScreenshotTest
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo
import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview
/**
* Test that takes a preview and a locale and runs a screenshot test on it.
*/
@RunWith(TestParameterInjector::class)
class TranslationsScreenshotTest(
@TestParameter(valuesProvider = ComposablePreviewProvider::class)
val indexedPreview: IndexedValue<ComposablePreview<AndroidPreviewInfo>>,
@TestParameter(value = ["de"])
val localeStr: String,
) {
@get:Rule
val paparazziRule = PaparazziPreviewRule.createFor(indexedPreview.value, locale = localeStr)
@Test
fun snapshot() {
val (_, preview) = indexedPreview
// Skip for dark mode screenshots
if (preview.previewInfo.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) {
return
}
// Skip for design system screenshots
if (preview.previewInfo.name.startsWith("io.element.android.libraries.designsystem")) {
return
}
ScreenshotTest.runTest(paparazzi = paparazziRule, preview = preview, localeStr = localeStr)
}
}

View File

@@ -1,44 +0,0 @@
/*
* 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 ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.airbnb.android.showkase.models.ShowkaseBrowserColor
class ColorTestPreview(
private val showkaseBrowserColor: ShowkaseBrowserColor
) : TestPreview {
@Composable
override fun Content() {
Box(
modifier = Modifier
.fillMaxWidth()
.height(250.dp)
.background(showkaseBrowserColor.color)
)
}
override val name: String = showkaseBrowserColor.colorName
override fun toString(): String = "Color_${showkaseBrowserColor.colorGroup}_${showkaseBrowserColor.colorName}"
}

View File

@@ -1,46 +0,0 @@
/*
* 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 ui
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.Dp
import com.airbnb.android.showkase.models.ShowkaseBrowserComponent
class ComponentTestPreview(
private val showkaseBrowserComponent: ShowkaseBrowserComponent
) : TestPreview {
@Composable
override fun Content() = showkaseBrowserComponent.component()
override val name: String = showkaseBrowserComponent.componentName
override fun customHeightDp(): Dp? {
return showkaseBrowserComponent.heightDp?.let { Dp(it.toFloat()) }
}
override fun toString(): String = showkaseBrowserComponent.componentKey
// Strip common package beginning
.replace("io.element.android.features.", "f.")
.replace("io.element.android.libraries.", "l.")
.replace("io.element.android.", "")
// Reduce default group (if present)
.replace("_DefaultGroup_", "_")
// No need to include `Preview` suffix of function name
.replace("Preview_", "_")
// Also for preview annotated with @ElementPreview
.replace("Preview-", "-")
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright (c) 2024 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 ui
import com.airbnb.android.showkase.models.Showkase
import com.google.testing.junit.testparameterinjector.TestParameterValuesProvider
object PreviewProvider : TestParameterValuesProvider() {
override fun provideValues(context: Context): List<TestPreview> {
val metadata = Showkase.getMetadata()
val components = metadata.componentList.map(::ComponentTestPreview)
val colors = metadata.colorList.map(::ColorTestPreview)
val typography = metadata.typographyList.map(::TypographyTestPreview)
return (components + colors + typography).filter { !it.toString().contains("compound") }
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2024 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 ui
import base.PaparazziPreviewRule
import base.ScreenshotTest
import base.Shard1ComposablePreviewProvider
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo
import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview
/**
* Test that takes a preview and runs a screenshot test on it.
* It uses a sharded preview provider so multiple 'shards' can run in parallel, optimizing CPU and time usage.
*/
@RunWith(TestParameterInjector::class)
class PreviewShard1Test(
@TestParameter(valuesProvider = Shard1ComposablePreviewProvider::class)
val preview: ComposablePreview<AndroidPreviewInfo>,
) {
@get:Rule
val paparazziRule = PaparazziPreviewRule.createFor(preview, locale = "en")
@Test
fun snapshot() {
ScreenshotTest.runTest(paparazzi = paparazziRule, preview = preview, localeStr = "en")
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2024 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 ui
import base.PaparazziPreviewRule
import base.ScreenshotTest
import base.Shard2ComposablePreviewProvider
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo
import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview
/**
* Test that takes a preview and runs a screenshot test on it.
* It uses a sharded preview provider so multiple 'shards' can run in parallel, optimizing CPU and time usage.
*/
@RunWith(TestParameterInjector::class)
class PreviewShard2Test(
@TestParameter(valuesProvider = Shard2ComposablePreviewProvider::class)
val preview: ComposablePreview<AndroidPreviewInfo>,
) {
@get:Rule
val paparazziRule = PaparazziPreviewRule.createFor(preview, locale = "en")
@Test
fun snapshot() {
ScreenshotTest.runTest(paparazzi = paparazziRule, preview = preview, localeStr = "en")
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2024 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 ui
import base.PaparazziPreviewRule
import base.ScreenshotTest
import base.Shard3ComposablePreviewProvider
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo
import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview
/**
* Test that takes a preview and runs a screenshot test on it.
* It uses a sharded preview provider so multiple 'shards' can run in parallel, optimizing CPU and time usage.
*/
@RunWith(TestParameterInjector::class)
class PreviewShard3Test(
@TestParameter(valuesProvider = Shard3ComposablePreviewProvider::class)
val preview: ComposablePreview<AndroidPreviewInfo>,
) {
@get:Rule
val paparazziRule = PaparazziPreviewRule.createFor(preview, locale = "en")
@Test
fun snapshot() {
ScreenshotTest.runTest(paparazzi = paparazziRule, preview = preview, localeStr = "en")
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2024 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 ui
import base.PaparazziPreviewRule
import base.ScreenshotTest
import base.Shard4ComposablePreviewProvider
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo
import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview
/**
* Test that takes a preview and runs a screenshot test on it.
* It uses a sharded preview provider so multiple 'shards' can run in parallel, optimizing CPU and time usage.
*/
@RunWith(TestParameterInjector::class)
class PreviewShard4Test(
@TestParameter(valuesProvider = Shard4ComposablePreviewProvider::class)
val preview: ComposablePreview<AndroidPreviewInfo>,
) {
@get:Rule
val paparazziRule = PaparazziPreviewRule.createFor(preview, locale = "en")
@Test
fun snapshot() {
ScreenshotTest.runTest(paparazzi = paparazziRule, preview = preview, localeStr = "en")
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright 2022 The Android Open Source Project
* 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
*
* 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 ui
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import org.junit.Test
import org.junit.runner.RunWith
/**
* Screenshot test for the English version only.
*/
@RunWith(TestParameterInjector::class)
class S : ScreenshotTest() {
/**
* *Note*: keep the method name as short as possible to get shorter filename for generated screenshot.
* Long name was preview_test.
*/
@Test
fun t(
@TestParameter(valuesProvider = PreviewProvider::class) componentTestPreview: TestPreview,
@TestParameter baseDeviceConfig: BaseDeviceConfig,
@TestParameter(value = ["1.0"]) fontScale: Float,
// Need to keep the TestParameter to have filename including the language.
@TestParameter(value = ["en"]) localeStr: String,
) {
doTest(
componentTestPreview = componentTestPreview,
baseDeviceConfig = baseDeviceConfig,
fontScale = fontScale,
localeStr = localeStr,
)
}
}

View File

@@ -1,125 +0,0 @@
/*
* Copyright (c) 2024 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 ui
import android.content.res.Configuration
import android.os.LocaleList
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.Density
import androidx.lifecycle.Lifecycle
import app.cash.paparazzi.Paparazzi
import app.cash.paparazzi.detectEnvironment
import com.android.ide.common.rendering.api.SessionParams
import com.android.resources.NightMode
import io.element.android.compound.theme.ElementTheme
import org.junit.Rule
import java.util.Locale
/**
* BMA: Inspired from https://github.com/airbnb/Showkase/blob/master/showkase-screenshot-testing-paparazzi-sample/src/test/java/com/airbnb/android/showkase/screenshot/testing/paparazzi/sample/PaparazziSampleScreenshotTest.kt
*
* Credit to Alex Vanyo for creating this sample in the Now In Android app by Google.
* PR here - https://github.com/android/nowinandroid/pull/101. Modified the test from that PR to
* my own needs for this sample.
*
* *Note*: keep the class name as short as possible to get shorter filename for generated screenshot.
* Long name was ScreenshotTest.
*/
open class ScreenshotTest {
@get:Rule
val paparazzi = Paparazzi(
environment = detectEnvironment(),
maxPercentDifference = 0.01,
renderingMode = SessionParams.RenderingMode.NORMAL,
)
protected fun doTest(
componentTestPreview: TestPreview,
baseDeviceConfig: BaseDeviceConfig,
fontScale: Float,
localeStr: String,
) {
val locale = localeStr.toLocale()
Locale.setDefault(locale) // Needed for regional settings, as first day of week
val densityScale = baseDeviceConfig.deviceConfig.density.dpiValue / 160f
val customScreenHeight = componentTestPreview.customHeightDp()?.value?.let { it * densityScale }?.toInt()
paparazzi.unsafeUpdateConfig(
deviceConfig = baseDeviceConfig.deviceConfig.copy(
softButtons = false,
locale = localeStr,
nightMode = componentTestPreview.isNightMode().let {
when (it) {
true -> NightMode.NIGHT
false -> NightMode.NOTNIGHT
}
},
screenHeight = customScreenHeight ?: baseDeviceConfig.deviceConfig.screenHeight,
),
)
paparazzi.snapshot {
val lifecycleOwner = LocalLifecycleOwner.current
CompositionLocalProvider(
LocalInspectionMode provides true,
LocalDensity provides Density(
density = LocalDensity.current.density,
fontScale = fontScale
),
LocalConfiguration provides Configuration().apply {
setLocales(LocaleList(locale))
uiMode = when (componentTestPreview.isNightMode()) {
true -> Configuration.UI_MODE_NIGHT_YES
false -> Configuration.UI_MODE_NIGHT_NO
}
},
// Needed so that UI that uses it don't crash during screenshot tests
LocalOnBackPressedDispatcherOwner provides object : OnBackPressedDispatcherOwner {
override val lifecycle: Lifecycle get() = lifecycleOwner.lifecycle
override val onBackPressedDispatcher: OnBackPressedDispatcher get() = OnBackPressedDispatcher()
}
) {
ElementTheme {
Box(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
) {
componentTestPreview.Content()
}
}
}
}
}
}
private fun String.toLocale(): Locale {
return when (this) {
"en" -> Locale.US
"fr" -> Locale.FRANCE
"de" -> Locale.GERMAN
else -> Locale.Builder().setLanguage(this).build()
}
}

View File

@@ -1,52 +0,0 @@
/*
* Copyright (c) 2024 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 ui
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import org.junit.Test
import org.junit.runner.RunWith
/**
* Screenshot test for the Locale other then English.
*/
@RunWith(TestParameterInjector::class)
class T : ScreenshotTest() {
/**
* *Note*: keep the method name as short as possible to get shorter filename for generated screenshot.
* Long name was preview_test.
*/
@SuppressWarnings("MemberNameEqualsClassName")
@Test
fun t(
@TestParameter(valuesProvider = PreviewProvider::class) componentTestPreview: TestPreview,
@TestParameter baseDeviceConfig: BaseDeviceConfig,
@TestParameter(value = ["1.0"]) fontScale: Float,
@TestParameter(value = ["de"]) localeStr: String,
) {
// Only test ComponentTestPreview, and only with the light theme
if (componentTestPreview.isNightMode() || componentTestPreview !is ComponentTestPreview) {
return
}
doTest(
componentTestPreview = componentTestPreview,
baseDeviceConfig = baseDeviceConfig,
fontScale = fontScale,
localeStr = localeStr,
)
}
}

View File

@@ -1,41 +0,0 @@
/*
* 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 ui
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import com.airbnb.android.showkase.models.ShowkaseElementsMetadata
import io.element.android.libraries.designsystem.preview.NIGHT_MODE_NAME
interface TestPreview {
@Composable
fun Content()
val name: String
fun customHeightDp(): Dp? = null
}
/**
* Showkase doesn't put the [Preview.uiMode] parameter in its [ShowkaseElementsMetadata]
* so we have to encode the night mode bit in a preview's name.
*/
fun TestPreview.isNightMode(): Boolean {
// Dark mode previews have name "N" so their component name contains "- N"
return this.name.contains("- $NIGHT_MODE_NAME")
}

View File

@@ -1,50 +0,0 @@
/*
* 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 ui
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicText
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.airbnb.android.showkase.models.ShowkaseBrowserTypography
import com.airbnb.android.showkase.ui.padding4x
import java.util.Locale
class TypographyTestPreview(
private val showkaseBrowserTypography: ShowkaseBrowserTypography
) : TestPreview {
@Composable
override fun Content() {
BasicText(
text = showkaseBrowserTypography.typographyName.replaceFirstChar {
it.titlecase(Locale.getDefault())
},
modifier = Modifier
.fillMaxWidth()
.padding(padding4x),
style = showkaseBrowserTypography.textStyle.copy(
color = MaterialTheme.colorScheme.onBackground
)
)
}
override val name: String = showkaseBrowserTypography.typographyName
override fun toString(): String = "Typo_${showkaseBrowserTypography.typographyGroup}_${showkaseBrowserTypography.typographyName}"
}

Some files were not shown because too many files have changed in this diff Show More