Files
Terminal.Gui/UnitTests/Views/TreeViewTests.cs
2023-12-07 14:56:01 +00:00

1287 lines
33 KiB
C#

using System.Collections.Generic;
using System.Linq;
using Xunit;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewsTests {
public class TreeViewTests {
readonly ITestOutputHelper output;
public TreeViewTests (ITestOutputHelper output)
{
this.output = output;
}
#region Test Setup Methods
class Factory {
public Car [] Cars { get; set; }
public override string ToString ()
{
return "Factory";
}
};
class Car {
public string Name { get; set; }
public override string ToString ()
{
return Name;
}
};
private TreeView<object> CreateTree ()
{
return CreateTree (out _, out _, out _);
}
private TreeView<object> CreateTree (out Factory factory1, out Car car1, out Car car2)
{
car1 = new Car ();
car2 = new Car ();
factory1 = new Factory () {
Cars = new [] { car1, car2 }
};
var tree = new TreeView<object> (new DelegateTreeBuilder<object> ((s) => s is Factory f ? f.Cars : null));
tree.AddObject (factory1);
return tree;
}
#endregion
/// <summary>
/// Tests that <see cref="TreeView.Expand(object)"/> and <see cref="TreeView.IsExpanded(object)"/> are consistent
/// </summary>
[Fact]
public void IsExpanded_TrueAfterExpand ()
{
var tree = CreateTree (out Factory f, out _, out _);
Assert.False (tree.IsExpanded (f));
tree.Expand (f);
Assert.True (tree.IsExpanded (f));
tree.Collapse (f);
Assert.False (tree.IsExpanded (f));
}
[Fact]
public void EmptyTreeView_ContentSizes ()
{
var emptyTree = new TreeView ();
Assert.Equal (0, emptyTree.ContentHeight);
Assert.Equal (0, emptyTree.GetContentWidth (true));
Assert.Equal (0, emptyTree.GetContentWidth (false));
}
[Fact]
public void EmptyTreeViewGeneric_ContentSizes ()
{
var emptyTree = new TreeView<string> ();
Assert.Equal (0, emptyTree.ContentHeight);
Assert.Equal (0, emptyTree.GetContentWidth (true));
Assert.Equal (0, emptyTree.GetContentWidth (false));
}
/// <summary>
/// Tests that <see cref="TreeView.Expand(object)"/> results in a correct content height
/// </summary>
[Fact]
public void ContentHeight_BiggerAfterExpand ()
{
var tree = CreateTree (out Factory f, out _, out _);
Assert.Equal (1, tree.ContentHeight);
tree.Expand (f);
Assert.Equal (3, tree.ContentHeight);
tree.Collapse (f);
Assert.Equal (1, tree.ContentHeight);
}
[Fact]
public void ContentWidth_BiggerAfterExpand ()
{
var tree = CreateTree (out Factory f, out Car car1, out _);
tree.BeginInit (); tree.EndInit ();
tree.Bounds = new Rect (0, 0, 10, 10);
InitFakeDriver ();
//-+Factory
Assert.Equal (9, tree.GetContentWidth (true));
car1.Name = "123456789";
tree.Expand (f);
//..├-123456789
Assert.Equal (13, tree.GetContentWidth (true));
tree.Collapse (f);
//-+Factory
Assert.Equal (9, tree.GetContentWidth (true));
Application.Shutdown ();
}
[Fact]
public void ContentWidth_VisibleVsAll ()
{
var tree = CreateTree (out Factory f, out Car car1, out Car car2);
tree.BeginInit (); tree.EndInit ();
// control only allows 1 row to be viewed at once
tree.Bounds = new Rect (0, 0, 20, 1);
InitFakeDriver ();
//-+Factory
Assert.Equal (9, tree.GetContentWidth (true));
Assert.Equal (9, tree.GetContentWidth (false));
car1.Name = "123456789";
car2.Name = "12345678";
tree.Expand (f);
// Although expanded the bigger (longer) child node is not in the rendered area of the control
Assert.Equal (9, tree.GetContentWidth (true));
Assert.Equal (13, tree.GetContentWidth (false)); // If you ask for the global max width it includes the longer child
// Now that we have scrolled down 1 row we should see the big child
tree.ScrollOffsetVertical = 1;
Assert.Equal (13, tree.GetContentWidth (true));
Assert.Equal (13, tree.GetContentWidth (false));
// Scroll down so only car2 is visible
tree.ScrollOffsetVertical = 2;
Assert.Equal (12, tree.GetContentWidth (true));
Assert.Equal (13, tree.GetContentWidth (false));
// Scroll way down (off bottom of control even)
tree.ScrollOffsetVertical = 5;
Assert.Equal (0, tree.GetContentWidth (true));
Assert.Equal (13, tree.GetContentWidth (false));
Application.Shutdown ();
}
/// <summary>
/// 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)
/// </summary>
[Fact]
public void IsExpanded_FalseIfCannotExpand ()
{
var tree = CreateTree (out Factory f, out Car c, out _);
// expose the car by expanding the factory
tree.Expand (f);
// car is not expanded
Assert.False (tree.IsExpanded (c));
//try to expand the car (should have no effect because cars have no children)
tree.Expand (c);
Assert.False (tree.IsExpanded (c));
// should also be ignored
tree.Collapse (c);
Assert.False (tree.IsExpanded (c));
Application.Shutdown ();
}
/// <summary>
/// Tests illegal ranges for <see cref="TreeView.ScrollOffset"/>
/// </summary>
[Fact]
public void ScrollOffset_CannotBeNegative ()
{
var tree = CreateTree ();
Assert.Equal (0, tree.ScrollOffsetVertical);
tree.ScrollOffsetVertical = -100;
Assert.Equal (0, tree.ScrollOffsetVertical);
tree.ScrollOffsetVertical = 10;
Assert.Equal (10, tree.ScrollOffsetVertical);
}
/// <summary>
/// Tests <see cref="TreeView.GetScrollOffsetOf(object)"/> for objects that are as yet undiscovered by the tree
/// </summary>
[Fact]
public void GetScrollOffsetOf_MinusOneForUnRevealed ()
{
var tree = CreateTree (out Factory f, out Car c1, out Car c2);
// to start with the tree is collapsed and only knows about the root object
Assert.Equal (0, tree.GetScrollOffsetOf (f));
Assert.Equal (-1, tree.GetScrollOffsetOf (c1));
Assert.Equal (-1, tree.GetScrollOffsetOf (c2));
// reveal it by expanding the root object
tree.Expand (f);
// tree now knows about children
Assert.Equal (0, tree.GetScrollOffsetOf (f));
Assert.Equal (1, tree.GetScrollOffsetOf (c1));
Assert.Equal (2, tree.GetScrollOffsetOf (c2));
// after collapsing the root node again
tree.Collapse (f);
// tree no longer knows about the locations of these objects
Assert.Equal (0, tree.GetScrollOffsetOf (f));
Assert.Equal (-1, tree.GetScrollOffsetOf (c1));
Assert.Equal (-1, tree.GetScrollOffsetOf (c2));
}
/// <summary>
/// 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)"/>
/// </summary>
[Fact]
public void RefreshObject_ChildRemoved ()
{
var tree = CreateTree (out Factory f, out Car c1, out Car c2);
//reveal it by expanding the root object
tree.Expand (f);
Assert.Equal (0, tree.GetScrollOffsetOf (f));
Assert.Equal (1, tree.GetScrollOffsetOf (c1));
Assert.Equal (2, tree.GetScrollOffsetOf (c2));
// Factory now no longer makes Car c1 (only c2)
f.Cars = new Car [] { c2 };
// Tree does not know this yet
Assert.Equal (0, tree.GetScrollOffsetOf (f));
Assert.Equal (1, tree.GetScrollOffsetOf (c1));
Assert.Equal (2, tree.GetScrollOffsetOf (c2));
// If the user has selected the node c1
tree.SelectedObject = c1;
// When we refresh the tree
tree.RefreshObject (f);
// Now tree knows that factory has only one child node c2
Assert.Equal (0, tree.GetScrollOffsetOf (f));
Assert.Equal (-1, tree.GetScrollOffsetOf (c1));
Assert.Equal (1, tree.GetScrollOffsetOf (c2));
// The old selection was c1 which is now gone so selection should default to the parent of that branch (the factory)
Assert.Equal (f, tree.SelectedObject);
}
/// <summary>
/// Tests that <see cref="TreeView.GetParent(object)"/> returns the parent object for
/// Cars (Factories). Note that the method only works once the parent branch (Factory)
/// is expanded to expose the child (Car)
/// </summary>
[Fact]
public void GetParent_ReturnsParentOnlyWhenExpanded ()
{
var tree = CreateTree (out Factory f, out Car c1, out Car c2);
Assert.Null (tree.GetParent (f));
Assert.Null (tree.GetParent (c1));
Assert.Null (tree.GetParent (c2));
// now when we expand the factory we discover the cars
tree.Expand (f);
Assert.Null (tree.GetParent (f));
Assert.Equal (f, tree.GetParent (c1));
Assert.Equal (f, tree.GetParent (c2));
tree.Collapse (f);
Assert.Null (tree.GetParent (f));
Assert.Null (tree.GetParent (c1));
Assert.Null (tree.GetParent (c2));
}
/// <summary>
/// Tests how the tree adapts to changes in the ChildrenGetter delegate during runtime
/// when some branches are expanded and the new delegate returns children for a node that
/// previously didn't have any children
/// </summary>
[Fact]
public void RefreshObject_AfterChangingChildrenGetterDuringRuntime ()
{
var tree = CreateTree (out Factory f, out Car c1, out Car c2);
string wheel = "Shiny Wheel";
// Expand the Factory
tree.Expand (f);
// c1 cannot have children
Assert.Equal (f, tree.GetParent (c1));
// expanding it does nothing
tree.Expand (c1);
Assert.False (tree.IsExpanded (c1));
// change the children getter so that now cars can have wheels
tree.TreeBuilder = new DelegateTreeBuilder<object> ((o) =>
// factories have cars
o is Factory ? new object [] { c1, c2 }
// cars have wheels
: new object [] { wheel });
// still cannot expand
tree.Expand (c1);
Assert.False (tree.IsExpanded (c1));
tree.RefreshObject (c1);
tree.Expand (c1);
Assert.True (tree.IsExpanded (c1));
Assert.Equal (wheel, tree.GetChildren (c1).FirstOrDefault ());
}
/// <summary>
/// Same as <see cref="RefreshObject_AfterChangingChildrenGetterDuringRuntime"/> but
/// uses <see cref="TreeView.RebuildTree()"/> instead of <see cref="TreeView.RefreshObject(object, bool)"/>
/// </summary>
[Fact]
public void RebuildTree_AfterChangingChildrenGetterDuringRuntime ()
{
var tree = CreateTree (out Factory f, out Car c1, out Car c2);
string wheel = "Shiny Wheel";
// Expand the Factory
tree.Expand (f);
// c1 cannot have children
Assert.Equal (f, tree.GetParent (c1));
// expanding it does nothing
tree.Expand (c1);
Assert.False (tree.IsExpanded (c1));
// change the children getter so that now cars can have wheels
tree.TreeBuilder = new DelegateTreeBuilder<object> ((o) =>
// factories have cars
o is Factory ? new object [] { c1, c2 }
// cars have wheels
: new object [] { wheel });
// still cannot expand
tree.Expand (c1);
Assert.False (tree.IsExpanded (c1));
// Rebuild the tree
tree.RebuildTree ();
// Rebuild should not have collapsed any branches or done anything wierd
Assert.True (tree.IsExpanded (f));
tree.Expand (c1);
Assert.True (tree.IsExpanded (c1));
Assert.Equal (wheel, tree.GetChildren (c1).FirstOrDefault ());
}
/// <summary>
/// Tests that <see cref="TreeView.GetChildren(object)"/> returns the child objects for
/// the factory. Note that the method only works once the parent branch (Factory)
/// is expanded to expose the child (Car)
/// </summary>
[Fact]
public void GetChildren_ReturnsChildrenOnlyWhenExpanded ()
{
var tree = CreateTree (out Factory f, out Car c1, out Car c2);
Assert.Empty (tree.GetChildren (f));
Assert.Empty (tree.GetChildren (c1));
Assert.Empty (tree.GetChildren (c2));
// now when we expand the factory we discover the cars
tree.Expand (f);
Assert.Contains (c1, tree.GetChildren (f));
Assert.Contains (c2, tree.GetChildren (f));
Assert.Empty (tree.GetChildren (c1));
Assert.Empty (tree.GetChildren (c2));
tree.Collapse (f);
Assert.Empty (tree.GetChildren (f));
Assert.Empty (tree.GetChildren (c1));
Assert.Empty (tree.GetChildren (c2));
}
[Fact]
public void TreeNode_WorksWithoutDelegate ()
{
var tree = new TreeView ();
var root = new TreeNode ("Root");
root.Children.Add (new TreeNode ("Leaf1"));
root.Children.Add (new TreeNode ("Leaf2"));
tree.AddObject (root);
tree.Expand (root);
Assert.Equal (2, tree.GetChildren (root).Count ());
}
[Fact]
public void MultiSelect_GetAllSelectedObjects ()
{
var tree = new TreeView ();
TreeNode l1;
TreeNode l2;
TreeNode l3;
TreeNode l4;
var root = new TreeNode ("Root");
root.Children.Add (l1 = new TreeNode ("Leaf1"));
root.Children.Add (l2 = new TreeNode ("Leaf2"));
root.Children.Add (l3 = new TreeNode ("Leaf3"));
root.Children.Add (l4 = new TreeNode ("Leaf4"));
tree.AddObject (root);
tree.MultiSelect = true;
tree.Expand (root);
Assert.Empty (tree.GetAllSelectedObjects ());
tree.SelectedObject = root;
Assert.Single (tree.GetAllSelectedObjects (), root);
// move selection down 1
tree.AdjustSelection (1, false);
Assert.Single (tree.GetAllSelectedObjects (), l1);
// expand selection down 2 (e.g. shift down twice)
tree.AdjustSelection (1, true);
tree.AdjustSelection (1, true);
Assert.Equal (3, tree.GetAllSelectedObjects ().Count ());
Assert.Contains (l1, tree.GetAllSelectedObjects ());
Assert.Contains (l2, tree.GetAllSelectedObjects ());
Assert.Contains (l3, tree.GetAllSelectedObjects ());
tree.Collapse (root);
// No selected objects since the root was collapsed
Assert.Empty (tree.GetAllSelectedObjects ());
}
[Fact]
public void ObjectActivated_Called ()
{
var tree = CreateTree (out Factory f, out Car car1, out _);
InitFakeDriver ();
object activated = null;
bool called = false;
// register for the event
tree.ObjectActivated += (s, e) => {
activated = e.ActivatedObject;
called = true;
};
Assert.False (called);
// no object is selected yet so no event should happen
tree.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()));
Assert.Null (activated);
Assert.False (called);
// down to select factory
tree.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()));
tree.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()));
Assert.True (called);
Assert.Same (f, activated);
Application.Shutdown ();
}
[Fact]
public void GoTo_OnlyAppliesToExposedObjects ()
{
var tree = CreateTree (out Factory f, out Car car1, out _);
tree.BeginInit (); tree.EndInit ();
// Make tree bounds 1 in height so that EnsureVisible always requires updating scroll offset
tree.Bounds = new Rect (0, 0, 50, 1);
Assert.Null (tree.SelectedObject);
Assert.Equal (0, tree.ScrollOffsetVertical);
// car 1 is not yet exposed
tree.GoTo (car1);
Assert.Null (tree.SelectedObject);
Assert.Equal (0, tree.ScrollOffsetVertical);
tree.Expand (f);
// Car1 is now exposed by expanding the factory
tree.GoTo (car1);
Assert.Equal (car1, tree.SelectedObject);
Assert.Equal (1, tree.ScrollOffsetVertical);
}
[Fact]
public void GoToEnd_ShouldNotFailOnEmptyTreeView ()
{
var tree = new TreeView ();
var exception = Record.Exception (() => tree.GoToEnd ());
Assert.Null (exception);
}
[Fact]
public void ObjectActivated_CustomKey ()
{
var tree = CreateTree (out Factory f, out Car car1, out _);
InitFakeDriver ();
tree.ObjectActivationKey = Key.Delete;
object activated = null;
bool called = false;
// register for the event
tree.ObjectActivated += (s, e) => {
activated = e.ActivatedObject;
called = true;
};
Assert.False (called);
// no object is selected yet so no event should happen
tree.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()));
Assert.Null (activated);
Assert.False (called);
// down to select factory
tree.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ()));
tree.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()));
// Enter is not the activation key in this unit test
Assert.Null (activated);
Assert.False (called);
// Delete is the activation key in this test so should result in activation occurring
tree.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers ()));
Assert.True (called);
Assert.Same (f, activated);
Application.Shutdown ();
}
[Fact]
public void ObjectActivationButton_DoubleClick ()
{
var tree = CreateTree (out Factory f, out Car car1, out _);
InitFakeDriver ();
object activated = null;
bool called = false;
// register for the event
tree.ObjectActivated += (s, e) => {
activated = e.ActivatedObject;
called = true;
};
Assert.False (called);
// double click triggers activation
tree.MouseEvent (new MouseEvent () { Y = 0, Flags = MouseFlags.Button1DoubleClicked });
Assert.True (called);
Assert.Same (f, activated);
Assert.Same (f, tree.SelectedObject);
Application.Shutdown ();
}
[Fact]
public void ObjectActivationButton_SetToNull ()
{
var tree = CreateTree (out Factory f, out Car car1, out _);
InitFakeDriver ();
// disable activation
tree.ObjectActivationButton = null;
object activated = null;
bool called = false;
// register for the event
tree.ObjectActivated += (s, e) => {
activated = e.ActivatedObject;
called = true;
};
Assert.False (called);
// double click does nothing because we changed button to null
tree.MouseEvent (new MouseEvent () { Y = 0, Flags = MouseFlags.Button1DoubleClicked });
Assert.False (called);
Assert.Null (activated);
Assert.Null (tree.SelectedObject);
Application.Shutdown ();
}
[Fact]
public void ObjectActivationButton_RightClick ()
{
var tree = CreateTree (out Factory f, out Car car1, out _);
InitFakeDriver ();
tree.ObjectActivationButton = MouseFlags.Button2Clicked;
tree.ExpandAll ();
object activated = null;
bool called = false;
// register for the event
tree.ObjectActivated += (s, e) => {
activated = e.ActivatedObject;
called = true;
};
Assert.False (called);
// double click does nothing because we changed button binding to right click
tree.MouseEvent (new MouseEvent () { Y = 1, Flags = MouseFlags.Button1DoubleClicked });
Assert.Null (activated);
Assert.False (called);
tree.MouseEvent (new MouseEvent () { Y = 1, Flags = MouseFlags.Button2Clicked });
Assert.True (called);
Assert.Same (car1, activated);
Assert.Same (car1, tree.SelectedObject);
Application.Shutdown ();
}
/// <summary>
/// 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)"/>
/// </summary>
[Fact]
public void RefreshObject_EqualityTest ()
{
var obj1 = new EqualityTestObject () { Name = "Bob", Age = 1 };
var obj2 = new EqualityTestObject () { Name = "Bob", Age = 2 }; ;
string root = "root";
var tree = new TreeView<object> ();
tree.TreeBuilder = new DelegateTreeBuilder<object> ((s) => ReferenceEquals (s, root) ? new object [] { obj1 } : null);
tree.AddObject (root);
// Tree is not expanded so the root has no children yet
Assert.Empty (tree.GetChildren (root));
tree.Expand (root);
// now that the tree is expanded we should get our child returned
Assert.Equal (1, tree.GetChildren (root).Count (child => ReferenceEquals (obj1, child)));
// change the getter to return an Equal object (but not the same reference - obj2)
tree.TreeBuilder = new DelegateTreeBuilder<object> ((s) => ReferenceEquals (s, root) ? new object [] { obj2 } : null);
// tree has cached the knowledge of what children the root has so won't know about the change (we still get obj1)
Assert.Equal (1, tree.GetChildren (root).Count (child => ReferenceEquals (obj1, child)));
// now that we refresh the root we should get the new child reference (obj2)
tree.RefreshObject (root);
Assert.Equal (1, tree.GetChildren (root).Count (child => ReferenceEquals (obj2, child)));
}
[Fact, AutoInitShutdown]
public void TestGetObjectOnRow ()
{
var tv = new TreeView { Width = 20, Height = 10 };
tv.BeginInit (); tv.EndInit ();
var n1 = new TreeNode ("normal");
var n1_1 = new TreeNode ("pink");
var n1_2 = new TreeNode ("normal");
n1.Children.Add (n1_1);
n1.Children.Add (n1_2);
var n2 = new TreeNode ("pink");
tv.AddObject (n1);
tv.AddObject (n2);
tv.Expand (n1);
tv.ColorScheme = new ColorScheme ();
tv.LayoutSubviews ();
tv.Draw ();
TestHelpers.AssertDriverContentsAre (
@"├-normal
│ ├─pink
│ └─normal
└─pink
", output);
Assert.Same (n1, tv.GetObjectOnRow (0));
Assert.Same (n1_1, tv.GetObjectOnRow (1));
Assert.Same (n1_2, tv.GetObjectOnRow (2));
Assert.Same (n2, tv.GetObjectOnRow (3));
Assert.Null (tv.GetObjectOnRow (4));
tv.Collapse (n1);
tv.Draw ();
TestHelpers.AssertDriverContentsAre (
@"├+normal
└─pink
", output);
Assert.Same (n1, tv.GetObjectOnRow (0));
Assert.Same (n2, tv.GetObjectOnRow (1));
Assert.Null (tv.GetObjectOnRow (2));
Assert.Null (tv.GetObjectOnRow (3));
Assert.Null (tv.GetObjectOnRow (4));
}
[Fact, AutoInitShutdown]
public void TestGetObjectRow ()
{
var tv = new TreeView { Width = 20, Height = 10 };
var n1 = new TreeNode ("normal");
var n1_1 = new TreeNode ("pink");
var n1_2 = new TreeNode ("normal");
n1.Children.Add (n1_1);
n1.Children.Add (n1_2);
var n2 = new TreeNode ("pink");
tv.AddObject (n1);
tv.AddObject (n2);
tv.Expand (n1);
tv.ColorScheme = new ColorScheme ();
tv.LayoutSubviews ();
tv.Draw ();
TestHelpers.AssertDriverContentsAre (
@"├-normal
│ ├─pink
│ └─normal
└─pink
", output);
Assert.Equal (0, tv.GetObjectRow (n1));
Assert.Equal (1, tv.GetObjectRow (n1_1));
Assert.Equal (2, tv.GetObjectRow (n1_2));
Assert.Equal (3, tv.GetObjectRow (n2));
tv.Collapse (n1);
tv.LayoutSubviews ();
tv.Draw ();
TestHelpers.AssertDriverContentsAre (
@"├+normal
└─pink
", output);
Assert.Equal (0, tv.GetObjectRow (n1));
Assert.Null (tv.GetObjectRow (n1_1));
Assert.Null (tv.GetObjectRow (n1_2));
Assert.Equal (1, tv.GetObjectRow (n2));
// scroll down 1
tv.ScrollOffsetVertical = 1;
tv.LayoutSubviews ();
tv.Draw ();
TestHelpers.AssertDriverContentsAre (
@"└─pink
", output);
Assert.Equal (-1, tv.GetObjectRow (n1));
Assert.Null (tv.GetObjectRow (n1_1));
Assert.Null (tv.GetObjectRow (n1_2));
Assert.Equal (0, tv.GetObjectRow (n2));
}
[Fact, AutoInitShutdown]
public void TestTreeViewColor ()
{
var tv = new TreeView { Width = 20, Height = 10 };
var n1 = new TreeNode ("normal");
var n1_1 = new TreeNode ("pink");
var n1_2 = new TreeNode ("normal");
n1.Children.Add (n1_1);
n1.Children.Add (n1_2);
var n2 = new TreeNode ("pink");
tv.AddObject (n1);
tv.AddObject (n2);
tv.Expand (n1);
tv.ColorScheme = new ColorScheme ();
Application.Top.Add(tv);
Application.Begin(Application.Top);
// create a new color scheme
var pink = new Attribute (Color.Magenta, Color.Black);
var hotpink = new Attribute (Color.BrightMagenta, Color.Black);
// Normal drawing of the tree view
TestHelpers.AssertDriverContentsAre (@"
├-normal
│ ├─pink
│ └─normal
└─pink
", output);
// Should all be the same color
TestHelpers.AssertDriverColorsAre (@"
0000000000
0000000000
0000000000
0000000000
", driver: Application.Driver,
new [] { tv.ColorScheme.Normal, pink });
var pinkScheme = new ColorScheme {
Normal = pink,
Focus = hotpink
};
// and a delegate that uses the pink color scheme
// for nodes "pink"
tv.ColorGetter = (n) => n.Text.Equals ("pink") ? pinkScheme : null;
// redraw now that the custom color
// delegate is registered
Application.Refresh();
// Same text
TestHelpers.AssertDriverContentsAre (@"
├-normal
│ ├─pink
│ └─normal
└─pink
", output);
// but now the item (only not lines) appear
// in pink when they are the word "pink"
TestHelpers.AssertDriverColorsAre (@"
00000000
00001111
0000000000
001111
", driver: Application.Driver,
new [] { tv.ColorScheme.Normal, pink });
}
[Fact, AutoInitShutdown]
public void TestBottomlessTreeView_MaxDepth_5 ()
{
var tv = new TreeView<string> () { Width = 20, Height = 10 };
tv.TreeBuilder = new DelegateTreeBuilder<string> (
(s) => new [] { (int.Parse (s) + 1).ToString () }
);
tv.AddObject ("1");
tv.ColorScheme = new ColorScheme ();
tv.LayoutSubviews ();
tv.Draw ();
// Nothing expanded
TestHelpers.AssertDriverContentsAre (
@"└+1
", output);
tv.MaxDepth = 5;
tv.ExpandAll ();
tv.Draw ();
// Normal drawing of the tree view
TestHelpers.AssertDriverContentsAre (
@"
└-1
└-2
└-3
└-4
└-5
└─6
", output);
Assert.False (tv.CanExpand ("6"));
Assert.False (tv.IsExpanded ("6"));
tv.Collapse ("6");
Assert.False (tv.CanExpand ("6"));
Assert.False (tv.IsExpanded ("6"));
tv.Collapse ("5");
Assert.True (tv.CanExpand ("5"));
Assert.False (tv.IsExpanded ("5"));
tv.Draw ();
// Normal drawing of the tree view
TestHelpers.AssertDriverContentsAre (
@"
└-1
└-2
└-3
└-4
└+5
", output);
}
[Fact, AutoInitShutdown]
public void TestBottomlessTreeView_MaxDepth_3 ()
{
var tv = new TreeView<string> () { Width = 20, Height = 10 };
tv.TreeBuilder = new DelegateTreeBuilder<string> (
(s) => new [] { (int.Parse (s) + 1).ToString () }
);
tv.AddObject ("1");
tv.ColorScheme = new ColorScheme ();
tv.LayoutSubviews ();
tv.Draw ();
// Nothing expanded
TestHelpers.AssertDriverContentsAre (
@"└+1
", output);
tv.MaxDepth = 3;
tv.ExpandAll ();
tv.Draw ();
// Normal drawing of the tree view
TestHelpers.AssertDriverContentsAre (
@"
└-1
└-2
└-3
└─4
", output);
}
[Fact, AutoInitShutdown]
public void TestTreeView_DrawLineEvent ()
{
var tv = new TreeView { Width = 20, Height = 10 };
var eventArgs = new List<DrawTreeViewLineEventArgs<ITreeNode>> ();
tv.DrawLine += (s, e) => {
eventArgs.Add (e);
};
var n1 = new TreeNode ("root one");
var n1_1 = new TreeNode ("leaf 1");
var n1_2 = new TreeNode ("leaf 2");
n1.Children.Add (n1_1);
n1.Children.Add (n1_2);
var n2 = new TreeNode ("root two");
tv.AddObject (n1);
tv.AddObject (n2);
tv.Expand (n1);
tv.ColorScheme = new ColorScheme ();
tv.LayoutSubviews ();
tv.Draw ();
// Normal drawing of the tree view
TestHelpers.AssertDriverContentsAre (
@"
├-root one
│ ├─leaf 1
│ └─leaf 2
└─root two
", output);
Assert.Equal (4, eventArgs.Count ());
Assert.Equal (0, eventArgs [0].Y);
Assert.Equal (1, eventArgs [1].Y);
Assert.Equal (2, eventArgs [2].Y);
Assert.Equal (3, eventArgs [3].Y);
Assert.All (eventArgs, ea => Assert.Equal (ea.Tree, tv));
Assert.All (eventArgs, ea => Assert.False (ea.Handled));
Assert.Equal ("├-root one", eventArgs [0].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ());
Assert.Equal ("│ ├─leaf 1", eventArgs [1].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ());
Assert.Equal ("│ └─leaf 2", eventArgs [2].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ());
Assert.Equal ("└─root two", eventArgs [3].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ());
Assert.Equal (1, eventArgs [0].IndexOfExpandCollapseSymbol);
Assert.Equal (3, eventArgs [1].IndexOfExpandCollapseSymbol);
Assert.Equal (3, eventArgs [2].IndexOfExpandCollapseSymbol);
Assert.Equal (1, eventArgs [3].IndexOfExpandCollapseSymbol);
Assert.Equal (2, eventArgs [0].IndexOfModelText);
Assert.Equal (4, eventArgs [1].IndexOfModelText);
Assert.Equal (4, eventArgs [2].IndexOfModelText);
Assert.Equal (2, eventArgs [3].IndexOfModelText);
Assert.Equal ("root one", eventArgs [0].Model.Text);
Assert.Equal ("leaf 1", eventArgs [1].Model.Text);
Assert.Equal ("leaf 2", eventArgs [2].Model.Text);
Assert.Equal ("root two", eventArgs [3].Model.Text);
}
[Fact, AutoInitShutdown]
public void TestTreeView_DrawLineEvent_WithScrolling ()
{
var tv = new TreeView { Width = 20, Height = 10 };
var eventArgs = new List<DrawTreeViewLineEventArgs<ITreeNode>> ();
tv.DrawLine += (s, e) => {
eventArgs.Add (e);
};
tv.ScrollOffsetHorizontal = 3;
tv.ScrollOffsetVertical = 1;
var n1 = new TreeNode ("root one");
var n1_1 = new TreeNode ("leaf 1");
var n1_2 = new TreeNode ("leaf 2");
n1.Children.Add (n1_1);
n1.Children.Add (n1_2);
var n2 = new TreeNode ("root two");
tv.AddObject (n1);
tv.AddObject (n2);
tv.Expand (n1);
tv.ColorScheme = new ColorScheme ();
tv.LayoutSubviews ();
tv.Draw ();
// Normal drawing of the tree view
TestHelpers.AssertDriverContentsAre (
@"
─leaf 1
─leaf 2
oot two
", output);
Assert.Equal (3, eventArgs.Count ());
Assert.Equal (0, eventArgs [0].Y);
Assert.Equal (1, eventArgs [1].Y);
Assert.Equal (2, eventArgs [2].Y);
Assert.All (eventArgs, ea => Assert.Equal (ea.Tree, tv));
Assert.All (eventArgs, ea => Assert.False (ea.Handled));
Assert.Equal ("─leaf 1", eventArgs [0].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ());
Assert.Equal ("─leaf 2", eventArgs [1].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ());
Assert.Equal ("oot two", eventArgs [2].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ());
Assert.Equal (0, eventArgs [0].IndexOfExpandCollapseSymbol);
Assert.Equal (0, eventArgs [1].IndexOfExpandCollapseSymbol);
Assert.Null (eventArgs [2].IndexOfExpandCollapseSymbol);
Assert.Equal (1, eventArgs [0].IndexOfModelText);
Assert.Equal (1, eventArgs [1].IndexOfModelText);
Assert.Equal (-1, eventArgs [2].IndexOfModelText);
Assert.Equal ("leaf 1", eventArgs [0].Model.Text);
Assert.Equal ("leaf 2", eventArgs [1].Model.Text);
Assert.Equal ("root two", eventArgs [2].Model.Text);
}
[Fact, AutoInitShutdown]
public void TestTreeView_DrawLineEvent_Handled ()
{
var tv = new TreeView { Width = 20, Height = 10 };
tv.DrawLine += (s, e) => {
if(e.Model.Text.Equals("leaf 1")) {
e.Handled = true;
for (int i = 0; i < 10; i++) {
e.Tree.AddRune (i,e.Y,new System.Text.Rune('F'));
}
}
};
var n1 = new TreeNode ("root one");
var n1_1 = new TreeNode ("leaf 1");
var n1_2 = new TreeNode ("leaf 2");
n1.Children.Add (n1_1);
n1.Children.Add (n1_2);
var n2 = new TreeNode ("root two");
tv.AddObject (n1);
tv.AddObject (n2);
tv.Expand (n1);
tv.ColorScheme = new ColorScheme ();
tv.LayoutSubviews ();
tv.Draw ();
// Normal drawing of the tree view
TestHelpers.AssertDriverContentsAre (
@"
├-root one
FFFFFFFFFF
│ └─leaf 2
└─root two
", output);
}
[Fact, AutoInitShutdown]
public void TestTreeView_Filter ()
{
var tv = new TreeView { Width = 20, Height = 10 };
var n1 = new TreeNode ("root one");
var n1_1 = new TreeNode ("leaf 1");
var n1_2 = new TreeNode ("leaf 2");
n1.Children.Add (n1_1);
n1.Children.Add (n1_2);
var n2 = new TreeNode ("root two");
tv.AddObject (n1);
tv.AddObject (n2);
tv.Expand (n1);
tv.ColorScheme = new ColorScheme ();
tv.LayoutSubviews ();
tv.Draw ();
// Normal drawing of the tree view
TestHelpers.AssertDriverContentsAre (
@"
├-root one
│ ├─leaf 1
│ └─leaf 2
└─root two
", output);
var filter = new TreeViewTextFilter<ITreeNode> (tv);
tv.Filter = filter;
// matches nothing
filter.Text = "asdfjhasdf";
tv.Draw ();
// Normal drawing of the tree view
TestHelpers.AssertDriverContentsAre (
@"", output);
// Matches everything
filter.Text = "root";
tv.Draw ();
TestHelpers.AssertDriverContentsAre (
@"
├-root one
│ ├─leaf 1
│ └─leaf 2
└─root two
", output);
// Matches 2 leaf nodes
filter.Text = "leaf";
tv.Draw ();
TestHelpers.AssertDriverContentsAre (
@"
├-root one
│ ├─leaf 1
│ └─leaf 2
", output);
// Matches 1 leaf nodes
filter.Text = "leaf 1";
tv.Draw ();
TestHelpers.AssertDriverContentsAre (
@"
├-root one
│ ├─leaf 1
", output);
}
[Fact, AutoInitShutdown]
public void DesiredCursorVisibility_MultiSelect ()
{
var tv = new TreeView { Width = 20, Height = 10 };
var n1 = new TreeNode ("normal");
var n2 = new TreeNode ("pink");
tv.AddObject (n1);
tv.AddObject (n2);
Application.Top.Add (tv);
Application.Begin (Application.Top);
Assert.True (tv.MultiSelect);
Assert.True (tv.HasFocus);
Assert.Equal (CursorVisibility.Invisible, tv.DesiredCursorVisibility);
tv.SelectAll ();
tv.DesiredCursorVisibility = CursorVisibility.Default;
Application.Refresh ();
Application.Driver.GetCursorVisibility (out CursorVisibility visibility);
Assert.Equal (CursorVisibility.Default, tv.DesiredCursorVisibility);
Assert.Equal (CursorVisibility.Default, visibility);
}
/// <summary>
/// Test object which considers for equality only <see cref="Name"/>
/// </summary>
private class EqualityTestObject {
public string Name { get; set; }
public int Age { get; set; }
public override int GetHashCode ()
{
return Name?.GetHashCode () ?? base.GetHashCode ();
}
public override bool Equals (object obj)
{
return obj is EqualityTestObject eto && Equals (Name, eto.Name);
}
}
private void InitFakeDriver ()
{
var driver = new FakeDriver ();
Application.Init (driver);
driver.Init ();
}
}
}