Files
ClaudeDo/tests/ClaudeDo.Worker.Tests/UiVm/TaskRowViewModelNotificationsTests.cs
T
Mika Kuns b24ff614ff chore(claude-do): TaskRowViewModel: manuelle OnPropertyChanged-Kette auf [Noti
src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs enthält ~107 handgeschriebene OnPropertyChanged(nameof(...))-Aufrufe in partial void OnXChanged-Methoden. Das Projekt nutzt an anderen Stellen den Generator: [NotifyPropertyChangedFor] in DetailsIslandViewModel.cs, ConflictResolverViewModel.cs, AgentConfigEditorViewModel.cs, Installer/WizardViewModel.cs.

Das ist die eine Datei, in der eine ve

ClaudeDo-Task: 87976462-5845-4e01-8233-32afadca02bd
2026-09-02 12:16:06 +02:00

464 lines
16 KiB
C#

using System.Collections.Generic;
using ClaudeDo.Data.Models;
using ClaudeDo.Ui.ViewModels.Islands;
using Xunit;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Worker.Tests.UiVm;
// Characterization test for TaskRowViewModel's PropertyChanged fan-out. For every source
// property that today drives dependent notifications (via a handwritten partial OnXChanged),
// this pins down the exact SET of names raised when that property changes. Order is
// deliberately not asserted: a NotifyPropertyChangedFor-based rewrite raises the source
// property's own notification before its dependents instead of after, which is an unobservable
// reordering, not a behavior change. This test must stay green across that rewrite.
public class TaskRowViewModelNotificationsTests
{
private static HashSet<string?> Raised(TaskRowViewModel vm, System.Action mutate)
{
var raised = new HashSet<string?>();
vm.PropertyChanged += (_, e) => raised.Add(e.PropertyName);
mutate();
return raised;
}
[Fact]
public void StatusChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.Status = TaskStatus.Running);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.Status),
nameof(TaskRowViewModel.StatusChipClass),
nameof(TaskRowViewModel.StatusLabel),
nameof(TaskRowViewModel.ShowStatusChip),
nameof(TaskRowViewModel.IsRunning),
nameof(TaskRowViewModel.IsWaitingForReview),
nameof(TaskRowViewModel.IsParked),
nameof(TaskRowViewModel.IsQueued),
nameof(TaskRowViewModel.IsWaiting),
nameof(TaskRowViewModel.HasFailureReason),
nameof(TaskRowViewModel.FailureReasonTooltip),
nameof(TaskRowViewModel.StatusChipTooltip),
nameof(TaskRowViewModel.IsDraft),
nameof(TaskRowViewModel.IsPlanned),
nameof(TaskRowViewModel.CanOpenPlanningSession),
nameof(TaskRowViewModel.CanRemoveFromQueue),
nameof(TaskRowViewModel.CanSendToQueue),
nameof(TaskRowViewModel.CanRefine),
nameof(TaskRowViewModel.SendToQueueDisabledReason),
nameof(TaskRowViewModel.CancelDisabledReason),
nameof(TaskRowViewModel.RefineDisabledReason),
nameof(TaskRowViewModel.PlanningDisabledReason),
nameof(TaskRowViewModel.MarkAsDisabledReason),
}, raised);
}
[Fact]
public void ParentTaskIdChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.ParentTaskId = "parent");
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.ParentTaskId),
nameof(TaskRowViewModel.IsChild),
nameof(TaskRowViewModel.IsAgentSuggested),
nameof(TaskRowViewModel.ShowAsChild),
nameof(TaskRowViewModel.IsDraft),
nameof(TaskRowViewModel.IsPlanned),
nameof(TaskRowViewModel.CanSendToQueue),
nameof(TaskRowViewModel.CanOpenPlanningSession),
nameof(TaskRowViewModel.SendToQueueDisabledReason),
nameof(TaskRowViewModel.PlanningDisabledReason),
}, raised);
}
[Fact]
public void ParentInViewChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.ParentInView = false);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.ParentInView),
nameof(TaskRowViewModel.ShowAsChild),
nameof(TaskRowViewModel.IsDraft),
nameof(TaskRowViewModel.IsPlanned),
}, raised);
}
[Fact]
public void CreatedByChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.CreatedBy = "someone");
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.CreatedBy),
nameof(TaskRowViewModel.IsAgentSuggested),
}, raised);
}
[Fact]
public void ParentFinalizedChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.ParentFinalized = true);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.ParentFinalized),
nameof(TaskRowViewModel.IsDraft),
nameof(TaskRowViewModel.IsPlanned),
nameof(TaskRowViewModel.CanSendToQueue),
nameof(TaskRowViewModel.SendToQueueDisabledReason),
}, raised);
}
[Fact]
public void PlanningPhaseChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.PlanningPhase = PlanningPhase.Active);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.PlanningPhase),
nameof(TaskRowViewModel.IsPlanningParent),
nameof(TaskRowViewModel.PlanningBadge),
nameof(TaskRowViewModel.ShowStatusChip),
nameof(TaskRowViewModel.IsPlanActive),
nameof(TaskRowViewModel.IsPlanFinalized),
nameof(TaskRowViewModel.CanOpenPlanningSession),
nameof(TaskRowViewModel.CanResumeOrDiscardPlanning),
nameof(TaskRowViewModel.CanQueuePlan),
nameof(TaskRowViewModel.CanSendToQueue),
nameof(TaskRowViewModel.CanFinalizePlanning),
nameof(TaskRowViewModel.CanRefine),
nameof(TaskRowViewModel.SendToQueueDisabledReason),
nameof(TaskRowViewModel.RefineDisabledReason),
nameof(TaskRowViewModel.PlanningDisabledReason),
}, raised);
}
[Fact]
public void IsRefiningChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.IsRefining = true);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.IsRefining),
nameof(TaskRowViewModel.CanRefine),
nameof(TaskRowViewModel.RefineDisabledReason),
}, raised);
}
[Fact]
public void IsManualChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.IsManual = true);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.IsManual),
nameof(TaskRowViewModel.ManualBadge),
nameof(TaskRowViewModel.CanRefine),
nameof(TaskRowViewModel.CanSendToQueue),
nameof(TaskRowViewModel.CanOpenPlanningSession),
nameof(TaskRowViewModel.SendToQueueDisabledReason),
nameof(TaskRowViewModel.RefineDisabledReason),
nameof(TaskRowViewModel.PlanningDisabledReason),
}, raised);
}
[Fact]
public void HasInteractiveSessionChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.HasInteractiveSession = true);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.HasInteractiveSession),
nameof(TaskRowViewModel.IsParked),
nameof(TaskRowViewModel.StatusChipClass),
nameof(TaskRowViewModel.StatusLabel),
nameof(TaskRowViewModel.ShowStatusChip),
nameof(TaskRowViewModel.StatusChipTooltip),
nameof(TaskRowViewModel.CanSendToQueue),
nameof(TaskRowViewModel.SendToQueueDisabledReason),
nameof(TaskRowViewModel.CanDeleteTask),
nameof(TaskRowViewModel.DeleteDisabledReason),
}, raised);
}
[Fact]
public void FailureReasonChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.FailureReason = "error");
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.FailureReason),
nameof(TaskRowViewModel.HasFailureReason),
nameof(TaskRowViewModel.FailureReasonLabel),
nameof(TaskRowViewModel.FailureReasonTooltip),
nameof(TaskRowViewModel.StatusChipTooltip),
}, raised);
}
[Fact]
public void FailureTurnsUsedChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.FailureTurnsUsed = 5);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.FailureTurnsUsed),
nameof(TaskRowViewModel.FailureReasonTooltip),
}, raised);
}
[Fact]
public void FailureMaxTurnsChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.FailureMaxTurns = 10);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.FailureMaxTurns),
nameof(TaskRowViewModel.FailureReasonTooltip),
}, raised);
}
[Fact]
public void HasQueuedSubtasksChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.HasQueuedSubtasks = true);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.HasQueuedSubtasks),
nameof(TaskRowViewModel.CanRemoveFromQueue),
nameof(TaskRowViewModel.CanSendToQueue),
nameof(TaskRowViewModel.CanQueuePlan),
nameof(TaskRowViewModel.SendToQueueDisabledReason),
}, raised);
}
[Fact]
public void IsHoveredChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.IsHovered = true);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.IsHovered),
nameof(TaskRowViewModel.ShowRowActions),
}, raised);
}
[Fact]
public void IsSelectedChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.IsSelected = true);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.IsSelected),
nameof(TaskRowViewModel.ShowRowActions),
}, raised);
}
[Fact]
public void BlockedByTaskIdChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.BlockedByTaskId = "blocker");
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.BlockedByTaskId),
nameof(TaskRowViewModel.IsQueued),
nameof(TaskRowViewModel.IsWaiting),
nameof(TaskRowViewModel.StatusChipClass),
}, raised);
}
[Fact]
public void HasPlanningChildrenChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.HasPlanningChildren = true);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.HasPlanningChildren),
nameof(TaskRowViewModel.IsPlanningParent),
nameof(TaskRowViewModel.CanQueuePlan),
}, raised);
}
[Fact]
public void NumberChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.Number = 5);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.Number),
nameof(TaskRowViewModel.ShowNumberBadge),
}, raised);
}
[Fact]
public void BranchChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.Branch = "main");
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.Branch),
nameof(TaskRowViewModel.HasBranch),
}, raised);
}
[Fact]
public void WorktreePathChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.WorktreePath = @"C:\repo\worktrees\t");
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.WorktreePath),
nameof(TaskRowViewModel.CanOpenWorktree),
nameof(TaskRowViewModel.OpenWorktreeDisabledReason),
}, raised);
}
[Fact]
public void WorktreeStateChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.WorktreeState = ClaudeDo.Data.Models.WorktreeState.Active);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.WorktreeState),
nameof(TaskRowViewModel.IsParked),
nameof(TaskRowViewModel.StatusLabel),
}, raised);
}
[Fact]
public void DoneChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.Done = true);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.Done),
nameof(TaskRowViewModel.IsOverdue),
nameof(TaskRowViewModel.CanAddToMyDay),
}, raised);
}
[Fact]
public void IsMyDayChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.IsMyDay = true);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.IsMyDay),
nameof(TaskRowViewModel.CanAddToMyDay),
}, raised);
}
[Fact]
public void ScheduledForChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.ScheduledFor = DateTime.UtcNow);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.ScheduledFor),
nameof(TaskRowViewModel.IsOverdue),
nameof(TaskRowViewModel.HasSchedule),
}, raised);
}
[Fact]
public void DiffAdditionsChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.DiffAdditions = 5);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.DiffAdditions),
nameof(TaskRowViewModel.HasDiff),
nameof(TaskRowViewModel.DiffAdditionsText),
}, raised);
}
[Fact]
public void DiffDeletionsChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.DiffDeletions = 5);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.DiffDeletions),
nameof(TaskRowViewModel.HasDiff),
nameof(TaskRowViewModel.DiffDeletionsText),
}, raised);
}
[Fact]
public void RoadblockCountChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.RoadblockCount = 3);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.RoadblockCount),
nameof(TaskRowViewModel.HasRoadblock),
nameof(TaskRowViewModel.RoadblockTooltip),
}, raised);
}
[Fact]
public void DropHintAboveChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.DropHintAbove = true);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.DropHintAbove),
nameof(TaskRowViewModel.IsDropTarget),
}, raised);
}
[Fact]
public void DropHintBelowChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.DropHintBelow = true);
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.DropHintBelow),
nameof(TaskRowViewModel.IsDropTarget),
}, raised);
}
[Fact]
public void CreationPhaseChanged_Notifies_Dependents()
{
var vm = new TaskRowViewModel { Id = "t" };
var raised = Raised(vm, () => vm.CreationPhase = "creating_worktree");
Assert.Equal(new HashSet<string?>
{
nameof(TaskRowViewModel.CreationPhase),
nameof(TaskRowViewModel.HasCreationPhase),
nameof(TaskRowViewModel.CreationPhaseLabel),
}, raised);
}
}