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

Add support for heightDp, widthDp, showBackground, backgroundColor #576

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ fun ComposablePreview<AndroidPreviewInfo>.captureRoboImage(
) {
val composablePreview = this
composablePreview.applyToRobolectricConfiguration()
captureRoboImage(filePath = filePath, roborazziOptions = roborazziOptions) {
captureSizedRoboImage(
filePath = filePath,
roborazziOptions = roborazziOptions,
widthDp = composablePreview.previewInfo.widthDp,
heightDp = composablePreview.previewInfo.heightDp,
showBackground = composablePreview.previewInfo.showBackground,
backgroundColor = composablePreview.previewInfo.backgroundColor
) {
composablePreview()
}
}
Expand Down
1 change: 1 addition & 0 deletions roborazzi-compose/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies {

testImplementation libs.androidx.compose.runtime
compileOnly libs.androidx.compose.ui
compileOnly libs.androidx.compose.foundation
compileOnly libs.androidx.activity.compose
compileOnly libs.robolectric
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
package com.github.takahirom.roborazzi

import android.app.Activity
import android.app.Application
import android.content.ComponentName
import android.view.ViewGroup
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewRootForTest
import androidx.compose.ui.unit.dp
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import org.robolectric.Shadows
import org.robolectric.shadows.ShadowDisplay
import java.io.File
import kotlin.math.roundToInt

fun captureRoboImage(
filePath: String = DefaultFileNameGenerator.generateFilePath(),
Expand All @@ -30,7 +40,7 @@ fun captureRoboImage(
content: @Composable () -> Unit,
) {
if (!roborazziOptions.taskType.isEnabled()) return
registerRoborazziActivityToRobolectricIfNeeded()
registerRoborazziActivityToRobolectricIfNeeded(RoborazziTransparentActivity::class.java)
val activityScenario = ActivityScenario.launch(RoborazziTransparentActivity::class.java)
activityScenario.use {
activityScenario.onActivity { activity ->
Expand All @@ -51,13 +61,15 @@ fun captureRoboImage(
/**
* Workaround for https://github.com/takahirom/roborazzi/issues/100
*/
private fun registerRoborazziActivityToRobolectricIfNeeded() {
internal fun registerRoborazziActivityToRobolectricIfNeeded(
activityClass: Class<out Activity>
) {
try {
val appContext: Application = ApplicationProvider.getApplicationContext()
Shadows.shadowOf(appContext.packageManager).addActivityIfNotPresent(
ComponentName(
appContext.packageName,
RoborazziTransparentActivity::class.java.name,
activityClass::class.java.name,
)
)
} catch (e: ClassNotFoundException) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.github.takahirom.roborazzi

import android.app.Activity
import android.graphics.Color
import android.view.ViewGroup
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewRootForTest
import androidx.compose.ui.unit.dp
import androidx.test.core.app.ActivityScenario
import org.robolectric.Shadows
import org.robolectric.shadows.ShadowDisplay
import java.io.File
import kotlin.math.roundToInt

fun captureSizedRoboImage(
widthDp: Int,
heightDp: Int,
showBackground: Boolean,
backgroundColor: Long,
filePath: String,
roborazziOptions: RoborazziOptions = provideRoborazziContext().options,
content: @Composable () -> Unit,
) {
if (!roborazziOptions.taskType.isEnabled()) return
registerRoborazziActivityToRobolectricIfNeeded(ComponentActivity::class.java)

val activityScenario = ActivityScenario.launch(ComponentActivity::class.java)

activityScenario.onActivity {
it.setShadowDisplay(
widthDp = widthDp,
heightDp = heightDp
)
}

activityScenario.use {
activityScenario.onActivity { activity ->

activity.setBackgroundColor(
showBackground = showBackground,
backgroundColor = backgroundColor
)

activity.setContent(
content = content.withSize(widthDp = widthDp, heightDp = heightDp)
)

val composeView = activity.window.decorView
.findViewById<ViewGroup>(android.R.id.content)
.getChildAt(0) as ComposeView

val viewRootForTest = composeView.getChildAt(0) as ViewRootForTest
viewRootForTest.view.captureRoboImage(filePath, roborazziOptions)
}

// Closing the activity is necessary to prevent memory leaks.
// If multiple captureRoboImage calls occur in a single test,
// they can lead to an activity leak.
}
}

fun Activity.setBackgroundColor(
showBackground: Boolean,
backgroundColor: Long,
) {
when (showBackground) {
false -> window.decorView.setBackgroundColor(Color.TRANSPARENT)
true -> if (backgroundColor != 0L) {
window.decorView.setBackgroundColor(backgroundColor.toInt())
}
}
}

fun Activity.setShadowDisplay(
widthDp: Int,
heightDp: Int
) {
if (widthDp > 0 || heightDp > 0) {
val display = ShadowDisplay.getDefaultDisplay()
val density = resources.displayMetrics.density
if (widthDp > 0) {
widthDp.let {
val widthPx = (widthDp * density).roundToInt()
Shadows.shadowOf(display).setWidth(widthPx)
}
}
if (heightDp > 0) {
heightDp.let {
val heightPx = (heightDp * density).roundToInt()
Shadows.shadowOf(display).setHeight(heightPx)
}
}
recreate()
}
}

/**
* WARNING:
* For this to work, it requires that the Display is within the widthDp and heightDp dimensions
*/
private fun (@Composable () -> Unit).withSize(
widthDp: Int,
heightDp: Int,
): @Composable () -> Unit {
val resizedPreview = @Composable {
val modifier = when {
widthDp > 0 && heightDp > 0 -> Modifier.size(widthDp.dp, heightDp.dp)
widthDp > 0 -> Modifier.width(widthDp.dp)
heightDp > 0 -> Modifier.height(heightDp.dp)
else -> Modifier
}
Box(modifier = modifier) { this@withSize() }
}
return resizedPreview
}
Loading