From 9d430376538f755e86ada6a1ea0eb06148923b8e Mon Sep 17 00:00:00 2001 From: Nathan Shurtz Date: Tue, 11 Aug 2026 14:52:29 -0500 Subject: [PATCH 1/4] Fix KotlinReflectionInternalError for value-class @PreviewParameter A @Preview whose @PreviewParameter provider yields a Kotlin value class (e.g. PreviewParameterProvider) crashed both getPreviews() and invocation. The scanner reflected/invoked the composable via kotlin-reflect, whose ValueClassAwareCaller asserts the Kotlin-descriptor arity equals the JVM method arity. That fails for @Composable functions because the Compose compiler appends synthetic Composer/changed/default-mask parameters that aren't in the descriptor (3 != 1). Non-value-class previews skip that caller, which is why only value-class previews crashed. Switch both paths to androidx.compose.runtime.reflect.ComposableMethod (java.lang.reflect, Compose-ABI aware), which accounts for the synthetic params: - ProvideComposablePreview.methodParametersTypeAsString: derive the real parameter count via asComposableMethod() instead of kotlinFunction. - ComposablePreviewInvocationHandler: invoke via ComposableMethod.invoke, unboxing value-class arguments; preserve the PreviewWrapper path, bound-instance invocation, and the "@PreviewParameter must be first" guard. Add a PreviewParameterProvider fixture (in its own top-level package) with an enumeration/toString test and a Paparazzi render test; the suite previously had no value-class coverage. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../ComposablePreviewInvocationHandler.kt | 163 +++++++++--------- .../core/preview/ProvideComposablePreview.kt | 24 +-- .../previewparameters/android/Composables.kt | 27 +++ .../ComposablePreviewToStringTest.kt | 22 +++ ...zziValueClassPreviewParameterInvokeTest.kt | 64 +++++++ ...reviewparameterpreview-8feqmps_float_0.png | Bin 0 -> 1697 bytes ...reviewparameterpreview-8feqmps_float_1.png | Bin 0 -> 1635 bytes 7 files changed, 210 insertions(+), 90 deletions(-) create mode 100644 tests/src/main/java/valueclass/previewparameters/android/Composables.kt create mode 100644 tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/paparazzi/runtime/PaparazziValueClassPreviewParameterInvokeTest.kt create mode 100644 tests/src/test/snapshots/images/Paparazzi_Preview_Test_valueclass.previewparameters.android.composableskt.valueclasspreviewparameterpreview-8feqmps_float_0.png create mode 100644 tests/src/test/snapshots/images/Paparazzi_Preview_Test_valueclass.previewparameters.android.composableskt.valueclasspreviewparameterpreview-8feqmps_float_1.png diff --git a/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ComposablePreviewInvocationHandler.kt b/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ComposablePreviewInvocationHandler.kt index 62b5fdf..e085afd 100644 --- a/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ComposablePreviewInvocationHandler.kt +++ b/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ComposablePreviewInvocationHandler.kt @@ -1,15 +1,14 @@ package sergio.sastre.composable.preview.scanner.core.preview -import sergio.sastre.composable.preview.scanner.core.preview.exception.PreviewParameterIsNotFirstArgumentException +import androidx.compose.runtime.Composer +import androidx.compose.runtime.reflect.asComposableMethod import io.github.classgraph.AnnotationClassRef import io.github.classgraph.AnnotationInfoList +import sergio.sastre.composable.preview.scanner.core.preview.exception.PreviewParameterIsNotFirstArgumentException import sergio.sastre.composable.preview.scanner.core.scanresult.filter.PREVIEW_WRAPPER_ANNOTATION import java.lang.reflect.InvocationHandler import java.lang.reflect.Method import java.lang.reflect.Modifier -import kotlin.math.pow -import kotlin.reflect.jvm.isAccessible -import kotlin.reflect.jvm.kotlinFunction /** * Used to handle calls to a [composableMethod]. @@ -30,51 +29,97 @@ internal class ComposablePreviewInvocationHandler( override fun invoke(proxy: Any?, method: Method?, args: Array?): Any? { if (method?.name != "invoke") return method?.invoke(this, *(args ?: emptyArray())) - val safeArgs = fillMissingComposeArgs(args) - - // Extract composer and changed once, providing defaults if args are missing - val composer = args?.getOrNull(args.size - 2) + // Args of ComposablePreview.invoke() are the compiler-added [Composer, changed] pair. + val composer = args?.getOrNull(args.size - 2) as? Composer val changed = args?.getOrNull(args.size - 1) as? Int ?: 0 val previewWrapperData = getPreviewWrapperProvider() val wrapMethod = previewWrapperData?.second - // Only wrap if we have both the method AND the minimum required compose arguments val canWrapPreview = wrapMethod != null && (args?.size ?: 0) >= 2 return when (canWrapPreview) { - false -> invokeDirectly(composer, changed, safeArgs) + false -> invokeComposable(composer) true -> { - val content: (Any?, Int) -> Unit = { c, i -> invokeDirectly(c, i, safeArgs) } - wrapMethod.invoke(previewWrapperData.first, content, composer, changed) + val content: (Any?, Int) -> Unit = { wrapComposer, _ -> + invokeComposable(wrapComposer as? Composer) + } + wrapMethod!!.invoke(previewWrapperData.first, content, composer, changed) } } } - private fun invokeDirectly(composer: Any?, changed: Int, safeArgs: Array): Any? { - val allParams = composableMethod.kotlinFunction!!.parameters - val hasDefaultParams = allParams.any { it.isOptional } + /** + * Invokes the @Composable via androidx' [asComposableMethod] (`java.lang.reflect` based) instead of + * kotlin-reflect. ComposableMethod supplies the compiler-added Composer/changed/default-mask + * parameters itself and fills trailing default parameters, so we only pass the real + * @PreviewParameter value (if any). + * + * This deliberately avoids `kotlin-reflect`: resolving/calling `Method.kotlinFunction` throws + * `KotlinReflectionInternalError` for previews whose signature contains a value class, because + * kotlin-reflect's ValueClassAwareCaller derives the expected argument count from the Kotlin + * descriptor and cannot reconcile the Compose compiler's synthetic JVM parameters. + */ + private fun invokeComposable(composer: Composer?): Any? { + val composable = composableMethod.asComposableMethod() + ?: error( + "Not a @Composable method: " + + "${composableMethod.declaringClass.name}.${composableMethod.name}", + ) + + // Unconditional: a @PreviewParameter anywhere but first can't be placed correctly, whether or + // not a value was resolved for it. (Matches the previous behavior.) + requirePreviewParameterIsFirstArgument() - // Update the composer and changed values in the arguments - val updatedArgs = arrayOf(*safeArgs) - if (updatedArgs.size >= (if (hasDefaultParams) 3 else 2)) { - val offset = if (hasDefaultParams) 1 else 0 - updatedArgs[updatedArgs.size - 2 - offset] = composer - updatedArgs[updatedArgs.size - 1 - offset] = changed + val realArgs: Array = when (parameter) { + NoParameter -> emptyArray() + else -> arrayOf(coerceToParameterType(parameter, composable.parameterTypes.firstOrNull())) } - val safeArgsWithParam = - when (parameter != NoParameter) { - true -> arrayOf(parameter, *updatedArgs) - false -> updatedArgs - } + val receiver = when (Modifier.isStatic(composableMethod.modifiers)) { + true -> null + false -> composableMethod.declaringClass.getDeclaredConstructor() + .apply { isAccessible = true } + .newInstance() + } + + return composable.invoke( + requireNotNull(composer) { "A Composer is required to invoke a @Composable preview" }, + receiver, + *realArgs, + ) + } - val isInsideClass = !Modifier.isStatic(composableMethod.modifiers) - val kotlinComposableMethod = - composableMethod.kotlinFunction!!.apply { isAccessible = true } - return when (isInsideClass) { - false -> kotlinComposableMethod.call(*safeArgsWithParam) - true -> kotlinComposableMethod.call( - composableMethod.declaringClass.getDeclaredConstructor().newInstance(), - *safeArgsWithParam + /** + * A @PreviewParameter provider yields boxed value-class instances (e.g. a Dp), but the JVM method + * expects the underlying type (e.g. float). Unbox via the value class's synthetic `unbox-impl` when + * the value does not already fit the target parameter slot (nullable/generic slots stay boxed). + * Loops to handle the rare nested-value-class case. + */ + private fun coerceToParameterType(value: Any?, targetType: Class<*>?): Any? { + if (targetType == null) return value + var current: Any? = value + while (true) { + val boxed = current ?: break + if (targetType.isInstance(boxed)) break + val unbox = boxed.javaClass.declaredMethods.firstOrNull { it.name == "unbox-impl" } ?: break + unbox.isAccessible = true + current = unbox.invoke(boxed) + } + return current + } + + /** + * Compose only supports a single @PreviewParameter and (from AS Meerkat on) enforces it is the + * first parameter. We forward the provider value as the first argument, so if it is not first we + * cannot place it correctly — fail explicitly rather than render the wrong argument. + */ + private fun requirePreviewParameterIsFirstArgument() { + val previewParameterIndex = composableMethod.parameterAnnotations.indexOfFirst { annotations -> + annotations.any { it.annotationClass.java.name in PREVIEW_PARAMETER_ANNOTATIONS } + } + if (previewParameterIndex > 0) { + throw PreviewParameterIsNotFirstArgumentException( + className = composableMethod.declaringClass.name, + methodName = composableMethod.name, ) } } @@ -93,48 +138,10 @@ internal class ComposablePreviewInvocationHandler( return PreviewWrapperCache.getProviderAndWrapMethod(wrapperClassRef.name) } - private fun fillMissingComposeArgs(passedComposeArgs: Array?): Array { - val safeArgs = passedComposeArgs ?: emptyArray() - val allParams = composableMethod.kotlinFunction!!.parameters - val defaultParams = allParams.filter { it.isOptional } - when (defaultParams.isEmpty()) { - true -> return safeArgs - false -> { - // Very rare case: - // if @PreviewParameters & default parameters available - // And @PreviewParameters is not the first of all arguments - // we cannot handle it: - // we don't know the value of that argument to pass it at a certain index - // and it'd throw an UndeclaredThrowableException - // - // Update: It seems that from AS Meerkat on, this is enforced :) - if (allParams.any { !it.isOptional && it.index != 0 }) { - throw PreviewParameterIsNotFirstArgumentException( - className = composableMethod.declaringClass.name, - methodName = composableMethod.name - ) - } - - // In kotlin reflect, null params resolve to default kotlin params. - // These params are added at the beginning of the method by the Compose Compiler - val defaultParamsAsNull: Array = arrayOfNulls(defaultParams.size) - - // When default params available, the Compose Compiler adds a mask at the end of the method - // to map the default parameters to their corresponding default values. - // - // This mask contains 1 bit for each parameter (0 -> null, 1 -> default value), - // including default params and those passed via @PreviewParameters - // so in order to resolve all parameters to their corresponding values, - // you need the highest possible number in binary e.g. - // 1 param -> 1 - // 2 params -> 11 -> 3 - // 3 params -> 111 -> 7 - // 4 params -> 1111 -> 15 - // x params -> 2 pow (x) - 1 - val paramsMask: MutableList = - mutableListOf(2.0.pow(allParams.size).toInt() - 1) - return (defaultParamsAsNull.toMutableList() + safeArgs.toMutableList() + paramsMask).toTypedArray() - } - } + private companion object { + private val PREVIEW_PARAMETER_ANNOTATIONS = setOf( + "androidx.compose.ui.tooling.preview.PreviewParameter", + "org.jetbrains.compose.ui.tooling.preview.PreviewParameter", + ) } -} \ No newline at end of file +} diff --git a/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ProvideComposablePreview.kt b/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ProvideComposablePreview.kt index 07f34d3..900b975 100644 --- a/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ProvideComposablePreview.kt +++ b/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ProvideComposablePreview.kt @@ -1,8 +1,8 @@ package sergio.sastre.composable.preview.scanner.core.preview +import androidx.compose.runtime.reflect.asComposableMethod import sergio.sastre.composable.preview.scanner.core.preview.mappers.ComposablePreviewMapper import java.lang.reflect.Proxy -import kotlin.reflect.jvm.kotlinFunction /** * Provides an invokable ComposablePreview @@ -54,24 +54,24 @@ class ProvideComposablePreview { private fun Class<*>.toClassName(): String = canonicalName ?: simpleName /** - * Returns the type of the parameters as an underscore separated simple string + * Returns the type of the (real) parameters as an underscore separated simple string. * - * Composable from preview methods contain always 2 parameters added at the end by the compiler: - * 1. androidx.compose.runtime.Composer - * 2. Int - * - * Moreover, if it is a Preview with default parameters, one extra 3. argument is added as a mask for default values - * - * All these 2-3 parameters are placed at the end of the method. + * Preview methods always have compiler-added parameters at the end (Composer, an Int + * `changed` mask, and — for previews with default parameters — an extra Int default mask). + * We resolve how many of the trailing parameters are compiler-added via androidx' + * [asComposableMethod] (`java.lang.reflect` based) rather than kotlin-reflect: resolving + * `Method.kotlinFunction` throws `KotlinReflectionInternalError` for previews whose + * signature contains a value class (e.g. a value-class `@PreviewParameter`), because + * kotlin-reflect's ValueClassAwareCaller does not account for the synthetic Compose params. */ @Suppress("NewApi") private fun methodParametersTypeAsString(): String { val previewMethod = composablePreviewMapper.previewMethod - val hasDefaultParams = previewMethod.kotlinFunction!!.parameters.any { it.isOptional } - val count = if (hasDefaultParams) 3 else 2 + val realParametersCount = previewMethod.asComposableMethod()?.parameterCount + ?: (previewMethod.parameterTypes.size - 2).coerceAtLeast(0) return previewMethod .genericParameterTypes - .dropLast(count) + .take(realParametersCount) .joinToString("_") { // From java.lang.List to List it.typeName diff --git a/tests/src/main/java/valueclass/previewparameters/android/Composables.kt b/tests/src/main/java/valueclass/previewparameters/android/Composables.kt new file mode 100644 index 0000000..6af3673 --- /dev/null +++ b/tests/src/main/java/valueclass/previewparameters/android/Composables.kt @@ -0,0 +1,27 @@ +package valueclass.previewparameters.android + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * Repro fixture (in its own top-level package so the broad invoke suites don't pick it up): a + * @PreviewParameter whose provider yields a Kotlin value class (Dp). The preview method's signature + * therefore contains a value-class parameter, which trips kotlin-reflect's ValueClassAwareCaller when + * the scanner resolves `Method.kotlinFunction`. + */ +class DpValueClassProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf(32.dp, 64.dp) +} + +@Preview +@Composable +fun ValueClassPreviewParameterPreview( + @PreviewParameter(provider = DpValueClassProvider::class) size: Dp, +) { + Text(size.toString()) +} diff --git a/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/api/main/screenshotid/ComposablePreviewToStringTest.kt b/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/api/main/screenshotid/ComposablePreviewToStringTest.kt index 8894761..8ffdb42 100644 --- a/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/api/main/screenshotid/ComposablePreviewToStringTest.kt +++ b/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/api/main/screenshotid/ComposablePreviewToStringTest.kt @@ -96,4 +96,26 @@ class ComposablePreviewToStringTest() { Assert.assertEquals(expectedPreviewString, previewToString) } + + @Test + fun `GIVEN a preview with a value-class @PreviewParameter WHEN toString THEN it is scanned without crashing`() { + val previews = + AndroidComposablePreviewScanner() + .scanPackageTrees("valueclass.previewparameters.android") + .getPreviews() + .map { it.toString() } + + // The JVM method name of a value-class-parameter @Composable is mangled as "-", + // and we intentionally keep that raw name rather than demangling it. The "" disambiguates + // overloads whose value-class parameters share an underlying JVM type: methodParametersType is + // the erased underlying type (here "float"), so e.g. two same-named previews taking different + // value classes over Float would otherwise produce identical ids. The hash is + // compiler-version-dependent, so it is matched loosely rather than asserted verbatim. + val idPattern = Regex( + "valueclass\\.previewparameters\\.android\\.ComposablesKt_" + + "ValueClassPreviewParameterPreview(-\\w+)?_float_\\d", + ) + Assert.assertEquals(2, previews.size) + Assert.assertTrue(previews.toString(), previews.all { idPattern.matches(it) }) + } } \ No newline at end of file diff --git a/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/paparazzi/runtime/PaparazziValueClassPreviewParameterInvokeTest.kt b/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/paparazzi/runtime/PaparazziValueClassPreviewParameterInvokeTest.kt new file mode 100644 index 0000000..b0bb8ce --- /dev/null +++ b/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/paparazzi/runtime/PaparazziValueClassPreviewParameterInvokeTest.kt @@ -0,0 +1,64 @@ +package sergio.sastre.composable.preview.scanner.tests.paparazzi.runtime + +import app.cash.paparazzi.Paparazzi +import app.cash.paparazzi.detectEnvironment +import com.android.ide.common.rendering.api.SessionParams +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import sergio.sastre.composable.preview.scanner.android.AndroidComposablePreviewScanner +import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo +import sergio.sastre.composable.preview.scanner.android.screenshotid.AndroidPreviewScreenshotIdBuilder +import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview +import sergio.sastre.composable.preview.scanner.tests.paparazzi.utils.DeviceConfigBuilder +import sergio.sastre.composable.preview.scanner.tests.paparazzi.utils.applyUiMode +import sergio.sastre.composable.preview.scanner.tests.paparazzi.utils.paparazziTestNameSnapshotHandler + +/** + * Ensures a @Preview whose @PreviewParameter provider yields a value class (e.g. Dp) can be both + * scanned and invoked/rendered. This exercises the invocation path (ComposablePreview.invoke()), + * which previously crashed in kotlin-reflect's ValueClassAwareCaller for value-class signatures. + * + * ./gradlew :tests:recordPaparazziDebug --tests 'PaparazziValueClassPreviewParameterInvokeTest' -Plibrary=paparazzi + */ +@RunWith(Parameterized::class) +class PaparazziValueClassPreviewParameterInvokeTest( + private val preview: ComposablePreview, +) { + + companion object { + private val cachedPreviews: List> by lazy { + AndroidComposablePreviewScanner() + .scanPackageTrees("valueclass.previewparameters.android") + .getPreviews() + } + + @JvmStatic + @Parameterized.Parameters + fun values(): List> = cachedPreviews + } + + @get:Rule + val paparazzi = Paparazzi( + environment = detectEnvironment().copy(compileSdkVersion = 34), + deviceConfig = DeviceConfigBuilder + .build(preview.previewInfo.device) + .applyUiMode(preview.previewInfo.uiMode), + renderingMode = SessionParams.RenderingMode.SHRINK, + snapshotHandler = paparazziTestNameSnapshotHandler() + ) + + @Test + fun snapshot() { + val screenshotId = AndroidPreviewScreenshotIdBuilder(preview) + .doNotIgnoreMethodParametersType() + .encodeUnsafeCharacters() + .build() + .replace("sergio.sastre.composable.preview.scanner.", "") + + paparazzi.snapshot(name = screenshotId) { + preview() + } + } +} diff --git a/tests/src/test/snapshots/images/Paparazzi_Preview_Test_valueclass.previewparameters.android.composableskt.valueclasspreviewparameterpreview-8feqmps_float_0.png b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_valueclass.previewparameters.android.composableskt.valueclasspreviewparameterpreview-8feqmps_float_0.png new file mode 100644 index 0000000000000000000000000000000000000000..c4d4a426a0ef128ae1b9e924a9abbf937a8ba466 GIT binary patch literal 1697 zcmV;S244AzP)mwj7UVU(I zVB(1Z+}zw`6%qr5Z)|T2`r+Z>Z-EZr0I8LMx8oseYikWJf#&Dug9mKmiGnvaHby-0 zHvKuCmkaQjnVAX0LLP|o%g>N+8o*BlbaLD7?rvja!uZR}%LIt$=jRRZF$!O(hv(kj zUV|t9)&jwk&?~{=@4kW)(xr#HC?fMX=f1kSY8W2!{B)?F3g~*%#D z@twQ7y9u75!U-Po`=psZ!kwI)H26`cw0&2fHCLqh)u79qi$}zYG{7T7OAprHk&$D0 zdD&F>QB=tyE48}T=Ob#x)@*NYH|mvt9&}lyTNDKGJsA?U(q+iR>nuF+Fe!X$HIG+I zJ}5P|hR`q9yuH2s+n`@uT#WK6WKj@AxR%jDe)jtM+7QB8kyCidb?`|MOdF3SN9!{a z@^dH|zUY<0b#ZZVMEs2-IB;X2^KbMGot~aH+FoROm)S-nPk zTWUci4U*JiO$o9<&5_q}CSP|V3swNqHH8LU8U<%0RWkDkwV?3EKy-?%`b@QC&g1|& zyh4K2C$Gw`f!55rPNun7F&iTkZkbKE`o#)5Bx|1-fsF*d>SMvVa}Zulcyc`1BbApd z%J|#5j=4dO?m>fucM{?W^#FjU5}MnFRx!{WPZ!4F2eHl<0Z5z4pu<@A)ciY#mrKu} zM(EQkOHZJSu&yH?x^)1O8U}s9RMl=pmjuO>!RR?N!WLu_OrV+1c5d2t$09 z znxIQ%Gv>8KEbZ+v&@Fc!x*KsdN@Z1mkH~+0+Zxh^1PCRiwrRfwba{(tPyQu}x* zk1J@an5E#&5a2R;CC}*)ZwX&*xldNvL>B9e;J1Q~5vS;8D9~k5heIV-@ATZVI<`){ zLhV`Zd3TONE(LUSlQNVV3UrK4uEWu)KJAqZV<@Z3dVLt{nAS=ZosKtur?hM-)jRD| z%2aPC(0!B1UK2UZ(3*>Vw`sYbEy=Wj*Z$KYiP*t|6Z9$gTiz zhIpqYQ-{6`vW{U>YL$^E37t>}O%rrE`#|sG@DBPgb!5HS(xEJ7)wCJ%r3!VyP!A(z z6hc_bJ~|&;y;K{He7Iqa7DIt9Bc8h-sa&5Ndk|sq^w5NeuqOu))Br=iPSIzx%oOwu zU)wEJjO054eGz4S#E}DZ3|%^zt5GVSby^D;#;=yUZ~kBAN{XeSUcI&nY`GAU?*jlU zHSOqEMImSK6`mH*Em261Vx2Qrz9|xUEjIB1G@Js>5d`xx`wmq zSFs@qwP{`V<}*1CZX-OsYxRy!EI;7Duyi`D+AJDH>FYje;Q$(|Acp{5&+Dy|Ji@3R z9ZPS)<70J9%HFIY+W0~^puxlarmt)D4o*a2pObRQZ2GIiE-u&eG{QNAlOqLW`2^sV( z8T2d}^eh?lEE)7HJ>>1}t$cWR7|Q$mdj>tFe0+Qil#KBEH>~OQ_O>jFVknP~k3SDO z%E1E9&(F)l!^3iScemWy+A3dPUz03kb#-;P;q~?P^5x|v=|)-S$;ruJ?CI(0;aoE_ zGv&s{#$aCo2y*E3VXb}yPIs# zQNZHjVpLImLB|8D3@R1q06sM}H8M5^fX5@Vv$G@5V1b_i{#Ss0b#?VcSttlvzr4IG zZ*Fb|bl*27?(OYW8dD0;F(~zLezPxKGYQkh#l?W;!+T3hOW#iQKMp#Uu7INu;$iBH z(rz?0=toCKBV*p*-`Bp25$n6e_=y1>V3pG6=jX?=06Yng3iE7lZzlyEZ`QB@Lp6P~ zxw$!fpTu%vK*z#0zCq#Rn3xJ6%!9J6r}YILuh#KUYSTARf9fRl*Mp9iKF@Q^X#ss!}gF<+xP07Yaf+sPa<_oDo*QAW!-tUx! z_V@Rb#c1}8!Lu)PhHlw^UCG)6dKfd%IO8FFSH%GIE53kE%+r?u8(^EPh<7{*4;#hf z>hguupsOcP8F;_bJp)MB%0V3naAM^my6yscY)qS!vYz0eg?eEOTs(kYyY|TSSyQ5< ziB(neBQ@x1U|m7?lzAR64|z*WG=cVj?pYg5rQd{3V^ODcr8o-!bfWYb;zlfc)tFUP zKK&)oRoQegJ(Yv?_9oChrL?U^LXJ|@(Lspou`FB_TL%=B0z))W=3fGRd3m|@7)>Qx z*E(MJ6B61X=m6Z7K5R&+qA|5#D6Fmw{41d23~i}>oSzXaR$HMEx)~j@~^m*GOTy4(a_21Q;vB{Ix$3Fo& zderX*;iqm;FTE@1G+Y8WO_wj)O4}}=G7vy^X~TNsu{}NXCd1zWx^@KvK1E*()wZhA zyDIM|K)1%|`QFB3uMVu?XddHbi*bRjj7L>5lE|@G^-ga*tBai&16Jl0%M2u6`n)Z@ z+oM&_6)1aG&&zgJ^lm^aPfkUq3d<9fN4=j1vFEd;yTZ@C*Oq*$Cb$04ab^C zRf#8Hb-fB^%imbLw90urpnF)z_Fl-_zJQLRm6oA~*v2*a0{lLg4pM_|YgG0X^IVjy z1`Jd6xfP@xNXo!ic(I|?13LSOVMDpBNh#|I6QcZGB7yQMISK8^@jl7s5!@I+@2FmZ=d}-jTC?b(BCq z8H3sKo`)1 z$HT~+TCyMZX7|G(nKDgLM^E;mV(e%&#SI%T=(hRM)JOspdn-03vAm@1+&r`A6IFtz zx6R&R9M2zcU|2Tn7BOX!2-KaaGg#Y86e&PwJ}pDM_xPYu%V6p)czn~zRhPN>2DYi9 za6n@WjKlWqJ=r^85?1XWY=rU|&#%fQ3U8N*3>08cUdlf?>Uv3mHFQZFQE=D`iUjC3 zp{eWKd Date: Wed, 12 Aug 2026 12:42:39 -0500 Subject: [PATCH 2/4] Add Roborazzi coverage for value-class @PreviewParameter Mirrors the Paparazzi test so the value-class @PreviewParameter fix is verified under both rendering libraries, which download resources differently. Scans the valueclass.previewparameters.android package and records reference snapshots. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...zziValueClassPreviewParameterInvokeTest.kt | 81 ++++++++++++++++++ ...reviewParameterPreview-8Feqmps_float_0.png | Bin 0 -> 2108 bytes ...reviewParameterPreview-8Feqmps_float_1.png | Bin 0 -> 2030 bytes 3 files changed, 81 insertions(+) create mode 100644 tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/roborazzi/runtime/RoborazziValueClassPreviewParameterInvokeTest.kt create mode 100644 tests/src/test/screenshots/valueclass/valueclass.previewparameters.android.ComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_0.png create mode 100644 tests/src/test/screenshots/valueclass/valueclass.previewparameters.android.ComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_1.png diff --git a/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/roborazzi/runtime/RoborazziValueClassPreviewParameterInvokeTest.kt b/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/roborazzi/runtime/RoborazziValueClassPreviewParameterInvokeTest.kt new file mode 100644 index 0000000..c2cbd11 --- /dev/null +++ b/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/roborazzi/runtime/RoborazziValueClassPreviewParameterInvokeTest.kt @@ -0,0 +1,81 @@ +package sergio.sastre.composable.preview.scanner.tests.roborazzi.runtime + +import com.github.takahirom.roborazzi.ExperimentalRoborazziApi +import com.github.takahirom.roborazzi.RoborazziComposeOptions +import com.github.takahirom.roborazzi.background +import com.github.takahirom.roborazzi.captureRoboImage +import com.github.takahirom.roborazzi.locale +import com.github.takahirom.roborazzi.size +import com.github.takahirom.roborazzi.uiMode +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.ParameterizedRobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import org.robolectric.annotation.GraphicsMode +import sergio.sastre.composable.preview.scanner.android.AndroidComposablePreviewScanner +import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo +import sergio.sastre.composable.preview.scanner.android.device.domain.RobolectricDeviceQualifierBuilder +import sergio.sastre.composable.preview.scanner.android.screenshotid.AndroidPreviewScreenshotIdBuilder +import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview + +/** + * Ensures a @Preview whose @PreviewParameter provider yields a value class (e.g. Dp) can be both + * scanned and invoked/rendered by Roborazzi. This exercises the invocation path + * (ComposablePreview.invoke()), which previously crashed in kotlin-reflect's ValueClassAwareCaller + * for value-class signatures. Mirrors PaparazziValueClassPreviewParameterInvokeTest so the fix is + * covered under both rendering libraries, which download resources differently. + * + * ./gradlew :tests:recordRoborazziDebug --tests 'RoborazziValueClassPreviewParameterInvokeTest' -Plibrary=roborazzi + */ +@RunWith(ParameterizedRobolectricTestRunner::class) +class RoborazziValueClassPreviewParameterInvokeTest( + private val preview: ComposablePreview, +) { + companion object { + private val cachedPreviews: List> by lazy { + AndroidComposablePreviewScanner() + .scanPackageTrees("valueclass.previewparameters.android") + .getPreviews() + } + + @JvmStatic + @ParameterizedRobolectricTestRunner.Parameters + fun values(): List> = cachedPreviews + } + + fun screenshotName(preview: ComposablePreview): String = + "src/test/screenshots/valueclass/${ + AndroidPreviewScreenshotIdBuilder(preview) + .doNotIgnoreMethodParametersType() + .build() + }.png" + + @OptIn(ExperimentalRoborazziApi::class) + @GraphicsMode(GraphicsMode.Mode.NATIVE) + @Config(sdk = [30]) + @Test + fun snapshot() { + RobolectricDeviceQualifierBuilder.build(preview.previewInfo.device)?.run { + RuntimeEnvironment.setQualifiers(this) + } + + captureRoboImage( + filePath = screenshotName(preview), + roborazziComposeOptions = RoborazziComposeOptions { + size( + widthDp = preview.previewInfo.widthDp, + heightDp = preview.previewInfo.heightDp + ) + background( + showBackground = preview.previewInfo.showBackground, + backgroundColor = preview.previewInfo.backgroundColor + ) + locale(preview.previewInfo.locale) + uiMode(preview.previewInfo.uiMode) + }, + ) { + preview() + } + } +} diff --git a/tests/src/test/screenshots/valueclass/valueclass.previewparameters.android.ComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_0.png b/tests/src/test/screenshots/valueclass/valueclass.previewparameters.android.ComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_0.png new file mode 100644 index 0000000000000000000000000000000000000000..9e18e6b119655fbee4b05a0230493f5ede73abd3 GIT binary patch literal 2108 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 M07*qoM6N<$g1 Date: Sun, 16 Aug 2026 13:19:36 +0200 Subject: [PATCH 3/4] test organization changes --- .../ValueClassComposables.kt | 54 ++++++++++++ .../previewparameters/android/Composables.kt | 27 ------ .../ComposablePreviewToStringTest.kt | 8 +- ...zziValueClassPreviewParameterInvokeTest.kt | 64 -------------- ...zziValueClassPreviewParameterInvokeTest.kt | 81 ------------------ ...ssPreviewParameterPreview-rCkNIVA_Dp_0.png | Bin 0 -> 508 bytes ...eviewParameterPreview-8Feqmps_float_0.png} | Bin ...eviewParameterPreview-8Feqmps_float_1.png} | Bin ...ssPreviewParameterPreview-rCkNIVA_Dp_0.png | Bin 0 -> 508 bytes ...reviewParameterPreview-8Feqmps_float_0.png | Bin 0 -> 2108 bytes ...reviewParameterPreview-8Feqmps_float_1.png | Bin 0 -> 2030 bytes ...sspreviewparameterpreview-rckniva_dp_0.png | Bin 0 -> 452 bytes ...reviewparameterpreview-8feqmps_float_0.png | Bin 0 -> 1648 bytes ...reviewparameterpreview-8feqmps_float_1.png | Bin 0 -> 1591 bytes ...reviewparameterpreview-8feqmps_float_0.png | Bin 1697 -> 0 bytes ...reviewparameterpreview-8feqmps_float_1.png | Bin 1635 -> 0 bytes 16 files changed, 58 insertions(+), 176 deletions(-) create mode 100644 tests/src/main/java/sergio/sastre/composable/preview/scanner/android/previewparameters/ValueClassComposables.kt delete mode 100644 tests/src/main/java/valueclass/previewparameters/android/Composables.kt delete mode 100644 tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/paparazzi/runtime/PaparazziValueClassPreviewParameterInvokeTest.kt delete mode 100644 tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/roborazzi/runtime/RoborazziValueClassPreviewParameterInvokeTest.kt create mode 100644 tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.NullableValueClassPreviewParameterPreview-rCkNIVA_Dp_0.png rename tests/src/test/screenshots/{valueclass/valueclass.previewparameters.android.ComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_0.png => android/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_0.png} (100%) rename tests/src/test/screenshots/{valueclass/valueclass.previewparameters.android.ComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_1.png => android/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_1.png} (100%) create mode 100644 tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.NullableValueClassPreviewParameterPreview-rCkNIVA_Dp_0.png create mode 100644 tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_0.png create mode 100644 tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_1.png create mode 100644 tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.valueclasscomposableskt.nullablevalueclasspreviewparameterpreview-rckniva_dp_0.png create mode 100644 tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.valueclasscomposableskt.valueclasspreviewparameterpreview-8feqmps_float_0.png create mode 100644 tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.valueclasscomposableskt.valueclasspreviewparameterpreview-8feqmps_float_1.png delete mode 100644 tests/src/test/snapshots/images/Paparazzi_Preview_Test_valueclass.previewparameters.android.composableskt.valueclasspreviewparameterpreview-8feqmps_float_0.png delete mode 100644 tests/src/test/snapshots/images/Paparazzi_Preview_Test_valueclass.previewparameters.android.composableskt.valueclasspreviewparameterpreview-8feqmps_float_1.png diff --git a/tests/src/main/java/sergio/sastre/composable/preview/scanner/android/previewparameters/ValueClassComposables.kt b/tests/src/main/java/sergio/sastre/composable/preview/scanner/android/previewparameters/ValueClassComposables.kt new file mode 100644 index 0000000..0a9f5de --- /dev/null +++ b/tests/src/main/java/sergio/sastre/composable/preview/scanner/android/previewparameters/ValueClassComposables.kt @@ -0,0 +1,54 @@ +package sergio.sastre.composable.preview.scanner.android.previewparameters + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * @PreviewParameter whose provider yields a Kotlin value class (Dp). The preview method's signature + * therefore contains a value-class parameter, which trips kotlin-reflect's ValueClassAwareCaller when + * the scanner resolves `Method.kotlinFunction`. + */ +class DpValueClassProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf(32.dp, 64.dp) +} + +@Preview(name = "valueClassDp") +@Composable +fun ValueClassPreviewParameterPreview( + @PreviewParameter(provider = DpValueClassProvider::class) size: Dp, +) { + Text(size.toString()) +} + +class NullableDpValueClassProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf(null) +} + +@Preview(name = "valueClassDp") +@Composable +fun NullableValueClassPreviewParameterPreview( + @PreviewParameter(provider = NullableDpValueClassProvider::class) size: Dp?, +) { + Text(size.toString()) +} + +@JvmInline +value class DpExtended(val value: Float) + +class DpExtendedValueClassProvider : PreviewParameterProvider { + override val values: Sequence = + sequenceOf(DpExtended(32F), DpExtended(64F)) +} + +@Preview(name = "valueClassDpExtended") +@Composable +fun DpExtendedValueClassPreviewParameterPreview( + @PreviewParameter(provider = DpExtendedValueClassProvider::class) size: DpExtended, +) { + Text(size.toString()) +} diff --git a/tests/src/main/java/valueclass/previewparameters/android/Composables.kt b/tests/src/main/java/valueclass/previewparameters/android/Composables.kt deleted file mode 100644 index 6af3673..0000000 --- a/tests/src/main/java/valueclass/previewparameters/android/Composables.kt +++ /dev/null @@ -1,27 +0,0 @@ -package valueclass.previewparameters.android - -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp - -/** - * Repro fixture (in its own top-level package so the broad invoke suites don't pick it up): a - * @PreviewParameter whose provider yields a Kotlin value class (Dp). The preview method's signature - * therefore contains a value-class parameter, which trips kotlin-reflect's ValueClassAwareCaller when - * the scanner resolves `Method.kotlinFunction`. - */ -class DpValueClassProvider : PreviewParameterProvider { - override val values: Sequence = sequenceOf(32.dp, 64.dp) -} - -@Preview -@Composable -fun ValueClassPreviewParameterPreview( - @PreviewParameter(provider = DpValueClassProvider::class) size: Dp, -) { - Text(size.toString()) -} diff --git a/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/api/main/screenshotid/ComposablePreviewToStringTest.kt b/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/api/main/screenshotid/ComposablePreviewToStringTest.kt index 8ffdb42..77192a3 100644 --- a/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/api/main/screenshotid/ComposablePreviewToStringTest.kt +++ b/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/api/main/screenshotid/ComposablePreviewToStringTest.kt @@ -101,7 +101,8 @@ class ComposablePreviewToStringTest() { fun `GIVEN a preview with a value-class @PreviewParameter WHEN toString THEN it is scanned without crashing`() { val previews = AndroidComposablePreviewScanner() - .scanPackageTrees("valueclass.previewparameters.android") + .scanPackageTrees("sergio.sastre.composable.preview.scanner") + .filterPreviews { it.name == "valueClassDp" } .getPreviews() .map { it.toString() } @@ -112,10 +113,9 @@ class ComposablePreviewToStringTest() { // value classes over Float would otherwise produce identical ids. The hash is // compiler-version-dependent, so it is matched loosely rather than asserted verbatim. val idPattern = Regex( - "valueclass\\.previewparameters\\.android\\.ComposablesKt_" + - "ValueClassPreviewParameterPreview(-\\w+)?_float_\\d", + "sergio\\.sastre\\.composable\\.preview\\.scanner\\.android\\.previewparameters\\.ValueClassComposablesKt_" + + "(-\\w+)?ValueClassPreviewParameterPreview_Dp_float_\\d", ) - Assert.assertEquals(2, previews.size) Assert.assertTrue(previews.toString(), previews.all { idPattern.matches(it) }) } } \ No newline at end of file diff --git a/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/paparazzi/runtime/PaparazziValueClassPreviewParameterInvokeTest.kt b/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/paparazzi/runtime/PaparazziValueClassPreviewParameterInvokeTest.kt deleted file mode 100644 index b0bb8ce..0000000 --- a/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/paparazzi/runtime/PaparazziValueClassPreviewParameterInvokeTest.kt +++ /dev/null @@ -1,64 +0,0 @@ -package sergio.sastre.composable.preview.scanner.tests.paparazzi.runtime - -import app.cash.paparazzi.Paparazzi -import app.cash.paparazzi.detectEnvironment -import com.android.ide.common.rendering.api.SessionParams -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import sergio.sastre.composable.preview.scanner.android.AndroidComposablePreviewScanner -import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo -import sergio.sastre.composable.preview.scanner.android.screenshotid.AndroidPreviewScreenshotIdBuilder -import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview -import sergio.sastre.composable.preview.scanner.tests.paparazzi.utils.DeviceConfigBuilder -import sergio.sastre.composable.preview.scanner.tests.paparazzi.utils.applyUiMode -import sergio.sastre.composable.preview.scanner.tests.paparazzi.utils.paparazziTestNameSnapshotHandler - -/** - * Ensures a @Preview whose @PreviewParameter provider yields a value class (e.g. Dp) can be both - * scanned and invoked/rendered. This exercises the invocation path (ComposablePreview.invoke()), - * which previously crashed in kotlin-reflect's ValueClassAwareCaller for value-class signatures. - * - * ./gradlew :tests:recordPaparazziDebug --tests 'PaparazziValueClassPreviewParameterInvokeTest' -Plibrary=paparazzi - */ -@RunWith(Parameterized::class) -class PaparazziValueClassPreviewParameterInvokeTest( - private val preview: ComposablePreview, -) { - - companion object { - private val cachedPreviews: List> by lazy { - AndroidComposablePreviewScanner() - .scanPackageTrees("valueclass.previewparameters.android") - .getPreviews() - } - - @JvmStatic - @Parameterized.Parameters - fun values(): List> = cachedPreviews - } - - @get:Rule - val paparazzi = Paparazzi( - environment = detectEnvironment().copy(compileSdkVersion = 34), - deviceConfig = DeviceConfigBuilder - .build(preview.previewInfo.device) - .applyUiMode(preview.previewInfo.uiMode), - renderingMode = SessionParams.RenderingMode.SHRINK, - snapshotHandler = paparazziTestNameSnapshotHandler() - ) - - @Test - fun snapshot() { - val screenshotId = AndroidPreviewScreenshotIdBuilder(preview) - .doNotIgnoreMethodParametersType() - .encodeUnsafeCharacters() - .build() - .replace("sergio.sastre.composable.preview.scanner.", "") - - paparazzi.snapshot(name = screenshotId) { - preview() - } - } -} diff --git a/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/roborazzi/runtime/RoborazziValueClassPreviewParameterInvokeTest.kt b/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/roborazzi/runtime/RoborazziValueClassPreviewParameterInvokeTest.kt deleted file mode 100644 index c2cbd11..0000000 --- a/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/roborazzi/runtime/RoborazziValueClassPreviewParameterInvokeTest.kt +++ /dev/null @@ -1,81 +0,0 @@ -package sergio.sastre.composable.preview.scanner.tests.roborazzi.runtime - -import com.github.takahirom.roborazzi.ExperimentalRoborazziApi -import com.github.takahirom.roborazzi.RoborazziComposeOptions -import com.github.takahirom.roborazzi.background -import com.github.takahirom.roborazzi.captureRoboImage -import com.github.takahirom.roborazzi.locale -import com.github.takahirom.roborazzi.size -import com.github.takahirom.roborazzi.uiMode -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.ParameterizedRobolectricTestRunner -import org.robolectric.RuntimeEnvironment -import org.robolectric.annotation.Config -import org.robolectric.annotation.GraphicsMode -import sergio.sastre.composable.preview.scanner.android.AndroidComposablePreviewScanner -import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo -import sergio.sastre.composable.preview.scanner.android.device.domain.RobolectricDeviceQualifierBuilder -import sergio.sastre.composable.preview.scanner.android.screenshotid.AndroidPreviewScreenshotIdBuilder -import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview - -/** - * Ensures a @Preview whose @PreviewParameter provider yields a value class (e.g. Dp) can be both - * scanned and invoked/rendered by Roborazzi. This exercises the invocation path - * (ComposablePreview.invoke()), which previously crashed in kotlin-reflect's ValueClassAwareCaller - * for value-class signatures. Mirrors PaparazziValueClassPreviewParameterInvokeTest so the fix is - * covered under both rendering libraries, which download resources differently. - * - * ./gradlew :tests:recordRoborazziDebug --tests 'RoborazziValueClassPreviewParameterInvokeTest' -Plibrary=roborazzi - */ -@RunWith(ParameterizedRobolectricTestRunner::class) -class RoborazziValueClassPreviewParameterInvokeTest( - private val preview: ComposablePreview, -) { - companion object { - private val cachedPreviews: List> by lazy { - AndroidComposablePreviewScanner() - .scanPackageTrees("valueclass.previewparameters.android") - .getPreviews() - } - - @JvmStatic - @ParameterizedRobolectricTestRunner.Parameters - fun values(): List> = cachedPreviews - } - - fun screenshotName(preview: ComposablePreview): String = - "src/test/screenshots/valueclass/${ - AndroidPreviewScreenshotIdBuilder(preview) - .doNotIgnoreMethodParametersType() - .build() - }.png" - - @OptIn(ExperimentalRoborazziApi::class) - @GraphicsMode(GraphicsMode.Mode.NATIVE) - @Config(sdk = [30]) - @Test - fun snapshot() { - RobolectricDeviceQualifierBuilder.build(preview.previewInfo.device)?.run { - RuntimeEnvironment.setQualifiers(this) - } - - captureRoboImage( - filePath = screenshotName(preview), - roborazziComposeOptions = RoborazziComposeOptions { - size( - widthDp = preview.previewInfo.widthDp, - heightDp = preview.previewInfo.heightDp - ) - background( - showBackground = preview.previewInfo.showBackground, - backgroundColor = preview.previewInfo.backgroundColor - ) - locale(preview.previewInfo.locale) - uiMode(preview.previewInfo.uiMode) - }, - ) { - preview() - } - } -} diff --git a/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.NullableValueClassPreviewParameterPreview-rCkNIVA_Dp_0.png b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.NullableValueClassPreviewParameterPreview-rCkNIVA_Dp_0.png new file mode 100644 index 0000000000000000000000000000000000000000..93a989d727beac46e28adffff7dc1ffe56d5b8e4 GIT binary patch literal 508 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 literal 0 HcmV?d00001 diff --git a/tests/src/test/snapshots/images/Paparazzi_Preview_Test_valueclass.previewparameters.android.composableskt.valueclasspreviewparameterpreview-8feqmps_float_0.png b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_valueclass.previewparameters.android.composableskt.valueclasspreviewparameterpreview-8feqmps_float_0.png deleted file mode 100644 index c4d4a426a0ef128ae1b9e924a9abbf937a8ba466..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1697 zcmV;S244AzP)mwj7UVU(I zVB(1Z+}zw`6%qr5Z)|T2`r+Z>Z-EZr0I8LMx8oseYikWJf#&Dug9mKmiGnvaHby-0 zHvKuCmkaQjnVAX0LLP|o%g>N+8o*BlbaLD7?rvja!uZR}%LIt$=jRRZF$!O(hv(kj zUV|t9)&jwk&?~{=@4kW)(xr#HC?fMX=f1kSY8W2!{B)?F3g~*%#D z@twQ7y9u75!U-Po`=psZ!kwI)H26`cw0&2fHCLqh)u79qi$}zYG{7T7OAprHk&$D0 zdD&F>QB=tyE48}T=Ob#x)@*NYH|mvt9&}lyTNDKGJsA?U(q+iR>nuF+Fe!X$HIG+I zJ}5P|hR`q9yuH2s+n`@uT#WK6WKj@AxR%jDe)jtM+7QB8kyCidb?`|MOdF3SN9!{a z@^dH|zUY<0b#ZZVMEs2-IB;X2^KbMGot~aH+FoROm)S-nPk zTWUci4U*JiO$o9<&5_q}CSP|V3swNqHH8LU8U<%0RWkDkwV?3EKy-?%`b@QC&g1|& zyh4K2C$Gw`f!55rPNun7F&iTkZkbKE`o#)5Bx|1-fsF*d>SMvVa}Zulcyc`1BbApd z%J|#5j=4dO?m>fucM{?W^#FjU5}MnFRx!{WPZ!4F2eHl<0Z5z4pu<@A)ciY#mrKu} zM(EQkOHZJSu&yH?x^)1O8U}s9RMl=pmjuO>!RR?N!WLu_OrV+1c5d2t$09 z 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 Date: Sun, 16 Aug 2026 16:47:54 +0200 Subject: [PATCH 4/4] show Value-class type name also when not nullable - For PreviewParameters Value-classes that are not nullable, it returns the underlying type. This returns the Value class name too for consistency - Adds coverage for other types of PreviewParameter classes --- .../ComposablePreviewInvocationHandler.kt | 2 +- .../core/preview/ProvideComposablePreview.kt | 67 ++++++++++- .../NonPrimitiveClassComposables.kt | 87 ++++++++++++++ .../ValueClassComposables.kt | 54 --------- .../ComposablePreviewToStringTest.kt | 112 ++++++++++++++++-- ...ameterPreview.valueClassArrayDp_Dp[]_0.png | Bin 0 -> 3186 bytes ...ameterPreview.innerClass_Outer$Inner_0.png | Bin 0 -> 1549 bytes ...terPreview.valueClassListDp_List_0.png | Bin 0 -> 3402 bytes ...viewParameterPreview.myClass_MyClass_0.png | Bin 0 -> 1047 bytes ...meterPreview-rCkNIVA.valueClassDp_Dp_0.png | Bin 0 -> 2108 bytes ...meterPreview-rCkNIVA.valueClassDp_Dp_1.png | Bin 0 -> 2030 bytes ...meterPreview-rCkNIVA.valueClassDp_Dp_2.png | Bin 0 -> 508 bytes ...ypen-backsticks.wildcardList_List_0.png | Bin 0 -> 867 bytes ...ameterPreview.valueClassArrayDp_Dp[]_0.png | Bin 0 -> 3186 bytes ...ameterPreview.innerClass_Outer$Inner_0.png | Bin 0 -> 1549 bytes ...terPreview.valueClassListDp_List_0.png | Bin 0 -> 3402 bytes ...viewParameterPreview.myClass_MyClass_0.png | Bin 0 -> 1047 bytes ...meterPreview-rCkNIVA.valueClassDp_Dp_0.png | Bin 0 -> 2108 bytes ...meterPreview-rCkNIVA.valueClassDp_Dp_1.png | Bin 0 -> 2030 bytes ...meterPreview-rCkNIVA.valueClassDp_Dp_2.png | Bin 0 -> 508 bytes ...ypen-backsticks.wildcardList_List_0.png | Bin 0 -> 867 bytes ...ameterpreview.valueclassarraydp_dp[]_0.png | Bin 0 -> 2448 bytes ...ameterpreview.innerclass_outer$inner_0.png | Bin 0 -> 1277 bytes ...review.valueclasslistdp_list%3cdp%3e_0.png | Bin 0 -> 2577 bytes ...viewparameterpreview.myclass_myclass_0.png | Bin 0 -> 896 bytes ...eterpreview-rckniva.valueclassdp_dp_0.png} | Bin ...eterpreview-rckniva.valueclassdp_dp_1.png} | Bin ...eterpreview-rckniva.valueclassdp_dp_2.png} | Bin ...acksticks.wildcardlist_list%3c%3f%3e_0.png | Bin 0 -> 757 bytes 29 files changed, 250 insertions(+), 72 deletions(-) create mode 100644 tests/src/main/java/sergio/sastre/composable/preview/scanner/android/previewparameters/NonPrimitiveClassComposables.kt delete mode 100644 tests/src/main/java/sergio/sastre/composable/preview/scanner/android/previewparameters/ValueClassComposables.kt create mode 100644 tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ArrayValueClassPreviewParameterPreview.valueClassArrayDp_Dp[]_0.png create mode 100644 tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.InnerClassPreviewParameterPreview.innerClass_Outer$Inner_0.png create mode 100644 tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ListValueClassPreviewParameterPreview.valueClassListDp_List_0.png create mode 100644 tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.MyClassPreviewParameterPreview.myClass_MyClass_0.png create mode 100644 tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_0.png create mode 100644 tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_1.png create mode 100644 tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_2.png create mode 100644 tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.WildcardList-with-hypen-backsticks.wildcardList_List_0.png create mode 100644 tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ArrayValueClassPreviewParameterPreview.valueClassArrayDp_Dp[]_0.png create mode 100644 tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.InnerClassPreviewParameterPreview.innerClass_Outer$Inner_0.png create mode 100644 tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ListValueClassPreviewParameterPreview.valueClassListDp_List_0.png create mode 100644 tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.MyClassPreviewParameterPreview.myClass_MyClass_0.png create mode 100644 tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_0.png create mode 100644 tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_1.png create mode 100644 tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_2.png create mode 100644 tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.WildcardList-with-hypen-backsticks.wildcardList_List_0.png create mode 100644 tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.arrayvalueclasspreviewparameterpreview.valueclassarraydp_dp[]_0.png create mode 100644 tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.innerclasspreviewparameterpreview.innerclass_outer$inner_0.png create mode 100644 tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.listvalueclasspreviewparameterpreview.valueclasslistdp_list%3cdp%3e_0.png create mode 100644 tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.myclasspreviewparameterpreview.myclass_myclass_0.png rename tests/src/test/snapshots/images/{Paparazzi_Preview_Test_android.previewparameters.valueclasscomposableskt.valueclasspreviewparameterpreview-8feqmps_float_0.png => Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.valueclasspreviewparameterpreview-rckniva.valueclassdp_dp_0.png} (100%) rename tests/src/test/snapshots/images/{Paparazzi_Preview_Test_android.previewparameters.valueclasscomposableskt.valueclasspreviewparameterpreview-8feqmps_float_1.png => Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.valueclasspreviewparameterpreview-rckniva.valueclassdp_dp_1.png} (100%) rename tests/src/test/snapshots/images/{Paparazzi_Preview_Test_android.previewparameters.valueclasscomposableskt.nullablevalueclasspreviewparameterpreview-rckniva_dp_0.png => Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.valueclasspreviewparameterpreview-rckniva.valueclassdp_dp_2.png} (100%) create mode 100644 tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.wildcardlist-with-hypen-backsticks.wildcardlist_list%3c%3f%3e_0.png diff --git a/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ComposablePreviewInvocationHandler.kt b/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ComposablePreviewInvocationHandler.kt index e085afd..c764d24 100644 --- a/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ComposablePreviewInvocationHandler.kt +++ b/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ComposablePreviewInvocationHandler.kt @@ -114,7 +114,7 @@ internal class ComposablePreviewInvocationHandler( */ private fun requirePreviewParameterIsFirstArgument() { val previewParameterIndex = composableMethod.parameterAnnotations.indexOfFirst { annotations -> - annotations.any { it.annotationClass.java.name in PREVIEW_PARAMETER_ANNOTATIONS } + annotations.any { (it as java.lang.annotation.Annotation).annotationType().name in PREVIEW_PARAMETER_ANNOTATIONS } } if (previewParameterIndex > 0) { throw PreviewParameterIsNotFirstArgumentException( diff --git a/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ProvideComposablePreview.kt b/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ProvideComposablePreview.kt index 900b975..8e5445c 100644 --- a/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ProvideComposablePreview.kt +++ b/core/src/main/java/sergio/sastre/composable/preview/scanner/core/preview/ProvideComposablePreview.kt @@ -2,7 +2,11 @@ package sergio.sastre.composable.preview.scanner.core.preview import androidx.compose.runtime.reflect.asComposableMethod import sergio.sastre.composable.preview.scanner.core.preview.mappers.ComposablePreviewMapper +import java.lang.reflect.GenericArrayType +import java.lang.reflect.ParameterizedType import java.lang.reflect.Proxy +import java.lang.reflect.Type +import java.lang.reflect.WildcardType /** * Provides an invokable ComposablePreview @@ -34,14 +38,23 @@ class ProvideComposablePreview { override val otherAnnotationsInfo = composablePreviewMapper.annotationsInfo override val declaringClass: String = composablePreviewMapper.previewMethod.declaringClass.toClassName() + + /** + * The JVM method name, including any compiler-generated hash (e.g., "-8Feqmps"). + * + * This hash is appended by the Compose compiler for overloads or methods using value classes + * to ensure uniqueness at the JVM level. We intentionally keep the raw JVM name to + * guarantee unique ComposablePreview IDs (and snapshot filenames), even if it means + * IDs might change across compiler versions or rebuilds. + */ override val methodName: String = composablePreviewMapper.previewMethod.name override val methodParametersType: String = methodParametersTypeAsString() override fun toString(): String { return buildList { - add(composablePreviewMapper.previewMethod.declaringClass.toClassName()) - add(composablePreviewMapper.previewMethod.name) + add(declaringClass) + add(methodName) if (methodParametersType.isNotBlank()){ add(methodParametersType) } @@ -72,13 +85,57 @@ class ProvideComposablePreview { return previewMethod .genericParameterTypes .take(realParametersCount) - .joinToString("_") { - // From java.lang.List to List - it.typeName + .mapIndexed { index, type -> + val v = if (index == 0) parameter else null + type.toResolvedTypeName(v) .replace(Regex("\\b[a-zA-Z_][a-zA-Z0-9_]*\\."), "") .replace("\\s+".toRegex(), "_") // blanks cause problems with some libs, like Android-Testify } + .joinToString("_") + } + + @Suppress("NewApi") + private fun Type.toResolvedTypeName(value: Any? = null): String { + return when (this) { + is Class<*> -> { + if (isArray) return "${componentType.toResolvedTypeName()}[]" + + // Recovery block for erased value classes (e.g. float -> Dp) + if (value != null && value != ComposablePreviewInvocationHandler.NoParameter) { + val valueClass = value.javaClass + val valueUnbox = valueClass.getUnderlyingType() + if (valueUnbox != null && (this == valueUnbox || this.toString() == valueUnbox.toString())) { + return valueClass.typeName + } + } + typeName + } + is ParameterizedType -> { + val raw = (rawType as? Class<*>)?.typeName ?: rawType.toString() + val args = actualTypeArguments.joinToString(", ") { it.toResolvedTypeName() } + "$raw<$args>" + } + is GenericArrayType -> "${genericComponentType.toResolvedTypeName()}[]" + is WildcardType -> { + val lower = lowerBounds.firstOrNull() + val upper = upperBounds.firstOrNull() + when { + lower != null -> "? super ${lower.toResolvedTypeName()}" + upper != null && upper != Any::class.java && upper.toString() != "class java.lang.Object" -> + "? extends ${upper.toResolvedTypeName()}" + else -> "?" + } + } + else -> toString() + } } + + private fun Class<*>.getUnderlyingType(): Class<*>? = + try { + declaredMethods.firstOrNull { it.name == "unbox-impl" }?.returnType + } catch (_: Exception) { + null + } } } } \ No newline at end of file diff --git a/tests/src/main/java/sergio/sastre/composable/preview/scanner/android/previewparameters/NonPrimitiveClassComposables.kt b/tests/src/main/java/sergio/sastre/composable/preview/scanner/android/previewparameters/NonPrimitiveClassComposables.kt new file mode 100644 index 0000000..d7ab96e --- /dev/null +++ b/tests/src/main/java/sergio/sastre/composable/preview/scanner/android/previewparameters/NonPrimitiveClassComposables.kt @@ -0,0 +1,87 @@ +package sergio.sastre.composable.preview.scanner.android.previewparameters + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +class DpValueClassProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf(32.dp, 64.dp, null) +} + +@Preview(name = "valueClassDp") +@Composable +fun ValueClassPreviewParameterPreview( + @PreviewParameter(provider = DpValueClassProvider::class) size: Dp?, +) { + Text(size.toString()) +} + +class ListDpValueClassProvider : PreviewParameterProvider> { + override val values: Sequence> = sequenceOf(listOf(32.dp, 64.dp)) +} + +@Preview(name = "valueClassListDp") +@Composable +fun ListValueClassPreviewParameterPreview( + @PreviewParameter(provider = ListDpValueClassProvider::class) sizes: List, +) { + Text(sizes.toString()) +} + +class ArrayDpValueClassProvider : PreviewParameterProvider> { + override val values: Sequence> = sequenceOf(arrayOf(32.dp, 64.dp)) +} + +@Preview(name = "valueClassArrayDp") +@Composable +fun ArrayValueClassPreviewParameterPreview( + @PreviewParameter(provider = ArrayDpValueClassProvider::class) sizes: Array, +) { + Text(sizes.joinToString()) +} + +data class MyClass(val name: String) + +class MyClassProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf(MyClass("test")) +} + +@Preview(name = "myClass") +@Composable +fun MyClassPreviewParameterPreview( + @PreviewParameter(provider = MyClassProvider::class) myClass: MyClass, +) { + Text(myClass.name) +} + +class WildcardListProvider : PreviewParameterProvider> { + override val values: Sequence> = sequenceOf(listOf(1, "2")) +} + +@Preview(name = "wildcardList") +@Composable +fun `WildcardList-with-hypen-backsticks`( + @PreviewParameter(provider = WildcardListProvider::class) list: List<*>, +) { + Text(list.toString()) +} + +class Outer { + class Inner(val value: String) +} + +class InnerClassProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf(Outer.Inner("nested")) +} + +@Preview(name = "innerClass") +@Composable +fun InnerClassPreviewParameterPreview( + @PreviewParameter(provider = InnerClassProvider::class) inner: Outer.Inner, +) { + Text(inner.value) +} diff --git a/tests/src/main/java/sergio/sastre/composable/preview/scanner/android/previewparameters/ValueClassComposables.kt b/tests/src/main/java/sergio/sastre/composable/preview/scanner/android/previewparameters/ValueClassComposables.kt deleted file mode 100644 index 0a9f5de..0000000 --- a/tests/src/main/java/sergio/sastre/composable/preview/scanner/android/previewparameters/ValueClassComposables.kt +++ /dev/null @@ -1,54 +0,0 @@ -package sergio.sastre.composable.preview.scanner.android.previewparameters - -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp - -/** - * @PreviewParameter whose provider yields a Kotlin value class (Dp). The preview method's signature - * therefore contains a value-class parameter, which trips kotlin-reflect's ValueClassAwareCaller when - * the scanner resolves `Method.kotlinFunction`. - */ -class DpValueClassProvider : PreviewParameterProvider { - override val values: Sequence = sequenceOf(32.dp, 64.dp) -} - -@Preview(name = "valueClassDp") -@Composable -fun ValueClassPreviewParameterPreview( - @PreviewParameter(provider = DpValueClassProvider::class) size: Dp, -) { - Text(size.toString()) -} - -class NullableDpValueClassProvider : PreviewParameterProvider { - override val values: Sequence = sequenceOf(null) -} - -@Preview(name = "valueClassDp") -@Composable -fun NullableValueClassPreviewParameterPreview( - @PreviewParameter(provider = NullableDpValueClassProvider::class) size: Dp?, -) { - Text(size.toString()) -} - -@JvmInline -value class DpExtended(val value: Float) - -class DpExtendedValueClassProvider : PreviewParameterProvider { - override val values: Sequence = - sequenceOf(DpExtended(32F), DpExtended(64F)) -} - -@Preview(name = "valueClassDpExtended") -@Composable -fun DpExtendedValueClassPreviewParameterPreview( - @PreviewParameter(provider = DpExtendedValueClassProvider::class) size: DpExtended, -) { - Text(size.toString()) -} diff --git a/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/api/main/screenshotid/ComposablePreviewToStringTest.kt b/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/api/main/screenshotid/ComposablePreviewToStringTest.kt index 77192a3..7fb468d 100644 --- a/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/api/main/screenshotid/ComposablePreviewToStringTest.kt +++ b/tests/src/test/java/sergio/sastre/composable/preview/scanner/tests/api/main/screenshotid/ComposablePreviewToStringTest.kt @@ -7,7 +7,7 @@ import sergio.sastre.composable.preview.scanner.android.AndroidComposablePreview import sergio.sastre.composable.preview.scanner.common.CommonComposablePreviewScanner @Suppress("DEPRECATION") -class ComposablePreviewToStringTest() { +class ComposablePreviewToStringTest { @Test fun `GIVEN Android previews WHEN toString THEN those preview names do not end with underscore`() { @@ -98,24 +98,112 @@ class ComposablePreviewToStringTest() { } @Test - fun `GIVEN a preview with a value-class @PreviewParameter WHEN toString THEN it is scanned without crashing`() { + fun `GIVEN a preview with a nullable value-class @PreviewParameter WHEN toString THEN it includes alphanumeric hashcode and value class name`() { val previews = AndroidComposablePreviewScanner() - .scanPackageTrees("sergio.sastre.composable.preview.scanner") + .scanPackageTrees("sergio.sastre.composable.preview.scanner.android.previewparameters") .filterPreviews { it.name == "valueClassDp" } .getPreviews() .map { it.toString() } - // The JVM method name of a value-class-parameter @Composable is mangled as "-", - // and we intentionally keep that raw name rather than demangling it. The "" disambiguates - // overloads whose value-class parameters share an underlying JVM type: methodParametersType is - // the erased underlying type (here "float"), so e.g. two same-named previews taking different - // value classes over Float would otherwise produce identical ids. The hash is - // compiler-version-dependent, so it is matched loosely rather than asserted verbatim. + Assert.assertTrue(previews.isNotEmpty()) + + val hashCodeRegex = "(-[a-zA-Z0-9]{7,11})" val idPattern = Regex( - "sergio\\.sastre\\.composable\\.preview\\.scanner\\.android\\.previewparameters\\.ValueClassComposablesKt_" + - "(-\\w+)?ValueClassPreviewParameterPreview_Dp_float_\\d", + NON_PRIMITIVE_CLASS_PATH + + "ValueClassPreviewParameterPreview${hashCodeRegex}_Dp_\\d", ) + Assert.assertTrue(previews.toString(), previews.all { idPattern.matches(it) }) } -} \ No newline at end of file + + @Test + fun `GIVEN a preview with a List of value-class @PreviewParameter WHEN toString THEN it also includes diamond parenthesis`() { + val previews = + AndroidComposablePreviewScanner() + .scanPackageTrees("sergio.sastre.composable.preview.scanner.android.previewparameters") + .filterPreviews { it.name == "valueClassListDp" } + .getPreviews() + .map { it.toString() } + + val idPattern = Regex( + NON_PRIMITIVE_CLASS_PATH + + "ListValueClassPreviewParameterPreview_List_\\d", + ) + + Assert.assertTrue(previews.toString(), previews.all { idPattern.matches(it) }) + } + + @Test + fun `GIVEN a preview with an Array of value-class @PreviewParameter WHEN toString THEN it also includes square parenthesis`() { + val previews = + AndroidComposablePreviewScanner() + .scanPackageTrees("sergio.sastre.composable.preview.scanner.android.previewparameters") + .filterPreviews { it.name == "valueClassArrayDp" } + .getPreviews() + .map { it.toString() } + + val idPattern = Regex( + NON_PRIMITIVE_CLASS_PATH + + "ArrayValueClassPreviewParameterPreview_Dp\\[\\]_\\d", + ) + + Assert.assertTrue(previews.toString(), previews.all { idPattern.matches(it) }) + } + + @Test + fun `GIVEN a preview with a regular class @PreviewParameter WHEN toString THEN the class name is in the name`() { + val previews = + AndroidComposablePreviewScanner() + .scanPackageTrees("sergio.sastre.composable.preview.scanner.android.previewparameters") + .filterPreviews { it.name == "myClass" } + .getPreviews() + .map { it.toString() } + + val idPattern = Regex( + NON_PRIMITIVE_CLASS_PATH + + "MyClassPreviewParameterPreview_MyClass_\\d", + ) + + Assert.assertTrue(previews.toString(), previews.all { idPattern.matches(it) }) + } + + @Test + fun `GIVEN a preview with a List of wildcard @PreviewParameter WHEN toString THEN wildcard translates to question mark`() { + val previews = + AndroidComposablePreviewScanner() + .scanPackageTrees("sergio.sastre.composable.preview.scanner.android.previewparameters") + .filterPreviews { it.name == "wildcardList" } + .getPreviews() + .map { it.toString() } + + val idPattern = Regex( + NON_PRIMITIVE_CLASS_PATH + + "WildcardList-with-hypen-backsticks_List<\\?>_\\d", + ) + + Assert.assertTrue(previews.toString(), previews.all { idPattern.matches(it) }) + } + + @Test + fun `GIVEN a preview with a nested class @PreviewParameter WHEN toString THEN the nested class name includes a dollar sign`() { + val previews = + AndroidComposablePreviewScanner() + .scanPackageTrees("sergio.sastre.composable.preview.scanner.android.previewparameters") + .filterPreviews { it.name == "innerClass" } + .getPreviews() + .map { it.toString() } + + val idPattern = Regex( + NON_PRIMITIVE_CLASS_PATH + + "InnerClassPreviewParameterPreview_Outer\\\$Inner_\\d", + ) + + Assert.assertTrue(previews.toString(), previews.all { idPattern.matches(it) }) + } + + companion object { + const val NON_PRIMITIVE_CLASS_PATH = + "sergio\\.sastre\\.composable\\.preview\\.scanner\\.android\\.previewparameters\\.NonPrimitiveClassComposablesKt_" + } +} diff --git a/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ArrayValueClassPreviewParameterPreview.valueClassArrayDp_Dp[]_0.png b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ArrayValueClassPreviewParameterPreview.valueClassArrayDp_Dp[]_0.png new file mode 100644 index 0000000000000000000000000000000000000000..9e2542814791f1ec2f8d1174fce49ed5f0b1f565 GIT binary patch literal 3186 zcmV-&42|=NP)rL^Q7M|whV@`e}_F@h=`LdH2I=S0w>NF^G zuag^`{733C|LNr0PTu6?0jsI=a&ITQoqWs5ol@8RcPE!Sd8L!Phs5hZB?p1gGlPF% z^Vc{z&&g+;TpdfaS%0S!|47 zIr*-WeNO%r>3Fc8Lgp55nx-;ua`JsAKM~to2iYxoIN8a+#g=`W__}{2{`$4no#ELPG08ZP?hGMPF^3S`@Q%MZY`gC6wnvFcWKiM{!USamY28Fe|#Zu)qs*gWyFYnfDH>s}M1XX33=UpGtA zu9(#nJ}7lPS2;OJ{TrFBrW{A{nFNRtoHZAwcPV^8>We%O>3G2PO|YGsLTvmvo{`EP zvE?vY^nsCep66KgXl72{AwCfH=eb<#42Mdb%Nmh@0UfVkBwfwk9{9r;G|?=CrCmbZ4+4;P1?G{mHi5I^d2 zs&OoOX>*;_ajW=2nazJ6OYxanviZzzu?2_e&=j_c-;N4pzQw8s!5TJ{b48@<;^$K5 z`)H&aLM66+2$C@p^3c%95&%3}+PC`pjE*pp;h0P@c_o%rFdMj*dFB*88)MftF z$zfJK2o8vavf`1hiyup!Z|wFgM*X$aTPxoxP(dK4a4-`^*XAje{`7r15SOTiCghSXdAA zl)CfTWz~cEfz)MpA5hvQb$Yf%;}^1WcNE2EM*55%;bY;f#k1<9lVEhw>pWh<*L?J-UOaC#^*C4~eMjmt8I^F7sjR2bb&%;Rk*AeEC>h!3Rbo*sGE zJpE9ZHGE#`a#(t#p5f?Yy^PNEShvlat-6&;Y`$DZCwd+7+!5)f!c; zmDqf_jDCjLd|_)b2R!7u?9`dyKB@D>y9*C=a_d}&bhTgZj-vRCC#%n}wW7Di=#7U! zucqtG0i(~0;44i4zNpR#`w+YUhLM1j|E~67a z3VCSvoEIFDe zM{p#hzfy*s(i=iNElzP7w)AwKldU=l8daYsIM1qEsl?Wu%jlO#o#%!~Hx;%#^)>a` zKcIKh*k%vtcP4U3ip@2}XFQikoyYhLQ$}6jOSH0sID*}E>T4$cEwzxAx8gY2w=9kk zdu=9$yPGV#d76upmltFh1M%+C5aRcxNr0bmHc3LVPFhx%rtMELd66CWB&?qFu| znFOD?Xs$q*0`~PjL_%+pI#1id3}y}2NS%&#XwxB^W$%G&-qpjLNBkPx9h|G_L zkH2!dE|kT)4P9UNBA68)jqfx1)zUp;vhLNVU3mcF85|EaZGvo%bg3{1=!c8LLS5Iz z2o=)eXH#BF&)rc#@tHb(lh3Stx=i=b09U)yuxKkL?QKZ_f_)INLKC{9e zSuB{IjV&K;g0l_;AayT_v>E)}V$<ysQU#NQmxY zjQD zS4VNE*Z>%EyZ8?qu{60r79D%PG+)yYQag$vc7x(G?LM;#YZM;nxbP<7Cg^qA0x$YS zmnou;`7$Gk_3>MUF|31Q!!`-`)Afek(7KsUUMq`vVqrPPD4~DrGdi7mJoRjnUrO@` z0=lCh-4vK6^8m&5HwjMI7t2T?wWAnnHz+=n0Ey^1<53_89SNgE*ZK{X(NWMTF|`f4 z)DxX7t>`)9UKn9*$YGnlWzm?r86}vFG4+h=Js|#*X2g58M!G(u6GOsI=Sy?PeyjNJ zgmgJ2!Mc$C>PR<+L&To}z|rFCL2eZ9tjs`iklIm-e5I^#a8XHKpDAnrD#>T`!^Hl-IF7aI!X}}Td_GgyAXJji=toGsXU?(d!seus zd_GgyAXJjq=mf=1jDIX_Dk{nAGldO6CB^84VN_Cl=KqeJ;?K1N>D#|A6pGIj3Wc@f Yf0A}D_7X2&5C8xG07*qoM6N<$g1Q|>K>z>% literal 0 HcmV?d00001 diff --git a/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.InnerClassPreviewParameterPreview.innerClass_Outer$Inner_0.png b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.InnerClassPreviewParameterPreview.innerClass_Outer$Inner_0.png new file mode 100644 index 0000000000000000000000000000000000000000..21d0e0dfe5eb00e2b9073a0f3bfd0ba103cba6b7 GIT binary patch literal 1549 zcmV+o2J-odP){2?iMW*{S05<=c4CC7m2oB<88SdbwBTd9hldZjpDd;k*hysBw7Mr z{-;VGh;9+>U->xn1oF7b5VVEpH2BK&^;jG<`u4FK{KF8JkM$({=|va2i{3!9Y^;@{ zv#K(h_tEri6UQY*&i?SB3dLoJ2h(?$ejHlH=$XkO`LJ`|Gj|>dabf?irrAOvuLsCkI1i4 zVZ{!j?@;@(qb$$*0wdAA%7?m9*)?oW@ZnQ>m1sL<=X83ZXqEE&N3>t$!~0WwyY+op zU$9XdS7idZ`{Q8!Sl<=!59~bnHT*EqgOOiQW871L&=xn9GBLf6OCnR~K6#nUH~kpQRjw<9|rAWm6KW|)kP%g{jP?hm(Y z`Z|-WAvoA_Qx7DkJzd|VIslekL?2bQSSv(Nz$XPdi|k@(AanPJ>!-e++V7@tu;r#6 zNZugs#3V{(h5Yn%8SFM77PlNced1fu#n3?J?$2J)^^>dXeK^>1GY=#;#a#Hu)d=ly zyVb?KdZsl`h3(;w6}i{rVS&DX z+tf8Qkh%M_Eo^;lS#^wqEjRT*&W9b$wv&x%u?Rj(4EJY-m^$1T8pz!JVOv(Bu8*_I z5FBiKryfZ1E^7P7NDt&tnB|?S#G}GK;xo+nWQ7b3B+1zy@)GR$4XMw;ws$6hj1S(O zv9`lhe_nKL0c>4e>t5g#*fJwy^~BqNu0ZDO54jk&zN4)<$px@wW)esa{wmZyJks0I zcr@V~!oA|lXl@}sL|xZ1G!L|6c>WvwnZ>3)w{d`Vzpu1rL+<|YfBo@+{p&g{;yV2+ zYJfB5(|vU*D;-wNpgS{?Lchte)nBR=}U_ zvM<~QX={$1s`T=c*In>tg0NX_0.png b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ListValueClassPreviewParameterPreview.valueClassListDp_List_0.png new file mode 100644 index 0000000000000000000000000000000000000000..803c5421a6e83490d650497a2e12194784abc608 GIT binary patch literal 3402 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 RR676w002ovPDHLkV1go0<68g# literal 0 HcmV?d00001 diff --git a/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_0.png b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_0.png new file mode 100644 index 0000000000000000000000000000000000000000..9e18e6b119655fbee4b05a0230493f5ede73abd3 GIT binary patch literal 2108 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 M07*qoM6N<$g1QSW;+VXA z#*6-nfa;lWYI|R3pr;N zOuR^dxZr#9+>&(_d!}yk*tx(aFi!LP6z5YKljMIEeQ}<*+4EXp+QfI$oBJ-F$S{*= zlrarb)6_rs(O~(}+#bmjxwF?SmQlWYpG9^t(4L}0s}H{RvUHLOKD0DO%=%x(b)}#2 zyId-{%l4i~_N(B2x_@!-gxjW(;s&Z`?w|Gg&|BfQ%U(FQN%Nsyq`Rf#Q}cAM9q!ix zzfFAibGg=%S1sQsypwP4D@=@ZFMP<*+mrVAkexY?tj2{-BC@wo`)s^|s@kBA- zX)E^s)w|mgAEuoses{v^xXqJ$a}0`GOFlbBI{YeF=)#@K{OOm>_0.png b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.WildcardList-with-hypen-backsticks.wildcardList_List_0.png new file mode 100644 index 0000000000000000000000000000000000000000..528a6b8b0c5038b9c6c3bba956eda1ddea034822 GIT binary patch literal 867 zcmV-p1DyPcP)CO4 zAvTh%q@-+ko;h>xbDneOO!wY*=FWZRo?rbJGtaqmp65Sz?mhQ80|WwrKsA^_I_J@8 zYh15$5Xt%@EwRSM%qRWx=&LoZ*Lj3w{gF0W<6?qwhCv&*z40Dvt~G9`F`P7kG>$aP zi8Z4Jse3hY7`H}6voQzeT5;8RzbF=y`bn=ye<3yfB;6zJAx(-$`F zk@l0`BbkV;B8_v;wDY9dYJ|DY?I!(!jN_m30(+1}+l{-1q)DtNr03S_qKsP(oyT#s zz7cGK9)CfHV>)UapLKjAnAeD%v|>dWcNsG0N75J_Zps75G2cm}bhu92jk^T%G_efk zI%iX~aU)?0kLEn0!(RHJ>z$^%6v~hel z-C~KKxzoT)C%fK>I$WoxjmygkNS-g9SVS<+VJyt^ca-No9gYdcIq(`Z%JCa%vJS@t z<17|IFM#`WSP_hCz-QJwNPc%=x+gw@al~lSeMoM6jbme4QF-I|thxxvi+`#s)rjU) z-ngUCwQbX3FO@cKFLYgLAIX@?8s~OFPN8+z#HEtPt%6>5a0i7O0$He>aSLHu<532|Yxl-{^0FnhPGniES4pH)vGb8}}p zZHviA>5V%CIp(bsO9NlOZa_ZcGtA!ZVCi||4nyb89S{DTtaA5Rhm>)zomd*qK2m=1N7QX9vcEx#f2F4f_#QflLNLFODl>#fJ4$z>cr zvT(DH#cQHYz#Z8ykU4pSyW8A?)XjJ2%*NU3)ITz(L%OmDoLINW{Etjtwe!NUnewcy t(!w2!weFi;r~Uu{ literal 0 HcmV?d00001 diff --git a/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ArrayValueClassPreviewParameterPreview.valueClassArrayDp_Dp[]_0.png b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ArrayValueClassPreviewParameterPreview.valueClassArrayDp_Dp[]_0.png new file mode 100644 index 0000000000000000000000000000000000000000..9e2542814791f1ec2f8d1174fce49ed5f0b1f565 GIT binary patch literal 3186 zcmV-&42|=NP)rL^Q7M|whV@`e}_F@h=`LdH2I=S0w>NF^G zuag^`{733C|LNr0PTu6?0jsI=a&ITQoqWs5ol@8RcPE!Sd8L!Phs5hZB?p1gGlPF% z^Vc{z&&g+;TpdfaS%0S!|47 zIr*-WeNO%r>3Fc8Lgp55nx-;ua`JsAKM~to2iYxoIN8a+#g=`W__}{2{`$4no#ELPG08ZP?hGMPF^3S`@Q%MZY`gC6wnvFcWKiM{!USamY28Fe|#Zu)qs*gWyFYnfDH>s}M1XX33=UpGtA zu9(#nJ}7lPS2;OJ{TrFBrW{A{nFNRtoHZAwcPV^8>We%O>3G2PO|YGsLTvmvo{`EP zvE?vY^nsCep66KgXl72{AwCfH=eb<#42Mdb%Nmh@0UfVkBwfwk9{9r;G|?=CrCmbZ4+4;P1?G{mHi5I^d2 zs&OoOX>*;_ajW=2nazJ6OYxanviZzzu?2_e&=j_c-;N4pzQw8s!5TJ{b48@<;^$K5 z`)H&aLM66+2$C@p^3c%95&%3}+PC`pjE*pp;h0P@c_o%rFdMj*dFB*88)MftF z$zfJK2o8vavf`1hiyup!Z|wFgM*X$aTPxoxP(dK4a4-`^*XAje{`7r15SOTiCghSXdAA zl)CfTWz~cEfz)MpA5hvQb$Yf%;}^1WcNE2EM*55%;bY;f#k1<9lVEhw>pWh<*L?J-UOaC#^*C4~eMjmt8I^F7sjR2bb&%;Rk*AeEC>h!3Rbo*sGE zJpE9ZHGE#`a#(t#p5f?Yy^PNEShvlat-6&;Y`$DZCwd+7+!5)f!c; zmDqf_jDCjLd|_)b2R!7u?9`dyKB@D>y9*C=a_d}&bhTgZj-vRCC#%n}wW7Di=#7U! zucqtG0i(~0;44i4zNpR#`w+YUhLM1j|E~67a z3VCSvoEIFDe zM{p#hzfy*s(i=iNElzP7w)AwKldU=l8daYsIM1qEsl?Wu%jlO#o#%!~Hx;%#^)>a` zKcIKh*k%vtcP4U3ip@2}XFQikoyYhLQ$}6jOSH0sID*}E>T4$cEwzxAx8gY2w=9kk zdu=9$yPGV#d76upmltFh1M%+C5aRcxNr0bmHc3LVPFhx%rtMELd66CWB&?qFu| znFOD?Xs$q*0`~PjL_%+pI#1id3}y}2NS%&#XwxB^W$%G&-qpjLNBkPx9h|G_L zkH2!dE|kT)4P9UNBA68)jqfx1)zUp;vhLNVU3mcF85|EaZGvo%bg3{1=!c8LLS5Iz z2o=)eXH#BF&)rc#@tHb(lh3Stx=i=b09U)yuxKkL?QKZ_f_)INLKC{9e zSuB{IjV&K;g0l_;AayT_v>E)}V$<ysQU#NQmxY zjQD zS4VNE*Z>%EyZ8?qu{60r79D%PG+)yYQag$vc7x(G?LM;#YZM;nxbP<7Cg^qA0x$YS zmnou;`7$Gk_3>MUF|31Q!!`-`)Afek(7KsUUMq`vVqrPPD4~DrGdi7mJoRjnUrO@` z0=lCh-4vK6^8m&5HwjMI7t2T?wWAnnHz+=n0Ey^1<53_89SNgE*ZK{X(NWMTF|`f4 z)DxX7t>`)9UKn9*$YGnlWzm?r86}vFG4+h=Js|#*X2g58M!G(u6GOsI=Sy?PeyjNJ zgmgJ2!Mc$C>PR<+L&To}z|rFCL2eZ9tjs`iklIm-e5I^#a8XHKpDAnrD#>T`!^Hl-IF7aI!X}}Td_GgyAXJji=toGsXU?(d!seus zd_GgyAXJjq=mf=1jDIX_Dk{nAGldO6CB^84VN_Cl=KqeJ;?K1N>D#|A6pGIj3Wc@f Yf0A}D_7X2&5C8xG07*qoM6N<$g1Q|>K>z>% literal 0 HcmV?d00001 diff --git a/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.InnerClassPreviewParameterPreview.innerClass_Outer$Inner_0.png b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.InnerClassPreviewParameterPreview.innerClass_Outer$Inner_0.png new file mode 100644 index 0000000000000000000000000000000000000000..21d0e0dfe5eb00e2b9073a0f3bfd0ba103cba6b7 GIT binary patch literal 1549 zcmV+o2J-odP){2?iMW*{S05<=c4CC7m2oB<88SdbwBTd9hldZjpDd;k*hysBw7Mr z{-;VGh;9+>U->xn1oF7b5VVEpH2BK&^;jG<`u4FK{KF8JkM$({=|va2i{3!9Y^;@{ zv#K(h_tEri6UQY*&i?SB3dLoJ2h(?$ejHlH=$XkO`LJ`|Gj|>dabf?irrAOvuLsCkI1i4 zVZ{!j?@;@(qb$$*0wdAA%7?m9*)?oW@ZnQ>m1sL<=X83ZXqEE&N3>t$!~0WwyY+op zU$9XdS7idZ`{Q8!Sl<=!59~bnHT*EqgOOiQW871L&=xn9GBLf6OCnR~K6#nUH~kpQRjw<9|rAWm6KW|)kP%g{jP?hm(Y z`Z|-WAvoA_Qx7DkJzd|VIslekL?2bQSSv(Nz$XPdi|k@(AanPJ>!-e++V7@tu;r#6 zNZugs#3V{(h5Yn%8SFM77PlNced1fu#n3?J?$2J)^^>dXeK^>1GY=#;#a#Hu)d=ly zyVb?KdZsl`h3(;w6}i{rVS&DX z+tf8Qkh%M_Eo^;lS#^wqEjRT*&W9b$wv&x%u?Rj(4EJY-m^$1T8pz!JVOv(Bu8*_I z5FBiKryfZ1E^7P7NDt&tnB|?S#G}GK;xo+nWQ7b3B+1zy@)GR$4XMw;ws$6hj1S(O zv9`lhe_nKL0c>4e>t5g#*fJwy^~BqNu0ZDO54jk&zN4)<$px@wW)esa{wmZyJks0I zcr@V~!oA|lXl@}sL|xZ1G!L|6c>WvwnZ>3)w{d`Vzpu1rL+<|YfBo@+{p&g{;yV2+ zYJfB5(|vU*D;-wNpgS{?Lchte)nBR=}U_ zvM<~QX={$1s`T=c*In>tg0NX_0.png b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ListValueClassPreviewParameterPreview.valueClassListDp_List_0.png new file mode 100644 index 0000000000000000000000000000000000000000..803c5421a6e83490d650497a2e12194784abc608 GIT binary patch literal 3402 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 RR676w002ovPDHLkV1go0<68g# literal 0 HcmV?d00001 diff --git a/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_0.png b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_0.png new file mode 100644 index 0000000000000000000000000000000000000000..9e18e6b119655fbee4b05a0230493f5ede73abd3 GIT binary patch literal 2108 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 M07*qoM6N<$g1QSW;+VXA z#*6-nfa;lWYI|R3pr;N zOuR^dxZr#9+>&(_d!}yk*tx(aFi!LP6z5YKljMIEeQ}<*+4EXp+QfI$oBJ-F$S{*= zlrarb)6_rs(O~(}+#bmjxwF?SmQlWYpG9^t(4L}0s}H{RvUHLOKD0DO%=%x(b)}#2 zyId-{%l4i~_N(B2x_@!-gxjW(;s&Z`?w|Gg&|BfQ%U(FQN%Nsyq`Rf#Q}cAM9q!ix zzfFAibGg=%S1sQsypwP4D@=@ZFMP<*+mrVAkexY?tj2{-BC@wo`)s^|s@kBA- zX)E^s)w|mgAEuoses{v^xXqJ$a}0`GOFlbBI{YeF=)#@K{OOm>_0.png b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.WildcardList-with-hypen-backsticks.wildcardList_List_0.png new file mode 100644 index 0000000000000000000000000000000000000000..528a6b8b0c5038b9c6c3bba956eda1ddea034822 GIT binary patch literal 867 zcmV-p1DyPcP)CO4 zAvTh%q@-+ko;h>xbDneOO!wY*=FWZRo?rbJGtaqmp65Sz?mhQ80|WwrKsA^_I_J@8 zYh15$5Xt%@EwRSM%qRWx=&LoZ*Lj3w{gF0W<6?qwhCv&*z40Dvt~G9`F`P7kG>$aP zi8Z4Jse3hY7`H}6voQzeT5;8RzbF=y`bn=ye<3yfB;6zJAx(-$`F zk@l0`BbkV;B8_v;wDY9dYJ|DY?I!(!jN_m30(+1}+l{-1q)DtNr03S_qKsP(oyT#s zz7cGK9)CfHV>)UapLKjAnAeD%v|>dWcNsG0N75J_Zps75G2cm}bhu92jk^T%G_efk zI%iX~aU)?0kLEn0!(RHJ>z$^%6v~hel z-C~KKxzoT)C%fK>I$WoxjmygkNS-g9SVS<+VJyt^ca-No9gYdcIq(`Z%JCa%vJS@t z<17|IFM#`WSP_hCz-QJwNPc%=x+gw@al~lSeMoM6jbme4QF-I|thxxvi+`#s)rjU) z-ngUCwQbX3FO@cKFLYgLAIX@?8s~OFPN8+z#HEtPt%6>5a0i7O0$He>aSLHu<532|Yxl-{^0FnhPGniES4pH)vGb8}}p zZHviA>5V%CIp(bsO9NlOZa_ZcGtA!ZVCi||4nyb89S{DTtaA5Rhm>)zomd*qK2m=1N7QX9vcEx#f2F4f_#QflLNLFODl>#fJ4$z>cr zvT(DH#cQHYz#Z8ykU4pSyW8A?)XjJ2%*NU3)ITz(L%OmDoLINW{Etjtwe!NUnewcy t(!w2!weFi;r~Uu{ literal 0 HcmV?d00001 diff --git a/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.arrayvalueclasspreviewparameterpreview.valueclassarraydp_dp[]_0.png b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.arrayvalueclasspreviewparameterpreview.valueclassarraydp_dp[]_0.png new file mode 100644 index 0000000000000000000000000000000000000000..ff43b478bfe1e1e15e40adb84725d11ec8dcc620 GIT binary patch literal 2448 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 literal 0 HcmV?d00001 diff --git a/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.listvalueclasspreviewparameterpreview.valueclasslistdp_list%3cdp%3e_0.png b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.listvalueclasspreviewparameterpreview.valueclasslistdp_list%3cdp%3e_0.png new file mode 100644 index 0000000000000000000000000000000000000000..ebe05f778a97fedad163c1f0dec99375f48ef2f3 GIT binary patch literal 2577 zcmV+s3hwoZP)l9I%el9H0dlai8>(v5h2{P=PD`0?YaJbn5!Co3f-r7168z8vI{FJHc#C7y5J zzMUQ&9aZJ()vGzFDJdyUxqJ8SAdh_b@F9t3U**@YUn|W~`t|E;zrS|v+UcW5k4{fcP9~EEZ9aSU?DXc%n`bPqd-v|GNHLn=R6D$S z^=if37cXA?-}j|UmsWp!^X5&rzRxr9Fb>w0bq6;O9y|z}=i9e$|DP|`@aNB;iJ1-b zOWZh|cwlr0XUMElVc|W4rOhW#p19hCgmfSg0!h+tt8Ym4^XJb=JOP%$>({SW^I4nc z2!dcuZM$9zJ~6Xn!~IcAKw+e_nmL)LQy@g!z8Ogx`HeOmh-KKWSZOuT*{(6uI})h2D|_m|cg zy?F6rt-Ud=WS=$4;yN$H!`v$AU=k6Pyp6-=|}MuMLda_4HJ(p zsAs}0S@C=OxyEkLB%U_O6zKEL%^G0RYddI4kE;+J1U(PLqicqX-0Rt&KY#weA2z%V zFfS#Z#LO0mhqw)Hhrnp1kMiT>2rg8 z)aKYA9onD>59?`@K}1+Q#FNy`^ZrhQ=4kQ=YoM03E5c@C!CN5G&uNbhuVbtUV^P5M z*1Gi9qppRqYGxEAS=)F^i6=4R!^~ERhe4=6+zK4eS^OnFvu4wX8+m`Hu?jTB(t~xL z+NA;vFNhEdF1>9>txapaY@82_08(U0kQpmnF;UuzCZ5EM4>MaO9<+|O8)4RC+G;9u ziT2Q-AOG$Bood!4Sv9-pMU${O*fJ=hniNyh3!E+8XLq_;Tf=S zZM83gIZrlpO8bnbw1cMCd5I>T#EcI!TOl6E!PeYFne)o_5YQ5{U9)4-?QWN59mIFL>0nX`Q+o_)ehcjBP~tQv&h0V>Qj>u!0> zLQf`vxRJMpXjehR=9)7%y+EWv!=;trtz$DDQ{K8~wfRv^jyo~rk{t^1>>FmbmenFK zs2KonmFL(1qtCi*D^SSX%qg7g1tOb$j66GK3+}wnq?WYJ9@#^hWAk??!R39!%vRIs z_S_7Uza$+WUGb{Kh}k|TH9BPC_o!s;1tLohkp+Hp+t!MAESez;xV3IQ)9Hzs;dR}5 z<+6Q1Z)_&|6nbPan3Cz7V60sfZ~aU!5ZSYDkykg&J*Q-!X=sdmkG~Cxt!F`*>F0@= zKA71$@z_Ikp0;txG+Q<|7&pa@s~un+60HWE6T3n?c!RB*0Fl;4G4DOIT3(Ao6&7*5 zS_N=~Om!<0Gd|3O5f6-}n!>BOtcloSY9>Yl)0nosE_+wj2HYM^Z+-2#)xH{|@M^$b zAkxd-?0LGLrv|w5EqEd()bqwRYmIqo zj-pP+Ybw~g*=^quAY$X`HJx+f4^!^1@wTt?qp=hSX^L12i4CKlCuaI!Ce+nZ#`Q+# z_3++Ua1pJbez2!%HR2GfT34Qfl6t?;66?UGnLPlums+(z@y<{oolzFXEZ zpQ~lFq!c4MxSW{rVJ4J#v{V|OwN>S4FWergHpZ&abR$|SqCjw}&tC3u8FK^m{28RW zq!L>JtS72HE{g|5t$2;C2Cr4a@*U=+$Xs_zV;rrRod$%N?CZ5Dh8#Cd=!vrgU zAeEuo9KLH01J)RtiPID%u2uv%2u|{ROwt&O+cBY$YW*`uYLSf{Q1$k0%LO3j+9n%% zD}(32-_Sd%=*O;4ZBwYVG9{SWy2=4^v4E}J1+(JoDH{<2q7TMv4>U6Nr7=D)@g!#AFw-ra&L+3- z4H~wgM@AATXWa=9M#!{o@W;$)# zQ^TU0D*AfVa=;ke@b9J)3W=G%nAwTS<&^EcLn-1dtLoQp>;2y;`-7Pzo&?ci5b3c6 zT%ere#7tM>A*zm_i1fIJH)Z!hWUrn`YbP<|amx#D*!Yl(cx<357wG(fhyY{Bob-P0 zR?1;uW-QZIC7yCdswPWnH&XWBY)-d3F*C*D8R8Kwiag4RPY|UfW;TtPB%T8kN*O4- nFA{O|BqcGE#FLVeviI^2i=Z74>q1w500000NkvXXu0mjf1Lz9s literal 0 HcmV?d00001 diff --git a/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.myclasspreviewparameterpreview.myclass_myclass_0.png b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.myclasspreviewparameterpreview.myclass_myclass_0.png new file mode 100644 index 0000000000000000000000000000000000000000..9a072df13ee1510fec6a0462c3fd17ed51ae5089 GIT binary patch literal 896 zcmV-`1AqL9P)XIo600aO6 z00DpiKmZ^B5C8}O{H4#&Pu1`D6MA`h`PIgyvF8YY-EOC%C`xELo&IV7(%2IK2mo9< z0ORpEDH2alPe&IZSwucQKAZw6GQD0;HJeRUE|*ocT2-x9OAQ7C_4f9bNqNbuQmMrJ z8jXf}e0)^T&(E=Orh8fKiBM2KWTVw;WfZzzuhsqiz4v~%+qEh#d9~YZFE2n@EEZqx zXSL^K0N&rYRUQQ>uWWcOycJfAi`GiB_&J%NY4Wv@;95!kv#Y#N1l_Znbn??ieRVHIWqqB z_0@Wo@Rv%XoP1A>kg~~TSADp;x=PMcD{}7waRCu$@r(;B5SL**8jX(L6UvgG^t_24 z9v+gj6vx~FAdF>>+Va{K1>~xHH=E6lWa|;46VZl#5O;G20MrlXXg6tEoAfeiAVI?* zDI1d+HNI!?Nq=b+3FxS1Tx4)a4o46KGNdGGU^y>Ou(hVc;8GKAqtDXAniA-CbL^n;lK|#GW z*@#fLDU5e?gvX9irS)A8l7-EIxI$iS^uEBkMd z>-Cyg5UWhoTq8f161^{$C z9zDOs0gn2v>2xYJXSrND)+>s#N}_69;65$xlk9do$2?K4nrd*c6!(T4=qz`K{3l3e5N~PjxuA;>Hb>}5j;O_T3tJmv)_s2Pd z%xP9)!C`T*6fR*4cUTwgOv|>I5(|zN-LRN2RRR1MzXrDIFAmXD4-UB*)g9#&uJf%R zeMv01Fn&??5IRWQ6@~WT=$xRAMQXu?5$d0U&O}v;*5D9PX@)O4ax~xsg6%=`E}a^m zp}?VJK|daXoG2=5qlYFsSR331UpJz&mTzPt)8}1TMAk&Uk;!X3k%Y;uzI|y%m+c9Q n^W%WifAhM&`9B8(E>ZFi3U>yBA+>dA00000NkvXXu0mjfJtJ;t literal 0 HcmV?d00001