diff --git a/macos/Tests/Helpers/TransferablePasteboardTests.swift b/macos/Tests/Helpers/TransferablePasteboardTests.swift new file mode 100644 index 000000000..055dd5785 --- /dev/null +++ b/macos/Tests/Helpers/TransferablePasteboardTests.swift @@ -0,0 +1,124 @@ +import Testing +import AppKit +import CoreTransferable +import UniformTypeIdentifiers +@testable import Ghostty + +struct TransferablePasteboardTests { + // MARK: - Test Helpers + + /// A simple Transferable type for testing pasteboard conversion. + private struct DummyTransferable: Transferable, Equatable { + let payload: String + + static var transferRepresentation: some TransferRepresentation { + DataRepresentation(contentType: .utf8PlainText) { value in + value.payload.data(using: .utf8)! + } importing: { data in + let string = String(data: data, encoding: .utf8)! + return DummyTransferable(payload: string) + } + } + } + + /// A Transferable type that registers multiple content types. + private struct MultiTypeTransferable: Transferable { + let text: String + + static var transferRepresentation: some TransferRepresentation { + DataRepresentation(contentType: .utf8PlainText) { value in + value.text.data(using: .utf8)! + } importing: { data in + MultiTypeTransferable(text: String(data: data, encoding: .utf8)!) + } + DataRepresentation(contentType: .plainText) { value in + value.text.data(using: .utf8)! + } importing: { data in + MultiTypeTransferable(text: String(data: data, encoding: .utf8)!) + } + } + } + + // MARK: - Basic Functionality + + @Test func pasteboardItemIsCreated() { + let transferable = DummyTransferable(payload: "hello") + let item = transferable.pasteboardItem() + #expect(item != nil) + } + + @Test func pasteboardItemContainsExpectedType() { + let transferable = DummyTransferable(payload: "hello") + guard let item = transferable.pasteboardItem() else { + Issue.record("Expected pasteboard item to be created") + return + } + + let expectedType = NSPasteboard.PasteboardType(UTType.utf8PlainText.identifier) + #expect(item.types.contains(expectedType)) + } + + @Test func pasteboardItemProvidesCorrectData() { + let transferable = DummyTransferable(payload: "test data") + guard let item = transferable.pasteboardItem() else { + Issue.record("Expected pasteboard item to be created") + return + } + + let pasteboardType = NSPasteboard.PasteboardType(UTType.utf8PlainText.identifier) + + // Write to a pasteboard to trigger data provider + let pasteboard = NSPasteboard(name: .init("test-\(UUID().uuidString)")) + pasteboard.clearContents() + pasteboard.writeObjects([item]) + + // Read back the data + guard let data = pasteboard.data(forType: pasteboardType) else { + Issue.record("Expected data to be available on pasteboard") + return + } + + let string = String(data: data, encoding: .utf8) + #expect(string == "test data") + } + + // MARK: - Multiple Content Types + + @Test func multipleTypesAreRegistered() { + let transferable = MultiTypeTransferable(text: "multi") + guard let item = transferable.pasteboardItem() else { + Issue.record("Expected pasteboard item to be created") + return + } + + let utf8Type = NSPasteboard.PasteboardType(UTType.utf8PlainText.identifier) + let plainType = NSPasteboard.PasteboardType(UTType.plainText.identifier) + + #expect(item.types.contains(utf8Type)) + #expect(item.types.contains(plainType)) + } + + @Test func multipleTypesProvideCorrectData() { + let transferable = MultiTypeTransferable(text: "shared content") + guard let item = transferable.pasteboardItem() else { + Issue.record("Expected pasteboard item to be created") + return + } + + let pasteboard = NSPasteboard(name: .init("test-\(UUID().uuidString)")) + pasteboard.clearContents() + pasteboard.writeObjects([item]) + + // Both types should provide the same content + let utf8Type = NSPasteboard.PasteboardType(UTType.utf8PlainText.identifier) + let plainType = NSPasteboard.PasteboardType(UTType.plainText.identifier) + + if let utf8Data = pasteboard.data(forType: utf8Type) { + #expect(String(data: utf8Data, encoding: .utf8) == "shared content") + } + + if let plainData = pasteboard.data(forType: plainType) { + #expect(String(data: plainData, encoding: .utf8) == "shared content") + } + } +} diff --git a/macos/Tests/Splits/TerminalSplitDropZoneTests.swift b/macos/Tests/Splits/TerminalSplitDropZoneTests.swift new file mode 100644 index 000000000..5c956fcc8 --- /dev/null +++ b/macos/Tests/Splits/TerminalSplitDropZoneTests.swift @@ -0,0 +1,128 @@ +import Testing +import Foundation +@testable import Ghostty + +struct TerminalSplitDropZoneTests { + private let standardSize = CGSize(width: 100, height: 100) + + // MARK: - Basic Edge Detection + + @Test func topEdge() { + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 50, y: 5), in: standardSize) + #expect(zone == .top) + } + + @Test func bottomEdge() { + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 50, y: 95), in: standardSize) + #expect(zone == .bottom) + } + + @Test func leftEdge() { + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 5, y: 50), in: standardSize) + #expect(zone == .left) + } + + @Test func rightEdge() { + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 95, y: 50), in: standardSize) + #expect(zone == .right) + } + + // MARK: - Corner Tie-Breaking + // When distances are equal, the check order determines the result: + // left -> right -> top -> bottom + + @Test func topLeftCornerSelectsLeft() { + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 0, y: 0), in: standardSize) + #expect(zone == .left) + } + + @Test func topRightCornerSelectsRight() { + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 100, y: 0), in: standardSize) + #expect(zone == .right) + } + + @Test func bottomLeftCornerSelectsLeft() { + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 0, y: 100), in: standardSize) + #expect(zone == .left) + } + + @Test func bottomRightCornerSelectsRight() { + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 100, y: 100), in: standardSize) + #expect(zone == .right) + } + + // MARK: - Center Point (All Distances Equal) + + @Test func centerSelectsLeft() { + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 50, y: 50), in: standardSize) + #expect(zone == .left) + } + + // MARK: - Non-Square Aspect Ratio + + @Test func rectangularViewTopEdge() { + let size = CGSize(width: 200, height: 100) + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 100, y: 10), in: size) + #expect(zone == .top) + } + + @Test func rectangularViewLeftEdge() { + let size = CGSize(width: 200, height: 100) + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 10, y: 50), in: size) + #expect(zone == .left) + } + + @Test func tallRectangleTopEdge() { + let size = CGSize(width: 100, height: 200) + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 50, y: 10), in: size) + #expect(zone == .top) + } + + // MARK: - Out-of-Bounds Points + + @Test func pointLeftOfViewSelectsLeft() { + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: -10, y: 50), in: standardSize) + #expect(zone == .left) + } + + @Test func pointAboveViewSelectsTop() { + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 50, y: -10), in: standardSize) + #expect(zone == .top) + } + + @Test func pointRightOfViewSelectsRight() { + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 110, y: 50), in: standardSize) + #expect(zone == .right) + } + + @Test func pointBelowViewSelectsBottom() { + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 50, y: 110), in: standardSize) + #expect(zone == .bottom) + } + + // MARK: - Diagonal Regions (Triangular Zones) + + @Test func upperLeftTriangleSelectsLeft() { + // Point in the upper-left triangle, closer to left than top + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 20, y: 30), in: standardSize) + #expect(zone == .left) + } + + @Test func upperRightTriangleSelectsRight() { + // Point in the upper-right triangle, closer to right than top + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 80, y: 30), in: standardSize) + #expect(zone == .right) + } + + @Test func lowerLeftTriangleSelectsLeft() { + // Point in the lower-left triangle, closer to left than bottom + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 20, y: 70), in: standardSize) + #expect(zone == .left) + } + + @Test func lowerRightTriangleSelectsRight() { + // Point in the lower-right triangle, closer to right than bottom + let zone = TerminalSplitDropZone.calculate(at: CGPoint(x: 80, y: 70), in: standardSize) + #expect(zone == .right) + } +}