feat(ui): add inline conflict resolver view-model
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
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<MergeResultDto> StartConflictMergeAsync(string taskId, string targetBranch)
|
||||
=> Task.FromResult(new MergeResultDto("conflict", new[] { "README.md" }, null));
|
||||
|
||||
public override Task<MergeConflictsDto> 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<MergeResultDto> ContinueMergeAsync(string taskId)
|
||||
{
|
||||
Continued = true;
|
||||
return Task.FromResult(new MergeResultDto(ContinueStatus, System.Array.Empty<string>(), null));
|
||||
}
|
||||
|
||||
public override Task AbortMergeAsync(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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user