TreeViewTests.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using Terminal.Gui;
  7. using Xunit;
  8. namespace UnitTests {
  9. public class TreeViewTests
  10. {
  11. #region Test Setup Methods
  12. class Factory
  13. {
  14. public Car[] Cars {get;set;}
  15. };
  16. class Car {
  17. };
  18. private TreeView CreateTree()
  19. {
  20. return CreateTree(out _, out _, out _);
  21. }
  22. private TreeView CreateTree(out Factory factory1, out Car car1, out Car car2)
  23. {
  24. car1 = new Car();
  25. car2 = new Car();
  26. factory1 = new Factory()
  27. {
  28. Cars = new []{car1 ,car2}
  29. };
  30. var tree = new TreeView(new DelegateTreeBuilder((s)=> s is Factory f ? f.Cars: null));
  31. tree.AddObject(factory1);
  32. return tree;
  33. }
  34. #endregion
  35. /// <summary>
  36. /// Tests that <see cref="TreeView.Expand(object)"/> and <see cref="TreeView.IsExpanded(object)"/> are consistent
  37. /// </summary>
  38. [Fact]
  39. public void IsExpanded_TrueAfterExpand()
  40. {
  41. var tree = CreateTree(out Factory f, out _, out _);
  42. Assert.False(tree.IsExpanded(f));
  43. tree.Expand(f);
  44. Assert.True(tree.IsExpanded(f));
  45. tree.Collapse(f);
  46. Assert.False(tree.IsExpanded(f));
  47. }
  48. /// <summary>
  49. /// Tests that <see cref="TreeView.IsExpanded(object)"/> and <see cref="TreeView.Expand(object)"/> behaves correctly when an object cannot be expanded (because it has no children)
  50. /// </summary>
  51. [Fact]
  52. public void IsExpanded_FalseIfCannotExpand()
  53. {
  54. var tree = CreateTree(out Factory f, out Car c, out _);
  55. // expose the car by expanding the factory
  56. tree.Expand(f);
  57. // car is not expanded
  58. Assert.False(tree.IsExpanded(c));
  59. //try to expand the car (should have no effect because cars have no children)
  60. tree.Expand(c);
  61. Assert.False(tree.IsExpanded(c));
  62. // should also be ignored
  63. tree.Collapse(c);
  64. Assert.False(tree.IsExpanded(c));
  65. }
  66. /// <summary>
  67. /// Tests illegal ranges for <see cref="TreeView.ScrollOffset"/>
  68. /// </summary>
  69. [Fact]
  70. public void ScrollOffset_CannotBeNegative()
  71. {
  72. var tree = CreateTree();
  73. Assert.Equal(0,tree.ScrollOffset);
  74. tree.ScrollOffset = -100;
  75. Assert.Equal(0,tree.ScrollOffset);
  76. tree.ScrollOffset = 10;
  77. Assert.Equal(10,tree.ScrollOffset);
  78. }
  79. /// <summary>
  80. /// Tests <see cref="TreeView.GetScrollOffsetOf(object)"/> for objects that are as yet undiscovered by the tree
  81. /// </summary>
  82. [Fact]
  83. public void GetScrollOffsetOf_MinusOneForUnRevealed()
  84. {
  85. var tree = CreateTree(out Factory f, out Car c1, out Car c2);
  86. // to start with the tree is collapsed and only knows about the root object
  87. Assert.Equal(0,tree.GetScrollOffsetOf(f));
  88. Assert.Equal(-1,tree.GetScrollOffsetOf(c1));
  89. Assert.Equal(-1,tree.GetScrollOffsetOf(c2));
  90. // reveal it by expanding the root object
  91. tree.Expand(f);
  92. // tree now knows about children
  93. Assert.Equal(0,tree.GetScrollOffsetOf(f));
  94. Assert.Equal(1,tree.GetScrollOffsetOf(c1));
  95. Assert.Equal(2,tree.GetScrollOffsetOf(c2));
  96. // after collapsing the root node again
  97. tree.Collapse(f);
  98. // tree no longer knows about the locations of these objects
  99. Assert.Equal(0,tree.GetScrollOffsetOf(f));
  100. Assert.Equal(-1,tree.GetScrollOffsetOf(c1));
  101. Assert.Equal(-1,tree.GetScrollOffsetOf(c2));
  102. }
  103. /// <summary>
  104. /// Simulates behind the scenes changes to an object (which children it has) and how to sync that into the tree using <see cref="TreeView.RefreshObject(object, bool)"/>
  105. /// </summary>
  106. [Fact]
  107. public void RefreshObject_ChildRemoved()
  108. {
  109. var tree = CreateTree(out Factory f, out Car c1, out Car c2);
  110. //reveal it by expanding the root object
  111. tree.Expand(f);
  112. Assert.Equal(0,tree.GetScrollOffsetOf(f));
  113. Assert.Equal(1,tree.GetScrollOffsetOf(c1));
  114. Assert.Equal(2,tree.GetScrollOffsetOf(c2));
  115. // Factory now no longer makes Car c1 (only c2)
  116. f.Cars = new Car[]{c2};
  117. // Tree does not know this yet
  118. Assert.Equal(0,tree.GetScrollOffsetOf(f));
  119. Assert.Equal(1,tree.GetScrollOffsetOf(c1));
  120. Assert.Equal(2,tree.GetScrollOffsetOf(c2));
  121. // If the user has selected the node c1
  122. tree.SelectedObject = c1;
  123. // When we refresh the tree
  124. tree.RefreshObject(f);
  125. // Now tree knows that factory has only one child node c2
  126. Assert.Equal(0,tree.GetScrollOffsetOf(f));
  127. Assert.Equal(-1,tree.GetScrollOffsetOf(c1));
  128. Assert.Equal(1,tree.GetScrollOffsetOf(c2));
  129. // The old selection was c1 which is now gone so selection should default to the parent of that branch (the factory)
  130. Assert.Equal(f,tree.SelectedObject);
  131. }
  132. /// <summary>
  133. /// Tests that <see cref="TreeView.GetParent(object)"/> returns the parent object for
  134. /// Cars (Factories). Note that the method only works once the parent branch (Factory)
  135. /// is expanded to expose the child (Car)
  136. /// </summary>
  137. [Fact]
  138. public void GetParent_ReturnsParentOnlyWhenExpanded()
  139. {
  140. var tree = CreateTree(out Factory f, out Car c1, out Car c2);
  141. Assert.Null(tree.GetParent(f));
  142. Assert.Null(tree.GetParent(c1));
  143. Assert.Null(tree.GetParent(c2));
  144. // now when we expand the factory we discover the cars
  145. tree.Expand(f);
  146. Assert.Null(tree.GetParent(f));
  147. Assert.Equal(f,tree.GetParent(c1));
  148. Assert.Equal(f,tree.GetParent(c2));
  149. tree.Collapse(f);
  150. Assert.Null(tree.GetParent(f));
  151. Assert.Null(tree.GetParent(c1));
  152. Assert.Null(tree.GetParent(c2));
  153. }
  154. /// <summary>
  155. /// Tests how the tree adapts to changes in the ChildrenGetter delegate during runtime
  156. /// when some branches are expanded and the new delegate returns children for a node that
  157. /// previously didn't have any children
  158. /// </summary>
  159. [Fact]
  160. public void RefreshObject_AfterChangingChildrenGetterDuringRuntime()
  161. {
  162. var tree = CreateTree(out Factory f, out Car c1, out Car c2);
  163. string wheel = "Shiny Wheel";
  164. // Expand the Factory
  165. tree.Expand(f);
  166. // c1 cannot have children
  167. Assert.Equal(f,tree.GetParent(c1));
  168. // expanding it does nothing
  169. tree.Expand(c1);
  170. Assert.False(tree.IsExpanded(c1));
  171. // change the children getter so that now cars can have wheels
  172. tree.TreeBuilder = new DelegateTreeBuilder((o)=>
  173. // factories have cars
  174. o is Factory ? new object[]{c1,c2}
  175. // cars have wheels
  176. : new object[]{wheel });
  177. // still cannot expand
  178. tree.Expand(c1);
  179. Assert.False(tree.IsExpanded(c1));
  180. tree.RefreshObject(c1);
  181. tree.Expand(c1);
  182. Assert.True(tree.IsExpanded(c1));
  183. Assert.Equal(wheel,tree.GetChildren(c1).FirstOrDefault());
  184. }
  185. /// <summary>
  186. /// Same as <see cref="RefreshObject_AfterChangingChildrenGetterDuringRuntime"/> but
  187. /// uses <see cref="TreeView.RebuildTree()"/> instead of <see cref="TreeView.RefreshObject(object, bool)"/>
  188. /// </summary>
  189. [Fact]
  190. public void RebuildTree_AfterChangingChildrenGetterDuringRuntime()
  191. {
  192. var tree = CreateTree(out Factory f, out Car c1, out Car c2);
  193. string wheel = "Shiny Wheel";
  194. // Expand the Factory
  195. tree.Expand(f);
  196. // c1 cannot have children
  197. Assert.Equal(f,tree.GetParent(c1));
  198. // expanding it does nothing
  199. tree.Expand(c1);
  200. Assert.False(tree.IsExpanded(c1));
  201. // change the children getter so that now cars can have wheels
  202. tree.TreeBuilder = new DelegateTreeBuilder((o)=>
  203. // factories have cars
  204. o is Factory ? new object[]{c1,c2}
  205. // cars have wheels
  206. : new object[]{wheel });
  207. // still cannot expand
  208. tree.Expand(c1);
  209. Assert.False(tree.IsExpanded(c1));
  210. // Rebuild the tree
  211. tree.RebuildTree();
  212. // Rebuild should not have collapsed any branches or done anything wierd
  213. Assert.True(tree.IsExpanded(f));
  214. tree.Expand(c1);
  215. Assert.True(tree.IsExpanded(c1));
  216. Assert.Equal(wheel,tree.GetChildren(c1).FirstOrDefault());
  217. }
  218. /// <summary>
  219. /// Tests that <see cref="TreeView.GetChildren(object)"/> returns the child objects for
  220. /// the factory. Note that the method only works once the parent branch (Factory)
  221. /// is expanded to expose the child (Car)
  222. /// </summary>
  223. [Fact]
  224. public void GetChildren_ReturnsChildrenOnlyWhenExpanded()
  225. {
  226. var tree = CreateTree(out Factory f, out Car c1, out Car c2);
  227. Assert.Empty(tree.GetChildren(f));
  228. Assert.Empty(tree.GetChildren(c1));
  229. Assert.Empty(tree.GetChildren(c2));
  230. // now when we expand the factory we discover the cars
  231. tree.Expand(f);
  232. Assert.Contains(c1,tree.GetChildren(f));
  233. Assert.Contains(c2,tree.GetChildren(f));
  234. Assert.Empty(tree.GetChildren(c1));
  235. Assert.Empty(tree.GetChildren(c2));
  236. tree.Collapse(f);
  237. Assert.Empty(tree.GetChildren(f));
  238. Assert.Empty(tree.GetChildren(c1));
  239. Assert.Empty(tree.GetChildren(c2));
  240. }
  241. [Fact]
  242. public void TreeNode_WorksWithoutDelegate()
  243. {
  244. var tree = new TreeView();
  245. var root = new TreeNode("Root");
  246. root.Children.Add(new TreeNode("Leaf1"));
  247. root.Children.Add(new TreeNode("Leaf2"));
  248. tree.AddObject(root);
  249. tree.Expand(root);
  250. Assert.Equal(2,tree.GetChildren(root).Count());
  251. }
  252. /// <summary>
  253. /// Simulates behind the scenes changes to an object (which children it has) and how to sync that into the tree using <see cref="TreeView.RefreshObject(object, bool)"/>
  254. /// </summary>
  255. [Fact]
  256. public void RefreshObject_EqualityTest()
  257. {
  258. var obj1 = new EqualityTestObject(){Name="Bob",Age=1 };
  259. var obj2 = new EqualityTestObject(){Name="Bob",Age=2 };;
  260. string root = "root";
  261. var tree = new TreeView();
  262. tree.TreeBuilder = new DelegateTreeBuilder((s)=> ReferenceEquals(s , root) ? new object[]{obj1 } : null);
  263. tree.AddObject(root);
  264. // Tree is not expanded so the root has no children yet
  265. Assert.Empty(tree.GetChildren(root));
  266. tree.Expand(root);
  267. // now that the tree is expanded we should get our child returned
  268. Assert.Equal(1,tree.GetChildren(root).Count(child=>ReferenceEquals(obj1,child)));
  269. // change the getter to return an Equal object (but not the same reference - obj2)
  270. tree.TreeBuilder = new DelegateTreeBuilder((s)=> ReferenceEquals(s , root) ? new object[]{obj2 } : null);
  271. // tree has cached the knowledge of what children the root has so won't know about the change (we still get obj1)
  272. Assert.Equal(1,tree.GetChildren(root).Count(child=>ReferenceEquals(obj1,child)));
  273. // now that we refresh the root we should get the new child reference (obj2)
  274. tree.RefreshObject(root);
  275. Assert.Equal(1,tree.GetChildren(root).Count(child=>ReferenceEquals(obj2,child)));
  276. }
  277. /// <summary>
  278. /// Test object which considers for equality only <see cref="Name"/>
  279. /// </summary>
  280. private class EqualityTestObject
  281. {
  282. public string Name { get;set;}
  283. public int Age { get;set;}
  284. public override int GetHashCode ()
  285. {
  286. return Name?.GetHashCode()??base.GetHashCode ();
  287. }
  288. public override bool Equals (object obj)
  289. {
  290. return obj is EqualityTestObject eto && Equals(Name, eto.Name);
  291. }
  292. }
  293. }
  294. }