feat(ui): Task-Zeile entdichten und Kontextmenü gruppieren (UX-Struktur A)
Gruppe 1 (Send to queue, Remove from queue, Cancel execution, Open quick session, Refine task) ist jetzt fix sichtbar und gated per IsEnabled + Grund statt komplett auszublenden. Planning/Schedule wandern in Untermenüs (Mark as existierte bereits); der Planning-Kopf zeigt einen Grund, wenn er sonst leer wäre. Refine-Button, ungesetzter Star und Dequeue-X erscheinen nur bei Hover-oder-Selektion (TaskRowViewModel.ShowRowActions); ein gesetzter Star und der Refining-Spinner bleiben immer sichtbar. Der Branch-Chip wandert aus der Zeile in eine Meta-Zeile in TaskHeaderBar. A3 headless verifiziert: Avalonia 12 zeigt ToolTips auf IsEnabled=false Controls nur mit ToolTip.ShowOnDisabled="True" (Default ist false) — dieses Attached Property existiert bereits und wird für die neuen Disabled-Reason- Tooltips genutzt.
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Ui.ViewModels.Islands;
|
||||
using Xunit;
|
||||
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
|
||||
namespace ClaudeDo.Ui.Tests.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// [UX-Struktur A2/A4] Group 1's disabled-reason properties (grayed out with an explanation
|
||||
/// instead of hidden — see task A1) and the hover-or-selected gate for the optional row actions.
|
||||
/// </summary>
|
||||
public class TaskRowContextMenuTests
|
||||
{
|
||||
private static TaskRowViewModel MakeRow() => new() { Id = "t1" };
|
||||
|
||||
[Fact]
|
||||
public void SendToQueueDisabledReason_Null_When_Sendable()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Idle;
|
||||
Assert.True(row.CanSendToQueue);
|
||||
Assert.Null(row.SendToQueueDisabledReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendToQueueDisabledReason_Set_When_AlreadyQueued()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Queued;
|
||||
Assert.False(row.CanSendToQueue);
|
||||
Assert.NotNull(row.SendToQueueDisabledReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendToQueueDisabledReason_Set_When_Manual()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Idle;
|
||||
row.IsManual = true;
|
||||
Assert.False(row.CanSendToQueue);
|
||||
Assert.NotNull(row.SendToQueueDisabledReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CancelDisabledReason_Null_While_Running()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Running;
|
||||
Assert.Null(row.CancelDisabledReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CancelDisabledReason_Set_When_NotRunning()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Idle;
|
||||
Assert.NotNull(row.CancelDisabledReason);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(TaskStatus.Idle)]
|
||||
[InlineData(TaskStatus.Running)]
|
||||
[InlineData(TaskStatus.Done)]
|
||||
public void QuickSessionDisabledReason_Is_Always_Null(TaskStatus status)
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = status;
|
||||
row.IsManual = true;
|
||||
Assert.Null(row.QuickSessionDisabledReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RefineDisabledReason_Null_When_Refinable()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Idle;
|
||||
Assert.True(row.CanRefine);
|
||||
Assert.Null(row.RefineDisabledReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RefineDisabledReason_Set_When_Manual()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Idle;
|
||||
row.IsManual = true;
|
||||
Assert.False(row.CanRefine);
|
||||
Assert.NotNull(row.RefineDisabledReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RefineDisabledReason_Set_When_Refining()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Idle;
|
||||
row.IsRefining = true;
|
||||
Assert.False(row.CanRefine);
|
||||
Assert.NotNull(row.RefineDisabledReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RefineDisabledReason_Set_When_NotIdle()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Running;
|
||||
Assert.False(row.CanRefine);
|
||||
Assert.NotNull(row.RefineDisabledReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlanningDisabledReason_Null_When_OpenAvailable()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Idle;
|
||||
Assert.True(row.CanOpenPlanningSession);
|
||||
Assert.Null(row.PlanningDisabledReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlanningDisabledReason_Null_When_PlanningActive()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Idle;
|
||||
row.PlanningPhase = PlanningPhase.Active;
|
||||
Assert.True(row.CanResumeOrDiscardPlanning);
|
||||
Assert.Null(row.PlanningDisabledReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlanningDisabledReason_Set_When_Finalized()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Idle;
|
||||
row.PlanningPhase = PlanningPhase.Finalized;
|
||||
Assert.False(row.CanOpenPlanningSession);
|
||||
Assert.False(row.CanResumeOrDiscardPlanning);
|
||||
Assert.False(row.CanFinalizePlanning);
|
||||
Assert.NotNull(row.PlanningDisabledReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlanningDisabledReason_Set_When_Manual()
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.Status = TaskStatus.Idle;
|
||||
row.IsManual = true;
|
||||
Assert.NotNull(row.PlanningDisabledReason);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, false, false)]
|
||||
[InlineData(true, false, true)]
|
||||
[InlineData(false, true, true)]
|
||||
[InlineData(true, true, true)]
|
||||
public void ShowRowActions_Follows_Hover_Or_Selection(bool isHovered, bool isSelected, bool expected)
|
||||
{
|
||||
var row = MakeRow();
|
||||
row.IsHovered = isHovered;
|
||||
row.IsSelected = isSelected;
|
||||
Assert.Equal(expected, row.ShowRowActions);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user