From 59d72635da268257afac2dc6653e158dc5cffd16 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Sat, 30 May 2026 09:39:12 +0200 Subject: [PATCH] test(ui): rebase IWorkerClient fakes onto shared StubWorkerClient base Add a StubWorkerClient base implementing the full IWorkerClient surface so the planning/conflict/diff test fakes only override the members they exercise. Eliminates the constructor-drift duplication across the three fakes. Co-Authored-By: Claude Opus 4.7 --- tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs | 61 +++++++++++++++++++ .../ConflictResolutionViewModelTests.cs | 49 +-------------- .../ViewModels/DetailsIslandPlanningTests.cs | 46 +------------- .../ViewModels/PlanningDiffViewModelTests.cs | 46 +------------- 4 files changed, 69 insertions(+), 133 deletions(-) create mode 100644 tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs 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]