From 1db80968ea0f3a9cc66dcb4d7e82ac6061af61b6 Mon Sep 17 00:00:00 2001 From: Qijia Liu Date: Tue, 3 Nov 2026 14:41:38 -0500 Subject: [PATCH] Quit after closing the last app window --- src/AppDelegate.swift | 8 ++++++++ src/CMakeLists.txt | 1 + src/SimStoreApp.swift | 2 ++ tests/AppLifecycleTest.swift | 32 ++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 4 ++++ 5 files changed, 47 insertions(+) create mode 100644 src/AppDelegate.swift create mode 100644 tests/AppLifecycleTest.swift diff --git a/src/AppDelegate.swift b/src/AppDelegate.swift new file mode 100644 index 0000000..99a5256 --- /dev/null +++ b/src/AppDelegate.swift @@ -0,0 +1,8 @@ +import AppKit + +@MainActor +final class AppDelegate: NSObject, NSApplicationDelegate { + func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + true + } +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 340720e..ef21635 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,5 @@ add_executable(SimStore MACOSX_BUNDLE + AppDelegate.swift SimStoreApp.swift ContentView.swift ) diff --git a/src/SimStoreApp.swift b/src/SimStoreApp.swift index 8d7c407..9a25b88 100644 --- a/src/SimStoreApp.swift +++ b/src/SimStoreApp.swift @@ -2,6 +2,8 @@ import SwiftUI @main struct SimStoreApp: App { + @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate + var body: some Scene { WindowGroup("SimStore") { ContentView() diff --git a/tests/AppLifecycleTest.swift b/tests/AppLifecycleTest.swift new file mode 100644 index 0000000..51e1fe8 --- /dev/null +++ b/tests/AppLifecycleTest.swift @@ -0,0 +1,32 @@ +import AppKit + +@main +struct AppLifecycleTest { + @MainActor + static func main() { + let application = NSApplication.shared + let delegate = AppDelegate() + application.delegate = delegate + application.setActivationPolicy(.regular) + + let window = NSWindow( + contentRect: NSRect(x: 200, y: 200, width: 640, height: 420), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + window.isReleasedWhenClosed = false + window.makeKeyAndOrderFront(nil) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + guard window.isVisible else { + fatalError("The test window did not appear.") + } + print("Closing the last window; the process must exit normally.") + window.performClose(nil) + } + withExtendedLifetime(delegate) { + application.run() + } + fatalError("The application event loop returned without terminating the process.") + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7007897..87cc31a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,6 +5,10 @@ target_compile_options(SimStoreSmokeTest PRIVATE -parse-as-library) add_test(NAME app-window COMMAND SimStoreSmokeTest "$") set_tests_properties(app-window PROPERTIES TIMEOUT 20 RUN_SERIAL TRUE) +add_executable(SimStoreLifecycleTest AppLifecycleTest.swift "${PROJECT_SOURCE_DIR}/src/AppDelegate.swift") +add_test(NAME app-window-close COMMAND SimStoreLifecycleTest) +set_tests_properties(app-window-close PROPERTIES TIMEOUT 10 RUN_SERIAL TRUE) + execute_process(COMMAND xcrun --sdk "${CMAKE_OSX_SYSROOT}" --show-sdk-version OUTPUT_VARIABLE SDK_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE