feat: copy and paste selected emoji
This commit is contained in:
@@ -95,9 +95,9 @@ Rates are shared across queries and refreshed on the next currency query after t
|
|||||||
|
|
||||||
Search for **Conjure Emoji**, identified by its centered 🤔 icon, and open it. Browse the grid or search by name, keyword, `:shortcode:`, or emoji. Use the category menu to filter results or show **Recently Used**. Your last 30 selections are saved locally. Favorites appear first, followed by recent selections when browsing All Emoji.
|
Search for **Conjure Emoji**, identified by its centered 🤔 icon, and open it. Browse the grid or search by name, keyword, `:shortcode:`, or emoji. Use the category menu to filter results or show **Recently Used**. Your last 30 selections are saved locally. Favorites appear first, followed by recent selections when browsing All Emoji.
|
||||||
|
|
||||||
Use the arrow keys or Control-H/J/K/L to navigate left/down/up/right. Return or clicking an emoji copies it and dismisses Conjure, restoring focus to the previous app. Paste with Command-V; no Accessibility permission is needed. Escape or the Back chevron returns to the launcher and restores your search.
|
Use the arrow keys or Control-H/J/K/L to navigate left/down/up/right. Return, clicking an emoji, or **Copy and Paste** copies it, dismisses Conjure, and tries to paste into the app that was focused when the launcher opened. Automatic pasting requires **System Settings → Privacy & Security → Accessibility → Conjure**; macOS prompts if permission is missing. After granting it, select an emoji again. If permission is denied, the previous app is unavailable, or focus cannot be restored promptly, the emoji stays copied for manual Command-V. Escape or the Back chevron returns to the launcher and restores your search.
|
||||||
|
|
||||||
Open **Actions** with Command-K and choose **Favorite** or **Remove Favorite**. Favorites are saved locally and grouped ahead of other emoji, with a small extra spacing break when browsing without a search. There are no star badges.
|
Open **Actions** with Command-K for **Copy and Paste** (default), **Copy Emoji** (copy only, no Accessibility permission needed), **Favorite**, or **Remove Favorite**. Favorites are saved locally and grouped ahead of other emoji, with a small extra spacing break when browsing without a search. There are no star badges.
|
||||||
|
|
||||||
The picker works offline with 1,870 emoji from [gemoji v4.1.0](https://github.com/github/gemoji/tree/v4.1.0). Its [MIT license](Sources/ConjureKit/Resources/LICENSE-gemoji.txt) is included in the application.
|
The picker works offline with 1,870 emoji from [gemoji v4.1.0](https://github.com/github/gemoji/tree/v4.1.0). Its [MIT license](Sources/ConjureKit/Resources/LICENSE-gemoji.txt) is included in the application.
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
ActionEntry(title: "Show in Finder", symbol: "finder", color: SunsetPalette.text),
|
ActionEntry(title: "Show in Finder", symbol: "finder", color: SunsetPalette.text),
|
||||||
ActionEntry(title: "Reset ranking", symbol: "arrow.counterclockwise", color: SunsetPalette.text),
|
ActionEntry(title: "Reset ranking", symbol: "arrow.counterclockwise", color: SunsetPalette.text),
|
||||||
ActionEntry(title: "Quit application", symbol: "stop.circle", color: SunsetPalette.text),
|
ActionEntry(title: "Quit application", symbol: "stop.circle", color: SunsetPalette.text),
|
||||||
ActionEntry(title: "Uninstall application", symbol: "trash", color: .systemRed)
|
ActionEntry(title: "Uninstall application", symbol: "trash", color: .systemRed),
|
||||||
|
ActionEntry(title: "Copy Emoji", symbol: "doc.on.doc", color: SunsetPalette.text)
|
||||||
]
|
]
|
||||||
private var visibleEntries: [Int] = []
|
private var visibleEntries: [Int] = []
|
||||||
private var availableEntries = Array(0..<6)
|
private var availableEntries = Array(0..<6)
|
||||||
@@ -26,6 +27,7 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
private var transitionGeneration = 0
|
private var transitionGeneration = 0
|
||||||
private var panel: ActionsPanelView { view as! ActionsPanelView }
|
private var panel: ActionsPanelView { view as! ActionsPanelView }
|
||||||
var open: (() -> Void)?
|
var open: (() -> Void)?
|
||||||
|
var copyEmoji: (() -> Void)?
|
||||||
var toggleFavorite: (() -> Void)?
|
var toggleFavorite: (() -> Void)?
|
||||||
var isFavorite = false
|
var isFavorite = false
|
||||||
var showInFinder: (() -> Void)?
|
var showInFinder: (() -> Void)?
|
||||||
@@ -126,11 +128,11 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
|
|
||||||
func show(for application: Application, relativeTo anchor: NSView) {
|
func show(for application: Application, relativeTo anchor: NSView) {
|
||||||
show(title: application.name, relativeTo: anchor,
|
show(title: application.name, relativeTo: anchor,
|
||||||
available: application.command == nil ? Array(entries.indices) : [0, 1, 3])
|
available: application.command == nil ? Array(0..<6) : [0, 1, 3])
|
||||||
}
|
}
|
||||||
|
|
||||||
func showEmoji(named name: String, relativeTo anchor: NSView) {
|
func showEmoji(named name: String, relativeTo anchor: NSView) {
|
||||||
show(title: name, relativeTo: anchor, available: [0, 1], emoji: true)
|
show(title: name, relativeTo: anchor, available: [0, 6, 1], emoji: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func show(title: String, relativeTo anchor: NSView, available: [Int], emoji: Bool = false) {
|
private func show(title: String, relativeTo anchor: NSView, available: [Int], emoji: Bool = false) {
|
||||||
@@ -140,8 +142,10 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
_ = view
|
_ = view
|
||||||
applicationLabel.stringValue = title
|
applicationLabel.stringValue = title
|
||||||
availableEntries = available
|
availableEntries = available
|
||||||
entries[0].button.title = emoji ? "Copy Emoji" : "Open"
|
let openTitle = emoji ? "Copy and Paste" : "Open"
|
||||||
entries[0].icon.image = NSImage(systemSymbolName: emoji ? "doc.on.doc" : "arrow.up.forward.app", accessibilityDescription: nil)
|
entries[0].button.title = openTitle
|
||||||
|
entries[0].button.setAccessibilityLabel(openTitle)
|
||||||
|
entries[0].icon.image = NSImage(systemSymbolName: emoji ? "doc.on.clipboard" : "arrow.up.forward.app", accessibilityDescription: nil)
|
||||||
search.setAccessibilityLabel(emoji ? "Search emoji actions" : "Search application actions")
|
search.setAccessibilityLabel(emoji ? "Search emoji actions" : "Search application actions")
|
||||||
let favorite = entries[1]
|
let favorite = entries[1]
|
||||||
let favoriteTitle = isFavorite ? "Remove Favorite" : "Favorite"
|
let favoriteTitle = isFavorite ? "Remove Favorite" : "Favorite"
|
||||||
@@ -215,9 +219,8 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
|
|
||||||
func controlTextDidChange(_ notification: Notification) {
|
func controlTextDidChange(_ notification: Notification) {
|
||||||
let query = search.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
let query = search.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
visibleEntries = entries.indices.filter {
|
visibleEntries = availableEntries.filter {
|
||||||
availableEntries.contains($0)
|
query.isEmpty || entries[$0].button.title.range(of: query, options: [.caseInsensitive, .diacriticInsensitive]) != nil
|
||||||
&& (query.isEmpty || entries[$0].button.title.range(of: query, options: [.caseInsensitive, .diacriticInsensitive]) != nil)
|
|
||||||
}
|
}
|
||||||
selectedEntry = visibleEntries.first
|
selectedEntry = visibleEntries.first
|
||||||
for index in entries.indices {
|
for index in entries.indices {
|
||||||
@@ -278,6 +281,7 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
|||||||
case 3: resetRanking?()
|
case 3: resetRanking?()
|
||||||
case 4: quit?()
|
case 4: quit?()
|
||||||
case 5: uninstall?()
|
case 5: uninstall?()
|
||||||
|
case 6: copyEmoji?()
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ final class EmojiPickerController: NSViewController, NSCollectionViewDataSource,
|
|||||||
private let category = NSPopUpButton()
|
private let category = NSPopUpButton()
|
||||||
private let status = NSTextField(labelWithString: "")
|
private let status = NSTextField(labelWithString: "")
|
||||||
private let empty = NSTextField(labelWithString: "No matching emoji")
|
private let empty = NSTextField(labelWithString: "No matching emoji")
|
||||||
private let copyButton = NSButton(title: "Copy Emoji", target: nil, action: nil)
|
private let copyButton = NSButton(title: "Copy and Paste", target: nil, action: nil)
|
||||||
private let actionsButton = NSButton(title: "Actions", target: nil, action: nil)
|
private let actionsButton = NSButton(title: "Actions", target: nil, action: nil)
|
||||||
private let actions = ApplicationActionsController()
|
private let actions = ApplicationActionsController()
|
||||||
private var favoriteCount = 0
|
private var favoriteCount = 0
|
||||||
@@ -19,6 +19,7 @@ final class EmojiPickerController: NSViewController, NSCollectionViewDataSource,
|
|||||||
var input: (() -> String)?
|
var input: (() -> String)?
|
||||||
var closed: (() -> Void)?
|
var closed: (() -> Void)?
|
||||||
var copied: (() -> Void)?
|
var copied: (() -> Void)?
|
||||||
|
var pasteRequested: (() -> Void)?
|
||||||
var focusSearch: (() -> Void)?
|
var focusSearch: (() -> Void)?
|
||||||
|
|
||||||
init(defaults: UserDefaults = .standard, pasteboard: NSPasteboard = .general) {
|
init(defaults: UserDefaults = .standard, pasteboard: NSPasteboard = .general) {
|
||||||
@@ -59,7 +60,7 @@ final class EmojiPickerController: NSViewController, NSCollectionViewDataSource,
|
|||||||
status.lineBreakMode = .byTruncatingTail
|
status.lineBreakMode = .byTruncatingTail
|
||||||
status.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
status.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||||
copyButton.target = self
|
copyButton.target = self
|
||||||
copyButton.action = #selector(copySelection)
|
copyButton.action = #selector(copyAndPasteSelection)
|
||||||
copyButton.keyEquivalent = "\r"
|
copyButton.keyEquivalent = "\r"
|
||||||
copyButton.isBordered = false
|
copyButton.isBordered = false
|
||||||
copyButton.font = .systemFont(ofSize: 12, weight: .medium)
|
copyButton.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
@@ -198,7 +199,8 @@ final class EmojiPickerController: NSViewController, NSCollectionViewDataSource,
|
|||||||
guard results.indices.contains(selected) else { return }
|
guard results.indices.contains(selected) else { return }
|
||||||
let emoji = results[selected]
|
let emoji = results[selected]
|
||||||
actions.isFavorite = (defaults.stringArray(forKey: "favoriteEmoji") ?? []).contains(emoji.value)
|
actions.isFavorite = (defaults.stringArray(forKey: "favoriteEmoji") ?? []).contains(emoji.value)
|
||||||
actions.open = { [weak self] in self?.copySelection() }
|
actions.open = { [weak self] in self?.copyAndPasteSelection() }
|
||||||
|
actions.copyEmoji = { [weak self] in self?.copySelection() }
|
||||||
actions.toggleFavorite = { [weak self] in self?.toggleFavorite() }
|
actions.toggleFavorite = { [weak self] in self?.toggleFavorite() }
|
||||||
actions.showEmoji(named: emoji.name, relativeTo: actionsButton)
|
actions.showEmoji(named: emoji.name, relativeTo: actionsButton)
|
||||||
copyButton.keyEquivalent = actions.isShown ? "" : "\r"
|
copyButton.keyEquivalent = actions.isShown ? "" : "\r"
|
||||||
@@ -220,7 +222,7 @@ final class EmojiPickerController: NSViewController, NSCollectionViewDataSource,
|
|||||||
case #selector(NSResponder.moveRight(_:)): moveSelection(by: 1)
|
case #selector(NSResponder.moveRight(_:)): moveSelection(by: 1)
|
||||||
case #selector(NSResponder.moveUp(_:)): moveVertically(-1)
|
case #selector(NSResponder.moveUp(_:)): moveVertically(-1)
|
||||||
case #selector(NSResponder.moveDown(_:)): moveVertically(1)
|
case #selector(NSResponder.moveDown(_:)): moveVertically(1)
|
||||||
case #selector(NSResponder.insertNewline(_:)): copySelection()
|
case #selector(NSResponder.insertNewline(_:)): copyAndPasteSelection()
|
||||||
case #selector(NSResponder.cancelOperation(_:)): goBack()
|
case #selector(NSResponder.cancelOperation(_:)): goBack()
|
||||||
default: return false
|
default: return false
|
||||||
}
|
}
|
||||||
@@ -238,7 +240,10 @@ final class EmojiPickerController: NSViewController, NSCollectionViewDataSource,
|
|||||||
(collection.item(at: indexPath(for: selected)) as? EmojiItem)?.highlight(true)
|
(collection.item(at: indexPath(for: selected)) as? EmojiItem)?.highlight(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func copySelection() {
|
@objc func copySelection() { copySelection(pasting: false) }
|
||||||
|
@objc func copyAndPasteSelection() { copySelection(pasting: true) }
|
||||||
|
|
||||||
|
private func copySelection(pasting: Bool) {
|
||||||
guard results.indices.contains(selected) else { return }
|
guard results.indices.contains(selected) else { return }
|
||||||
let value = results[selected].value
|
let value = results[selected].value
|
||||||
pasteboard.clearContents()
|
pasteboard.clearContents()
|
||||||
@@ -250,7 +255,7 @@ final class EmojiPickerController: NSViewController, NSCollectionViewDataSource,
|
|||||||
recent.removeAll { $0 == value }
|
recent.removeAll { $0 == value }
|
||||||
defaults.set(Array(([value] + recent).prefix(30)), forKey: "recentEmoji")
|
defaults.set(Array(([value] + recent).prefix(30)), forKey: "recentEmoji")
|
||||||
closed?()
|
closed?()
|
||||||
copied?()
|
if pasting { pasteRequested?() } else { copied?() }
|
||||||
}
|
}
|
||||||
|
|
||||||
func numberOfSections(in collectionView: NSCollectionView) -> Int { splitFavorites ? 2 : 1 }
|
func numberOfSections(in collectionView: NSCollectionView) -> Int { splitFavorites ? 2 : 1 }
|
||||||
@@ -273,7 +278,7 @@ final class EmojiPickerController: NSViewController, NSCollectionViewDataSource,
|
|||||||
item.highlight(index == selected)
|
item.highlight(index == selected)
|
||||||
item.clicked = { [weak self] in
|
item.clicked = { [weak self] in
|
||||||
self?.selected = index
|
self?.selected = index
|
||||||
self?.copySelection()
|
self?.copyAndPasteSelection()
|
||||||
}
|
}
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import AppKit
|
import AppKit
|
||||||
import ConjureKit
|
import ConjureKit
|
||||||
import CoreText
|
import CoreText
|
||||||
|
import ApplicationServices
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSearchFieldDelegate, NSTableViewDataSource, NSTableViewDelegate {
|
final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSearchFieldDelegate, NSTableViewDataSource, NSTableViewDelegate {
|
||||||
@@ -760,6 +761,32 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func pasteEmoji() {
|
||||||
|
let target = previousApplication
|
||||||
|
let changeCount = NSPasteboard.general.changeCount
|
||||||
|
dismiss()
|
||||||
|
guard let target, !target.isTerminated,
|
||||||
|
target.processIdentifier != ProcessInfo.processInfo.processIdentifier,
|
||||||
|
AXIsProcessTrustedWithOptions(["AXTrustedCheckOptionPrompt": true] as CFDictionary)
|
||||||
|
else { return }
|
||||||
|
Task { [weak self] in
|
||||||
|
// Wait for focus restoration; never paste into a different app or a reopened launcher.
|
||||||
|
for _ in 0..<20 {
|
||||||
|
try? await Task.sleep(for: .milliseconds(25))
|
||||||
|
guard let self, self.window?.isVisible == false, !target.isTerminated,
|
||||||
|
NSPasteboard.general.changeCount == changeCount else { return }
|
||||||
|
guard NSWorkspace.shared.frontmostApplication?.processIdentifier == target.processIdentifier else { continue }
|
||||||
|
guard let down = CGEvent(keyboardEventSource: nil, virtualKey: 9, keyDown: true),
|
||||||
|
let up = CGEvent(keyboardEventSource: nil, virtualKey: 9, keyDown: false) else { return }
|
||||||
|
down.flags = .maskCommand
|
||||||
|
up.flags = .maskCommand
|
||||||
|
down.postToPid(target.processIdentifier)
|
||||||
|
up.postToPid(target.processIdentifier)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func goBackCommand() {
|
@objc private func goBackCommand() {
|
||||||
translation?.goBack()
|
translation?.goBack()
|
||||||
emojiPicker?.goBack()
|
emojiPicker?.goBack()
|
||||||
@@ -790,6 +817,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
|||||||
tool.input = { [weak self] in self?.searchField.stringValue ?? "" }
|
tool.input = { [weak self] in self?.searchField.stringValue ?? "" }
|
||||||
tool.focusSearch = { [weak self] in self?.window?.makeFirstResponder(self?.searchField) }
|
tool.focusSearch = { [weak self] in self?.window?.makeFirstResponder(self?.searchField) }
|
||||||
tool.copied = { [weak self] in self?.dismiss() }
|
tool.copied = { [weak self] in self?.dismiss() }
|
||||||
|
tool.pasteRequested = { [weak self] in self?.pasteEmoji() }
|
||||||
controller = tool
|
controller = tool
|
||||||
placeholder = "Search emoji by name or :shortcode:…"
|
placeholder = "Search emoji by name or :shortcode:…"
|
||||||
accessibilityLabel = "Search emoji"
|
accessibilityLabel = "Search emoji"
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
- [x] conjure should pop up quicker when launching than it currently does.
|
|
||||||
- [ ] conjure emoji selector should have an extra action (default) that both copies and tries to paste the selected emoji into the application that was focused when the conjure window was spawned.
|
|
||||||
- [ ] add a notes app inspired by raycast notes
|
- [ ] add a notes app inspired by raycast notes
|
||||||
- [ ] improve on the conjure translation command
|
- [ ] improve on the conjure translation command
|
||||||
@@ -14,27 +14,36 @@ import Testing
|
|||||||
}
|
}
|
||||||
var query = "face"
|
var query = "face"
|
||||||
var closed = false
|
var closed = false
|
||||||
|
var pasteRequests = 0
|
||||||
let picker = EmojiPickerController(defaults: defaults, pasteboard: pasteboard)
|
let picker = EmojiPickerController(defaults: defaults, pasteboard: pasteboard)
|
||||||
picker.input = { query }
|
picker.input = { query }
|
||||||
picker.closed = { closed = true }
|
picker.closed = { closed = true }
|
||||||
|
picker.pasteRequested = {
|
||||||
|
#expect(closed)
|
||||||
|
#expect(pasteboard.string(forType: .string) == EmojiCatalog.search(query)[1].value)
|
||||||
|
pasteRequests += 1
|
||||||
|
}
|
||||||
_ = picker.view
|
_ = picker.view
|
||||||
let matches = EmojiCatalog.search(query)
|
let matches = EmojiCatalog.search(query)
|
||||||
#expect(picker.handle(#selector(NSResponder.moveRight(_:))))
|
#expect(picker.handle(#selector(NSResponder.moveRight(_:))))
|
||||||
#expect(picker.handle(#selector(NSResponder.insertNewline(_:))))
|
#expect(picker.handle(#selector(NSResponder.insertNewline(_:))))
|
||||||
#expect(pasteboard.string(forType: .string) == matches[1].value)
|
#expect(pasteboard.string(forType: .string) == matches[1].value)
|
||||||
#expect(closed)
|
#expect(closed)
|
||||||
|
#expect(pasteRequests == 1)
|
||||||
#expect(defaults.stringArray(forKey: "recentEmoji") == [matches[1].value])
|
#expect(defaults.stringArray(forKey: "recentEmoji") == [matches[1].value])
|
||||||
|
|
||||||
closed = false
|
closed = false
|
||||||
query = "no-such-emoji"
|
query = "no-such-emoji"
|
||||||
picker.inputChanged()
|
picker.inputChanged()
|
||||||
picker.copySelection()
|
picker.copyAndPasteSelection()
|
||||||
#expect(!closed)
|
#expect(!closed)
|
||||||
#expect(pasteboard.string(forType: .string) == matches[1].value)
|
#expect(pasteboard.string(forType: .string) == matches[1].value)
|
||||||
|
#expect(pasteRequests == 1)
|
||||||
query = "👩💻"
|
query = "👩💻"
|
||||||
picker.inputChanged()
|
picker.inputChanged()
|
||||||
picker.copySelection()
|
picker.copySelection()
|
||||||
#expect(pasteboard.string(forType: .string) == "👩💻")
|
#expect(pasteboard.string(forType: .string) == "👩💻")
|
||||||
|
#expect(pasteRequests == 1)
|
||||||
|
|
||||||
let reopened = EmojiPickerController(defaults: defaults, pasteboard: pasteboard)
|
let reopened = EmojiPickerController(defaults: defaults, pasteboard: pasteboard)
|
||||||
_ = reopened.view
|
_ = reopened.view
|
||||||
@@ -43,6 +52,39 @@ import Testing
|
|||||||
#expect(defaults.stringArray(forKey: "recentEmoji") == ["👩💻", matches[1].value])
|
#expect(defaults.stringArray(forKey: "recentEmoji") == ["👩💻", matches[1].value])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test @MainActor func emojiActionsDefaultToPasteAndKeepCopyOnlyAvailable() throws {
|
||||||
|
_ = NSApplication.shared
|
||||||
|
let suite = "ConjureEmojiActions.\(UUID().uuidString)"
|
||||||
|
let defaults = UserDefaults(suiteName: suite)!
|
||||||
|
let pasteboard = NSPasteboard.withUniqueName()
|
||||||
|
defer { defaults.removePersistentDomain(forName: suite); pasteboard.releaseGlobally() }
|
||||||
|
let picker = EmojiPickerController(defaults: defaults, pasteboard: pasteboard)
|
||||||
|
picker.input = { "👩💻" }
|
||||||
|
var pasteRequests = 0
|
||||||
|
picker.pasteRequested = { pasteRequests += 1 }
|
||||||
|
let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 750, height: 400),
|
||||||
|
styleMask: [.borderless], backing: .buffered, defer: false)
|
||||||
|
window.contentView = picker.view
|
||||||
|
picker.showActions()
|
||||||
|
#expect(picker.handle(#selector(NSResponder.insertNewline(_:))))
|
||||||
|
#expect(pasteboard.string(forType: .string) == "👩💻")
|
||||||
|
#expect(pasteRequests == 1)
|
||||||
|
|
||||||
|
pasteboard.clearContents()
|
||||||
|
picker.showActions()
|
||||||
|
#expect(picker.handle(#selector(NSResponder.moveDown(_:))))
|
||||||
|
#expect(picker.handle(#selector(NSResponder.insertNewline(_:))))
|
||||||
|
#expect(pasteboard.string(forType: .string) == "👩💻")
|
||||||
|
#expect(pasteRequests == 1)
|
||||||
|
|
||||||
|
picker.showActions()
|
||||||
|
#expect(picker.handle(#selector(NSResponder.moveDown(_:))))
|
||||||
|
#expect(picker.handle(#selector(NSResponder.moveDown(_:))))
|
||||||
|
#expect(picker.handle(#selector(NSResponder.insertNewline(_:))))
|
||||||
|
#expect(defaults.stringArray(forKey: "favoriteEmoji") == ["👩💻"])
|
||||||
|
#expect(pasteRequests == 1)
|
||||||
|
}
|
||||||
|
|
||||||
@Test @MainActor func emojiCommandRestoresLauncherSearchOnEscape() throws {
|
@Test @MainActor func emojiCommandRestoresLauncherSearchOnEscape() throws {
|
||||||
_ = NSApplication.shared
|
_ = NSApplication.shared
|
||||||
let suite = "ConjureEmojiCommand.\(UUID().uuidString)"
|
let suite = "ConjureEmojiCommand.\(UUID().uuidString)"
|
||||||
|
|||||||
Reference in New Issue
Block a user