Introduce TankDefinition/TankCatalog loaded from Tanks.json, bundle the Drachengas USDZ tank models, add tank selection and preview views, and update the AR experience, editor and project views to use the catalog. Includes tests for catalog loading and model selection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
135 lines
5.2 KiB
Swift
135 lines
5.2 KiB
Swift
//
|
|
// TankCatalogTests.swift
|
|
// GasTankARTests
|
|
//
|
|
// Created by Dennis Nemec on 14.09.26.
|
|
//
|
|
|
|
import Testing
|
|
import Foundation
|
|
@testable import GasTankAR
|
|
|
|
/// Prüft das Laden/Decodieren des Tank-Katalogs aus JSON sowie die abgeleiteten
|
|
/// Abfragen (verfügbare Größen/Farben, Auflösung & Normalisierung der Auswahl).
|
|
struct TankCatalogTests {
|
|
|
|
// Kleiner, in sich konsistenter Beispielkatalog für Query-Tests.
|
|
private static let sampleJSON = """
|
|
{
|
|
"sizeDisplayNames": { "t1_2": "1,2 t", "t2_9": "2,9 t" },
|
|
"colorDisplayNames": { "white": "Weiß", "green": "Hellgrün", "olive": "Olivgrün" },
|
|
"tanks": [
|
|
{ "size": "t1_2", "color": "white", "placement": "aboveGround", "modelPath": "DG-2700_wh.usdz" },
|
|
{ "size": "t1_2", "color": "green", "placement": "aboveGround", "modelPath": "DG-2700_mi.usdz" },
|
|
{ "size": "t1_2", "color": "olive", "placement": "belowGround", "modelPath": "DG-2700_ol.usdz" },
|
|
{ "size": "t2_9", "color": "white", "placement": "aboveGround", "modelPath": "DG-6400_wh.usdz" },
|
|
{ "size": "t2_9", "color": "olive", "placement": "belowGround", "modelPath": "DG-6400_ol.usdz" }
|
|
]
|
|
}
|
|
"""
|
|
|
|
private static func sampleCatalog() throws -> TankCatalog {
|
|
let loader = DataTankCatalogLoader(data: Data(sampleJSON.utf8))
|
|
return TankCatalog(file: try loader.load())
|
|
}
|
|
|
|
// MARK: - Decoding
|
|
|
|
@Test func decodesAllFieldsFromJSON() throws {
|
|
let catalog = try Self.sampleCatalog()
|
|
let def = try #require(catalog.definitions.first)
|
|
|
|
#expect(def.size == .t1_2)
|
|
#expect(def.color == .white)
|
|
#expect(def.placement == .aboveGround)
|
|
#expect(def.modelPath == "DG-2700_wh.usdz")
|
|
#expect(def.modelResourceName == "DG-2700_wh")
|
|
#expect(def.modelFileExtension == "usdz")
|
|
}
|
|
|
|
@Test func decodesDisplayNameMappings() throws {
|
|
let catalog = try Self.sampleCatalog()
|
|
#expect(catalog.sizeDisplayNames["t1_2"] == "1,2 t")
|
|
#expect(catalog.colorDisplayNames["olive"] == "Olivgrün")
|
|
}
|
|
|
|
@Test func displayNameFallsBackToRawValueWhenMissing() {
|
|
// Katalog ohne Mappings → Anzeigename ist der rawValue.
|
|
let catalog = TankCatalog(definitions: [])
|
|
#expect(catalog.displayName(for: TankSize.t2_1) == "t2_1")
|
|
#expect(catalog.displayName(for: TankColor.green) == "green")
|
|
}
|
|
|
|
@Test func missingResourceThrows() {
|
|
let loader = BundleTankCatalogLoader(
|
|
bundle: .main,
|
|
resourceName: "Datei-die-nicht-existiert",
|
|
resourceExtension: "json"
|
|
)
|
|
#expect(throws: TankCatalogLoaderError.self) {
|
|
_ = try loader.load()
|
|
}
|
|
}
|
|
|
|
@Test func emptyCatalogWhenLoaderFails() {
|
|
let loader = BundleTankCatalogLoader(resourceName: "gibt-es-nicht")
|
|
let catalog = TankCatalog(loader: loader)
|
|
#expect(catalog.definitions.isEmpty)
|
|
}
|
|
|
|
// MARK: - Abgeleitete Abfragen
|
|
|
|
@Test func availableColorsDependOnPlacement() throws {
|
|
let catalog = try Self.sampleCatalog()
|
|
#expect(catalog.colors(for: .t1_2, placement: .aboveGround) == [.white, .green])
|
|
#expect(catalog.colors(for: .t1_2, placement: .belowGround) == [.olive])
|
|
}
|
|
|
|
@Test func availableSizesPerPlacement() throws {
|
|
let catalog = try Self.sampleCatalog()
|
|
#expect(catalog.sizes(for: .aboveGround) == [.t1_2, .t2_9])
|
|
#expect(catalog.sizes(for: .belowGround) == [.t1_2, .t2_9])
|
|
}
|
|
|
|
@Test func placementsPreserveOrder() throws {
|
|
let catalog = try Self.sampleCatalog()
|
|
#expect(catalog.placements == [.aboveGround, .belowGround])
|
|
}
|
|
|
|
@Test func displayNamesResolveFromCatalog() throws {
|
|
let catalog = try Self.sampleCatalog()
|
|
#expect(catalog.displayName(for: TankSize.t2_9) == "2,9 t")
|
|
#expect(catalog.displayName(for: TankColor.olive) == "Olivgrün")
|
|
}
|
|
|
|
// MARK: - Auflösung & Normalisierung
|
|
|
|
@Test func definitionMatchesExactSelection() throws {
|
|
let catalog = try Self.sampleCatalog()
|
|
let tank = Tank(size: .t2_9, color: .olive, placement: .belowGround)
|
|
#expect(catalog.definition(for: tank)?.modelPath == "DG-6400_ol.usdz")
|
|
}
|
|
|
|
@Test func normalizesInconsistentColorForPlacement() throws {
|
|
let catalog = try Self.sampleCatalog()
|
|
// Weiß gibt es unterirdisch nicht → sollte auf Olivgrün korrigiert werden.
|
|
let tank = Tank(size: .t2_9, color: .white, placement: .belowGround)
|
|
let normalized = catalog.normalized(tank)
|
|
#expect(normalized.color == .olive)
|
|
#expect(catalog.definition(for: normalized) != nil)
|
|
}
|
|
|
|
@Test func resolvedDefinitionHealsLegacySelection() throws {
|
|
let catalog = try Self.sampleCatalog()
|
|
// Altdaten: unterirdisch + weiß → muss trotzdem das Olivgrün-Modell liefern.
|
|
let legacy = Tank(size: .t2_9, color: .white, placement: .belowGround)
|
|
#expect(catalog.resolvedDefinition(for: legacy)?.modelPath == "DG-6400_ol.usdz")
|
|
}
|
|
|
|
@Test func validSelectionStaysUnchanged() throws {
|
|
let catalog = try Self.sampleCatalog()
|
|
let tank = Tank(size: .t1_2, color: .green, placement: .aboveGround)
|
|
#expect(catalog.normalized(tank) == tank)
|
|
}
|
|
}
|