import AppKit import Testing @testable import Haven @MainActor private final class PopoverStub: NSPopover { var stubShown = false var showCount = 0 var closeCount = 0 override var isShown: Bool { stubShown } override func show( relativeTo positioningRect: NSRect, of positioningView: NSView, preferredEdge: NSRectEdge ) { stubShown = true showCount += 1 } override func performClose(_ sender: Any?) { stubShown = false closeCount += 1 } } @MainActor private final class OpenPanelStub: NSOpenPanel { var completion: ((NSApplication.ModalResponse) -> Void)? override func begin( completionHandler handler: @escaping (NSApplication.ModalResponse) -> Void ) { completion = handler } } @Suite("AppKit adapters", .serialized) @MainActor struct PlatformCoverageTests { @Test("Application delegate creates the status item and menu controller") func appDelegateLaunch() { let delegate = AppDelegate() delegate.applicationDidFinishLaunching( Notification(name: NSApplication.didFinishLaunchingNotification) ) #expect(delegate.statusItem != nil) #expect(delegate.menuController != nil) #expect(delegate.statusItem.button?.image != nil) } @Test("Menu controller exercises popover and modal lifecycle") func menuControllerLifecycle() { let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) let popover = PopoverStub() let controller = MenuController(statusItem: statusItem, popover: popover) #expect(MenuController.shared === controller) controller.beginModalPresentation() #expect(controller.popover.behavior == .applicationDefined) controller.endModalPresentation() #expect(controller.popover.behavior == .transient) popover.stubShown = true controller.endModalPresentation() controller.eventMonitor?.handler(nil) popover.stubShown = true controller.togglePopover(nil) controller.showPopover(sender: nil) controller.togglePopover(nil) controller.togglePopover(nil) controller.closePopover(sender: nil) controller.togglePopover(nil) controller.closePopover(sender: nil) #expect(popover.showCount > 0) #expect(popover.closeCount > 0) NSStatusBar.system.removeStatusItem(statusItem) } @Test("Event monitor start and stop are idempotent") func eventMonitorLifecycle() { var eventSeen = false let monitor = EventMonitor(mask: [.leftMouseDown]) { _ in eventSeen = true } monitor.start() monitor.stop() monitor.stop() #expect(!eventSeen) } @Test("Repeated start does not leak AppKit event monitors") func eventMonitorRepeatedStartDoesNotLeak() { final class RegistrationCounter { var adds = 0 var removes = 0 } let counter = RegistrationCounter() let opaqueHandle = NSObject() let monitor = EventMonitor( mask: [.leftMouseDown], handler: { _ in }, addMonitor: { _, _ in counter.adds += 1 return opaqueHandle }, removeMonitor: { _ in counter.removes += 1 } ) monitor.start() monitor.start() monitor.start() monitor.stop() monitor.stop() // The first two extra start() calls must each have removed the previous // monitor before installing the next one, so adds and removes stay in // lockstep and no AppKit handle outlives its owner. #expect(counter.adds == 3) #expect(counter.removes == 3) } @Test("Screen manager exposes AppKit screen metadata") func screenManager() { let manager = ScreenManager.shared #expect(manager.screens == NSScreen.screens) if let screen = manager.screens.first { #expect(manager.screenName(for: screen) == screen.localizedName) } } @Test("Application bootstrap wires the delegate and optional event loop") func applicationBootstrap() { let delegate = AppDelegate() var received: AppDelegate? var runs = 0 HavenApplication.start( delegate: delegate, shouldRun: false, setDelegate: { received = $0 }, run: { runs += 1 }) #expect(received === delegate) #expect(runs == 0) HavenApplication.start( delegate: delegate, shouldRun: true, setDelegate: { received = $0 }, run: { runs += 1 }) #expect(runs == 1) } @Test("Folder panel adapter configures directory selection") func folderPanel() { let directory = URL(fileURLWithPath: "/tmp") let configured = AppState.makeFolderPanel(at: directory) #expect(!configured.canChooseFiles) #expect(configured.canChooseDirectories) #expect(configured.canCreateDirectories) #expect(!configured.allowsMultipleSelection) #expect(configured.directoryURL == directory) } @Test("Folder picker adapter forwards cancellation") func folderPicker() { let panel = OpenPanelStub() var result: URL? = URL(fileURLWithPath: "/sentinel") AppState.presentFolderPicker(panel: panel) { result = $0 } #expect(panel.completion != nil) panel.completion?(.cancel) #expect(result == nil) } }