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..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 @@ -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 as java.lang.annotation.Annotation).annotationType().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..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 @@ -1,8 +1,12 @@ 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 kotlin.reflect.jvm.kotlinFunction +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) } @@ -54,31 +67,75 @@ 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) - .joinToString("_") { - // From java.lang.List to List - it.typeName + .take(realParametersCount) + .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/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..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`() { @@ -96,4 +96,114 @@ class ComposablePreviewToStringTest() { Assert.assertEquals(expectedPreviewString, previewToString) } -} \ No newline at end of file + + @Test + 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.android.previewparameters") + .filterPreviews { it.name == "valueClassDp" } + .getPreviews() + .map { it.toString() } + + Assert.assertTrue(previews.isNotEmpty()) + + val hashCodeRegex = "(-[a-zA-Z0-9]{7,11})" + val idPattern = Regex( + NON_PRIMITIVE_CLASS_PATH + + "ValueClassPreviewParameterPreview${hashCodeRegex}_Dp_\\d", + ) + + Assert.assertTrue(previews.toString(), previews.all { idPattern.matches(it) }) + } + + @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 0000000..9e25428 Binary files /dev/null and b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ArrayValueClassPreviewParameterPreview.valueClassArrayDp_Dp[]_0.png differ 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 0000000..21d0e0d Binary files /dev/null and b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.InnerClassPreviewParameterPreview.innerClass_Outer$Inner_0.png differ diff --git a/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ListValueClassPreviewParameterPreview.valueClassListDp_List_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 0000000..803c542 Binary files /dev/null and b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ListValueClassPreviewParameterPreview.valueClassListDp_List_0.png differ diff --git a/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.MyClassPreviewParameterPreview.myClass_MyClass_0.png b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.MyClassPreviewParameterPreview.myClass_MyClass_0.png new file mode 100644 index 0000000..febd888 Binary files /dev/null and b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.MyClassPreviewParameterPreview.myClass_MyClass_0.png differ 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 0000000..9e18e6b Binary files /dev/null and b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_0.png differ diff --git a/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_1.png b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_1.png new file mode 100644 index 0000000..90444a9 Binary files /dev/null and b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_1.png differ diff --git a/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_2.png b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_2.png new file mode 100644 index 0000000..93a989d Binary files /dev/null and b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_2.png differ diff --git a/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.WildcardList-with-hypen-backsticks.wildcardList_List_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 0000000..528a6b8 Binary files /dev/null and b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.WildcardList-with-hypen-backsticks.wildcardList_List_0.png differ 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 0000000..93a989d Binary files /dev/null and b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.NullableValueClassPreviewParameterPreview-rCkNIVA_Dp_0.png differ diff --git a/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_0.png b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_0.png new file mode 100644 index 0000000..9e18e6b Binary files /dev/null and b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_0.png differ diff --git a/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_1.png b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_1.png new file mode 100644 index 0000000..90444a9 Binary files /dev/null and b/tests/src/test/screenshots/android/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_1.png differ 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 0000000..9e25428 Binary files /dev/null and b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ArrayValueClassPreviewParameterPreview.valueClassArrayDp_Dp[]_0.png differ 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 0000000..21d0e0d Binary files /dev/null and b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.InnerClassPreviewParameterPreview.innerClass_Outer$Inner_0.png differ diff --git a/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ListValueClassPreviewParameterPreview.valueClassListDp_List_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 0000000..803c542 Binary files /dev/null and b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ListValueClassPreviewParameterPreview.valueClassListDp_List_0.png differ diff --git a/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.MyClassPreviewParameterPreview.myClass_MyClass_0.png b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.MyClassPreviewParameterPreview.myClass_MyClass_0.png new file mode 100644 index 0000000..febd888 Binary files /dev/null and b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.MyClassPreviewParameterPreview.myClass_MyClass_0.png differ 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 0000000..9e18e6b Binary files /dev/null and b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_0.png differ diff --git a/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_1.png b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_1.png new file mode 100644 index 0000000..90444a9 Binary files /dev/null and b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_1.png differ diff --git a/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_2.png b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_2.png new file mode 100644 index 0000000..93a989d Binary files /dev/null and b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.ValueClassPreviewParameterPreview-rCkNIVA.valueClassDp_Dp_2.png differ diff --git a/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.WildcardList-with-hypen-backsticks.wildcardList_List_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 0000000..528a6b8 Binary files /dev/null and b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.NonPrimitiveClassComposablesKt.WildcardList-with-hypen-backsticks.wildcardList_List_0.png differ diff --git a/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.NullableValueClassPreviewParameterPreview-rCkNIVA_Dp_0.png b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.NullableValueClassPreviewParameterPreview-rCkNIVA_Dp_0.png new file mode 100644 index 0000000..93a989d Binary files /dev/null and b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.NullableValueClassPreviewParameterPreview-rCkNIVA_Dp_0.png differ diff --git a/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_0.png b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_0.png new file mode 100644 index 0000000..9e18e6b Binary files /dev/null and b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_0.png differ diff --git a/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_1.png b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_1.png new file mode 100644 index 0000000..90444a9 Binary files /dev/null and b/tests/src/test/screenshots/sourceset/sergio.sastre.composable.preview.scanner.android.previewparameters.ValueClassComposablesKt.ValueClassPreviewParameterPreview-8Feqmps_float_1.png differ 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 0000000..ff43b47 Binary files /dev/null and b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.arrayvalueclasspreviewparameterpreview.valueclassarraydp_dp[]_0.png differ diff --git a/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.innerclasspreviewparameterpreview.innerclass_outer$inner_0.png b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.innerclasspreviewparameterpreview.innerclass_outer$inner_0.png new file mode 100644 index 0000000..3a68adf Binary files /dev/null and b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.innerclasspreviewparameterpreview.innerclass_outer$inner_0.png differ 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 0000000..ebe05f7 Binary files /dev/null and b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.listvalueclasspreviewparameterpreview.valueclasslistdp_list%3cdp%3e_0.png differ 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 0000000..9a072df Binary files /dev/null and b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.myclasspreviewparameterpreview.myclass_myclass_0.png differ diff --git a/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.valueclasspreviewparameterpreview-rckniva.valueclassdp_dp_0.png b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.valueclasspreviewparameterpreview-rckniva.valueclassdp_dp_0.png new file mode 100644 index 0000000..ca7b505 Binary files /dev/null and b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.valueclasspreviewparameterpreview-rckniva.valueclassdp_dp_0.png differ diff --git a/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.valueclasspreviewparameterpreview-rckniva.valueclassdp_dp_1.png b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.valueclasspreviewparameterpreview-rckniva.valueclassdp_dp_1.png new file mode 100644 index 0000000..0086981 Binary files /dev/null and b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.valueclasspreviewparameterpreview-rckniva.valueclassdp_dp_1.png differ diff --git a/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.valueclasspreviewparameterpreview-rckniva.valueclassdp_dp_2.png b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.valueclasspreviewparameterpreview-rckniva.valueclassdp_dp_2.png new file mode 100644 index 0000000..6d986f8 Binary files /dev/null and b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.valueclasspreviewparameterpreview-rckniva.valueclassdp_dp_2.png differ diff --git a/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.wildcardlist-with-hypen-backsticks.wildcardlist_list%3c%3f%3e_0.png b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.wildcardlist-with-hypen-backsticks.wildcardlist_list%3c%3f%3e_0.png new file mode 100644 index 0000000..2d156a7 Binary files /dev/null and b/tests/src/test/snapshots/images/Paparazzi_Preview_Test_android.previewparameters.nonprimitiveclasscomposableskt.wildcardlist-with-hypen-backsticks.wildcardlist_list%3c%3f%3e_0.png differ