import CodexBarCoreimport SwiftUIimport Testing@testable import CodexBar struct CostHistoryChartMenuViewTests { @Test @MainActor func `model breakdown keeps every item behind a bounded scrolling viewport`() { let breakdown = (1...6).map { index in CostUsageDailyReport.ModelBreakdown( modelName: "model-\(index)", costUSD: Double(index), totalTokens: index * 100) } let ordered = CostHistoryChartMenuView.orderedBreakdownItems(breakdown) #expect(ordered.map(\.modelName) == [ "model-6", "model-5", "model-4", "model-3", "model-2", "model-1", ]) #expect(CostHistoryChartMenuView.detailViewportRowCount(itemCount: ordered.count) == 4) #expect(CostHistoryChartMenuView.detailRowsNeedScrolling(itemCount: ordered.count)) } @Test @MainActor func `menu hosting view publishes measured height through intrinsic size`() { let hosting = MenuHostingView(rootView: EmptyView()) hosting.frame = CGRect(x: 0, y: 0, width: 320, height: 1) hosting.applyMeasuredHeight(width: 320, height: 123.2) #expect(hosting.frame.size == CGSize(width: 320, height: 124)) #expect(hosting.intrinsicContentSize.height == 124) } @Test @MainActor func `cost history defaults selection to latest day`() { let daily = [ CostUsageDailyReport.Entry( date: "2026-06-07", inputTokens: 100, outputTokens: 50, totalTokens: 150, costUSD: 1.25, modelsUsed: ["gpt-5.5"], modelBreakdowns: nil), CostUsageDailyReport.Entry( date: "2026-06-09", inputTokens: 200, outputTokens: 75, totalTokens: 275, costUSD: 2.5, modelsUsed: ["gpt-5.5"], modelBreakdowns: nil), ] #expect( CostHistoryChartMenuView._defaultSelectedDateKeyForTesting( provider: .codex, daily: daily) == "2026-06-09") } @Test @MainActor func `cost history detail height follows visible rows and caps at viewport limit`() { let singleRowHeight = CostHistoryChartMenuView._detailViewportHeightForTesting(modeSubtitlePresence: [false]) let twoRowHeight = CostHistoryChartMenuView._detailViewportHeightForTesting(modeSubtitlePresence: [false, true]) let fourRowHeight = CostHistoryChartMenuView._detailViewportHeightForTesting( modeSubtitlePresence: [false, false, false, false]) let fiveRowHeight = CostHistoryChartMenuView._detailViewportHeightForTesting( modeSubtitlePresence: [false, false, false, false, true]) #expect(singleRowHeight < twoRowHeight) #expect(twoRowHeight < fourRowHeight) #expect(fiveRowHeight == fourRowHeight) let emptyBlockHeight = CostHistoryChartMenuView._detailBlockHeightForTesting(modeSubtitlePresence: []) let populatedBlockHeight = CostHistoryChartMenuView._detailBlockHeightForTesting(modeSubtitlePresence: [false]) #expect(emptyBlockHeight < populatedBlockHeight) } @Test @MainActor func `axis dates span first to last for multi-day data`() { let daily = [ CostUsageDailyReport.Entry( date: "2026-05-21", inputTokens: nil, outputTokens: nil, totalTokens: nil, costUSD: 1.0, modelsUsed: nil, modelBreakdowns: nil), CostUsageDailyReport.Entry( date: "2026-06-17", inputTokens: nil, outputTokens: nil, totalTokens: nil, costUSD: 2.0, modelsUsed: nil, modelBreakdowns: nil), ] let dates = CostHistoryChartMenuView._axisDatesForTesting(provider: .codex, daily: daily) let cal = Calendar.current #expect(dates.count == 2) #expect(cal.component(.month, from: dates[0]) == 5) #expect(cal.component(.day, from: dates[0]) == 21) #expect(cal.component(.month, from: dates[1]) == 6) #expect(cal.component(.day, from: dates[1]) == 17) #expect( CostHistoryChartMenuView._axisLabelPlacementForTesting( provider: .codex, daily: daily) == .edges) } @Test @MainActor func `axis dates collapse to one for single-day data`() { let daily = [ CostUsageDailyReport.Entry( date: "2026-06-17", inputTokens: nil, outputTokens: nil, totalTokens: nil, costUSD: 1.0, modelsUsed: nil, modelBreakdowns: nil), ] let dates = CostHistoryChartMenuView._axisDatesForTesting(provider: .codex, daily: daily) #expect(dates.count == 1) #expect( CostHistoryChartMenuView._axisLabelPlacementForTesting( provider: .codex, daily: daily) == .centered) } @Test @MainActor func `axis dates are empty when there is no cost data`() { let daily = [ CostUsageDailyReport.Entry( date: "2026-06-17", inputTokens: nil, outputTokens: nil, totalTokens: nil, costUSD: nil, modelsUsed: nil, modelBreakdowns: nil), ] let dates = CostHistoryChartMenuView._axisDatesForTesting(provider: .codex, daily: daily) #expect(dates.isEmpty) #expect( CostHistoryChartMenuView._axisLabelPlacementForTesting( provider: .codex, daily: daily) == .hidden) } @Test @MainActor func `cost history total card height grows with rows and the total line`() { let oneRow = CostHistoryChartMenuView._totalCardHeightForTesting( modeSubtitlePresence: [false], hasTotal: false) let threeRows = CostHistoryChartMenuView._totalCardHeightForTesting( modeSubtitlePresence: [false, false, false], hasTotal: false) #expect(oneRow < threeRows) let withoutTotal = CostHistoryChartMenuView._totalCardHeightForTesting( modeSubtitlePresence: [false], hasTotal: false) let withTotal = CostHistoryChartMenuView._totalCardHeightForTesting( modeSubtitlePresence: [false], hasTotal: true) #expect(withTotal > withoutTotal) }}
import CodexBarCore
import SwiftUI
import Testing
@testable import CodexBar
struct CostHistoryChartMenuViewTests {
@Test
@MainActor
func `model breakdown keeps every item behind a bounded scrolling viewport`() {
let breakdown = (1...6).map { index in
CostUsageDailyReport.ModelBreakdown(
modelName: "model-\(index)",
costUSD: Double(index),
totalTokens: index * 100)
}
let ordered = CostHistoryChartMenuView.orderedBreakdownItems(breakdown)
#expect(ordered.map(\.modelName) == [
"model-6",
"model-5",
"model-4",
"model-3",
"model-2",
"model-1",
])
#expect(CostHistoryChartMenuView.detailViewportRowCount(itemCount: ordered.count) == 4)
#expect(CostHistoryChartMenuView.detailRowsNeedScrolling(itemCount: ordered.count))
}
@Test
@MainActor
func `menu hosting view publishes measured height through intrinsic size`() {
let hosting = MenuHostingView(rootView: EmptyView())
hosting.frame = CGRect(x: 0, y: 0, width: 320, height: 1)
hosting.applyMeasuredHeight(width: 320, height: 123.2)
#expect(hosting.frame.size == CGSize(width: 320, height: 124))
#expect(hosting.intrinsicContentSize.height == 124)
}
@Test
@MainActor
func `cost history defaults selection to latest day`() {
let daily = [
CostUsageDailyReport.Entry(
date: "2026-06-07",
inputTokens: 100,
outputTokens: 50,
totalTokens: 150,
costUSD: 1.25,
modelsUsed: ["gpt-5.5"],
modelBreakdowns: nil),
CostUsageDailyReport.Entry(
date: "2026-06-09",
inputTokens: 200,
outputTokens: 75,
totalTokens: 275,
costUSD: 2.5,
modelsUsed: ["gpt-5.5"],
modelBreakdowns: nil),
]
#expect(
CostHistoryChartMenuView._defaultSelectedDateKeyForTesting(
provider: .codex,
daily: daily) == "2026-06-09")
}
@Test
@MainActor
func `cost history detail height follows visible rows and caps at viewport limit`() {
let singleRowHeight = CostHistoryChartMenuView._detailViewportHeightForTesting(modeSubtitlePresence: [false])
let twoRowHeight = CostHistoryChartMenuView._detailViewportHeightForTesting(modeSubtitlePresence: [false, true])
let fourRowHeight = CostHistoryChartMenuView._detailViewportHeightForTesting(
modeSubtitlePresence: [false, false, false, false])
let fiveRowHeight = CostHistoryChartMenuView._detailViewportHeightForTesting(
modeSubtitlePresence: [false, false, false, false, true])
#expect(singleRowHeight < twoRowHeight)
#expect(twoRowHeight < fourRowHeight)
#expect(fiveRowHeight == fourRowHeight)
let emptyBlockHeight = CostHistoryChartMenuView._detailBlockHeightForTesting(modeSubtitlePresence: [])
let populatedBlockHeight = CostHistoryChartMenuView._detailBlockHeightForTesting(modeSubtitlePresence: [false])
#expect(emptyBlockHeight < populatedBlockHeight)
}
@Test
@MainActor
func `axis dates span first to last for multi-day data`() {
let daily = [
CostUsageDailyReport.Entry(
date: "2026-05-21",
inputTokens: nil,
outputTokens: nil,
totalTokens: nil,
costUSD: 1.0,
modelsUsed: nil,
modelBreakdowns: nil),
CostUsageDailyReport.Entry(
date: "2026-06-17",
inputTokens: nil,
outputTokens: nil,
totalTokens: nil,
costUSD: 2.0,
modelsUsed: nil,
modelBreakdowns: nil),
]
let dates = CostHistoryChartMenuView._axisDatesForTesting(provider: .codex, daily: daily)
let cal = Calendar.current
#expect(dates.count == 2)
#expect(cal.component(.month, from: dates[0]) == 5)
#expect(cal.component(.day, from: dates[0]) == 21)
#expect(cal.component(.month, from: dates[1]) == 6)
#expect(cal.component(.day, from: dates[1]) == 17)
#expect(
CostHistoryChartMenuView._axisLabelPlacementForTesting(
provider: .codex,
daily: daily) == .edges)
}
@Test
@MainActor
func `axis dates collapse to one for single-day data`() {
let daily = [
CostUsageDailyReport.Entry(
date: "2026-06-17",
inputTokens: nil,
outputTokens: nil,
totalTokens: nil,
costUSD: 1.0,
modelsUsed: nil,
modelBreakdowns: nil),
]
let dates = CostHistoryChartMenuView._axisDatesForTesting(provider: .codex, daily: daily)
#expect(dates.count == 1)
#expect(
CostHistoryChartMenuView._axisLabelPlacementForTesting(
provider: .codex,
daily: daily) == .centered)
}
@Test
@MainActor
func `axis dates are empty when there is no cost data`() {
let daily = [
CostUsageDailyReport.Entry(
date: "2026-06-17",
inputTokens: nil,
outputTokens: nil,
totalTokens: nil,
costUSD: nil,
modelsUsed: nil,
modelBreakdowns: nil),
]
let dates = CostHistoryChartMenuView._axisDatesForTesting(provider: .codex, daily: daily)
#expect(dates.isEmpty)
#expect(
CostHistoryChartMenuView._axisLabelPlacementForTesting(
provider: .codex,
daily: daily) == .hidden)
}
@Test
@MainActor
func `cost history total card height grows with rows and the total line`() {
let oneRow = CostHistoryChartMenuView._totalCardHeightForTesting(
modeSubtitlePresence: [false],
hasTotal: false)
let threeRows = CostHistoryChartMenuView._totalCardHeightForTesting(
modeSubtitlePresence: [false, false, false],
hasTotal: false)
#expect(oneRow < threeRows)
let withoutTotal = CostHistoryChartMenuView._totalCardHeightForTesting(
modeSubtitlePresence: [false],
hasTotal: false)
let withTotal = CostHistoryChartMenuView._totalCardHeightForTesting(
modeSubtitlePresence: [false],
hasTotal: true)
#expect(withTotal > withoutTotal)
}
}