Files
ClaudeDo/tests/ClaudeDo.Worker.Tests/UiVm/TaskRowViewModelPlanningTests.cs
mika kuns ce79a2d0fe feat(planning): gate subtask queueing behind plan finalization
Planning subtasks are now "Draft" until their parent plan is finalized,
then "Planned" (queueable). Finalizing a plan no longer auto-queues the
child chain; the user sends the plan to the queue explicitly.

- TaskStateService rejects a child entering Queued/Running unless its parent
  is Finalized; this single invariant covers UI, queue, RunNow and MCP paths
- WorkerHub.SetTaskStatus routes Queued through the gated EnqueueAsync
- Finalize call sites pass queueAgentTasks: false
- PlanningChainCoordinator.QueuePlanAsync guards the chain build on Finalized
- TaskRowViewModel derives Draft/Planned from ParentFinalized; gates
  CanSendToQueue / CanQueuePlan; view shows a PLANNED badge

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:41:48 +02:00

101 lines
2.8 KiB
C#

using ClaudeDo.Data.Models;
using ClaudeDo.Ui.ViewModels.Islands;
using Xunit;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Worker.Tests.UiVm;
public class TaskRowViewModelPlanningTests
{
private static TaskRowViewModel MakeRow(
TaskStatus status,
string? parentTaskId = null,
PlanningPhase phase = PlanningPhase.None)
=> new TaskRowViewModel { Id = "t", Status = status, ParentTaskId = parentTaskId, PlanningPhase = phase };
[Fact]
public void IdleChild_IsDraft_WhenParentIdIsNotNull()
{
var vm = MakeRow(TaskStatus.Idle, parentTaskId: "parent-id");
Assert.True(vm.IsChild);
Assert.True(vm.IsDraft);
Assert.False(vm.IsPlanningParent);
}
[Fact]
public void ActivePlanning_SetsIsPlanningParent()
{
var vm = MakeRow(TaskStatus.Idle, phase: PlanningPhase.Active);
Assert.True(vm.IsPlanningParent);
Assert.False(vm.IsChild);
Assert.Equal("PLANNING", vm.PlanningBadge);
}
[Fact]
public void FinalizedPlanning_ShowsPlannedBadge()
{
var vm = MakeRow(TaskStatus.Idle, phase: PlanningPhase.Finalized);
Assert.True(vm.IsPlanningParent);
Assert.Equal("PLANNED", vm.PlanningBadge);
}
[Fact]
public void PlainIdle_NoBadge()
{
var vm = MakeRow(TaskStatus.Idle);
Assert.False(vm.IsPlanningParent);
Assert.Null(vm.PlanningBadge);
}
[Fact]
public void DraftChild_CannotSendToQueue()
{
var vm = MakeRow(TaskStatus.Idle, parentTaskId: "parent-id");
vm.ParentFinalized = false;
Assert.True(vm.IsDraft);
Assert.False(vm.IsPlanned);
Assert.False(vm.CanSendToQueue);
}
[Fact]
public void PlannedChild_CanSendToQueue()
{
var vm = MakeRow(TaskStatus.Idle, parentTaskId: "parent-id");
vm.ParentFinalized = true;
Assert.False(vm.IsDraft);
Assert.True(vm.IsPlanned);
Assert.True(vm.CanSendToQueue);
}
[Fact]
public void StandaloneIdle_CanSendToQueue()
{
var vm = MakeRow(TaskStatus.Idle);
Assert.False(vm.IsChild);
Assert.True(vm.CanSendToQueue);
}
[Fact]
public void FinalizedParentWithChildren_CanQueuePlan()
{
var vm = MakeRow(TaskStatus.Idle, phase: PlanningPhase.Finalized);
vm.HasPlanningChildren = true;
Assert.True(vm.CanQueuePlan);
}
[Fact]
public void ActiveParentWithChildren_CannotQueuePlan()
{
var vm = MakeRow(TaskStatus.Idle, phase: PlanningPhase.Active);
vm.HasPlanningChildren = true;
Assert.False(vm.CanQueuePlan);
}
[Fact]
public void FinalizedParentWithoutChildren_CannotQueuePlan()
{
var vm = MakeRow(TaskStatus.Idle, phase: PlanningPhase.Finalized);
Assert.False(vm.CanQueuePlan);
}
}