diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt index abc9fa69..248894ae 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt @@ -495,12 +495,22 @@ private fun ApplicationVariant.configureSnapshotsTasks( "io.github.sergio-sastre.ComposablePreviewScanner:android:0.8.1", ) + val paparazziMajorVersion = + project.provider { + val dep = + project.configurations.findByName("testImplementation")?.allDependencies?.find { + it.group == "app.cash.paparazzi" && it.name == "paparazzi" + } + parseMajorVersion(dep?.version) + } + val generateTask = GenerateSnapshotTestsTask.register( project, extension.snapshots, android, this@configureSnapshotsTasks, + paparazziMajorVersion, ) if (AgpVersions.isAGP90(AgpVersions.CURRENT)) { @@ -537,6 +547,9 @@ private fun ApplicationVariant.configureSnapshotsTasks( } } +internal fun parseMajorVersion(version: String?, defaultVersion: Int = 2): Int = + version?.trimStart()?.takeWhile { it.isDigit() }?.toIntOrNull() ?: defaultVersion + /** * Configure the upload AAB and APK tasks and set them up as finalizers on the respective producer * tasks diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/snapshot/GenerateSnapshotTestsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/snapshot/GenerateSnapshotTestsTask.kt index b86651df..25eb9df0 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/snapshot/GenerateSnapshotTestsTask.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/snapshot/GenerateSnapshotTestsTask.kt @@ -10,6 +10,7 @@ import org.gradle.api.Project import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.Optional @@ -31,6 +32,8 @@ abstract class GenerateSnapshotTestsTask : DefaultTask() { @get:Input @get:Optional abstract val theme: Property + @get:Input abstract val paparazziMajorVersion: Property + @get:OutputDirectory abstract val outputDir: DirectoryProperty @TaskAction @@ -48,6 +51,7 @@ abstract class GenerateSnapshotTestsTask : DefaultTask() { includePrivatePreviews = includePrivatePreviews.get(), packageTrees = packageTrees.get(), theme = theme.orNull, + paparazziMajorVersion = paparazziMajorVersion.get(), ) File(packageDir, "$CLASS_NAME.kt").writeText(content) logger.lifecycle("Generated snapshot test: ${packageDir.absolutePath}/$CLASS_NAME.kt") @@ -62,6 +66,7 @@ abstract class GenerateSnapshotTestsTask : DefaultTask() { extension: SnapshotsExtension, android: BaseExtension, variant: ApplicationVariant, + paparazziMajorVersion: Provider, ): TaskProvider { return project.tasks.register( "sentryGenerateSnapshotsTests${variant.name.capitalized}", @@ -69,6 +74,7 @@ abstract class GenerateSnapshotTestsTask : DefaultTask() { ) { task -> task.includePrivatePreviews.set(extension.includePrivatePreviews) task.theme.set(extension.theme) + task.paparazziMajorVersion.value(paparazziMajorVersion) // Fall back to the Android namespace when the user doesn't configure packageTrees // TODO do we actually need this? task.packageTrees.set( @@ -87,6 +93,7 @@ abstract class GenerateSnapshotTestsTask : DefaultTask() { includePrivatePreviews: Boolean, packageTrees: List, theme: String? = null, + paparazziMajorVersion: Int = 2, ): String { val includePrivateExpr = if (includePrivatePreviews) "\n .includePrivatePreviews()" else "" @@ -217,17 +224,17 @@ private class TestNameOverrideHandler( private object PaparazziPreviewRule { const val UNDEFINED_API_LEVEL = -1 - const val MAX_API_LEVEL = 36 fun createFor(preview: ComposablePreview): Paparazzi { val previewInfo = preview.previewInfo - val previewApiLevel = when (previewInfo.apiLevel == UNDEFINED_API_LEVEL) { - true -> MAX_API_LEVEL - false -> previewInfo.apiLevel + val env = detectEnvironment() + val environment = when (previewInfo.apiLevel == UNDEFINED_API_LEVEL) { + true -> env + false -> env.copy(compileSdkVersion = previewInfo.apiLevel) } val tolerance = 0.0 return Paparazzi( - environment = detectEnvironment().copy(compileSdkVersion = previewApiLevel), + environment = environment, deviceConfig = DeviceConfigBuilder.build(preview.previewInfo), ${if (theme != null) "theme = \"$theme\"," else ""} supportsRtl = true, @@ -240,7 +247,7 @@ private object PaparazziPreviewRule { snapshotHandler = TestNameOverrideHandler( when (System.getProperty("paparazzi.test.verify")?.toBoolean() == true) { true -> SnapshotVerifier(maxPercentDifference = tolerance) - false -> HtmlReportWriter(maxPercentDifference = tolerance) + false -> ${if (paparazziMajorVersion >= 2) "HtmlReportWriter(maxPercentDifference = tolerance)" else "HtmlReportWriter()"} } ), maxPercentDifference = tolerance, diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/snapshot/GenerateSnapshotTestsTaskTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/snapshot/GenerateSnapshotTestsTaskTest.kt index 1f63f513..31832fff 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/snapshot/GenerateSnapshotTestsTaskTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/snapshot/GenerateSnapshotTestsTaskTest.kt @@ -1,6 +1,8 @@ package io.sentry.android.gradle.snapshot +import io.sentry.android.gradle.parseMajorVersion import java.io.File +import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue import org.gradle.testfixtures.ProjectBuilder @@ -124,11 +126,47 @@ class GenerateSnapshotTestsTaskTest { ) } + @Test + fun `parseMajorVersion extracts major from standard semver`() { + assertEquals(1, parseMajorVersion("1.3.5")) + assertEquals(2, parseMajorVersion("2.0.0-alpha01")) + } + + @Test + fun `parseMajorVersion extracts major from dynamic versions`() { + assertEquals(1, parseMajorVersion("1.+")) + assertEquals(2, parseMajorVersion("2.+")) + } + + @Test + fun `parseMajorVersion returns default for unparseable versions`() { + assertEquals(2, parseMajorVersion("latest.release")) + assertEquals(2, parseMajorVersion(null)) + assertEquals(2, parseMajorVersion("+")) + } + + @Test + fun `generated file uses HtmlReportWriter without maxPercentDifference for paparazzi 1`() { + val content = generateAndRead(packageTrees = listOf("com.example"), paparazziMajorVersion = 1) + + assertTrue(content.contains("HtmlReportWriter()")) + assertFalse(content.contains("HtmlReportWriter(maxPercentDifference")) + } + + @Test + fun `generated file uses HtmlReportWriter with maxPercentDifference for paparazzi 2`() { + val content = generateAndRead(packageTrees = listOf("com.example"), paparazziMajorVersion = 2) + + assertTrue(content.contains("HtmlReportWriter(maxPercentDifference = tolerance)")) + assertFalse(content.contains("HtmlReportWriter()")) + } + private fun generateAndRead( packageTrees: List, includePrivatePreviews: Boolean = false, + paparazziMajorVersion: Int = 2, ): String { - val task = createTask(packageTrees, includePrivatePreviews) + val task = createTask(packageTrees, includePrivatePreviews, paparazziMajorVersion) task.generate() val file = File(task.outputDir.get().asFile, "io/sentry/snapshot/ComposablePreviewSnapshotTest.kt") @@ -138,12 +176,14 @@ class GenerateSnapshotTestsTaskTest { private fun createTask( packageTrees: List, includePrivatePreviews: Boolean = false, + paparazziMajorVersion: Int = 2, ): GenerateSnapshotTestsTask { val project = ProjectBuilder.builder().build() return project.tasks .register("testGenerateSnapshotTests", GenerateSnapshotTestsTask::class.java) { task -> task.includePrivatePreviews.set(includePrivatePreviews) task.packageTrees.set(packageTrees) + task.paparazziMajorVersion.set(paparazziMajorVersion) task.outputDir.set(tmpDir.newFolder("output")) } .get()