Zum Inhalt

apple/PhysarumEngine/Tests/PhysarumEngineTests/PresetCodableTests.swift

import XCTest
@testable import PhysarumEngine

/// The native `Preset` Codable model must stay compatible with the web Zod
/// schema (the JSON is shared, exported by scripts/export-native-presets.ts).
final class PresetCodableTests: XCTestCase {
    func testDecodeMinimalPreset() throws {
        let preset = try TestFixtures.minimalPreset()
        XCTAssertEqual(preset.id, "t")
        XCTAssertEqual(preset.name, "Test")
        XCTAssertTrue(preset.builtIn)
        XCTAssertEqual(preset.params.species.count, 3)
        XCTAssertEqual(preset.params.species[0].moveSpeed, 1.5)
        XCTAssertEqual(preset.params.species[0].color, RGB(0.9, 0.6, 0.2))
        XCTAssertEqual(preset.params.interactionMatrix.count, 3)
        XCTAssertEqual(preset.params.simSpeed, 1)
    }

    func testSignalNameRawValues() throws {
        // The only kebab-cased raw value — a regression here breaks tag colours.
        XCTAssertEqual(try decodeSignal("neural-bleed"), .neuralBleed)
        XCTAssertEqual(try decodeSignal("biolink"), .biolink)
        XCTAssertEqual(try decodeSignal("pearl"), .pearl)
        XCTAssertEqual(try decodeSignal("voidwitch"), .voidwitch)
    }

    func testSpawnPatternAndBlendModeDecode() throws {
        for raw in ["random", "clusters", "center", "ring"] {
            XCTAssertNoThrow(try decode(SpawnPattern.self, raw), "spawnPattern \(raw)")
        }
        for raw in ["additive", "paint"] {
            XCTAssertNoThrow(try decode(BlendMode.self, raw), "blendMode \(raw)")
        }
    }

    /// Every committed built-in JSON must decode cleanly into the native model,
    /// with 3 species, a 3×3 matrix, a positive agent count and population share.
    /// This is the guard against web ⇄ native preset drift.
    func testAllCommittedPresetsDecode() throws {
        guard let dir = TestFixtures.committedPresetsDir() else {
            throw XCTSkip("committed presets dir not found (non-checkout layout)")
        }
        let urls = try FileManager.default
            .contentsOfDirectory(at: dir, includingPropertiesForKeys: nil)
            .filter { $0.pathExtension == "json" }
        XCTAssertEqual(urls.count, 7, "expected the 7 curated built-in presets")
        for url in urls {
            let name = url.lastPathComponent
            let preset = try JSONDecoder().decode(Preset.self, from: Data(contentsOf: url))
            XCTAssertEqual(preset.params.species.count, 3, "\(name): 3 species")
            XCTAssertEqual(preset.params.interactionMatrix.count, 3, "\(name): 3 matrix rows")
            for row in preset.params.interactionMatrix {
                XCTAssertEqual(row.count, 3, "\(name): matrix row is 3 wide")
            }
            XCTAssertGreaterThan(preset.params.agentCount, 0, "\(name): agentCount > 0")
            let shareSum = preset.params.species.reduce(Float(0)) { $0 + $1.populationShare }
            XCTAssertGreaterThan(shareSum, 0, "\(name): population share sum > 0")
        }
    }

    // MARK: helpers

    private func decodeSignal(_ raw: String) throws -> SignalName {
        try decode(SignalName.self, raw)
    }

    private func decode<T: Decodable>(_ type: T.Type, _ raw: String) throws -> T {
        // Decode a bare JSON string into the RawRepresentable enum.
        try JSONDecoder().decode(T.self, from: Data("\"\(raw)\"".utf8))
    }
}