import AppKit import CodexBarCore import Testing @testable import CodexBar @MainActor @Suite(.serialized) struct StatusMenuTests { func disableMenuCardsForTesting() { StatusItemController.menuCardRenderingEnabled = false StatusItemController.setMenuRefreshEnabledForTesting(false) } 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 } func makeSettings() -> SettingsStore { let suite = "StatusMenuTests-\(UUID().uuidString)" let defaults = UserDefaults(suiteName: suite)! defaults.removePersistentDomain(forName: suite) let configStore = testConfigStore(suiteName: suite) let settings = SettingsStore( userDefaults: defaults, configStore: configStore, zaiTokenStore: NoopZaiTokenStore(), syntheticTokenStore: NoopSyntheticTokenStore()) settings.providerDetectionCompleted = true return settings } func makeCodexStore(settings: SettingsStore, dashboardAuthorized: Bool) -> UsageStore { let now = Date() let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) store._setSnapshotForTesting( UsageSnapshot( primary: RateWindow( usedPercent: 22, windowMinutes: 300, resetsAt: now.addingTimeInterval(1800), resetDescription: nil), secondary: nil, tertiary: nil, updatedAt: now, identity: ProviderIdentitySnapshot( providerID: .codex, accountEmail: "codex@example.com", accountOrganization: nil, loginMethod: "Plus Plan")), provider: .codex) store.openAIDashboard = OpenAIDashboardSnapshot( signedInEmail: "other@example.com", codeReviewRemainingPercent: 88, codeReviewLimit: RateWindow( usedPercent: 12, windowMinutes: nil, resetsAt: now.addingTimeInterval(3600), resetDescription: nil), creditEvents: [], dailyBreakdown: [], usageBreakdown: [], creditsPurchaseURL: nil, updatedAt: now) store.openAIDashboardAttachmentAuthorized = dashboardAuthorized store.openAIDashboardRequiresLogin = false return store } private func switcherButtons(in menu: NSMenu) -> [NSButton] { guard let switcherView = menu.items.first?.view as? ProviderSwitcherView else { return [] } return switcherView.subviews .compactMap { $0 as? NSButton } .sorted { $0.tag < $1.tag } } private func representedIDs(in menu: NSMenu) -> [String] { menu.items.compactMap { $0.representedObject as? String } } @Test func `alibaba dashboard action follows selected region`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = false settings.alibabaCodingPlanAPIRegion = .chinaMainland 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()) #expect(controller.dashboardURL(for: .alibaba) == AlibabaCodingPlanAPIRegion.chinaMainland.dashboardURL) } @Test func `zai dashboard action follows selected region`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = 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()) settings.zaiAPIRegion = .global #expect(controller.dashboardURL(for: .zai) == ZaiAPIRegion.global.dashboardURL) #expect( controller.dashboardURL(for: .zai)?.absoluteString == "https://z.ai/manage-apikey/coding-plan/personal/my-plan") #expect( controller.dashboardURL( for: .zai, environment: [ZaiSettingsReader.apiHostKey: "open.bigmodel.cn"]) == ZaiAPIRegion.bigmodelCN.dashboardURL) settings.zaiAPIRegion = .bigmodelCN #expect(controller.dashboardURL(for: .zai) == ZaiAPIRegion.bigmodelCN.dashboardURL) #expect(controller.dashboardURL(for: .zai)?.absoluteString == "https://bigmodel.cn/coding-plan/personal/usage") settings.addTokenAccount(provider: .zai, label: "Team", token: "team-token", usageScope: "team") #expect(controller.dashboardURL(for: .zai) == ZaiAPIRegion.bigmodelCN.teamDashboardURL) #expect( controller.dashboardURL(for: .zai)?.absoluteString == "https://bigmodel.cn/coding-plan/team/usage-stats") } @Test func `opencode go dashboard action follows configured workspace`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.opencodegoWorkspaceID = "https://opencode.ai/workspace/wrk_abc123/go" 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()) #expect(controller.dashboardURL(for: .opencodego)? .absoluteString == "https://opencode.ai/workspace/wrk_abc123/go") } @Test func `claude subscription dashboard action opens usage page`() { for plan in ["Claude Pro", "Claude Team"] { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual let fetcher = UsageFetcher() let store = UsageStore( fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) store._setSnapshotForTesting( UsageSnapshot( primary: RateWindow( usedPercent: 12, windowMinutes: 300, resetsAt: nil, resetDescription: nil), secondary: nil, tertiary: nil, updatedAt: Date(), identity: ProviderIdentitySnapshot( providerID: .claude, accountEmail: nil, accountOrganization: nil, loginMethod: plan)), provider: .claude) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) #expect(controller.dashboardURL(for: .claude)?.absoluteString == "https://claude.ai/settings/usage") } } @Test func `remembers provider when menu opens`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true let registry = ProviderRegistry.shared if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: false) } if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: true) } if let geminiMeta = registry.metadata[.gemini] { settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, 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() } let claudeMenu = controller.makeMenu() controller.menuWillOpen(claudeMenu) #expect(controller.lastMenuProvider == .claude) // No providers enabled: fall back to Codex. for provider in UsageProvider.allCases { if let meta = registry.metadata[provider] { settings.setProviderEnabled(provider: provider, metadata: meta, enabled: false) } } let unmappedMenu = controller.makeMenu() controller.menuWillOpen(unmappedMenu) #expect(controller.lastMenuProvider == .codex) } @Test func `merged menu open does not persist resolved provider when selection is nil`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = nil let registry = ProviderRegistry.shared let selectedProviders: Set = [.codex, .claude] for provider in UsageProvider.allCases { guard let metadata = registry.metadata[provider] else { continue } let shouldEnable = selectedProviders.contains(provider) settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable) } 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() } let expectedResolved = store.enabledProviders().first ?? .codex #expect(store.enabledProviders().count > 1) #expect(controller.shouldMergeIcons == true) let menu = controller.makeMenu() #expect(settings.selectedMenuProvider == nil) controller.menuWillOpen(menu) #expect(settings.selectedMenuProvider == nil) #expect(controller.lastMenuProvider == expectedResolved) } @Test func `shortcut closes tracked menu instead of queueing another open`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = 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()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let key = ObjectIdentifier(menu) controller.openMenus[key] = menu #expect(controller.openMenus[key] != nil) #expect(controller.closeOpenMenusFromShortcutIfNeeded() == true) #expect(controller.openMenus.isEmpty) #expect(controller.menuRefreshTasks.isEmpty) #expect(controller.closeOpenMenusFromShortcutIfNeeded() == false) } @Test func `open menu defers store data refresh until next open`() async { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = false let store = self.makeCodexStore(settings: settings, dashboardAuthorized: false) let controller = StatusItemController( store: store, settings: settings, account: UsageFetcher().loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let key = ObjectIdentifier(menu) controller.openMenus[key] = menu StatusItemController.setMenuRefreshEnabledForTesting(true) defer { StatusItemController.resetMenuRefreshEnabledForTesting() } let openedVersion = controller.menuVersions[key] let now = Date() store._setSnapshotForTesting( UsageSnapshot( primary: RateWindow( usedPercent: 11, windowMinutes: 300, resetsAt: now.addingTimeInterval(1800), resetDescription: nil), secondary: nil, tertiary: nil, updatedAt: now, identity: ProviderIdentitySnapshot( providerID: .codex, accountEmail: "codex@example.com", accountOrganization: nil, loginMethod: "Plus Plan")), provider: .codex) for _ in 0..<50 where controller.menuContentVersion == openedVersion { await Task.yield() } let staleVersion = controller.menuContentVersion controller.refreshOpenMenusIfNeeded() #expect(controller.menuContentVersion != openedVersion) #expect(controller.menuVersions[key] == openedVersion) controller.menuDidClose(menu) controller.menuWillOpen(menu) #expect(controller.menuVersions[key] == staleVersion) } @Test func `merged menu refresh uses resolved enabled provider when selection is cleared`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .codex settings.openAIWebAccessEnabled = true let registry = ProviderRegistry.shared if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: false) } if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: true) } if let geminiMeta = registry.metadata[.gemini] { settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: true) } let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let event = CreditEvent(date: Date(), service: "CLI", creditsUsed: 1) let breakdown = OpenAIDashboardSnapshot.makeDailyBreakdown(from: [event], maxDays: 30) store.openAIDashboard = OpenAIDashboardSnapshot( signedInEmail: "user@example.com", codeReviewRemainingPercent: 100, creditEvents: [event], dailyBreakdown: breakdown, usageBreakdown: breakdown, creditsPurchaseURL: nil, updatedAt: Date()) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) defer { controller.releaseStatusItemsForTesting() } let expectedResolved = store.enabledProviders().first ?? .codex #expect(store.enabledProviders().count > 1) #expect(controller.shouldMergeIcons == true) func hasOpenAIWebSubmenus(_ menu: NSMenu) -> Bool { let usageItem = menu.items.first { ($0.representedObject as? String) == "menuCardUsage" } let creditsItem = menu.items.first { ($0.representedObject as? String) == "menuCardCredits" } let hasUsageBreakdown = usageItem?.submenu?.items .contains { ($0.representedObject as? String) == "usageBreakdownChart" } == true let hasCreditsHistory = creditsItem?.submenu?.items .contains { ($0.representedObject as? String) == "creditsHistoryChart" } == true return hasUsageBreakdown || hasCreditsHistory } let menu = controller.makeMenu() controller.menuWillOpen(menu) #expect(controller.lastMenuProvider == expectedResolved) #expect(settings.selectedMenuProvider == nil) #expect(hasOpenAIWebSubmenus(menu) == false) controller.menuContentVersion &+= 1 controller.refreshOpenMenusIfNeeded() #expect(hasOpenAIWebSubmenus(menu) == false) } @Test func `delayed menu refresh skips when refresh disabled during delay`() async { StatusItemController.menuCardRenderingEnabled = false StatusItemController.setMenuRefreshEnabledForTesting(true) StatusItemController.setMenuOpenRefreshDelayForTesting(.milliseconds(50)) defer { StatusItemController.resetMenuOpenRefreshDelayForTesting() StatusItemController.resetMenuRefreshEnabledForTesting() } let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = false let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) var delayedRefreshWakeCount = 0 await withStatusItemControllerForTesting( store: store, settings: settings, fetcher: fetcher, statusBar: self.makeStatusBarForTesting()) { controller in controller.onDelayedMenuRefreshAttemptForTesting = { delayedRefreshWakeCount += 1 } let menu = controller.makeMenu() controller.menuWillOpen(menu) controller.menuRefreshEnabledOverrideForTesting = false try? await Task.sleep(for: .milliseconds(180)) } #expect(delayedRefreshWakeCount == 0) } @Test func `login state callbacks do not attach menus after release`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = 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()) controller.releaseStatusItemsForTesting() #expect(controller.statusItem.menu == nil) #expect(controller.statusItems.isEmpty) controller.activeLoginProvider = .codex let loginTask = Task {} controller.loginTask = loginTask loginTask.cancel() controller.loginTask = nil controller.activeLoginProvider = nil #expect(controller.statusItem.menu == nil) #expect(controller.statusItems.isEmpty) } @Test func `display only dashboard does not show code review in status menu card`() throws { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual let fetcher = UsageFetcher() let store = self.makeCodexStore(settings: settings, dashboardAuthorized: false) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) let model = try #require(controller.menuCardModel(for: .codex)) #expect(model.metrics.contains { $0.id == "code-review" } == false) } @Test func `display only dashboard does not show code review in providers pane`() { let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual let store = self.makeCodexStore(settings: settings, dashboardAuthorized: false) let pane = ProvidersPane(settings: settings, store: store) let model = pane._test_menuCardModel(for: .codex) #expect(model.metrics.contains { $0.id == "code-review" } == false) } @Test func `attached dashboard still shows code review in providers pane`() { let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual let store = self.makeCodexStore(settings: settings, dashboardAuthorized: true) let pane = ProvidersPane(settings: settings, store: store) let model = pane._test_menuCardModel(for: .codex) #expect(model.metrics.contains { $0.id == "code-review" && $0.percent == 88 }) } @Test func `open merged menu rebuilds switcher when usage bars mode changes`() async { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .codex settings.usageBarsShowUsed = false let registry = ProviderRegistry.shared for provider in UsageProvider.allCases { guard let metadata = registry.metadata[provider] else { continue } let shouldEnable = provider == .codex || provider == .claude settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable) } 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()) #expect(store.enabledProviders().count == 2) #expect(controller.shouldMergeIcons == true) let menu = controller.makeMenu() controller.menuWillOpen(menu) controller.openMenus[ObjectIdentifier(menu)] = menu controller.menuRefreshEnabledOverrideForTesting = true let initialSwitcher = menu.items.first?.view as? ProviderSwitcherView #expect(initialSwitcher != nil) let initialSwitcherID = initialSwitcher.map(ObjectIdentifier.init) settings.usageBarsShowUsed = true controller.handleProviderConfigChange(reason: "usageBarsShowUsed") for _ in 0..<20 { await Task.yield() } #expect(controller.parentMenuRebuildsDeferredDuringTracking.contains(ObjectIdentifier(menu))) if let initialSwitcherID, let currentSwitcher = menu.items.first?.view as? ProviderSwitcherView { #expect(initialSwitcherID == ObjectIdentifier(currentSwitcher)) } controller.menuDidClose(menu) controller.menuWillOpen(menu) defer { controller.menuDidClose(menu) } let updatedSwitcher = menu.items.first?.view as? ProviderSwitcherView #expect(updatedSwitcher != nil) if let initialSwitcherID, let updatedSwitcher { #expect(initialSwitcherID != ObjectIdentifier(updatedSwitcher)) } } @Test func `merged switcher includes overview tab when multiple providers enabled`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .claude settings.mergedMenuLastSelectedWasOverview = false let registry = ProviderRegistry.shared for provider in UsageProvider.allCases { guard let metadata = registry.metadata[provider] else { continue } let shouldEnable = provider == .codex || provider == .claude settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable) } 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()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let buttons = self.switcherButtons(in: menu) #expect(buttons.count == store.enabledProvidersForDisplay().count + 1) #expect(buttons.contains(where: { $0.tag == 0 })) #expect(buttons.first(where: { $0.state == .on })?.tag == 2) } @Test func `merged switcher overview selection persists without overwriting provider selection`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .claude settings.mergedMenuLastSelectedWasOverview = false let registry = ProviderRegistry.shared for provider in UsageProvider.allCases { guard let metadata = registry.metadata[provider] else { continue } let shouldEnable = provider == .codex || provider == .claude settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable) } 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()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let overviewButton = self.switcherButtons(in: menu).first(where: { $0.tag == 0 }) #expect(overviewButton != nil) overviewButton?.performClick(nil) #expect(settings.mergedMenuLastSelectedWasOverview == true) #expect(settings.selectedMenuProvider == .claude) controller.menuDidClose(menu) let reopenedMenu = controller.makeMenu() controller.menuWillOpen(reopenedMenu) let reopenedSelectedTag = self.switcherButtons(in: reopenedMenu).first(where: { $0.state == .on })?.tag #expect(reopenedSelectedTag == 0) #expect(settings.selectedMenuProvider == .claude) } @Test func `open menu rebuilds switcher when overview availability changes`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .codex settings.mergedMenuLastSelectedWasOverview = false let registry = ProviderRegistry.shared for provider in UsageProvider.allCases { guard let metadata = registry.metadata[provider] else { continue } let shouldEnable = provider == .codex || provider == .claude settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable) } let activeProviders: [UsageProvider] = [.codex, .claude] _ = settings.setMergedOverviewProviderSelection( provider: .codex, isSelected: false, activeProviders: activeProviders) _ = settings.setMergedOverviewProviderSelection( provider: .claude, isSelected: false, activeProviders: activeProviders) 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()) let menu = controller.makeMenu() controller.menuWillOpen(menu) controller.openMenus[ObjectIdentifier(menu)] = menu controller.menuRefreshEnabledOverrideForTesting = true let initialButtons = self.switcherButtons(in: menu) #expect(initialButtons.count == activeProviders.count) _ = settings.setMergedOverviewProviderSelection( provider: .codex, isSelected: true, activeProviders: activeProviders) controller.menuContentVersion &+= 1 controller.menuDidClose(menu) controller.menuWillOpen(menu) let updatedButtons = self.switcherButtons(in: menu) #expect(updatedButtons.count == activeProviders.count + 1) } @Test func `overview tab omits contextual provider actions`() throws { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .codex settings.mergedMenuLastSelectedWasOverview = true let registry = ProviderRegistry.shared for provider in UsageProvider.allCases { guard let metadata = registry.metadata[provider] else { continue } let shouldEnable = provider == .codex || provider == .claude || provider == .cursor settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable) } 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()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let titles = Set(menu.items.map(\.title)) #expect(!titles.contains("Add Account...")) #expect(!titles.contains("Switch Account...")) #expect(!titles.contains("Usage Dashboard")) #expect(!titles.contains("Status Page")) #expect(titles.contains("Refresh")) #expect(titles.contains("Settings...")) #expect(titles.contains("About CodexBar")) #expect(titles.contains("Quit")) let refreshItem = try #require(menu.items.first { $0.title == "Refresh" }) #expect(controller.isPersistentRefreshItem(refreshItem)) #expect(refreshItem.view is PersistentRefreshMenuView) #expect(refreshItem.keyEquivalent.isEmpty) #expect(refreshItem.keyEquivalentModifierMask.isEmpty) let settingsItem = menu.items.first { $0.title == "Settings..." } #expect(settingsItem != nil) #expect(settingsItem?.keyEquivalent == ",") #expect(settingsItem?.keyEquivalentModifierMask == [.command]) let quitItem = menu.items.first { $0.title == "Quit" } #expect(quitItem != nil) #expect(quitItem?.keyEquivalent == "q") #expect(quitItem?.keyEquivalentModifierMask == [.command]) } } @MainActor extension StatusMenuTests { @Test func `status blurb uses wrapped view-backed menu item`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = true settings.refreshFrequency = .manual settings.mergeIcons = false let registry = ProviderRegistry.shared for provider in UsageProvider.allCases { guard let metadata = registry.metadata[provider] else { continue } settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: provider == .codex) } let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let statusText = "An SSL error has occurred and a secure connection to the server cannot be made." store.statuses[.codex] = ProviderStatus( indicator: .critical, description: statusText, updatedAt: nil) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) let menu = controller.makeMenu(for: .codex) controller.menuWillOpen(menu) let statusItem = menu.items.first(where: { $0.toolTip == statusText }) #expect(statusItem != nil) #expect(statusItem?.view != nil) #expect(statusItem?.title.isEmpty == true) #expect(statusItem?.view?.frame.width == 310) } @Test func `provider toggle updates status item visibility`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = false let registry = ProviderRegistry.shared if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true) } if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: true) } if let geminiMeta = registry.metadata[.gemini] { settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, 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.statusItems[.claude]?.isVisible == true) if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: false) } controller.handleProviderConfigChange(reason: "test") #expect(controller.statusItems[.claude] == nil) } @Test func `provider config changes preserve status item instances`() throws { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = false let registry = ProviderRegistry.shared try settings.setProviderEnabled(provider: .codex, metadata: #require(registry.metadata[.codex]), enabled: true) try settings.setProviderEnabled( provider: .claude, metadata: #require(registry.metadata[.claude]), enabled: true) try settings.setProviderEnabled( provider: .gemini, metadata: #require(registry.metadata[.gemini]), 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()) let codexItem = try #require(controller.statusItems[.codex]) #expect(controller.statusItem.autosaveName == "codexbar-merged") #expect(codexItem.autosaveName == "codexbar-codex") try settings.setProviderEnabled( provider: .gemini, metadata: #require(registry.metadata[.gemini]), enabled: true) controller.handleProviderConfigChange(reason: "test") #expect(controller.statusItems[.codex] === codexItem) #expect(controller.statusItems[.codex]?.autosaveName == "codexbar-codex") #expect(controller.statusItems[.gemini]?.autosaveName == "codexbar-gemini") } @Test func `hides open AI web submenus when no history`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .codex let registry = ProviderRegistry.shared if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true) } if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: false) } if let geminiMeta = registry.metadata[.gemini] { settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false) } let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) store.openAIDashboard = OpenAIDashboardSnapshot( signedInEmail: "user@example.com", codeReviewRemainingPercent: 100, creditEvents: [], dailyBreakdown: [], usageBreakdown: [], creditsPurchaseURL: nil, updatedAt: Date()) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) defer { controller.releaseStatusItemsForTesting() } let menu = controller.makeMenu() controller.menuWillOpen(menu) let titles = Set(menu.items.map(\.title)) #expect(!titles.contains("Credits history")) #expect(!titles.contains("Usage breakdown")) } @Test func `hides open AI web submenus when open AI web extras disabled`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .codex settings.openAIWebAccessEnabled = false let registry = ProviderRegistry.shared if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true) } if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: false) } if let geminiMeta = registry.metadata[.gemini] { settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false) } let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let event = CreditEvent(date: Date(), service: "CLI", creditsUsed: 1) let breakdown = OpenAIDashboardSnapshot.makeDailyBreakdown(from: [event], maxDays: 30) store.openAIDashboard = OpenAIDashboardSnapshot( signedInEmail: "user@example.com", codeReviewRemainingPercent: 100, creditEvents: [event], dailyBreakdown: breakdown, usageBreakdown: breakdown, creditsPurchaseURL: nil, updatedAt: Date()) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let titles = Set(menu.items.map(\.title)) #expect(!titles.contains("Credits history")) #expect(!titles.contains("Usage breakdown")) } @Test func `hosted chart submenu matches widened parent menu width`() { let previousMenuCardRendering = StatusItemController.menuCardRenderingEnabled let previousMenuRefresh = StatusItemController.menuRefreshEnabled StatusItemController.menuCardRenderingEnabled = true StatusItemController.setMenuRefreshEnabledForTesting(false) defer { StatusItemController.menuCardRenderingEnabled = previousMenuCardRendering StatusItemController.setMenuRefreshEnabledForTesting(previousMenuRefresh) } let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let event = CreditEvent(date: Date(), service: "CLI", creditsUsed: 1) let breakdown = OpenAIDashboardSnapshot.makeDailyBreakdown(from: [event], maxDays: 30) store.openAIDashboard = OpenAIDashboardSnapshot( signedInEmail: "user@example.com", codeReviewRemainingPercent: 100, creditEvents: [event], dailyBreakdown: breakdown, usageBreakdown: breakdown, creditsPurchaseURL: nil, updatedAt: Date()) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) let parentMenu = NSMenu() parentMenu.autoenablesItems = false let wideItem = NSMenuItem(title: String(repeating: "W", count: 60), action: nil, keyEquivalent: "") parentMenu.addItem(wideItem) let submenu = controller.makeHostedSubviewPlaceholderMenu(chartID: StatusItemController.usageBreakdownChartID) let submenuItem = NSMenuItem(title: "Usage breakdown", action: nil, keyEquivalent: "") submenuItem.submenu = submenu parentMenu.addItem(submenuItem) let parentWidth = ceil(parentMenu.size.width) #expect(parentWidth > 310) controller.hydrateHostedSubviewMenuIfNeeded(submenu) let chartItem = submenu.items.first #expect(chartItem?.representedObject as? String == StatusItemController.usageBreakdownChartID) #expect(chartItem?.view != nil) #expect(abs((chartItem?.view?.frame.width ?? 0) - parentWidth) <= 0.5) } @Test func `hosted storage submenu is height capped and scroll enabled`() { let previousMenuCardRendering = StatusItemController.menuCardRenderingEnabled let previousMenuRefresh = StatusItemController.menuRefreshEnabled StatusItemController.menuCardRenderingEnabled = true StatusItemController.setMenuRefreshEnabledForTesting(false) defer { StatusItemController.menuCardRenderingEnabled = previousMenuCardRendering StatusItemController.setMenuRefreshEnabledForTesting(previousMenuRefresh) } let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.providerStorageFootprintsEnabled = true let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let root = "/Users/test/.claude" store.providerStorageFootprints[.claude] = ProviderStorageFootprint( provider: .claude, totalBytes: 1_756_000_000, paths: [root], missingPaths: [], unreadablePaths: [], components: [ .init(path: "\(root)/projects", totalBytes: 1_500_000_000), .init(path: "\(root)/file-history", totalBytes: 103_000_000), .init(path: "\(root)/telemetry", totalBytes: 51_000_000), .init(path: "\(root)/plugins", totalBytes: 33_000_000), .init(path: "\(root)/history.jsonl", totalBytes: 3_800_000), .init(path: "\(root)/shell-snapshots", totalBytes: 1_500_000), .init(path: "\(root)/plans", totalBytes: 1_100_000), .init(path: "\(root)/paste-cache", totalBytes: 541_000), .init(path: "\(root)/session-env", totalBytes: 208_000), .init(path: "\(root)/todos", totalBytes: 6700), ], updatedAt: Date(timeIntervalSince1970: 0)) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) let submenu = NSMenu() let didAppend = controller.appendStorageBreakdownItem(to: submenu, provider: .claude, width: 310) #expect(didAppend) let item = submenu.items.first #expect(item?.isEnabled == true) #expect((item?.view?.frame.height ?? 0) <= 620) } @Test func `shows open AI web submenus when history exists`() throws { self.disableMenuCardsForTesting() let settings = SettingsStore( configStore: testConfigStore(suiteName: "StatusMenuTests-history"), zaiTokenStore: NoopZaiTokenStore(), syntheticTokenStore: NoopSyntheticTokenStore()) settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .codex settings.openAIWebAccessEnabled = true let registry = ProviderRegistry.shared if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true) } if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: false) } if let geminiMeta = registry.metadata[.gemini] { settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false) } let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let calendar = Calendar(identifier: .gregorian) var components = DateComponents() components.calendar = calendar components.timeZone = TimeZone(secondsFromGMT: 0) components.year = 2025 components.month = 12 components.day = 18 let date = try #require(components.date) let events = [CreditEvent(date: date, service: "CLI", creditsUsed: 1)] let breakdown = OpenAIDashboardSnapshot.makeDailyBreakdown(from: events, maxDays: 30) store.openAIDashboard = OpenAIDashboardSnapshot( signedInEmail: "user@example.com", codeReviewRemainingPercent: 100, creditEvents: events, dailyBreakdown: breakdown, usageBreakdown: breakdown, creditsPurchaseURL: nil, updatedAt: Date()) store.openAIDashboardAttachmentAuthorized = true store.openAIDashboardRequiresLogin = false let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) defer { controller.releaseStatusItemsForTesting() } let menu = controller.makeMenu() controller.menuWillOpen(menu) let usageItem = menu.items.first { ($0.representedObject as? String) == "menuCardUsage" } let creditsItem = menu.items.first { ($0.representedObject as? String) == "menuCardCredits" } let creditsHistoryItem = menu.items.first { item in item.submenu?.items.contains { ($0.representedObject as? String) == "creditsHistoryChart" } == true } #expect( usageItem?.submenu?.items .contains { ($0.representedObject as? String) == "usageBreakdownChart" } == true) #expect(creditsItem == nil && creditsHistoryItem != nil) } @Test func `shows open AI API usage chart submenu without codex web history`() throws { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = false settings.selectedMenuProvider = .openai settings.costUsageEnabled = true settings.costSummaryDisplayStyle = .both let registry = ProviderRegistry.shared let metadata = try #require(registry.metadata[.openai]) settings.setProviderEnabled(provider: .openai, metadata: metadata, enabled: true) let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let now = Date(timeIntervalSince1970: 1_700_000_000) let usage = OpenAIAPIUsageSnapshot( daily: [ OpenAIAPIUsageSnapshot.DailyBucket( day: "2023-11-14", startTime: now, endTime: now.addingTimeInterval(86400), costUSD: 9, requests: 12, inputTokens: 100, cachedInputTokens: 0, outputTokens: 50, totalTokens: 150, lineItems: [], models: []), ], updatedAt: now) store._setSnapshotForTesting(usage.toUsageSnapshot(), provider: .openai) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) defer { controller.releaseStatusItemsForTesting() } let menu = controller.makeMenu(for: .openai) controller.menuWillOpen(menu) let usageItem = menu.items.first { ($0.representedObject as? String) == "menuCardUsage" } #expect(usageItem?.submenu?.items .contains { ($0.representedObject as? String) == StatusItemController.costHistoryChartID } == true) #expect(menu.items.contains { ($0.representedObject as? String) == "menuCardHeader" } == false) #expect(menu.items.contains { ($0.representedObject as? String) == "menuCardExtraUsage" } == false) } @Test func `shows credits before cost in codex menu card sections`() throws { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .codex settings.costUsageEnabled = true settings.costSummaryDisplayStyle = .both let registry = ProviderRegistry.shared let metadata = registry.metadata try settings.setProviderEnabled(provider: .codex, metadata: #require(metadata[.codex]), enabled: true) try settings.setProviderEnabled(provider: .claude, metadata: #require(metadata[.claude]), enabled: false) try settings.setProviderEnabled(provider: .gemini, metadata: #require(metadata[.gemini]), enabled: false) let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) store.credits = CreditsSnapshot(remaining: 100, events: [], updatedAt: Date()) store.openAIDashboard = OpenAIDashboardSnapshot( signedInEmail: "user@example.com", codeReviewRemainingPercent: 100, creditEvents: [], dailyBreakdown: [], usageBreakdown: [], creditsPurchaseURL: nil, updatedAt: Date()) store.openAIDashboardAttachmentAuthorized = true store.openAIDashboardRequiresLogin = false store._setTokenSnapshotForTesting(CostUsageTokenSnapshot( sessionTokens: 123, sessionCostUSD: 0.12, last30DaysTokens: 123, last30DaysCostUSD: 1.23, daily: [ CostUsageDailyReport.Entry( date: "2025-12-23", inputTokens: nil, outputTokens: nil, totalTokens: 123, costUSD: 1.23, modelsUsed: nil, modelBreakdowns: nil), ], updatedAt: Date()), provider: .codex) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) defer { controller.releaseStatusItemsForTesting() } let menu = controller.makeMenu() controller.menuWillOpen(menu) let ids = menu.items.compactMap { $0.representedObject as? String } let creditsIndex = ids.firstIndex(of: "menuCardCredits") let costIndex = ids.firstIndex(of: "menuCardCost") #expect(creditsIndex != nil) #expect(costIndex != nil) #expect(try #require(creditsIndex) < costIndex!) } @Test func `hosted cost submenu preserves provider context after empty hydration`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = false settings.costUsageEnabled = true let registry = ProviderRegistry.shared if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, 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()) let submenu = controller.makeHostedSubviewPlaceholderMenu( chartID: StatusItemController.costHistoryChartID, provider: .codex) #expect(submenu.autoenablesItems == false) #expect(submenu.items.first?.isEnabled == true) controller.hydrateHostedSubviewMenuIfNeeded(submenu) #expect(submenu.items.count == 1) #expect(submenu.items.first?.title == "No data available") #expect(submenu.items.first?.toolTip == UsageProvider.codex.rawValue) store._setTokenSnapshotForTesting(CostUsageTokenSnapshot( sessionTokens: 123, sessionCostUSD: 0.12, last30DaysTokens: 123, last30DaysCostUSD: 1.23, daily: [ CostUsageDailyReport.Entry( date: "2025-12-23", inputTokens: nil, outputTokens: nil, totalTokens: 123, costUSD: 1.23, modelsUsed: nil, modelBreakdowns: nil), ], updatedAt: Date()), provider: .codex) controller.hydrateHostedSubviewMenuIfNeeded(submenu) #expect(submenu.items.count == 1) #expect(submenu.items.first?.title != "No data available") #expect(submenu.items.first?.representedObject as? String == StatusItemController.costHistoryChartID) #expect(submenu.items.first?.isEnabled == true) } @Test func `shows extra usage for claude when using menu card sections`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .claude settings.costUsageEnabled = true settings.costSummaryDisplayStyle = .both settings.claudeWebExtrasEnabled = true let registry = ProviderRegistry.shared if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: false) } if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: true) } if let geminiMeta = registry.metadata[.gemini] { settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false) } let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let identity = ProviderIdentitySnapshot( providerID: .claude, accountEmail: "user@example.com", accountOrganization: nil, loginMethod: "web") let snapshot = UsageSnapshot( primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: "Resets soon"), secondary: nil, tertiary: nil, providerCost: ProviderCostSnapshot( used: 0, limit: 2000, currencyCode: "EUR", period: "Monthly", resetsAt: nil, updatedAt: Date()), updatedAt: Date(), identity: identity) store._setSnapshotForTesting(snapshot, provider: .claude) store._setTokenSnapshotForTesting(CostUsageTokenSnapshot( sessionTokens: 123, sessionCostUSD: 0.12, last30DaysTokens: 123, last30DaysCostUSD: 1.23, daily: [ CostUsageDailyReport.Entry( date: "2025-12-23", inputTokens: nil, outputTokens: nil, totalTokens: 123, costUSD: 1.23, modelsUsed: nil, modelBreakdowns: nil), ], updatedAt: Date()), provider: .claude) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) defer { controller.releaseStatusItemsForTesting() } let menu = controller.makeMenu() controller.menuWillOpen(menu) let ids = menu.items.compactMap { $0.representedObject as? String } #expect(ids.contains("menuCardExtraUsage")) } @Test func `shows vertex cost when usage error present`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .vertexai settings.costUsageEnabled = true settings.costSummaryDisplayStyle = .both let registry = ProviderRegistry.shared if let vertexMeta = registry.metadata[.vertexai] { settings.setProviderEnabled(provider: .vertexai, metadata: vertexMeta, enabled: true) } if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: false) } if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: false) } let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) store._setErrorForTesting("No Vertex AI usage data found for the current project.", provider: .vertexai) store._setTokenSnapshotForTesting(CostUsageTokenSnapshot( sessionTokens: 10, sessionCostUSD: 0.01, last30DaysTokens: 100, last30DaysCostUSD: 1.0, daily: [ CostUsageDailyReport.Entry( date: "2025-12-23", inputTokens: nil, outputTokens: nil, totalTokens: 100, costUSD: 1.0, modelsUsed: nil, modelBreakdowns: nil), ], updatedAt: Date()), provider: .vertexai) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection(), statusBar: self.makeStatusBarForTesting()) defer { controller.releaseStatusItemsForTesting() } let menu = controller.makeMenu() controller.menuWillOpen(menu) let ids = menu.items.compactMap { $0.representedObject as? String } #expect(ids.contains("menuCardCost")) } } extension StatusMenuTests { @Test func `overview tab renders overview rows for all active providers when three or fewer`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .claude settings.mergedMenuLastSelectedWasOverview = true let registry = ProviderRegistry.shared for provider in UsageProvider.allCases { guard let metadata = registry.metadata[provider] else { continue } let shouldEnable = provider == .codex || provider == .claude || provider == .cursor settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable) } 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()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let ids = self.representedIDs(in: menu) let overviewRows = ids.filter { $0.hasPrefix("overviewRow-") } #expect(overviewRows.count == 3) #expect(overviewRows.contains("overviewRow-codex")) #expect(overviewRows.contains("overviewRow-claude")) #expect(overviewRows.contains("overviewRow-cursor")) #expect(ids.contains("menuCard") == false) } @Test func `overview tab honors stored subset when three or fewer`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .claude settings.mergedMenuLastSelectedWasOverview = true let registry = ProviderRegistry.shared for provider in UsageProvider.allCases { guard let metadata = registry.metadata[provider] else { continue } let shouldEnable = provider == .codex || provider == .claude || provider == .cursor settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable) } _ = settings.setMergedOverviewProviderSelection( provider: .claude, isSelected: false, activeProviders: [.codex, .claude, .cursor]) 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()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let ids = self.representedIDs(in: menu) let overviewRows = ids.filter { $0.hasPrefix("overviewRow-") } #expect(overviewRows.count == 2) #expect(overviewRows.contains("overviewRow-codex")) #expect(overviewRows.contains("overviewRow-cursor")) #expect(overviewRows.contains("overviewRow-claude") == false) #expect(ids.contains("menuCard") == false) } @Test func `overview tab with explicit empty selection is hidden and shows provider detail`() { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .codex settings.mergedMenuLastSelectedWasOverview = true settings.mergedOverviewSelectedProviders = [] let registry = ProviderRegistry.shared for provider in UsageProvider.allCases { guard let metadata = registry.metadata[provider] else { continue } let shouldEnable = provider == .codex || provider == .claude || provider == .cursor || provider == .opencode settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable) } 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()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let ids = self.representedIDs(in: menu) let switcherButtons = self.switcherButtons(in: menu) #expect(switcherButtons.count == store.enabledProvidersForDisplay().count) #expect(switcherButtons.contains(where: { $0.title == "Overview" }) == false) #expect(switcherButtons.contains(where: { $0.state == .on && $0.tag == 0 })) #expect(ids.contains("menuCard")) #expect(ids.contains(where: { $0.hasPrefix("overviewRow-") }) == false) #expect(ids.contains("overviewEmptyState") == false) #expect(menu.items.contains(where: { $0.title == "No providers selected for Overview." }) == false) } @Test func `overview rows keep menu item action in rendered mode`() throws { StatusItemController.menuCardRenderingEnabled = true StatusItemController.setMenuRefreshEnabledForTesting(false) defer { self.disableMenuCardsForTesting() } let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .codex settings.mergedMenuLastSelectedWasOverview = true let registry = ProviderRegistry.shared for provider in UsageProvider.allCases { guard let metadata = registry.metadata[provider] else { continue } let shouldEnable = provider == .codex || provider == .claude settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable) } 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()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let claudeRow = try #require(menu.items.first { ($0.representedObject as? String) == "overviewRow-claude" }) #expect(claudeRow.action != nil) #expect(claudeRow.target is StatusItemController) } }