macos: add tests for NSPasteboard.getOpinionatedStringContents

This commit is contained in:
Claude Fable 5
2026-07-03 10:34:21 +02:00
committed by Lukas
parent bb7cd85b2e
commit acd09c0a6c

View File

@@ -2,7 +2,8 @@
// NSPasteboardTests.swift
// GhosttyTests
//
// Tests for NSPasteboard.PasteboardType MIME type conversion.
// Tests for NSPasteboard.PasteboardType MIME type conversion and
// NSPasteboard.getOpinionatedStringContents.
//
import Testing
@@ -31,3 +32,224 @@ struct NSPasteboardTypeExtensionTests {
#expect(pasteboardType == .png)
}
}
/// Tests for `NSPasteboard.getOpinionatedStringContents`, which per its documented
/// semantics must, for each pasteboard item:
/// - prefer the absolute filesystem path of a file URL, shell-escaped,
/// - otherwise fall back to any plain string on the item,
/// and return nil when nothing usable is found. Multiple results join with a space.
struct NSPasteboardOpinionatedContentsTests {
// MARK: - Test Helpers
/// Creates a uniquely-named pasteboard so tests never touch the user's
/// general pasteboard and can run concurrently.
private func makePasteboard() -> NSPasteboard {
let pasteboard = NSPasteboard(name: .init("test-\(UUID().uuidString)"))
pasteboard.clearContents()
return pasteboard
}
/// Builds an item carrying a plain string (public.utf8-plain-text).
private func stringItem(_ string: String) -> NSPasteboardItem {
let item = NSPasteboardItem()
item.setString(string, forType: .string)
return item
}
/// Builds an item carrying a file URL (public.file-url). The string stored on
/// the pasteboard is the URL string form, e.g. "file:///Users/test%20file.txt",
/// which is exactly what AppKit registers when a file URL is copied.
private func fileURLItem(_ urlString: String) -> NSPasteboardItem {
let item = NSPasteboardItem()
item.setString(urlString, forType: .fileURL)
return item
}
// MARK: - Plain String Contents
@Test func testSingleStringItem() {
let pasteboard = makePasteboard()
pasteboard.writeObjects([stringItem("hello world")])
#expect(pasteboard.getOpinionatedStringContents() == "hello world")
}
@Test func testMultipleStringItemsJoinedWithSpace() {
let pasteboard = makePasteboard()
pasteboard.writeObjects([stringItem("first"), stringItem("second")])
#expect(pasteboard.getOpinionatedStringContents() == "first second")
}
/// A remote URL that is present as plain text is returned verbatim, not
/// treated as a file.
@Test func testStringContainingRemoteURL() {
let pasteboard = makePasteboard()
pasteboard.writeObjects([stringItem("https://example.com/page")])
#expect(pasteboard.getOpinionatedStringContents() == "https://example.com/page")
}
// MARK: - File URL Contents
/// A file URL must produce the absolute filesystem path, not the URL string.
@Test func testSingleFileURL() {
let pasteboard = makePasteboard()
pasteboard.writeObjects([fileURLItem("file:///Users/test/document.txt")])
#expect(pasteboard.getOpinionatedStringContents() == "/Users/test/document.txt")
}
/// Percent-encoded characters must be decoded to the real path, and
/// shell-sensitive characters escaped for insertion into a terminal buffer.
@Test func testFileURLWithCharactersNeedingEscaping() {
let pasteboard = makePasteboard()
pasteboard.writeObjects([fileURLItem("file:///Users/test/my%20file%20(1).txt")])
#expect(pasteboard.getOpinionatedStringContents() == #"/Users/test/my\ file\ \(1\).txt"#)
}
@Test func testMultipleFileURLsJoinedWithSpace() {
let pasteboard = makePasteboard()
pasteboard.writeObjects([
fileURLItem("file:///Users/test/a.txt"),
fileURLItem("file:///Users/test/b.txt"),
])
#expect(pasteboard.getOpinionatedStringContents() == "/Users/test/a.txt /Users/test/b.txt")
}
/// When an item carries both a file URL and a string, the file path wins.
@Test func testFileURLTakesPrecedenceOverString() {
let pasteboard = makePasteboard()
let item = NSPasteboardItem()
item.setString("file:///Users/test/document.txt", forType: .fileURL)
item.setString("document.txt", forType: .string)
pasteboard.writeObjects([item])
#expect(pasteboard.getOpinionatedStringContents() == "/Users/test/document.txt")
}
@Test func testMixedFileURLAndStringItems() {
let pasteboard = makePasteboard()
pasteboard.writeObjects([
fileURLItem("file:///Users/test/a.txt"),
stringItem("plain text"),
])
#expect(pasteboard.getOpinionatedStringContents() == "/Users/test/a.txt plain text")
}
/// A mailto URL present as plain text is returned verbatim, like any string.
@Test func testMailtoStringReturnedVerbatim() {
let pasteboard = makePasteboard()
pasteboard.writeObjects([stringItem("mailto:exam@ple.com")])
#expect(pasteboard.getOpinionatedStringContents() == "mailto:exam@ple.com")
}
/// A non-file URL stored under the file-URL type must not be treated as a
/// filesystem path (its path would be empty); the string fallback wins.
@Test func testMailtoUnderFileURLTypeFallsBackToString() {
let pasteboard = makePasteboard()
let item = NSPasteboardItem()
item.setString("mailto:exam@ple.com", forType: .fileURL)
item.setString("exam@ple.com", forType: .string)
pasteboard.writeObjects([item])
#expect(pasteboard.getOpinionatedStringContents() == "exam@ple.com")
}
// MARK: - Remote File Promises
/// Builds an item mimicking a remote-file drag (e.g. Panic Transmit/Nova):
/// file-promise metadata plus a remote public.url, but no public.file-url.
private func remoteFilePromiseItem(url urlString: String, string: String?) -> NSPasteboardItem {
let item = NSPasteboardItem()
item.setString("file.txt", forType: .init("com.apple.pasteboard.promised-file-name"))
item.setString("public.data", forType: .init("com.apple.pasteboard.promised-file-content-type"))
item.setData(Data([0x00]), forType: .init("com.apple.NSFilePromiseItemMetaData"))
item.setData(Data([0x00]), forType: .init("com.apple.pasteboard.NSFilePromiseID"))
item.setString(urlString, forType: .init("public.url"))
if let string {
item.setString(string, forType: .string)
}
return item
}
/// A remote file promise has no local filesystem path, so the item's plain
/// string is returned as-is: no file-path treatment, no shell escaping.
@Test func testRemoteFilePromiseFallsBackToString() {
let pasteboard = makePasteboard()
pasteboard.writeObjects([
remoteFilePromiseItem(
url: "sftp://example.com/remote%20dir/file.txt",
string: "sftp://example.com/remote%20dir/file.txt"
),
])
#expect(pasteboard.getOpinionatedStringContents() == "sftp://example.com/remote%20dir/file.txt")
}
@Test func testMultipleRemoteFilePromisesJoinedWithSpace() {
let pasteboard = makePasteboard()
pasteboard.writeObjects([
remoteFilePromiseItem(url: "sftp://example.com/a.txt", string: "sftp://example.com/a.txt"),
remoteFilePromiseItem(url: "sftp://example.com/b.txt", string: "sftp://example.com/b.txt"),
])
#expect(pasteboard.getOpinionatedStringContents() == "sftp://example.com/a.txt sftp://example.com/b.txt")
}
/// A promise item that offers no plain string (only promise metadata and a
/// remote URL) contributes nothing.
@Test func testRemoteFilePromiseWithoutStringReturnsNil() {
let pasteboard = makePasteboard()
pasteboard.writeObjects([
remoteFilePromiseItem(url: "sftp://example.com/file.txt", string: nil),
])
#expect(pasteboard.getOpinionatedStringContents() == nil)
}
/// A local file drag next to a remote promise: the local item yields its
/// escaped path, the remote one its string.
@Test func testMixedLocalFileAndRemoteFilePromise() {
let pasteboard = makePasteboard()
pasteboard.writeObjects([
fileURLItem("file:///Users/test/local.txt"),
remoteFilePromiseItem(url: "sftp://example.com/remote.txt", string: "sftp://example.com/remote.txt"),
])
#expect(pasteboard.getOpinionatedStringContents() == "/Users/test/local.txt sftp://example.com/remote.txt")
}
// MARK: - Nothing Usable
@Test func testEmptyPasteboardReturnsNil() {
let pasteboard = makePasteboard()
#expect(pasteboard.getOpinionatedStringContents() == nil)
}
/// An item with only a binary type has no string or file path to offer.
@Test func testNonStringItemReturnsNil() {
let pasteboard = makePasteboard()
let item = NSPasteboardItem()
item.setData(Data([0x89, 0x50, 0x4e, 0x47]), forType: .png)
pasteboard.writeObjects([item])
#expect(pasteboard.getOpinionatedStringContents() == nil)
}
/// A remote URL item (public.url, no file URL and no string rep) is dropped:
/// only file URLs are read from the clipboard.
@Test func testRemoteURLOnlyItemReturnsNil() {
let pasteboard = makePasteboard()
let item = NSPasteboardItem()
item.setString("https://example.com/page", forType: .init("public.url"))
pasteboard.writeObjects([item])
#expect(pasteboard.getOpinionatedStringContents() == nil)
}
}