Skip to content

Commit 187d488

Browse files
authored
Fixes #2429 Show all reaction parameters in 'Possible referenced objects' (#2434)
The reference tree lazily enumerates a node's children the first time its parent is expanded. When a parameter was added to a reaction whose children had not been enumerated yet, the AddedEvent handler rendered that single node, leaving the reaction with one child. The tree then treated the reaction as fully enumerated and skipped loading the rest, so only the last added parameter was shown. The handler now propagates an addition only to parent nodes whose children have already been enumerated (new HierarchicalStructureNode.ChildrenLoaded), letting lazy loading list them all on expand otherwise. Checking whether the children were loaded rather than whether any exist also renders the first child added to an already-enumerated, empty parent.
1 parent ea261bd commit 187d488

3 files changed

Lines changed: 144 additions & 5 deletions

File tree

src/MoBi.Presentation/Nodes/HierarchicalStructureNode.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ public HierarchicalStructureNode(ObjectBaseDTO objectBaseDTO) : base(objectBaseD
1717
_childrenLoaded = false;
1818
}
1919

20+
/// <summary>
21+
/// Returns <c>true</c> once the children have been enumerated (lazily loaded) into this node, even if that
22+
/// enumeration produced no children. Unlike <see cref="AbstractNode.HasChildren" /> this distinguishes an
23+
/// enumerated-but-empty node from one whose children were never loaded, and it does not trigger the loading.
24+
/// </summary>
25+
public bool ChildrenLoaded => _childrenLoaded;
26+
2027
public override IEnumerable<ITreeNode> Children
2128
{
2229
get

src/MoBi.Presentation/Presenter/SelectReferencePresenterBase.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using MoBi.Core.Helper;
1010
using MoBi.Presentation.DTO;
1111
using MoBi.Presentation.Mappers;
12+
using MoBi.Presentation.Nodes;
1213
using MoBi.Presentation.Settings;
1314
using MoBi.Presentation.Views;
1415
using OSPSuite.Assets;
@@ -477,16 +478,17 @@ public void Handle(RemovedEvent eventToHandle)
477478

478479
public void Handle(AddedEvent eventToHandle)
479480
{
480-
if (_view.Shows(eventToHandle.Parent))
481-
{
482-
var parentNodes = _view.GetNodes(eventToHandle.Parent);
483-
parentNodes.Each(parentNode =>
481+
// Only add nodes when the parent has had children loaded already.
482+
// If it has not been expanded yet, then do not add nodes.
483+
_view.GetNodes(eventToHandle.Parent)
484+
.OfType<HierarchicalStructureNode>()
485+
.Where(parentNode => parentNode.ChildrenLoaded)
486+
.Each(parentNode =>
484487
{
485488
var node = _referenceMapper.MapFrom(eventToHandle.AddedObject);
486489
parentNode.AddChild(node);
487490
_view.AddNode(node);
488491
});
489-
}
490492
}
491493
}
492494
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using FakeItEasy;
4+
using MoBi.Core.Domain.Model;
5+
using MoBi.Core.Events;
6+
using MoBi.Presentation.Mappers;
7+
using MoBi.Presentation.Presenter;
8+
using MoBi.Presentation.Settings;
9+
using MoBi.Presentation.Views;
10+
using OSPSuite.BDDHelper;
11+
using OSPSuite.BDDHelper.Extensions;
12+
using OSPSuite.Core.Domain;
13+
using OSPSuite.Core.Domain.Builder;
14+
using OSPSuite.Presentation.Nodes;
15+
using IBuildingBlockRepository = MoBi.Core.Domain.Repository.IBuildingBlockRepository;
16+
17+
namespace MoBi.Presentation
18+
{
19+
public abstract class concern_for_SelectReferenceAtReactionPresenter : ContextSpecification<ISelectReferenceAtReactionPresenter>
20+
{
21+
protected ISelectReferenceView _view;
22+
protected IMoBiContext _context;
23+
protected IObjectBaseToObjectBaseDTOMapper _objectBaseDTOMapper;
24+
protected IObjectBaseDTOToReferenceNodeMapper _referenceMapper;
25+
protected IBuildingBlockRepository _buildingBlockRepository;
26+
protected ReactionBuilder _reaction;
27+
protected ITreeNode _reactionNode;
28+
protected List<ITreeNode> _renderedNodes;
29+
30+
protected override void Context()
31+
{
32+
_view = A.Fake<ISelectReferenceView>();
33+
_context = A.Fake<IMoBiContext>();
34+
_objectBaseDTOMapper = new ObjectBaseToObjectBaseDTOMapper();
35+
_referenceMapper = new ObjectBaseDTOToReferenceNodeMapper(_objectBaseDTOMapper);
36+
_buildingBlockRepository = A.Fake<IBuildingBlockRepository>();
37+
38+
_reaction = new ReactionBuilder().WithName("newReaction");
39+
_reaction.Id = "reactionId";
40+
41+
var repo = A.Fake<IWithIdRepository>();
42+
A.CallTo(() => _context.ObjectRepository).Returns(repo);
43+
A.CallTo(() => repo.ContainsObjectWithId(_reaction.Id)).Returns(true);
44+
A.CallTo(() => _context.Get<IObjectBase>(_reaction.Id)).Returns(_reaction);
45+
46+
sut = new SelectReferenceAtReactionPresenter(_view, _objectBaseDTOMapper, _context, A.Fake<IUserSettings>(),
47+
A.Fake<IObjectBaseToDummyMoleculeDTOMapper>(), A.Fake<IParameterToDummyParameterDTOMapper>(),
48+
_referenceMapper, A.Fake<IObjectPathCreatorAtReaction>(), _buildingBlockRepository);
49+
50+
_reactionNode = _referenceMapper.MapFrom(_reaction);
51+
A.CallTo(() => _view.Shows(_reaction)).Returns(true);
52+
A.CallTo(() => _view.GetNodes(_reaction)).Returns(new List<ITreeNode> { _reactionNode });
53+
54+
_renderedNodes = new List<ITreeNode>();
55+
A.CallTo(() => _view.AddNode(A<ITreeNode>._)).Invokes(x => _renderedNodes.Add(x.GetArgument<ITreeNode>(0)));
56+
}
57+
58+
protected void AddParameterAndRaiseEvent(string name = "lastParam", string id = "pNew")
59+
{
60+
var newParameter = new Parameter().WithName(name).WithId(id);
61+
_reaction.Add(newParameter);
62+
sut.Handle(new AddedEvent<IParameter>(newParameter, _reaction));
63+
}
64+
65+
protected void EnumerateReactionNodeChildren() => _reactionNode.Children.ToList();
66+
}
67+
68+
public class When_a_parameter_is_added_to_a_reaction_whose_children_are_not_yet_enumerated : concern_for_SelectReferenceAtReactionPresenter
69+
{
70+
protected override void Context()
71+
{
72+
base.Context();
73+
_reaction.Add(new Parameter().WithName("k_on").WithId("p1"));
74+
_reaction.Add(new Parameter().WithName("k_off").WithId("p2"));
75+
}
76+
77+
protected override void Because()
78+
{
79+
AddParameterAndRaiseEvent();
80+
}
81+
82+
[Observation]
83+
public void should_not_add_the_node_so_the_tree_still_enumerates_all_parameters_when_the_reaction_is_expanded()
84+
{
85+
_renderedNodes.ShouldBeEmpty();
86+
}
87+
}
88+
89+
public class When_a_parameter_is_added_to_a_reaction_whose_children_are_already_enumerated : concern_for_SelectReferenceAtReactionPresenter
90+
{
91+
protected override void Context()
92+
{
93+
base.Context();
94+
_reaction.Add(new Parameter().WithName("k_on").WithId("p1"));
95+
_reaction.Add(new Parameter().WithName("k_off").WithId("p2"));
96+
EnumerateReactionNodeChildren();
97+
}
98+
99+
protected override void Because()
100+
{
101+
AddParameterAndRaiseEvent();
102+
}
103+
104+
[Observation]
105+
public void should_add_the_newly_created_parameter_node()
106+
{
107+
_renderedNodes.Select(x => x.Text).ShouldContain("lastParam");
108+
}
109+
}
110+
111+
public class When_the_first_parameter_is_added_to_an_already_enumerated_empty_reaction : concern_for_SelectReferenceAtReactionPresenter
112+
{
113+
protected override void Context()
114+
{
115+
base.Context();
116+
EnumerateReactionNodeChildren();
117+
}
118+
119+
protected override void Because()
120+
{
121+
AddParameterAndRaiseEvent("firstParam", "p1");
122+
}
123+
124+
[Observation]
125+
public void should_add_the_first_parameter_node()
126+
{
127+
_renderedNodes.Select(x => x.Text).ShouldContain("firstParam");
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)