Files
terminalX/vendor/libghostty-spm/Tests/GhosttyKitTest/TerminalThemeConfigurationTests.swift
kid aa92d0e676 初始提交: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>
2026-07-24 10:20:46 +08:00

221 lines
7.3 KiB
Swift

@testable import GhosttyTerminal
import Combine
import SwiftUI
import Testing
@MainActor
struct TerminalThemeConfigurationTests {
@Test
func `command builder preserves insertion order`() {
let configuration = TerminalConfiguration {
$0.withFontSize(13)
$0.withCursorStyle(.bar)
$0.withCursorStyleBlink(false)
$0.withBackground("#101010")
}
#expect(
configuration.rendered
== """
font-size = 13
cursor-style = bar
cursor-style-blink = false
background = #101010
"""
)
}
@Test
func `rendered config composes base behavior and theme`() {
let state = TerminalViewState(
configSource: .generated(
"""
font-size = 14
cursor-style = block
"""
),
theme: .init(
light: TerminalConfiguration()
.backgroundOpacity(0.82)
.background("#111111"),
dark: TerminalConfiguration()
.backgroundOpacity(0.62)
.background("#000000")
),
terminalConfiguration: TerminalConfiguration()
.cursorStyleBlink(true)
)
#expect(state.renderedConfig.contains("font-size = 14"))
#expect(state.renderedConfig.contains("cursor-style = block"))
#expect(state.renderedConfig.contains("cursor-style-blink = true"))
#expect(state.renderedConfig.contains("background-opacity = 0.82"))
#expect(state.renderedConfig.contains("background = #111111"))
}
@Test
func `valid configuration update preserves controller identity`() {
let state = TerminalViewState(
terminalConfiguration: TerminalConfiguration()
.fontSize(14)
)
let controller = state.controller
let didApply = state.setTerminalConfiguration(
TerminalConfiguration()
.fontSize(16)
.cursorStyle(.underline)
)
#expect(didApply)
#expect(state.controller === controller)
#expect(state.terminalConfiguration == TerminalConfiguration()
.fontSize(16)
.cursorStyle(.underline))
#expect(state.renderedConfig.contains("font-size = 16"))
#expect(state.renderedConfig.contains("cursor-style = underline"))
let fontSizeLines = state.renderedConfig
.split(separator: "\n")
.filter { $0.hasPrefix("font-size = ") }
#expect(fontSizeLines.last == "font-size = 16")
}
@Test
func `adopting dark mode switches rendered theme variant`() {
let state = TerminalViewState(
theme: .init(
light: TerminalConfiguration()
.backgroundOpacity(0.91),
dark: TerminalConfiguration()
.backgroundOpacity(0.47)
)
)
var notificationCount = 0
var colorSchemeAtNotification: TerminalColorScheme?
let cancellable = state.objectWillChange.sink {
notificationCount += 1
colorSchemeAtNotification = state.effectiveColorScheme
}
defer { cancellable.cancel() }
let controller = state.controller
#expect(state.effectiveColorScheme == .light)
#expect(state.renderedConfig.contains("background-opacity = 0.91"))
state.adopt(colorScheme: .dark)
#expect(notificationCount == 1)
#expect(colorSchemeAtNotification == .light)
#expect(state.effectiveColorScheme == .dark)
#expect(state.renderedConfig.contains("background-opacity = 0.47"))
#expect(!state.renderedConfig.contains("background-opacity = 0.91"))
#expect(state.controller === controller)
}
@Test
func `invalid dark theme rolls back color scheme and rendered config`() {
let state = TerminalViewState(
theme: .init(
light: TerminalConfiguration()
.backgroundOpacity(0.91),
dark: TerminalConfiguration()
.custom("not-a-real-ghostty-option", "true")
)
)
var notificationCount = 0
let cancellable = state.objectWillChange.sink {
notificationCount += 1
}
defer { cancellable.cancel() }
let previousRenderedConfig = state.renderedConfig
state.adopt(colorScheme: .dark)
// Controller rolls back on config failure, so color scheme stays light
#expect(notificationCount == 0)
#expect(state.effectiveColorScheme == .light)
#expect(state.renderedConfig == previousRenderedConfig)
#expect(state.renderedConfig.contains("background-opacity = 0.91"))
}
@Test
func `adopting current color scheme does not notify`() {
let state = TerminalViewState()
var notificationCount = 0
let cancellable = state.objectWillChange.sink {
notificationCount += 1
}
defer { cancellable.cancel() }
#expect(state.effectiveColorScheme == .light)
state.adopt(colorScheme: .light)
#expect(notificationCount == 0)
#expect(state.effectiveColorScheme == .light)
}
@Test
func `shared controller accepts mutations`() {
let controller = TerminalController()
let state = TerminalViewState(controller: controller)
// With the single-source-of-truth architecture, mutations go
// directly to the controller and succeed.
let didSetTheme = state.setTheme(
.init(
light: TerminalConfiguration()
.backgroundOpacity(0.5)
)
)
#expect(didSetTheme)
#expect(state.controller === controller)
#expect(state.renderedConfig.contains("background-opacity = 0.5"))
}
@Test
func `no op theme update returns false`() {
let theme = TerminalTheme(
light: TerminalConfiguration()
.backgroundOpacity(0.7)
)
let state = TerminalViewState(theme: theme)
var notificationCount = 0
let cancellable = state.objectWillChange.sink {
notificationCount += 1
}
defer { cancellable.cancel() }
let previousRenderedConfig = state.renderedConfig
let didApply = state.setTheme(theme)
#expect(!didApply)
#expect(notificationCount == 0)
#expect(state.renderedConfig == previousRenderedConfig)
}
@Test
func `invalid configuration does not replace rendered config`() {
let state = TerminalViewState(
terminalConfiguration: TerminalConfiguration()
.fontSize(14)
)
var notificationCount = 0
let cancellable = state.objectWillChange.sink {
notificationCount += 1
}
defer { cancellable.cancel() }
let previousRenderedConfig = state.renderedConfig
let didApply = state.setTerminalConfiguration(
TerminalConfiguration()
.custom("not-a-real-ghostty-option", "true")
)
#expect(!didApply)
#expect(notificationCount == 0)
#expect(state.renderedConfig == previousRenderedConfig)
#expect(state.terminalConfiguration == TerminalConfiguration().fontSize(14))
}
}