Skip to content
Merged
Changes from all 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
58 changes: 56 additions & 2 deletions cmuxTests/WorkspaceTerminalFocusRecoveryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -316,13 +316,21 @@ struct WorkspaceTerminalFocusRecoverySwiftTests {

window.makeFirstResponder(nil)
panel.surface.setFocus(false)
// Only a hidden-to-visible transition schedules the automatic apply. The panel can be
// visible again after setup, which would turn the reveal below into a no-op.
panel.hostedView.setVisibleInUI(false)
surfaceView.frame = NSRect(x: 0, y: 0, width: 0, height: 0)
try #require(!panel.hostedView.debugPortalVisibleInUI, "The reveal below must start from a hidden panel")

panel.hostedView.setVisibleInUI(true)
try #require(panel.hostedView.debugPortalVisibleInUI, "Portal authority should let the selected workspace's panel reveal")
await AppKitTestEventPump().drain()

_ = await AppKitTestEventPump().waitUntil { panel.hostedView.isSurfaceViewFirstResponder() }
#expect(panel.hostedView.isSurfaceViewFirstResponder())
#expect(
panel.hostedView.isSurfaceViewFirstResponder(),
"First responder after the reveal: \(String(describing: window.firstResponder))"
)
#expect(panel.hostedView.debugRenderStats().desiredFocus)
#expect(
!panel.surface.debugDesiredFocusState(),
Expand Down Expand Up @@ -402,15 +410,47 @@ struct WorkspaceTerminalFocusRecoverySwiftTests {

let surfaceView = try #require(findSurfaceView(in: panel.hostedView), "Expected terminal surface view")

// Put the panel on screen so the find overlay mounts its search field.
panel.hostedView.setVisibleInUI(true)
var mountedSearchField: NSTextField?
let searchFieldMounted = await AppKitTestEventPump().waitUntil(timeout: .seconds(3)) {
mountedSearchField = findMountedSearchField(in: panel.hostedView)
return mountedSearchField != nil
}
try #require(searchFieldMounted, "Expected the find overlay to mount its search field")
let searchField = try #require(mountedSearchField)
// Re-applying the mounted state bumps the overlay generation, which cancels the
// mount's remaining forced field-focus retries.
panel.hostedView.setSearchOverlay(searchState: searchState)
// The overlay's focus binding starts out set, and only the field's end-editing callback
// clears it. Let the field hold focus once and then resign it, so the overlay has no
// pending claim on focus when the panel reveals.
if !cmuxTextFieldIsFirstResponder(searchField, in: window) {
window.makeFirstResponder(searchField)
}
await AppKitTestEventPump().drain()
try #require(cmuxTextFieldIsFirstResponder(searchField, in: window), "Expected the find field to take focus")

window.makeFirstResponder(nil)
// The restore under test: the terminal, not the find field, is the panel's focus intent.
panel.hostedView.preparePanelFocusIntentForActivation(.surface)
panel.surface.setFocus(false)
await AppKitTestEventPump().drain()

// Only a hidden-to-visible transition schedules the automatic apply.
panel.hostedView.setVisibleInUI(false)
surfaceView.frame = NSRect(x: 0, y: 0, width: 0, height: 0)
try #require(!panel.hostedView.debugPortalVisibleInUI, "The reveal below must start from a hidden panel")

panel.hostedView.setVisibleInUI(true)
try #require(panel.hostedView.debugPortalVisibleInUI, "Portal authority should let the selected workspace's panel reveal")
await AppKitTestEventPump().drain()

_ = await AppKitTestEventPump().waitUntil { panel.hostedView.isSurfaceViewFirstResponder() }
#expect(panel.hostedView.isSurfaceViewFirstResponder())
#expect(
panel.hostedView.isSurfaceViewFirstResponder(),
"First responder after the reveal: \(String(describing: window.firstResponder))"
)
#expect(
!panel.surface.debugDesiredFocusState(),
"Find terminal restore must not drop hidden/tiny focus recovery before Ghostty focus is reapplied"
Expand Down Expand Up @@ -566,4 +606,18 @@ struct WorkspaceTerminalFocusRecoverySwiftTests {
}
return nil
}

#if DEBUG
private func findMountedSearchField(in hostedView: GhosttySurfaceScrollView) -> NSTextField? {
guard let overlay = hostedView.debugSearchOverlayHostingViewForTesting() else { return nil }
var stack: [NSView] = [overlay]
while let current = stack.popLast() {
if let field = current as? NSTextField, field.isEditable {
return field
}
stack.append(contentsOf: current.subviews)
}
return nil
}
#endif
}
Loading