Files
ClaudeDo/tests/ClaudeDo.Worker.Tests/UiVm/TaskRowViewModelPlanningTests.cs
Mika Kuns 4847c5c0a4 feat(ui): My Day actions, orphan-aware grouping, menu restructure
Pending UI work:
- My Day add/remove context actions on task rows (parent removal cascades to children)
- orphan-aware grouping: a child whose parent isn't in view renders as a top-level row, not an indented draft
- shell menu restructure (Worker / Repositories submenus); 'Finalize plan' action, drop 'Queue subtasks sequentially'
- notes editor refinements
- subtask-row hover tweak (Surface3, no transition)
- bump Avalonia 12.0.0 -> 12.0.4
2026-06-18 16:22:29 +02:00

192 lines
5.8 KiB
C#

using System.IO;
using ClaudeDo.Data.Models;
using ClaudeDo.Localization;
using ClaudeDo.Ui.Localization;
using ClaudeDo.Ui.ViewModels.Islands;
using Xunit;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Worker.Tests.UiVm;
public class TaskRowViewModelPlanningTests
{
public TaskRowViewModelPlanningTests()
{
var dir = AppContext.BaseDirectory;
while (dir is not null && !Directory.Exists(Path.Combine(dir, "src", "ClaudeDo.Localization", "locales")))
dir = Path.GetDirectoryName(dir);
Loc.Current = new Localizer(
LocaleStore.Load(Path.Combine(dir!, "src", "ClaudeDo.Localization", "locales")), "en");
}
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);
}
[Fact]
public void ActivePlanningParent_CannotSendToQueue()
{
var vm = MakeRow(TaskStatus.Idle, phase: PlanningPhase.Active);
vm.HasPlanningChildren = true;
Assert.False(vm.CanSendToQueue);
}
[Fact]
public void FinalizedParentWithChildren_CanSendToQueue()
{
var vm = MakeRow(TaskStatus.Idle, phase: PlanningPhase.Finalized);
vm.HasPlanningChildren = true;
Assert.True(vm.CanSendToQueue);
}
[Fact]
public void ActivePlanning_CanFinalizePlanning()
{
var vm = MakeRow(TaskStatus.Idle, phase: PlanningPhase.Active);
Assert.True(vm.CanFinalizePlanning);
}
[Fact]
public void FinalizedPlanning_CannotFinalizePlanning()
{
var vm = MakeRow(TaskStatus.Idle, phase: PlanningPhase.Finalized);
Assert.False(vm.CanFinalizePlanning);
}
[Fact]
public void PlainIdle_CannotFinalizePlanning()
{
var vm = MakeRow(TaskStatus.Idle);
Assert.False(vm.CanFinalizePlanning);
}
[Fact]
public void ChildWithParentInView_RendersAsChild()
{
var vm = MakeRow(TaskStatus.Idle, parentTaskId: "parent-id");
Assert.True(vm.ParentInView); // default
Assert.True(vm.ShowAsChild);
Assert.True(vm.IsDraft);
}
[Fact]
public void OrphanedChild_RendersFlat_WithNoDraftOrPlannedBadge()
{
// Parent absent from the view (e.g. removed from My Day, or daily-prep placed a lone
// child there): the row stays a child by data but must read as a normal top-level task.
var draftOrphan = MakeRow(TaskStatus.Idle, parentTaskId: "missing");
draftOrphan.ParentInView = false;
Assert.True(draftOrphan.IsChild);
Assert.False(draftOrphan.ShowAsChild);
Assert.False(draftOrphan.IsDraft);
var plannedOrphan = MakeRow(TaskStatus.Idle, parentTaskId: "missing");
plannedOrphan.ParentFinalized = true;
plannedOrphan.ParentInView = false;
Assert.False(plannedOrphan.ShowAsChild);
Assert.False(plannedOrphan.IsPlanned);
Assert.False(plannedOrphan.IsDraft);
}
[Fact]
public void CanAddToMyDay_TrueOnlyWhenNotInMyDayAndNotDone()
{
var row = MakeRow(TaskStatus.Idle);
Assert.True(row.CanAddToMyDay); // idle, not yet in My Day
row.IsMyDay = true;
Assert.False(row.CanAddToMyDay); // already in My Day
row.IsMyDay = false;
row.Done = true;
Assert.False(row.CanAddToMyDay); // done tasks don't belong in today's focus
}
}