fix: run action-only commands without presenting launcher

This commit is contained in:
2026-10-18 18:56:56 +02:00
parent a68694e66b
commit 0074c88e5d
5 changed files with 91 additions and 19 deletions
@@ -59,6 +59,51 @@ func themeToggleDismissesOnlyOnSuccessAndPreventsOverlappingRequests(_ succeeds:
}
}
@MainActor private final class ThemeWindowEvents {
var becameKey = false
}
@Test(.serialized, arguments: [true, false], [true, false]) @MainActor
func actionActivationPreservesWindowVisibilityAndSearch(_ directPresentation: Bool, _ initiallyVisible: Bool) async throws {
_ = NSApplication.shared
let suite = "ConjureBackgroundTheme.\(UUID().uuidString)"
let defaults = UserDefaults(suiteName: suite)!
defer { defaults.removePersistentDomain(forName: suite) }
let history = LaunchHistory(defaults: defaults, now: { Date(timeIntervalSinceReferenceDate: 12_000) })
let theme = Application(command: .toggleTheme)
let competitor = Application(url: URL(fileURLWithPath: "/Applications/Zebra.app"), name: "Zebra")
let entries = [theme, competitor]
history.recordLaunch(of: competitor)
var pending: CheckedContinuation<Void, Never>?
let launcher = LauncherWindowController(history: history, defaults: defaults, toggleSystemTheme: {
await withCheckedContinuation { pending = $0 }
})
let window = try #require(launcher.window)
defer { window.orderOut(nil) }
if initiallyVisible { window.orderFront(nil) }
func descendants(_ view: NSView) -> [NSView] { [view] + view.subviews.flatMap(descendants) }
let content = try #require(window.contentView)
let search = try #require(descendants(content).compactMap { $0 as? NSSearchField }.first)
search.stringValue = "keep this search"
let events = ThemeWindowEvents()
let observer = NotificationCenter.default.addObserver(
forName: NSWindow.didBecomeKeyNotification, object: window, queue: .main
) { _ in MainActor.assumeIsolated { events.becameKey = true } }
defer { NotificationCenter.default.removeObserver(observer) }
if directPresentation { launcher.present(command: .toggleTheme) }
else { launcher.toggle(command: .toggleTheme) }
#expect(window.isVisible == initiallyVisible)
try await waitForTheme("Background action started") { pending != nil }
#expect(window.isVisible == initiallyVisible)
#expect(search.stringValue == "keep this search")
#expect(history.suggestions(in: entries).first?.id == competitor.id)
pending?.resume()
try await waitForTheme("Background action completed") { history.suggestions(in: entries).first?.id == theme.id }
#expect(window.isVisible == initiallyVisible)
#expect(search.stringValue == "keep this search")
#expect(!events.becameKey)
}
@MainActor private func waitForTheme(_ state: Comment, _ condition: () -> Bool) async throws {
// Scheduling deadline only; launch ranking uses the fixed clock above.
for _ in 0..<1_000 {