using Shouldly; using Xunit; using ZB.MOM.WW.LmxOpcUa.Client.UI.Services; using ZB.MOM.WW.LmxOpcUa.Client.UI.Tests.Fakes; using ZB.MOM.WW.LmxOpcUa.Client.UI.ViewModels; using BrowseResult = ZB.MOM.WW.LmxOpcUa.Client.Shared.Models.BrowseResult; namespace ZB.MOM.WW.LmxOpcUa.Client.UI.Tests; public class BrowseTreeViewModelTests { private readonly FakeOpcUaClientService _service; private readonly SynchronousUiDispatcher _dispatcher; private readonly BrowseTreeViewModel _vm; public BrowseTreeViewModelTests() { _service = new FakeOpcUaClientService { BrowseResults = new[] { new BrowseResult("ns=2;s=Node1", "Node1", "Object", true), new BrowseResult("ns=2;s=Node2", "Node2", "Variable", false) } }; _dispatcher = new SynchronousUiDispatcher(); _vm = new BrowseTreeViewModel(_service, _dispatcher); } [Fact] public async Task LoadRootsAsync_PopulatesRootNodes() { await _vm.LoadRootsAsync(); _vm.RootNodes.Count.ShouldBe(2); _vm.RootNodes[0].DisplayName.ShouldBe("Node1"); _vm.RootNodes[1].DisplayName.ShouldBe("Node2"); } [Fact] public async Task LoadRootsAsync_BrowsesWithNullParent() { await _vm.LoadRootsAsync(); _service.BrowseCallCount.ShouldBe(1); _service.LastBrowseParentNodeId.ShouldBeNull(); } [Fact] public void Clear_RemovesAllRootNodes() { _vm.RootNodes.Add(new TreeNodeViewModel("ns=2;s=X", "X", "Object", false, _service, _dispatcher)); _vm.Clear(); _vm.RootNodes.ShouldBeEmpty(); } [Fact] public async Task LoadRootsAsync_NodeWithChildren_HasPlaceholder() { await _vm.LoadRootsAsync(); var nodeWithChildren = _vm.RootNodes[0]; nodeWithChildren.HasChildren.ShouldBeTrue(); nodeWithChildren.Children.Count.ShouldBe(1); nodeWithChildren.Children[0].IsPlaceholder.ShouldBeTrue(); } [Fact] public async Task LoadRootsAsync_NodeWithoutChildren_HasNoPlaceholder() { await _vm.LoadRootsAsync(); var leafNode = _vm.RootNodes[1]; leafNode.HasChildren.ShouldBeFalse(); leafNode.Children.ShouldBeEmpty(); } [Fact] public async Task TreeNode_FirstExpand_TriggersChildBrowse() { _service.BrowseResults = new[] { new BrowseResult("ns=2;s=Parent", "Parent", "Object", true) }; await _vm.LoadRootsAsync(); // Reset browse results for child browse _service.BrowseResults = new[] { new BrowseResult("ns=2;s=Child1", "Child1", "Variable", false) }; var parent = _vm.RootNodes[0]; var initialBrowseCount = _service.BrowseCallCount; parent.IsExpanded = true; // Allow async operation to complete await Task.Delay(50); _service.BrowseCallCount.ShouldBe(initialBrowseCount + 1); parent.Children.Count.ShouldBe(1); parent.Children[0].DisplayName.ShouldBe("Child1"); } [Fact] public async Task TreeNode_SecondExpand_DoesNotBrowseAgain() { _service.BrowseResults = new[] { new BrowseResult("ns=2;s=Parent", "Parent", "Object", true) }; await _vm.LoadRootsAsync(); _service.BrowseResults = new[] { new BrowseResult("ns=2;s=Child1", "Child1", "Variable", false) }; var parent = _vm.RootNodes[0]; parent.IsExpanded = true; await Task.Delay(50); var browseCountAfterFirst = _service.BrowseCallCount; parent.IsExpanded = false; parent.IsExpanded = true; await Task.Delay(50); _service.BrowseCallCount.ShouldBe(browseCountAfterFirst); } [Fact] public async Task TreeNode_IsLoading_TransitionsDuringBrowse() { _service.BrowseResults = new[] { new BrowseResult("ns=2;s=Parent", "Parent", "Object", true) }; await _vm.LoadRootsAsync(); _service.BrowseResults = Array.Empty(); var parent = _vm.RootNodes[0]; parent.IsExpanded = true; await Task.Delay(50); // After completion, IsLoading should be false parent.IsLoading.ShouldBeFalse(); } }