207 lines
7.4 KiB
Swift
207 lines
7.4 KiB
Swift
import AppKit
|
|
import CodexBarCore
|
|
import Foundation
|
|
import Testing
|
|
@testable import CodexBar
|
|
|
|
struct StatusItemControllerMenuTests {
|
|
@MainActor
|
|
private final class RecordingUpdater: UpdaterProviding {
|
|
var automaticallyChecksForUpdates = false
|
|
var automaticallyDownloadsUpdates = false
|
|
let isAvailable = true
|
|
let unavailableReason: String? = nil
|
|
let updateStatus = UpdateStatus(isUpdateReady: true)
|
|
var checkForUpdatesCount = 0
|
|
var installUpdateCount = 0
|
|
|
|
func checkForUpdates(_ sender: Any?) {
|
|
_ = sender
|
|
self.checkForUpdatesCount += 1
|
|
}
|
|
|
|
func installUpdate() {
|
|
self.installUpdateCount += 1
|
|
}
|
|
}
|
|
|
|
private func makeSnapshot(
|
|
primary: RateWindow?,
|
|
secondary: RateWindow?,
|
|
tertiary: RateWindow? = nil,
|
|
providerCost: ProviderCostSnapshot? = nil)
|
|
-> UsageSnapshot
|
|
{
|
|
UsageSnapshot(
|
|
primary: primary,
|
|
secondary: secondary,
|
|
tertiary: tertiary,
|
|
providerCost: providerCost,
|
|
updatedAt: Date())
|
|
}
|
|
|
|
@Test
|
|
func `cursor switcher falls back to on demand budget when plan exhausted and showing remaining`() {
|
|
let primary = RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil)
|
|
let secondary = RateWindow(usedPercent: 36, windowMinutes: nil, resetsAt: nil, resetDescription: nil)
|
|
let providerCost = ProviderCostSnapshot(
|
|
used: 12,
|
|
limit: 200,
|
|
currencyCode: "USD",
|
|
updatedAt: Date())
|
|
let snapshot = self.makeSnapshot(primary: primary, secondary: secondary, providerCost: providerCost)
|
|
|
|
let percent = StatusItemController.switcherWeeklyMetricPercent(
|
|
for: .cursor,
|
|
snapshot: snapshot,
|
|
showUsed: false)
|
|
|
|
#expect(percent == 94)
|
|
}
|
|
|
|
@Test
|
|
func `cursor switcher uses primary when showing used`() {
|
|
let primary = RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil)
|
|
let secondary = RateWindow(usedPercent: 36, windowMinutes: nil, resetsAt: nil, resetDescription: nil)
|
|
let snapshot = self.makeSnapshot(primary: primary, secondary: secondary)
|
|
|
|
let percent = StatusItemController.switcherWeeklyMetricPercent(
|
|
for: .cursor,
|
|
snapshot: snapshot,
|
|
showUsed: true)
|
|
|
|
#expect(percent == 100)
|
|
}
|
|
|
|
@Test
|
|
func `cursor switcher keeps primary when remaining is positive`() {
|
|
let primary = RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil)
|
|
let secondary = RateWindow(usedPercent: 40, windowMinutes: nil, resetsAt: nil, resetDescription: nil)
|
|
let snapshot = self.makeSnapshot(primary: primary, secondary: secondary)
|
|
|
|
let percent = StatusItemController.switcherWeeklyMetricPercent(
|
|
for: .cursor,
|
|
snapshot: snapshot,
|
|
showUsed: false)
|
|
|
|
#expect(percent == 80)
|
|
}
|
|
|
|
@Test
|
|
func `cursor switcher does not treat auto lane as extra remaining quota`() {
|
|
let primary = RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil)
|
|
let secondary = RateWindow(usedPercent: 36, windowMinutes: nil, resetsAt: nil, resetDescription: nil)
|
|
let snapshot = self.makeSnapshot(primary: primary, secondary: secondary)
|
|
|
|
let percent = StatusItemController.switcherWeeklyMetricPercent(
|
|
for: .cursor,
|
|
snapshot: snapshot,
|
|
showUsed: false)
|
|
|
|
#expect(percent == 0)
|
|
}
|
|
|
|
@Test
|
|
func `perplexity switcher falls back after recurring credits are exhausted`() {
|
|
let primary = RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil)
|
|
let secondary = RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil)
|
|
let tertiary = RateWindow(usedPercent: 24, windowMinutes: nil, resetsAt: nil, resetDescription: nil)
|
|
let snapshot = self.makeSnapshot(primary: primary, secondary: secondary, tertiary: tertiary)
|
|
|
|
let percent = StatusItemController.switcherWeeklyMetricPercent(
|
|
for: .perplexity,
|
|
snapshot: snapshot,
|
|
showUsed: false)
|
|
|
|
#expect(percent == 76)
|
|
}
|
|
|
|
@Test
|
|
func `mistral switcher uses monthly plan metric when selected`() {
|
|
let snapshot = UsageSnapshot(
|
|
primary: nil,
|
|
secondary: nil,
|
|
extraRateWindows: [
|
|
NamedRateWindow(
|
|
id: "mistral-monthly-plan",
|
|
title: "Monthly Plan",
|
|
window: RateWindow(usedPercent: 42, windowMinutes: nil, resetsAt: nil, resetDescription: nil)),
|
|
],
|
|
updatedAt: Date())
|
|
|
|
let percent = StatusItemController.switcherWeeklyMetricPercent(
|
|
for: .mistral,
|
|
snapshot: snapshot,
|
|
showUsed: true,
|
|
preference: .monthlyPlan)
|
|
|
|
#expect(percent == 42)
|
|
}
|
|
|
|
@Test
|
|
func `mistral switcher ignores pay as you go balance primary`() {
|
|
let snapshot = UsageSnapshot(
|
|
primary: RateWindow(
|
|
usedPercent: 0,
|
|
windowMinutes: nil,
|
|
resetsAt: nil,
|
|
resetDescription: "$12.50"),
|
|
secondary: nil,
|
|
updatedAt: Date())
|
|
|
|
let percent = StatusItemController.switcherWeeklyMetricPercent(
|
|
for: .mistral,
|
|
snapshot: snapshot,
|
|
showUsed: true,
|
|
preference: .automatic)
|
|
|
|
#expect(percent == nil)
|
|
}
|
|
|
|
@Test
|
|
@MainActor
|
|
func `menu card width stays at base width when menu accessories are present`() {
|
|
let shortcutMenu = NSMenu()
|
|
let refreshItem = NSMenuItem(title: "Refresh", action: nil, keyEquivalent: "r")
|
|
shortcutMenu.addItem(refreshItem)
|
|
#expect(ceil(shortcutMenu.size.width) < 310)
|
|
|
|
let submenuMenu = NSMenu()
|
|
let parentItem = NSMenuItem(title: "Session", action: nil, keyEquivalent: "")
|
|
parentItem.submenu = NSMenu(title: "Session")
|
|
submenuMenu.addItem(parentItem)
|
|
#expect(ceil(submenuMenu.size.width) < 310)
|
|
}
|
|
|
|
@Test
|
|
@MainActor
|
|
func `update menu action installs prepared update instead of checking again`() throws {
|
|
let suite = "StatusItemControllerMenuTests-\(UUID().uuidString)"
|
|
let defaults = try #require(UserDefaults(suiteName: suite))
|
|
defaults.removePersistentDomain(forName: suite)
|
|
let settings = SettingsStore(
|
|
userDefaults: defaults,
|
|
configStore: testConfigStore(suiteName: suite),
|
|
zaiTokenStore: NoopZaiTokenStore(),
|
|
syntheticTokenStore: NoopSyntheticTokenStore())
|
|
settings.statusChecksEnabled = false
|
|
settings.refreshFrequency = .manual
|
|
|
|
let fetcher = UsageFetcher()
|
|
let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)
|
|
let updater = RecordingUpdater()
|
|
let controller = StatusItemController(
|
|
store: store,
|
|
settings: settings,
|
|
account: fetcher.loadAccountInfo(),
|
|
updater: updater,
|
|
preferencesSelection: PreferencesSelection(),
|
|
statusBar: .system)
|
|
|
|
controller.installUpdate()
|
|
|
|
#expect(updater.installUpdateCount == 1)
|
|
#expect(updater.checkForUpdatesCount == 0)
|
|
}
|
|
}
|