Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions Sources/Bonsplit/Internal/Views/TabBarDropDestinationNSView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,24 @@ final class TabBarDropDestinationNSView: NSView {
guard hasLocalTabDrag || hasSupportedPasteboardType else {
return false
}
guard let eventType else { return true }
// NSPasteboard(name: .drag) retains its advertised types after a drag
// ends. A passive hit test must therefore be owned by the live local
// tab-drag session; otherwise stale file/tab types can cover the tab
// hit regions and swallow ordinary hover/cursor events.
guard let eventType else { return hasLocalTabDrag }
switch eventType {
case .leftMouseUp:
// The drag pasteboard retains types after a drag ends. A normal click's
// mouse-up may only be captured while the controller owns a live session.
return hasLocalTabDrag
case .leftMouseDragged,
case .leftMouseDragged:
// External Finder and cross-process tab drags arrive through the
// AppKit drag event even though they have no local tab state.
return true
case .leftMouseUp,
.mouseMoved,
.cursorUpdate,
.periodic,
.appKitDefined,
.applicationDefined,
.systemDefined:
return true
return hasLocalTabDrag
default:
return false
}
Expand Down
6 changes: 3 additions & 3 deletions Tests/BonsplitTests/BonsplitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2583,13 +2583,13 @@ final class BonsplitTests: XCTestCase {
RunLoop.current.run(until: Date().addingTimeInterval(0.01))
} while Date() < registrationDeadline

guard dropDestination(at: trailingEmptyPoint) != nil else {
guard let resolvedTabPoint = tabPoint,
dropDestination(at: trailingEmptyPoint) != nil else {
throw XCTSkip(
"This SwiftUI runtime did not mount the native tab-bar drop destination"
"This SwiftUI runtime did not mount the native tab-bar drop destination or the pane tab hit region"
)
}

let resolvedTabPoint = try XCTUnwrap(tabPoint, "Expected a mounted pane tab hit region")
let tabPointInWindow = hostingView.convert(resolvedTabPoint, to: nil)
XCTAssertTrue(
BonsplitTabItemHitRegionRegistry.containsWindowPoint(tabPointInWindow, in: window),
Expand Down
73 changes: 73 additions & 0 deletions Tests/BonsplitTests/TabWideTitleDragTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import AppKit
import CoreGraphics
import Testing
@testable import Bonsplit

@Suite("Wide tab drag hit testing")
struct TabWideTitleDragTests {
private let resolver = TabDropIndexResolver()
private let bounds = CGRect(x: 0, y: 0, width: 1_400, height: 34)

@Test("A wide source tab keeps midpoint insertion boundaries stable")
func wideSourceTabUsesStableMidpoints() {
let tabFrames = [
CGRect(x: 12, y: 0, width: 84, height: 34),
// Models a long directory or agent-session title.
CGRect(x: 104, y: 0, width: 760, height: 34),
CGRect(x: 876, y: 0, width: 120, height: 34),
CGRect(x: 1_004, y: 0, width: 132, height: 34),
]
let probes: [(x: CGFloat, expectedIndex: Int)] = [
(20, 0),
(54, 1),
(120, 1),
(483.9, 1),
(484, 2),
(700, 2),
(875.9, 2),
(936, 3),
(1_200, 4),
]

for probe in probes {
let resolvedIndex = resolver.insertionIndex(
at: CGPoint(x: probe.x, y: bounds.midY),
in: bounds,
tabFrames: tabFrames
)
#expect(resolvedIndex == probe.expectedIndex)
}
}

@Test("Stale pasteboard types do not capture passive pointer events")
@MainActor
func stalePasteboardDoesNotCoverTabHitRegions() {
let staleTypes = [
TabBarDropHandler.tabTransferPasteboardType,
NSPasteboard.PasteboardType.fileURL,
]

#expect(!TabBarDropDestinationNSView.shouldCaptureHitTest(
eventType: .mouseMoved,
pasteboardTypes: staleTypes
))
#expect(!TabBarDropDestinationNSView.shouldCaptureHitTest(
eventType: .cursorUpdate,
pasteboardTypes: staleTypes
))
#expect(!TabBarDropDestinationNSView.shouldCaptureHitTest(
eventType: .leftMouseUp,
pasteboardTypes: staleTypes
))
#expect(TabBarDropDestinationNSView.shouldCaptureHitTest(
eventType: .leftMouseDragged,
pasteboardTypes: staleTypes
))
#expect(TabBarDropDestinationNSView.shouldCaptureHitTest(
eventType: .mouseMoved,
pasteboardTypes: staleTypes,
hasLocalTabDrag: true
))
}

}
Loading