fix(centralui): surface inherited compositions in the templates tree (followup #9)
The templates tree rendered a derived/composed member (e.g. LeftReactorSide, derived from ReactorSide) as a flat leaf, omitting compositions it inherits from its base (e.g. LeakTest composed onto ReactorSide). BuildCompositionLeavesFor recursed only over a template's OWN composition rows; an inherited composition row lives on the ancestor, and TemplateComposition has no IsInherited placeholder (unlike attributes/alarms/scripts/native-sources), so the child's own Compositions was empty. Same 'derived templates don't surface inherited members' family as followups #1/#2, but for compositions. Deploy/flatten was always correct (TemplateResolver.ResolveAllMembers walks the chain) — display-only. Fix: - BuildCompositionLeavesFor now renders the EFFECTIVE composition set (own + inherited) via EffectiveCompositionsFor, which walks the inheritance chain (leaf->root, child wins on InstanceName), mirroring the resolver. - Inherited slots are flagged (TemplateTreeNode.IsInherited), badged 'inherited' in the label, and their context menu offers only 'Open composed template' (Rename/Delete edit the ancestor's slot, so suppressed on inherited nodes). - The same inherited row can appear under several derived members (LeakTest under both LeftReactorSide and RightReactorSide), so composition nodes use a path-qualified KeyOverride to keep TreeView selection/expansion keys unique; recursion is cycle-guarded. Tests: +1 bUnit (TemplatesPageTests.Renders_InheritedComposition_UnderDerivedComposedMember); CentralUI suite 867 green; full solution builds 0/0. Docs: Component-CentralUI.md (effective composition set in tree); known-issues tracker #9 recorded + resolved. Note: CentralUI change — shows on wonder-app-vd03 only after that host is redeployed.
This commit is contained in:
@@ -129,6 +129,51 @@ public class TemplatesPageTests : BunitContext
|
||||
Assert.Contains("bi-arrow-return-right", cut.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Renders_InheritedComposition_UnderDerivedComposedMember()
|
||||
{
|
||||
// followup #9: LeakTest is composed onto the BASE ReactorSide. LeftReactorSide is
|
||||
// DERIVED from ReactorSide and composed into CvdReactor, so the tree must surface
|
||||
// the INHERITED LeakTest slot under LeftReactorSide — the composition row lives on
|
||||
// ReactorSide (no IsInherited placeholder on the child), so the builder must use
|
||||
// the effective (own + inherited) composition set, mirroring the resolver.
|
||||
var leakTest = new Template("LeakTest") { Id = 50 };
|
||||
var reactorSide = new Template("ReactorSide") { Id = 7 };
|
||||
reactorSide.Compositions.Add(
|
||||
new TemplateComposition("LeakTest") { Id = 100, TemplateId = 7, ComposedTemplateId = 50 });
|
||||
var leftReactorSide = new Template("LeftReactorSide") { Id = 8, ParentTemplateId = 7, IsDerived = true };
|
||||
var cvdReactor = new Template("CvdReactor") { Id = 1 };
|
||||
cvdReactor.Compositions.Add(
|
||||
new TemplateComposition("LeftReactorSide") { Id = 200, TemplateId = 1, ComposedTemplateId = 8 });
|
||||
|
||||
_repo.GetAllTemplatesAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<Template>>(
|
||||
new List<Template> { cvdReactor, reactorSide, leftReactorSide, leakTest }));
|
||||
_repo.GetAllFoldersAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<TemplateFolder>>(new List<TemplateFolder>()));
|
||||
|
||||
var cut = Render<TemplatesPage>();
|
||||
|
||||
// Expand CvdReactor to reveal its composed member LeftReactorSide.
|
||||
cut.FindAll("li[role='treeitem']")
|
||||
.First(li => li.QuerySelector(".tv-label")?.TextContent.Trim() == "CvdReactor")
|
||||
.QuerySelector(".tv-toggle")!.Click();
|
||||
|
||||
// LeftReactorSide must be expandable: before the fix it was a flat leaf with no
|
||||
// toggle because its own Compositions are empty (LeakTest is inherited, not owned).
|
||||
var leftLi = cut.FindAll("li[role='treeitem']")
|
||||
.First(li => li.QuerySelector(".tv-label")?.TextContent.Trim() == "LeftReactorSide");
|
||||
var leftToggle = leftLi.QuerySelector(".tv-toggle");
|
||||
Assert.NotNull(leftToggle);
|
||||
leftToggle!.Click();
|
||||
|
||||
// The inherited LeakTest slot now renders under LeftReactorSide, badged "inherited".
|
||||
var leftLiAfter = cut.FindAll("li[role='treeitem']")
|
||||
.First(li => li.QuerySelector(".tv-label")?.TextContent.Trim() == "LeftReactorSide");
|
||||
Assert.Contains("LeakTest", leftLiAfter.TextContent);
|
||||
Assert.Contains("inherited", leftLiAfter.TextContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SearchBox_IsPresentAndBound_ToTemplateFolderTreeFilter()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user