Files
ClaudeDo/tests/ClaudeDo.Ui.Tests/ViewModels/NotesEditorViewModelTests.cs
T
mika kuns b5464fc533 fix(worker-client): stop swallowing mutating hub call failures
TryInvokeAsync catches every exception and returns null, which is fine
for read-only calls but hid real HubException reasons behind a generic
"offline" message for the 7 mutating call sites (RestoreDefaultAgents,
UpsertPrimeSchedule, AddDailyNote, CleanupFinishedWorktrees,
ResetAllWorktrees, ForceRemoveWorktree, BuildPlanningIntegrationBranch)
— the same bug class fixed for ApproveReview in e1807fd. Each of the 22
TryInvokeAsync call sites was audited; the 15 read-only ones are left
unchanged (empty/offline is the right display). For the 7 switched to
a direct hub invoke, every caller was checked and, where it had no
catch, one was added so the exception surfaces (StatusMessage,
ShowErrorAsync/CombinedWarning) instead of crashing.
2026-07-29 13:19:38 +02:00

123 lines
4.3 KiB
C#

using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.Services.Interfaces;
using ClaudeDo.Ui.ViewModels.Islands;
namespace ClaudeDo.Ui.Tests.ViewModels;
public class NotesEditorViewModelTests
{
private sealed class FakeNotes : INotesApi
{
public readonly List<DailyNoteDto> Store = new();
private int _seq;
public Task<List<DailyNoteDto>> ListAsync(DateOnly day) =>
Task.FromResult(Store.Where(n => n.Date == day.ToString("yyyy-MM-dd")).ToList());
public Task<DailyNoteDto?> AddAsync(DateOnly day, string text)
{
var dto = new DailyNoteDto($"id{_seq++}", day.ToString("yyyy-MM-dd"), text, Store.Count);
Store.Add(dto);
return Task.FromResult<DailyNoteDto?>(dto);
}
public Task UpdateAsync(string id, string text)
{
var i = Store.FindIndex(n => n.Id == id);
if (i >= 0) Store[i] = Store[i] with { Text = text };
return Task.CompletedTask;
}
public Task DeleteAsync(string id) { Store.RemoveAll(n => n.Id == id); return Task.CompletedTask; }
}
private sealed class ThrowingNotes : INotesApi
{
public string ExceptionMessage { get; init; } = "worker offline";
public Task<List<DailyNoteDto>> ListAsync(DateOnly day) => Task.FromResult(new List<DailyNoteDto>());
public Task<DailyNoteDto?> AddAsync(DateOnly day, string text) => throw new Exception(ExceptionMessage);
public Task UpdateAsync(string id, string text) => Task.CompletedTask;
public Task DeleteAsync(string id) => Task.CompletedTask;
}
[Fact]
public async Task AddBullet_WhenApiThrows_RaisesErrorReported_AndKeepsDraftText()
{
var api = new ThrowingNotes();
var vm = new NotesEditorViewModel(api);
await vm.LoadDayAsync(new DateOnly(2026, 6, 1));
string? reportedError = null;
vm.ErrorReported += msg => reportedError = msg;
vm.NewBulletText = "Standup vorbereitet";
await vm.AddBulletCommand.ExecuteAsync(null);
Assert.Equal(api.ExceptionMessage, reportedError);
Assert.Empty(vm.Bullets);
Assert.Equal("Standup vorbereitet", vm.NewBulletText);
}
[Fact]
public async Task AddBullet_PersistsAndAppears_ForCurrentDay()
{
var api = new FakeNotes();
var vm = new NotesEditorViewModel(api);
await vm.LoadDayAsync(new DateOnly(2026, 6, 1));
vm.NewBulletText = "Standup vorbereitet";
await vm.AddBulletCommand.ExecuteAsync(null);
Assert.Single(vm.Bullets);
Assert.Equal("Standup vorbereitet", vm.Bullets[0].Text);
Assert.Equal("", vm.NewBulletText);
Assert.Single(api.Store);
}
[Fact]
public async Task PrevAndNextDay_NavigateAndReload()
{
var api = new FakeNotes();
await api.AddAsync(new DateOnly(2026, 5, 31), "gestern");
var vm = new NotesEditorViewModel(api);
await vm.LoadDayAsync(new DateOnly(2026, 6, 1));
Assert.Empty(vm.Bullets);
await vm.PrevDayCommand.ExecuteAsync(null);
Assert.Equal(new DateOnly(2026, 5, 31), vm.CurrentDay);
Assert.Single(vm.Bullets);
await vm.NextDayCommand.ExecuteAsync(null);
Assert.Equal(new DateOnly(2026, 6, 1), vm.CurrentDay);
Assert.Empty(vm.Bullets);
}
[Fact]
public async Task CommitBullet_EmptyText_RemovesFromStoreAndList()
{
var api = new FakeNotes();
var vm = new NotesEditorViewModel(api);
await vm.LoadDayAsync(new DateOnly(2026, 6, 1));
vm.NewBulletText = "weg damit";
await vm.AddBulletCommand.ExecuteAsync(null);
vm.Bullets[0].Text = " ";
await vm.CommitBulletCommand.ExecuteAsync(vm.Bullets[0]);
Assert.Empty(vm.Bullets);
Assert.Empty(api.Store);
}
[Fact]
public async Task CommitBullet_NonEmptyText_PersistsEdit()
{
var api = new FakeNotes();
var vm = new NotesEditorViewModel(api);
await vm.LoadDayAsync(new DateOnly(2026, 6, 1));
vm.NewBulletText = "original";
await vm.AddBulletCommand.ExecuteAsync(null);
vm.Bullets[0].Text = "geändert";
await vm.CommitBulletCommand.ExecuteAsync(vm.Bullets[0]);
Assert.Single(vm.Bullets);
Assert.Equal("geändert", api.Store[0].Text);
}
}