import AppKit import ConjureKit import Testing @testable import Conjure @Test(arguments: ["launcher", "launcher-actions", "emoji", "emoji-actions", "translate-input", "translate-output", "translate-key", "notes-editor"]) @MainActor func escapeDismissesEntireLauncher(_ 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-output" { let output = try #require(descendants(content).compactMap { $0 as? NSTextView }.first { $0.accessibilityLabel() == "Japanese translation" }) window.makeFirstResponder(output) } else if destination == "translate-key" { let provider = try #require(descendants(content).compactMap { $0 as? NSPopUpButton }.first) provider.selectItem(at: 1) _ = provider.target?.perform(provider.action, with: provider) let key = try #require(descendants(content).compactMap { $0 as? NSSecureTextField }.first) window.makeFirstResponder(key) } 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) #expect(!window.isVisible) #expect(!descendants(content).contains { $0 is NSCollectionView }) }