macOS: fix window cascading (#11426)

Added test case for cascading **without moving previous window**, #11161
will follow up for more accurate cascading after this.

Fixed window cascading after last pr, now we should perform cascading
**after** showing the window.
This commit is contained in:
Mitchell Hashimoto
2026-03-12 10:44:37 -07:00
committed by GitHub
2 changed files with 60 additions and 3 deletions

View File

@@ -10,6 +10,61 @@ import XCTest
final class GhosttyWindowPositionUITests: GhosttyCustomConfigCase {
override static var runsForEachTargetApplicationUIConfiguration: Bool { false }
// MARK: - Cascading
@MainActor func testWindowCascading() async throws {
try updateConfig(
"""
title = "GhosttyWindowPositionUITests"
"""
)
let app = try ghosttyApplication()
// Suppress Restoration
app.launchArguments += ["-NSQuitAlwaysKeepsWindows", "NO"]
// Clean run
app.launchEnvironment["GHOSTTY_CLEAR_USER_DEFAULTS"] = "YES"
app.launch() // window in the center
// app.menuBarItems["Window"].firstMatch.click()
// app.menuItems["_zoomTopLeft:"].firstMatch.click()
//
// // wait for the animation to finish
// try await Task.sleep(for: .seconds(0.5))
let window = app.windows.firstMatch
let windowFrame = window.frame
// XCTAssertEqual(windowFrame.minX, 0, "Window should be on the left")
app.typeKey("n", modifierFlags: [.command])
let window2 = app.windows.firstMatch
XCTAssertTrue(window2.waitForExistence(timeout: 5), "New window should appear")
let windowFrame2 = window2.frame
XCTAssertNotEqual(windowFrame, windowFrame2, "New window should have moved")
XCTAssertEqual(windowFrame2.minX, windowFrame.minX + 30, accuracy: 5, "New window should be on the right")
app.typeKey("n", modifierFlags: [.command])
let window3 = app.windows.firstMatch
XCTAssertTrue(window3.waitForExistence(timeout: 5), "New window should appear")
let windowFrame3 = window3.frame
XCTAssertNotEqual(windowFrame2, windowFrame3, "New window should have moved")
XCTAssertEqual(windowFrame3.minX, windowFrame2.minX + 30, accuracy: 5, "New window should be on the right")
app.typeKey("n", modifierFlags: [.command])
let window4 = app.windows.firstMatch
XCTAssertTrue(window4.waitForExistence(timeout: 5), "New window should appear")
let windowFrame4 = window4.frame
XCTAssertNotEqual(windowFrame3, windowFrame4, "New window should have moved")
XCTAssertEqual(windowFrame4.minX, windowFrame3.minX + 30, accuracy: 5, "New window should be on the right")
}
// MARK: - Restore round-trip per titlebar style
@MainActor func testRestoredNative() throws { try runRestoreTest(titlebarStyle: "native") }

View File

@@ -200,7 +200,9 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
if all.count > 1 {
lastCascadePoint = window.cascadeTopLeft(from: lastCascadePoint)
} else {
lastCascadePoint = window.cascadeTopLeft(from: NSPoint(x: window.frame.minX, y: window.frame.maxY))
// We assume the window frame is already correct at this point,
// so we pass .zero to let cascade use the current frame position.
lastCascadePoint = window.cascadeTopLeft(from: .zero)
}
}
@@ -256,6 +258,8 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
// take effect. Our best theory is there is some next-event-loop-tick logic
// that Cocoa is doing that we need to be after.
DispatchQueue.main.async {
c.showWindow(self)
// Only cascade if we aren't fullscreen.
if let window = c.window {
if !window.styleMask.contains(.fullScreen) {
@@ -264,8 +268,6 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
}
}
c.showWindow(self)
// All new_window actions force our app to be active, so that the new
// window is focused and visible.
NSApp.activate(ignoringOtherApps: true)