The pill tests asserted on real localized strings but never set Loc.Current, which defaults to a key-echo localizer. They only passed because an unrelated test class happened to install a real Localizer first; adding the usage-monitor modal tests changed the ordering and the assertions started seeing raw keys. Initialize Loc.Current in the constructor, matching every other UI test class.
144 lines
5.2 KiB
C#
144 lines
5.2 KiB
C#
using System;
|
||
using System.IO;
|
||
using ClaudeDo.Localization;
|
||
using ClaudeDo.Ui.Localization;
|
||
using ClaudeDo.Ui.Services;
|
||
using ClaudeDo.Ui.ViewModels;
|
||
using Xunit;
|
||
|
||
namespace ClaudeDo.Ui.Tests.ViewModels;
|
||
|
||
public class UsagePillViewModelTests
|
||
{
|
||
// Loc.Current is ambient static state defaulting to a key-echo localizer, so these
|
||
// assertions on real strings must install a real one instead of relying on whichever
|
||
// other test class happened to run first.
|
||
public UsagePillViewModelTests()
|
||
{
|
||
var dir = AppContext.BaseDirectory;
|
||
while (dir is not null && !Directory.Exists(Path.Combine(dir, "src", "ClaudeDo.Localization", "locales")))
|
||
dir = Path.GetDirectoryName(dir);
|
||
Loc.Current = new Localizer(
|
||
LocaleStore.Load(Path.Combine(dir!, "src", "ClaudeDo.Localization", "locales")), "en");
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|