初始提交:terminalX 可运行态(M0/M1/M1.5 已真机验证)

- M0: libghostty SSH 终端(渲染/输入/连接)+ 白屏修复(OutputGate) + 会话状态机/自动重连
- M1: tsnet 用户态组网 + SSH-over-tsnet(fd 桥),shell 级真机验证;R5(Go+gvisor+C+++Swift 同进程) retire
- M1.5: tmux -CC 原生 tab(MVP)
- 结构: packages/(TXCore·TXTransport), apps/TerminalX, vendor/(libghostty-spm/libssh2/mbedtls/tsnet-bridge), artifacts/
- 文档: CLAUDE.md + docs/HANDOFF.md(新会话入口)
- 环境: 认证代理→依赖 vendor 本地化;Go 在 ~/.local/go;仅模拟器/未签名
- 待续: M2 mosh, tmux 多 pane, M4 安全(host key/SE)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
kid
2026-07-24 10:20:46 +08:00
commit aa92d0e676
2761 changed files with 803505 additions and 0 deletions

View File

@@ -0,0 +1,178 @@
import XCTest
import AppKit
final class GhosttyTerminalAppUITests: XCTestCase {
private var app: XCUIApplication!
private var systemAlertMonitor: NSObjectProtocol?
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments = ["--ui-testing"]
systemAlertMonitor = installSystemAlertHandler()
app.launch()
}
override func tearDownWithError() throws {
capture("final-state")
app = nil
}
func testTerminalUserOperations() throws {
let terminal = try requireTerminalInteractionTarget()
capture("01-launch")
clickTerminal(in: terminal)
capture("02-focus")
typeTerminalText("echo mac-single\n", in: terminal)
capture("03-single-line-input")
typeTerminalText("echo mac first line\n", in: terminal)
typeTerminalText("echo mac second line\n", in: terminal)
capture("04-multiple-lines")
typeTerminalText("中文键盘测试,标点和全角字符。\n", in: terminal)
capture("05-chinese-input")
typeTerminalText("日本語キーボードテスト、かなと漢字。\n", in: terminal)
capture("06-japanese-input")
typeTerminalText("Mixed input: English 中文 日本語 123\n", in: terminal)
capture("07-multilingual-input")
terminal.swipeUp()
capture("08-swipe-up")
terminal.swipeDown()
capture("09-swipe-down")
app.typeKey("=", modifierFlags: .command)
capture("10-keyboard-zoom-in")
app.typeKey("-", modifierFlags: .command)
capture("11-keyboard-zoom-out")
typeTerminalText("clear\n", in: terminal)
capture("12-clear-command")
assertNoCopyMenuWithoutSelection(in: terminal)
typeTerminalText("echo selection anchor\n", in: terminal)
dragPointerSelection(in: terminal)
capture("13-pointer-selection")
openCopyMenuAndCopySelection(in: terminal, screenshotName: "14-pointer-copy-menu")
longPressTerminal(in: terminal)
capture("15-long-press")
}
private func requireTerminalInteractionTarget() throws -> XCUIElement {
let terminal = app.descendants(matching: .any)["terminal.surface"].firstMatch
if terminal.waitForExistence(timeout: 4), terminal.isHittable {
return terminal
}
let window = app.windows.firstMatch
XCTAssertTrue(window.waitForExistence(timeout: 8))
XCTAssertTrue(window.isHittable)
return window
}
private func clickTerminal(in element: XCUIElement) {
element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.55)).click()
}
private func installSystemAlertHandler() -> NSObjectProtocol {
addUIInterruptionMonitor(withDescription: "System alert") { alert in
let preferredButtons = [
"OK", "Ok", "", "确定", "允许", "Allow", "继续", "Continue",
"关闭", "Close", "Dismiss",
]
for title in preferredButtons {
let button = alert.buttons[title].firstMatch
if button.exists {
button.click()
return true
}
}
return false
}
}
private func typeTerminalText(_ text: String, in element: XCUIElement) {
clickTerminal(in: element)
element.typeText(text)
}
private func longPressTerminal(in element: XCUIElement) {
element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.55)).press(forDuration: 0.7)
}
private func dragPointerSelection(in element: XCUIElement) {
log("pointer-selection-coordinates", "start=(0.008, 0.10), end=(0.42, 0.10), rightClick=(0.20, 0.10)")
let start = element.coordinate(withNormalizedOffset: CGVector(dx: 0.008, dy: 0.10))
let end = element.coordinate(withNormalizedOffset: CGVector(dx: 0.42, dy: 0.10))
start.press(forDuration: 0.1, thenDragTo: end)
}
private func openCopyMenuAndCopySelection(in element: XCUIElement, screenshotName: String) {
NSPasteboard.general.clearContents()
disableSystemAlertMonitorBeforeContextMenu()
element.coordinate(withNormalizedOffset: CGVector(dx: 0.20, dy: 0.10)).rightClick()
let copy = copyMenuItem()
if !copy.waitForExistence(timeout: 3) {
capture("\(screenshotName)-missing")
XCTFail("Copy menu item not found after pointer selection right click. Hierarchy: \(app.debugDescription)")
return
}
copy.click()
capture(screenshotName)
let actual = copiedPasteboardText(timeout: 2)
log("pointer-selection-pasteboard", actual ?? "<nil>")
XCTAssertEqual(actual, "selection anchor")
}
private func assertNoCopyMenuWithoutSelection(in element: XCUIElement) {
disableSystemAlertMonitorBeforeContextMenu()
element.coordinate(withNormalizedOffset: CGVector(dx: 0.20, dy: 0.10)).rightClick()
XCTAssertFalse(
copyMenuItem().waitForExistence(timeout: 0.5),
"Copy menu item appeared without an active terminal selection."
)
}
private func disableSystemAlertMonitorBeforeContextMenu() {
if let systemAlertMonitor {
removeUIInterruptionMonitor(systemAlertMonitor)
self.systemAlertMonitor = nil
}
}
private func copyMenuItem() -> XCUIElement {
app.menuItems["Copy"].firstMatch
}
private func copiedPasteboardText(timeout: TimeInterval) -> String? {
let deadline = Date().addingTimeInterval(timeout)
repeat {
if let string = NSPasteboard.general.string(forType: .string) {
return string
}
RunLoop.current.run(mode: .default, before: Date().addingTimeInterval(0.05))
} while Date() < deadline
return nil
}
private func log(_ name: String, _ value: String) {
let attachment = XCTAttachment(string: value)
attachment.name = name
attachment.lifetime = .keepAlways
add(attachment)
}
private func capture(_ name: String) {
guard let app else { return }
let attachment = XCTAttachment(screenshot: app.screenshot())
attachment.name = name
attachment.lifetime = .keepAlways
add(attachment)
}
}