|
| 1 | +import AppKit |
| 2 | +import CodexBarCore |
| 3 | +import Testing |
| 4 | +@testable import CodexBar |
| 5 | + |
| 6 | +@MainActor |
| 7 | +@Suite(.serialized) |
| 8 | +struct StatusMenuNativeSectionSpacingTests { |
| 9 | + @Test |
| 10 | + func `storage credits and cost never create adjacent separators`() throws { |
| 11 | + let previousRendering = StatusItemController.menuCardRenderingEnabled |
| 12 | + StatusItemController.menuCardRenderingEnabled = true |
| 13 | + defer { StatusItemController.menuCardRenderingEnabled = previousRendering } |
| 14 | + |
| 15 | + let settings = self.makeSettings() |
| 16 | + settings.statusChecksEnabled = false |
| 17 | + settings.refreshFrequency = .manual |
| 18 | + settings.mergeIcons = true |
| 19 | + settings.selectedMenuProvider = .codex |
| 20 | + settings.costUsageEnabled = true |
| 21 | + settings.providerStorageFootprintsEnabled = true |
| 22 | + self.enableOnlyCodex(settings) |
| 23 | + |
| 24 | + let fetcher = UsageFetcher() |
| 25 | + let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) |
| 26 | + let storageRoot = "/Users/test/.codex" |
| 27 | + store.providerStorageFootprints[.codex] = ProviderStorageFootprint( |
| 28 | + provider: .codex, |
| 29 | + totalBytes: 1024, |
| 30 | + paths: [storageRoot], |
| 31 | + missingPaths: [], |
| 32 | + unreadablePaths: [], |
| 33 | + components: [.init(path: storageRoot, totalBytes: 1024)], |
| 34 | + updatedAt: Date()) |
| 35 | + store.credits = CreditsSnapshot(remaining: 100, events: [], updatedAt: Date()) |
| 36 | + store.openAIDashboard = OpenAIDashboardSnapshot( |
| 37 | + signedInEmail: "user@example.com", |
| 38 | + codeReviewRemainingPercent: 100, |
| 39 | + creditEvents: [], |
| 40 | + dailyBreakdown: [], |
| 41 | + usageBreakdown: [], |
| 42 | + creditsPurchaseURL: nil, |
| 43 | + updatedAt: Date()) |
| 44 | + store.openAIDashboardAttachmentAuthorized = true |
| 45 | + store.openAIDashboardRequiresLogin = false |
| 46 | + store._setTokenSnapshotForTesting(CostUsageTokenSnapshot( |
| 47 | + sessionTokens: 123, |
| 48 | + sessionCostUSD: 0.12, |
| 49 | + last30DaysTokens: 123, |
| 50 | + last30DaysCostUSD: 1.23, |
| 51 | + daily: [ |
| 52 | + CostUsageDailyReport.Entry( |
| 53 | + date: "2025-12-23", |
| 54 | + inputTokens: nil, |
| 55 | + outputTokens: nil, |
| 56 | + totalTokens: 123, |
| 57 | + costUSD: 1.23, |
| 58 | + modelsUsed: nil, |
| 59 | + modelBreakdowns: nil), |
| 60 | + ], |
| 61 | + updatedAt: Date()), provider: .codex) |
| 62 | + |
| 63 | + let controller = StatusItemController( |
| 64 | + store: store, |
| 65 | + settings: settings, |
| 66 | + account: fetcher.loadAccountInfo(), |
| 67 | + updater: DisabledUpdaterController(), |
| 68 | + preferencesSelection: PreferencesSelection(), |
| 69 | + statusBar: .system) |
| 70 | + defer { controller.releaseStatusItemsForTesting() } |
| 71 | + |
| 72 | + let menu = controller.makeMenu(for: .codex) |
| 73 | + controller.menuWillOpen(menu) |
| 74 | + let storageIndex = try #require(menu.items.firstIndex { |
| 75 | + ($0.representedObject as? String) == "menuCardStorage" |
| 76 | + }) |
| 77 | + let creditsIndex = try #require(menu.items.firstIndex { |
| 78 | + ($0.representedObject as? String) == "menuCardCredits" |
| 79 | + }) |
| 80 | + let costIndex = try #require(menu.items.firstIndex { |
| 81 | + ($0.representedObject as? String) == "menuCardCost" |
| 82 | + }) |
| 83 | + #expect(storageIndex < creditsIndex) |
| 84 | + #expect(creditsIndex < costIndex) |
| 85 | + #expect(menu.items[costIndex + 1].isSeparatorItem) |
| 86 | + #expect(!zip(menu.items, menu.items.dropFirst()).contains { first, second in |
| 87 | + first.isSeparatorItem && second.isSeparatorItem |
| 88 | + }) |
| 89 | + } |
| 90 | + |
| 91 | + private func makeSettings() -> SettingsStore { |
| 92 | + let suite = "StatusMenuNativeSectionSpacingTests-\(UUID().uuidString)" |
| 93 | + let defaults = UserDefaults(suiteName: suite)! |
| 94 | + defaults.removePersistentDomain(forName: suite) |
| 95 | + let settings = SettingsStore( |
| 96 | + userDefaults: defaults, |
| 97 | + configStore: testConfigStore(suiteName: suite), |
| 98 | + zaiTokenStore: NoopZaiTokenStore(), |
| 99 | + syntheticTokenStore: NoopSyntheticTokenStore()) |
| 100 | + settings.providerDetectionCompleted = true |
| 101 | + return settings |
| 102 | + } |
| 103 | + |
| 104 | + private func enableOnlyCodex(_ settings: SettingsStore) { |
| 105 | + for provider in UsageProvider.allCases { |
| 106 | + guard let metadata = ProviderRegistry.shared.metadata[provider] else { continue } |
| 107 | + settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: provider == .codex) |
| 108 | + } |
| 109 | + } |
| 110 | +} |
0 commit comments