Add regression test for wheel scrollback follow bug

This commit is contained in:
Lawrence Chen 2026-03-22 18:11:00 -07:00
parent e8cf83ca57
commit 7634abe616
No known key found for this signature in database

View file

@ -1590,6 +1590,30 @@ final class GhosttySurfaceOverlayTests: XCTestCase {
}
}
private final class ScrollbarPostingSurfaceView: GhosttyNSView {
var nextScrollbar: GhosttyScrollbar?
override func scrollWheel(with event: NSEvent) {
super.scrollWheel(with: event)
guard let nextScrollbar else { return }
NotificationCenter.default.post(
name: .ghosttyDidUpdateScrollbar,
object: self,
userInfo: [GhosttyNotificationKey.scrollbar: nextScrollbar]
)
}
}
private func makeScrollbar(total: UInt64, offset: UInt64, len: UInt64) -> GhosttyScrollbar {
GhosttyScrollbar(
c: ghostty_action_scrollbar_s(
total: total,
offset: offset,
len: len
)
)
}
private func findEditableTextField(in view: NSView) -> NSTextField? {
if let field = view as? NSTextField, field.isEditable {
return field
@ -1675,6 +1699,79 @@ final class GhosttySurfaceOverlayTests: XCTestCase {
)
}
func testExplicitWheelScrollKeepsScrollbackPinnedAgainstLaterBottomPacket() {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 360, height: 240),
styleMask: [.titled, .closable],
backing: .buffered,
defer: false
)
defer { window.orderOut(nil) }
guard let contentView = window.contentView else {
XCTFail("Expected content view")
return
}
let surfaceView = ScrollbarPostingSurfaceView(frame: NSRect(x: 0, y: 0, width: 160, height: 120))
surfaceView.cellSize = CGSize(width: 10, height: 10)
let hostedView = GhosttySurfaceScrollView(surfaceView: surfaceView)
hostedView.frame = contentView.bounds
hostedView.autoresizingMask = [.width, .height]
contentView.addSubview(hostedView)
window.makeKeyAndOrderFront(nil)
window.displayIfNeeded()
contentView.layoutSubtreeIfNeeded()
hostedView.layoutSubtreeIfNeeded()
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
guard let scrollView = hostedView.subviews.first(where: { $0 is NSScrollView }) as? NSScrollView else {
XCTFail("Expected hosted terminal scroll view")
return
}
NotificationCenter.default.post(
name: .ghosttyDidUpdateScrollbar,
object: surfaceView,
userInfo: [GhosttyNotificationKey.scrollbar: makeScrollbar(total: 100, offset: 90, len: 10)]
)
RunLoop.current.run(until: Date().addingTimeInterval(0.01))
XCTAssertEqual(scrollView.contentView.bounds.origin.y, 0, accuracy: 0.01)
surfaceView.nextScrollbar = makeScrollbar(total: 100, offset: 40, len: 10)
guard let cgEvent = CGEvent(
scrollWheelEvent2Source: nil,
units: .pixel,
wheelCount: 2,
wheel1: 0,
wheel2: -12,
wheel3: 0
), let scrollEvent = NSEvent(cgEvent: cgEvent) else {
XCTFail("Expected scroll wheel event")
return
}
scrollView.scrollWheel(with: scrollEvent)
RunLoop.current.run(until: Date().addingTimeInterval(0.01))
XCTAssertEqual(scrollView.contentView.bounds.origin.y, 500, accuracy: 0.01)
NotificationCenter.default.post(
name: .ghosttyDidUpdateScrollbar,
object: surfaceView,
userInfo: [GhosttyNotificationKey.scrollbar: makeScrollbar(total: 100, offset: 90, len: 10)]
)
RunLoop.current.run(until: Date().addingTimeInterval(0.01))
XCTAssertEqual(
scrollView.contentView.bounds.origin.y,
500,
accuracy: 0.01,
"A passive bottom packet should not yank the viewport after an explicit wheel scroll into scrollback"
)
}
func testInactiveOverlayVisibilityTracksRequestedState() {
let hostedView = GhosttySurfaceScrollView(
surfaceView: GhosttyNSView(frame: NSRect(x: 0, y: 0, width: 80, height: 50))