namespace Zcbot.WindowsNode.Tests; public sealed class JobPageViewModelTests { [Fact] public void Refresh_PopulatesJobsFormatsStateAndSelectsTheFirstItem() { var active = Snapshot("software_running", "正在生成图表", minutesAgo: 2); var succeeded = Snapshot("succeeded", "历史任务", minutesAgo: 10); var viewModel = new JobPageViewModel(new FakeJobMonitorService(active, succeeded)); viewModel.Refresh(); Assert.Equal(2, viewModel.Jobs.Count); Assert.Same(viewModel.Jobs[0], viewModel.SelectedJob); Assert.Equal("软件执行中", viewModel.Jobs[0].StageText); Assert.StartsWith("已执行 02:", viewModel.Jobs[0].ProgressText); Assert.Equal("活动任务 1 个 · 最近记录 2 条", viewModel.Summary); Assert.Contains(active.JobId.ToString(), viewModel.SelectedDetail); } [Fact] public void Refresh_PreservesSelectionByJobIdAcrossSnapshots() { var first = Snapshot("accepted", "第一项", minutesAgo: 3); var second = Snapshot("failed", "第二项", minutesAgo: 2); var service = new FakeJobMonitorService(first, second); var viewModel = new JobPageViewModel(service); viewModel.Refresh(); viewModel.SelectedJob = viewModel.Jobs[1]; service.Snapshots = [ second with { Detail = "更新后的错误" }, first, ]; viewModel.Refresh(); Assert.Equal(second.JobId, viewModel.SelectedJob?.JobId); Assert.Contains("更新后的错误", viewModel.SelectedDetail); Assert.Equal("error", viewModel.SelectedJob?.Tone); } [Fact] public void Refresh_ReportsReadFailuresWithoutDroppingTheCurrentList() { var service = new FakeJobMonitorService(Snapshot("accepted", "任务", 1)); var viewModel = new JobPageViewModel(service); viewModel.Refresh(); service.Failure = new IOException("locked"); viewModel.Refresh(); Assert.Single(viewModel.Jobs); Assert.Contains("locked", viewModel.Summary); } private static JobDisplaySnapshot Snapshot( string stage, string title, int minutesAgo) { var updatedAt = DateTimeOffset.UtcNow.AddMinutes(-minutesAgo); return new JobDisplaySnapshot( Guid.NewGuid(), "origin.plot@v2", title, "input.csv", stage, stage == "succeeded" ? 100 : 25, title, updatedAt.AddMinutes(-1), updatedAt, stage == "succeeded"); } private sealed class FakeJobMonitorService(params JobDisplaySnapshot[] snapshots) : IJobMonitorService { internal IReadOnlyList Snapshots { get; set; } = snapshots; internal Exception? Failure { get; set; } public IReadOnlyList ReadSnapshots(int limit = 50) { if (Failure is not null) throw Failure; return Snapshots; } } }