diff --git a/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs b/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs
new file mode 100644
index 0000000..22498dd
--- /dev/null
+++ b/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs
@@ -0,0 +1,61 @@
+using System.ComponentModel;
+using ClaudeDo.Data.Models;
+using ClaudeDo.Data.Repositories;
+using ClaudeDo.Ui.Services;
+using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
+
+namespace ClaudeDo.Ui.Tests;
+
+///
+/// No-op base for tests. Override only the members a
+/// test actually exercises; everything else returns a benign default. Keeping the
+/// full interface surface here means new interface members only break this file.
+///
+public abstract class StubWorkerClient : IWorkerClient
+{
+#pragma warning disable CS0067 // interface-mandated events; tests don't raise them
+ public event PropertyChangedEventHandler? PropertyChanged;
+ public event Action? TaskStartedEvent;
+ public event Action? TaskFinishedEvent;
+ public event Action? TaskUpdatedEvent;
+ public event Action? ConnectionRestoredEvent;
+ public event Action? WorktreeUpdatedEvent;
+ public event Action? ListUpdatedEvent;
+ public event Action? TaskMessageEvent;
+ public event Action? PlanningMergeStartedEvent;
+ public event Action? PlanningSubtaskMergedEvent;
+ public event Action>? PlanningMergeConflictEvent;
+ public event Action? PlanningMergeAbortedEvent;
+ public event Action? PlanningCompletedEvent;
+#pragma warning restore CS0067
+
+ public virtual bool IsConnected => false;
+
+ public virtual Task WakeQueueAsync() => Task.CompletedTask;
+ public virtual Task RunNowAsync(string taskId) => Task.CompletedTask;
+ public virtual Task ContinueTaskAsync(string taskId, string followUpPrompt) => Task.CompletedTask;
+ public virtual Task ResetTaskAsync(string taskId) => Task.CompletedTask;
+ public virtual Task CancelTaskAsync(string taskId) => Task.CompletedTask;
+ public virtual Task> GetAgentsAsync() => Task.FromResult(new List());
+ public virtual Task GetListConfigAsync(string listId) => Task.FromResult(null);
+ public virtual Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto) => Task.CompletedTask;
+ public virtual Task SetTaskStatusAsync(string taskId, TaskStatus status) => Task.CompletedTask;
+ public virtual Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask;
+ public virtual Task OpenInteractiveTerminalAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask;
+ public virtual Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask;
+ public virtual Task DiscardPlanningSessionAsync(string taskId, bool dequeueQueuedChildren = false, CancellationToken ct = default)
+ => Task.FromResult(new DiscardPlanningOutcome(DiscardPlanningResult.Discarded, 0, 0));
+ public virtual Task FinalizePlanningSessionAsync(string taskId, bool queueAgentTasks = true, CancellationToken ct = default) => Task.CompletedTask;
+ public virtual Task GetPendingDraftCountAsync(string taskId, CancellationToken ct = default) => Task.FromResult(0);
+ public virtual Task GetMergeTargetsAsync(string taskId) => Task.FromResult(null);
+ public virtual Task> GetPlanningAggregateAsync(string planningTaskId)
+ => Task.FromResult>(Array.Empty());
+ public virtual Task BuildPlanningIntegrationBranchAsync(string planningTaskId, string targetBranch)
+ => Task.FromResult(null);
+ public virtual Task MergeAllPlanningAsync(string planningTaskId, string targetBranch) => Task.CompletedTask;
+ public virtual Task ContinuePlanningMergeAsync(string planningTaskId) => Task.CompletedTask;
+ public virtual Task AbortPlanningMergeAsync(string planningTaskId) => Task.CompletedTask;
+ public virtual Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default) => Task.CompletedTask;
+
+ protected void RaisePropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+}
diff --git a/tests/ClaudeDo.Ui.Tests/ViewModels/ConflictResolutionViewModelTests.cs b/tests/ClaudeDo.Ui.Tests/ViewModels/ConflictResolutionViewModelTests.cs
index 4e4a357..01fb7fd 100644
--- a/tests/ClaudeDo.Ui.Tests/ViewModels/ConflictResolutionViewModelTests.cs
+++ b/tests/ClaudeDo.Ui.Tests/ViewModels/ConflictResolutionViewModelTests.cs
@@ -1,4 +1,3 @@
-using System.ComponentModel;
using ClaudeDo.Data.Models;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels.Planning;
@@ -8,63 +7,21 @@ namespace ClaudeDo.Ui.Tests.ViewModels;
public class ConflictResolutionViewModelTests
{
// ------------------------------------------------------------------ fake
- private sealed class FakeWorker : IWorkerClient
+ private sealed class FakeWorker : StubWorkerClient
{
- public bool IsConnected => false;
-
public string? ContinueCalledWith { get; private set; }
public string? AbortCalledWith { get; private set; }
public Exception? ContinueThrows { get; set; }
public Exception? AbortThrows { get; set; }
- public event PropertyChangedEventHandler? PropertyChanged;
- public event Action? TaskStartedEvent;
- public event Action? TaskFinishedEvent;
- public event Action? TaskUpdatedEvent;
- public event Action? ConnectionRestoredEvent;
- public event Action? WorktreeUpdatedEvent;
- public event Action? ListUpdatedEvent;
- public event Action? TaskMessageEvent;
- public event Action? PlanningMergeStartedEvent;
- public event Action? PlanningSubtaskMergedEvent;
- public event Action>? PlanningMergeConflictEvent;
- public event Action? PlanningMergeAbortedEvent;
- public event Action? PlanningCompletedEvent;
-
- public Task WakeQueueAsync() => Task.CompletedTask;
- public Task RunNowAsync(string taskId) => Task.CompletedTask;
- public Task ContinueTaskAsync(string taskId, string followUpPrompt) => Task.CompletedTask;
- public Task ResetTaskAsync(string taskId) => Task.CompletedTask;
- public Task CancelTaskAsync(string taskId) => Task.CompletedTask;
- public Task> GetAgentsAsync() => Task.FromResult(new List());
- public Task GetListConfigAsync(string listId) => Task.FromResult(null);
- public Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto) => Task.CompletedTask;
- public Task SetTaskStatusAsync(string taskId, ClaudeDo.Data.Models.TaskStatus status) => Task.CompletedTask;
- public Task SetTaskTagsAsync(string taskId, IEnumerable tagNames) => Task.CompletedTask;
- public Task> GetAllTagsAsync() => Task.FromResult(new List());
- public Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask;
- public Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask;
- public Task DiscardPlanningSessionAsync(string taskId, bool dequeueQueuedChildren = false, CancellationToken ct = default)
- => Task.FromResult(new ClaudeDo.Data.Repositories.DiscardPlanningOutcome(ClaudeDo.Data.Repositories.DiscardPlanningResult.Discarded, 0, 0));
- public Task FinalizePlanningSessionAsync(string taskId, bool queueAgentTasks = true, CancellationToken ct = default) => Task.CompletedTask;
- public Task GetPendingDraftCountAsync(string taskId, CancellationToken ct = default) => Task.FromResult(0);
- public Task GetMergeTargetsAsync(string taskId) => Task.FromResult(null);
- public Task> GetPlanningAggregateAsync(string planningTaskId) =>
- Task.FromResult>(Array.Empty());
- public Task BuildPlanningIntegrationBranchAsync(string planningTaskId, string targetBranch) =>
- Task.FromResult(null);
- public Task MergeAllPlanningAsync(string planningTaskId, string targetBranch) => Task.CompletedTask;
- public Task OpenInteractiveTerminalAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask;
- public Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default) => Task.CompletedTask;
-
- public Task ContinuePlanningMergeAsync(string planningTaskId)
+ public override Task ContinuePlanningMergeAsync(string planningTaskId)
{
ContinueCalledWith = planningTaskId;
if (ContinueThrows is not null) throw ContinueThrows;
return Task.CompletedTask;
}
- public Task AbortPlanningMergeAsync(string planningTaskId)
+ public override Task AbortPlanningMergeAsync(string planningTaskId)
{
AbortCalledWith = planningTaskId;
if (AbortThrows is not null) throw AbortThrows;
diff --git a/tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandPlanningTests.cs b/tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandPlanningTests.cs
index 1b16d49..ac689a3 100644
--- a/tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandPlanningTests.cs
+++ b/tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandPlanningTests.cs
@@ -1,5 +1,4 @@
using System.Collections.ObjectModel;
-using System.ComponentModel;
using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Ui.Services;
@@ -42,52 +41,11 @@ public class DetailsIslandPlanningTests : IDisposable
public ClaudeDoDbContext CreateDbContext() => _create();
}
- private sealed class FakeWorkerClient : IWorkerClient
+ private sealed class FakeWorkerClient : StubWorkerClient
{
- public event PropertyChangedEventHandler? PropertyChanged;
- public event Action? TaskStartedEvent;
- public event Action? TaskFinishedEvent;
- public event Action? TaskUpdatedEvent;
- public event Action? ConnectionRestoredEvent;
- public event Action? WorktreeUpdatedEvent;
- public event Action? ListUpdatedEvent;
- public event Action? TaskMessageEvent;
- public event Action? PlanningMergeStartedEvent;
- public event Action? PlanningSubtaskMergedEvent;
- public event Action>? PlanningMergeConflictEvent;
- public event Action? PlanningMergeAbortedEvent;
- public event Action? PlanningCompletedEvent;
-
- public bool IsConnected => false;
public MergeTargetsDto? MergeTargetsResult { get; set; }
- public Task WakeQueueAsync() => Task.CompletedTask;
- public Task RunNowAsync(string taskId) => Task.CompletedTask;
- public Task ContinueTaskAsync(string taskId, string followUpPrompt) => Task.CompletedTask;
- public Task ResetTaskAsync(string taskId) => Task.CompletedTask;
- public Task CancelTaskAsync(string taskId) => Task.CompletedTask;
- public Task> GetAgentsAsync() => Task.FromResult(new List());
- public Task GetListConfigAsync(string listId) => Task.FromResult(null);
- public Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto) => Task.CompletedTask;
- public Task SetTaskStatusAsync(string taskId, TaskStatus status) => Task.CompletedTask;
- public Task SetTaskTagsAsync(string taskId, IEnumerable tagNames) => Task.CompletedTask;
- public Task> GetAllTagsAsync() => Task.FromResult(new List());
- public Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask;
- public Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask;
- public Task DiscardPlanningSessionAsync(string taskId, bool dequeueQueuedChildren = false, CancellationToken ct = default)
- => Task.FromResult(new ClaudeDo.Data.Repositories.DiscardPlanningOutcome(ClaudeDo.Data.Repositories.DiscardPlanningResult.Discarded, 0, 0));
- public Task FinalizePlanningSessionAsync(string taskId, bool queueAgentTasks = true, CancellationToken ct = default) => Task.CompletedTask;
- public Task GetPendingDraftCountAsync(string taskId, CancellationToken ct = default) => Task.FromResult(0);
- public Task GetMergeTargetsAsync(string taskId) => Task.FromResult(MergeTargetsResult);
- public Task> GetPlanningAggregateAsync(string planningTaskId) =>
- Task.FromResult>(Array.Empty());
- public Task BuildPlanningIntegrationBranchAsync(string planningTaskId, string targetBranch) =>
- Task.FromResult(null);
- public Task MergeAllPlanningAsync(string planningTaskId, string targetBranch) => Task.CompletedTask;
- public Task ContinuePlanningMergeAsync(string planningTaskId) => Task.CompletedTask;
- public Task AbortPlanningMergeAsync(string planningTaskId) => Task.CompletedTask;
- public Task OpenInteractiveTerminalAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask;
- public Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default) => Task.CompletedTask;
+ public override Task GetMergeTargetsAsync(string taskId) => Task.FromResult(MergeTargetsResult);
}
private sealed class NullServiceProvider : IServiceProvider
diff --git a/tests/ClaudeDo.Ui.Tests/ViewModels/PlanningDiffViewModelTests.cs b/tests/ClaudeDo.Ui.Tests/ViewModels/PlanningDiffViewModelTests.cs
index f8f6b68..4e1e530 100644
--- a/tests/ClaudeDo.Ui.Tests/ViewModels/PlanningDiffViewModelTests.cs
+++ b/tests/ClaudeDo.Ui.Tests/ViewModels/PlanningDiffViewModelTests.cs
@@ -1,4 +1,3 @@
-using System.ComponentModel;
using ClaudeDo.Data.Models;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels.Planning;
@@ -7,54 +6,15 @@ namespace ClaudeDo.Ui.Tests.ViewModels;
public class PlanningDiffViewModelTests
{
- private sealed class FakePlanningWorker : IWorkerClient
+ private sealed class FakePlanningWorker : StubWorkerClient
{
- public event PropertyChangedEventHandler? PropertyChanged;
- public event Action? TaskStartedEvent;
- public event Action? TaskFinishedEvent;
- public event Action? TaskUpdatedEvent;
- public event Action? ConnectionRestoredEvent;
- public event Action? WorktreeUpdatedEvent;
- public event Action? ListUpdatedEvent;
- public event Action? TaskMessageEvent;
- public event Action? PlanningMergeStartedEvent;
- public event Action? PlanningSubtaskMergedEvent;
- public event Action>? PlanningMergeConflictEvent;
- public event Action? PlanningMergeAbortedEvent;
- public event Action? PlanningCompletedEvent;
-
- public bool IsConnected => false;
-
public IReadOnlyList AggregateResult { get; set; } = Array.Empty();
public CombinedDiffResultDto? CombinedResult { get; set; }
- public Task WakeQueueAsync() => Task.CompletedTask;
- public Task RunNowAsync(string taskId) => Task.CompletedTask;
- public Task ContinueTaskAsync(string taskId, string followUpPrompt) => Task.CompletedTask;
- public Task ResetTaskAsync(string taskId) => Task.CompletedTask;
- public Task CancelTaskAsync(string taskId) => Task.CompletedTask;
- public Task> GetAgentsAsync() => Task.FromResult(new List());
- public Task GetListConfigAsync(string listId) => Task.FromResult(null);
- public Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto) => Task.CompletedTask;
- public Task SetTaskStatusAsync(string taskId, ClaudeDo.Data.Models.TaskStatus status) => Task.CompletedTask;
- public Task SetTaskTagsAsync(string taskId, IEnumerable tagNames) => Task.CompletedTask;
- public Task> GetAllTagsAsync() => Task.FromResult(new List());
- public Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask;
- public Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask;
- public Task DiscardPlanningSessionAsync(string taskId, bool dequeueQueuedChildren = false, CancellationToken ct = default)
- => Task.FromResult(new ClaudeDo.Data.Repositories.DiscardPlanningOutcome(ClaudeDo.Data.Repositories.DiscardPlanningResult.Discarded, 0, 0));
- public Task FinalizePlanningSessionAsync(string taskId, bool queueAgentTasks = true, CancellationToken ct = default) => Task.CompletedTask;
- public Task GetPendingDraftCountAsync(string taskId, CancellationToken ct = default) => Task.FromResult(0);
- public Task GetMergeTargetsAsync(string taskId) => Task.FromResult(null);
- public Task> GetPlanningAggregateAsync(string planningTaskId) =>
+ public override Task> GetPlanningAggregateAsync(string planningTaskId) =>
Task.FromResult(AggregateResult);
- public Task BuildPlanningIntegrationBranchAsync(string planningTaskId, string targetBranch) =>
+ public override Task BuildPlanningIntegrationBranchAsync(string planningTaskId, string targetBranch) =>
Task.FromResult(CombinedResult);
- public Task MergeAllPlanningAsync(string planningTaskId, string targetBranch) => Task.CompletedTask;
- public Task ContinuePlanningMergeAsync(string planningTaskId) => Task.CompletedTask;
- public Task AbortPlanningMergeAsync(string planningTaskId) => Task.CompletedTask;
- public Task OpenInteractiveTerminalAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask;
- public Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default) => Task.CompletedTask;
}
[Fact]