Skip to content

Commit

Permalink
Merge pull request #612 from pedromfmachado/add-activity-theme-option
Browse files Browse the repository at this point in the history
Added RoborazziComposeActivityThemeOption
  • Loading branch information
takahirom authored Dec 19, 2024
2 parents ad3eb0c + 2efd214 commit 1ebc786
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ fun ComposablePreview<AndroidPreviewInfo>.toRoborazziComposeOptions(): Roborazzi
uiMode(previewInfo.uiMode)
previewDevice(previewInfo.device)
fontScale(previewInfo.fontScale)

/*
We don't specify `inspectionMode` by default.
The default value for `inspectionMode` in Compose is `false`.
This is to maintain higher fidelity in tests.
If you encounter issues integrating the library, you can set `inspectionMode` to `true`.
inspectionMode(true)
*/
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ internal fun registerActivityToRobolectricIfNeeded() {
val appContext: Application = ApplicationProvider.getApplicationContext()
Shadows.shadowOf(appContext.packageManager).addActivityIfNotPresent(
ComponentName(
appContext.packageName,
RoborazziTransparentActivity::class.java.name,
appContext,
RoborazziActivity::class.java,
)
)
} catch (e: ClassNotFoundException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewRootForTest
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import java.io.File


Expand Down Expand Up @@ -59,7 +60,7 @@ fun captureRoboImage(
content: @Composable () -> Unit,
) {
if (!roborazziOptions.taskType.isEnabled()) return
launchRoborazziTransparentActivity { activityScenario ->
launchRoborazziActivity(roborazziComposeOptions) { activityScenario ->
val configuredContent = roborazziComposeOptions
.configured(activityScenario) {
content()
Expand All @@ -70,19 +71,27 @@ fun captureRoboImage(
}
}

private fun launchRoborazziTransparentActivity(block: (ActivityScenario<RoborazziTransparentActivity>) -> Unit = {}) {
registerActivityToRobolectricIfNeeded()

val activityScenario = ActivityScenario.launch(RoborazziTransparentActivity::class.java)
private fun launchRoborazziActivity(roborazziComposeOptions: RoborazziComposeOptions,block: (ActivityScenario<out ComponentActivity>) -> Unit = {}) {
val activityScenario = roborazziComposeOptions.createScenario {
createActivityScenario(theme = android.R.style.Theme_Translucent_NoTitleBar_Fullscreen)
}

// 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.
return activityScenario.use { block(activityScenario) }
}

internal fun createActivityScenario(theme: Int): ActivityScenario<out ComponentActivity> {
registerActivityToRobolectricIfNeeded()
return ActivityScenario.launch(RoborazziActivity.createIntent(
context = ApplicationProvider.getApplicationContext(),
theme = theme
))
}


private fun ActivityScenario<out ComponentActivity>.captureRoboImage(
private fun ActivityScenario<out androidx.activity.ComponentActivity>.captureRoboImage(
filePath: String,
roborazziOptions: RoborazziOptions = provideRoborazziContext().options,
content: @Composable () -> Unit,
Expand Down Expand Up @@ -115,4 +124,4 @@ private fun ActivityScenario<out ComponentActivity>.captureRoboImage(
}
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.github.takahirom.roborazzi
import android.app.Activity
import android.content.res.Configuration
import android.graphics.Color
import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
Expand Down Expand Up @@ -32,24 +33,36 @@ interface RoborazziComposeActivityScenarioOption : RoborazziComposeOption {
fun configureWithActivityScenario(scenario: ActivityScenario<out Activity>)
}

@ExperimentalRoborazziApi
interface RoborazziComposeActivityScenarioCreatorOption : RoborazziComposeOption {
@Suppress("RemoveRedundantQualifierName")
fun createScenario(chain: () -> ActivityScenario<out androidx.activity.ComponentActivity>): ActivityScenario<out androidx.activity.ComponentActivity>
}

@ExperimentalRoborazziApi
interface RoborazziComposeComposableOption : RoborazziComposeOption {
fun configureWithComposable(content: @Composable () -> Unit): @Composable () -> Unit
}

@ExperimentalRoborazziApi
class RoborazziComposeOptions private constructor(
private val activityScenarioCreatorOptions: List<RoborazziComposeActivityScenarioCreatorOption>,
private val activityScenarioOptions: List<RoborazziComposeActivityScenarioOption>,
private val composableOptions: List<RoborazziComposeComposableOption>,
private val setupOptions: List<RoborazziComposeSetupOption>
) {
class Builder {
private val activityScenarioOptions =
mutableListOf<RoborazziComposeActivityScenarioOption>()
private val activityScenarioCreatorOptions =
mutableListOf<RoborazziComposeActivityScenarioCreatorOption>()
private val composableOptions = mutableListOf<RoborazziComposeComposableOption>()
private val setupOptions = mutableListOf<RoborazziComposeSetupOption>()

fun addOption(option: RoborazziComposeOption): Builder {
if (option is RoborazziComposeActivityScenarioCreatorOption) {
activityScenarioCreatorOptions.add(option)
}
if (option is RoborazziComposeActivityScenarioOption) {
activityScenarioOptions.add(option)
}
Expand All @@ -64,6 +77,7 @@ class RoborazziComposeOptions private constructor(

fun build(): RoborazziComposeOptions {
return RoborazziComposeOptions(
activityScenarioCreatorOptions = activityScenarioCreatorOptions,
activityScenarioOptions = activityScenarioOptions,
composableOptions = composableOptions,
setupOptions = setupOptions
Expand All @@ -74,12 +88,20 @@ class RoborazziComposeOptions private constructor(
fun builder(): Builder {
return Builder()
.apply {
activityScenarioCreatorOptions.forEach { addOption(it) }
activityScenarioOptions.forEach { addOption(it) }
composableOptions.forEach { addOption(it) }
setupOptions.forEach { addOption(it) }
}
}

@ExperimentalRoborazziApi
fun createScenario(chain: () -> ActivityScenario<out androidx.activity.ComponentActivity>): ActivityScenario<out androidx.activity.ComponentActivity> {
return activityScenarioCreatorOptions.fold(chain) { acc, option ->
{ option.createScenario(acc) }
}()
}

@ExperimentalRoborazziApi
fun configured(
activityScenario: ActivityScenario<out Activity>,
Expand Down Expand Up @@ -261,3 +283,16 @@ data class RoborazziComposeInspectionModeOption(private val inspectionMode: Bool
}
}
}

@ExperimentalRoborazziApi
fun RoborazziComposeOptions.Builder.theme(themeResId: Int): RoborazziComposeOptions.Builder {
return addOption(RoborazziComposeActivityThemeOption(themeResId))
}

@ExperimentalRoborazziApi
data class RoborazziComposeActivityThemeOption(private val themeResId: Int) :
RoborazziComposeActivityScenarioCreatorOption {
override fun createScenario(chain: () -> ActivityScenario<out ComponentActivity>): ActivityScenario<out ComponentActivity> {
return createActivityScenario(themeResId)
}
}
9 changes: 6 additions & 3 deletions roborazzi/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<activity android:name=".RoborazziTransparentActivity"
android:theme="@style/RoborazziTransparentTheme"/>
<activity
android:name=".RoborazziTransparentActivity"
android:theme="@style/RoborazziTransparentTheme" />
<activity android:name=".RoborazziActivity" />
</application>
</manifest>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
package com.github.takahirom.roborazzi

import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity

class RoborazziTransparentActivity: ComponentActivity() {
@Deprecated("Use RoborazziActivity instead", ReplaceWith("RoborazziActivity"), level = DeprecationLevel.ERROR)
class RoborazziTransparentActivity: RoborazziActivity()

open class RoborazziActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(android.R.style.Theme_Translucent_NoTitleBar_Fullscreen)
setTheme(intent.getIntExtra(EXTRA_THEME, android.R.style.Theme_Translucent_NoTitleBar_Fullscreen))
super.onCreate(savedInstanceState)
}
companion object {
const val EXTRA_THEME = "EXTRA_THEME"
fun createIntent(context: Context, theme: Int = android.R.style.Theme_Translucent_NoTitleBar_Fullscreen): Intent {
return Intent(context, RoborazziActivity::class.java).apply {
putExtra(EXTRA_THEME, theme)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.github.takahirom.roborazzi.RoborazziComposeOptions
import com.github.takahirom.roborazzi.captureRoboImage
import com.github.takahirom.roborazzi.fontScale
import com.github.takahirom.roborazzi.roborazziSystemPropertyOutputDirectory
import com.github.takahirom.roborazzi.theme
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
Expand All @@ -39,6 +40,18 @@ class ComposeLambdaTest {
}
}

@OptIn(ExperimentalRoborazziApi::class)
@Test
fun whenNonTransparentThemeItShouldHaveNonTransparentBackground() {
captureRoboImage(
roborazziComposeOptions = RoborazziComposeOptions {
theme(android.R.style.Theme_Material_Light)
}
) {
Text("This composable function should NOT have transparent background!")
}
}

@OptIn(ExperimentalRoborazziApi::class)
@Test
fun captureComposeLambdaImageWithRoborazziComposeOptions() {
Expand All @@ -48,15 +61,6 @@ class ComposeLambdaTest {
// We have several options to configure the test environment.
fontScale(2f)

/*
We don't specify `inspectionMode` by default.
The default value for `inspectionMode` in Compose is `false`.
This is to maintain higher fidelity in tests.
If you encounter issues integrating the library, you can set `inspectionMode` to `true`.
inspectionMode(true)
*/

// We can also configure the activity scenario and the composable content.
addOption(
object : RoborazziComposeComposableOption,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ import androidx.compose.ui.test.performClick
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.github.takahirom.roborazzi.Dump
import com.github.takahirom.roborazzi.RoborazziActivity
import com.github.takahirom.roborazzi.RoborazziOptions
import com.github.takahirom.roborazzi.RoborazziRule
import com.github.takahirom.roborazzi.RoborazziTransparentActivity
import com.github.takahirom.roborazzi.captureRoboImage
import org.junit.Rule
import org.junit.Test
Expand All @@ -48,7 +48,7 @@ import org.robolectric.annotation.GraphicsMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class ComposeTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<RoborazziTransparentActivity>()
val composeTestRule = createAndroidComposeRule<RoborazziActivity>()

@get:Rule
val roborazziRule = RoborazziRule(
Expand Down

0 comments on commit 1ebc786

Please sign in to comment.