diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsNode.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsNode.cs index fcb16e5d..80d87bbe 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsNode.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsNode.cs @@ -40,7 +40,12 @@ public sealed class UnsNode /// Badge count. For equipment this is tag + virtual-tag count; for container nodes it is the direct child count. public int ChildCount { get; set; } - /// True when this node has children that are loaded lazily (equipment with > 0). + /// + /// Gets a value indicating whether this node has children that load lazily (equipment with tags/virtual tags). + /// Structural nodes (Cluster/Area/Line) always carry false even when they have eager children, so a + /// renderer must decide whether to show an expand chevron with Children.Count > 0 || HasLazyChildren, + /// not on HasLazyChildren alone. + /// public bool HasLazyChildren { get; init; } /// Eagerly-materialised children. Empty for lazy-loaded equipment until expanded. @@ -162,7 +167,7 @@ public static class UnsTreeAssembly ? cluster.Name : $"{cluster.Site} ({cluster.Name})", ClusterId = cluster.ClusterId, - EntityId = cluster.ClusterId, + EntityId = cluster.ClusterId, // Cluster's own logical id IS its ClusterId — EntityId mirrors it for uniform navigation. HasLazyChildren = false, }; node.Children.AddRange(areaNodes); diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/UnsTreeAssemblyTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/UnsTreeAssemblyTests.cs index 6df18be8..05d70e02 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/UnsTreeAssemblyTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/UnsTreeAssemblyTests.cs @@ -43,6 +43,7 @@ public sealed class UnsTreeAssemblyTests var cluster = tree.Single().Children.Single(); cluster.Key.ShouldBe("clu:c1"); + cluster.EntityId.ShouldBe("c1"); var area = cluster.Children.Single(); area.Kind.ShouldBe(UnsNodeKind.Area); @@ -120,9 +121,12 @@ public sealed class UnsTreeAssemblyTests public void Build_orders_deterministically() { // Two enterprises out of order; clusters/areas/lines/equipment scrambled. + // Two clusters with identical Name but different ClusterId to verify tie-break ordering. var clusters = new[] { new ClusterRow("cZ", "zeta", "SiteZ", "Zeta"), + new ClusterRow("c9", "zeta", "SiteZ", "Zeta"), + new ClusterRow("c8", "zeta", "SiteZ", "Zeta"), new ClusterRow("c2", "alpha", "Site2", "Bravo"), new ClusterRow("c1", "alpha", "Site1", "Alpha"), }; @@ -162,5 +166,9 @@ public sealed class UnsTreeAssemblyTests // Equipment under l1 ordered by Name then EquipmentId: "EquipA"(e1) < "EquipB"(e2). var l1 = a1.Children.Single(l => l.Key == "line:l1"); l1.Children.Select(eq => eq.Key).ShouldBe(new[] { "eq:e1", "eq:e2" }); + + // Clusters with the same Name tie-break by ClusterId ordinal: "c8" < "c9" < "cZ". + var zetaEnt = tree.Single(e => e.Key == "ent:zeta"); + zetaEnt.Children.Select(c => c.Key).ShouldBe(new[] { "clu:c8", "clu:c9", "clu:cZ" }); } }