import CoreGraphics import Foundation import Testing @testable import CodexBar struct MenuBarVisibilityWatcherTests { @Test func `does not flag intentionally hidden status item`() { let snapshot = StatusItemVisibilitySnapshot( isVisible: false, hasButton: true, hasWindow: false, hasScreen: false, buttonWidth: 0) #expect(!MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot)) } @Test func `flags visible item without attached window`() { let snapshot = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: false, hasScreen: false, buttonWidth: 18) #expect(MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot)) } @Test func `flags visible item without button`() { let snapshot = StatusItemVisibilitySnapshot( isVisible: true, hasButton: false, hasWindow: false, hasScreen: false, buttonWidth: 0) #expect(MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot)) } @Test func `flags visible item with zero width`() { let snapshot = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: true, buttonWidth: 0) #expect(MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot)) } @Test func `allows visible item attached to a screen with width`() { let snapshot = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: true, buttonWidth: 18) #expect(!MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot)) } @Test func `window probe matches autosave name and reports display bounds`() { let snapshots = MenuBarStatusItemWindowProbe.snapshots( matching: ["codexbar-merged"], windowInfo: [[ kCGWindowName as String: "codexbar-merged", kCGWindowOwnerName as String: "Control Center", kCGWindowIsOnscreen as String: true, kCGWindowBounds as String: [ "X": 1680, "Y": 0, "Width": 70, "Height": 24, ], ]], displayBounds: [CGRect(x: 0, y: 0, width: 2056, height: 1329)]) #expect(snapshots.count == 1) #expect(snapshots.first?.name == "codexbar-merged") #expect(snapshots.first?.ownerName == "Control Center") #expect(snapshots.first?.isOnscreen == true) #expect(snapshots.first?.isWithinDisplayBounds == true) } @Test func `window probe detects offscreen status item by bounds`() { let snapshots = MenuBarStatusItemWindowProbe.snapshots( matching: ["codexbar-merged"], windowInfo: [[ kCGWindowName as String: "codexbar-merged", kCGWindowOwnerName as String: "Control Center", kCGWindowIsOnscreen as String: true, kCGWindowBounds as String: [ "X": 2023, "Y": 0, "Width": 71, "Height": 24, ], ]], displayBounds: [CGRect(x: 0, y: 0, width: 2056, height: 1329)]) #expect(snapshots.count == 1) #expect(snapshots.first?.isOnscreen == true) #expect(snapshots.first?.isWithinDisplayBounds == false) } @Test func `window probe identifies Tahoe Control Center blocked proxy geometry`() { let snapshot = MenuBarStatusItemWindowSnapshot( name: "codexbar-merged", ownerName: "Control Center", bounds: CGRect(x: 0, y: -22, width: 76, height: 22), isOnscreen: true, displayBounds: nil) #expect(snapshot.isTahoeBlockedProxy) } @Test func `window probe does not classify generic offscreen manager placement as Tahoe proxy`() { let snapshot = MenuBarStatusItemWindowSnapshot( name: "codexbar-merged", ownerName: "Control Center", bounds: CGRect(x: 2023, y: 0, width: 71, height: 24), isOnscreen: true, displayBounds: nil) #expect(!snapshot.isTahoeBlockedProxy) } @Test func `window probe does not classify stale hidden Control Center record as Tahoe proxy`() { let snapshot = MenuBarStatusItemWindowSnapshot( name: "codexbar-merged", ownerName: "Control Center", bounds: CGRect(x: 0, y: -22, width: 76, height: 22), isOnscreen: false, displayBounds: nil) #expect(!snapshot.isTahoeBlockedProxy) } @Test func `allows visible item attached to a detached screen`() { let snapshot = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: true, isOnCurrentScreen: false, buttonWidth: 18) #expect(!MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot)) } @Test func `classifies detached live item as displaced but not blocked`() { let snapshot = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: false, isOnCurrentScreen: false, buttonWidth: 18) #expect(!MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot)) #expect(MenuBarVisibilityWatcher.isDisplacedSnapshot(snapshot: snapshot)) } @Test func `classifies stale screen live item as displaced but not blocked`() { let snapshot = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: true, isOnCurrentScreen: false, buttonWidth: 18) #expect(!MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: snapshot)) #expect(MenuBarVisibilityWatcher.isDisplacedSnapshot(snapshot: snapshot)) } @Test func `guidance shows once then repeats after a day`() throws { let defaults = try #require(UserDefaults(suiteName: "MenuBarVisibilityWatcherTests")) defaults.removePersistentDomain(forName: "MenuBarVisibilityWatcherTests") let now = Date(timeIntervalSince1970: 1000) #expect(MenuBarVisibilityWatcher.shouldShowGuidance(defaults: defaults, now: now)) MenuBarVisibilityWatcher.markGuidanceShown(defaults: defaults, now: now) #expect(!MenuBarVisibilityWatcher.shouldShowGuidance( defaults: defaults, now: now.addingTimeInterval(MenuBarVisibilityWatcher.guidanceRepeatInterval - 1))) #expect(MenuBarVisibilityWatcher.shouldShowGuidance( defaults: defaults, now: now.addingTimeInterval(MenuBarVisibilityWatcher.guidanceRepeatInterval))) } @Test func `startup recovery triggers for blocked visible snapshot`() { let launchedAt = Date(timeIntervalSince1970: 1000) let blocked = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: false, hasScreen: false, buttonWidth: 18) #expect(MenuBarVisibilityWatcher.shouldAttemptStartupRecovery( appLaunchedAt: launchedAt, now: launchedAt.addingTimeInterval(2), snapshots: [blocked])) } @Test func `startup recovery retries detached Tahoe proxy corroborated by Control Center geometry`() { let launchedAt = Date(timeIntervalSince1970: 1000) let detachedProxy = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: false, isOnCurrentScreen: false, buttonWidth: 76) let blockedWindow = MenuBarStatusItemWindowSnapshot( name: "codexbar-merged", ownerName: "Control Center", bounds: CGRect(x: 0, y: -22, width: 76, height: 22), isOnscreen: true, displayBounds: nil) #expect(!MenuBarVisibilityWatcher.isBlockedSnapshot(snapshot: detachedProxy)) #expect(MenuBarVisibilityWatcher.shouldAttemptStartupRecovery( appLaunchedAt: launchedAt, now: launchedAt.addingTimeInterval(2), snapshots: [detachedProxy], windowSnapshots: [blockedWindow], detectTahoeBlockedStatusItem: true)) } @Test func `startup recovery retries expected hidden Tahoe item with enabled default and no window`() { let launchedAt = Date(timeIntervalSince1970: 1000) let hidden = StatusItemVisibilitySnapshot( isVisible: false, hasButton: true, hasWindow: false, hasScreen: false, buttonWidth: 76) let evidence = StatusItemStartupVisibilityEvidence( autosaveName: "codexbar-merged", expectsVisibility: true, visibilityDefault: true, snapshot: hidden) #expect(MenuBarVisibilityWatcher.shouldAttemptStartupRecovery( appLaunchedAt: launchedAt, now: launchedAt.addingTimeInterval(2), snapshots: [hidden], evidence: [evidence], detectTahoeBlockedStatusItem: true)) } @Test func `startup recovery ignores hidden Tahoe item without app and defaults visibility agreement`() { let launchedAt = Date(timeIntervalSince1970: 1000) let hidden = StatusItemVisibilitySnapshot( isVisible: false, hasButton: true, hasWindow: false, hasScreen: false, buttonWidth: 76) let intentionallyHidden = StatusItemStartupVisibilityEvidence( autosaveName: "codexbar-merged", expectsVisibility: false, visibilityDefault: true, snapshot: hidden) let disabledByUser = StatusItemStartupVisibilityEvidence( autosaveName: "codexbar-merged", expectsVisibility: true, visibilityDefault: false, snapshot: hidden) let unknownDefault = StatusItemStartupVisibilityEvidence( autosaveName: "codexbar-merged", expectsVisibility: true, visibilityDefault: nil, snapshot: hidden) for evidence in [intentionallyHidden, disabledByUser, unknownDefault] { #expect(!MenuBarVisibilityWatcher.shouldAttemptStartupRecovery( appLaunchedAt: launchedAt, now: launchedAt.addingTimeInterval(2), snapshots: [hidden], evidence: [evidence], detectTahoeBlockedStatusItem: true)) } } @Test func `startup recovery ignores hidden item when matching window still exists`() { let launchedAt = Date(timeIntervalSince1970: 1000) let hidden = StatusItemVisibilitySnapshot( isVisible: false, hasButton: true, hasWindow: false, hasScreen: false, buttonWidth: 76) let evidence = StatusItemStartupVisibilityEvidence( autosaveName: "codexbar-merged", expectsVisibility: true, visibilityDefault: true, snapshot: hidden) let existingWindow = MenuBarStatusItemWindowSnapshot( name: "codexbar-merged", ownerName: "Control Center", bounds: CGRect(x: 1500, y: 0, width: 76, height: 24), isOnscreen: true, displayBounds: CGRect(x: 0, y: 0, width: 2056, height: 1329)) #expect(!MenuBarVisibilityWatcher.shouldAttemptStartupRecovery( appLaunchedAt: launchedAt, now: launchedAt.addingTimeInterval(2), snapshots: [hidden], evidence: [evidence], windowSnapshots: [existingWindow], detectTahoeBlockedStatusItem: true)) } @Test func `startup recovery ignores stale hidden matching window record`() { let launchedAt = Date(timeIntervalSince1970: 1000) let hidden = StatusItemVisibilitySnapshot( isVisible: false, hasButton: true, hasWindow: false, hasScreen: false, buttonWidth: 76) let evidence = StatusItemStartupVisibilityEvidence( autosaveName: "codexbar-merged", expectsVisibility: true, visibilityDefault: true, snapshot: hidden) let staleWindow = MenuBarStatusItemWindowSnapshot( name: "codexbar-merged", ownerName: "Control Center", bounds: CGRect(x: 1500, y: 0, width: 76, height: 24), isOnscreen: false, displayBounds: CGRect(x: 0, y: 0, width: 2056, height: 1329)) #expect(MenuBarVisibilityWatcher.shouldAttemptStartupRecovery( appLaunchedAt: launchedAt, now: launchedAt.addingTimeInterval(2), snapshots: [hidden], evidence: [evidence], windowSnapshots: [staleWindow], detectTahoeBlockedStatusItem: true)) } @Test func `startup recovery keeps hidden no-window detection Tahoe only`() { let launchedAt = Date(timeIntervalSince1970: 1000) let hidden = StatusItemVisibilitySnapshot( isVisible: false, hasButton: true, hasWindow: false, hasScreen: false, buttonWidth: 76) let evidence = StatusItemStartupVisibilityEvidence( autosaveName: "codexbar-merged", expectsVisibility: true, visibilityDefault: true, snapshot: hidden) #expect(!MenuBarVisibilityWatcher.shouldAttemptStartupRecovery( appLaunchedAt: launchedAt, now: launchedAt.addingTimeInterval(2), snapshots: [hidden], evidence: [evidence])) } @Test func `startup recovery ignores detached live item without Tahoe proxy corroboration`() { let launchedAt = Date(timeIntervalSince1970: 1000) let managed = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: false, isOnCurrentScreen: false, buttonWidth: 18) #expect(!MenuBarVisibilityWatcher.shouldAttemptStartupRecovery( appLaunchedAt: launchedAt, now: launchedAt.addingTimeInterval(2), snapshots: [managed], detectTahoeBlockedStatusItem: true)) } @Test func `startup recovery ignores live item attached to a stale screen`() { let launchedAt = Date(timeIntervalSince1970: 1000) let managed = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: true, isOnCurrentScreen: false, buttonWidth: 18) #expect(!MenuBarVisibilityWatcher.shouldAttemptStartupRecovery( appLaunchedAt: launchedAt, now: launchedAt.addingTimeInterval(2), snapshots: [managed])) } @Test func `startup recovery triggers when one split status item is blocked`() { let launchedAt = Date(timeIntervalSince1970: 1000) let healthy = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: true, buttonWidth: 18) let blocked = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: false, hasScreen: false, buttonWidth: 18) #expect(MenuBarVisibilityWatcher.shouldAttemptStartupRecovery( appLaunchedAt: launchedAt, now: launchedAt.addingTimeInterval(2), snapshots: [healthy, blocked])) } @Test func `startup recovery ignores stale checks`() { let launchedAt = Date(timeIntervalSince1970: 1000) let blocked = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: false, hasScreen: false, buttonWidth: 18) #expect(!MenuBarVisibilityWatcher.shouldAttemptStartupRecovery( appLaunchedAt: launchedAt, now: launchedAt.addingTimeInterval(MenuBarVisibilityWatcher.startupFreshnessInterval + 1), snapshots: [blocked])) } @Test func `startup recovery ignores healthy visible snapshot`() { let launchedAt = Date(timeIntervalSince1970: 1000) let healthy = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: true, buttonWidth: 18) #expect(!MenuBarVisibilityWatcher.shouldAttemptStartupRecovery( appLaunchedAt: launchedAt, now: launchedAt.addingTimeInterval(2), snapshots: [healthy])) } @Test func `screen change placement refresh ignores display removal with healthy status item`() { let healthy = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: true, buttonWidth: 18) #expect(!MenuBarVisibilityWatcher.shouldRefreshScreenChangePlacement( previousScreenCount: 2, currentScreenCount: 1, snapshots: [healthy])) } @Test func `screen change placement refresh ignores display removal when no status item is visible`() { let hidden = StatusItemVisibilitySnapshot( isVisible: false, hasButton: true, hasWindow: true, hasScreen: true, buttonWidth: 18) #expect(!MenuBarVisibilityWatcher.shouldRefreshScreenChangePlacement( previousScreenCount: 2, currentScreenCount: 1, snapshots: [hidden])) } @Test func `screen change recovery triggers for blocked status item without display count change`() { let blocked = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: false, hasScreen: false, buttonWidth: 18) #expect(MenuBarVisibilityWatcher.shouldAttemptScreenChangeRecovery(snapshots: [blocked])) } @Test func `screen change placement refresh triggers for detached live item after display removal`() { let displaced = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: false, isOnCurrentScreen: false, buttonWidth: 18) #expect(MenuBarVisibilityWatcher.shouldRefreshScreenChangePlacement( previousScreenCount: 2, currentScreenCount: 1, snapshots: [displaced])) } @Test func `screen change placement refresh triggers for stale screen live item after display removal`() { let displaced = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: true, isOnCurrentScreen: false, buttonWidth: 18) #expect(MenuBarVisibilityWatcher.shouldRefreshScreenChangePlacement( previousScreenCount: 2, currentScreenCount: 1, snapshots: [displaced])) } @Test func `screen change placement refresh ignores healthy item when display count does not shrink`() { let healthy = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: true, buttonWidth: 18) #expect(!MenuBarVisibilityWatcher.shouldRefreshScreenChangePlacement( previousScreenCount: 1, currentScreenCount: 2, snapshots: [healthy])) } @Test func `screen change placement refresh triggers for displaced live item when display count is unchanged`() { let displaced = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: true, isOnCurrentScreen: false, buttonWidth: 18) #expect(MenuBarVisibilityWatcher.shouldRefreshScreenChangePlacement( previousScreenCount: 2, currentScreenCount: 2, snapshots: [displaced])) } @Test func `manager parked item with live window is not blocked`() { // A menu bar manager parks items off the active screen with the window intact. // hasAnyBlockedVisibleSnapshot must return false so verifyScreenChangeRecoveryIfNeeded // does not trigger repeated recreation that corrupts Control Center. let managed = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: false, isOnCurrentScreen: false, buttonWidth: 18) #expect(!MenuBarVisibilityWatcher.hasAnyBlockedVisibleSnapshot([managed])) #expect(MenuBarVisibilityWatcher.hasAnyDisplacedVisibleSnapshot([managed])) } @Test func `manager parked item with live window on stale screen is not blocked`() { let managed = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: true, hasScreen: true, isOnCurrentScreen: false, buttonWidth: 18) #expect(!MenuBarVisibilityWatcher.hasAnyBlockedVisibleSnapshot([managed])) #expect(MenuBarVisibilityWatcher.hasAnyDisplacedVisibleSnapshot([managed])) } @Test func `item without window is blocked regardless of screen state`() { // A missing window cannot be caused by a manager parking the item; it signals // a genuine system block and must trigger recovery. let blocked = StatusItemVisibilitySnapshot( isVisible: true, hasButton: true, hasWindow: false, hasScreen: false, isOnCurrentScreen: false, buttonWidth: 18) #expect(MenuBarVisibilityWatcher.hasAnyBlockedVisibleSnapshot([blocked])) #expect(!MenuBarVisibilityWatcher.hasAnyDisplacedVisibleSnapshot([blocked])) } }