Skip to content

FLineSerializationSmokeTest.kt

src/jvmMain/kotlin/afield/examples/graphics/FLineSerializationSmokeTest.kt · 157 lines

package afield.examples.graphics

import afield.geometry.path.FLine
import afield.geometry.path.set2DPlane
import afield.math.linalg.Vec2
import afield.math.linalg.Vec3
import afield.math.linalg.Vec4
import afield.graphics.render.color
import afield.graphics.render.filled
import afield.graphics.render.fline
import afield.graphics.render.onClick
import afield.graphics.render.pointSize
import afield.graphics.render.stroked
import afield.graphics.render.thickness
import afield.math.linalg.vec
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlin.math.abs

object FLineSerializationSmokeTest

FIELD_TEST id: graphics.fline-serialization.smoke runner: jvm-main entrypoint: afield.examples.graphics.FLineSerializationSmokeTest tags: [smoke, graphics, fline, serialization, json] timeoutSec: 20 expect: exitCode: 0 stdoutContains: - "FLine serialization smoke tests passed"

object FLineSerializationSmokeTest {

    @Serializable
    private data class Envelope(
        val name: String,
        val line: FLine,
        val lines: List<FLine>
    )

fun main(args: Array<String>)

    @JvmStatic
    fun main(args: Array<String>) {
        check(runTests()) { "FLine serialization smoke tests failed" }
    }

fun runTests(): Boolean

body — 109 lines, from line 48
    fun runTests(): Boolean {
        return try {
            testRoundTripsEmbeddedFLine()
            println("FLine serialization smoke tests passed")
            true
        } catch (t: Throwable) {
            t.printStackTrace()
            false
        }
    }

    private fun testRoundTripsEmbeddedFLine() {
        val line = fline {
            moveTo(1f, 2f, 3f)
            here.properties["tag"] = "start"
            here.properties["weight"] = 7
            lineTo(4f, 5f, 6f)
            here.properties["uv"] = vec(0.25f, 0.5f)
            cubicTo(
                7f, 8f, 9f,
                10f, 11f, 12f,
                13f, 14f, 15f
            )
            here.properties["normal"] = vec(0.1f, 0.2f, 0.3f)
            close()

            properties["title"] = "demo"
            properties["count"] = 42
            properties["ratio"] = 0.75f
            properties["offset"] = vec(2f, 3f)
            properties["anchor"] = vec(4f, 5f, 6f)
            properties["rgba"] = vec(0.2f, 0.3f, 0.4f, 0.5f)

            color = vec(0.9f, 0.8f, 0.7f, 1f)
            thickness = 2.5
            pointSize = 6.0
            filled = true
            stroked = false
            onClick = { _ -> }
            set2DPlane(vec(10f, 20f, 30f), vec(1f, 0f, 0f), vec(0f, 1f, 0f))
        }

        val json = Json.encodeToString(
            Envelope(
                name = "wrapper",
                line = line,
                lines = listOf(line.copy())
            )
        )
        val decoded = Json.decodeFromString<Envelope>(json)
        val restored = decoded.line

        require(decoded.name == "wrapper") { "Envelope data did not round-trip" }
        require(decoded.lines.size == 1) { "Expected list field to round-trip" }
        require(restored.nodes.size == 4) { "Expected 4 nodes, got ${restored.nodes.size}" }
        require(restored.nodes[0] is FLine.MoveTo) { "Expected first node to be MoveTo" }
        require(restored.nodes[1] is FLine.LineTo) { "Expected second node to be LineTo" }
        require(restored.nodes[2] is FLine.CubicTo) { "Expected third node to be CubicTo" }
        require(restored.nodes[3] is FLine.Close) { "Expected fourth node to be Close" }

        require(restored.properties["title"] == "demo") { "String property did not round-trip" }
        require(restored.properties["count"] == 42) { "Int property did not round-trip" }
        require(restored.properties["ratio"] == 0.75f) { "Float property did not round-trip" }

        requireVec2(restored.properties["offset"], 2f, 3f, "offset")
        requireVec3(restored.properties["anchor"], 4f, 5f, 6f, "anchor")
        requireVec4(restored.properties["rgba"], 0.2f, 0.3f, 0.4f, 0.5f, "rgba")

        require(restored.filled) { "Boolean line property 'filled' did not round-trip" }
        require(!restored.stroked) { "Boolean line property 'stroked' did not round-trip" }
        require(abs((restored.thickness ?: 0.0) - 2.5) < 1e-9) { "Thickness did not round-trip" }
        require(abs(restored.pointSize - 6.0) < 1e-9) { "Point size did not round-trip" }
        requireColor(restored.color, 0.9f, 0.8f, 0.7f, 1f, "line color")

        require(restored.properties["onClick"] == null) { "Runtime callback property should not serialize" }
        require(restored.properties["_fline_plane2d"] == null) { "Internal plane property should not serialize" }

        require(restored.nodes[0].properties["tag"] == "start") { "Node string property did not round-trip" }
        require(restored.nodes[0].properties["weight"] == 7) { "Node numeric property did not round-trip" }

        requireVec2(restored.nodes[1].properties["uv"], 0.25f, 0.5f, "node uv")
        requireVec3(restored.nodes[2].properties["normal"], 0.1f, 0.2f, 0.3f, "node normal")
    }

    private fun requireColor(actual: Any?, r: Float, g: Float, b: Float, a: Float, label: String) {
        requireVec4(actual, r, g, b, a, label)
    }

    private fun requireVec2(actual: Any?, x: Float, y: Float, label: String) {
        val value = actual as? Vec2 ?: error("Expected Vec2 for $label, got ${actual?.let { it::class.qualifiedName }}")
        require(abs(value.x - x) < 1e-6f && abs(value.y - y) < 1e-6f) {
            "Unexpected $label: (${value.x}, ${value.y})"
        }
    }

    private fun requireVec3(actual: Any?, x: Float, y: Float, z: Float, label: String) {
        val value = actual as? Vec3 ?: error("Expected Vec3 for $label, got ${actual?.let { it::class.qualifiedName }}")
        require(abs(value.x - x) < 1e-6f && abs(value.y - y) < 1e-6f && abs(value.z - z) < 1e-6f) {
            "Unexpected $label: (${value.x}, ${value.y}, ${value.z})"
        }
    }

    private fun requireVec4(actual: Any?, x: Float, y: Float, z: Float, w: Float, label: String) {
        val value = actual as? Vec4 ?: error("Expected Vec4 for $label, got ${actual?.let { it::class.qualifiedName }}")
        require(abs(value.x - x) < 1e-6f && abs(value.y - y) < 1e-6f && abs(value.z - z) < 1e-6f && abs(value.w - w) < 1e-6f) {
            "Unexpected $label: (${value.x}, ${value.y}, ${value.z}, ${value.w})"
        }
    }
}