Files
steipete--codexbar/Tests/CodexBarTests/BatteryDrainDiagnosticTests.swift
2026-07-13 12:22:33 +08:00

191 lines
6.9 KiB
Swift

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)
}
}