chore(claude-do): merge [A2] WorktreesOverview + Reset-All: getrennte OperationStatu

ClaudeDo-Task: cec07e2c-2814-4a43-898c-a6be07a13f01
This commit is contained in:
Mika Kuns
2026-08-12 09:54:58 +02:00
8 changed files with 189 additions and 100 deletions
@@ -98,7 +98,7 @@ public class WorktreesOverviewBatchMergeTests
Assert.Equal(BatchMergeOutcome.Blocked, a.MergeOutcome);
Assert.Equal(BatchMergeOutcome.Failed, b.MergeOutcome);
Assert.Empty(vm.ConflictRows);
Assert.False(vm.IsMerging);
Assert.False(vm.BatchMergeStatus.IsRunning);
}
[Fact]
@@ -164,7 +164,40 @@ public class WorktreesOverviewBatchMergeTests
Assert.False(vm.MergeAllCommand.CanExecute(null));
vm.SelectedTarget = "main";
Assert.True(vm.MergeAllCommand.CanExecute(null));
vm.IsMerging = true;
Assert.False(vm.MergeAllCommand.CanExecute(null));
using (vm.BatchMergeStatus.Begin("merging"))
{
Assert.False(vm.MergeAllCommand.CanExecute(null));
}
Assert.True(vm.MergeAllCommand.CanExecute(null));
}
[Fact]
public void MergeAll_CanExecuteChanged_fires_when_batch_status_changes()
{
var vm = NewVm();
var a = ActiveRow("a"); a.IsChecked = true;
vm.AddRowForTest(a);
vm.SelectedTarget = "main";
var raised = 0;
vm.MergeAllCommand.CanExecuteChanged += (_, _) => raised++;
using (vm.BatchMergeStatus.Begin("merging")) { }
Assert.Equal(2, raised);
}
[Fact]
public void ForceRemove_canExecute_is_false_while_a_removal_is_in_flight()
{
var vm = NewVm();
var row = ActiveRow("a");
Assert.True(vm.ForceRemoveCommand.CanExecute(row));
using (vm.ForceRemoveStatus.Begin("removing"))
{
Assert.False(vm.ForceRemoveCommand.CanExecute(row));
}
Assert.True(vm.ForceRemoveCommand.CanExecute(row));
}
}
@@ -20,6 +20,8 @@ public class WorktreesOverviewModalErrorTests
private sealed class ThrowingWorker : StubWorkerClient
{
public string ExceptionMessage { get; init; } = "worktree is locked by another process";
public override Task<List<WorktreeOverviewDto>> GetWorktreesOverviewAsync(string? listId) =>
throw new Exception(ExceptionMessage);
public override Task<WorktreeCleanupDto?> CleanupFinishedWorktreesAsync(string? listId = null) =>
throw new Exception(ExceptionMessage);
public override Task<ForceRemoveResultDto?> ForceRemoveWorktreeAsync(string taskId) =>
@@ -39,7 +41,7 @@ public class WorktreesOverviewModalErrorTests
Assert.NotNull(vm.StatusMessage);
Assert.Contains(worker.ExceptionMessage, vm.StatusMessage);
Assert.False(vm.IsBusy);
Assert.False(vm.CleanupStatus.IsRunning);
}
[Fact]
@@ -61,5 +63,17 @@ public class WorktreesOverviewModalErrorTests
Assert.NotNull(vm.StatusMessage);
Assert.Contains(worker.ExceptionMessage, vm.StatusMessage);
Assert.Contains(row, vm.Rows);
Assert.False(vm.ForceRemoveStatus.IsRunning);
}
[Fact]
public async Task Refresh_WhenWorkerThrows_ResetsRefreshStatus()
{
var worker = new ThrowingWorker();
var vm = NewVm(worker);
await Assert.ThrowsAsync<Exception>(() => vm.RefreshCommand.ExecuteAsync(null));
Assert.False(vm.RefreshStatus.IsRunning);
}
}
@@ -117,4 +117,29 @@ public class WorktreesOverviewReconcileTickTests
Assert.False(vm.Rows.Single(r => r.TaskId == "d").IsChecked);
Assert.Equal(1, vm.SelectedCount);
}
[Theory]
[InlineData("refresh")]
[InlineData("cleanup")]
[InlineData("forceRemove")]
[InlineData("batchMerge")]
public async Task Tick_skips_the_reload_while_any_action_status_is_running(string which)
{
var (vm, worker) = await BuildLoadedAsync();
var status = which switch
{
"refresh" => vm.RefreshStatus,
"cleanup" => vm.CleanupStatus,
"forceRemove" => vm.ForceRemoveStatus,
"batchMerge" => vm.BatchMergeStatus,
_ => throw new ArgumentOutOfRangeException(nameof(which)),
};
using var op = status.Begin("running");
worker.Worktrees.Add(Wt("d"));
await vm.ReconcileTickAsync();
Assert.DoesNotContain(vm.Rows, r => r.TaskId == "d");
}
}
@@ -47,7 +47,7 @@ public class WorktreesSettingsTabViewModelTests
await vm.ConfirmResetAllCommand.ExecuteAsync(null);
Assert.Contains(worker.ExceptionMessage, vm.StatusMessage);
Assert.False(vm.IsBusy);
Assert.False(vm.ResetStatus.IsRunning);
Assert.False(vm.ShowResetConfirm);
}
}