import AppKit import CodexBarCore import Foundation import Testing @testable import CodexBar /// Regression coverage for battery drain caused by fallback-provider animation. /// See GitHub issues #269, #139. @MainActor @Suite(.serialized) struct BatteryDrainDiagnosticTests { private func ensureAppKitInitialized() { _ = NSApplication.shared } private func makeStatusBarForTesting() -> NSStatusBar { // Use the real system status bar in tests. Creating standalone NSStatusBar instances // has caused AppKit teardown crashes under swiftpm-testing-helper. .system } @Test func `Fallback provider should not animate when all providers are disabled`() { self.ensureAppKitInitialized() let settings = SettingsStore( configStore: testConfigStore(suiteName: "BatteryDrain-AllDisabled"), zaiTokenStore: NoopZaiTokenStore(), syntheticTokenStore: NoopSyntheticTokenStore()) settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = false let registry = ProviderRegistry.shared for provider in UsageProvider.allCases { if let meta = registry.metadata[provider] { settings.setProviderEnabled(provider: provider, metadata: meta, enabled: false) } } let fetcher = UsageFetcher() let store = UsageStore( fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) defer { controller.releaseStatusItemsForTesting() } #expect( controller.needsMenuBarIconAnimation() == false, "Should not animate when only fallback provider is visible") #expect( controller.animationDriver == nil, "Animation driver should not start for fallback provider") } @Test func `Enabled provider with data should not animate`() { self.ensureAppKitInitialized() let settings = SettingsStore( configStore: testConfigStore(suiteName: "BatteryDrain-HasData"), zaiTokenStore: NoopZaiTokenStore(), syntheticTokenStore: NoopSyntheticTokenStore()) settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .codex let registry = ProviderRegistry.shared if let meta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: meta, enabled: true) } let fetcher = UsageFetcher() let store = UsageStore( fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let snapshot = UsageSnapshot( primary: RateWindow(usedPercent: 50, windowMinutes: nil, resetsAt: nil, resetDescription: nil), secondary: RateWindow(usedPercent: 30, windowMinutes: nil, resetsAt: nil, resetDescription: nil), updatedAt: Date()) store._setSnapshotForTesting(snapshot, provider: .codex) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) defer { controller.releaseStatusItemsForTesting() } #expect( controller.needsMenuBarIconAnimation() == false, "Should not animate when provider has data") #expect( controller.animationDriver == nil, "Animation driver should be nil when data is present") } @Test func `Enabled provider without data should animate`() { self.ensureAppKitInitialized() let settings = SettingsStore( configStore: testConfigStore(suiteName: "BatteryDrain-NoData"), zaiTokenStore: NoopZaiTokenStore(), syntheticTokenStore: NoopSyntheticTokenStore()) settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = false let registry = ProviderRegistry.shared if let meta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: meta, enabled: true) } let fetcher = UsageFetcher() let store = UsageStore( fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) defer { controller.releaseStatusItemsForTesting() } #expect( controller.needsMenuBarIconAnimation() == true, "Should animate when enabled provider has no data") } @Test func `Enabled provider with error should not animate`() { self.ensureAppKitInitialized() let settings = SettingsStore( configStore: testConfigStore(suiteName: "BatteryDrain-ErrorStops"), zaiTokenStore: NoopZaiTokenStore(), syntheticTokenStore: NoopSyntheticTokenStore()) settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = false let registry = ProviderRegistry.shared if let meta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: meta, enabled: true) } let fetcher = UsageFetcher() let store = UsageStore( fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) store._setErrorForTesting("simulated Codex RPC timeout", provider: .codex) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) #expect(store.isStale(provider: .codex) == true) #expect( controller.needsMenuBarIconAnimation() == false, "Should not animate when provider has recorded an error") #expect(controller.animationDriver == nil) } }