Skip to content

Commit

Permalink
Merge pull request #555 from takahirom/takahirom/add-alertdialog-test…
Browse files Browse the repository at this point in the history
…/2024-11-15

Fix alert dialog support
  • Loading branch information
takahirom authored Dec 15, 2024
2 parents 46c9b48 + d766c55 commit 371cce6
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ class CustomPreviewTester : ComposePreviewTester<AndroidPreviewInfo> by AndroidC
composeTestRule.setContent {
preview()
}
composeTestRule.onRoot().captureRoboImage("${roborazziSystemPropertyOutputDirectory()}/${preview.methodName}.png")
composeTestRule.onRoot().captureRoboImage("${roborazziSystemPropertyOutputDirectory()}/${preview.methodName}.${provideRoborazziContext().imageExtension}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ class AndroidComposePreviewTester : ComposePreviewTester<AndroidPreviewInfo> {
preview.declaringClass,
createScreenshotIdFor(preview)
)
val filePath = "$pathPrefix$name.png"
val filePath = "$pathPrefix$name.${provideRoborazziContext().imageExtension}"
preview.captureRoboImage(filePath)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.takahirom.roborazzi

import android.annotation.SuppressLint
import android.view.ViewGroup
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
Expand Down Expand Up @@ -101,12 +102,17 @@ private fun ActivityScenario<out ComponentActivity>.captureRoboImage(

onActivity { activity ->
activity.setContent(content = { content() })

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(file, roborazziOptions)
captureScreenIfMultipleWindows(
file = file,
roborazziOptions = roborazziOptions,
captureSingleComponent = {
val composeView = activity.window.decorView
.findViewById<ViewGroup>(android.R.id.content)
.getChildAt(0) as ComposeView
@SuppressLint("VisibleForTests")
val viewRootForTest = composeView.getChildAt(0) as ViewRootForTest
viewRootForTest.view.captureRoboImage(file, roborazziOptions)
}
)
}
}
87 changes: 67 additions & 20 deletions roborazzi/src/main/java/com/github/takahirom/roborazzi/Roborazzi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -144,18 +144,22 @@ fun captureScreenRoboImage(
// Views needs to be laid out before we can capture them
Espresso.onIdle()

val rootsOracle = RootsOracle_Factory({ Looper.getMainLooper() })
.get()
// Invoke rootOracle.listActiveRoots() via reflection
val listActiveRoots = rootsOracle.javaClass.getMethod("listActiveRoots")
listActiveRoots.isAccessible = true
@Suppress("UNCHECKED_CAST") val roots: List<Root> = listActiveRoots.invoke(rootsOracle) as List<Root>
val roots: List<Root> = fetchRobolectricWindowRoots()
debugLog {
"captureScreenRoboImage roots: ${roots.joinToString("\n") { it.toString() }}"
}
captureRootsInternal(roots, roborazziOptions, file)
}

@InternalRoborazziApi
fun captureRootsInternal(
roots: List<Root>,
roborazziOptions: RoborazziOptions,
file: File
) {
capture(
rootComponent = RoboComponent.Screen(
rootsOrderByDepth = roots.sortedBy { it.windowLayoutParams.get()?.type },
rootsOrderByDepth = roots,
roborazziOptions = roborazziOptions
),
roborazziOptions = roborazziOptions,
Expand All @@ -169,6 +173,43 @@ fun captureScreenRoboImage(
}
}

@InternalRoborazziApi
fun captureScreenIfMultipleWindows(
file: File,
roborazziOptions: RoborazziOptions,
captureSingleComponent: () -> Unit
) {
if (fetchRobolectricWindowRoots().size > 1) {
roborazziReportLog(
"It seems that there are multiple windows." +
"We capture all windows using captureScreenRoboImage(). " +
"We can add a flag to disable this behavior so please let us know if you need it."
)
captureScreenRoboImage(file, roborazziOptions)
} else {
captureSingleComponent()
}
}

@InternalRoborazziApi
fun fetchRobolectricWindowRoots(): List<Root> {
try {
@Suppress("INACCESSIBLE_TYPE") val rootsOracle = RootsOracle_Factory({ Looper.getMainLooper() })
.get()
// Invoke rootOracle.listActiveRoots() via reflection
@Suppress("INACCESSIBLE_TYPE") val listActiveRoots =
rootsOracle.javaClass.getMethod("listActiveRoots")
listActiveRoots.isAccessible = true
@Suppress("UNCHECKED_CAST", "INACCESSIBLE_TYPE") val roots: List<Root> =
(listActiveRoots.invoke(rootsOracle) as List<Root>
).sortedBy { it.windowLayoutParams.get()?.type }
return roots
} catch (e: Throwable) {
e.printStackTrace()
return emptyList()
}
}

fun Bitmap.captureRoboImage(
filePath: String = DefaultFileNameGenerator.generateFilePath(),
roborazziOptions: RoborazziOptions = provideRoborazziContext().options,
Expand Down Expand Up @@ -278,20 +319,26 @@ fun SemanticsNodeInteraction.captureRoboImage(
roborazziOptions: RoborazziOptions = provideRoborazziContext().options,
) {
if (!roborazziOptions.taskType.isEnabled()) return
capture(
rootComponent = RoboComponent.Compose(
node = this.fetchSemanticsNode("fail to captureRoboImage"),
roborazziOptions = roborazziOptions
),
captureScreenIfMultipleWindows(
file = file,
roborazziOptions = roborazziOptions,
) { canvas ->
processOutputImageAndReportWithDefaults(
canvas = canvas,
goldenFile = file,
roborazziOptions = roborazziOptions
)
canvas.release()
}
captureSingleComponent = {
capture(
rootComponent = RoboComponent.Compose(
node = this.fetchSemanticsNode("fail to captureRoboImage"),
roborazziOptions = roborazziOptions
),
roborazziOptions = roborazziOptions,
) { canvas ->
processOutputImageAndReportWithDefaults(
canvas = canvas,
goldenFile = file,
roborazziOptions = roborazziOptions
)
canvas.release()
}
}
)
}

fun SemanticsNodeInteraction.captureRoboGif(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package com.github.takahirom.preview.tests

import android.content.res.Configuration
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ElevatedCard
Expand Down Expand Up @@ -111,6 +114,36 @@ fun PreviewWithProperties2() {
}
}

@Preview
@Composable
fun PreviewDialog() {
MaterialTheme {
AlertDialog(
onDismissRequest = {},
confirmButton = @Composable { Text("Confirm") },
text = @Composable { Text("Generate Preview Test Sample!") }
)
}
}

@Preview
@Composable
fun PreviewDialogSurface() {
MaterialTheme {
Surface {
Box(Modifier.height(300.dp)) {
Text("Hello, World!")
}
AlertDialog(
onDismissRequest = {},
confirmButton = @Composable { Text("Confirm") },
text = @Composable { Text("Generate Preview Test Sample!") }
)
}
}
}


@Preview(
name = "Preview width & height large",
widthDp = 2000,
Expand Down Expand Up @@ -212,4 +245,4 @@ fun PreviewShowBackgroundWithBackgroundColor() {
text = "Hello, World!"
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.github.takahirom.sample
package com.github.takahirom

import org.junit.Assert.assertEquals
import org.junit.Test

import org.junit.Assert.*

/**
* Example local unit test, which will execute on the development machine (host).
*
Expand Down

0 comments on commit 371cce6

Please sign in to comment.