Files
vphone-cli/VPhoneKit/VPhoneCoreKitTests/Support/ClipboardTransferTests.swift
T
LakrandClaude Opus 5.5 8682c98aaa Sync the clipboard on ⌘C, ⌘X and ⌘V, and touch the drawn display
The Edit menu's Copy, Cut and Paste now reach the VM view, which carries
the clipboard through vphoned: paste sends a changed Mac clipboard to the
guest before the guest pastes, and copy and cut bring the guest clipboard
back once the guest has written it. Only small text and images are
synced (VPhoneClipboardTransfer); anything else stays on its own side.
Without a connected agent the items are disabled and the keys reach the
guest unchanged.

capturesSystemKeys makes VZVirtualMachineView swallow every key in a
local event monitor it re-adds on each focus change, so a monitor of ours
always ran after it. VPhoneApplication asks the main menu in
sendEvent(_:), which runs before any local monitor, and drops the key-up
of a key a menu item took.

A full-screen VM letterboxes the guest display, so touches measured
against the view mapped the bars onto the guest screen. They are now
measured against the drawn display (VPhoneDisplayGeometry): points on a
bar clamp to the nearest edge and count as near it for edge swipes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:22:00 +09:00

71 lines
3.1 KiB
Swift

import Foundation
import Testing
@testable import VPhoneCoreKit
struct ClipboardTransferTests {
// MARK: - Mac to Guest
@Test func `copied text sends its text`() {
let types = ["public.utf8-plain-text", "public.rtf", "public.html"]
#expect(VPhoneClipboardTransfer.macKind(types: types) == .text)
}
@Test func `a screenshot sends its image`() {
#expect(VPhoneClipboardTransfer.macKind(types: ["public.png"]) == .image(type: "public.png"))
#expect(VPhoneClipboardTransfer.macKind(types: ["public.tiff"]) == .image(type: "public.tiff"))
}
@Test func `the owner's first type wins`() {
#expect(
VPhoneClipboardTransfer.macKind(types: ["public.tiff", "public.utf8-plain-text"])
== .image(type: "public.tiff"),
)
#expect(VPhoneClipboardTransfer.macKind(types: ["public.utf8-plain-text", "public.png"]) == .text)
}
@Test func `a copied file sends its name as text`() {
let types = ["public.file-url", "com.apple.finder.node", "public.utf8-plain-text"]
#expect(VPhoneClipboardTransfer.macKind(types: types) == .text)
}
@Test func `nothing syncable sends nothing`() {
#expect(VPhoneClipboardTransfer.macKind(types: []) == nil)
#expect(VPhoneClipboardTransfer.macKind(types: ["public.file-url", "public.rtf", "com.adobe.pdf"]) == nil)
}
@Test func `TIFF is converted before it reaches the guest`() {
#expect(!VPhoneClipboardTransfer.guestImageTypes.contains("public.tiff"))
#expect(VPhoneClipboardTransfer.macImageTypes.isSuperset(of: VPhoneClipboardTransfer.guestImageTypes))
}
// MARK: - Guest to Mac
@Test func `guest text wins over a guest image`() {
#expect(VPhoneClipboardTransfer.guestKind(text: "hello", hasImage: true) == .text)
#expect(VPhoneClipboardTransfer.guestKind(text: "", hasImage: true) == .image(type: "public.png"))
#expect(VPhoneClipboardTransfer.guestKind(text: nil, hasImage: true) == .image(type: "public.png"))
}
@Test func `an empty guest clipboard brings nothing back`() {
#expect(VPhoneClipboardTransfer.guestKind(text: "", hasImage: false) == nil)
#expect(VPhoneClipboardTransfer.guestKind(text: nil, hasImage: false) == nil)
}
// MARK: - Size
@Test func `text is limited by its UTF-8 size`() {
let limit = VPhoneClipboardTransfer.maxTextBytes
#expect(VPhoneClipboardTransfer.fits(text: String(repeating: "a", count: limit)))
#expect(!VPhoneClipboardTransfer.fits(text: String(repeating: "a", count: limit + 1)))
// Three bytes each, so fewer characters than the limit still exceed it.
#expect(!VPhoneClipboardTransfer.fits(text: String(repeating: "界", count: limit / 3 + 1)))
}
@Test func `images are limited and must not be empty`() {
let limit = VPhoneClipboardTransfer.maxImageBytes
#expect(VPhoneClipboardTransfer.fits(image: Data(count: limit)))
#expect(!VPhoneClipboardTransfer.fits(image: Data(count: limit + 1)))
#expect(!VPhoneClipboardTransfer.fits(image: Data()))
}
}