1import AppKit2import Testing34@testable import Haven56@MainActor7private final class PopoverStub: NSPopover {8 var stubShown = false9 var showCount = 010 var closeCount = 011 override var isShown: Bool { stubShown }12 override func show(13 relativeTo positioningRect: NSRect, of positioningView: NSView,14 preferredEdge: NSRectEdge15 ) {16 stubShown = true17 showCount += 118 }19 override func performClose(_ sender: Any?) {20 stubShown = false21 closeCount += 122 }23}2425@MainActor26private final class OpenPanelStub: NSOpenPanel {27 var completion: ((NSApplication.ModalResponse) -> Void)?2829 override func begin(30 completionHandler handler: @escaping (NSApplication.ModalResponse) -> Void31 ) {32 completion = handler33 }34}3536@Suite("AppKit adapters", .serialized)37@MainActor38struct PlatformCoverageTests {39 @Test("Application delegate creates the status item and menu controller")40 func appDelegateLaunch() {41 let delegate = AppDelegate()4243 44 45 #expect(delegate.currentStatusItem == nil)46 #expect(delegate.currentMenuController == nil)4748 delegate.applicationDidFinishLaunching(49 Notification(name: NSApplication.didFinishLaunchingNotification)50 )5152 #expect(delegate.currentStatusItem != nil)53 #expect(delegate.currentMenuController != nil)54 #expect(delegate.currentStatusItem?.button?.image != nil)5556 57 58 delegate.invalidate()59 #expect(delegate.currentStatusItem == nil)60 #expect(delegate.currentMenuController == nil)61 }6263 @Test("Menu controller exercises popover and modal lifecycle")64 func menuControllerLifecycle() {65 let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)66 let popover = PopoverStub()67 let controller = MenuController(statusItem: statusItem, popover: popover)6869 #expect(MenuController.shared === controller)70 controller.beginModalPresentation()71 #expect(controller.popover.behavior == .applicationDefined)72 controller.endModalPresentation()73 #expect(controller.popover.behavior == .transient)7475 popover.stubShown = true76 controller.endModalPresentation()77 controller.eventMonitor?.handler(nil)78 popover.stubShown = true79 controller.togglePopover(nil)8081 controller.showPopover(sender: nil)82 controller.togglePopover(nil)83 controller.togglePopover(nil)84 controller.closePopover(sender: nil)85 controller.togglePopover(nil)86 controller.closePopover(sender: nil)87 #expect(popover.showCount > 0)88 #expect(popover.closeCount > 0)8990 NSStatusBar.system.removeStatusItem(statusItem)91 }9293 @Test("Event monitor start and stop are idempotent")94 func eventMonitorLifecycle() {95 var eventSeen = false96 let monitor = EventMonitor(mask: [.leftMouseDown]) { _ in eventSeen = true }9798 monitor.start()99 monitor.stop()100 monitor.stop()101102 #expect(!eventSeen)103 }104105 @Test("Repeated start does not leak AppKit event monitors")106 func eventMonitorRepeatedStartDoesNotLeak() {107 final class RegistrationCounter {108 var adds = 0109 var removes = 0110 }111 let counter = RegistrationCounter()112 let opaqueHandle = NSObject()113 let monitor = EventMonitor(114 mask: [.leftMouseDown],115 handler: { _ in },116 addMonitor: { _, _ in117 counter.adds += 1118 return opaqueHandle119 },120 removeMonitor: { _ in counter.removes += 1 }121 )122123 monitor.start()124 monitor.start()125 monitor.start()126 monitor.stop()127 monitor.stop()128129 130 131 132 #expect(counter.adds == 3)133 #expect(counter.removes == 3)134 }135136 @Test("Screen manager exposes AppKit screen metadata")137 func screenManager() {138 let manager = ScreenManager.shared139 #expect(manager.screens == NSScreen.screens)140 if let screen = manager.screens.first {141 #expect(manager.screenName(for: screen) == screen.localizedName)142 }143 }144145 @Test("Application bootstrap wires the delegate and optional event loop")146 func applicationBootstrap() {147 let delegate = AppDelegate()148 var received: AppDelegate?149 var runs = 0150 HavenApplication.start(151 delegate: delegate, shouldRun: false,152 setDelegate: { received = $0 }, run: { runs += 1 })153 #expect(received === delegate)154 #expect(runs == 0)155156 HavenApplication.start(157 delegate: delegate, shouldRun: true,158 setDelegate: { received = $0 }, run: { runs += 1 })159 #expect(runs == 1)160 }161162 @Test("Application bootstrap installs the accessory activation policy")163 func applicationBootstrapInstallsAccessoryPolicy() {164 let delegate = AppDelegate()165 var installed: NSApplication.ActivationPolicy?166 let choice = HavenApplication.start(167 delegate: delegate, shouldRun: false,168 setDelegate: { _ in }, run: {},169 setActivationPolicy: { installed = $0 })170171 #expect(installed == .accessory)172 #expect(choice.policy == .accessory)173 }174175 @Test("Folder panel adapter configures directory selection")176 func folderPanel() {177 let directory = URL(fileURLWithPath: "/tmp")178 let configured = AppState.makeFolderPanel(at: directory)179 #expect(!configured.canChooseFiles)180 #expect(configured.canChooseDirectories)181 #expect(configured.canCreateDirectories)182 #expect(!configured.allowsMultipleSelection)183 #expect(configured.directoryURL == directory)184 }185186 @Test("Folder picker adapter forwards cancellation")187 func folderPicker() {188 let panel = OpenPanelStub()189 var result: URL? = URL(fileURLWithPath: "/sentinel")190191 AppState.presentFolderPicker(panel: panel) { result = $0 }192 #expect(panel.completion != nil)193194 panel.completion?(.cancel)195 #expect(result == nil)196 }197}