Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce compose preview scanner #56

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,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
Loading