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 } }