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))
}
}