Skip to content
Merged
Show file tree
Hide file tree
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
94 changes: 68 additions & 26 deletions cmuxTests/TerminalAndGhosttyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,48 @@ final class GhosttyPasteboardHelperTests: XCTestCase {

@MainActor
final class TerminalOffscreenStartupTests: XCTestCase {
private var trackedPanels: [TerminalPanel] = []
private var trackedManagers: [TabManager] = []

private func makeTrackedPanel(
workspaceId: UUID,
initialCommand: String? = nil,
initialInput: String? = nil
) -> TerminalPanel {
let panel = TerminalPanel(
workspaceId: workspaceId,
initialCommand: initialCommand,
initialInput: initialInput
)
trackedPanels.append(panel)
return panel
}

private func makeTrackedManager() -> TabManager {
track(TabManager())
}

private func track<Manager: TabManager>(_ manager: Manager) -> Manager {
trackedManagers.append(manager)
return manager
}

override func tearDown() {
// Offscreen startup tests intentionally create runtimes without a
// visible window. Dropping those panels lets Ghostty free the native
// surface asynchronously, and the next suite then waits through the
// SIGHUP grace period (or records a cross-suite pending-free issue).
for panel in trackedPanels.reversed() {
panel.surface.releaseHostedSurfaceForTesting()
}
trackedPanels.removeAll()
for manager in trackedManagers.reversed() {
manager.closeWorkspacesForTesting()
}
trackedManagers.removeAll()
super.tearDown()
}

#if DEBUG
private final class RecordingMobileTabManager: TabManager {
private(set) var scheduledMetadataRefreshes: [(workspaceId: UUID, panelId: UUID, reason: String)] = []
Expand All @@ -995,7 +1037,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {
#endif

func testPlainSurfaceDoesNotStartRuntimeBeforeWindowAttachmentOrInput() {
let panel = TerminalPanel(workspaceId: UUID())
let panel = makeTrackedPanel(workspaceId: UUID())

XCTAssertNil(panel.hostedView.window)
XCTAssertFalse(panel.surface.debugHasHeadlessStartupWindowForTesting())
Expand All @@ -1007,7 +1049,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {
}

func testPlainHostedViewWindowAttachmentCreatesRuntimeSurface() throws {
let panel = TerminalPanel(workspaceId: UUID())
let panel = makeTrackedPanel(workspaceId: UUID())
XCTAssertEqual(panel.hostedView.debugSurfaceId, panel.surface.id)
XCTAssertNil(panel.surface.surface)
XCTAssertFalse(panel.surface.debugHasHeadlessStartupWindowForTesting())
Expand Down Expand Up @@ -1058,7 +1100,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {
}

func testInitialInputSurfaceAttemptsRuntimeCreationBeforeWindowAttachment() {
let panel = TerminalPanel(
let panel = makeTrackedPanel(
workspaceId: UUID(),
initialInput: "echo resume\n"
)
Expand All @@ -1075,7 +1117,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {
}

func testInitialCommandSurfaceAttemptsRuntimeCreationBeforeWindowAttachment() {
let panel = TerminalPanel(
let panel = makeTrackedPanel(
workspaceId: UUID(),
initialCommand: "echo startup"
)
Expand All @@ -1092,7 +1134,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {
}

func testHeadlessStartupWindowDoesNotCountAsViewInWindowForHealth() {
let panel = TerminalPanel(
let panel = makeTrackedPanel(
workspaceId: UUID(),
initialCommand: "echo startup"
)
Expand All @@ -1110,7 +1152,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {

func testForceRefreshIgnoresHeadlessStartupWindow() throws {
#if DEBUG
let panel = TerminalPanel(
let panel = makeTrackedPanel(
workspaceId: UUID(),
initialCommand: "echo startup"
)
Expand All @@ -1132,7 +1174,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {
}

func testColdSocketInputQueuesInsteadOfDroppingWhenRuntimeSurfaceIsMissing() {
let panel = TerminalPanel(workspaceId: UUID())
let panel = makeTrackedPanel(workspaceId: UUID())

panel.surface.releaseHostedSurfaceForTesting()
XCTAssertNil(panel.surface.surface)
Expand All @@ -1148,7 +1190,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {
}

func testColdSocketInputRejectsOversizedQueueInsteadOfDroppingExistingInput() {
let panel = TerminalPanel(workspaceId: UUID())
let panel = makeTrackedPanel(workspaceId: UUID())

panel.surface.releaseHostedSurfaceForTesting()
XCTAssertTrue(panel.surface.sendInput("echo keep-me\n"))
Expand All @@ -1165,7 +1207,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {
}

func testColdSocketInputQueuesBackspaceControlCharacterAsKeyEvent() {
let panel = TerminalPanel(workspaceId: UUID())
let panel = makeTrackedPanel(workspaceId: UUID())

panel.surface.releaseHostedSurfaceForTesting()
XCTAssertTrue(panel.surface.sendInput("abc\u{08}"))
Expand All @@ -1179,7 +1221,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {
}

func testColdSocketInputQueuesReturnAsCommittedTextInputInsteadOfPasteOrKeyEvent() {
let panel = TerminalPanel(workspaceId: UUID())
let panel = makeTrackedPanel(workspaceId: UUID())

panel.surface.releaseHostedSurfaceForTesting()
XCTAssertTrue(panel.surface.sendInput("printf 'ok\\n'\n"))
Expand All @@ -1205,7 +1247,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {

/// Verifies OSC 11 is queued as terminal output bytes instead of literal shell input.
func testColdSocketInputQueuesOSC11AsRawTerminalBytes() {
let panel = TerminalPanel(workspaceId: UUID())
let panel = makeTrackedPanel(workspaceId: UUID())

panel.surface.releaseHostedSurfaceForTesting()
let osc11 = "\u{1B}]11;#341c1c\u{1B}\\"
Expand Down Expand Up @@ -1236,7 +1278,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {
}

func testColdSocketInputChunksLongCommittedTextInput() {
let panel = TerminalPanel(workspaceId: UUID())
let panel = makeTrackedPanel(workspaceId: UUID())

panel.surface.releaseHostedSurfaceForTesting()
let command = "printf '" + String(repeating: "x", count: 360) + "'\n"
Expand All @@ -1254,7 +1296,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {
}

func testTeardownClosesHeadlessStartupWindow() {
let panel = TerminalPanel(
let panel = makeTrackedPanel(
workspaceId: UUID(),
initialCommand: "echo startup"
)
Expand All @@ -1269,7 +1311,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {
}

func testClosedSurfaceRejectsColdSocketInputInsteadOfQueueingIt() {
let panel = TerminalPanel(workspaceId: UUID())
let panel = makeTrackedPanel(workspaceId: UUID())

panel.surface.releaseHostedSurfaceForTesting()
panel.surface.beginPortalCloseLifecycle(reason: "test.closed")
Expand All @@ -1296,7 +1338,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {
// via a non-keyboard path, so the named-key layer has to recognize "ctrl-f".
// A recognized-but-undeliverable key returns `.surfaceUnavailable` on a closed
// surface, whereas an unrecognized key returns `.unknownKey`.
let panel = TerminalPanel(workspaceId: UUID())
let panel = makeTrackedPanel(workspaceId: UUID())
panel.surface.releaseHostedSurfaceForTesting()
panel.surface.beginPortalCloseLifecycle(reason: "test.closed")

Expand Down Expand Up @@ -1330,7 +1372,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {

func testDaemonSendWorkspaceQueuesColdControlInputInsteadOfReportingDroppedOK() throws {
let previousManager = TerminalController.shared.activeTabManagerForCallerNotification()
let manager = TabManager()
let manager = makeTrackedManager()
TerminalController.shared.setActiveTabManager(manager)
defer {
TerminalController.shared.setActiveTabManager(previousManager)
Expand Down Expand Up @@ -1360,7 +1402,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {

func testMobileTerminalInputReportsRejectedClosedSurface() async throws {
let previousManager = TerminalController.shared.activeTabManagerForCallerNotification()
let manager = TabManager()
let manager = makeTrackedManager()
TerminalController.shared.setActiveTabManager(manager)
defer {
TerminalController.shared.setActiveTabManager(previousManager)
Expand Down Expand Up @@ -1445,7 +1487,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {

func testMobileRPCRejectsMalformedWorkspaceIDBeforeImplicitFallback() async throws {
let previousManager = TerminalController.shared.activeTabManagerForCallerNotification()
let manager = TabManager()
let manager = makeTrackedManager()
TerminalController.shared.setActiveTabManager(manager)
defer {
TerminalController.shared.setActiveTabManager(previousManager)
Expand Down Expand Up @@ -1493,7 +1535,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {

func testMobileWorkspaceListRejectsMissingScopedTargets() async throws {
let previousManager = TerminalController.shared.activeTabManagerForCallerNotification()
let manager = TabManager()
let manager = makeTrackedManager()
TerminalController.shared.setActiveTabManager(manager)
defer {
TerminalController.shared.setActiveTabManager(previousManager)
Expand Down Expand Up @@ -1537,7 +1579,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {

func testMobileAttachTicketCreateWithoutTerminalStaysWorkspaceScoped() async throws {
let previousManager = TerminalController.shared.activeTabManagerForCallerNotification()
let manager = TabManager()
let manager = makeTrackedManager()
TerminalController.shared.setActiveTabManager(manager)
defer {
TerminalController.shared.setActiveTabManager(previousManager)
Expand Down Expand Up @@ -1573,7 +1615,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {

func testMobileAttachTicketCreateResolvesTerminalIDAcrossWorkspaces() async throws {
let previousManager = TerminalController.shared.activeTabManagerForCallerNotification()
let manager = TabManager()
let manager = makeTrackedManager()
TerminalController.shared.setActiveTabManager(manager)
defer {
TerminalController.shared.setActiveTabManager(previousManager)
Expand Down Expand Up @@ -1619,7 +1661,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {

func testMobileAttachTicketCreateCanFilterRoutesForQRPairing() async throws {
let previousManager = TerminalController.shared.activeTabManagerForCallerNotification()
let manager = TabManager()
let manager = makeTrackedManager()
TerminalController.shared.setActiveTabManager(manager)
defer {
TerminalController.shared.setActiveTabManager(previousManager)
Expand Down Expand Up @@ -1663,7 +1705,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {

func testMobileTerminalCreateReturnsBeforeStartingGhostty() async throws {
let previousManager = TerminalController.shared.activeTabManagerForCallerNotification()
let manager = TabManager()
let manager = makeTrackedManager()
TerminalController.shared.setActiveTabManager(manager)
defer {
TerminalController.shared.setActiveTabManager(previousManager)
Expand Down Expand Up @@ -1705,7 +1747,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {
#if DEBUG
func testMobileWorkspaceCreateSkipsHiddenMacSideWorkAndReturnsCreatedScopeOnly() async throws {
let previousManager = TerminalController.shared.activeTabManagerForCallerNotification()
let manager = RecordingMobileTabManager()
let manager = track(RecordingMobileTabManager())
TerminalController.shared.setActiveTabManager(manager)
defer {
TerminalController.shared.setActiveTabManager(previousManager)
Expand Down Expand Up @@ -1744,7 +1786,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {

func testMobileTerminalCreateSkipsHiddenMacSideWorkAndKeepsMacSelection() async throws {
let previousManager = TerminalController.shared.activeTabManagerForCallerNotification()
let manager = RecordingMobileTabManager()
let manager = track(RecordingMobileTabManager())
TerminalController.shared.setActiveTabManager(manager)
defer {
TerminalController.shared.setActiveTabManager(previousManager)
Expand Down Expand Up @@ -1789,7 +1831,7 @@ final class TerminalOffscreenStartupTests: XCTestCase {

func testMobileBrowserCreateReturnsStreamableDescriptorAndKeepsMacSelection() async throws {
let previousManager = TerminalController.shared.activeTabManagerForCallerNotification()
let manager = RecordingMobileTabManager()
let manager = track(RecordingMobileTabManager())
TerminalController.shared.setActiveTabManager(manager)
defer {
TerminalController.shared.setActiveTabManager(previousManager)
Expand Down
13 changes: 11 additions & 2 deletions cmuxTests/WindowOverlayChromeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,17 @@ struct WindowOverlayChromeTests {
#expect(tabsFrame.height == 28)
}

@Test("Browser content stays inside the content hierarchy without covering either chrome strip", arguments: [false, true])
func browserAndTerminalRespectChrome(useGlass: Bool) throws {
@Test("Browser content stays inside the content hierarchy without covering either chrome strip")
func browserAndTerminalRespectChrome() throws {
// Swift Testing starts parameterized cases concurrently even inside
// this serialized suite. Each case temporarily changes process-wide
// window backdrop defaults, so exercise the two settings sequentially.
for useGlass in [false, true] {
try exerciseBrowserAndTerminalRespectChrome(useGlass: useGlass)
}
}

private func exerciseBrowserAndTerminalRespectChrome(useGlass: Bool) throws {
// A terminal surface re-applies the configured window backdrop when it
// mounts (`GhosttyNSView.viewDidMoveToWindow` →
// `applyWindowBackgroundIfActive`). With glass off in settings, that
Expand Down
Loading