2
0

TreeViewTests.cs 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using System.Text;
  4. using Xunit;
  5. using Xunit.Abstractions;
  6. namespace Terminal.Gui.ViewsTests;
  7. public class TreeViewTests {
  8. readonly ITestOutputHelper _output;
  9. public TreeViewTests (ITestOutputHelper output) => _output = output;
  10. /// <summary>
  11. /// Tests that <see cref="TreeView.Expand(object)"/> and <see cref="TreeView.IsExpanded(object)"/> are consistent
  12. /// </summary>
  13. [Fact]
  14. public void IsExpanded_TrueAfterExpand ()
  15. {
  16. var tree = CreateTree (out var f, out _, out _);
  17. Assert.False (tree.IsExpanded (f));
  18. tree.Expand (f);
  19. Assert.True (tree.IsExpanded (f));
  20. tree.Collapse (f);
  21. Assert.False (tree.IsExpanded (f));
  22. }
  23. [Fact]
  24. public void EmptyTreeView_ContentSizes ()
  25. {
  26. var emptyTree = new TreeView ();
  27. Assert.Equal (0, emptyTree.ContentHeight);
  28. Assert.Equal (0, emptyTree.GetContentWidth (true));
  29. Assert.Equal (0, emptyTree.GetContentWidth (false));
  30. }
  31. [Fact]
  32. public void EmptyTreeViewGeneric_ContentSizes ()
  33. {
  34. var emptyTree = new TreeView<string> ();
  35. Assert.Equal (0, emptyTree.ContentHeight);
  36. Assert.Equal (0, emptyTree.GetContentWidth (true));
  37. Assert.Equal (0, emptyTree.GetContentWidth (false));
  38. }
  39. /// <summary>
  40. /// Tests that <see cref="TreeView.Expand(object)"/> results in a correct content height
  41. /// </summary>
  42. [Fact]
  43. public void ContentHeight_BiggerAfterExpand ()
  44. {
  45. var tree = CreateTree (out var f, out _, out _);
  46. Assert.Equal (1, tree.ContentHeight);
  47. tree.Expand (f);
  48. Assert.Equal (3, tree.ContentHeight);
  49. tree.Collapse (f);
  50. Assert.Equal (1, tree.ContentHeight);
  51. }
  52. [Fact]
  53. public void ContentWidth_BiggerAfterExpand ()
  54. {
  55. var tree = CreateTree (out var f, out var car1, out _);
  56. tree.BeginInit ();
  57. tree.EndInit ();
  58. tree.Bounds = new Rect (0, 0, 10, 10);
  59. InitFakeDriver ();
  60. //-+Factory
  61. Assert.Equal (9, tree.GetContentWidth (true));
  62. car1.Name = "123456789";
  63. tree.Expand (f);
  64. //..├-123456789
  65. Assert.Equal (13, tree.GetContentWidth (true));
  66. tree.Collapse (f);
  67. //-+Factory
  68. Assert.Equal (9, tree.GetContentWidth (true));
  69. Application.Shutdown ();
  70. }
  71. [Fact]
  72. public void ContentWidth_VisibleVsAll ()
  73. {
  74. var tree = CreateTree (out var f, out var car1, out var car2);
  75. tree.BeginInit ();
  76. tree.EndInit ();
  77. // control only allows 1 row to be viewed at once
  78. tree.Bounds = new Rect (0, 0, 20, 1);
  79. InitFakeDriver ();
  80. //-+Factory
  81. Assert.Equal (9, tree.GetContentWidth (true));
  82. Assert.Equal (9, tree.GetContentWidth (false));
  83. car1.Name = "123456789";
  84. car2.Name = "12345678";
  85. tree.Expand (f);
  86. // Although expanded the bigger (longer) child node is not in the rendered area of the control
  87. Assert.Equal (9, tree.GetContentWidth (true));
  88. Assert.Equal (13, tree.GetContentWidth (false)); // If you ask for the global max width it includes the longer child
  89. // Now that we have scrolled down 1 row we should see the big child
  90. tree.ScrollOffsetVertical = 1;
  91. Assert.Equal (13, tree.GetContentWidth (true));
  92. Assert.Equal (13, tree.GetContentWidth (false));
  93. // Scroll down so only car2 is visible
  94. tree.ScrollOffsetVertical = 2;
  95. Assert.Equal (12, tree.GetContentWidth (true));
  96. Assert.Equal (13, tree.GetContentWidth (false));
  97. // Scroll way down (off bottom of control even)
  98. tree.ScrollOffsetVertical = 5;
  99. Assert.Equal (0, tree.GetContentWidth (true));
  100. Assert.Equal (13, tree.GetContentWidth (false));
  101. Application.Shutdown ();
  102. }
  103. /// <summary>
  104. /// Tests that <see cref="TreeView.IsExpanded(object)"/> and <see cref="TreeView.Expand(object)"/> behaves correctly when
  105. /// an object cannot be expanded (because it has no children)
  106. /// </summary>
  107. [Fact]
  108. public void IsExpanded_FalseIfCannotExpand ()
  109. {
  110. var tree = CreateTree (out var f, out var c, out _);
  111. // expose the car by expanding the factory
  112. tree.Expand (f);
  113. // car is not expanded
  114. Assert.False (tree.IsExpanded (c));
  115. //try to expand the car (should have no effect because cars have no children)
  116. tree.Expand (c);
  117. Assert.False (tree.IsExpanded (c));
  118. // should also be ignored
  119. tree.Collapse (c);
  120. Assert.False (tree.IsExpanded (c));
  121. Application.Shutdown ();
  122. }
  123. /// <summary>
  124. /// Tests illegal ranges for <see cref="TreeView.ScrollOffset"/>
  125. /// </summary>
  126. [Fact]
  127. public void ScrollOffset_CannotBeNegative ()
  128. {
  129. var tree = CreateTree ();
  130. Assert.Equal (0, tree.ScrollOffsetVertical);
  131. tree.ScrollOffsetVertical = -100;
  132. Assert.Equal (0, tree.ScrollOffsetVertical);
  133. tree.ScrollOffsetVertical = 10;
  134. Assert.Equal (10, tree.ScrollOffsetVertical);
  135. }
  136. /// <summary>
  137. /// Tests <see cref="TreeView.GetScrollOffsetOf(object)"/> for objects that are as yet undiscovered by the tree
  138. /// </summary>
  139. [Fact]
  140. public void GetScrollOffsetOf_MinusOneForUnRevealed ()
  141. {
  142. var tree = CreateTree (out var f, out var c1, out var c2);
  143. // to start with the tree is collapsed and only knows about the root object
  144. Assert.Equal (0, tree.GetScrollOffsetOf (f));
  145. Assert.Equal (-1, tree.GetScrollOffsetOf (c1));
  146. Assert.Equal (-1, tree.GetScrollOffsetOf (c2));
  147. // reveal it by expanding the root object
  148. tree.Expand (f);
  149. // tree now knows about children
  150. Assert.Equal (0, tree.GetScrollOffsetOf (f));
  151. Assert.Equal (1, tree.GetScrollOffsetOf (c1));
  152. Assert.Equal (2, tree.GetScrollOffsetOf (c2));
  153. // after collapsing the root node again
  154. tree.Collapse (f);
  155. // tree no longer knows about the locations of these objects
  156. Assert.Equal (0, tree.GetScrollOffsetOf (f));
  157. Assert.Equal (-1, tree.GetScrollOffsetOf (c1));
  158. Assert.Equal (-1, tree.GetScrollOffsetOf (c2));
  159. }
  160. /// <summary>
  161. /// Simulates behind the scenes changes to an object (which children it has) and how to sync that into the tree using
  162. /// <see cref="TreeView.RefreshObject(object, bool)"/>
  163. /// </summary>
  164. [Fact]
  165. public void RefreshObject_ChildRemoved ()
  166. {
  167. var tree = CreateTree (out var f, out var c1, out var c2);
  168. //reveal it by expanding the root object
  169. tree.Expand (f);
  170. Assert.Equal (0, tree.GetScrollOffsetOf (f));
  171. Assert.Equal (1, tree.GetScrollOffsetOf (c1));
  172. Assert.Equal (2, tree.GetScrollOffsetOf (c2));
  173. // Factory now no longer makes Car c1 (only c2)
  174. f.Cars = new [] { c2 };
  175. // Tree does not know this yet
  176. Assert.Equal (0, tree.GetScrollOffsetOf (f));
  177. Assert.Equal (1, tree.GetScrollOffsetOf (c1));
  178. Assert.Equal (2, tree.GetScrollOffsetOf (c2));
  179. // If the user has selected the node c1
  180. tree.SelectedObject = c1;
  181. // When we refresh the tree
  182. tree.RefreshObject (f);
  183. // Now tree knows that factory has only one child node c2
  184. Assert.Equal (0, tree.GetScrollOffsetOf (f));
  185. Assert.Equal (-1, tree.GetScrollOffsetOf (c1));
  186. Assert.Equal (1, tree.GetScrollOffsetOf (c2));
  187. // The old selection was c1 which is now gone so selection should default to the parent of that branch (the factory)
  188. Assert.Equal (f, tree.SelectedObject);
  189. }
  190. /// <summary>
  191. /// Tests that <see cref="TreeView.GetParent(object)"/> returns the parent object for
  192. /// Cars (Factories). Note that the method only works once the parent branch (Factory)
  193. /// is expanded to expose the child (Car)
  194. /// </summary>
  195. [Fact]
  196. public void GetParent_ReturnsParentOnlyWhenExpanded ()
  197. {
  198. var tree = CreateTree (out var f, out var c1, out var c2);
  199. Assert.Null (tree.GetParent (f));
  200. Assert.Null (tree.GetParent (c1));
  201. Assert.Null (tree.GetParent (c2));
  202. // now when we expand the factory we discover the cars
  203. tree.Expand (f);
  204. Assert.Null (tree.GetParent (f));
  205. Assert.Equal (f, tree.GetParent (c1));
  206. Assert.Equal (f, tree.GetParent (c2));
  207. tree.Collapse (f);
  208. Assert.Null (tree.GetParent (f));
  209. Assert.Null (tree.GetParent (c1));
  210. Assert.Null (tree.GetParent (c2));
  211. }
  212. /// <summary>
  213. /// Tests how the tree adapts to changes in the ChildrenGetter delegate during runtime
  214. /// when some branches are expanded and the new delegate returns children for a node that
  215. /// previously didn't have any children
  216. /// </summary>
  217. [Fact]
  218. public void RefreshObject_AfterChangingChildrenGetterDuringRuntime ()
  219. {
  220. var tree = CreateTree (out var f, out var c1, out var c2);
  221. var wheel = "Shiny Wheel";
  222. // Expand the Factory
  223. tree.Expand (f);
  224. // c1 cannot have children
  225. Assert.Equal (f, tree.GetParent (c1));
  226. // expanding it does nothing
  227. tree.Expand (c1);
  228. Assert.False (tree.IsExpanded (c1));
  229. // change the children getter so that now cars can have wheels
  230. tree.TreeBuilder = new DelegateTreeBuilder<object> (o =>
  231. // factories have cars
  232. o is Factory ? new object [] { c1, c2 }
  233. // cars have wheels
  234. : new object [] { wheel });
  235. // still cannot expand
  236. tree.Expand (c1);
  237. Assert.False (tree.IsExpanded (c1));
  238. tree.RefreshObject (c1);
  239. tree.Expand (c1);
  240. Assert.True (tree.IsExpanded (c1));
  241. Assert.Equal (wheel, tree.GetChildren (c1).FirstOrDefault ());
  242. }
  243. /// <summary>
  244. /// Same as <see cref="RefreshObject_AfterChangingChildrenGetterDuringRuntime"/> but
  245. /// uses <see cref="TreeView.RebuildTree()"/> instead of <see cref="TreeView.RefreshObject(object, bool)"/>
  246. /// </summary>
  247. [Fact]
  248. public void RebuildTree_AfterChangingChildrenGetterDuringRuntime ()
  249. {
  250. var tree = CreateTree (out var f, out var c1, out var c2);
  251. var wheel = "Shiny Wheel";
  252. // Expand the Factory
  253. tree.Expand (f);
  254. // c1 cannot have children
  255. Assert.Equal (f, tree.GetParent (c1));
  256. // expanding it does nothing
  257. tree.Expand (c1);
  258. Assert.False (tree.IsExpanded (c1));
  259. // change the children getter so that now cars can have wheels
  260. tree.TreeBuilder = new DelegateTreeBuilder<object> (o =>
  261. // factories have cars
  262. o is Factory ? new object [] { c1, c2 }
  263. // cars have wheels
  264. : new object [] { wheel });
  265. // still cannot expand
  266. tree.Expand (c1);
  267. Assert.False (tree.IsExpanded (c1));
  268. // Rebuild the tree
  269. tree.RebuildTree ();
  270. // Rebuild should not have collapsed any branches or done anything wierd
  271. Assert.True (tree.IsExpanded (f));
  272. tree.Expand (c1);
  273. Assert.True (tree.IsExpanded (c1));
  274. Assert.Equal (wheel, tree.GetChildren (c1).FirstOrDefault ());
  275. }
  276. /// <summary>
  277. /// Tests that <see cref="TreeView.GetChildren(object)"/> returns the child objects for
  278. /// the factory. Note that the method only works once the parent branch (Factory)
  279. /// is expanded to expose the child (Car)
  280. /// </summary>
  281. [Fact]
  282. public void GetChildren_ReturnsChildrenOnlyWhenExpanded ()
  283. {
  284. var tree = CreateTree (out var f, out var c1, out var c2);
  285. Assert.Empty (tree.GetChildren (f));
  286. Assert.Empty (tree.GetChildren (c1));
  287. Assert.Empty (tree.GetChildren (c2));
  288. // now when we expand the factory we discover the cars
  289. tree.Expand (f);
  290. Assert.Contains (c1, tree.GetChildren (f));
  291. Assert.Contains (c2, tree.GetChildren (f));
  292. Assert.Empty (tree.GetChildren (c1));
  293. Assert.Empty (tree.GetChildren (c2));
  294. tree.Collapse (f);
  295. Assert.Empty (tree.GetChildren (f));
  296. Assert.Empty (tree.GetChildren (c1));
  297. Assert.Empty (tree.GetChildren (c2));
  298. }
  299. [Fact]
  300. public void TreeNode_WorksWithoutDelegate ()
  301. {
  302. var tree = new TreeView ();
  303. var root = new TreeNode ("Root");
  304. root.Children.Add (new TreeNode ("Leaf1"));
  305. root.Children.Add (new TreeNode ("Leaf2"));
  306. tree.AddObject (root);
  307. tree.Expand (root);
  308. Assert.Equal (2, tree.GetChildren (root).Count ());
  309. }
  310. [Fact]
  311. public void MultiSelect_GetAllSelectedObjects ()
  312. {
  313. var tree = new TreeView ();
  314. TreeNode l1;
  315. TreeNode l2;
  316. TreeNode l3;
  317. TreeNode l4;
  318. var root = new TreeNode ("Root");
  319. root.Children.Add (l1 = new TreeNode ("Leaf1"));
  320. root.Children.Add (l2 = new TreeNode ("Leaf2"));
  321. root.Children.Add (l3 = new TreeNode ("Leaf3"));
  322. root.Children.Add (l4 = new TreeNode ("Leaf4"));
  323. tree.AddObject (root);
  324. tree.MultiSelect = true;
  325. tree.Expand (root);
  326. Assert.Empty (tree.GetAllSelectedObjects ());
  327. tree.SelectedObject = root;
  328. Assert.Single (tree.GetAllSelectedObjects (), root);
  329. // move selection down 1
  330. tree.AdjustSelection (1);
  331. Assert.Single (tree.GetAllSelectedObjects (), l1);
  332. // expand selection down 2 (e.g. shift down twice)
  333. tree.AdjustSelection (1, true);
  334. tree.AdjustSelection (1, true);
  335. Assert.Equal (3, tree.GetAllSelectedObjects ().Count ());
  336. Assert.Contains (l1, tree.GetAllSelectedObjects ());
  337. Assert.Contains (l2, tree.GetAllSelectedObjects ());
  338. Assert.Contains (l3, tree.GetAllSelectedObjects ());
  339. tree.Collapse (root);
  340. // No selected objects since the root was collapsed
  341. Assert.Empty (tree.GetAllSelectedObjects ());
  342. }
  343. [Fact]
  344. public void ObjectActivated_Called ()
  345. {
  346. var tree = CreateTree (out var f, out var car1, out _);
  347. InitFakeDriver ();
  348. object activated = null;
  349. var called = false;
  350. // register for the event
  351. tree.ObjectActivated += (s, e) => {
  352. activated = e.ActivatedObject;
  353. called = true;
  354. };
  355. Assert.False (called);
  356. // no object is selected yet so no event should happen
  357. tree.NewKeyDownEvent (new Key (KeyCode.Enter));
  358. Assert.Null (activated);
  359. Assert.False (called);
  360. // down to select factory
  361. tree.NewKeyDownEvent (new Key (KeyCode.CursorDown));
  362. tree.NewKeyDownEvent (new Key (KeyCode.Enter));
  363. Assert.True (called);
  364. Assert.Same (f, activated);
  365. Application.Shutdown ();
  366. }
  367. [Fact]
  368. public void GoTo_OnlyAppliesToExposedObjects ()
  369. {
  370. var tree = CreateTree (out var f, out var car1, out _);
  371. tree.BeginInit ();
  372. tree.EndInit ();
  373. // Make tree bounds 1 in height so that EnsureVisible always requires updating scroll offset
  374. tree.Bounds = new Rect (0, 0, 50, 1);
  375. Assert.Null (tree.SelectedObject);
  376. Assert.Equal (0, tree.ScrollOffsetVertical);
  377. // car 1 is not yet exposed
  378. tree.GoTo (car1);
  379. Assert.Null (tree.SelectedObject);
  380. Assert.Equal (0, tree.ScrollOffsetVertical);
  381. tree.Expand (f);
  382. // Car1 is now exposed by expanding the factory
  383. tree.GoTo (car1);
  384. Assert.Equal (car1, tree.SelectedObject);
  385. Assert.Equal (1, tree.ScrollOffsetVertical);
  386. }
  387. [Fact]
  388. public void GoToEnd_ShouldNotFailOnEmptyTreeView ()
  389. {
  390. var tree = new TreeView ();
  391. var exception = Record.Exception (() => tree.GoToEnd ());
  392. Assert.Null (exception);
  393. }
  394. [Fact]
  395. public void ObjectActivated_CustomKey ()
  396. {
  397. var tree = CreateTree (out var f, out var car1, out _);
  398. InitFakeDriver ();
  399. tree.ObjectActivationKey = KeyCode.Delete;
  400. object activated = null;
  401. var called = false;
  402. // register for the event
  403. tree.ObjectActivated += (s, e) => {
  404. activated = e.ActivatedObject;
  405. called = true;
  406. };
  407. Assert.False (called);
  408. // no object is selected yet so no event should happen
  409. tree.NewKeyDownEvent (new Key (KeyCode.Enter));
  410. Assert.Null (activated);
  411. Assert.False (called);
  412. // down to select factory
  413. tree.NewKeyDownEvent (new Key (KeyCode.CursorDown));
  414. tree.NewKeyDownEvent (new Key (KeyCode.Enter));
  415. // Enter is not the activation key in this unit test
  416. Assert.Null (activated);
  417. Assert.False (called);
  418. // Delete is the activation key in this test so should result in activation occurring
  419. tree.NewKeyDownEvent (new Key (KeyCode.Delete));
  420. Assert.True (called);
  421. Assert.Same (f, activated);
  422. Application.Shutdown ();
  423. }
  424. [Fact]
  425. public void ObjectActivationButton_DoubleClick ()
  426. {
  427. var tree = CreateTree (out var f, out var car1, out _);
  428. InitFakeDriver ();
  429. object activated = null;
  430. var called = false;
  431. // register for the event
  432. tree.ObjectActivated += (s, e) => {
  433. activated = e.ActivatedObject;
  434. called = true;
  435. };
  436. Assert.False (called);
  437. // double click triggers activation
  438. tree.MouseEvent (new MouseEvent { Y = 0, Flags = MouseFlags.Button1DoubleClicked });
  439. Assert.True (called);
  440. Assert.Same (f, activated);
  441. Assert.Same (f, tree.SelectedObject);
  442. Application.Shutdown ();
  443. }
  444. [Fact]
  445. public void ObjectActivationButton_SetToNull ()
  446. {
  447. var tree = CreateTree (out var f, out var car1, out _);
  448. InitFakeDriver ();
  449. // disable activation
  450. tree.ObjectActivationButton = null;
  451. object activated = null;
  452. var called = false;
  453. // register for the event
  454. tree.ObjectActivated += (s, e) => {
  455. activated = e.ActivatedObject;
  456. called = true;
  457. };
  458. Assert.False (called);
  459. // double click does nothing because we changed button to null
  460. tree.MouseEvent (new MouseEvent { Y = 0, Flags = MouseFlags.Button1DoubleClicked });
  461. Assert.False (called);
  462. Assert.Null (activated);
  463. Assert.Null (tree.SelectedObject);
  464. Application.Shutdown ();
  465. }
  466. [Fact]
  467. public void ObjectActivationButton_RightClick ()
  468. {
  469. var tree = CreateTree (out var f, out var car1, out _);
  470. InitFakeDriver ();
  471. tree.ObjectActivationButton = MouseFlags.Button2Clicked;
  472. tree.ExpandAll ();
  473. object activated = null;
  474. var called = false;
  475. // register for the event
  476. tree.ObjectActivated += (s, e) => {
  477. activated = e.ActivatedObject;
  478. called = true;
  479. };
  480. Assert.False (called);
  481. // double click does nothing because we changed button binding to right click
  482. tree.MouseEvent (new MouseEvent { Y = 1, Flags = MouseFlags.Button1DoubleClicked });
  483. Assert.Null (activated);
  484. Assert.False (called);
  485. tree.MouseEvent (new MouseEvent { Y = 1, Flags = MouseFlags.Button2Clicked });
  486. Assert.True (called);
  487. Assert.Same (car1, activated);
  488. Assert.Same (car1, tree.SelectedObject);
  489. Application.Shutdown ();
  490. }
  491. /// <summary>
  492. /// Simulates behind the scenes changes to an object (which children it has) and how to sync that into the tree using
  493. /// <see cref="TreeView.RefreshObject(object, bool)"/>
  494. /// </summary>
  495. [Fact]
  496. public void RefreshObject_EqualityTest ()
  497. {
  498. var obj1 = new EqualityTestObject { Name = "Bob", Age = 1 };
  499. var obj2 = new EqualityTestObject { Name = "Bob", Age = 2 };
  500. ;
  501. var root = "root";
  502. var tree = new TreeView<object> ();
  503. tree.TreeBuilder = new DelegateTreeBuilder<object> (s => ReferenceEquals (s, root) ? new object [] { obj1 } : null);
  504. tree.AddObject (root);
  505. // Tree is not expanded so the root has no children yet
  506. Assert.Empty (tree.GetChildren (root));
  507. tree.Expand (root);
  508. // now that the tree is expanded we should get our child returned
  509. Assert.Equal (1, tree.GetChildren (root).Count (child => ReferenceEquals (obj1, child)));
  510. // change the getter to return an Equal object (but not the same reference - obj2)
  511. tree.TreeBuilder = new DelegateTreeBuilder<object> (s => ReferenceEquals (s, root) ? new object [] { obj2 } : null);
  512. // tree has cached the knowledge of what children the root has so won't know about the change (we still get obj1)
  513. Assert.Equal (1, tree.GetChildren (root).Count (child => ReferenceEquals (obj1, child)));
  514. // now that we refresh the root we should get the new child reference (obj2)
  515. tree.RefreshObject (root);
  516. Assert.Equal (1, tree.GetChildren (root).Count (child => ReferenceEquals (obj2, child)));
  517. }
  518. [Fact, AutoInitShutdown]
  519. public void TestGetObjectOnRow ()
  520. {
  521. var tv = new TreeView { Width = 20, Height = 10 };
  522. tv.BeginInit ();
  523. tv.EndInit ();
  524. var n1 = new TreeNode ("normal");
  525. var n1_1 = new TreeNode ("pink");
  526. var n1_2 = new TreeNode ("normal");
  527. n1.Children.Add (n1_1);
  528. n1.Children.Add (n1_2);
  529. var n2 = new TreeNode ("pink");
  530. tv.AddObject (n1);
  531. tv.AddObject (n2);
  532. tv.Expand (n1);
  533. tv.ColorScheme = new ColorScheme ();
  534. tv.LayoutSubviews ();
  535. tv.Draw ();
  536. TestHelpers.AssertDriverContentsAre (
  537. @"├-normal
  538. │ ├─pink
  539. │ └─normal
  540. └─pink
  541. ", _output);
  542. Assert.Same (n1, tv.GetObjectOnRow (0));
  543. Assert.Same (n1_1, tv.GetObjectOnRow (1));
  544. Assert.Same (n1_2, tv.GetObjectOnRow (2));
  545. Assert.Same (n2, tv.GetObjectOnRow (3));
  546. Assert.Null (tv.GetObjectOnRow (4));
  547. tv.Collapse (n1);
  548. tv.Draw ();
  549. TestHelpers.AssertDriverContentsAre (
  550. @"├+normal
  551. └─pink
  552. ", _output);
  553. Assert.Same (n1, tv.GetObjectOnRow (0));
  554. Assert.Same (n2, tv.GetObjectOnRow (1));
  555. Assert.Null (tv.GetObjectOnRow (2));
  556. Assert.Null (tv.GetObjectOnRow (3));
  557. Assert.Null (tv.GetObjectOnRow (4));
  558. }
  559. [Fact, AutoInitShutdown]
  560. public void TestGetObjectRow ()
  561. {
  562. var tv = new TreeView { Width = 20, Height = 10 };
  563. var n1 = new TreeNode ("normal");
  564. var n1_1 = new TreeNode ("pink");
  565. var n1_2 = new TreeNode ("normal");
  566. n1.Children.Add (n1_1);
  567. n1.Children.Add (n1_2);
  568. var n2 = new TreeNode ("pink");
  569. tv.AddObject (n1);
  570. tv.AddObject (n2);
  571. tv.Expand (n1);
  572. tv.ColorScheme = new ColorScheme ();
  573. tv.LayoutSubviews ();
  574. tv.Draw ();
  575. TestHelpers.AssertDriverContentsAre (
  576. @"├-normal
  577. │ ├─pink
  578. │ └─normal
  579. └─pink
  580. ", _output);
  581. Assert.Equal (0, tv.GetObjectRow (n1));
  582. Assert.Equal (1, tv.GetObjectRow (n1_1));
  583. Assert.Equal (2, tv.GetObjectRow (n1_2));
  584. Assert.Equal (3, tv.GetObjectRow (n2));
  585. tv.Collapse (n1);
  586. tv.LayoutSubviews ();
  587. tv.Draw ();
  588. TestHelpers.AssertDriverContentsAre (
  589. @"├+normal
  590. └─pink
  591. ", _output);
  592. Assert.Equal (0, tv.GetObjectRow (n1));
  593. Assert.Null (tv.GetObjectRow (n1_1));
  594. Assert.Null (tv.GetObjectRow (n1_2));
  595. Assert.Equal (1, tv.GetObjectRow (n2));
  596. // scroll down 1
  597. tv.ScrollOffsetVertical = 1;
  598. tv.LayoutSubviews ();
  599. tv.Draw ();
  600. TestHelpers.AssertDriverContentsAre (
  601. @"└─pink
  602. ", _output);
  603. Assert.Equal (-1, tv.GetObjectRow (n1));
  604. Assert.Null (tv.GetObjectRow (n1_1));
  605. Assert.Null (tv.GetObjectRow (n1_2));
  606. Assert.Equal (0, tv.GetObjectRow (n2));
  607. }
  608. [Fact, AutoInitShutdown]
  609. public void TestTreeViewColor ()
  610. {
  611. var tv = new TreeView { Width = 20, Height = 10 };
  612. tv.BeginInit ();
  613. tv.EndInit ();
  614. var n1 = new TreeNode ("normal");
  615. var n1_1 = new TreeNode ("pink");
  616. var n1_2 = new TreeNode ("normal");
  617. n1.Children.Add (n1_1);
  618. n1.Children.Add (n1_2);
  619. var n2 = new TreeNode ("pink");
  620. tv.AddObject (n1);
  621. tv.AddObject (n2);
  622. tv.Expand (n1);
  623. tv.ColorScheme = new ColorScheme ();
  624. tv.LayoutSubviews ();
  625. tv.Draw ();
  626. // create a new color scheme
  627. var pink = new Attribute (Color.Magenta, Color.Black);
  628. var hotpink = new Attribute (Color.BrightMagenta, Color.Black);
  629. // Normal drawing of the tree view
  630. TestHelpers.AssertDriverContentsAre (@"
  631. ├-normal
  632. │ ├─pink
  633. │ └─normal
  634. └─pink
  635. ", _output);
  636. // Should all be the same color
  637. TestHelpers.AssertDriverColorsAre (@"
  638. 0000000000
  639. 0000000000
  640. 0000000000
  641. 0000000000
  642. ", Application.Driver, tv.ColorScheme.Normal, pink);
  643. var pinkScheme = new ColorScheme {
  644. Normal = pink,
  645. Focus = hotpink
  646. };
  647. // and a delegate that uses the pink color scheme
  648. // for nodes "pink"
  649. tv.ColorGetter = n => n.Text.Equals ("pink") ? pinkScheme : null;
  650. // redraw now that the custom color
  651. // delegate is registered
  652. tv.Draw ();
  653. // Same text
  654. TestHelpers.AssertDriverContentsAre (@"
  655. ├-normal
  656. │ ├─pink
  657. │ └─normal
  658. └─pink
  659. ", _output);
  660. // but now the item (only not lines) appear
  661. // in pink when they are the word "pink"
  662. TestHelpers.AssertDriverColorsAre (@"
  663. 00000000
  664. 00001111
  665. 0000000000
  666. 001111
  667. ", Application.Driver, tv.ColorScheme.Normal, pink);
  668. }
  669. [Fact, AutoInitShutdown]
  670. public void TestBottomlessTreeView_MaxDepth_5 ()
  671. {
  672. var tv = new TreeView<string> { Width = 20, Height = 10 };
  673. tv.TreeBuilder = new DelegateTreeBuilder<string> (
  674. s => new [] { (int.Parse (s) + 1).ToString () }
  675. );
  676. tv.AddObject ("1");
  677. tv.ColorScheme = new ColorScheme ();
  678. tv.LayoutSubviews ();
  679. tv.Draw ();
  680. // Nothing expanded
  681. TestHelpers.AssertDriverContentsAre (
  682. @"└+1
  683. ", _output);
  684. tv.MaxDepth = 5;
  685. tv.ExpandAll ();
  686. tv.Draw ();
  687. // Normal drawing of the tree view
  688. TestHelpers.AssertDriverContentsAre (
  689. @"
  690. └-1
  691. └-2
  692. └-3
  693. └-4
  694. └-5
  695. └─6
  696. ", _output);
  697. Assert.False (tv.CanExpand ("6"));
  698. Assert.False (tv.IsExpanded ("6"));
  699. tv.Collapse ("6");
  700. Assert.False (tv.CanExpand ("6"));
  701. Assert.False (tv.IsExpanded ("6"));
  702. tv.Collapse ("5");
  703. Assert.True (tv.CanExpand ("5"));
  704. Assert.False (tv.IsExpanded ("5"));
  705. tv.Draw ();
  706. // Normal drawing of the tree view
  707. TestHelpers.AssertDriverContentsAre (
  708. @"
  709. └-1
  710. └-2
  711. └-3
  712. └-4
  713. └+5
  714. ", _output);
  715. }
  716. [Fact, AutoInitShutdown]
  717. public void TestBottomlessTreeView_MaxDepth_3 ()
  718. {
  719. var tv = new TreeView<string> { Width = 20, Height = 10 };
  720. tv.TreeBuilder = new DelegateTreeBuilder<string> (
  721. s => new [] { (int.Parse (s) + 1).ToString () }
  722. );
  723. tv.AddObject ("1");
  724. tv.ColorScheme = new ColorScheme ();
  725. tv.LayoutSubviews ();
  726. tv.Draw ();
  727. // Nothing expanded
  728. TestHelpers.AssertDriverContentsAre (
  729. @"└+1
  730. ", _output);
  731. tv.MaxDepth = 3;
  732. tv.ExpandAll ();
  733. tv.Draw ();
  734. // Normal drawing of the tree view
  735. TestHelpers.AssertDriverContentsAre (
  736. @"
  737. └-1
  738. └-2
  739. └-3
  740. └─4
  741. ", _output);
  742. }
  743. [Fact, AutoInitShutdown]
  744. public void TestTreeView_DrawLineEvent ()
  745. {
  746. var tv = new TreeView { Width = 20, Height = 10 };
  747. var eventArgs = new List<DrawTreeViewLineEventArgs<ITreeNode>> ();
  748. tv.DrawLine += (s, e) => {
  749. eventArgs.Add (e);
  750. };
  751. var n1 = new TreeNode ("root one");
  752. var n1_1 = new TreeNode ("leaf 1");
  753. var n1_2 = new TreeNode ("leaf 2");
  754. n1.Children.Add (n1_1);
  755. n1.Children.Add (n1_2);
  756. var n2 = new TreeNode ("root two");
  757. tv.AddObject (n1);
  758. tv.AddObject (n2);
  759. tv.Expand (n1);
  760. tv.ColorScheme = new ColorScheme ();
  761. tv.LayoutSubviews ();
  762. tv.Draw ();
  763. // Normal drawing of the tree view
  764. TestHelpers.AssertDriverContentsAre (
  765. @"
  766. ├-root one
  767. │ ├─leaf 1
  768. │ └─leaf 2
  769. └─root two
  770. ", _output);
  771. Assert.Equal (4, eventArgs.Count ());
  772. Assert.Equal (0, eventArgs [0].Y);
  773. Assert.Equal (1, eventArgs [1].Y);
  774. Assert.Equal (2, eventArgs [2].Y);
  775. Assert.Equal (3, eventArgs [3].Y);
  776. Assert.All (eventArgs, ea => Assert.Equal (ea.Tree, tv));
  777. Assert.All (eventArgs, ea => Assert.False (ea.Handled));
  778. Assert.Equal ("├-root one", eventArgs [0].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ());
  779. Assert.Equal ("│ ├─leaf 1", eventArgs [1].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ());
  780. Assert.Equal ("│ └─leaf 2", eventArgs [2].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ());
  781. Assert.Equal ("└─root two", eventArgs [3].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ());
  782. Assert.Equal (1, eventArgs [0].IndexOfExpandCollapseSymbol);
  783. Assert.Equal (3, eventArgs [1].IndexOfExpandCollapseSymbol);
  784. Assert.Equal (3, eventArgs [2].IndexOfExpandCollapseSymbol);
  785. Assert.Equal (1, eventArgs [3].IndexOfExpandCollapseSymbol);
  786. Assert.Equal (2, eventArgs [0].IndexOfModelText);
  787. Assert.Equal (4, eventArgs [1].IndexOfModelText);
  788. Assert.Equal (4, eventArgs [2].IndexOfModelText);
  789. Assert.Equal (2, eventArgs [3].IndexOfModelText);
  790. Assert.Equal ("root one", eventArgs [0].Model.Text);
  791. Assert.Equal ("leaf 1", eventArgs [1].Model.Text);
  792. Assert.Equal ("leaf 2", eventArgs [2].Model.Text);
  793. Assert.Equal ("root two", eventArgs [3].Model.Text);
  794. }
  795. [Fact, AutoInitShutdown]
  796. public void TestTreeView_DrawLineEvent_WithScrolling ()
  797. {
  798. var tv = new TreeView { Width = 20, Height = 10 };
  799. var eventArgs = new List<DrawTreeViewLineEventArgs<ITreeNode>> ();
  800. tv.DrawLine += (s, e) => {
  801. eventArgs.Add (e);
  802. };
  803. tv.ScrollOffsetHorizontal = 3;
  804. tv.ScrollOffsetVertical = 1;
  805. var n1 = new TreeNode ("root one");
  806. var n1_1 = new TreeNode ("leaf 1");
  807. var n1_2 = new TreeNode ("leaf 2");
  808. n1.Children.Add (n1_1);
  809. n1.Children.Add (n1_2);
  810. var n2 = new TreeNode ("root two");
  811. tv.AddObject (n1);
  812. tv.AddObject (n2);
  813. tv.Expand (n1);
  814. tv.ColorScheme = new ColorScheme ();
  815. tv.LayoutSubviews ();
  816. tv.Draw ();
  817. // Normal drawing of the tree view
  818. TestHelpers.AssertDriverContentsAre (
  819. @"
  820. ─leaf 1
  821. ─leaf 2
  822. oot two
  823. ", _output);
  824. Assert.Equal (3, eventArgs.Count ());
  825. Assert.Equal (0, eventArgs [0].Y);
  826. Assert.Equal (1, eventArgs [1].Y);
  827. Assert.Equal (2, eventArgs [2].Y);
  828. Assert.All (eventArgs, ea => Assert.Equal (ea.Tree, tv));
  829. Assert.All (eventArgs, ea => Assert.False (ea.Handled));
  830. Assert.Equal ("─leaf 1", eventArgs [0].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ());
  831. Assert.Equal ("─leaf 2", eventArgs [1].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ());
  832. Assert.Equal ("oot two", eventArgs [2].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ());
  833. Assert.Equal (0, eventArgs [0].IndexOfExpandCollapseSymbol);
  834. Assert.Equal (0, eventArgs [1].IndexOfExpandCollapseSymbol);
  835. Assert.Null (eventArgs [2].IndexOfExpandCollapseSymbol);
  836. Assert.Equal (1, eventArgs [0].IndexOfModelText);
  837. Assert.Equal (1, eventArgs [1].IndexOfModelText);
  838. Assert.Equal (-1, eventArgs [2].IndexOfModelText);
  839. Assert.Equal ("leaf 1", eventArgs [0].Model.Text);
  840. Assert.Equal ("leaf 2", eventArgs [1].Model.Text);
  841. Assert.Equal ("root two", eventArgs [2].Model.Text);
  842. }
  843. [Fact, AutoInitShutdown]
  844. public void TestTreeView_DrawLineEvent_Handled ()
  845. {
  846. var tv = new TreeView { Width = 20, Height = 10 };
  847. tv.DrawLine += (s, e) => {
  848. if (e.Model.Text.Equals ("leaf 1")) {
  849. e.Handled = true;
  850. for (var i = 0; i < 10; i++) {
  851. e.Tree.AddRune (i, e.Y, new Rune ('F'));
  852. }
  853. }
  854. };
  855. var n1 = new TreeNode ("root one");
  856. var n1_1 = new TreeNode ("leaf 1");
  857. var n1_2 = new TreeNode ("leaf 2");
  858. n1.Children.Add (n1_1);
  859. n1.Children.Add (n1_2);
  860. var n2 = new TreeNode ("root two");
  861. tv.AddObject (n1);
  862. tv.AddObject (n2);
  863. tv.Expand (n1);
  864. tv.ColorScheme = new ColorScheme ();
  865. tv.LayoutSubviews ();
  866. tv.Draw ();
  867. // Normal drawing of the tree view
  868. TestHelpers.AssertDriverContentsAre (
  869. @"
  870. ├-root one
  871. FFFFFFFFFF
  872. │ └─leaf 2
  873. └─root two
  874. ", _output);
  875. }
  876. [Fact, AutoInitShutdown]
  877. public void TestTreeView_Filter ()
  878. {
  879. var tv = new TreeView { Width = 20, Height = 10 };
  880. var n1 = new TreeNode ("root one");
  881. var n1_1 = new TreeNode ("leaf 1");
  882. var n1_2 = new TreeNode ("leaf 2");
  883. n1.Children.Add (n1_1);
  884. n1.Children.Add (n1_2);
  885. var n2 = new TreeNode ("root two");
  886. tv.AddObject (n1);
  887. tv.AddObject (n2);
  888. tv.Expand (n1);
  889. tv.ColorScheme = new ColorScheme ();
  890. tv.LayoutSubviews ();
  891. tv.Draw ();
  892. // Normal drawing of the tree view
  893. TestHelpers.AssertDriverContentsAre (
  894. @"
  895. ├-root one
  896. │ ├─leaf 1
  897. │ └─leaf 2
  898. └─root two
  899. ", _output);
  900. var filter = new TreeViewTextFilter<ITreeNode> (tv);
  901. tv.Filter = filter;
  902. // matches nothing
  903. filter.Text = "asdfjhasdf";
  904. tv.Draw ();
  905. // Normal drawing of the tree view
  906. TestHelpers.AssertDriverContentsAre (
  907. @"", _output);
  908. // Matches everything
  909. filter.Text = "root";
  910. tv.Draw ();
  911. TestHelpers.AssertDriverContentsAre (
  912. @"
  913. ├-root one
  914. │ ├─leaf 1
  915. │ └─leaf 2
  916. └─root two
  917. ", _output);
  918. // Matches 2 leaf nodes
  919. filter.Text = "leaf";
  920. tv.Draw ();
  921. TestHelpers.AssertDriverContentsAre (
  922. @"
  923. ├-root one
  924. │ ├─leaf 1
  925. │ └─leaf 2
  926. ", _output);
  927. // Matches 1 leaf nodes
  928. filter.Text = "leaf 1";
  929. tv.Draw ();
  930. TestHelpers.AssertDriverContentsAre (
  931. @"
  932. ├-root one
  933. │ ├─leaf 1
  934. ", _output);
  935. }
  936. [Fact, AutoInitShutdown]
  937. public void DesiredCursorVisibility_MultiSelect ()
  938. {
  939. var tv = new TreeView { Width = 20, Height = 10 };
  940. var n1 = new TreeNode ("normal");
  941. var n2 = new TreeNode ("pink");
  942. tv.AddObject (n1);
  943. tv.AddObject (n2);
  944. Application.Top.Add (tv);
  945. Application.Begin (Application.Top);
  946. Assert.True (tv.MultiSelect);
  947. Assert.True (tv.HasFocus);
  948. Assert.Equal (CursorVisibility.Invisible, tv.DesiredCursorVisibility);
  949. tv.SelectAll ();
  950. tv.DesiredCursorVisibility = CursorVisibility.Default;
  951. Application.Refresh ();
  952. Application.Driver.GetCursorVisibility (out var visibility);
  953. Assert.Equal (CursorVisibility.Default, tv.DesiredCursorVisibility);
  954. Assert.Equal (CursorVisibility.Default, visibility);
  955. }
  956. void InitFakeDriver ()
  957. {
  958. var driver = new FakeDriver ();
  959. Application.Init (driver);
  960. driver.Init ();
  961. }
  962. /// <summary>
  963. /// Test object which considers for equality only <see cref="Name"/>
  964. /// </summary>
  965. class EqualityTestObject {
  966. public string Name { get; set; }
  967. public int Age { get; set; }
  968. public override int GetHashCode () => Name?.GetHashCode () ?? base.GetHashCode ();
  969. public override bool Equals (object obj) => obj is EqualityTestObject eto && Equals (Name, eto.Name);
  970. }
  971. #region Test Setup Methods
  972. class Factory {
  973. public Car [] Cars { get; set; }
  974. public override string ToString () => "Factory";
  975. }
  976. class Car {
  977. public string Name { get; set; }
  978. public override string ToString () => Name;
  979. }
  980. TreeView<object> CreateTree () => CreateTree (out _, out _, out _);
  981. TreeView<object> CreateTree (out Factory factory1, out Car car1, out Car car2)
  982. {
  983. car1 = new Car ();
  984. car2 = new Car ();
  985. factory1 = new Factory {
  986. Cars = new [] { car1, car2 }
  987. };
  988. var tree = new TreeView<object> (new DelegateTreeBuilder<object> (s => s is Factory f ? f.Cars : null));
  989. tree.AddObject (factory1);
  990. return tree;
  991. }
  992. #endregion
  993. }