184 lines
7.0 KiB
Swift
184 lines
7.0 KiB
Swift
import AppKit
|
|
import CodexBarCore
|
|
import SwiftUI
|
|
import Testing
|
|
@testable import CodexBar
|
|
|
|
@MainActor
|
|
@Suite(.serialized)
|
|
struct StatusMenuCostMenuCardTests {
|
|
@Test
|
|
func `cost menu shows no detail lines`() {
|
|
let tokenUsage = UsageMenuCardView.Model.TokenUsageSection(
|
|
sessionLine: "Today: $74.83 - 87M tokens",
|
|
monthLine: "Last 30 days: $4,279.64 - 5.7B tokens",
|
|
hintLine: "Costs are estimated from local usage.",
|
|
errorLine: "Cost refresh failed.",
|
|
errorCopyText: nil)
|
|
|
|
let visibleLines = StatusItemController.costMenuVisibleDetailLines(
|
|
tokenUsage: tokenUsage,
|
|
hasSubmenu: true)
|
|
#expect(visibleLines == [])
|
|
|
|
let fallbackTitle = StatusItemController.costMenuFallbackAttributedTitle(visibleDetailLines: visibleLines)
|
|
#expect(fallbackTitle.string == "Cost")
|
|
}
|
|
|
|
@Test
|
|
func `cost menu preserves summary lines without history submenu`() {
|
|
let tokenUsage = UsageMenuCardView.Model.TokenUsageSection(
|
|
sessionLine: "Today: $74.83 - 87M tokens",
|
|
monthLine: "Last 30 days: $4,279.64 - 5.7B tokens",
|
|
hintLine: "Costs are estimated from local usage.",
|
|
errorLine: "Cost refresh failed.",
|
|
errorCopyText: nil)
|
|
|
|
let visibleLines = StatusItemController.costMenuVisibleDetailLines(
|
|
tokenUsage: tokenUsage,
|
|
hasSubmenu: false)
|
|
#expect(visibleLines == [
|
|
"Today: $74.83 - 87M tokens",
|
|
"Last 30 days: $4,279.64 - 5.7B tokens",
|
|
"Cost refresh failed.",
|
|
])
|
|
|
|
let fallbackTitle = StatusItemController.costMenuFallbackAttributedTitle(visibleDetailLines: visibleLines)
|
|
#expect(fallbackTitle.string.contains("Today: $74.83 - 87M tokens"))
|
|
#expect(fallbackTitle.string.contains("Last 30 days: $4,279.64 - 5.7B tokens"))
|
|
#expect(fallbackTitle.string.contains("Cost refresh failed."))
|
|
}
|
|
|
|
@Test
|
|
func `cost menu tooltip preserves hint and error details`() {
|
|
let tokenUsage = UsageMenuCardView.Model.TokenUsageSection(
|
|
sessionLine: "Today: $1.00",
|
|
monthLine: "Last 30 days: $9.00",
|
|
hintLine: "Costs are estimated from local usage.",
|
|
errorLine: "Cost refresh failed.",
|
|
errorCopyText: nil)
|
|
|
|
#expect(StatusItemController.costMenuTooltipLines(tokenUsage: tokenUsage) == [
|
|
"Today: $1.00",
|
|
"Last 30 days: $9.00",
|
|
"Costs are estimated from local usage.",
|
|
"Cost refresh failed.",
|
|
])
|
|
}
|
|
|
|
@Test
|
|
func `cost menu with history submenu omits native tooltip`() {
|
|
let settings = self.makeSettings()
|
|
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: .system)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
|
|
let tokenUsage = UsageMenuCardView.Model.TokenUsageSection(
|
|
sessionLine: "Today: $1.00",
|
|
monthLine: "Last 30 days: $9.00",
|
|
hintLine: "Costs are estimated from local usage.",
|
|
errorLine: nil,
|
|
errorCopyText: nil)
|
|
let submenu = NSMenu()
|
|
|
|
let item = controller.makeCostMenuCardItem(
|
|
model: self.makeModel(tokenUsage: tokenUsage),
|
|
submenu: submenu,
|
|
width: StatusItemController.menuCardBaseWidth)
|
|
|
|
#expect(item.submenu === submenu)
|
|
#expect(item.toolTip == nil)
|
|
}
|
|
|
|
@Test
|
|
func `rendered cost menu keeps long dynamic details inside fixed row width`() throws {
|
|
let previousRendering = StatusItemController.menuCardRenderingEnabled
|
|
StatusItemController.menuCardRenderingEnabled = true
|
|
defer { StatusItemController.menuCardRenderingEnabled = previousRendering }
|
|
|
|
let settings = self.makeSettings()
|
|
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: .system)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
|
|
let width = StatusItemController.menuCardBaseWidth
|
|
let tokenUsage = UsageMenuCardView.Model.TokenUsageSection(
|
|
sessionLine: "Today: $227.42 - 267M tokens - " + String(repeating: "wide ", count: 20),
|
|
monthLine: "Last 30 days: $52,431.09 - 77B tokens - " + String(repeating: "wide ", count: 20),
|
|
hintLine: "Costs are estimated from local usage.",
|
|
errorLine: nil,
|
|
errorCopyText: nil)
|
|
let model = self.makeModel(tokenUsage: tokenUsage)
|
|
|
|
// No history submenu — detail lines are visible and must be clipped to the row width.
|
|
let item = controller.makeCostMenuCardItem(
|
|
model: model,
|
|
submenu: nil,
|
|
width: width)
|
|
let view = try #require(item.view)
|
|
|
|
#expect(view is any MenuCardMeasuring)
|
|
#expect(abs(view.frame.width - width) <= 0.5)
|
|
#expect(item.title == "Cost")
|
|
#expect(item.toolTip?.contains("$52,431.09") == true)
|
|
#expect(item.submenu == nil)
|
|
}
|
|
|
|
private func makeSettings() -> SettingsStore {
|
|
let suite = "StatusMenuCostMenuCardTests-\(UUID().uuidString)"
|
|
let defaults = UserDefaults(suiteName: suite)!
|
|
defaults.removePersistentDomain(forName: suite)
|
|
return SettingsStore(
|
|
userDefaults: defaults,
|
|
configStore: testConfigStore(suiteName: suite),
|
|
zaiTokenStore: NoopZaiTokenStore(),
|
|
syntheticTokenStore: NoopSyntheticTokenStore())
|
|
}
|
|
|
|
private func makeModel(
|
|
tokenUsage: UsageMenuCardView.Model.TokenUsageSection) -> UsageMenuCardView.Model
|
|
{
|
|
UsageMenuCardView.Model(
|
|
provider: .codex,
|
|
providerName: "Codex",
|
|
email: "user@example.com",
|
|
subtitleText: "Updated now",
|
|
subtitleStyle: .info,
|
|
planText: "Pro",
|
|
metrics: [],
|
|
usageNotes: [],
|
|
openAIAPIUsage: nil,
|
|
inlineUsageDashboard: nil,
|
|
creditsText: nil,
|
|
creditsRemaining: nil,
|
|
creditsProgressPercent: nil,
|
|
creditsScaleText: nil,
|
|
creditsHintText: nil,
|
|
creditsHintCopyText: nil,
|
|
providerCost: nil,
|
|
tokenUsage: tokenUsage,
|
|
placeholder: nil,
|
|
progressColor: .blue)
|
|
}
|
|
}
|