Files
ClaudeDo/tests/ClaudeDo.Ui.Tests/ViewModels/TasksIslandPlanningOperationTests.cs
T
mika kuns 76d836a278 test: dedupe the nested test doubles into one shared file
TestDbFactory was redeclared as a private nested class in 40 test files,
NullServiceProvider and StubNotesApi in 14 each, DefaultStub in 5 — 73
declarations, all semantically identical (StubNotesApi differed only in
formatting and type qualification). They now live in TestDoubles.cs next to the
existing StubWorkerClient, which was already the shared-double pattern in this
project.
2026-08-26 10:11:58 +02:00

128 lines
4.4 KiB
C#

using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Ui.ViewModels.Islands;
using Microsoft.EntityFrameworkCore;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Ui.Tests.ViewModels;
// PlanningOperation backs the shared feedback surface for the two planning actions triggered
// from the task-row context menu (which closes the instant a click lands, so there is nothing
// per-row left to anchor an indicator to) — see docs/superpowers/plans/2026-08-11-operation-feedback.md, A4.
public class TasksIslandPlanningOperationTests : IDisposable
{
private readonly string _dbPath;
public TasksIslandPlanningOperationTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_planning_op_{Guid.NewGuid():N}.db");
using var ctx = NewContext();
ctx.Database.EnsureCreated();
}
public void Dispose()
{
try { File.Delete(_dbPath); } catch { }
try { File.Delete(_dbPath + "-wal"); } catch { }
try { File.Delete(_dbPath + "-shm"); } catch { }
}
private ClaudeDoDbContext NewContext()
{
var opts = new DbContextOptionsBuilder<ClaudeDoDbContext>()
.UseSqlite($"Data Source={_dbPath}")
.Options;
return new ClaudeDoDbContext(opts);
}
private sealed class GatedWorkerClient : StubWorkerClient
{
public override bool IsConnected => true;
public TaskCompletionSource<object?> QueueGate { get; } = new();
public TaskCompletionSource<object?> FinalizeGate { get; } = new();
public bool ThrowOnQueue { get; set; }
public bool ThrowOnFinalize { get; set; }
public override async Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default)
{
if (ThrowOnQueue) throw new InvalidOperationException("boom");
await QueueGate.Task;
}
public override async Task FinalizePlanningSessionAsync(string taskId, bool queueAgentTasks = true, CancellationToken ct = default)
{
if (ThrowOnFinalize) throw new InvalidOperationException("boom");
await FinalizeGate.Task;
}
}
private TasksIslandViewModel BuildViewModel(GatedWorkerClient worker) =>
new(new TestDbFactory(NewContext), worker);
private static TaskRowViewModel Row(string id) => new() { Id = id, Status = TaskStatus.Idle };
[Fact]
public async Task QueuePlanningSubtasks_sets_PlanningOperation_IsRunning_while_pending()
{
var worker = new GatedWorkerClient();
var vm = BuildViewModel(worker);
var row = Row("parent-1");
var run = vm.QueuePlanningSubtasksCommand.ExecuteAsync(row);
Assert.True(vm.PlanningOperation.IsRunning);
worker.QueueGate.SetResult(null);
await run;
Assert.False(vm.PlanningOperation.IsRunning);
}
[Fact]
public async Task FinalizePlanningSession_sets_PlanningOperation_IsRunning_while_pending()
{
var worker = new GatedWorkerClient();
var vm = BuildViewModel(worker);
var row = Row("parent-2");
var run = vm.FinalizePlanningSessionCommand.ExecuteAsync(row);
Assert.True(vm.PlanningOperation.IsRunning);
worker.FinalizeGate.SetResult(null);
await run;
Assert.False(vm.PlanningOperation.IsRunning);
}
[Fact]
public async Task PlanningCommands_CanExecute_is_locked_while_either_operation_runs()
{
var worker = new GatedWorkerClient();
var vm = BuildViewModel(worker);
var row = Row("parent-3");
var run = vm.QueuePlanningSubtasksCommand.ExecuteAsync(row);
Assert.False(vm.QueuePlanningSubtasksCommand.CanExecute(row));
Assert.False(vm.FinalizePlanningSessionCommand.CanExecute(row));
worker.QueueGate.SetResult(null);
await run;
Assert.True(vm.QueuePlanningSubtasksCommand.CanExecute(row));
Assert.True(vm.FinalizePlanningSessionCommand.CanExecute(row));
}
[Fact]
public async Task QueuePlanningSubtasks_ends_the_operation_when_the_worker_call_throws()
{
var worker = new GatedWorkerClient { ThrowOnQueue = true };
var vm = BuildViewModel(worker);
var row = Row("parent-4");
await vm.QueuePlanningSubtasksCommand.ExecuteAsync(row);
Assert.False(vm.PlanningOperation.IsRunning);
Assert.True(vm.QueuePlanningSubtasksCommand.CanExecute(row));
}
}