import AppKit import ConjureKit import Testing @testable import Conjure @Test @MainActor func actionsBackgroundKeepsWindowCoordinates() throws { _ = NSApplication.shared let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 750, height: 480), styleMask: [.borderless], backing: .buffered, defer: false) let host = try #require(window.contentView) let anchor = NSView(frame: NSRect(x: 700, y: 20, width: 30, height: 30)) host.addSubview(anchor) let actions = ApplicationActionsController() defer { actions.close(animated: false) } func descendants(_ view: NSView) -> [NSView] { [view] + view.subviews.flatMap(descendants) } for width in [750.0, 900.0] { window.setContentSize(NSSize(width: width, height: 480)) actions.showEmoji(named: "Smile", relativeTo: anchor) let background = try #require(descendants(host).compactMap { $0 as? LauncherBackground }.first) let search = try #require(descendants(actions.view).compactMap { $0 as? NSSearchField }.first) for query in ["", "Copy Emoji", "no matching action"] { search.stringValue = query actions.controlTextDidChange(Notification(name: NSControl.textDidChangeNotification, object: search)) host.layoutSubtreeIfNeeded() let rect = background.convert(background.bounds, to: host) #expect(rect == host.bounds) #expect(background.bounds.size != actions.view.bounds.size) } actions.close(animated: false) } } @Test @MainActor func providerPickerReplacesAnchorDownwardAndKeepsCurrentSelection() throws { _ = NSApplication.shared let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 750, height: 480), styleMask: .borderless, backing: .buffered, defer: false) window.isReleasedWhenClosed = false let host = try #require(window.contentView) let pill = NSView(frame: NSRect(x: 500, y: 420, width: 230, height: 36)) let anchor = NSButton(frame: pill.bounds) pill.addSubview(anchor) host.addSubview(pill) let popup = ApplicationActionsController() defer { popup.close(animated: false); window.orderOut(nil) } var selections: [Bool] = [] popup.selectProvider = { selections.append($0) } popup.showProviders(selectedOpenAI: true, relativeTo: anchor) host.layoutSubtreeIfNeeded() let rect = popup.view.convert(popup.view.bounds, to: host) #expect(rect.maxY == pill.frame.maxY) #expect(rect.minY < pill.frame.minY) #expect(rect.maxX == pill.frame.maxX) #expect(pill.isAccessibilityHidden()) _ = popup.handle(#selector(NSResponder.insertNewline(_:))) #expect(selections == [true]) // Reopen during the close animation, then switch modes without retaining the old anchor. popup.showTranslation(relativeTo: anchor) popup.showProviders(selectedOpenAI: false, relativeTo: anchor) host.layoutSubtreeIfNeeded() #expect(popup.view.convert(popup.view.bounds, to: host).maxY == pill.frame.maxY) popup.moveSelection(by: 1) _ = popup.handle(#selector(NSResponder.insertNewline(_:))) #expect(selections == [true, true]) popup.close(animated: false) #expect(pill.alphaValue == 1) #expect(!pill.isAccessibilityHidden()) }