Skip to content

Commit

Permalink
Merge pull request #445 from takahirom/takahirom/support-includePriva…
Browse files Browse the repository at this point in the history
…tePreviews-of-ComposablePreviewcanner/2024-07-23

Support includePrivatePreviews of ComposablePreviewScanner
  • Loading branch information
takahirom authored Jul 27, 2024
2 parents d71cc54 + 2519f46 commit f4d40a7
Show file tree
Hide file tree
Showing 12 changed files with 336 additions and 95 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,7 @@ If you are having trouble debugging your test, try Dump mode as follows.
# Experimental Compose Preview Support
Roborazzi provides support for generating screenshot tests and easy setup for Jetpack Compose Preview.
This support uses [ComposePreviewScanner](https://github.com/sergio-sastre/ComposablePreviewScanner) to scan the Composable Previews in your project.
This support uses [ComposablePreviewScanner](https://github.com/sergio-sastre/ComposablePreviewScanner) to scan the Composable Previews in your project.
## Generate Compose Preview screenshot tests
Expand Down Expand Up @@ -980,27 +980,29 @@ roborazzi {
enable = true
// The package names to scan for Composable Previews.
packages = listOf("com.example")
// The fully qualified class name of the custom test class that implements [com.github.takahirom.roborazzi.ComposePreviewTester].
testerQualifiedClassName = "com.example.MyCustomComposePreviewTester"
// robolectricConfig will be passed to Robolectric's @Config annotation in the generated test class.
// See https://robolectric.org/configuring/ for more information.
robolectricConfig = mapOf(
"sdk" to "[32]",
"qualifiers" to "RobolectricDeviceQualifiers.Pixel5",
)
// If true, the private previews will be included in the test.
includePrivatePreviews = true
// The fully qualified class name of the custom test class that implements [com.github.takahirom.roborazzi.ComposePreviewTester].
testerQualifiedClassName = "com.example.MyCustomComposePreviewTester"
}
}
```
## Manually adding Compose Preview screenshot tests
Roborazzi provides a helper function for ComposePreviewScanner.
Roborazzi provides a helper function for ComposablePreviewScanner.
You can add the following dependency to your project to use the helper function:
`testImplementation("io.github.takahirom.roborazzi:roborazzi-compose-preview-scanner-support:[version]")`
Then you can use the `ComposablePreview<AndroidPreviewInfo>.captureRoboImage()` function to capture the Composable Preview using the settings in Preview annotations.
To obtain the `ComposablePreview` object, please refer to [ComposePreviewScanner](https://github.com/sergio-sastre/ComposablePreviewScanner).
To obtain the `ComposablePreview` object, please refer to [ComposablePreviewScanner](https://github.com/sergio-sastre/ComposablePreviewScanner).
```kotlin
fun ComposablePreview<AndroidPreviewInfo>.captureRoboImage(
Expand All @@ -1021,11 +1023,11 @@ We are looking forward to your contributions to support more annotation options.
<!-- Generated by docs/topics/idea_plugin.md. Do not edit this file. -->
# Roborazzi IntelliJ IDEA and Android Studio Plugin
https://plugins.jetbrains.com/plugin/24561-roborazzi
[https://plugins.jetbrains.com/plugin/24561-roborazzi](https://plugins.jetbrains.com/plugin/24561-roborazzi)
Demo of Roborazzi IntelliJ IDEA and Android Studio Plugin.
https://github.com/takahirom/roborazzi/assets/1386930/bd3e8106-1971-4ec4-b764-3f1831999228
[https://github.com/takahirom/roborazzi/assets/1386930/bd3e8106-1971-4ec4-b764-3f1831999228](https://github.com/takahirom/roborazzi/assets/1386930/bd3e8106-1971-4ec4-b764-3f1831999228)
The Roborazzi plugin enhances your development workflow by automatically displaying screenshots related to Roborazzi's screenshot tests within IntelliJ IDEA. Easily view and verify visual outputs directly in your IDE, streamlining your testing and development process.
</div>
Expand Down
12 changes: 7 additions & 5 deletions docs/topics/preview_support.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Experimental Compose Preview Support

Roborazzi provides support for generating screenshot tests and easy setup for Jetpack Compose Preview.
This support uses [ComposePreviewScanner](https://github.com/sergio-sastre/ComposablePreviewScanner) to scan the Composable Previews in your project.
This support uses [ComposablePreviewScanner](https://github.com/sergio-sastre/ComposablePreviewScanner) to scan the Composable Previews in your project.

## Generate Compose Preview screenshot tests

Expand Down Expand Up @@ -30,27 +30,29 @@ roborazzi {
enable = true
// The package names to scan for Composable Previews.
packages = listOf("com.example")
// The fully qualified class name of the custom test class that implements [com.github.takahirom.roborazzi.ComposePreviewTester].
testerQualifiedClassName = "com.example.MyCustomComposePreviewTester"
// robolectricConfig will be passed to Robolectric's @Config annotation in the generated test class.
// See https://robolectric.org/configuring/ for more information.
robolectricConfig = mapOf(
"sdk" to "[32]",
"qualifiers" to "RobolectricDeviceQualifiers.Pixel5",
)
// If true, the private previews will be included in the test.
includePrivatePreviews = true
// The fully qualified class name of the custom test class that implements [com.github.takahirom.roborazzi.ComposePreviewTester].
testerQualifiedClassName = "com.example.MyCustomComposePreviewTester"
}
}
```

## Manually adding Compose Preview screenshot tests

Roborazzi provides a helper function for ComposePreviewScanner.
Roborazzi provides a helper function for ComposablePreviewScanner.
You can add the following dependency to your project to use the helper function:

`testImplementation("io.github.takahirom.roborazzi:roborazzi-compose-preview-scanner-support:[version]")`

Then you can use the `ComposablePreview<AndroidPreviewInfo>.captureRoboImage()` function to capture the Composable Preview using the settings in Preview annotations.
To obtain the `ComposablePreview` object, please refer to [ComposePreviewScanner](https://github.com/sergio-sastre/ComposablePreviewScanner).
To obtain the `ComposablePreview` object, please refer to [ComposablePreviewScanner](https://github.com/sergio-sastre/ComposablePreviewScanner).

```kotlin
fun ComposablePreview<AndroidPreviewInfo>.captureRoboImage(
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ webjar-material-design-icons = "4.0.0"
webjar-materialize = "1.0.0"
webjars-locator-lite = "0.0.4"

composable-preview-scanner = "0.1.2"
composable-preview-scanner = "0.1.3"

[libraries]
roborazzi = { module = "io.github.takahirom.roborazzi:roborazzi", version.ref = "roborazzi-for-replacing-by-include-build" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class GeneratePreviewTestTest {
checkHasImages()
}
}

@Test
fun whenKmpModuleAndRecordRunImagesShouldBeRecorded() {
RoborazziGradleRootProject(testProjectDir).previewModule.apply {
Expand All @@ -26,6 +27,40 @@ class GeneratePreviewTestTest {
checkHasImages()
}
}

@Test
fun whenDisablePreviewAndRecordRunImagesShouldNotBeRecorded() {
RoborazziGradleRootProject(testProjectDir).previewModule.apply {
buildGradle.enable = false

record()

checkNoImages()
}
}

@Test
fun whenCustomTesterAndRecordRunImagesShouldBeRecordedAndCanSeeJUnitLog() {
RoborazziGradleRootProject(testProjectDir).previewModule.apply {
buildGradle.useCustomTester = true

record {
itShouldHaveJUnitRuleLog()
}

checkHasImages()
}
}

@Test
fun whenIncludePrivatePreviewsAndRecordRunImagesShouldBeRecorded() {
RoborazziGradleRootProject(testProjectDir).previewModule.apply {
buildGradle.isIncludePrivatePreviews = true
record()

checkHasPrivatePreviewImages()
}
}
}

class PreviewModule(
Expand All @@ -35,6 +70,7 @@ class PreviewModule(
companion object {
val moduleName = "sample-generate-preview-tests"
}

val buildGradle = BuildGradle(testProjectDir)

class BuildGradle(private val projectFolder: TemporaryFolder) {
Expand All @@ -44,14 +80,8 @@ class PreviewModule(
val file =
projectFolder.root.resolve(PATH)
file.parentFile.mkdirs()
val roborazziExtension = """
roborazzi {
generateComposePreviewRobolectricTests {
enable = true
packages = listOf("com.github.takahirom.preview.tests")
}
}
""".trimIndent()

val roborazziExtension = createRoborazziExtension()
val androidBlock = """
android {
namespace = "com.github.takahirom.preview.tests"
Expand Down Expand Up @@ -89,7 +119,7 @@ class PreviewModule(
}
""".trimIndent()
val buildGradleText = if(isKmp)
val buildGradleText = if (isKmp)
"""
plugins {
kotlin("multiplatform")
Expand Down Expand Up @@ -122,6 +152,12 @@ class PreviewModule(
implementation(libs.junit)
implementation(libs.robolectric)
implementation(libs.composable.preview.scanner)
implementation(libs.androidx.compose.ui.test.junit4)
}
}
val androidDebug by creating {
dependencies {
implementation(libs.androidx.compose.ui.test.manifest)
}
}
Expand All @@ -144,7 +180,7 @@ class PreviewModule(
maven { url = uri("https://jitpack.io") }
}
""".trimIndent()
else """
else """
plugins {
id("com.android.application")
// id("com.android.library")
Expand All @@ -171,6 +207,8 @@ class PreviewModule(
testImplementation("io.github.takahirom.roborazzi:roborazzi-compose-preview-scanner-support:0.1.0")
testImplementation(libs.junit)
testImplementation(libs.robolectric)
testImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.test.manifest)
testImplementation(libs.composable.preview.scanner)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.test.espresso.core)
Expand All @@ -180,10 +218,44 @@ class PreviewModule(
buildGradleText.trimIndent()
)
}

var enable = true
var isIncludePrivatePreviews = false
var useCustomTester = false

private fun createRoborazziExtension(): String {
val includePrivatePreviewsExpr = if (isIncludePrivatePreviews) {
"""includePrivatePreviews = $isIncludePrivatePreviews"""
} else {
""
}
val customTesterExpr = if (useCustomTester) {
"""testerQualifiedClassName = "com.github.takahirom.sample.CustomPreviewTester""""
} else {
""
}
val roborazziExtension = """
roborazzi {
generateComposePreviewRobolectricTests {
enable = $enable
packages = listOf("com.github.takahirom.preview.tests")
$includePrivatePreviewsExpr
$customTesterExpr
}
}
""".trimIndent()
return roborazziExtension
}
}

fun record(checks: BuildResult.() -> Unit = {}) {
val result = runTask("recordRoborazziDebug")
result.checks()
}

fun record() {
runTask("recordRoborazziDebug")
fun BuildResult.itShouldHaveJUnitRuleLog() {
assert(output.contains("JUnit4TestLifecycleOptions starting"))
assert(output.contains("JUnit4TestLifecycleOptions finished"))
}

private fun runTask(
Expand All @@ -205,4 +277,24 @@ class PreviewModule(
println("images:" + images?.toList())
assert(images?.isNotEmpty() == true)
}

fun checkNoImages() {
val images = testProjectDir.root.resolve("$moduleName/build/outputs/roborazzi/").listFiles()
println("images:" + images?.toList())
checkResultCount(
testProjectDir.root.resolve("$moduleName/build/test-results/roborazzi/results-summary.json")
// All zero
)

assert(images?.isEmpty() == true)
}

fun checkHasPrivatePreviewImages() {
val privateImages =
testProjectDir.root.resolve("$moduleName/build/outputs/roborazzi/").listFiles()
.orEmpty()
.filter { it.name.contains("PreviewWithPrivate") }
println("images:" + privateImages.toList())
assert(privateImages.isNotEmpty() == true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -337,19 +337,7 @@ dependencies {
) {
val recordedFile =
testProjectDir.root.resolve("app/$buildDirName/test-results/roborazzi/results-summary.json")
val results = CaptureResults.fromJsonFile(recordedFile.absolutePath)
assert(results.resultSummary.recorded == recorded) {
"Expected count: $recorded, actual count: ${results.resultSummary.recorded} summary:${results.resultSummary}"
}
assert(results.resultSummary.added == added) {
"Expected count: $added, actual count: ${results.resultSummary.added} summary:${results.resultSummary}"
}
assert(results.resultSummary.changed == changed) {
"Expected count: $changed, actual count: ${results.resultSummary.changed} summary:${results.resultSummary}"
}
assert(results.resultSummary.unchanged == unchanged) {
"Expected count: $unchanged, actual count: ${results.resultSummary.unchanged} summary:${results.resultSummary}"
}
checkResultCount(recordedFile, recorded, added, changed, unchanged)
}

fun checkRecordedFileExists(path: String) {
Expand Down Expand Up @@ -646,3 +634,25 @@ class RoborazziTest {

val buildGradle = BuildGradle(testProjectDir)
}

fun checkResultCount(
recordedFile: File,
recorded: Int = 0,
added: Int = 0,
changed: Int = 0,
unchanged: Int = 0
) {
val results = CaptureResults.fromJsonFile(recordedFile.absolutePath)
assert(results.resultSummary.recorded == recorded) {
"Expected count: $recorded, actual count: ${results.resultSummary.recorded} summary:${results.resultSummary}"
}
assert(results.resultSummary.added == added) {
"Expected count: $added, actual count: ${results.resultSummary.added} summary:${results.resultSummary}"
}
assert(results.resultSummary.changed == changed) {
"Expected count: $changed, actual count: ${results.resultSummary.changed} summary:${results.resultSummary}"
}
assert(results.resultSummary.unchanged == unchanged) {
"Expected count: $unchanged, actual count: ${results.resultSummary.unchanged} summary:${results.resultSummary}"
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* This file is overwritten by the IntegrationTest.
*/

plugins {
id("com.android.application")
// id("com.android.library")
Expand Down Expand Up @@ -64,6 +68,7 @@ dependencies {
testImplementation(libs.junit)
testImplementation(libs.robolectric)
testImplementation(libs.composable.preview.scanner)
testImplementation(libs.androidx.compose.ui.test.junit4)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.test.espresso.core)
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ fun PreviewDarkMode() {
}
}

@Preview
@Composable
private fun PreviewWithPrivate() {
val isSystemInDarkTheme = isSystemInDarkTheme()
MaterialTheme {
Card(
Modifier
.width(180.dp)
) {
Text(
modifier = Modifier.padding(8.dp),
text = "This is private preview"
)
}
}
}

@Preview(
name = "Preview Name",
// These properties are not supported by Roborazzi yet.
Expand Down
Loading

0 comments on commit f4d40a7

Please sign in to comment.