Die Silhouette bestand aus zwei überlappenden Teilpfaden. Gefüllt stimmte die Vereinigung, aber die Kontur zeichnete jeden Teilpfad vollständig nach — auch dort, wo er innen lag. Dazu kippte die Form ab einer Breite in einen anderen Zweig, und die beiden Zweige waren nicht dieselbe Geometrie. Zusammen war das das Flackern an den Rundungen. Jetzt ein durchgehender Umriss, dessen Zweige ineinander übergehen statt zu springen. Neu ist die Wahl zwischen verschmolzen und schwebend. Eine echte Notch **muss** an der Kante kleben und oben eckig sein, sonst klafft ein Spalt zum Gehäuse; eine Insel **muss** Abstand halten und rundum gerundet sein, sonst sieht sie aus wie ein abgeschnittenes Rechteck. Beides zu mischen geht nicht, deshalb eine Wahl und keine zwei Schalter. Ihre Höhe rät Onyx nicht mehr, sondern misst die Menüleiste: 24 Punkte minus Luft oben und unten. Vorher hing ein festes Maß unten heraus. Dazu „nur der Hauptbildschirm" als dritte Anzeigewahl — gemeint ist der mit der Menüleiste, nicht `NSScreen.main`, das mit dem aktiven Fenster mitwandert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
108 lines
3.8 KiB
Swift
108 lines
3.8 KiB
Swift
import Testing
|
|
@testable import OnyxNotch
|
|
|
|
@Suite("Eingeklappt und trotzdem da")
|
|
struct AmbientStateTests {
|
|
|
|
/// Öffnet das Panel auf dem üblichen Weg: hinein, Entprellzeit ab.
|
|
private func opened() -> NotchStateMachine {
|
|
var machine = NotchStateMachine()
|
|
machine.handle(.pointerEntered)
|
|
machine.handle(.armTimerFired)
|
|
return machine
|
|
}
|
|
|
|
@Test("Ohne Live Activity ist eingeklappt gleich unsichtbar")
|
|
func idleIsOffScreen() {
|
|
let machine = NotchStateMachine()
|
|
#expect(!machine.isExpanded)
|
|
#expect(!machine.isOnScreen)
|
|
}
|
|
|
|
/// Der ganze Zweck: Fenster auf dem Schirm, Fläche in Notchgröße.
|
|
@Test("Eine Live Activity holt das Fenster, ohne auszufahren")
|
|
func ambientShowsWithoutExpanding() {
|
|
var machine = NotchStateMachine()
|
|
let effects = machine.handle(.ambient(true))
|
|
|
|
#expect(effects == [.showAmbient])
|
|
#expect(machine.isOnScreen)
|
|
#expect(!machine.isExpanded)
|
|
}
|
|
|
|
@Test("Fällt sie weg, geht das Fenster wieder fort")
|
|
func ambientVanishing() {
|
|
var machine = NotchStateMachine()
|
|
machine.handle(.ambient(true))
|
|
#expect(machine.handle(.ambient(false)) == [.hideAmbient])
|
|
#expect(!machine.isOnScreen)
|
|
}
|
|
|
|
/// Zweimal dieselbe Meldung ist keine Änderung. Ohne diese Sperre blendet
|
|
/// jeder Lautstärkeschritt das Fenster neu ein.
|
|
@Test("Dieselbe Meldung zweimal bewirkt nichts")
|
|
func repeatedIsQuiet() {
|
|
var machine = NotchStateMachine()
|
|
machine.handle(.ambient(true))
|
|
#expect(machine.handle(.ambient(true)).isEmpty)
|
|
}
|
|
|
|
/// Ausgefahren ist ohnehin alles zu sehen — dort darf eine Activity das
|
|
/// Fenster nicht ein zweites Mal einblenden.
|
|
@Test("Ausgefahren bleibt eine Live Activity ohne Wirkung")
|
|
func ambientWhileExpanded() {
|
|
var machine = opened()
|
|
#expect(machine.handle(.ambient(true)).isEmpty)
|
|
#expect(machine.isExpanded)
|
|
#expect(machine.isOnScreen)
|
|
}
|
|
|
|
/// Der eigentliche Grund für die Trennung: nach dem Einfahren darf das
|
|
/// Fenster nicht fortgeräumt werden, wenn noch etwas anliegt.
|
|
@Test("Nach dem Einfahren bleibt das Fenster, solange etwas anliegt")
|
|
func staysAfterCollapse() {
|
|
var machine = opened()
|
|
machine.handle(.ambient(true))
|
|
machine.handle(.pointerExited)
|
|
machine.handle(.closeTimerFired)
|
|
|
|
#expect(!machine.isExpanded)
|
|
#expect(machine.isOnScreen)
|
|
#expect(machine.hasAmbient)
|
|
}
|
|
|
|
@Test("Unterdrückung nimmt auch die Live Activity weg")
|
|
func suppressionHidesAmbient() {
|
|
var machine = NotchStateMachine()
|
|
machine.handle(.ambient(true))
|
|
let effects = machine.handle(.suppressed(true))
|
|
|
|
#expect(effects.contains(.hideAmbient))
|
|
#expect(!machine.isOnScreen)
|
|
// Gemerkt bleibt sie trotzdem — sonst käme sie nach dem Vollbildvideo
|
|
// nicht von selbst zurück.
|
|
#expect(machine.hasAmbient)
|
|
}
|
|
|
|
@Test("Nach der Unterdrückung kommt sie von selbst zurück")
|
|
func ambientReturnsAfterSuppression() {
|
|
var machine = NotchStateMachine()
|
|
machine.handle(.ambient(true))
|
|
machine.handle(.suppressed(true))
|
|
|
|
#expect(machine.handle(.suppressed(false)) == [.showAmbient])
|
|
#expect(machine.isOnScreen)
|
|
}
|
|
|
|
/// Eine Activity darf die Phase nicht anfassen: sonst gälte ein Klick
|
|
/// plötzlich als Fixieren, oder eine Frist liefe, die niemand gestartet hat.
|
|
@Test("Eine Live Activity ändert die Phase nicht")
|
|
func ambientLeavesPhaseAlone() {
|
|
var machine = NotchStateMachine()
|
|
machine.handle(.pointerEntered)
|
|
let before = machine.phase
|
|
machine.handle(.ambient(true))
|
|
#expect(machine.phase == before)
|
|
}
|
|
}
|