feat: add system theme toggle command
This commit is contained in:
@@ -53,7 +53,7 @@ import Testing
|
||||
let content = try #require(launcher.window?.contentView)
|
||||
func descendants(_ view: NSView) -> [NSView] { [view] + view.subviews.flatMap(descendants) }
|
||||
let search = try #require(descendants(content).compactMap { $0 as? NSSearchField }.first)
|
||||
search.stringValue = "ct"
|
||||
search.stringValue = command.name
|
||||
launcher.controlTextDidChange(Notification(name: NSControl.textDidChangeNotification, object: search))
|
||||
_ = launcher.control(search, textView: NSTextView(), doCommandBy: #selector(NSResponder.insertNewline(_:)))
|
||||
#expect(search.accessibilityLabel() == "English text to translate into Japanese")
|
||||
|
||||
@@ -33,7 +33,7 @@ import Testing
|
||||
#expect(search.accessibilityLabel() == "Search applications, calculate, or convert units and currencies")
|
||||
}
|
||||
|
||||
@Test(arguments: Application.Command.allCases, [true, false])
|
||||
@Test(arguments: [Application.Command.translate, .emoji, .notes], [true, false])
|
||||
@MainActor func commandsAreFullyVisibleImmediately(_ command: Application.Command, _ fromShortcut: Bool) throws {
|
||||
_ = NSApplication.shared
|
||||
let suite = "ConjurePresentation.\(UUID().uuidString)"
|
||||
@@ -65,6 +65,7 @@ import Testing
|
||||
case .emoji: $0 is NSCollectionView
|
||||
case .translate: $0.accessibilityLabel() == "Japanese translation"
|
||||
case .notes: $0.accessibilityLabel() == "Note editor"
|
||||
case .toggleTheme: false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import AppKit
|
||||
import ConjureKit
|
||||
import Testing
|
||||
@testable import Conjure
|
||||
|
||||
@Test(.serialized, arguments: [true, false]) @MainActor
|
||||
func themeToggleDismissesOnlyOnSuccessAndPreventsOverlappingRequests(_ succeeds: Bool) async throws {
|
||||
_ = NSApplication.shared
|
||||
let suite = "ConjureTheme.\(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, any Error>?
|
||||
var requests = 0
|
||||
let launcher = LauncherWindowController(history: history, defaults: defaults, toggleSystemTheme: {
|
||||
requests += 1
|
||||
try await withCheckedThrowingContinuation { pending = $0 }
|
||||
})
|
||||
let window = try #require(launcher.window)
|
||||
defer {
|
||||
if let sheet = window.attachedSheet {
|
||||
window.endSheet(sheet)
|
||||
sheet.orderOut(nil)
|
||||
}
|
||||
window.orderOut(nil)
|
||||
}
|
||||
// Exercise search and dismissal without activating the test host or another running app.
|
||||
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 = "theme"
|
||||
launcher.controlTextDidChange(Notification(name: NSControl.textDidChangeNotification, object: search))
|
||||
_ = launcher.control(search, textView: NSTextView(), doCommandBy: #selector(NSResponder.insertNewline(_:)))
|
||||
try await waitForTheme("Request started") { pending != nil }
|
||||
#expect(window.isVisible)
|
||||
#expect(history.suggestions(in: entries).first?.id == competitor.id)
|
||||
launcher.toggle(command: .toggleTheme)
|
||||
#expect(requests == 1)
|
||||
let continuation = try #require(pending)
|
||||
if succeeds {
|
||||
continuation.resume()
|
||||
try await waitForTheme("Success dismissed launcher") { !window.isVisible }
|
||||
#expect(history.suggestions(in: entries).first?.id == theme.id)
|
||||
} else {
|
||||
continuation.resume(throwing: NSError(domain: "ThemeTest", code: -1743,
|
||||
userInfo: [NSLocalizedDescriptionKey: "Automation permission denied"]))
|
||||
try await waitForTheme("Failure showed alert") { window.attachedSheet != nil }
|
||||
#expect(window.isVisible)
|
||||
#expect(history.suggestions(in: entries).first?.id == competitor.id)
|
||||
let sheet = try #require(window.attachedSheet)
|
||||
window.endSheet(sheet)
|
||||
sheet.orderOut(nil)
|
||||
try await waitForTheme("Failure alert closed") { window.attachedSheet == nil }
|
||||
}
|
||||
}
|
||||
|
||||
@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 {
|
||||
if condition() { return }
|
||||
try await Task.sleep(for: .milliseconds(1))
|
||||
}
|
||||
try #require(condition(), state)
|
||||
}
|
||||
Reference in New Issue
Block a user