@@ -13,8 +13,9 @@ final class StatusBarTextTests: XCTestCase {
1313 func testBothServices() {
1414 let title = ViewModel.statusBarText(
1515 balance: 42, isLoading: false,
16+ hyperConfigured: true,
1617 claudeFiveHourPercent: 62, claudeSevenDayPercent: 8,
17− zaiFiveHourPercent: nil
18+ zaiFiveHourPercent: nil, zaiWeeklyPercent: nil
1819 )
1920 XCTAssertEqual(title, "⚡42 · 🕐62% · 📅8%")
2021 }
@@ -22,8 +23,9 @@ final class StatusBarTextTests: XCTestCase {
2223 func testHyperOnly() {
2324 let title = ViewModel.statusBarText(
2425 balance: 42, isLoading: false,
26+ hyperConfigured: true,
2527 claudeFiveHourPercent: nil, claudeSevenDayPercent: nil,
26− zaiFiveHourPercent: nil
28+ zaiFiveHourPercent: nil, zaiWeeklyPercent: nil
2729 )
2830 XCTAssertEqual(title, "⚡42")
2931 }
@@ -31,8 +33,9 @@ final class StatusBarTextTests: XCTestCase {
3133 func testClaudeOnly() {
3234 let title = ViewModel.statusBarText(
3335 balance: nil, isLoading: false,
36+ hyperConfigured: false,
3437 claudeFiveHourPercent: 62, claudeSevenDayPercent: 8,
35− zaiFiveHourPercent: nil
38+ zaiFiveHourPercent: nil, zaiWeeklyPercent: nil
3639 )
3740 XCTAssertEqual(title, "🕐62% · 📅8%")
3841 }
@@ -40,29 +43,32 @@ final class StatusBarTextTests: XCTestCase {
4043 func testLoadingWithNothingToShow() {
4144 let title = ViewModel.statusBarText(
4245 balance: nil, isLoading: true,
46+ hyperConfigured: false,
4347 claudeFiveHourPercent: nil, claudeSevenDayPercent: nil,
44− zaiFiveHourPercent: nil
48+ zaiFiveHourPercent: nil, zaiWeeklyPercent: nil
4549 )
46− XCTAssertEqual(title, "⚡…")
50+ XCTAssertEqual(title, "…")
4751 }
4852
4953 func testNothingToShow() {
5054 let title = ViewModel.statusBarText(
5155 balance: nil, isLoading: false,
56+ hyperConfigured: false,
5257 claudeFiveHourPercent: nil, claudeSevenDayPercent: nil,
53− zaiFiveHourPercent: nil
58+ zaiFiveHourPercent: nil, zaiWeeklyPercent: nil
5459 )
55− XCTAssertEqual(title, "⚡?")
60+ XCTAssertEqual(title, "")
5661 }
5762
5863 /// The placeholders only stand in for an *empty* title. Someone who runs Claude Code
59− /// without a Hyper key refreshes every few minutes, and a title that fell back to `⚡…`
64+ /// without a Hyper key refreshes every few minutes, and a title that fell back to `…`
6065 /// each time would blink their percentage away and back.
6166 func testLoadingKeepsAClaudePercentOnScreen() {
6267 let title = ViewModel.statusBarText(
6368 balance: nil, isLoading: true,
69+ hyperConfigured: false,
6470 claudeFiveHourPercent: 62, claudeSevenDayPercent: nil,
65− zaiFiveHourPercent: nil
71+ zaiFiveHourPercent: nil, zaiWeeklyPercent: nil
6672 )
6773 XCTAssertEqual(title, "🕐62%")
6874 }
@@ -70,8 +76,9 @@ final class StatusBarTextTests: XCTestCase {
7076 func testLoadingKeepsAStaleBalanceOnScreen() {
7177 let title = ViewModel.statusBarText(
7278 balance: 42, isLoading: true,
79+ hyperConfigured: true,
7380 claudeFiveHourPercent: 62, claudeSevenDayPercent: 8,
74− zaiFiveHourPercent: nil
81+ zaiFiveHourPercent: nil, zaiWeeklyPercent: nil
7582 )
7683 XCTAssertEqual(title, "⚡42 · 🕐62% · 📅8%")
7784 }
@@ -79,8 +86,9 @@ final class StatusBarTextTests: XCTestCase {
7986 func testZeroPercentIsShownRatherThanOmitted() {
8087 let title = ViewModel.statusBarText(
8188 balance: 42, isLoading: false,
89+ hyperConfigured: true,
8290 claudeFiveHourPercent: 0, claudeSevenDayPercent: 8,
83− zaiFiveHourPercent: nil
91+ zaiFiveHourPercent: nil, zaiWeeklyPercent: nil
8492 )
8593 XCTAssertEqual(title, "⚡42 · 📅8%")
8694 }
@@ -88,8 +96,9 @@ final class StatusBarTextTests: XCTestCase {
8896 func testBothClaudeWindowsZero() {
8997 let title = ViewModel.statusBarText(
9098 balance: 42, isLoading: false,
99+ hyperConfigured: true,
91100 claudeFiveHourPercent: 0, claudeSevenDayPercent: 0,
92− zaiFiveHourPercent: nil
101+ zaiFiveHourPercent: nil, zaiWeeklyPercent: nil
93102 )
94103 XCTAssertEqual(title, "⚡42")
95104 }
@@ -97,8 +106,9 @@ final class StatusBarTextTests: XCTestCase {
97106 func testFiveHourOnly() {
98107 let title = ViewModel.statusBarText(
99108 balance: nil, isLoading: false,
109+ hyperConfigured: false,
100110 claudeFiveHourPercent: 62, claudeSevenDayPercent: nil,
101− zaiFiveHourPercent: nil
111+ zaiFiveHourPercent: nil, zaiWeeklyPercent: nil
102112 )
103113 XCTAssertEqual(title, "🕐62%")
104114 }
@@ -106,19 +116,55 @@ final class StatusBarTextTests: XCTestCase {
106116 func testSevenDayOnly() {
107117 let title = ViewModel.statusBarText(
108118 balance: nil, isLoading: false,
119+ hyperConfigured: false,
109120 claudeFiveHourPercent: nil, claudeSevenDayPercent: 8,
110− zaiFiveHourPercent: nil
121+ zaiFiveHourPercent: nil, zaiWeeklyPercent: nil
111122 )
112123 XCTAssertEqual(title, "📅8%")
113124 }
114125
126+ // MARK: - Hyper not configured
127+
128+ func testHyperNotConfiguredHidesBoltEvenWithBalance() {
129+ // balance is non-nil but Hyper is not configured — should not appear.
130+ // This can happen briefly during the transition when a key is deleted.
131+ let title = ViewModel.statusBarText(
132+ balance: 42, isLoading: false,
133+ hyperConfigured: false,
134+ claudeFiveHourPercent: nil, claudeSevenDayPercent: nil,
135+ zaiFiveHourPercent: nil, zaiWeeklyPercent: nil
136+ )
137+ XCTAssertEqual(title, "")
138+ }
139+
140+ func testHyperNotConfiguredLoadingShowsJustDots() {
141+ let title = ViewModel.statusBarText(
142+ balance: nil, isLoading: true,
143+ hyperConfigured: false,
144+ claudeFiveHourPercent: nil, claudeSevenDayPercent: nil,
145+ zaiFiveHourPercent: nil, zaiWeeklyPercent: nil
146+ )
147+ XCTAssertEqual(title, "…")
148+ }
149+
150+ func testHyperNotConfiguredButClaudeStillShows() {
151+ let title = ViewModel.statusBarText(
152+ balance: nil, isLoading: false,
153+ hyperConfigured: false,
154+ claudeFiveHourPercent: 62, claudeSevenDayPercent: 8,
155+ zaiFiveHourPercent: nil, zaiWeeklyPercent: nil
156+ )
157+ XCTAssertEqual(title, "🕐62% · 📅8%")
158+ }
159+
115160 // MARK: - z.ai in Title
116161
117162 func testZaiOnly() {
118163 let title = ViewModel.statusBarText(
119164 balance: nil, isLoading: false,
165+ hyperConfigured: false,
120166 claudeFiveHourPercent: nil, claudeSevenDayPercent: nil,
121− zaiFiveHourPercent: 12
167+ zaiFiveHourPercent: 12, zaiWeeklyPercent: nil
122168 )
123169 XCTAssertEqual(title, "🤖12%")
124170 }
@@ -126,8 +172,9 @@ final class StatusBarTextTests: XCTestCase {
126172 func testZaiWithHyper() {
127173 let title = ViewModel.statusBarText(
128174 balance: 42, isLoading: false,
175+ hyperConfigured: true,
129176 claudeFiveHourPercent: nil, claudeSevenDayPercent: nil,
130− zaiFiveHourPercent: 12
177+ zaiFiveHourPercent: 12, zaiWeeklyPercent: nil
131178 )
132179 XCTAssertEqual(title, "⚡42 · 🤖12%")
133180 }
@@ -135,8 +182,9 @@ final class StatusBarTextTests: XCTestCase {
135182 func testZaiWithClaude() {
136183 let title = ViewModel.statusBarText(
137184 balance: nil, isLoading: false,
185+ hyperConfigured: false,
138186 claudeFiveHourPercent: 62, claudeSevenDayPercent: 8,
139− zaiFiveHourPercent: 12
187+ zaiFiveHourPercent: 12, zaiWeeklyPercent: nil
140188 )
141189 XCTAssertEqual(title, "🕐62% · 📅8% · 🤖12%")
142190 }
@@ -144,17 +192,19 @@ final class StatusBarTextTests: XCTestCase {
144192 func testAllThreeServices() {
145193 let title = ViewModel.statusBarText(
146194 balance: 42, isLoading: false,
195+ hyperConfigured: true,
147196 claudeFiveHourPercent: 62, claudeSevenDayPercent: 8,
148− zaiFiveHourPercent: 12
197+ zaiFiveHourPercent: 12, zaiWeeklyPercent: 3
149198 )
150− XCTAssertEqual(title, "⚡42 · 🕐62% · 📅8% · 🤖12%")
199+ XCTAssertEqual(title, "⚡42 · 🕐62% · 📅8% · 🤖12% · 📆3%")
151200 }
152201
153202 func testZaiZeroIsOmitted() {
154203 let title = ViewModel.statusBarText(
155204 balance: 42, isLoading: false,
205+ hyperConfigured: true,
156206 claudeFiveHourPercent: nil, claudeSevenDayPercent: nil,
157− zaiFiveHourPercent: 0
207+ zaiFiveHourPercent: 0, zaiWeeklyPercent: 0
158208 )
159209 XCTAssertEqual(title, "⚡42")
160210 }
@@ -162,12 +212,55 @@ final class StatusBarTextTests: XCTestCase {
162212 func testLoadingKeepsZaiPercentOnScreen() {
163213 let title = ViewModel.statusBarText(
164214 balance: nil, isLoading: true,
215+ hyperConfigured: false,
216+ claudeFiveHourPercent: nil, claudeSevenDayPercent: nil,
217+ zaiFiveHourPercent: 12, zaiWeeklyPercent: nil
218+ )
219+ XCTAssertEqual(title, "🤖12%")
220+ }
221+
222+ // MARK: - z.ai Weekly
223+
224+ func testZaiWeeklyOnly() {
225+ let title = ViewModel.statusBarText(
226+ balance: nil, isLoading: false,
227+ hyperConfigured: false,
228+ claudeFiveHourPercent: nil, claudeSevenDayPercent: nil,
229+ zaiFiveHourPercent: nil, zaiWeeklyPercent: 3
230+ )
231+ XCTAssertEqual(title, "📆3%")
232+ }
233+
234+ func testZaiWeeklyWithFiveHour() {
235+ let title = ViewModel.statusBarText(
236+ balance: nil, isLoading: false,
237+ hyperConfigured: false,
165238 claudeFiveHourPercent: nil, claudeSevenDayPercent: nil,
166− zaiFiveHourPercent: 12
239+ zaiFiveHourPercent: 12, zaiWeeklyPercent: 3
240+ )
241+ XCTAssertEqual(title, "🤖12% · 📆3%")
242+ }
243+
244+ func testZaiWeeklyZeroIsOmitted() {
245+ let title = ViewModel.statusBarText(
246+ balance: nil, isLoading: false,
247+ hyperConfigured: false,
248+ claudeFiveHourPercent: nil, claudeSevenDayPercent: nil,
249+ zaiFiveHourPercent: 12, zaiWeeklyPercent: 0
167250 )
168251 XCTAssertEqual(title, "🤖12%")
169252 }
170253
254+ func testLoadingKeepsZaiWeeklyOnScreen() {
255+ let title = ViewModel.statusBarText(
256+ balance: nil, isLoading: true,
257+ hyperConfigured: false,
258+ claudeFiveHourPercent: nil, claudeSevenDayPercent: nil,
259+ zaiFiveHourPercent: nil, zaiWeeklyPercent: 3
260+ )
261+ XCTAssertEqual(title, "📆3%")
262+ }
263+
171264 // MARK: - Claude Window Percentages
172265
173266 func testFiveHourPercentRounds() {
@@ -222,4 +315,4 @@ final class StatusBarTextTests: XCTestCase {
222315 XCTAssertNil(vm.claudeFiveHourPercent)
223316 XCTAssertNil(vm.claudeSevenDayPercent)
224317 }
225−}
318+}