Files
2026-07-13 12:22:33 +08:00

75 lines
3.0 KiB
Swift

import AppKit
import CodexBarCore
import Testing
@testable import CodexBar
@MainActor
struct AppDelegateTests {
@Test
func `builds status controller after launch`() {
let appDelegate = AppDelegate()
var factoryCalls = 0
var ttyShutdowns = 0
let dummyStatusController = DummyStatusController()
let managedCodexAccountCoordinator = ManagedCodexAccountCoordinator()
let settings = SettingsStore(
configStore: testConfigStore(suiteName: "AppDelegateTests"),
zaiTokenStore: NoopZaiTokenStore(),
syntheticTokenStore: NoopSyntheticTokenStore())
let fetcher = UsageFetcher()
let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)
let account = fetcher.loadAccountInfo()
let promotionCoordinator = CodexAccountPromotionCoordinator(
settingsStore: settings,
usageStore: store,
managedAccountCoordinator: managedCodexAccountCoordinator)
appDelegate.terminateActiveProcessesForAppShutdown = {
ttyShutdowns += 1
}
// Install a test factory that records invocations without touching NSStatusBar.
StatusItemController.factory = { _, _, _, _, _, receivedManagedCoordinator, receivedPromotionCoordinator in
factoryCalls += 1
#expect(receivedManagedCoordinator === managedCodexAccountCoordinator)
#expect(receivedPromotionCoordinator === promotionCoordinator)
return dummyStatusController
}
defer { StatusItemController.factory = StatusItemController.defaultFactory }
// configure should not eagerly construct the status controller
appDelegate.configure(.init(
store: store,
settings: settings,
account: account,
selection: PreferencesSelection(),
managedCodexAccountCoordinator: managedCodexAccountCoordinator,
codexAccountPromotionCoordinator: promotionCoordinator))
#expect(factoryCalls == 0)
// construction happens once after launch
appDelegate.applicationDidFinishLaunching(Notification(name: NSApplication.didFinishLaunchingNotification))
#expect(factoryCalls == 1)
// idempotent on subsequent calls
appDelegate.applicationDidFinishLaunching(Notification(name: NSApplication.didFinishLaunchingNotification))
#expect(factoryCalls == 1)
// production termination should ask the status controller to detach AppKit status/menu state
appDelegate.applicationWillTerminate(Notification(name: NSApplication.willTerminateNotification))
#expect(dummyStatusController.shutdowns == 1)
#expect(ttyShutdowns == 1)
}
}
@MainActor
private final class DummyStatusController: StatusItemControlling {
private(set) var shutdowns = 0
func openMenuFromShortcut() {}
func runLoginFlowFromSettings(provider _: UsageProvider) async {}
func prepareForAppShutdown() {
self.shutdowns += 1
}
}