macOS: GhosttyUITests

This commit is contained in:
Lars
2025-10-13 16:27:49 +02:00
committed by Mitchell Hashimoto
parent 9c5d4a5511
commit 4f667520fa
6 changed files with 239 additions and 13 deletions

View File

@@ -28,6 +28,13 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
810ACCA52E9D3302004F8F92 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A5B30529299BEAAA0047F10C /* Project object */;
proxyType = 1;
remoteGlobalIDString = A5B30530299BEAAA0047F10C;
remoteInfo = Ghostty;
};
A54F45F72E1F047A0046BD5C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A5B30529299BEAAA0047F10C /* Project object */;
@@ -42,6 +49,7 @@
3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyReleaseLocal.entitlements; sourceTree = "<group>"; };
55154BDF2B33911F001622DC /* ghostty */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ghostty; path = "../zig-out/share/ghostty"; sourceTree = "<group>"; };
552964E52B34A9B400030505 /* vim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vim; path = "../zig-out/share/vim"; sourceTree = "<group>"; };
810ACC9F2E9D3301004F8F92 /* GhosttyUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GhosttyUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
9351BE8E2D22937F003B3499 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = nvim; path = "../zig-out/share/nvim"; sourceTree = "<group>"; };
A51BFC282B30F26D00E92F16 /* GhosttyDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyDebug.entitlements; sourceTree = "<group>"; };
A546F1132D7B68D7003B11A0 /* locale */ = {isa = PBXFileReference; lastKnownFileType = folder; name = locale; path = "../zig-out/share/locale"; sourceTree = "<group>"; };
@@ -198,11 +206,19 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
810ACCA02E9D3302004F8F92 /* GhosttyUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = GhosttyUITests; sourceTree = "<group>"; };
81F82BC72E82815D001EDFA7 /* Sources */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (81F82CB12E8281F9001EDFA7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 81F82CB02E8281F5001EDFA7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Sources; sourceTree = "<group>"; };
A54F45F42E1F047A0046BD5C /* Tests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Tests; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
810ACC9C2E9D3301004F8F92 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
A54F45F02E1F047A0046BD5C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -259,6 +275,7 @@
3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */,
81F82BC72E82815D001EDFA7 /* Sources */,
A54F45F42E1F047A0046BD5C /* Tests */,
810ACCA02E9D3302004F8F92 /* GhosttyUITests */,
A5D495A3299BECBA00DD1313 /* Frameworks */,
A5A1F8862A489D7400D1E8BC /* Resources */,
A5B30532299BEAAA0047F10C /* Products */,
@@ -271,6 +288,7 @@
A5B30531299BEAAA0047F10C /* Ghostty.app */,
A5D4499D2B53AE7B000F5B83 /* Ghostty-iOS.app */,
A54F45F32E1F047A0046BD5C /* GhosttyTests.xctest */,
810ACC9F2E9D3301004F8F92 /* GhosttyUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
@@ -287,6 +305,29 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
810ACC9E2E9D3301004F8F92 /* GhosttyUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 810ACCA72E9D3302004F8F92 /* Build configuration list for PBXNativeTarget "GhosttyUITests" */;
buildPhases = (
810ACC9B2E9D3301004F8F92 /* Sources */,
810ACC9C2E9D3301004F8F92 /* Frameworks */,
810ACC9D2E9D3301004F8F92 /* Resources */,
);
buildRules = (
);
dependencies = (
810ACCA62E9D3302004F8F92 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
810ACCA02E9D3302004F8F92 /* GhosttyUITests */,
);
name = GhosttyUITests;
packageProductDependencies = (
);
productName = GhosttyUITests;
productReference = 810ACC9F2E9D3301004F8F92 /* GhosttyUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
A54F45F22E1F047A0046BD5C /* GhosttyTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = A54F45FC2E1F047A0046BD5C /* Build configuration list for PBXNativeTarget "GhosttyTests" */;
@@ -360,9 +401,13 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 2600;
LastSwiftUpdateCheck = 2610;
LastUpgradeCheck = 1610;
TargetAttributes = {
810ACC9E2E9D3301004F8F92 = {
CreatedOnToolsVersion = 26.1;
TestTargetID = A5B30530299BEAAA0047F10C;
};
A54F45F22E1F047A0046BD5C = {
CreatedOnToolsVersion = 26.0;
TestTargetID = A5B30530299BEAAA0047F10C;
@@ -395,11 +440,19 @@
A5B30530299BEAAA0047F10C /* Ghostty */,
A5D4499C2B53AE7B000F5B83 /* Ghostty-iOS */,
A54F45F22E1F047A0046BD5C /* GhosttyTests */,
810ACC9E2E9D3301004F8F92 /* GhosttyUITests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
810ACC9D2E9D3301004F8F92 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
A54F45F12E1F047A0046BD5C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -438,6 +491,13 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
810ACC9B2E9D3301004F8F92 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
A54F45EF2E1F047A0046BD5C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -462,6 +522,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
810ACCA62E9D3302004F8F92 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = A5B30530299BEAAA0047F10C /* Ghostty */;
targetProxy = 810ACCA52E9D3302004F8F92 /* PBXContainerItemProxy */;
};
A54F45F82E1F047A0046BD5C /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = A5B30530299BEAAA0047F10C /* Ghostty */;
@@ -579,6 +644,73 @@
};
name = ReleaseLocal;
};
810ACCA82E9D3302004F8F92 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.1;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.GhosttyUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TEST_TARGET_NAME = Ghostty;
};
name = Debug;
};
810ACCA92E9D3302004F8F92 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.1;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.GhosttyUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TEST_TARGET_NAME = Ghostty;
};
name = Release;
};
810ACCAA2E9D3302004F8F92 /* ReleaseLocal */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.1;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.GhosttyUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TEST_TARGET_NAME = Ghostty;
};
name = ReleaseLocal;
};
A54F45F92E1F047A0046BD5C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -995,6 +1127,16 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
810ACCA72E9D3302004F8F92 /* Build configuration list for PBXNativeTarget "GhosttyUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
810ACCA82E9D3302004F8F92 /* Debug */,
810ACCA92E9D3302004F8F92 /* Release */,
810ACCAA2E9D3302004F8F92 /* ReleaseLocal */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = ReleaseLocal;
};
A54F45FC2E1F047A0046BD5C /* Build configuration list for PBXNativeTarget "GhosttyTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@@ -40,6 +40,17 @@
ReferencedContainer = "container:Ghostty.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "810ACC9E2E9D3301004F8F92"
BuildableName = "GhosttyUITests.xctest"
BlueprintName = "GhosttyUITests"
ReferencedContainer = "container:Ghostty.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction

View File

@@ -0,0 +1,39 @@
//
// GhosttyTitleUITests.swift
// GhosttyUITests
//
// Created by luca on 13.10.2025.
//
import XCTest
final class GhosttyTitleUITests: XCTestCase {
override class var runsForEachTargetApplicationUIConfiguration: Bool {
true
}
var configFile: URL?
override func setUpWithError() throws {
continueAfterFailure = false
let temporaryConfig = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("ghostty")
try #"title = "GhosttyUITestsLaunchTests""#.write(to: temporaryConfig, atomically: true, encoding: .utf8)
configFile = temporaryConfig
}
override func tearDown() async throws {
if let configFile {
try FileManager.default.removeItem(at: configFile)
}
}
@MainActor
func testTitle() throws {
let app = XCUIApplication()
app.launchEnvironment["GHOSTTY_CONFIG_PATH"] = configFile?.path
app.launch()
XCTAssert(app.windows.firstMatch.title == "GhosttyUITestsLaunchTests", "Oops, `title=` doesn't work!")
}
}

View File

@@ -96,7 +96,7 @@ class AppDelegate: NSObject,
private var derivedConfig: DerivedConfig = DerivedConfig()
/// The ghostty global state. Only one per process.
let ghostty: Ghostty.App = Ghostty.App()
let ghostty: Ghostty.App
/// The global undo manager for app-level state such as window restoration.
lazy var undoManager = ExpiringUndoManager()
@@ -155,6 +155,11 @@ class AppDelegate: NSObject,
@Published private(set) var appIcon: NSImage? = nil
override init() {
#if DEBUG
ghostty = Ghostty.App(configPath: ProcessInfo.processInfo.environment["GHOSTTY_CONFIG_PATH"])
#else
ghostty = Ghostty.App()
#endif
super.init()
ghostty.delegate = self

View File

@@ -27,8 +27,10 @@ extension Ghostty {
/// The global app configuration. This defines the app level configuration plus any behavior
/// for new windows, tabs, etc. Note that when creating a new window, it may inherit some
/// configuration (i.e. font size) from the previously focused window. This would override this.
@Published private(set) var config: Config
@Published fileprivate(set) var config: Config
/// Preferred config file than the default ones
private var configPath: String?
/// The ghostty app instance. We only have one of these for the entire app, although I guess
/// in theory you can have multiple... I don't know why you would...
@Published var app: ghostty_app_t? = nil {
@@ -44,9 +46,10 @@ extension Ghostty {
return ghostty_app_needs_confirm_quit(app)
}
init() {
init(configPath: String? = nil) {
self.configPath = configPath
// Initialize the global configuration.
self.config = Config()
self.config = configPath.flatMap({ Self.readConfig(at: $0, finalize: true) }) ?? Config()
if self.config.config == nil {
readiness = .error
return
@@ -143,7 +146,7 @@ extension Ghostty {
}
// Hard or full updates have to reload the full configuration
let newConfig = Config()
let newConfig = configPath.flatMap({ Self.readConfig(at: $0, finalize: true) }) ?? Config()
guard newConfig.loaded else {
Ghostty.logger.warning("failed to reload configuration")
return
@@ -163,7 +166,7 @@ extension Ghostty {
// Hard or full updates have to reload the full configuration.
// NOTE: We never set this on self.config because this is a surface-only
// config. We free it after the call.
let newConfig = Config()
let newConfig = configPath.flatMap({ Self.readConfig(at: $0, finalize: true) }) ?? Config()
guard newConfig.loaded else {
Ghostty.logger.warning("failed to reload configuration")
return
@@ -2074,3 +2077,27 @@ extension Ghostty {
#endif
}
}
extension Ghostty.App {
static func readConfig(at path: String, finalize: Bool = true) -> Ghostty.Config? {
guard
let cfg = ghostty_config_new()
else {
return nil
}
if FileManager.default.fileExists(atPath: path) {
ghostty_config_load_file(cfg, path)
}
if !isRunningInXcode() {
ghostty_config_load_cli_args(cfg)
}
ghostty_config_load_recursive_files(cfg)
if finalize {
// Finalize will make our defaults available,
// and also will combine all the keys into one file,
// we might not need this in the future
ghostty_config_finalize(cfg)
}
return Ghostty.Config(config: cfg)
}
}

View File

@@ -33,14 +33,16 @@ extension Ghostty {
return diags
}
init() {
if let cfg = Self.loadConfig() {
self.config = cfg
}
init(config: ghostty_config_t?) {
self.config = config
}
init(clone config: ghostty_config_t) {
self.config = ghostty_config_clone(config)
convenience init() {
self.init(config: Self.loadConfig())
}
convenience init(clone config: ghostty_config_t) {
self.init(config: ghostty_config_clone(config))
}
deinit {