using ClaudeDo.Data.Models; using ClaudeDo.Ui.Services; using ClaudeDo.Ui.ViewModels.Planning; namespace ClaudeDo.Ui.Tests.ViewModels; public class ConflictResolutionViewModelTests { // ------------------------------------------------------------------ fake private sealed class FakeWorker : StubWorkerClient { public string? ContinueCalledWith { get; private set; } public string? AbortCalledWith { get; private set; } public Exception? ContinueThrows { get; set; } public Exception? AbortThrows { get; set; } public override Task ContinuePlanningMergeAsync(string planningTaskId) { ContinueCalledWith = planningTaskId; if (ContinueThrows is not null) throw ContinueThrows; return Task.CompletedTask; } public override Task AbortPlanningMergeAsync(string planningTaskId) { AbortCalledWith = planningTaskId; if (AbortThrows is not null) throw AbortThrows; return Task.CompletedTask; } } private static ConflictResolutionViewModel BuildVm(FakeWorker worker, string planningTaskId = "plan-1") => new ConflictResolutionViewModel( worker, planningTaskId, subtaskTitle: "My subtask", targetBranch: "main", conflictedFiles: new[] { "src/Foo.cs", "src/Bar.cs" }, worktreePath: "C:/worktrees/plan-1"); // ------------------------------------------------------------------ tests [Fact] public async Task ContinueAsync_CallsHub_AndClosesOnSuccess() { var worker = new FakeWorker(); var vm = BuildVm(worker, "plan-42"); bool closeCalled = false; vm.CloseRequested = () => closeCalled = true; await vm.ContinueCommand.ExecuteAsync(null); Assert.Equal("plan-42", worker.ContinueCalledWith); Assert.True(closeCalled); Assert.Null(vm.ActionError); } [Fact] public async Task ContinueAsync_HubThrows_ShowsActionErrorAndStaysOpen() { var worker = new FakeWorker { ContinueThrows = new InvalidOperationException("hub down") }; var vm = BuildVm(worker); bool closeCalled = false; vm.CloseRequested = () => closeCalled = true; await vm.ContinueCommand.ExecuteAsync(null); Assert.False(closeCalled); Assert.Equal("hub down", vm.ActionError); } [Fact] public async Task AbortAsync_CallsHub_AndClosesOnSuccess() { var worker = new FakeWorker(); var vm = BuildVm(worker, "plan-99"); bool closeCalled = false; vm.CloseRequested = () => closeCalled = true; await vm.AbortCommand.ExecuteAsync(null); Assert.Equal("plan-99", worker.AbortCalledWith); Assert.True(closeCalled); Assert.Null(vm.ActionError); } [Fact] public async Task AbortAsync_HubThrows_ShowsActionError() { var worker = new FakeWorker { AbortThrows = new InvalidOperationException("abort failed") }; var vm = BuildVm(worker); bool closeCalled = false; vm.CloseRequested = () => closeCalled = true; await vm.AbortCommand.ExecuteAsync(null); Assert.False(closeCalled); Assert.Equal("abort failed", vm.ActionError); } // OpenInVsCode is not unit-tested here because abstracting Process.Start // would require an indirection layer that isn't part of the approved design. // The error path is covered by the VsCodeError property being set on catch. }