Unify terminal appearance preference controls
This commit is contained in:
1 parent
17d0a2d855
commit
a4189629e6
10 files changed
+310
-71
No files matched your search
@@ -17143,6 +17143,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"No Theme Selected" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "未选择主题"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "未選擇主題"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"No Transfers" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -28762,6 +28779,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"This theme is unavailable for this appearance. Using “%@” instead." : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "此主题不适用于当前外观,将改用“%@”。"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "此主題不適用於目前外觀,將改用「%@」。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"This Vault record requires sync format %lld." : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
@@ -31898,4 +31932,4 @@
|
||||
}
|
||||
},
|
||||
"version" : "1.2"
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,20 @@ public nonisolated enum TermindTerminalThemeCatalog {
|
||||
|
||||
public static let defaultDark = TermindTerminalPalette.termindDark.ghosttyDefinition
|
||||
public static let defaultLight = TermindTerminalPalette.termindLight.ghosttyDefinition
|
||||
public static let darkThemes = allThemes.filter(\.isDark).sorted { $0.name < $1.name }
|
||||
public static let lightThemes = allThemes.filter { !$0.isDark }.sorted { $0.name < $1.name }
|
||||
|
||||
public static func themes(isDark: Bool) -> [GhosttyThemeDefinition] {
|
||||
isDark ? darkThemes : lightThemes
|
||||
}
|
||||
|
||||
public static func fallbackTheme(isDark: Bool) -> GhosttyThemeDefinition {
|
||||
isDark ? defaultDark : defaultLight
|
||||
}
|
||||
|
||||
public static func resolvedTheme(named name: String, isDark: Bool) -> GhosttyThemeDefinition {
|
||||
theme(named: name).flatMap { $0.isDark == isDark ? $0 : nil } ?? fallbackTheme(isDark: isDark)
|
||||
}
|
||||
|
||||
public static func terminalConfiguration(
|
||||
themeName: String,
|
||||
@@ -67,8 +81,7 @@ public nonisolated enum TermindTerminalThemeCatalog {
|
||||
isDark: Bool,
|
||||
font: TerminalFontPreferences = .init()
|
||||
) -> GhosttyTerminal.TerminalConfiguration {
|
||||
let fallback = isDark ? defaultDark : defaultLight
|
||||
let selected = theme(named: themeName).flatMap { $0.isDark == isDark ? $0 : nil } ?? fallback
|
||||
let selected = resolvedTheme(named: themeName, isDark: isDark)
|
||||
let base = selected.toTerminalConfiguration()
|
||||
let themed = customEnabled ? TerminalCustomConfiguration(customText).applying(to: base) : base
|
||||
// Ghostty renders theme commands after session overrides. Typography must
|
||||
@@ -88,8 +101,7 @@ public nonisolated enum TermindTerminalThemeCatalog {
|
||||
customText: String,
|
||||
isDark: Bool
|
||||
) -> TermindTerminalPalette.RGB {
|
||||
let fallback = isDark ? defaultDark : defaultLight
|
||||
let selected = theme(named: themeName).flatMap { $0.isDark == isDark ? $0 : nil } ?? fallback
|
||||
let selected = resolvedTheme(named: themeName, isDark: isDark)
|
||||
if customEnabled,
|
||||
let value = TerminalCustomConfiguration(customText).lastValue(for: "background"),
|
||||
let customBackground = TermindTerminalPalette.RGB(hex: value)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import SwiftUI
|
||||
import TermindDomain
|
||||
|
||||
public struct TerminalFontFields: View {
|
||||
public static let sizeRange: ClosedRange<Double> = 8...36
|
||||
|
||||
@Binding private var preferences: TerminalFontPreferences
|
||||
private let families: [String]
|
||||
|
||||
public init(preferences: Binding<TerminalFontPreferences>, families: [String]) {
|
||||
_preferences = preferences
|
||||
self.families = families
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
TerminalFontPicker(family: $preferences.family, families: families)
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Picker("Font Size", selection: $preferences.sizeFollowsTheme) {
|
||||
Text("Follow Theme").tag(true)
|
||||
Text("Custom").tag(false)
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
if !preferences.sizeFollowsTheme {
|
||||
sizeEditor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private var sizeEditor: some View {
|
||||
#if os(macOS)
|
||||
HStack {
|
||||
Slider(value: $preferences.size, in: Self.sizeRange, step: 1)
|
||||
.labelsHidden()
|
||||
.accessibilityLabel("Font Size")
|
||||
.accessibilityValue(preferences.size.formatted())
|
||||
sizeLabel.frame(width: 24, alignment: .trailing)
|
||||
}
|
||||
#else
|
||||
Stepper(value: $preferences.size, in: Self.sizeRange, step: 1) {
|
||||
sizeLabel
|
||||
}
|
||||
.accessibilityLabel("Font Size")
|
||||
.accessibilityValue(preferences.size.formatted())
|
||||
#endif
|
||||
}
|
||||
|
||||
private var sizeLabel: some View {
|
||||
Text(preferences.size, format: .number.precision(.fractionLength(0)))
|
||||
.monospacedDigit()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import GhosttyTheme
|
||||
import SwiftUI
|
||||
|
||||
public struct TerminalThemePicker: View {
|
||||
@Binding private var selection: String
|
||||
private let isDark: Bool
|
||||
|
||||
public init(selection: Binding<String>, isDark: Bool) {
|
||||
_selection = selection
|
||||
self.isDark = isDark
|
||||
}
|
||||
|
||||
private var themes: [GhosttyThemeDefinition] {
|
||||
TermindTerminalThemeCatalog.themes(isDark: isDark)
|
||||
}
|
||||
|
||||
private var selectedThemeIsAvailable: Bool {
|
||||
TermindTerminalThemeCatalog.theme(named: selection)?.isDark == isDark
|
||||
}
|
||||
|
||||
private var fallbackName: String {
|
||||
TermindTerminalThemeCatalog.fallbackTheme(isDark: isDark).name
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Picker("Theme", selection: $selection) {
|
||||
if !selectedThemeIsAvailable {
|
||||
if selection.isEmpty {
|
||||
Text("No Theme Selected").tag(selection)
|
||||
} else {
|
||||
Text(verbatim: selection).tag(selection)
|
||||
}
|
||||
}
|
||||
ForEach(themes, id: \.name) { theme in
|
||||
Text(verbatim: theme.name).tag(theme.name)
|
||||
}
|
||||
}
|
||||
if !selectedThemeIsAvailable {
|
||||
Text("This theme is unavailable for this appearance. Using “\(Text(verbatim: fallbackName))” instead.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,21 +15,7 @@ struct MobileTerminalSettingsView: View {
|
||||
themeSection(dark: false)
|
||||
themeSection(dark: true)
|
||||
Section("Font") {
|
||||
TerminalFontPicker(family: $font.family, families: TerminalFontCatalog.shared.families)
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Picker("Font Size", selection: $font.sizeFollowsTheme) {
|
||||
Text("Follow Theme").tag(true)
|
||||
Text("Custom").tag(false)
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
if !font.sizeFollowsTheme {
|
||||
Stepper(value: $font.size, in: 8...36, step: 1) {
|
||||
Text(font.size, format: .number.precision(.fractionLength(0))).monospacedDigit()
|
||||
}
|
||||
.accessibilityLabel("Font Size")
|
||||
.accessibilityValue(font.size.formatted())
|
||||
}
|
||||
}
|
||||
TerminalFontFields(preferences: $font, families: TerminalFontCatalog.shared.families)
|
||||
}
|
||||
Section("Integration") { Toggle("Shell Integration", isOn: binding(\.shellIntegration)) }
|
||||
if let error = model.storageError { Text(error).foregroundStyle(.red) }
|
||||
@@ -64,11 +50,7 @@ struct MobileTerminalSettingsView: View {
|
||||
|
||||
private func themeSection(dark: Bool) -> some View {
|
||||
Section {
|
||||
Picker("Theme", selection: binding(dark ? \.darkTheme : \.lightTheme)) {
|
||||
ForEach(TermindTerminalThemeCatalog.allThemes.filter { $0.isDark == dark }, id: \.name) {
|
||||
Text(verbatim: $0.name).tag($0.name)
|
||||
}
|
||||
}
|
||||
TerminalThemePicker(selection: binding(dark ? \.darkTheme : \.lightTheme), isDark: dark)
|
||||
Toggle("Custom Configuration", isOn: binding(dark ? \.darkCustomEnabled : \.lightCustomEnabled))
|
||||
Button("Edit Configuration", systemImage: "pencil") {
|
||||
customText =
|
||||
|
||||
@@ -11,14 +11,27 @@ import UIKit
|
||||
struct MobileTerminalFontTests {
|
||||
@Test func fontCatalogIncludesOnlyAvailableMonospacedFamilies() {
|
||||
TerminalFontCatalog.shared.refresh()
|
||||
#expect(!TerminalFontCatalog.shared.families.isEmpty)
|
||||
#expect(!TerminalFontCatalog.shared.families.contains("Helvetica"))
|
||||
for family in TerminalFontCatalog.shared.families {
|
||||
let catalogFamilies = TerminalFontCatalog.shared.families
|
||||
#expect(!catalogFamilies.isEmpty)
|
||||
#expect(
|
||||
catalogFamilies
|
||||
== catalogFamilies.sorted { $0.localizedStandardCompare($1) == .orderedAscending })
|
||||
for family in catalogFamilies {
|
||||
#expect(
|
||||
UIFont.fontNames(forFamilyName: family).contains {
|
||||
UIFont(name: $0, size: 14)?.fontDescriptor.symbolicTraits.contains(.traitMonoSpace) == true
|
||||
})
|
||||
}
|
||||
|
||||
let proportionalFamily = UIFont.familyNames.first { family in
|
||||
let fonts = UIFont.fontNames(forFamilyName: family).compactMap { UIFont(name: $0, size: 14) }
|
||||
return !fonts.isEmpty
|
||||
&& fonts.allSatisfy { !$0.fontDescriptor.symbolicTraits.contains(.traitMonoSpace) }
|
||||
}
|
||||
#expect(proportionalFamily != nil)
|
||||
if let proportionalFamily {
|
||||
#expect(!catalogFamilies.contains(proportionalFamily))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(arguments: ["en", "zh-Hans", "zh-Hant"])
|
||||
|
||||
@@ -682,10 +682,6 @@ private struct TerminalSettings: View {
|
||||
@Environment(AppModel.self) private var app
|
||||
@TerminalFontSettings private var font
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
private var darkTheme: String { app.terminalSettings.preferences.darkTheme }
|
||||
private var lightTheme: String { app.terminalSettings.preferences.lightTheme }
|
||||
private var darkCustomEnabled: Bool { app.terminalSettings.preferences.darkCustomEnabled }
|
||||
private var lightCustomEnabled: Bool { app.terminalSettings.preferences.lightCustomEnabled }
|
||||
|
||||
private func binding<Value>(_ keyPath: WritableKeyPath<TerminalPreferences, Value>) -> Binding<Value> {
|
||||
Binding(
|
||||
@@ -693,31 +689,20 @@ private struct TerminalSettings: View {
|
||||
set: { app.terminalSettings.set($0, for: keyPath) })
|
||||
}
|
||||
|
||||
private var darkThemes: [GhosttyThemeDefinition] {
|
||||
TermindTerminalThemeCatalog.allThemes.filter(\.isDark).sorted { $0.name < $1.name }
|
||||
}
|
||||
private var lightThemes: [GhosttyThemeDefinition] {
|
||||
TermindTerminalThemeCatalog.allThemes.filter { !$0.isDark }.sorted { $0.name < $1.name }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if let error = app.terminalSettings.storageError {
|
||||
Text(error).foregroundStyle(.orange)
|
||||
}
|
||||
Section("Light") {
|
||||
Picker("Theme", selection: binding(\.lightTheme)) {
|
||||
ForEach(lightThemes) { Text($0.name).tag($0.name) }
|
||||
}
|
||||
.disabled(lightCustomEnabled)
|
||||
TerminalThemePicker(selection: binding(\.lightTheme), isDark: false)
|
||||
Toggle("Custom Configuration", isOn: binding(\.lightCustomEnabled))
|
||||
FormNavigationAction(title: "Edit Custom Configuration") {
|
||||
editConfiguration(.light)
|
||||
}
|
||||
}
|
||||
Section {
|
||||
Picker("Theme", selection: binding(\.darkTheme)) { ForEach(darkThemes) { Text($0.name).tag($0.name) } }
|
||||
.disabled(darkCustomEnabled)
|
||||
TerminalThemePicker(selection: binding(\.darkTheme), isDark: true)
|
||||
Toggle("Custom Configuration", isOn: binding(\.darkCustomEnabled))
|
||||
FormNavigationAction(title: "Edit Custom Configuration") {
|
||||
editConfiguration(.dark)
|
||||
@@ -730,22 +715,7 @@ private struct TerminalSettings: View {
|
||||
)
|
||||
}
|
||||
Section("Font") {
|
||||
TerminalFontPicker(family: $font.family, families: TerminalFontCatalog.shared.families)
|
||||
VStack(alignment: .leading, spacing: Metrics.Space.m) {
|
||||
Picker("Font Size", selection: $font.sizeFollowsTheme) {
|
||||
Text("Follow Theme").tag(true)
|
||||
Text("Custom").tag(false)
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
if !font.sizeFollowsTheme {
|
||||
HStack {
|
||||
Slider(value: $font.size, in: 9...24, step: 1)
|
||||
.labelsHidden().accessibilityLabel("Font Size")
|
||||
Text(font.size, format: .number.precision(.fractionLength(0)))
|
||||
.monospacedDigit().frame(width: 24, alignment: .trailing)
|
||||
}
|
||||
}
|
||||
}
|
||||
TerminalFontFields(preferences: $font, families: TerminalFontCatalog.shared.families)
|
||||
}
|
||||
Section("Mouse") {
|
||||
Toggle("Copy on Selection", isOn: binding(\.copyOnSelect))
|
||||
@@ -777,12 +747,6 @@ private struct TerminalSettings: View {
|
||||
}
|
||||
.onAppear {
|
||||
TerminalFontCatalog.shared.refresh()
|
||||
if TermindTerminalThemeCatalog.darkTheme(named: darkTheme) == nil {
|
||||
app.terminalSettings.set(SettingsKey.defaultTerminalThemeDark, for: \.darkTheme)
|
||||
}
|
||||
if TermindTerminalThemeCatalog.lightTheme(named: lightTheme) == nil {
|
||||
app.terminalSettings.set(SettingsKey.defaultTerminalThemeLight, for: \.lightTheme)
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase) { _, phase in
|
||||
if phase == .active { TerminalFontCatalog.shared.refresh() }
|
||||
|
||||
@@ -10,7 +10,10 @@ final class TerminalFontCatalog {
|
||||
|
||||
func refresh() {
|
||||
families = NSFontManager.shared.availableFontFamilies.filter { family in
|
||||
NSFontManager.shared.font(withFamily: family, traits: [], weight: 5, size: 12)?.isFixedPitch == true
|
||||
NSFontManager.shared.availableMembers(ofFontFamily: family)?.contains { member in
|
||||
guard let fontName = member.first as? String else { return false }
|
||||
return NSFont(name: fontName, size: 12)?.isFixedPitch == true
|
||||
} == true
|
||||
}.sorted { $0.localizedStandardCompare($1) == .orderedAscending }
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import AppKit
|
||||
import GhosttyTerminal
|
||||
import TermindDomain
|
||||
import TermindPresentation
|
||||
@@ -44,9 +45,31 @@ struct TerminalFontRenderingTests {
|
||||
#expect(state.theme.dark.rendered.hasSuffix("font-family = Courier"))
|
||||
}
|
||||
|
||||
@Test func installedMonospacedFamiliesIncludeMenlo() {
|
||||
@Test func fontCatalogIncludesEveryLoadableMonospacedFamilyInLocalizedOrder() {
|
||||
TerminalFontCatalog.shared.refresh()
|
||||
#expect(TerminalFontCatalog.shared.families.contains("Menlo"))
|
||||
#expect(!TerminalFontCatalog.shared.families.contains("Helvetica"))
|
||||
let catalogFamilies = TerminalFontCatalog.shared.families
|
||||
let manager = NSFontManager.shared
|
||||
|
||||
let fixedPitchFamilies = manager.availableFontFamilies.filter { family in
|
||||
NSFontDescriptor(fontAttributes: [.family: family])
|
||||
.matchingFontDescriptors(withMandatoryKeys: [.family])
|
||||
.contains { NSFont(descriptor: $0, size: 14)?.isFixedPitch == true }
|
||||
}
|
||||
.sorted { $0.localizedStandardCompare($1) == .orderedAscending }
|
||||
|
||||
#expect(!catalogFamilies.isEmpty)
|
||||
#expect(catalogFamilies == fixedPitchFamilies)
|
||||
|
||||
let proportionalFamily = manager.availableFontFamilies.first { family in
|
||||
let fonts = NSFontDescriptor(fontAttributes: [.family: family])
|
||||
.matchingFontDescriptors(withMandatoryKeys: [.family])
|
||||
.compactMap { NSFont(descriptor: $0, size: 14) }
|
||||
return !fonts.isEmpty && fonts.allSatisfy { !$0.isFixedPitch }
|
||||
}
|
||||
#expect(proportionalFamily != nil)
|
||||
if let proportionalFamily {
|
||||
#expect(!catalogFamilies.contains(proportionalFamily))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
import TermindInfrastructure
|
||||
@@ -9,6 +11,31 @@ import Testing
|
||||
@testable import Termind
|
||||
|
||||
struct TerminalThemeTests {
|
||||
@Test
|
||||
func appearanceOptionsAreFilteredAndSortedByName() {
|
||||
let lightNames = TermindTerminalThemeCatalog.lightThemes.map(\.name)
|
||||
let darkNames = TermindTerminalThemeCatalog.darkThemes.map(\.name)
|
||||
|
||||
#expect(lightNames == lightNames.sorted())
|
||||
#expect(darkNames == darkNames.sorted())
|
||||
#expect(TermindTerminalThemeCatalog.lightThemes.allSatisfy { !$0.isDark })
|
||||
#expect(TermindTerminalThemeCatalog.darkThemes.allSatisfy { $0.isDark })
|
||||
}
|
||||
|
||||
@Test
|
||||
func resolverFallsBackForMissingOrWrongAppearancePreferences() {
|
||||
let preferences = TerminalPreferences(
|
||||
darkTheme: SettingsKey.defaultTerminalThemeLight,
|
||||
lightTheme: "Missing Light Theme"
|
||||
)
|
||||
|
||||
let light = TermindTerminalThemeCatalog.resolvedTheme(named: preferences.lightTheme, isDark: false)
|
||||
let dark = TermindTerminalThemeCatalog.resolvedTheme(named: preferences.darkTheme, isDark: true)
|
||||
|
||||
#expect(light.name == SettingsKey.defaultTerminalThemeLight)
|
||||
#expect(dark.name == SettingsKey.defaultTerminalThemeDark)
|
||||
}
|
||||
|
||||
@Test
|
||||
func unavailableThemesFallBackForBothAppearances() {
|
||||
let light = TermindTerminalThemeCatalog.background(
|
||||
@@ -89,4 +116,90 @@ struct TerminalThemeTests {
|
||||
|
||||
#expect(background == TermindTerminalPalette.termindDark.background)
|
||||
}
|
||||
|
||||
@Test
|
||||
func changingBaseThemeWithCustomOverlayPreservesOverrideAndChangesUncoveredColors() throws {
|
||||
let first = TermindTerminalThemeCatalog.defaultDark
|
||||
let second = try #require(
|
||||
TermindTerminalThemeCatalog.darkThemes.first {
|
||||
$0.name != first.name && $0.foreground != first.foreground
|
||||
})
|
||||
|
||||
let firstConfiguration = TermindTerminalThemeCatalog.terminalConfiguration(
|
||||
themeName: first.name,
|
||||
customEnabled: true,
|
||||
customText: "background = 010203",
|
||||
isDark: true
|
||||
)
|
||||
let secondConfiguration = TermindTerminalThemeCatalog.terminalConfiguration(
|
||||
themeName: second.name,
|
||||
customEnabled: true,
|
||||
customText: "background = 010203",
|
||||
isDark: true
|
||||
)
|
||||
|
||||
#expect(firstConfiguration.rendered.hasSuffix("background = 010203"))
|
||||
#expect(secondConfiguration.rendered.hasSuffix("background = 010203"))
|
||||
#expect(firstConfiguration.rendered != secondConfiguration.rendered)
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func presentingTerminalSettingsDoesNotRewriteUnavailableThemes() async throws {
|
||||
let initial = TerminalPreferences(
|
||||
darkTheme: SettingsKey.defaultTerminalThemeLight,
|
||||
lightTheme: "Missing Light Theme"
|
||||
)
|
||||
let persistence = RecordingTerminalPreferencesPersistence(initial: initial)
|
||||
let terminalSettings = TerminalSettingsModel(persistence: persistence)
|
||||
let app = AppModel(hosts: HostLibraryModel(), terminalSettings: terminalSettings)
|
||||
let windowModel = WindowModel(workspaces: [])
|
||||
windowModel.openSettings(route: .category(.terminal))
|
||||
let appearance = AppearanceProbe()
|
||||
defer { app.shutdownForTermination() }
|
||||
|
||||
let view = SettingsPageView()
|
||||
.environment(app)
|
||||
.environment(windowModel)
|
||||
.frame(width: 640, height: 1100)
|
||||
.onAppear { appearance.count += 1 }
|
||||
let controller = NSHostingController(rootView: view)
|
||||
let window = NSWindow(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 640, height: 1100),
|
||||
styleMask: [.borderless], backing: .buffered, defer: false
|
||||
)
|
||||
window.isReleasedWhenClosed = false
|
||||
window.contentViewController = controller
|
||||
defer {
|
||||
window.contentViewController = nil
|
||||
window.close()
|
||||
}
|
||||
|
||||
controller.view.layoutSubtreeIfNeeded()
|
||||
for _ in 0..<10 where appearance.count == 0 {
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
controller.view.layoutSubtreeIfNeeded()
|
||||
}
|
||||
|
||||
#expect(appearance.count > 0)
|
||||
#expect(windowModel.settingsRoute == .category(.terminal))
|
||||
#expect(terminalSettings.preferences == initial)
|
||||
#expect(persistence.saveCount == 0)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor private final class RecordingTerminalPreferencesPersistence: TerminalPreferencesPersisting {
|
||||
let initial: TerminalPreferences
|
||||
var saveCount = 0
|
||||
|
||||
init(initial: TerminalPreferences) {
|
||||
self.initial = initial
|
||||
}
|
||||
|
||||
func loadTerminal() throws -> TerminalPreferences { initial }
|
||||
|
||||
func saveTerminal(_ value: TerminalPreferences) throws { saveCount += 1 }
|
||||
}
|
||||
|
||||
@MainActor private final class AppearanceProbe {
|
||||
var count = 0
|
||||
}
|
||||
Reference in new issue
Block a user