@@ -1,7 +1,7 @@
11import XCTest
22@testable import UsageMenubar
33
4โ/// The menu bar title and the Claude number it is built from.
4+/// The menu bar title and the Claude numbers it is built from.
55///
66/// Balances are kept under 1,000 so the assertions don't depend on the grouping
77/// separator of whatever locale the test happens to run in.
@@ -11,27 +11,42 @@ final class StatusBarTextTests: XCTestCase {
1111 // MARK: - Title
1212
1313 func testBothServices() {
14โ let title = ViewModel.statusBarText(balance: 42, isLoading: false, claudePercent: 62)
15โ XCTAssertEqual(title, "โก42 ยท 62%")
14+ let title = ViewModel.statusBarText(
15+ balance: 42, isLoading: false,
16+ claudeFiveHourPercent: 62, claudeSevenDayPercent: 8
17+ )
18+ XCTAssertEqual(title, "โก42 ยท ๐62% ยท ๐
8%")
1619 }
1720
1821 func testHyperOnly() {
19โ let title = ViewModel.statusBarText(balance: 42, isLoading: false, claudePercent: nil)
22+ let title = ViewModel.statusBarText(
23+ balance: 42, isLoading: false,
24+ claudeFiveHourPercent: nil, claudeSevenDayPercent: nil
25+ )
2026 XCTAssertEqual(title, "โก42")
2127 }
2228
2329 func testClaudeOnly() {
24โ let title = ViewModel.statusBarText(balance: nil, isLoading: false, claudePercent: 62)
25โ XCTAssertEqual(title, "62%")
30+ let title = ViewModel.statusBarText(
31+ balance: nil, isLoading: false,
32+ claudeFiveHourPercent: 62, claudeSevenDayPercent: 8
33+ )
34+ XCTAssertEqual(title, "๐62% ยท ๐
8%")
2635 }
2736
2837 func testLoadingWithNothingToShow() {
29โ let title = ViewModel.statusBarText(balance: nil, isLoading: true, claudePercent: nil)
38+ let title = ViewModel.statusBarText(
39+ balance: nil, isLoading: true,
40+ claudeFiveHourPercent: nil, claudeSevenDayPercent: nil
41+ )
3042 XCTAssertEqual(title, "โกโฆ")
3143 }
3244
3345 func testNothingToShow() {
34โ let title = ViewModel.statusBarText(balance: nil, isLoading: false, claudePercent: nil)
46+ let title = ViewModel.statusBarText(
47+ balance: nil, isLoading: false,
48+ claudeFiveHourPercent: nil, claudeSevenDayPercent: nil
49+ )
3550 XCTAssertEqual(title, "โก?")
3651 }
3752
@@ -39,54 +54,105 @@ final class StatusBarTextTests: XCTestCase {
3954 /// without a Hyper key refreshes every few minutes, and a title that fell back to `โกโฆ`
4055 /// each time would blink their percentage away and back.
4156 func testLoadingKeepsAClaudePercentOnScreen() {
42โ let title = ViewModel.statusBarText(balance: nil, isLoading: true, claudePercent: 62)
43โ XCTAssertEqual(title, "62%")
57+ let title = ViewModel.statusBarText(
58+ balance: nil, isLoading: true,
59+ claudeFiveHourPercent: 62, claudeSevenDayPercent: nil
60+ )
61+ XCTAssertEqual(title, "๐62%")
4462 }
4563
4664 func testLoadingKeepsAStaleBalanceOnScreen() {
47โ let title = ViewModel.statusBarText(balance: 42, isLoading: true, claudePercent: 62)
48โ XCTAssertEqual(title, "โก42 ยท 62%")
65+ let title = ViewModel.statusBarText(
66+ balance: 42, isLoading: true,
67+ claudeFiveHourPercent: 62, claudeSevenDayPercent: 8
68+ )
69+ XCTAssertEqual(title, "โก42 ยท ๐62% ยท ๐
8%")
4970 }
5071
5172 func testZeroPercentIsShownRatherThanOmitted() {
52โ let title = ViewModel.statusBarText(balance: 42, isLoading: false, claudePercent: 0)
53โ XCTAssertEqual(title, "โก42 ยท 0%")
73+ let title = ViewModel.statusBarText(
74+ balance: 42, isLoading: false,
75+ claudeFiveHourPercent: 0, claudeSevenDayPercent: 8
76+ )
77+ XCTAssertEqual(title, "โก42 ยท ๐
8%")
5478 }
5579
56โ // MARK: - Claude Headline
80+ func testBothClaudeWindowsZero() {
81+ let title = ViewModel.statusBarText(
82+ balance: 42, isLoading: false,
83+ claudeFiveHourPercent: 0, claudeSevenDayPercent: 0
84+ )
85+ XCTAssertEqual(title, "โก42")
86+ }
5787
58โ func testHeadlinePrefersTheActiveLimit() {
59โ let usage = ClaudeUsage(
60โ fiveHour: UsageWindow(utilization: 12),
61โ sevenDay: nil,
62โ sevenDayOpus: nil,
63โ limits: [
64โ Limit(percent: 12, isActive: false),
65โ Limit(percent: 68, isActive: true)
66โ ]
88+ func testFiveHourOnly() {
89+ let title = ViewModel.statusBarText(
90+ balance: nil, isLoading: false,
91+ claudeFiveHourPercent: 62, claudeSevenDayPercent: nil
6792 )
68โ XCTAssertEqual(ViewModel.claudeHeadline(for: usage)?.percent, 68)
93+ XCTAssertEqual(title, "๐62%")
6994 }
7095
71โ /// Below the thresholds the server flags no limit at all, and the 5-hour window
72โ /// stands in โ rounded, because the title has no room for a decimal.
73โ func testHeadlineFallsBackToTheRoundedFiveHourWindow() {
96+ func testSevenDayOnly() {
97+ let title = ViewModel.statusBarText(
98+ balance: nil, isLoading: false,
99+ claudeFiveHourPercent: nil, claudeSevenDayPercent: 8
100+ )
101+ XCTAssertEqual(title, "๐
8%")
102+ }
103+
104+ // MARK: - Claude Window Percentages
105+
106+ func testFiveHourPercentRounds() {
74107 let usage = ClaudeUsage(
75108 fiveHour: UsageWindow(utilization: 61.6),
76โ sevenDay: UsageWindow(utilization: 4),
77โ sevenDayOpus: nil,
78โ limits: []
109+ sevenDay: nil, sevenDayOpus: nil, limits: nil
110+ )
111+ let vm = ViewModel()
112+ vm.claudeUsage = usage
113+ XCTAssertEqual(vm.claudeFiveHourPercent, 62)
114+ XCTAssertNil(vm.claudeSevenDayPercent)
115+ }
116+
117+ func testSevenDayPercentRounds() {
118+ let usage = ClaudeUsage(
119+ fiveHour: nil,
120+ sevenDay: UsageWindow(utilization: 4.4),
121+ sevenDayOpus: nil, limits: nil
122+ )
123+ let vm = ViewModel()
124+ vm.claudeUsage = usage
125+ XCTAssertNil(vm.claudeFiveHourPercent)
126+ XCTAssertEqual(vm.claudeSevenDayPercent, 4)
127+ }
128+
129+ func testSevenDayFallsBackToOpus() {
130+ let usage = ClaudeUsage(
131+ fiveHour: nil,
132+ sevenDay: nil,
133+ sevenDayOpus: UsageWindow(utilization: 33.5),
134+ limits: nil
79135 )
80โ XCTAssertEqual(ViewModel.claudeHeadline(for: usage)?.percent, 62)
136+ let vm = ViewModel()
137+ vm.claudeUsage = usage
138+ XCTAssertEqual(vm.claudeSevenDayPercent, 34)
81139 }
82140
83โ func testHeadlineIsNilWithoutUsage() {
84โ XCTAssertNil(ViewModel.claudeHeadline(for: nil))
141+ func testSevenDayDoesNotFallBackToZeroOpus() {
142+ let usage = ClaudeUsage(
143+ fiveHour: nil,
144+ sevenDay: nil,
145+ sevenDayOpus: UsageWindow(utilization: 0),
146+ limits: nil
147+ )
148+ let vm = ViewModel()
149+ vm.claudeUsage = usage
150+ XCTAssertNil(vm.claudeSevenDayPercent)
85151 }
86152
87โ /// The endpoint answers `{}` for an account that has never used the plan.
88โ func testHeadlineIsNilForAnEmptyPayload() {
89โ let usage = ClaudeUsage(fiveHour: nil, sevenDay: nil, sevenDayOpus: nil, limits: nil)
90โ XCTAssertNil(ViewModel.claudeHeadline(for: usage))
153+ func testNilUsageYieldsNilPercents() {
154+ let vm = ViewModel()
155+ XCTAssertNil(vm.claudeFiveHourPercent)
156+ XCTAssertNil(vm.claudeSevenDayPercent)
91157 }
92158}