using ClaudeDo.Data; using ClaudeDo.Data.Models; using ClaudeDo.Ui.Services; using ClaudeDo.Ui.ViewModels.Islands; using CommunityToolkit.Mvvm.Input; using Microsoft.EntityFrameworkCore; using Xunit; using TaskStatus = ClaudeDo.Data.Models.TaskStatus; namespace ClaudeDo.Worker.Tests.UiVm; // ── Fake worker client ──────────────────────────────────────────────────────── sealed class FakeWorkerClient : IWorkerClient { public int StartPlanningCalls { get; private set; } public int ResumePlanningCalls { get; private set; } public int DiscardPlanningCalls { get; private set; } public int FinalizePlanningCalls { get; private set; } public int WakeQueueCalls { get; private set; } public event Action? TaskUpdatedEvent; public event Action? WorktreeUpdatedEvent; public event Action? TaskMessageEvent; public void RaiseTaskUpdated(string taskId) => TaskUpdatedEvent?.Invoke(taskId); public void RaiseWorktreeUpdated(string taskId) => WorktreeUpdatedEvent?.Invoke(taskId); public void RaiseTaskMessage(string taskId, string line) => TaskMessageEvent?.Invoke(taskId, line); public Task WakeQueueAsync() { WakeQueueCalls++; return Task.CompletedTask; } public Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default) { StartPlanningCalls++; return Task.CompletedTask; } public Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default) { ResumePlanningCalls++; return Task.CompletedTask; } public Task DiscardPlanningSessionAsync(string taskId, CancellationToken ct = default) { DiscardPlanningCalls++; return Task.CompletedTask; } public Task FinalizePlanningSessionAsync(string taskId, bool queueAgentTasks = true, CancellationToken ct = default) { FinalizePlanningCalls++; return Task.CompletedTask; } public Task GetPendingDraftCountAsync(string taskId, CancellationToken ct = default) => Task.FromResult(0); } // ── Helper to build VM with pre-seeded Items ────────────────────────────────── file static class VmFactory { // Minimal SQLite :memory: factory — never actually called in these tests // (we seed Items directly), but required by the VM constructor. private static IDbContextFactory NullDbFactory() { var opts = new DbContextOptionsBuilder() .UseSqlite("DataSource=:memory:") .Options; return new NullDbContextFactory(opts); } private sealed class NullDbContextFactory(DbContextOptions opts) : IDbContextFactory { public ClaudeDoDbContext CreateDbContext() => new(opts); } public static (TasksIslandViewModel vm, FakeWorkerClient worker) Create( IEnumerable rows) { var worker = new FakeWorkerClient(); var vm = new TasksIslandViewModel(NullDbFactory(), worker); foreach (var r in rows) vm.Items.Add(r); vm.Regroup(); return (vm, worker); } } // ── Tests ───────────────────────────────────────────────────────────────────── public class TasksIslandViewModelPlanningTests { private static TaskRowViewModel MakeRow(string id, TaskStatus status, string? parentId = null, int sortOrder = 0) => new TaskRowViewModel { Id = id, Status = status, ParentTaskId = parentId }; [Fact] public void ToggleExpand_CollapsesChildrenOfPlanningParent() { var parent = MakeRow("p1", TaskStatus.Planning); var child1 = MakeRow("c1", TaskStatus.Draft, "p1"); var child2 = MakeRow("c2", TaskStatus.Draft, "p1"); var (vm, _) = VmFactory.Create([parent, child1, child2]); // Initially expanded — children visible in OpenItems Assert.Contains(child1, vm.OpenItems); Assert.Contains(child2, vm.OpenItems); // Collapse the parent vm.ToggleExpandCommand.Execute(parent); // Children should no longer appear Assert.DoesNotContain(child1, vm.OpenItems); Assert.DoesNotContain(child2, vm.OpenItems); // Parent still present Assert.Contains(parent, vm.OpenItems); } [Fact] public async Task OpenPlanningSession_IgnoresNonManualRow() { var row = MakeRow("t1", TaskStatus.Queued); var (vm, worker) = VmFactory.Create([row]); await ((IAsyncRelayCommand)vm.OpenPlanningSessionCommand).ExecuteAsync(row); Assert.Equal(0, worker.StartPlanningCalls); } [Fact] public async Task OpenPlanningSession_CallsWorkerForManualRow() { var row = MakeRow("t1", TaskStatus.Manual); var (vm, worker) = VmFactory.Create([row]); await ((IAsyncRelayCommand)vm.OpenPlanningSessionCommand).ExecuteAsync(row); Assert.Equal(1, worker.StartPlanningCalls); } [Fact] public void ToggleExpand_ExpandsCollapsedParentAgain() { var parent = MakeRow("p1", TaskStatus.Planned); var child = MakeRow("c1", TaskStatus.Draft, "p1"); var (vm, _) = VmFactory.Create([parent, child]); // Collapse vm.ToggleExpandCommand.Execute(parent); Assert.DoesNotContain(child, vm.OpenItems); // Re-expand vm.ToggleExpandCommand.Execute(parent); Assert.Contains(child, vm.OpenItems); } }