using System.Collections.Generic; using System.Threading.Tasks; using ClaudeDo.Ui.Services; using ClaudeDo.Ui.ViewModels.Conflicts; using Xunit; namespace ClaudeDo.Ui.Tests.ViewModels; public class ConflictResolverViewModelTests { private sealed class FakeWorker : StubWorkerClient { public string? WrittenPath; public string? WrittenContent; public bool Continued; public bool Aborted; public string ContinueStatus = "merged"; public override Task StartConflictMergeAsync(string taskId, string targetBranch) => Task.FromResult(new MergeResultDto("conflict", new[] { "README.md" }, null)); public override Task GetMergeConflictsAsync(string taskId) => Task.FromResult(new MergeConflictsDto(taskId, new[] { new ConflictFileDto("README.md", new[] { new ConflictHunkDto("ours\n", "theirs\n", "base\n") }) })); public override Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent) { WrittenPath = path; WrittenContent = resolvedContent; return Task.CompletedTask; } public override Task ContinueConflictMergeAsync(string taskId) { Continued = true; return Task.FromResult(new MergeResultDto(ContinueStatus, System.Array.Empty(), null)); } public override Task AbortConflictMergeAsync(string taskId) { Aborted = true; return Task.CompletedTask; } } [Fact] public async Task OpenAsync_LoadsConflicts_AndBlocksContinueUntilResolved() { var vm = new ConflictResolverViewModel(new FakeWorker(), "task-1"); var hasConflicts = await vm.OpenAsync("main"); Assert.True(hasConflicts); var file = Assert.Single(vm.Files); Assert.Equal("README.md", file.Path); Assert.False(vm.CanContinue); file.Hunks[0].AcceptIncomingCommand.Execute(null); Assert.True(vm.CanContinue); } [Fact] public async Task Continue_WritesComposedResolution_AndClosesOnMerged() { var worker = new FakeWorker(); var vm = new ConflictResolverViewModel(worker, "task-1"); var closed = false; vm.CloseRequested = () => closed = true; await vm.OpenAsync("main"); vm.Files[0].Hunks[0].AcceptCurrentCommand.Execute(null); await vm.ContinueCommand.ExecuteAsync(null); Assert.Equal("README.md", worker.WrittenPath); Assert.Equal("ours\n", worker.WrittenContent); Assert.True(worker.Continued); Assert.True(closed); } [Fact] public async Task Continue_StaysOpenAndReportsError_WhenStillConflicted() { var worker = new FakeWorker { ContinueStatus = "conflict" }; var vm = new ConflictResolverViewModel(worker, "task-1"); var closed = false; vm.CloseRequested = () => closed = true; await vm.OpenAsync("main"); vm.Files[0].Hunks[0].AcceptBothCommand.Execute(null); await vm.ContinueCommand.ExecuteAsync(null); Assert.False(closed); Assert.NotNull(vm.Error); } [Fact] public async Task Abort_CallsWorkerAndCloses() { var worker = new FakeWorker(); var vm = new ConflictResolverViewModel(worker, "task-1"); var closed = false; vm.CloseRequested = () => closed = true; await vm.AbortCommand.ExecuteAsync(null); Assert.True(worker.Aborted); Assert.True(closed); } }