diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8fea77418..a9daefcb4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,6 @@ kim = "0.17.7" dropbox-differ = "0.0.2" google-android-material = "1.5.0" -gson = "2.10.1" junit = "4.13.2" ktor-serialization-kotlinx-xml = "2.3.0" kotlinx-serialization = "1.6.3" @@ -80,7 +79,6 @@ compose-ui-graphics-desktop = { module = "org.jetbrains.compose.ui:ui-graphics-d compose-ui-test-junit4-desktop = { module = "org.jetbrains.compose.ui:ui-test-junit4-desktop", version.ref = "composeMultiplatform" } dropbox-differ = { module = "com.dropbox.differ:differ", version.ref = "dropbox-differ" } google-android-material = { module = "com.google.android.material:material", version.ref = "google-android-material" } -gson = { module = "com.google.code.gson:gson", version.ref = "gson"} junit = { module = "junit:junit", version.ref = "junit" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } ktor-serialization-kotlinx-xml = { module = "io.ktor:ktor-serialization-kotlinx-xml", version.ref = "ktor-serialization-kotlinx-xml" } diff --git a/include-build/roborazzi-core/build.gradle b/include-build/roborazzi-core/build.gradle index f3daffe49..d6fe81760 100644 --- a/include-build/roborazzi-core/build.gradle +++ b/include-build/roborazzi-core/build.gradle @@ -1,6 +1,7 @@ plugins { id "org.jetbrains.kotlin.multiplatform" id "com.android.library" + id("org.jetbrains.kotlin.plugin.serialization") version libs.versions.kotlin } if (System.getenv("INTEGRATION_TEST") != "true") { pluginManager.apply("com.vanniktech.maven.publish") @@ -55,7 +56,7 @@ kotlin { commonJvmMain { dependencies { api libs.dropbox.differ - compileOnly libs.gson + compileOnly libs.kotlinx.serialization.json implementation libs.junit } } @@ -68,7 +69,7 @@ kotlin { } jvmMain { dependencies { - implementation libs.gson + implementation libs.kotlinx.serialization.json } } jvmTest { @@ -82,7 +83,7 @@ kotlin { compileOnly libs.androidx.compose.ui.test.junit4 api libs.androidx.test.espresso.core implementation libs.androidx.core.ktx - implementation libs.gson + implementation libs.kotlinx.serialization.json } } } diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/CaptureResult.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/CaptureResult.kt index cd897b0d6..cb69285c6 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/CaptureResult.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/CaptureResult.kt @@ -1,19 +1,30 @@ package com.github.takahirom.roborazzi -import com.github.takahirom.roborazzi.CaptureResults.Companion.gson -import com.google.gson.annotations.JsonAdapter -import com.google.gson.annotations.SerializedName +import com.github.takahirom.roborazzi.CaptureResults.Companion.json +import kotlinx.serialization.Contextual +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import java.io.File import java.io.FileReader -@JsonAdapter(CaptureResult.JsonAdapter::class) +@Serializable(with = CaptureResult.CaptureResultSerializer::class) sealed interface CaptureResult { val type: String val timestampNs: Long val compareFile: File? val actualFile: File? val goldenFile: File? - val contextData: Map + val contextData: Map val reportFile: File get() = when (val result = this) { @@ -23,15 +34,15 @@ sealed interface CaptureResult { is Unchanged -> result.goldenFile } + @Serializable data class Recorded( - @SerializedName("golden_file_path") - override val goldenFile: File, - @SerializedName("timestamp") + @SerialName("golden_file_path") + override val goldenFile:@Contextual File, + @SerialName("timestamp") override val timestampNs: Long, - @SerializedName("context_data") - override val contextData: Map + @SerialName("context_data") + override val contextData: Map ) : CaptureResult { - override val type = "recorded" override val actualFile: File? get() = null @@ -39,43 +50,46 @@ sealed interface CaptureResult { get() = null } + @Serializable data class Added( - @SerializedName("compare_file_path") - override val compareFile: File, - @SerializedName("actual_file_path") - override val actualFile: File, - @SerializedName("golden_file_path") - override val goldenFile: File, - @SerializedName("timestamp") + @SerialName("compare_file_path") + override val compareFile:@Contextual File, + @SerialName("actual_file_path") + override val actualFile:@Contextual File, + @SerialName("golden_file_path") + override val goldenFile:@Contextual File, + @SerialName("timestamp") override val timestampNs: Long, - @SerializedName("context_data") - override val contextData: Map + @SerialName("context_data") + override val contextData: Map ) : CaptureResult { override val type = "added" } + @Serializable data class Changed( - @SerializedName("compare_file_path") - override val compareFile: File, - @SerializedName("golden_file_path") - override val goldenFile: File, - @SerializedName("actual_file_path") - override val actualFile: File, - @SerializedName("timestamp") + @SerialName("compare_file_path") + override val compareFile:@Contextual File, + @SerialName("golden_file_path") + override val goldenFile:@Contextual File, + @SerialName("actual_file_path") + override val actualFile:@Contextual File, + @SerialName("timestamp") override val timestampNs: Long, - @SerializedName("context_data") - override val contextData: Map + @SerialName("context_data") + override val contextData: Map ) : CaptureResult { override val type = "changed" } + @Serializable data class Unchanged( - @SerializedName("golden_file_path") - override val goldenFile: File, - @SerializedName("timestamp") + @SerialName("golden_file_path") + override val goldenFile:@Contextual File, + @SerialName("timestamp") override val timestampNs: Long, - @SerializedName("context_data") - override val contextData: Map + @SerialName("context_data") + override val contextData: Map ) : CaptureResult { override val type = "unchanged" override val actualFile: File? @@ -86,38 +100,32 @@ sealed interface CaptureResult { companion object { fun fromJsonFile(filePath: String): CaptureResult { - return gson.fromJson(FileReader(filePath), CaptureResult::class.java) + val jsonElement = json.parseToJsonElement(FileReader(filePath).readText()) + return json.decodeFromJsonElement(jsonElement) } } - object JsonAdapter : com.google.gson.JsonSerializer, - com.google.gson.JsonDeserializer { - override fun serialize( - src: CaptureResult, - typeOfSrc: java.lang.reflect.Type, - context: com.google.gson.JsonSerializationContext - ): com.google.gson.JsonElement { - val jsonElement = when (src) { - is Recorded -> context.serialize(src, Recorded::class.java) - is Changed -> context.serialize(src, Changed::class.java) - is Unchanged -> context.serialize(src, Unchanged::class.java) - is Added -> context.serialize(src, Added::class.java) + object CaptureResultSerializer : KSerializer { + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("CaptureResult", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: CaptureResult) = + when (value) { + is Recorded -> encoder.encodeSerializableValue(Recorded.serializer(), value) + is Changed -> encoder.encodeSerializableValue(Changed.serializer(), value) + is Unchanged -> encoder.encodeSerializableValue(Unchanged.serializer(), value) + is Added -> encoder.encodeSerializableValue(Added.serializer(), value) } - return jsonElement - } - override fun deserialize( - json: com.google.gson.JsonElement, - typeOfT: java.lang.reflect.Type, - context: com.google.gson.JsonDeserializationContext - ): CaptureResult? { - val type = requireNotNull(json.asJsonObject.get("type")?.asString) + override fun deserialize(decoder: Decoder): CaptureResult { + require(decoder is JsonDecoder) + val type = decoder.decodeJsonElement().jsonObject["type"]!!.jsonPrimitive.content return when (type) { - "recorded" -> context.deserialize(json, Recorded::class.java) - "changed" -> context.deserialize(json, Changed::class.java) - "unchanged" -> context.deserialize(json, Unchanged::class.java) - "added" -> context.deserialize(json, Added::class.java) - else -> throw IllegalArgumentException("Unknown type $type") + "recorded" -> decoder.decodeSerializableValue(Recorded.serializer()) + "changed" -> decoder.decodeSerializableValue(Changed.serializer()) + "unchanged" -> decoder.decodeSerializableValue(Unchanged.serializer()) + "added" -> decoder.decodeSerializableValue(Added.serializer()) + else -> throw IllegalArgumentException("Unknown type $type") } } } diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/CaptureResults.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/CaptureResults.kt index 9a16719bc..0d7a82da2 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/CaptureResults.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/CaptureResults.kt @@ -1,31 +1,41 @@ package com.github.takahirom.roborazzi -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonDeserializer -import com.google.gson.JsonElement -import com.google.gson.JsonNull -import com.google.gson.JsonObject -import com.google.gson.JsonParseException -import com.google.gson.JsonParser -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSerializationContext -import com.google.gson.JsonSerializer -import com.google.gson.annotations.SerializedName +import kotlinx.serialization.ContextualSerializer +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encodeToString +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.booleanOrNull +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.double +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.int +import kotlinx.serialization.json.intOrNull +import kotlinx.serialization.json.long +import kotlinx.serialization.json.longOrNull +import kotlinx.serialization.modules.SerializersModule import java.io.File -import java.io.FileReader -import java.lang.reflect.Type +@Serializable data class CaptureResults( - @SerializedName("summary") + @SerialName("summary") val resultSummary: ResultSummary, - @SerializedName("results") + @SerialName("results") val captureResults: List ) { fun toJson(): String { - return gson.toJson(this) + return json.encodeToString(this) } class Tab( @@ -150,36 +160,38 @@ data class CaptureResults( } companion object { - val gson: Gson = GsonBuilder() - .registerTypeAdapter(File::class.java, object : JsonSerializer, JsonDeserializer { - override fun serialize( - src: File?, - typeOfSrc: Type?, - context: JsonSerializationContext? - ): JsonElement { - val absolutePath = src?.absolutePath ?: return JsonNull.INSTANCE - return JsonPrimitive(absolutePath) - } + val json = Json { + isLenient = true + encodeDefaults = true + ignoreUnknownKeys = true + classDiscriminator = "#class" + serializersModule = SerializersModule { + contextual(File::class, + object : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("FileSerializer", PrimitiveKind.STRING) - override fun deserialize( - json: JsonElement?, - typeOfT: Type?, - context: JsonDeserializationContext? - ): File { - val path = json?.asString ?: throw JsonParseException("File path is null") - return File(path) - } - }) - .create() + override fun serialize(encoder: Encoder, value: File) { + encoder.encodeString(value.absolutePath) + } + + override fun deserialize(decoder: Decoder): File { + val path = decoder.decodeString() + return File(path) + } + } + ) + contextual(Any::class, AnySerializer) + } + } fun fromJsonFile(inputPath: String): CaptureResults { - val jsonObject = JsonParser.parseString(FileReader(inputPath).readText()).asJsonObject - return fromJson(jsonObject) + val jsonElement = json.parseToJsonElement(File(inputPath).readText()) + return json.decodeFromJsonElement(jsonElement) } - fun fromJson(jsonObject: JsonObject): CaptureResults { - // Auto convert using Gson - return gson.fromJson(jsonObject, CaptureResults::class.java) + fun fromJson(jsonString: JsonObject): CaptureResults { + return json.decodeFromJsonElement(jsonString) } fun from(results: List): CaptureResults { @@ -196,3 +208,34 @@ data class CaptureResults( } } } + +object AnySerializer : KSerializer { + @OptIn(ExperimentalSerializationApi::class) + override val descriptor: SerialDescriptor + get() = ContextualSerializer(Any::class, null, emptyArray()).descriptor + + private val delegateSerializer = JsonPrimitive.serializer() + + override fun serialize(encoder: Encoder, value: Any) { + when (value) { + is String -> encoder.encodeString(value) + is Int -> encoder.encodeInt(value) + is Long -> encoder.encodeLong(value) + is Double -> encoder.encodeDouble(value.toDouble()) + is Boolean -> encoder.encodeBoolean(value) + else -> throw IllegalArgumentException("Unknown type: ${value::class.qualifiedName}") + } + } + + override fun deserialize(decoder: Decoder): Any { + val input = decoder.decodeSerializableValue(delegateSerializer) + return when { + input.isString -> input.content + input.booleanOrNull != null -> input.boolean + input.intOrNull != null -> input.int + input.longOrNull != null -> input.long + input.doubleOrNull != null -> input.double + else -> throw IllegalArgumentException("Unknown type: ${input::class.qualifiedName}") + } + } +} diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ResultSummary.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ResultSummary.kt index 0638414c5..17dde8ce0 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ResultSummary.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/ResultSummary.kt @@ -1,5 +1,8 @@ package com.github.takahirom.roborazzi +import kotlinx.serialization.Serializable + +@Serializable data class ResultSummary( val total: Int, val recorded: Int, diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt index c731f7846..1458cd038 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt @@ -2,7 +2,8 @@ package com.github.takahirom.roborazzi import com.dropbox.differ.ImageComparator import com.dropbox.differ.SimpleImageComparator -import com.github.takahirom.roborazzi.CaptureResults.Companion.gson +import com.github.takahirom.roborazzi.CaptureResults.Companion.json +import kotlinx.serialization.json.encodeToJsonElement import java.io.File import java.io.FileWriter @@ -172,7 +173,7 @@ data class RoborazziOptions( val reportFileName = getReportFileName(absolutePath, captureResult.timestampNs, nameWithoutExtension) - val jsonResult = gson.toJson(captureResult) + val jsonResult = json.encodeToJsonElement(captureResult) FileWriter(reportFileName).use { it.write(jsonResult.toString()) } debugLog { "JsonResult file($reportFileName) has been written" } } diff --git a/include-build/roborazzi-core/src/jvmTest/kotlin/io/github/takahirom/roborazzi/CaptureResultTest.kt b/include-build/roborazzi-core/src/jvmTest/kotlin/io/github/takahirom/roborazzi/CaptureResultTest.kt index 1978faa3e..3dad5918a 100644 --- a/include-build/roborazzi-core/src/jvmTest/kotlin/io/github/takahirom/roborazzi/CaptureResultTest.kt +++ b/include-build/roborazzi-core/src/jvmTest/kotlin/io/github/takahirom/roborazzi/CaptureResultTest.kt @@ -2,9 +2,20 @@ package io.github.takahirom.roborazzi import com.github.takahirom.roborazzi.CaptureResult import com.github.takahirom.roborazzi.CaptureResults -import com.github.takahirom.roborazzi.CaptureResults.Companion.gson +import com.github.takahirom.roborazzi.CaptureResults.Companion.json import com.github.takahirom.roborazzi.ResultSummary -import com.google.gson.JsonParser +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.double +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.int +import kotlinx.serialization.json.intOrNull +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.long +import kotlinx.serialization.json.longOrNull import org.junit.Assert.assertEquals import org.junit.Test import java.io.File @@ -25,7 +36,10 @@ class CaptureResultTest { actualFile = File("/actual_file"), goldenFile = File("/golden_file"), timestampNs = 123456789, - contextData = mapOf("key" to 2), + contextData = mapOf( + "key" to 2, + "keyDouble" to 2.5, + ), ), CaptureResult.Changed( compareFile = File("/compare_file"), @@ -43,59 +57,61 @@ class CaptureResultTest { val expectedReportResults = CaptureResults(expectedSummary, expectedCaptureResults) - val actualJson = gson.toJsonTree(expectedReportResults).asJsonObject - val actualJsonSummary = actualJson.get("summary").asJsonObject - val actualJsonResults = actualJson.get("results").asJsonArray + val actualJson = json.encodeToJsonElement(expectedReportResults) as JsonObject + val actualJsonSummary = actualJson["summary"]!!.jsonObject + val actualJsonResults = actualJson["results"]!!.jsonArray // Test summary - assertEquals(expectedSummary.total, actualJsonSummary.get("total").asInt) - assertEquals(expectedSummary.recorded, actualJsonSummary.get("recorded").asInt) - assertEquals(expectedSummary.added, actualJsonSummary.get("added").asInt) - assertEquals(expectedSummary.changed, actualJsonSummary.get("changed").asInt) - assertEquals(expectedSummary.unchanged, actualJsonSummary.get("unchanged").asInt) + assertEquals(expectedSummary.total, actualJsonSummary["total"]!!.jsonPrimitive.int) + assertEquals(expectedSummary.recorded, actualJsonSummary["recorded"]!!.jsonPrimitive.int) + assertEquals(expectedSummary.added, actualJsonSummary["added"]!!.jsonPrimitive.int) + assertEquals(expectedSummary.changed, actualJsonSummary["changed"]!!.jsonPrimitive.int) + assertEquals(expectedSummary.unchanged, actualJsonSummary["unchanged"]!!.jsonPrimitive.int) // Test capture results - assertEquals(expectedCaptureResults.size, actualJsonResults.size()) - - for (i in 0 until actualJsonResults.size()) { - val actualJsonResult = actualJsonResults.get(i).asJsonObject + assertEquals(expectedCaptureResults.size, actualJsonResults.size) + for (i in 0 until actualJsonResults.size) { + val actualJsonResult = actualJsonResults[i].jsonObject val expectedCaptureResult = expectedCaptureResults[i] - assertEquals( expectedCaptureResult.type, - actualJsonResult.get("type")?.asString + actualJsonResult["type"]?.jsonPrimitive?.content ) assertEquals( expectedCaptureResult.compareFile?.absolutePath, - actualJsonResult.get("compare_file_path")?.asString + actualJsonResult["compare_file_path"]?.jsonPrimitive?.content ) assertEquals( expectedCaptureResult.goldenFile?.absolutePath, - actualJsonResult.get("golden_file_path")?.asString + actualJsonResult["golden_file_path"]?.jsonPrimitive?.content ) assertEquals( expectedCaptureResult.actualFile?.absolutePath, - actualJsonResult.get("actual_file_path")?.asString + actualJsonResult["actual_file_path"]?.jsonPrimitive?.content + ) + assertEquals( + expectedCaptureResult.timestampNs, + actualJsonResult["timestamp"]?.jsonPrimitive?.long ) - assertEquals(expectedCaptureResult.timestampNs, actualJsonResult.get("timestamp").asLong) assertEquals( expectedCaptureResult.contextData.entries.map { it.key to it.value }, - (actualJsonResult.get("context_data").asJsonObject.entrySet() - .associate { + (actualJsonResult["context_data"]?.jsonObject?.entries + ?.associate { it.key to when (expectedCaptureResult.contextData[it.key]) { - is Number -> if (it.value.asLong > Int.MAX_VALUE) { - it.value.asLong - } else { - it.value.asInt + is Number -> when { + it.value.jsonPrimitive.intOrNull != null -> it.value.jsonPrimitive.int + it.value.jsonPrimitive.longOrNull != null -> it.value.jsonPrimitive.long + it.value.jsonPrimitive.doubleOrNull != null -> it.value.jsonPrimitive.double + else -> error("Unsupported type") } - is String -> it.value.asString - is Boolean -> it.value.asBoolean + is String -> it.value.jsonPrimitive.content + is Boolean -> it.value.jsonPrimitive.boolean else -> error("Unsupported type") } } - .entries as Set> + ?.entries as Set> ).map { it.key to it.value }) } } @@ -127,7 +143,8 @@ class CaptureResultTest { "golden_file_path": "golden_file", "timestamp": 123456789, "context_data": { - "key": 2 + "key": 2, + "keyDouble": 2.5 } }, { @@ -151,7 +168,7 @@ class CaptureResultTest { ] } """.trimIndent() - val actualJsonObject = JsonParser.parseString(jsonString).asJsonObject + val actualJsonObject = json.parseToJsonElement(jsonString).jsonObject val actualCaptureResults = CaptureResults.fromJson(actualJsonObject) val actualSummary = actualCaptureResults.resultSummary val actualCaptureResultList = actualCaptureResults.captureResults @@ -175,15 +192,15 @@ class CaptureResultTest { assertEquals(File("compare_file"), actualAddedResult.compareFile) assertEquals(File("actual_file"), actualAddedResult.actualFile) assertEquals(123456789, actualAddedResult.timestampNs) - assertEquals(2, (actualAddedResult.contextData["key"] as Double).toInt()) + assertEquals(2, actualAddedResult.contextData["key"]) + assertEquals(2.5, actualAddedResult.contextData["keyDouble"]) val actualChangedResult = actualCaptureResultList[2] as CaptureResult.Changed assertEquals(File("compare_file"), actualChangedResult.compareFile) assertEquals(File("actual_file"), actualChangedResult.actualFile) assertEquals(File("golden_file"), actualChangedResult.goldenFile) assertEquals(123456789, actualChangedResult.timestampNs) - // Currently long value is deserialized as double so we can't handle long value correctly -// assertEquals(9223372036854775707, (actualChangedResult.contextData["key"] as Double).toLong()) + assertEquals(9223372036854775707, actualChangedResult.contextData["key"]) val actualUnchangedResult = actualCaptureResultList[3] as CaptureResult.Unchanged assertEquals(File("golden_file"), actualUnchangedResult.goldenFile) diff --git a/include-build/roborazzi-gradle-plugin/build.gradle b/include-build/roborazzi-gradle-plugin/build.gradle index 3105dbe68..68bd842aa 100644 --- a/include-build/roborazzi-gradle-plugin/build.gradle +++ b/include-build/roborazzi-gradle-plugin/build.gradle @@ -24,7 +24,7 @@ dependencies { // We don't use junit for plugin exclude group: 'junit', module: 'junit' } - implementation libs.gson + implementation libs.kotlinx.serialization.json integrationTestDepImplementation libs.android.tools.build.gradle integrationTestDepImplementation libs.kotlin.gradle.plugin integrationTestDepImplementation libs.compose.gradle.plugin diff --git a/roborazzi-compose-desktop/build.gradle b/roborazzi-compose-desktop/build.gradle index 8aad831c1..a640cdfb4 100644 --- a/roborazzi-compose-desktop/build.gradle +++ b/roborazzi-compose-desktop/build.gradle @@ -21,7 +21,7 @@ kotlin { dependencies { // Please see settings.gradle api "io.github.takahirom.roborazzi:roborazzi-core:$VERSION_NAME" - implementation libs.gson + implementation(compose.runtime) implementation(compose.desktop.currentOs) diff --git a/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/ContextDataTest.kt b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/ContextDataTest.kt index 51bdab321..d626c488c 100644 --- a/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/ContextDataTest.kt +++ b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/boxed/ContextDataTest.kt @@ -51,13 +51,19 @@ class ContextDataTest { val testKey1 = "test_key" val testValue1 = "test_value" val testKey2 = "test_key2" + val testKey3 = "test_key3_double" + val testKey4 = "test_key3_long" val testValue2 = 10 + val testValue3 = 5.5 + val testValue4 = Long.MAX_VALUE - 100 onView(ViewMatchers.isRoot()) .captureRoboImage( roborazziOptions = provideRoborazziContext().options.copy( contextData = mapOf( testKey1 to testValue1, - testKey2 to testValue2 + testKey2 to testValue2, + testKey3 to testValue3, + testKey4 to testValue4, ) ) ) @@ -73,16 +79,23 @@ class ContextDataTest { val chunks = PngImageParser.readChunks(it, listOf(PngChunkType.TEXT)) chunks.verifyKeyValueExistsInImage(testKey1, testValue1) chunks.verifyKeyValueExistsInImage(testKey2, testValue2.toString()) + chunks.verifyKeyValueExistsInImage(testKey3, testValue3.toString()) + chunks.verifyKeyValueExistsInImage(testKey4, testValue4.toString()) } } File("build/test-results/roborazzi/results") .listFiles()!! + .sortedBy { it.name } + .reversed() .first { it.name.contains(methodSignature) && it.name.endsWith(".json") } .let { + println(it.readText()) CaptureResult.fromJsonFile(it.path) .let { result -> result.verifyKeyValueExistsInJson(testKey1, testValue1) - result.verifyKeyValueExistsInJson(testKey2, testValue2.toString()) + result.verifyKeyValueExistsInJson(testKey2, testValue2) + result.verifyKeyValueExistsInJson(testKey3, testValue3) + result.verifyKeyValueExistsInJson(testKey4, testValue4) } } } @@ -146,16 +159,9 @@ class ContextDataTest { private fun CaptureResult.verifyKeyValueExistsInJson( key: String, - value: String + value: Any ) { val any = contextData[key] - if (any is Double) { - // gson always parse number as double - assert(any.toInt() == value.toInt()) { - "Expected $value but got $any" - } - return - } assert(any == value) { "Expected $value but got $any" }