Files
ClaudeDo/tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandDependencyWaitTests.cs
T
mika kuns 4cf601e423 feat(ui): surface depends_on blocking in status chip, chain label, and detail pane
Usability-Audit 2026-09-10 found three gaps versus the dependency-chain-display
spec: a Queued task blocked on an unmet task_dependencies edge rendered the same
chip as one about to run, a cross-list predecessor silently dropped the
"after #N" chip, and the detail pane showed nothing about a wait at all.

- TaskRowViewModel gains IsBlockedByDependency/DependencyWaitLabel (extrinsic,
  assigned by TasksIslandViewModel like the existing chain fields); IsQueued/
  IsWaiting/StatusChipClass/StatusLabel/StatusChipTooltip now account for it
  alongside the existing BlockedByTaskId planning-chain link.
- TasksIslandViewModel.ApplyDependencyWaitState computes the blocked flag and
  wait label per row; a new RefreshExternalDependencyCacheAsync backfills
  Number/Title/Status for predecessors not loaded into the current list's
  Items (reusing the already-open DbContext, no new hub method), so
  BuildAfterLabel/blocking both resolve a listenfremder Vorgänger correctly.
- New "waiting" chip style (IslandStyles.axaml) and locale keys (en+de);
  TaskHeaderBar shows a "Waiting on #N" line bound straight to the same row.

Tests: TasksIslandChainTests (blocked/unblocked chip, cross-list predecessor),
DetailsIslandDependencyWaitTests (new), TaskRowViewModelNotificationsTests
updated for the new notification set.
2026-09-10 13:32:36 +02:00

73 lines
2.3 KiB
C#

using ClaudeDo.Data;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels.Islands;
using Microsoft.EntityFrameworkCore;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Ui.Tests.ViewModels;
// Usability-Audit 2026-09-10: the detail pane showed nothing when a task was blocked on a
// predecessor -- the header bar now renders a "Waiting on #N" line bound straight to the same
// Task.IsWaiting/DependencyWaitLabel TasksIslandViewModel already computes (no new VM logic here).
public class DetailsIslandDependencyWaitTests : IDisposable
{
private readonly string _dbPath;
public DetailsIslandDependencyWaitTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_dep_wait_test_{Guid.NewGuid():N}.db");
using var ctx = NewContext();
ctx.Database.EnsureCreated();
}
public void Dispose()
{
try { File.Delete(_dbPath); } catch { }
try { File.Delete(_dbPath + "-wal"); } catch { }
try { File.Delete(_dbPath + "-shm"); } catch { }
}
private ClaudeDoDbContext NewContext()
{
var opts = new DbContextOptionsBuilder<ClaudeDoDbContext>()
.UseSqlite($"Data Source={_dbPath}")
.Options;
return new ClaudeDoDbContext(opts);
}
private DetailsIslandViewModel NewVm()
{
var factory = new TestDbFactory(NewContext);
return new DetailsIslandViewModel(factory, new DefaultStub(), new NullServiceProvider(), new ClaudeDo.Ui.Services.MergeCoordinator());
}
[Fact]
public void UnblockedTask_HasNoWaitingLine()
{
var vm = NewVm();
vm.Bind(new TaskRowViewModel { Id = Guid.NewGuid().ToString("N"), Number = 1, Status = TaskStatus.Queued });
Assert.False(vm.Task!.IsWaiting);
Assert.Null(vm.Task.DependencyWaitLabel);
}
[Fact]
public void BlockedTask_ExposesWaitingLineViaBoundRow()
{
var vm = NewVm();
var row = new TaskRowViewModel
{
Id = Guid.NewGuid().ToString("N"),
Number = 2,
Status = TaskStatus.Queued,
IsBlockedByDependency = true,
DependencyWaitLabel = "#42",
};
vm.Bind(row);
Assert.True(vm.Task!.IsWaiting);
Assert.Equal("#42", vm.Task.DependencyWaitLabel);
}
}