185 lines
8.2 KiB
Swift
185 lines
8.2 KiB
Swift
import AppKit
|
|
import CodexBarCore
|
|
import Testing
|
|
@testable import CodexBar
|
|
|
|
/// Regression coverage for the combined "Session + Weekly" menu-bar metric ignoring Claude web's
|
|
/// synthetic `five_hour: null` session placeholder. Claude web parses an account with no live session
|
|
/// (but a real `seven_day` lane) into a 0% 5-hour `primary` with no reset signal; the combined metric
|
|
/// must drop that placeholder so the readout falls back to the weekly lane instead of rendering a
|
|
/// non-existent `5h 0%`/`5h 100%` session. A genuine, freshly reset session (which still carries a
|
|
/// `resetsAt`) must survive the filter.
|
|
@MainActor
|
|
@Suite(.serialized)
|
|
struct StatusItemCombinedMetricPlaceholderTests {
|
|
private func makeStatusBarForTesting() -> NSStatusBar {
|
|
// Use the real system status bar in tests. Standalone NSStatusBar instances have caused
|
|
// AppKit teardown crashes under swiftpm-testing-helper.
|
|
.system
|
|
}
|
|
|
|
/// Builds a Claude-only controller with the combined Session + Weekly metric selected.
|
|
private func makeClaudeCombinedController(
|
|
suiteName: String,
|
|
displayMode: MenuBarDisplayMode,
|
|
showUsed: Bool) -> (controller: StatusItemController, store: UsageStore)
|
|
{
|
|
let settings = SettingsStore(
|
|
configStore: testConfigStore(suiteName: suiteName),
|
|
zaiTokenStore: NoopZaiTokenStore(),
|
|
syntheticTokenStore: NoopSyntheticTokenStore())
|
|
settings.statusChecksEnabled = false
|
|
settings.refreshFrequency = .manual
|
|
settings.mergeIcons = true
|
|
settings.selectedMenuProvider = .claude
|
|
settings.menuBarDisplayMode = displayMode
|
|
settings.usageBarsShowUsed = showUsed
|
|
settings.setMenuBarMetricPreference(.primaryAndSecondary, for: .claude)
|
|
|
|
let registry = ProviderRegistry.shared
|
|
if let claudeMeta = registry.metadata[.claude] {
|
|
settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, 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())
|
|
return (controller, store)
|
|
}
|
|
|
|
@Test
|
|
func `combined metric ignores the claude web null-session placeholder in percent mode`() {
|
|
let (controller, store) = self.makeClaudeCombinedController(
|
|
suiteName: "StatusItemCombinedMetricPlaceholderTests-percent",
|
|
displayMode: .percent,
|
|
showUsed: true)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
|
|
let now = Date()
|
|
// `primary` is the synthetic placeholder Claude web emits for `five_hour: null`: a 0% 5h window
|
|
// flagged `isSyntheticPlaceholder`. `secondary` is the real weekly lane.
|
|
let snapshot = UsageSnapshot(
|
|
primary: RateWindow(
|
|
usedPercent: 0,
|
|
windowMinutes: 300,
|
|
resetsAt: nil,
|
|
resetDescription: nil,
|
|
isSyntheticPlaceholder: true),
|
|
secondary: RateWindow(
|
|
usedPercent: 42,
|
|
windowMinutes: 7 * 24 * 60,
|
|
resetsAt: now.addingTimeInterval(24 * 60 * 60),
|
|
resetDescription: nil),
|
|
updatedAt: now)
|
|
store._setSnapshotForTesting(snapshot, provider: .claude)
|
|
store._setErrorForTesting(nil, provider: .claude)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .claude, snapshot: snapshot)
|
|
|
|
// Weekly lane only — the placeholder session is dropped (no "5h" component).
|
|
#expect(displayText == "W 42%")
|
|
#expect(displayText?.contains("5h") == false)
|
|
}
|
|
|
|
@Test
|
|
func `combined metric ignores the claude web null-session placeholder in both mode`() {
|
|
let (controller, store) = self.makeClaudeCombinedController(
|
|
suiteName: "StatusItemCombinedMetricPlaceholderTests-both",
|
|
displayMode: .both,
|
|
showUsed: false)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
|
|
let now = Date()
|
|
let snapshot = UsageSnapshot(
|
|
primary: RateWindow(
|
|
usedPercent: 0,
|
|
windowMinutes: 300,
|
|
resetsAt: nil,
|
|
resetDescription: nil,
|
|
isSyntheticPlaceholder: true),
|
|
secondary: RateWindow(
|
|
usedPercent: 42,
|
|
windowMinutes: 7 * 24 * 60,
|
|
resetsAt: now.addingTimeInterval(60 * 60),
|
|
resetDescription: nil),
|
|
updatedAt: now)
|
|
store._setSnapshotForTesting(snapshot, provider: .claude)
|
|
store._setErrorForTesting(nil, provider: .claude)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .claude, snapshot: snapshot)
|
|
|
|
// Percent comes from the weekly lane (58% remaining), not the placeholder session's 100%.
|
|
#expect(displayText?.hasPrefix("58%") == true)
|
|
#expect(displayText?.hasPrefix("100%") == false)
|
|
// The placeholder session lane never surfaces, so no "5h" label appears.
|
|
#expect(displayText?.contains("5h") == false)
|
|
}
|
|
|
|
@Test
|
|
func `combined metric keeps a real freshly reset claude session lane`() {
|
|
let (controller, store) = self.makeClaudeCombinedController(
|
|
suiteName: "StatusItemCombinedMetricPlaceholderTests-fresh",
|
|
displayMode: .percent,
|
|
showUsed: true)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
|
|
let now = Date()
|
|
// A real, freshly reset session: 0% used but with a concrete reset time — unlike the placeholder.
|
|
let snapshot = UsageSnapshot(
|
|
primary: RateWindow(
|
|
usedPercent: 0,
|
|
windowMinutes: 300,
|
|
resetsAt: now.addingTimeInterval(4 * 60 * 60),
|
|
resetDescription: nil),
|
|
secondary: RateWindow(
|
|
usedPercent: 42,
|
|
windowMinutes: 7 * 24 * 60,
|
|
resetsAt: now.addingTimeInterval(24 * 60 * 60),
|
|
resetDescription: nil),
|
|
updatedAt: now)
|
|
store._setSnapshotForTesting(snapshot, provider: .claude)
|
|
store._setErrorForTesting(nil, provider: .claude)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .claude, snapshot: snapshot)
|
|
|
|
// The real session lane survives the filter, so both lanes render.
|
|
#expect(displayText == "5h 0% · W 42%")
|
|
}
|
|
|
|
@Test
|
|
func `combined metric keeps an unflagged zero-usage session sharing the placeholder shape`() {
|
|
// Precision guard: a real empty session can share the placeholder's exact RateWindow shape
|
|
// (0% used, 5h cadence, no reset) — e.g. the Claude CLI scrape, where session reset text can be
|
|
// absent. Because the drop keys on the explicit `isSyntheticPlaceholder` marker (set only at the
|
|
// Claude web boundary) rather than the shape, this unflagged session must be kept, not dropped.
|
|
let (controller, store) = self.makeClaudeCombinedController(
|
|
suiteName: "StatusItemCombinedMetricPlaceholderTests-unflagged-shape",
|
|
displayMode: .percent,
|
|
showUsed: true)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
|
|
let now = Date()
|
|
let snapshot = UsageSnapshot(
|
|
primary: RateWindow(usedPercent: 0, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
|
|
secondary: RateWindow(
|
|
usedPercent: 42,
|
|
windowMinutes: 7 * 24 * 60,
|
|
resetsAt: now.addingTimeInterval(24 * 60 * 60),
|
|
resetDescription: nil),
|
|
updatedAt: now)
|
|
store._setSnapshotForTesting(snapshot, provider: .claude)
|
|
store._setErrorForTesting(nil, provider: .claude)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .claude, snapshot: snapshot)
|
|
|
|
// Unflagged session is real, so it renders despite matching the placeholder shape.
|
|
#expect(displayText == "5h 0% · W 42%")
|
|
}
|
|
}
|