using ClaudeDo.Data.Models; using ClaudeDo.Ui.Services; using ClaudeDo.Ui.ViewModels.Modals; using TaskStatus = ClaudeDo.Data.Models.TaskStatus; using Xunit; namespace ClaudeDo.Ui.Tests.ViewModels; public class WorktreesOverviewBatchMergeTests { private static WorktreeOverviewRowViewModel ActiveRow(string id) => new() { TaskId = id, TaskTitle = $"Task {id}", TaskStatus = TaskStatus.WaitingForReview, State = WorktreeState.Active, }; [Fact] public void Row_outcome_helpers_reflect_state() { var row = ActiveRow("a"); Assert.Equal(BatchMergeOutcome.None, row.MergeOutcome); Assert.False(row.IsConflict); row.MergeOutcome = BatchMergeOutcome.Conflict; Assert.True(row.IsConflict); row.MergeOutcome = BatchMergeOutcome.Merged; Assert.False(row.IsConflict); } private static WorktreesOverviewModalViewModel NewVm() => new(new ClaudeDo.Ui.Services.WorkerClient("http://127.0.0.1:1/hub"), () => null!); private static MergeResultDto Merged() => new("merged", System.Array.Empty(), null); private static MergeResultDto Conflict() => new("conflict", new[] { "f.cs" }, null); private static MergeResultDto Blocked() => new("blocked", System.Array.Empty(), "blocked"); [Fact] public async System.Threading.Tasks.Task MergeSelected_only_processes_checked_active_rows() { var vm = NewVm(); var a = ActiveRow("a"); a.IsChecked = true; var b = ActiveRow("b"); b.IsChecked = false; var c = ActiveRow("c"); c.IsChecked = true; c.State = WorktreeState.Merged; vm.Rows.Add(a); vm.Rows.Add(b); vm.Rows.Add(c); vm.SelectedTarget = "main"; var seen = new System.Collections.Generic.List(); await vm.MergeSelectedAsync((id, target, remove, msg) => { seen.Add(id); Assert.Equal("main", target); Assert.False(remove); return System.Threading.Tasks.Task.FromResult(Merged()); }); Assert.Equal(new[] { "a" }, seen); Assert.Equal(BatchMergeOutcome.Merged, a.MergeOutcome); Assert.False(a.IsChecked); } [Fact] public async System.Threading.Tasks.Task MergeSelected_continues_past_conflict_and_collects_it() { var vm = NewVm(); var a = ActiveRow("a"); a.IsChecked = true; var b = ActiveRow("b"); b.IsChecked = true; var c = ActiveRow("c"); c.IsChecked = true; vm.Rows.Add(a); vm.Rows.Add(b); vm.Rows.Add(c); vm.SelectedTarget = "main"; await vm.MergeSelectedAsync((id, target, remove, msg) => System.Threading.Tasks.Task.FromResult(id == "b" ? Conflict() : Merged())); Assert.Equal(BatchMergeOutcome.Merged, a.MergeOutcome); Assert.Equal(BatchMergeOutcome.Conflict, b.MergeOutcome); Assert.Equal(BatchMergeOutcome.Merged, c.MergeOutcome); Assert.Contains(b, vm.ConflictRows); Assert.Single(vm.ConflictRows); } [Fact] public async System.Threading.Tasks.Task MergeSelected_maps_blocked_and_exception_to_failure_outcomes() { var vm = NewVm(); var a = ActiveRow("a"); a.IsChecked = true; var b = ActiveRow("b"); b.IsChecked = true; vm.Rows.Add(a); vm.Rows.Add(b); vm.SelectedTarget = "main"; await vm.MergeSelectedAsync((id, target, remove, msg) => id == "a" ? System.Threading.Tasks.Task.FromResult(Blocked()) : throw new System.InvalidOperationException("boom")); Assert.Equal(BatchMergeOutcome.Blocked, a.MergeOutcome); Assert.Equal(BatchMergeOutcome.Failed, b.MergeOutcome); Assert.Empty(vm.ConflictRows); Assert.False(vm.IsMerging); } [Fact] public async System.Threading.Tasks.Task MergeSelected_noop_when_no_target() { var vm = NewVm(); var a = ActiveRow("a"); a.IsChecked = true; vm.Rows.Add(a); vm.SelectedTarget = null; var called = false; await vm.MergeSelectedAsync((id, t, r, m) => { called = true; return System.Threading.Tasks.Task.FromResult(Merged()); }); Assert.False(called); Assert.Equal(BatchMergeOutcome.None, a.MergeOutcome); } }