Files
ClaudeDo/tests/ClaudeDo.Ui.Tests/ViewModels/UsagePillViewModelTests.cs
T
mika kuns 7cbd4e66ae feat(ui): add usage pill to footer and mission control header
Adds the IWorkerClient/WorkerClient usage surface (GetUsageSnapshot,
GetModelUsage, GetTaskUsage, UsageUpdated event) and a shared
UsagePillViewModel hosted once in IslandsShellViewModel (footer) and
once in MissionControlViewModel (header), showing "5h X% · 7d Y%"
with warn/blocked/stale states via existing design tokens. The
OpenMonitorCommand is wired but currently a no-op, pending the usage
monitor modal.
2026-08-05 13:16:29 +02:00

129 lines
4.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels;
using Xunit;
namespace ClaudeDo.Ui.Tests.ViewModels;
public class UsagePillViewModelTests
{
private sealed class FakeWorker : StubWorkerClient
{
public UsageSnapshotDto? InitialSnapshot;
public override Task<UsageSnapshotDto?> GetUsageSnapshotAsync() => Task.FromResult(InitialSnapshot);
}
private static UsageSnapshotDto Snapshot(
double? fiveHourPercent = null,
DateTimeOffset? fiveHourResetsAt = null,
double? sevenDayPercent = null,
DateTimeOffset? sevenDayResetsAt = null,
int fiveHourThresholdPct = 80,
int sevenDayThresholdPct = 90,
bool isGateBlocked = false,
string? gateReason = null,
DateTime? fetchedAtUtc = null,
bool isStale = false,
string? lastError = null)
=> new(
fiveHourPercent, fiveHourResetsAt, sevenDayPercent, sevenDayResetsAt,
Array.Empty<UsageLimitDto>(), fiveHourThresholdPct, sevenDayThresholdPct,
isGateBlocked, gateReason, fetchedAtUtc ?? DateTime.UtcNow, isStale, lastError);
// ── Text formatting ─────────────────────────────────────────────────────
[Fact]
public void Text_BothBucketsKnown_ShowsBoth()
{
var worker = new FakeWorker { InitialSnapshot = Snapshot(fiveHourPercent: 30, sevenDayPercent: 57) };
var vm = new UsagePillViewModel(worker);
Assert.Contains("5h", vm.Text);
Assert.Contains("30", vm.Text);
Assert.Contains("7d", vm.Text);
Assert.Contains("57", vm.Text);
}
[Fact]
public void Text_OnlyFiveHourKnown_ShowsOnlyFiveHour()
{
var worker = new FakeWorker { InitialSnapshot = Snapshot(fiveHourPercent: 42) };
var vm = new UsagePillViewModel(worker);
Assert.Contains("5h", vm.Text);
Assert.Contains("42", vm.Text);
Assert.DoesNotContain("7d", vm.Text);
}
[Fact]
public void Text_NoSnapshot_ShowsEmptyPlaceholder()
{
var worker = new FakeWorker();
var vm = new UsagePillViewModel(worker);
Assert.Equal("Usage ", vm.Text);
}
// ── IsWarn / IsBlocked threshold logic ──────────────────────────────────
[Fact]
public void IsWarn_BelowThresholdMinusTen_IsFalse()
{
var worker = new FakeWorker { InitialSnapshot = Snapshot(fiveHourPercent: 69, fiveHourThresholdPct: 80) };
var vm = new UsagePillViewModel(worker);
Assert.False(vm.IsWarn);
Assert.False(vm.IsBlocked);
}
[Fact]
public void IsWarn_AtThresholdMinusTen_IsTrue()
{
var worker = new FakeWorker { InitialSnapshot = Snapshot(fiveHourPercent: 70, fiveHourThresholdPct: 80) };
var vm = new UsagePillViewModel(worker);
Assert.True(vm.IsWarn);
Assert.False(vm.IsBlocked);
}
[Fact]
public void IsBlocked_WhenGateReportsBlocked_IsTrue()
{
var worker = new FakeWorker
{
InitialSnapshot = Snapshot(fiveHourPercent: 80, fiveHourThresholdPct: 80, isGateBlocked: true, gateReason: "5h-Limit 80% >= 80%")
};
var vm = new UsagePillViewModel(worker);
Assert.True(vm.IsBlocked);
}
// ── Stale ────────────────────────────────────────────────────────────────
[Fact]
public void Stale_Snapshot_SetsIsStale_AndTooltipMentionsStamp()
{
var fetchedAt = new DateTime(2026, 8, 5, 9, 41, 0, DateTimeKind.Utc);
var worker = new FakeWorker { InitialSnapshot = Snapshot(isStale: true, fetchedAtUtc: fetchedAt, lastError: "timeout") };
var vm = new UsagePillViewModel(worker);
Assert.True(vm.IsStale);
Assert.Contains(fetchedAt.ToLocalTime().ToString("HH:mm"), vm.Tooltip);
Assert.Contains("timeout", vm.Tooltip);
}
// ── UsageUpdated event ───────────────────────────────────────────────────
[Fact]
public void UsageUpdated_Event_UpdatesText()
{
var worker = new FakeWorker();
var vm = new UsagePillViewModel(worker);
Assert.Equal("Usage ", vm.Text);
worker.RaiseUsageUpdated(Snapshot(fiveHourPercent: 12));
Assert.Contains("12", vm.Text);
}
}