Skip to content

Commit

Permalink
Merge pull request #56 from DroidKaigi/takahirom/introduce-compose-pr…
Browse files Browse the repository at this point in the history
…eview-scanner/2024-06-17

Introduce compose preview scanner
  • Loading branch information
takahirom authored Jun 19, 2024
2 parents 90a587f + 3b826a5 commit 3427e85
Show file tree
Hide file tree
Showing 13 changed files with 28 additions and 184 deletions.
1 change: 1 addition & 0 deletions app-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,5 @@ dependencies {
implementation(libs.firebaseDynamicLinks)
debugImplementation(projects.core.testingManifest)
testImplementation(projects.core.testing)
testImplementation(libs.composablePreviewScanner)
}
11 changes: 0 additions & 11 deletions app-android/src/main/res/values-night/splash_theme.xml

This file was deleted.

2 changes: 1 addition & 1 deletion app-android/src/main/res/values/splash_theme.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
<item name="windowSplashScreenBackground">@android:color/white</item>
<item name="windowSplashScreenAnimationDuration">5000</item>
<item name="postSplashScreenTheme">@style/Theme.App</item>
<item name="postSplashScreenTheme">@style/Theme.KaigiApp</item>
<item name="android:windowSplashScreenBrandingImage" tools:targetApi="s">@drawable/splash_branding_image</item>
</style>
</resources>
139 changes: 23 additions & 116 deletions app-android/src/test/java/io/github/droidkaigi/confsched/PreviewTest.kt
Original file line number Diff line number Diff line change
@@ -1,143 +1,50 @@
package io.github.droidkaigi.confsched

import android.content.res.Configuration
import android.os.LocaleList
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalConfiguration
import com.airbnb.android.showkase.models.Showkase
import com.airbnb.android.showkase.models.ShowkaseBrowserComponent
import com.github.takahirom.roborazzi.DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH
import com.github.takahirom.roborazzi.captureRoboImage
import io.github.droidkaigi.confsched.designsystem.preview.MultiLanguagePreviewDefinition
import io.github.droidkaigi.confsched.designsystem.preview.MultiThemePreviewDefinition
import io.github.droidkaigi.confsched.designsystem.preview.ShowkaseMultiplePreviewsWorkaround
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.ParameterizedRobolectricTestRunner
import java.util.Locale
import org.robolectric.RuntimeEnvironment
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

@RunWith(ParameterizedRobolectricTestRunner::class)
class PreviewTest(
val showkaseBrowserComponent: ShowkaseBrowserComponent,
private val preview: ComposablePreview<AndroidPreviewInfo>,
) {
object RobolectricPreviewInfosApplier {
fun applyFor(preview: ComposablePreview<AndroidPreviewInfo>) {
val uiMode =
when (preview.previewInfo.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) {
true -> "+night"
false -> "+notnight"
}
RuntimeEnvironment.setQualifiers(uiMode)
}
}

@Test
fun previewScreenshot() {
val filePath =
DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH + "/" + showkaseBrowserComponent.componentKey + ".png"
DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH + "/" + preview.methodName + ".png"
RobolectricPreviewInfosApplier.applyFor(preview)
captureRoboImage(
filePath,
filePath = filePath,
) {
ProvidesPreviewValues(group = showkaseBrowserComponent.group, componentKey = showkaseBrowserComponent.componentKey) {
showkaseBrowserComponent.component()
}
}
}

@Suppress("TestFunctionName")
@ShowkaseMultiplePreviewsWorkaround
@Composable
private fun ProvidesPreviewValues(group: String, componentKey: String, content: @Composable () -> Unit) {
val appliers = arrayListOf<(Configuration) -> Unit>()

if (isCustomGroup(group = group)) {
val previewValue = extractPreviewValues(group = group, componentKey)

when (group) {
MultiLanguagePreviewDefinition.Group -> {
appliers += { c ->
c.setLocales(newLocales(baseLocales = c.locales, previewValue = previewValue))
}
}
MultiThemePreviewDefinition.Group -> {
appliers += { c ->
c.uiMode = newUiMode(baseUiMode = c.uiMode, previewValue = previewValue)
}
}
}
}

val newConfiguration = appliers.fold(LocalConfiguration.current) { c, a -> c.apply(a) }

CompositionLocalProvider(LocalConfiguration provides newConfiguration) {
// Notify locale changes to lang() through the following invocation.
LocaleList.setDefault(LocalConfiguration.current.locales)

content()
}
}

/**
* Depends on the naming rule from Showkase.
* We must not include "_${group}_" in an original preview function name.
*/
@ShowkaseMultiplePreviewsWorkaround
private fun extractPreviewValues(group: String, componentKey: String): String {
val components = componentKey.split("_")

// _${group_ is expected here
val groupIndex = requireNotNull(components.indexOf(group).takeIf { it > 0 }) {
"Failed to extract a preview value for $group: $group is not found in $components"
}

val modifiedPreviewName = requireNotNull(components.getOrNull(groupIndex + 1)) {
"Failed to extract a preview value for $group: $components is unexpectedly aligned"
}

// ${preview_name}_${preview_value}_${...others}
val match = requireNotNull(Regex("\\w+-([\\w-]+)-[_\\w]+").matchEntire(modifiedPreviewName)) {
"Failed to extract a preview value for $group: no value was found in $modifiedPreviewName"
}

return requireNotNull(match.groupValues.getOrNull(1)) {
"Failed to extract a preview value for $group: this may be a development issue"
preview()
}
}

@ShowkaseMultiplePreviewsWorkaround
private fun newLocales(baseLocales: LocaleList, previewValue: String): LocaleList {
val locale = when (previewValue) {
MultiLanguagePreviewDefinition.English.Name -> {
MultiLanguagePreviewDefinition.English.Locale
}
MultiLanguagePreviewDefinition.Japanese.Name -> {
MultiLanguagePreviewDefinition.Japanese.Locale
}
else -> return baseLocales
}

return LocaleList(Locale.getAvailableLocales().first { it.toString() == locale })
}

@ShowkaseMultiplePreviewsWorkaround
private fun newUiMode(baseUiMode: Int, previewValue: String): Int {
val nightMode = when (previewValue) {
// FIXME
// MultiThemePreviewDefinition.DarkMode.Name -> {
// MultiThemePreviewDefinition.DarkMode.UiMode
// }
// MultiThemePreviewDefinition.LightMode.Name -> {
// MultiThemePreviewDefinition.LightMode.UiMode
// }
else -> baseUiMode
}

val currentNightMode = baseUiMode and Configuration.UI_MODE_NIGHT_MASK
return baseUiMode xor currentNightMode or nightMode
}

companion object {
fun isCustomGroup(group: String): Boolean {
return group != "Default Group"
}

@ParameterizedRobolectricTestRunner.Parameters
@JvmStatic
fun components(): Iterable<Array<Any?>> {
return Showkase.getMetadata().componentList.map { showkaseBrowserComponent ->
arrayOf(showkaseBrowserComponent)
}
fun components(): List<ComposablePreview<AndroidPreviewInfo>> {
return AndroidComposablePreviewScanner()
.scanPackageTrees("io.github.droidkaigi.confsched")
.getPreviews()
}
}
}

This file was deleted.

4 changes: 0 additions & 4 deletions build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,6 @@ gradlePlugin {
id = "droidkaigi.primitive.kmp.android.hilt"
implementationClass = "io.github.droidkaigi.confsched.primitive.KmpAndroidHiltPlugin"
}
register("kotlinMppAndroidShowkase") {
id = "droidkaigi.primitive.kmp.android.showkase"
implementationClass = "io.github.droidkaigi.confsched.primitive.KmpAndroidShowkasePlugin"
}
register("kotlinMppKotlinSerialization") {
id = "droidkaigi.primitive.kmp.serialization"
implementationClass = "io.github.droidkaigi.confsched.primitive.KotlinSerializationPlugin"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ class AndroidRoborazziPlugin : Plugin<Project> {
testImplementation(libs.library("androidxTestExtJunit"))
testImplementation(libs.library("roborazzi"))
testImplementation(libs.library("roborazziCompose"))
// For preview screenshot tests
implementation(libs.library("showkaseRuntime"))
ksp(libs.library("showkaseProcessor"))
}
}
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,7 @@ class KmpRoborazziPlugin : Plugin<Project> {
kotlin {
if (plugins.hasPlugin("com.android.library")) {
sourceSets.getByName("androidUnitTest") {
val kspConfiguration = configurations["kspAndroid"]
kspConfiguration.dependencies.add(
libs.library("showkaseProcessor").let {
DefaultExternalModuleDependency(
it.module.group,
it.module.name,
it.versionConstraint.requiredVersion
)
}
)
dependencies {
implementation(libs.library("showkaseRuntime"))
implementation(libs.library("androidxTestEspressoEspressoCore"))
implementation(libs.library("junit"))
implementation(libs.library("robolectric"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ class KoverEntryPointPlugin : Plugin<Project> {
filters {
excludes {
packages(
"com.airbnb.android.showkase",
"dagger.hilt.*",
"hilt_aggregated_deps",
)
Expand Down
1 change: 0 additions & 1 deletion core/designsystem/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ plugins {
id("droidkaigi.primitive.kmp.compose")
id("droidkaigi.primitive.kmp.android.hilt")
id("droidkaigi.primitive.detekt")
id("droidkaigi.primitive.kmp.android.showkase")
}

android.namespace = "io.github.droidkaigi.confsched.core.designsystem"
Expand Down
5 changes: 2 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ ktlint = "0.49.1"
kotlinxSerialization = "1.6.3"
ktor = "2.3.10"
roborazzi = "1.20.0"
showkase = "1.0.0-beta18"
ksp = "1.9.24-1.0.20"
firebaseBom = "33.1.0"
multiplatformFirebase = "1.8.1"
Expand All @@ -45,6 +44,7 @@ molecule = "1.4.1"
kover = "0.7.6"
androidxLifecycleProcess = "2.8.2"
skie = "0.8.2"
composablePreviewScanner = "0.1.1"

[libraries]
androidGradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
Expand Down Expand Up @@ -168,8 +168,7 @@ roborazzi = { module = "io.github.takahirom.roborazzi:roborazzi", version.ref =
roborazziCompose = { module = "io.github.takahirom.roborazzi:roborazzi-compose", version.ref = "roborazzi" }
roborazziIos = { module = "io.github.takahirom.roborazzi:roborazzi-compose-ios", version.ref = "roborazzi" }
roborazziRule = { module = "io.github.takahirom.roborazzi:roborazzi-junit-rule", version.ref = "roborazzi" }
showkaseRuntime = { group = "com.airbnb.android", name = "showkase", version.ref = "showkase" }
showkaseProcessor = { group = "com.airbnb.android", name = "showkase-processor", version.ref = "showkase" }
composablePreviewScanner = { module = "com.github.sergio-sastre.ComposablePreviewScanner:android", version.ref = "composablePreviewScanner" }

[plugins]
androidGradlePlugin = { id = "com.android.application", version.ref = "androidGradlePlugin" }
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencyResolutionManagement {
maven {
url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev/")
}
maven { url = uri("https://jitpack.io") }
}
}
rootProject.name = "conference-app-2024"
Expand Down

0 comments on commit 3427e85

Please sign in to comment.