import Testing import Foundation import CoreGraphics import SwiftUI import OnyxNotch @testable import ActivityProvider @Suite("Warteschlange der Live Activities") struct ActivityQueueTests { private let now = Date(timeIntervalSince1970: 1_780_000_000) private func ticket(_ id: String, rank: Int, at offset: TimeInterval = 0, for duration: TimeInterval? = 2) -> ActivityTicket { ActivityTicket(id: id, rank: rank, raised: now.addingTimeInterval(offset), duration: duration) } @Test("Leer heißt: nichts zu zeigen") func emptyShowsNothing() { #expect(ActivityQueue().current(now: now) == nil) } /// Ohne diese Regel verschluckt ein beiläufiger Lautstärkeschritt die /// Meldung, dass der Akku fast leer ist. @Test("Rang schlägt Aktualität") func rankBeatsRecency() { var queue = ActivityQueue() queue.raise(ticket("battery", rank: 90)) queue.raise(ticket("volume", rank: 10, at: 1)) #expect(queue.current(now: now.addingTimeInterval(1))?.id == "battery") } @Test("Bei gleichem Rang gilt die jüngere") func sameRankPrefersNewest() { var queue = ActivityQueue() queue.raise(ticket("volume", rank: 10)) queue.raise(ticket("brightness", rank: 10, at: 1)) #expect(queue.current(now: now.addingTimeInterval(1))?.id == "brightness") } /// Wer die Lautstärke dreimal verstellt, will dreimal die volle Standzeit — /// nicht drei Einträge in einer Schlange. @Test("Dieselbe Kennung ersetzt, statt sich anzureihen") func raisingAgainReplaces() { var queue = ActivityQueue() queue.raise(ticket("volume", rank: 10)) queue.raise(ticket("volume", rank: 10, at: 1)) #expect(queue.all.count == 1) #expect(queue.current(now: now.addingTimeInterval(1))?.raised == now.addingTimeInterval(1)) } @Test("Abgelaufene zählen nicht mehr") func expiredIsGone() { var queue = ActivityQueue() queue.raise(ticket("volume", rank: 10, for: 2)) #expect(queue.current(now: now.addingTimeInterval(1)) != nil) #expect(queue.current(now: now.addingTimeInterval(2)) == nil) } /// Genau dafür gibt es dauerhafte: etwas, das steht, wenn sonst nichts /// ansteht — das Wetter etwa. @Test("Eine dauerhafte läuft nie ab") func persistentNeverExpires() { var queue = ActivityQueue() queue.raise(ticket("weather", rank: 1, for: nil)) #expect(queue.current(now: now.addingTimeInterval(86_400))?.id == "weather") } @Test("Nach dem Ablauf der kurzen kommt die dauerhafte zurück") func persistentReturns() { var queue = ActivityQueue() queue.raise(ticket("weather", rank: 1, for: nil)) queue.raise(ticket("volume", rank: 10, for: 2)) #expect(queue.current(now: now)?.id == "volume") #expect(queue.current(now: now.addingTimeInterval(3))?.id == "weather") } @Test("Zurückziehen wirkt sofort") func withdrawWorks() { var queue = ActivityQueue() queue.raise(ticket("media", rank: 20, for: nil)) queue.withdraw(id: "media") #expect(queue.current(now: now) == nil) } /// Eine Notch, die zehnmal je Sekunde nachfragt, ob etwas abgelaufen ist, /// hält den Rechner wach. Deshalb rechnet die Schlange den Weckzeitpunkt aus. @Test("Der nächste Ablauf ist der früheste noch offene") func nextExpiryIsEarliest() { var queue = ActivityQueue() queue.raise(ticket("volume", rank: 10, for: 5)) queue.raise(ticket("charge", rank: 50, for: 2)) queue.raise(ticket("weather", rank: 1, for: nil)) #expect(queue.nextExpiry(after: now) == now.addingTimeInterval(2)) } @Test("Ohne offene Fristen gibt es keinen Weckzeitpunkt") func noExpiryWithoutDurations() { var queue = ActivityQueue() queue.raise(ticket("weather", rank: 1, for: nil)) #expect(queue.nextExpiry(after: now) == nil) } @Test("Aufräumen entfernt genau die abgelaufenen") func pruneRemovesExpiredOnly() { var queue = ActivityQueue() queue.raise(ticket("volume", rank: 10, for: 1)) queue.raise(ticket("weather", rank: 1, for: nil)) queue.prune(now: now.addingTimeInterval(2)) #expect(queue.all.map(\.id) == ["weather"]) } } @Suite("Fläche unter der Notch") @MainActor struct AmbientSizeTests { private struct Row: NotchActivity { let id = "volume" let rank = ActivityRank.response let duration: TimeInterval? = 2 var size: CGSize { CGSize(width: 196, height: 34) } func makeContent() -> AnyView { AnyView(EmptyView()) } } @Test("Die angemeldete Activity gibt die Fläche vor") func sizeComesFromActivity() { let presenter = ActivityPresenter() presenter.raise(Row()) #expect(presenter.contentSize == CGSize(width: 196, height: 34)) } @Test("Ohne Activity gibt es keine Fläche") func noActivityNoSize() { #expect(ActivityPresenter().contentSize == nil) } }