feat(centralui): ExecutionTree node double-click raises OnNodeActivated
This commit is contained in:
@@ -45,7 +45,8 @@
|
||||
<span class="execution-tree-toggle execution-tree-toggle--leaf" aria-hidden="true"></span>
|
||||
}
|
||||
|
||||
<div class="execution-tree-body">
|
||||
<div class="execution-tree-body"
|
||||
@ondblclick="() => OnNodeActivated.InvokeAsync(node.ExecutionId)">
|
||||
<div class="execution-tree-headline">
|
||||
<a class="execution-tree-link font-monospace"
|
||||
data-test="tree-node-link-@node.ExecutionId"
|
||||
@@ -116,6 +117,7 @@
|
||||
the flat-list assembly entirely. *@
|
||||
<ExecutionTree PreBuiltRoots="subtree.Children"
|
||||
ArrivedFromExecutionId="ArrivedFromExecutionId"
|
||||
OnNodeActivated="OnNodeActivated"
|
||||
Depth="Depth + 1" />
|
||||
}
|
||||
</li>
|
||||
|
||||
@@ -78,6 +78,15 @@ public partial class ExecutionTree
|
||||
/// </summary>
|
||||
[Parameter] public int Depth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a node is double-clicked, carrying that node's
|
||||
/// <see cref="ExecutionTreeNode.ExecutionId"/>. The same callback is
|
||||
/// threaded unchanged into every recursive child instance, so a
|
||||
/// double-click on a node at any depth invokes the root-supplied handler
|
||||
/// (used to open the node detail modal).
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback<Guid> OnNodeActivated { get; set; }
|
||||
|
||||
// The subtrees this instance renders: assembled from Nodes on the root,
|
||||
// or taken straight from PreBuiltRoots on a nested instance.
|
||||
private IReadOnlyList<Subtree> _rootsToRender = Array.Empty<Subtree>();
|
||||
|
||||
@@ -25,7 +25,10 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* The node card: a flex row of [toggle][body]. */
|
||||
/* The node card: a flex row of [toggle][body].
|
||||
user-select: none — the body is double-clickable (opens the node detail
|
||||
modal), so suppress the text selection a double-click would otherwise
|
||||
leave behind. */
|
||||
.execution-tree-node {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@@ -35,6 +38,7 @@
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 0.375rem;
|
||||
background-color: var(--bs-body-bg);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* The execution the user drilled in from — a left accent rule + tinted
|
||||
|
||||
@@ -227,6 +227,63 @@ public class ExecutionTreeTests : BunitContext
|
||||
cut.Find($"[data-test=\"tree-toggle-{root}\"]").GetAttribute("aria-expanded"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoubleClickingNode_RaisesOnNodeActivated_WithExecutionId()
|
||||
{
|
||||
// Double-clicking a node's body raises OnNodeActivated carrying that
|
||||
// node's ExecutionId — the affordance a later task uses to open the
|
||||
// node detail modal.
|
||||
var root = Guid.Parse("aaaaaaaa-4444-4444-4444-444444444444");
|
||||
var child = Guid.Parse("bbbbbbbb-4444-4444-4444-444444444444");
|
||||
var nodes = new List<ExecutionTreeNode>
|
||||
{
|
||||
Node(root, null),
|
||||
Node(child, root),
|
||||
};
|
||||
|
||||
Guid? activated = null;
|
||||
var cut = Render<ExecutionTree>(p => p
|
||||
.Add(c => c.Nodes, nodes)
|
||||
.Add(c => c.ArrivedFromExecutionId, root)
|
||||
.Add(c => c.OnNodeActivated, (Guid id) => activated = id));
|
||||
|
||||
var rootBody = cut.Find($"[data-test=\"tree-node-{root}\"] .execution-tree-body");
|
||||
rootBody.DoubleClick();
|
||||
|
||||
Assert.Equal(root, activated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoubleClickingNestedNode_BubblesOnNodeActivated_ToRoot()
|
||||
{
|
||||
// root → child → grandchild. Double-clicking a deeply nested node's
|
||||
// body invokes the SAME root-supplied callback — the EventCallback is
|
||||
// threaded unchanged down every recursive ExecutionTree instance.
|
||||
var root = Guid.Parse("aaaaaaaa-5555-5555-5555-555555555555");
|
||||
var child = Guid.Parse("bbbbbbbb-5555-5555-5555-555555555555");
|
||||
var grandchild = Guid.Parse("cccccccc-5555-5555-5555-555555555555");
|
||||
var nodes = new List<ExecutionTreeNode>
|
||||
{
|
||||
Node(root, null),
|
||||
Node(child, root),
|
||||
Node(grandchild, child),
|
||||
};
|
||||
|
||||
Guid? activated = null;
|
||||
var cut = Render<ExecutionTree>(p => p
|
||||
.Add(c => c.Nodes, nodes)
|
||||
.Add(c => c.ArrivedFromExecutionId, root)
|
||||
.Add(c => c.OnNodeActivated, (Guid id) => activated = id));
|
||||
|
||||
// Double-click the grandchild (two recursion levels deep).
|
||||
cut.Find($"[data-test=\"tree-node-{grandchild}\"] .execution-tree-body").DoubleClick();
|
||||
Assert.Equal(grandchild, activated);
|
||||
|
||||
// And the child (one level deep) — both reach the root's callback.
|
||||
cut.Find($"[data-test=\"tree-node-{child}\"] .execution-tree-body").DoubleClick();
|
||||
Assert.Equal(child, activated);
|
||||
}
|
||||
|
||||
private static int CountOccurrences(string haystack, string needle)
|
||||
{
|
||||
int count = 0, idx = 0;
|
||||
|
||||
Reference in New Issue
Block a user