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.
129 lines
4.6 KiB
C#
129 lines
4.6 KiB
C#
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);
|
||
}
|
||
}
|