Files
yetone 044f61f50f usage: a custom balance's card says when it was read, shows each of its amounts on a line of its own and a percent as a meter (#420 by emo172: 余额卡片显示读取时间、百分比进度条) — a balance read from a provider's own Balance URL showed one line, "Balance $2.00 · Today $2.50 · Used 90%", and nothing said how old it was. magpie now hands the card the amounts the user's expression names, each with its label, and a "%" one's share; the Usage page shows each on a line of its own, the user's label quiet and the first amount the larger, "Balance" only where no label was given, and a percent as the quota meter, amber from 90%. Every card that was read says so, "Updated 3 minutes ago" (3分钟前更新), and one standing in for a reading that failed just now says "As of … — couldn't be read just now" (截至 …,暂时无法获取最新用量), allowance cards too. The menu bar panel's Balances show the same amounts and meter and an amber "As of" (截至), and the tray's tooltip adds "as of …, couldn't be read just now". One amount alone reads as it did, and the balance line, the CLI and the expression syntax are unchanged. No balance URL, path or site is added: /api/usage/token and unlimited_quota are one kind of relay's and stay the user's to write. TestBalanceParts, TestTrayUsageText and balance-parts.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; balance-parts, click-scroll, old-webkit, quota-families, panel-usage, tray-usages, balance-fix, usage-refresh, currency, claude-usage-asked pass.
2026-10-01 19:44:44 +08:00

88 lines
3.8 KiB
Go

package gui
import (
"testing"
"time"
"github.com/yetone/magpie/internal/provider"
)
func TestTrayUsageText(t *testing.T) {
now := time.Date(2026, 9, 27, 12, 0, 0, 0, time.UTC)
in := func(d time.Duration) *time.Time { at := now.Add(d); return &at }
// the shortest window first, whatever order the vendor gives them in;
// on-demand spending and a single model's window are left out
q := provider.SubscriptionQuota{Provider: "claude", Name: "Claude", User: "a@b.c", Windows: []provider.QuotaWindow{
{Name: "Weekly", Used: 17.6, ResetsAt: in(3*24*time.Hour + 4*time.Hour), Span: 7 * 24 * time.Hour},
{Name: "5-hour", Used: 42.2, ResetsAt: in(2*time.Hour + 10*time.Minute), Span: 5 * time.Hour},
{Name: "Opus weekly", Used: 90, Model: "opus", Span: 7 * 24 * time.Hour},
{Name: "Extra usage", Used: 5, Aside: true},
}}
label, tip := trayUsageText(q, now, false)
if label != "42% · 18%" {
t.Errorf("label %q", label)
}
if want := "Claude\n5-hour 42.2% used · resets in 2h 10m\nWeekly 17.6% used · resets in 3d 4h"; tip != want {
t.Errorf("tip %q, want %q", tip, want)
}
// or what is left of each, as Settings or the Usage page says (#122)
label, tip = trayUsageText(q, now, true)
if label != "58% · 82%" {
t.Errorf("left label %q", label)
}
if want := "Claude\n5-hour 57.8% left · resets in 2h 10m\nWeekly 82.4% left · resets in 3d 4h"; tip != want {
t.Errorf("left tip %q, want %q", tip, want)
}
if id := trayCardID(q); id != "claude|a@b.c" {
t.Errorf("id %q", id)
}
// no lengths known: the vendor's order, two at most, clamped, its own count
q = provider.SubscriptionQuota{Provider: "zcode", Name: "ZCode", Windows: []provider.QuotaWindow{
{Name: "5 小时", Used: 120, Display: "1.2k / 1k"},
{Name: "Weekly", Used: -3, ResetSecs: 90},
{Name: "Monthly", Used: 1},
}}
label, tip = trayUsageText(q, now, false)
if label != "100% · 0%" {
t.Errorf("label %q", label)
}
if want := "ZCode\n5 小时 1.2k / 1k · 100% used\nWeekly 0% used · resets in 2m\nMonthly 1% used"; tip != want {
t.Errorf("tip %q, want %q", tip, want)
}
if id := trayCardID(q); id != "zcode" {
t.Errorf("id %q", id)
}
q = provider.SubscriptionQuota{Name: "Copilot", Windows: []provider.QuotaWindow{{Name: "Premium", Used: 187418.3 / 218000 * 100}}}
label, tip = trayUsageText(q, now, false)
if label != "86%" || tip != "Copilot\nPremium 86.0% used" {
t.Errorf("copilot used: %q %q", label, tip)
}
label, tip = trayUsageText(q, now, true)
if label != "14%" || tip != "Copilot\nPremium 14.0% left" {
t.Errorf("copilot left: %q %q", label, tip)
}
// a balance, an error, nothing
if label, _ = trayUsageText(provider.SubscriptionQuota{Name: "DeepSeek", Balance: "¥12.30"}, now, false); label != "¥12.30" {
t.Errorf("balance label %q", label)
}
// one read a while ago, standing in for one that couldn't be read now,
// says when it was read (#420)
read := time.Date(2026, 9, 27, 9, 5, 0, 0, time.Local)
if label, tip = trayUsageText(provider.SubscriptionQuota{Name: "Relay", Balance: "$4.20", AsOf: &read}, now, false); label != "$4.20" || tip != "Relay · $4.20\nas of Sep 27 09:05, couldn't be read just now" {
t.Errorf("balance as of: %q %q", label, tip)
}
if _, tip = trayUsageText(provider.SubscriptionQuota{Name: "Kimi", AsOf: &read, Windows: []provider.QuotaWindow{{Name: "Weekly", Used: 10}}}, now, false); tip != "Kimi\nWeekly 10% used\nas of Sep 27 09:05, couldn't be read just now" {
t.Errorf("windows as of: %q", tip)
}
if label, tip = trayUsageText(provider.SubscriptionQuota{Name: "Codex", Error: "signed out"}, now, false); label != "" || tip != "Codex: signed out" {
t.Errorf("error: %q %q", label, tip)
}
if label, tip = trayUsageText(provider.SubscriptionQuota{Name: "Empty"}, now, false); label != "" || tip != "" {
t.Errorf("empty: %q %q", label, tip)
}
}