Prepare notify regression source terminal in app setup

This commit is contained in:
Lawrence Chen 2026-03-05 22:39:26 -08:00
parent 5f82a1ae2a
commit ae4781ef66
2 changed files with 88 additions and 21 deletions

View file

@ -5776,6 +5776,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
"expectedLatestWindowId": window1.windowId.uuidString,
"expectedLatestTabId": tabId1.uuidString,
], at: path)
self.prepareMultiWindowNotificationSourceTerminalIfNeeded(
at: path,
windowId: window1.windowId,
tabManager: window1.tabManager,
tabId: tabId1,
surfaceId: surfaceId1
)
self.publishMultiWindowNotificationSocketStateIfNeeded(at: path)
}
}
@ -5783,6 +5790,71 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
}
}
private func prepareMultiWindowNotificationSourceTerminalIfNeeded(
at path: String,
windowId: UUID,
tabManager: TabManager,
tabId: UUID,
surfaceId: UUID
) {
let env = ProcessInfo.processInfo.environment
guard env["CMUX_UI_TEST_NOTIFY_SOURCE_TERMINAL_READY"] == "1" else { return }
writeMultiWindowNotificationTestData([
"sourceTerminalReady": "pending",
"sourceTerminalFocusFailure": "",
], at: path)
let deadline = Date().addingTimeInterval(8.0)
func publish(ready: Bool, failure: String = "") {
writeMultiWindowNotificationTestData([
"sourceTerminalReady": ready ? "1" : "0",
"sourceTerminalFocusFailure": failure,
], at: path)
}
func poll() {
guard let workspace = tabManager.tabs.first(where: { $0.id == tabId }) else {
publish(ready: false, failure: "workspace_missing")
return
}
guard let terminalPanel = workspace.terminalPanel(for: surfaceId) else {
publish(ready: false, failure: "terminal_missing")
return
}
let isWindowFrontmost = {
guard let window = self.mainWindow(for: windowId) else { return false }
return NSApp.keyWindow === window || NSApp.mainWindow === window
}()
if isWindowFrontmost && terminalPanel.hostedView.isSurfaceViewFirstResponder() {
publish(ready: true)
return
}
guard Date() < deadline else {
publish(
ready: false,
failure: isWindowFrontmost ? "terminal_not_first_responder" : "window_not_frontmost"
)
return
}
_ = self.focusMainWindow(windowId: windowId)
if let tab = tabManager.tabs.first(where: { $0.id == tabId }) {
tabManager.selectTab(tab)
tabManager.focusSurface(tabId: tabId, surfaceId: surfaceId)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
poll()
}
}
poll()
}
private func publishMultiWindowNotificationSocketStateIfNeeded(at path: String) {
let env = ProcessInfo.processInfo.environment
guard env["CMUX_UI_TEST_SOCKET_SANITY"] == "1" else { return }

View file

@ -199,6 +199,7 @@ final class MultiWindowNotificationsUITests: XCTestCase {
app.launchEnvironment["CMUX_SOCKET_MODE"] = "allowAll"
app.launchEnvironment["CMUX_SOCKET_ENABLE"] = "1"
app.launchEnvironment["CMUX_UI_TEST_SOCKET_SANITY"] = "1"
app.launchEnvironment["CMUX_UI_TEST_NOTIFY_SOURCE_TERMINAL_READY"] = "1"
app.launchEnvironment["CMUX_UI_TEST_ENABLE_DUPLICATE_LAUNCH_OBSERVER"] = "1"
app.launchEnvironment["CMUX_TAG"] = launchTag
app.launch()
@ -211,9 +212,15 @@ final class MultiWindowNotificationsUITests: XCTestCase {
let tabId2 = data["tabId2"] ?? ""
let surfaceId2 = data["surfaceId2"] ?? ""
let socketReady = data["socketReady"] ?? ""
return !tabId2.isEmpty && !surfaceId2.isEmpty && !socketReady.isEmpty && socketReady != "pending"
let sourceTerminalReady = data["sourceTerminalReady"] ?? ""
return !tabId2.isEmpty &&
!surfaceId2.isEmpty &&
!socketReady.isEmpty &&
socketReady != "pending" &&
!sourceTerminalReady.isEmpty &&
sourceTerminalReady != "pending"
},
"Expected multi-window notification setup data and socket readiness"
"Expected multi-window notification setup data, socket readiness, and source terminal focus"
)
guard let setup = loadData() else {
@ -224,18 +231,6 @@ final class MultiWindowNotificationsUITests: XCTestCase {
XCTFail("Missing setup workspace id")
return
}
guard let tabId1 = setup["tabId1"], !tabId1.isEmpty else {
XCTFail("Missing source workspace id")
return
}
guard let window1Id = setup["window1Id"], !window1Id.isEmpty else {
XCTFail("Missing source window id")
return
}
guard let sourceSurfaceId = setup["surfaceId1"], !sourceSurfaceId.isEmpty else {
XCTFail("Missing source surface id")
return
}
if let expectedSocketPath = setup["socketExpectedPath"], !expectedSocketPath.isEmpty {
socketPath = expectedSocketPath
}
@ -257,6 +252,13 @@ final class MultiWindowNotificationsUITests: XCTestCase {
XCTFail("Missing target surface id for workspace \(tabId2)")
return
}
guard setup["sourceTerminalReady"] == "1" else {
XCTFail(
"Expected source terminal to be focused before typing. " +
"failure=\(setup["sourceTerminalFocusFailure"] ?? "<unknown>")"
)
return
}
XCTAssertTrue(waitForWindowCount(atLeast: 2, app: app, timeout: 6.0))
@ -302,13 +304,6 @@ final class MultiWindowNotificationsUITests: XCTestCase {
)
return
}
XCTAssertEqual(socketCommand("focus_window \(window1Id)"), "OK", "Expected source window to be focusable")
XCTAssertEqual(socketCommand("select_workspace \(tabId1)"), "OK", "Expected source workspace to be selectable")
XCTAssertEqual(socketCommand("focus_surface \(sourceSurfaceId)"), "OK", "Expected source terminal to be focusable")
XCTAssertTrue(
waitForTerminalFocus(surfaceId: sourceSurfaceId, timeout: 4.0),
"Expected source terminal surface to own first responder before typing"
)
app.typeText("sh \(commandScriptPath)")
app.typeKey(XCUIKeyboardKey.return.rawValue, modifierFlags: [])