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 delegate.applicationDidFinishLaunching(44 Notification(name: NSApplication.didFinishLaunchingNotification)45 )4647 #expect(delegate.statusItem != nil)48 #expect(delegate.menuController != nil)49 #expect(delegate.statusItem.button?.image != nil)50 }5152 @Test("Menu controller exercises popover and modal lifecycle")53 func menuControllerLifecycle() {54 let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)55 let popover = PopoverStub()56 let controller = MenuController(statusItem: statusItem, popover: popover)5758 #expect(MenuController.shared === controller)59 controller.beginModalPresentation()60 #expect(controller.popover.behavior == .applicationDefined)61 controller.endModalPresentation()62 #expect(controller.popover.behavior == .transient)6364 popover.stubShown = true65 controller.endModalPresentation()66 controller.eventMonitor?.handler(nil)67 popover.stubShown = true68 controller.togglePopover(nil)6970 controller.showPopover(sender: nil)71 controller.togglePopover(nil)72 controller.togglePopover(nil)73 controller.closePopover(sender: nil)74 controller.togglePopover(nil)75 controller.closePopover(sender: nil)76 #expect(popover.showCount > 0)77 #expect(popover.closeCount > 0)7879 NSStatusBar.system.removeStatusItem(statusItem)80 }8182 @Test("Event monitor start and stop are idempotent")83 func eventMonitorLifecycle() {84 var eventSeen = false85 let monitor = EventMonitor(mask: [.leftMouseDown]) { _ in eventSeen = true }8687 monitor.start()88 monitor.stop()89 monitor.stop()9091 #expect(!eventSeen)92 }9394 @Test("Repeated start does not leak AppKit event monitors")95 func eventMonitorRepeatedStartDoesNotLeak() {96 final class RegistrationCounter {97 var adds = 098 var removes = 099 }100 let counter = RegistrationCounter()101 let opaqueHandle = NSObject()102 let monitor = EventMonitor(103 mask: [.leftMouseDown],104 handler: { _ in },105 addMonitor: { _, _ in106 counter.adds += 1107 return opaqueHandle108 },109 removeMonitor: { _ in counter.removes += 1 }110 )111112 monitor.start()113 monitor.start()114 monitor.start()115 monitor.stop()116 monitor.stop()117118 119 120 121 #expect(counter.adds == 3)122 #expect(counter.removes == 3)123 }124125 @Test("Screen manager exposes AppKit screen metadata")126 func screenManager() {127 let manager = ScreenManager.shared128 #expect(manager.screens == NSScreen.screens)129 if let screen = manager.screens.first {130 #expect(manager.screenName(for: screen) == screen.localizedName)131 }132 }133134 @Test("Application bootstrap wires the delegate and optional event loop")135 func applicationBootstrap() {136 let delegate = AppDelegate()137 var received: AppDelegate?138 var runs = 0139 HavenApplication.start(140 delegate: delegate, shouldRun: false,141 setDelegate: { received = $0 }, run: { runs += 1 })142 #expect(received === delegate)143 #expect(runs == 0)144145 HavenApplication.start(146 delegate: delegate, shouldRun: true,147 setDelegate: { received = $0 }, run: { runs += 1 })148 #expect(runs == 1)149 }150151 @Test("Folder panel adapter configures directory selection")152 func folderPanel() {153 let directory = URL(fileURLWithPath: "/tmp")154 let configured = AppState.makeFolderPanel(at: directory)155 #expect(!configured.canChooseFiles)156 #expect(configured.canChooseDirectories)157 #expect(configured.canCreateDirectories)158 #expect(!configured.allowsMultipleSelection)159 #expect(configured.directoryURL == directory)160 }161162 @Test("Folder picker adapter forwards cancellation")163 func folderPicker() {164 let panel = OpenPanelStub()165 var result: URL? = URL(fileURLWithPath: "/sentinel")166167 AppState.presentFolderPicker(panel: panel) { result = $0 }168 #expect(panel.completion != nil)169170 panel.completion?(.cancel)171 #expect(result == nil)172 }173}