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

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