import Foundation import Testing @testable import FlaroDesign /// Wo die schwebende Videokachel landet. @Suite("Kachel-Platzierung") struct MiniPlayerPlacementTests { /// Ein iPhone im Hochformat, Kachel 180 × 101. private let placement = MiniPlayerPlacement( bounds: CGSize(width: 390, height: 844), tile: CGSize(width: 180, height: 101), inset: 12 ) @Test("Die nähere Seite gewinnt") func picksTheNearestSide() { #expect(placement.nearestSide(toCenter: CGPoint(x: 50, y: 400)) == .leading) #expect(placement.nearestSide(toCenter: CGPoint(x: 340, y: 400)) == .trailing) } @Test("Die Höhe bleibt frei, nicht auf Ecken festgelegt") func keepsTheVerticalPosition() { // Der Kern des Umbaus: vier Ecken sind schnell gebaut, aber im Weg. let middle = placement.center(side: .trailing, verticalFraction: 0.5) let top = placement.center(side: .trailing, verticalFraction: 0) let bottom = placement.center(side: .trailing, verticalFraction: 1) #expect(top.y < middle.y) #expect(middle.y < bottom.y) #expect(top.x == middle.x && middle.x == bottom.x) } @Test("Anteil und Mittelpunkt rechnen ineinander um") func roundTripsTheFraction() { for fraction in [0.0, 0.25, 0.5, 0.9, 1.0] { let center = placement.center(side: .leading, verticalFraction: fraction) let back = placement.verticalFraction(forCenter: center) #expect(abs(back - fraction) < 0.0001) } } @Test("Der Anteil bleibt zwischen null und eins") func clampsTheFraction() { // Sonst wandert die Kachel beim Drehen aus dem Bild: der Anteil wird // gespeichert, und ein Wert über eins landete unterhalb des Randes. #expect(placement.verticalFraction(forCenter: CGPoint(x: 0, y: -9_000)) == 0) #expect(placement.verticalFraction(forCenter: CGPoint(x: 0, y: 9_000)) == 1) #expect(placement.center(side: .leading, verticalFraction: 5).y <= 844 - 12) } @Test("An jeder Seite bleibt der Sicherheitsabstand") func keepsTheInset() { for side in MiniPlayerPlacement.Side.allCases { for fraction in [0.0, 0.5, 1.0] { let center = placement.center(side: side, verticalFraction: fraction) #expect(center.x - 90 >= 12) #expect(center.x + 90 <= 390 - 12) #expect(center.y - 50.5 >= 12) #expect(center.y + 50.5 <= 844 - 12) } } } @Test("Beim Ziehen rutscht nichts über den Rand") func clampsWhileDragging() { // Ohne diese Grenze zieht man die Kachel hinter den Bildschirmrand, // und dort ist sie mit dem Finger nicht mehr einzuholen. let farOut = placement.clamped(center: CGPoint(x: -500, y: -500)) #expect(farOut.x == 102.0) #expect(farOut.y == 62.5) let alsoFarOut = placement.clamped(center: CGPoint(x: 9_000, y: 9_000)) #expect(alsoFarOut.x == 288.0) #expect(alsoFarOut.y == 781.5) } @Test("Ein Punkt in der Mitte bleibt, wo er ist") func leavesValidPointsAlone() { let inside = CGPoint(x: 195, y: 400) #expect(placement.clamped(center: inside) == inside) } @Test("Ein zu enger Bereich schiebt die Kachel nicht hinaus") func survivesTinyBounds() { // Kommt beim Drehen und in der Übergangsphase vor: der Bereich ist // kurzzeitig schmaler als die Kachel. Ein falsch herum geklammerter // Wert schöbe sie dann dauerhaft aus dem Bild. let narrow = MiniPlayerPlacement( bounds: CGSize(width: 100, height: 80), tile: CGSize(width: 180, height: 101), inset: 12 ) #expect(narrow.clamped(center: CGPoint(x: 0, y: 0)) == CGPoint(x: 102, y: 62.5)) #expect(narrow.center(side: .trailing, verticalFraction: 1) == CGPoint(x: 102, y: 62.5)) } }