import AppKit import ConjureKit import Testing @testable import Conjure @Test(arguments: ["launcher", "launcher-actions", "emoji", "emoji-actions", "translate-input", "translate-output", "translate-actions", "translate-providers", "notes-editor"]) @MainActor func escapeClosesPickerBeforeDismissingLauncher(_ destination: String) throws { _ = NSApplication.shared let suite = "ConjureEscape.\(UUID().uuidString)" let defaults = UserDefaults(suiteName: suite)! let notesDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(suite) defer { defaults.removePersistentDomain(forName: suite) try? FileManager.default.removeItem(at: notesDirectory) } let launcher = LauncherWindowController( history: LaunchHistory(defaults: defaults, now: { Date(timeIntervalSinceReferenceDate: 12_000) }), defaults: defaults, notesDirectory: notesDirectory) let window = try #require(launcher.window) defer { window.orderOut(nil) } let content = try #require(window.contentView) func descendants(_ view: NSView) -> [NSView] { [view] + view.subviews.flatMap(descendants) } let search = try #require(descendants(content).compactMap { $0 as? NSSearchField }.first) if !destination.hasPrefix("launcher") { search.stringValue = destination.hasPrefix("emoji") ? "emoji" : destination.hasPrefix("notes") ? "notes" : "translate" launcher.controlTextDidChange(Notification(name: NSControl.textDidChangeNotification, object: search)) _ = launcher.control(search, textView: NSTextView(), doCommandBy: #selector(NSResponder.insertNewline(_:))) } window.makeKeyAndOrderFront(nil) window.makeFirstResponder(search) if destination == "launcher-actions" { search.stringValue = "emoji" launcher.controlTextDidChange(Notification(name: NSControl.textDidChangeNotification, object: search)) let button = try #require(descendants(content).compactMap { $0 as? NSButton }.first { $0.title == "Actions" && $0.target === launcher }) button.performClick(nil) } if destination == "emoji-actions" { let button = try #require(descendants(content).compactMap { $0 as? NSButton }.first { $0.title == "Actions" && $0.target is EmojiPickerController }) (button.target as? EmojiPickerController)?.showActions() } else if destination == "translate-actions" { let button = try #require(descendants(content).compactMap { $0 as? NSButton }.first { $0.title == "Actions" && $0.target is TranslationController }) button.performClick(nil) } else if destination == "translate-providers" { let shortcut = try #require(NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: .command, timestamp: 1, windowNumber: window.windowNumber, context: nil, characters: "p", charactersIgnoringModifiers: "p", isARepeat: false, keyCode: 35)) #expect(window.performKeyEquivalent(with: shortcut)) } else if destination == "translate-output" { let output = try #require(descendants(content).compactMap { $0 as? NSTextView }.first { $0.accessibilityLabel() == "Japanese translation" }) window.makeFirstResponder(output) } else if destination == "notes-editor" { let editor = try #require(descendants(content).compactMap { $0 as? NotesTextView }.first) window.makeFirstResponder(editor) } #expect(window.isVisible) let escape = try #require(NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [], timestamp: 1, windowNumber: window.windowNumber, context: nil, characters: "\u{1b}", charactersIgnoringModifiers: "\u{1b}", isARepeat: false, keyCode: 53)) window.sendEvent(escape) if destination.hasSuffix("-actions") || destination == "translate-providers" { #expect(window.isVisible) #expect(window.firstResponder === search.currentEditor()) window.sendEvent(escape) } #expect(!window.isVisible) #expect(!descendants(content).contains { $0 is NSCollectionView }) }