v0.5.0 — Phase β-4 Media + Advanced Card-Types
Alle 7 Card-Types werden gerendert und können erstellt werden. image-occlusion mit Touch-Drag-Mask-Editor (kein PencilKit — Server- Schema erlaubt nur Rechtecke), audio-front mit AVAudioPlayer und File-Picker. - MediaUploadResponse-DTO, MaskRegion-Codable mit 0..1-Coordinates - MaskRegions.parse/encode (1:1-Port aus cards-domain, Sortierung nach ID lexikographisch) - CardFieldsBuilder.imageOcclusion mit stringified-JSON-mask_regions + audioFront - CardsAPI.uploadMedia (Multipart, 25 MiB) + fetchMedia (streamed) - MediaCache actor mit LRU 200 MB (contentModificationDate-Eviction) - mediaCache Environment-Key - RemoteImage + AudioPlayerButton SwiftUI-Views - CardRenderer: imageOcclusion (Mask-Overlay über RemoteImage) + audioFront (AudioPlayerButton + back-Text auf Flip) - MaskEditorView: Touch-Drag-Rechteck, Label-Edit, Delete - CardEditorView erweitert: PhotosPicker für Image, fileImporter für Audio, Magic-Byte-MIME-Detection - 6 neue Tests für MaskRegions (30 Total grün) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cf1160b270
commit
80eb3708b4
12 changed files with 923 additions and 44 deletions
79
Tests/UnitTests/MaskRegionsTests.swift
Normal file
79
Tests/UnitTests/MaskRegionsTests.swift
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import Foundation
|
||||
import Testing
|
||||
@testable import CardsNative
|
||||
|
||||
@Suite("MaskRegions")
|
||||
struct MaskRegionsTests {
|
||||
@Test("Parsed Liste sortiert nach ID lexikographisch")
|
||||
func parseSortsByIdLexically() {
|
||||
let json = """
|
||||
[
|
||||
{"id":"m003","x":0.1,"y":0.1,"w":0.2,"h":0.2,"label":"C"},
|
||||
{"id":"m001","x":0,"y":0,"w":0.1,"h":0.1,"label":"A"},
|
||||
{"id":"m002","x":0.5,"y":0.5,"w":0.3,"h":0.3}
|
||||
]
|
||||
"""
|
||||
let regions = MaskRegions.parse(json)
|
||||
#expect(regions.count == 3)
|
||||
#expect(regions[0].id == "m001")
|
||||
#expect(regions[1].id == "m002")
|
||||
#expect(regions[2].id == "m003")
|
||||
#expect(regions[2].label == "C")
|
||||
#expect(regions[1].label == nil)
|
||||
}
|
||||
|
||||
@Test("Bei Parse-Fehler → leere Liste")
|
||||
func parseInvalidReturnsEmpty() {
|
||||
#expect(MaskRegions.parse("[}").isEmpty)
|
||||
#expect(MaskRegions.parse("{}").isEmpty)
|
||||
#expect(MaskRegions.parse("").isEmpty)
|
||||
}
|
||||
|
||||
@Test("region(forSubIndex:) mappt aufsteigend")
|
||||
func subIndexLookup() {
|
||||
let json = """
|
||||
[{"id":"b","x":0,"y":0,"w":0.1,"h":0.1},
|
||||
{"id":"a","x":0,"y":0,"w":0.2,"h":0.2}]
|
||||
"""
|
||||
#expect(MaskRegions.region(for: json, subIndex: 0)?.id == "a")
|
||||
#expect(MaskRegions.region(for: json, subIndex: 1)?.id == "b")
|
||||
#expect(MaskRegions.region(for: json, subIndex: 2) == nil)
|
||||
}
|
||||
|
||||
@Test("Encode-Roundtrip")
|
||||
func encodeRoundtrip() {
|
||||
let original = [
|
||||
MaskRegion(id: "m1", x: 0.1, y: 0.2, w: 0.3, h: 0.4, label: "test"),
|
||||
MaskRegion(id: "m2", x: 0.5, y: 0.6, w: 0.2, h: 0.2, label: nil),
|
||||
]
|
||||
let encoded = MaskRegions.encode(original)
|
||||
let parsed = MaskRegions.parse(encoded)
|
||||
#expect(parsed.count == 2)
|
||||
#expect(parsed[0].id == "m1")
|
||||
#expect(parsed[0].label == "test")
|
||||
#expect(parsed[1].label == nil)
|
||||
}
|
||||
|
||||
@Test("CardFieldsBuilder.imageOcclusion produziert korrekte Felder")
|
||||
func builderImageOcclusion() {
|
||||
let regions = [MaskRegion(id: "m1", x: 0, y: 0, w: 0.5, h: 0.5, label: "x")]
|
||||
let fields = CardFieldsBuilder.imageOcclusion(
|
||||
imageRef: "media_123",
|
||||
regions: regions,
|
||||
note: "Hinweis"
|
||||
)
|
||||
#expect(fields["image_ref"] == "media_123")
|
||||
#expect(fields["note"] == "Hinweis")
|
||||
let reparsed = MaskRegions.parse(fields["mask_regions"] ?? "")
|
||||
#expect(reparsed.count == 1)
|
||||
#expect(reparsed[0].id == "m1")
|
||||
}
|
||||
|
||||
@Test("CardFieldsBuilder.audioFront produziert korrekte Felder")
|
||||
func builderAudioFront() {
|
||||
let fields = CardFieldsBuilder.audioFront(audioRef: "audio_456", back: "Antwort")
|
||||
#expect(fields["audio_ref"] == "audio_456")
|
||||
#expect(fields["back"] == "Antwort")
|
||||
#expect(fields.count == 2)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue