Files
ClaudeDo/tests/ClaudeDo.Ui.Tests/ViewModels/LogVisualizerViewModelTests.cs
T

180 lines
5.9 KiB
C#

using ClaudeDo.Data.Models;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels.Modals;
namespace ClaudeDo.Ui.Tests.ViewModels;
public class LogVisualizerViewModelTests
{
private sealed class FakeClient : StubWorkerClient
{
private readonly IReadOnlyList<WorkerLogEntry> _logs;
public FakeClient(IReadOnlyList<WorkerLogEntry> logs) => _logs = logs;
public override Task<IReadOnlyList<WorkerLogEntry>> GetRecentLogsAsync() => Task.FromResult(_logs);
}
private static WorkerLogEntry E(WorkerLogLevel lvl, string msg)
=> new(msg, lvl, new DateTime(2026, 6, 23, 8, 0, 0, DateTimeKind.Utc));
private static WorkerLogEntry E(WorkerLogLevel lvl, string msg, DateTime timestampUtc)
=> new(msg, lvl, timestampUtc);
[Fact]
public async Task Refresh_populates_rows_from_worker_newest_first()
{
var vm = new LogVisualizerViewModel(new FakeClient(new[] { E(WorkerLogLevel.Info, "a"), E(WorkerLogLevel.Error, "b") }));
await vm.RefreshAsync();
Assert.Equal(new[] { "b", "a" }, vm.Rows.Select(r => r.Message));
}
[Fact]
public async Task WarnErrorOnly_filters_out_info()
{
var vm = new LogVisualizerViewModel(new FakeClient(new[]
{ E(WorkerLogLevel.Info, "a"), E(WorkerLogLevel.Warn, "w"), E(WorkerLogLevel.Error, "e") }));
await vm.RefreshAsync();
vm.WarnErrorOnly = true;
Assert.Equal(new[] { "e", "w" }, vm.Rows.Select(r => r.Message));
}
[Fact]
public async Task Empty_logs_yield_no_rows_and_a_status()
{
var vm = new LogVisualizerViewModel(new FakeClient(Array.Empty<WorkerLogEntry>()));
await vm.RefreshAsync();
Assert.Empty(vm.Rows);
Assert.False(string.IsNullOrEmpty(vm.StatusText));
}
[Fact]
public async Task Refresh_orders_newest_entry_first()
{
var t0 = new DateTime(2026, 6, 23, 8, 0, 0, DateTimeKind.Utc);
var vm = new LogVisualizerViewModel(new FakeClient(new[]
{
E(WorkerLogLevel.Info, "oldest", t0),
E(WorkerLogLevel.Info, "middle", t0.AddMinutes(1)),
E(WorkerLogLevel.Info, "newest", t0.AddMinutes(2)),
}));
await vm.RefreshAsync();
Assert.Equal(new[] { "newest", "middle", "oldest" }, vm.Rows.Select(r => r.Message));
}
[Fact]
public async Task Refresh_orders_newest_entry_first_with_warn_error_only()
{
var t0 = new DateTime(2026, 6, 23, 8, 0, 0, DateTimeKind.Utc);
var vm = new LogVisualizerViewModel(new FakeClient(new[]
{
E(WorkerLogLevel.Warn, "oldest", t0),
E(WorkerLogLevel.Info, "skipped", t0.AddMinutes(1)),
E(WorkerLogLevel.Error, "middle", t0.AddMinutes(2)),
E(WorkerLogLevel.Warn, "newest", t0.AddMinutes(3)),
}));
await vm.RefreshAsync();
vm.WarnErrorOnly = true;
Assert.Equal(new[] { "newest", "middle", "oldest" }, vm.Rows.Select(r => r.Message));
}
[Fact]
public async Task CopyLast_orders_chronologically_oldest_first()
{
var t0 = new DateTime(2026, 6, 23, 8, 0, 0, DateTimeKind.Utc);
var vm = new LogVisualizerViewModel(new FakeClient(new[]
{
E(WorkerLogLevel.Info, "oldest", t0),
E(WorkerLogLevel.Warn, "middle", t0.AddMinutes(1)),
E(WorkerLogLevel.Error, "newest", t0.AddMinutes(2)),
}));
await vm.RefreshAsync();
var text = vm.BuildCopyText();
var lines = text.Split('\n');
Assert.Equal(3, lines.Length);
Assert.EndsWith("INFO oldest", lines[0]);
Assert.EndsWith("WARN middle", lines[1]);
Assert.EndsWith("ERROR newest", lines[2]);
}
[Fact]
public async Task CopyLast_caps_at_40_most_recent_entries()
{
var t0 = new DateTime(2026, 6, 23, 8, 0, 0, DateTimeKind.Utc);
var entries = Enumerable.Range(0, 50)
.Select(i => E(WorkerLogLevel.Info, $"line{i}", t0.AddMinutes(i)))
.ToArray();
var vm = new LogVisualizerViewModel(new FakeClient(entries));
await vm.RefreshAsync();
var lines = vm.BuildCopyText().Split('\n');
Assert.Equal(40, lines.Length);
Assert.EndsWith("line10", lines[0]);
Assert.EndsWith("line49", lines[^1]);
}
[Fact]
public async Task CopyLast_respects_active_warn_error_filter()
{
var t0 = new DateTime(2026, 6, 23, 8, 0, 0, DateTimeKind.Utc);
var vm = new LogVisualizerViewModel(new FakeClient(new[]
{
E(WorkerLogLevel.Info, "skipped", t0),
E(WorkerLogLevel.Warn, "kept", t0.AddMinutes(1)),
}));
await vm.RefreshAsync();
vm.WarnErrorOnly = true;
var lines = vm.BuildCopyText().Split('\n');
Assert.Single(lines);
Assert.EndsWith("WARN kept", lines[0]);
}
[Fact]
public async Task CopyLastCommand_disabled_when_no_rows()
{
var vm = new LogVisualizerViewModel(new FakeClient(Array.Empty<WorkerLogEntry>()));
await vm.RefreshAsync();
Assert.False(vm.CopyLastCommand.CanExecute(null));
}
[Fact]
public async Task CopyLastCommand_copies_and_reports_count_on_success()
{
string? copied = null;
var vm = new LogVisualizerViewModel(new FakeClient(new[] { E(WorkerLogLevel.Info, "a") }),
text => { copied = text; return Task.FromResult(true); });
await vm.RefreshAsync();
await vm.CopyLastCommand.ExecuteAsync(null);
Assert.EndsWith("INFO a", copied);
Assert.Contains("1", vm.StatusText);
}
[Fact]
public async Task CopyLastCommand_reports_failure_when_clipboard_unavailable()
{
var vm = new LogVisualizerViewModel(new FakeClient(new[] { E(WorkerLogLevel.Info, "a") }),
_ => Task.FromResult(false));
await vm.RefreshAsync();
await vm.CopyLastCommand.ExecuteAsync(null);
Assert.False(string.IsNullOrEmpty(vm.StatusText));
}
}