diff --git a/Terminal.Gui/Views/TableView/TreeTableSource.cs b/Terminal.Gui/Views/TableView/TreeTableSource.cs index 9125c0c95..06b304634 100644 --- a/Terminal.Gui/Views/TableView/TreeTableSource.cs +++ b/Terminal.Gui/Views/TableView/TreeTableSource.cs @@ -87,8 +87,8 @@ public class TreeTableSource : IEnumerableTableSource, IDisposable where T Branch branch = RowToBranch (row); // Everything on line before the expansion run and branch text - Rune [] prefix = branch.GetLinePrefix (Application.Driver).ToArray (); - Rune expansion = branch.GetExpandableSymbol (Application.Driver); + Rune [] prefix = branch.GetLinePrefix ().ToArray (); + Rune expansion = branch.GetExpandableSymbol (); string lineBody = _tree.AspectGetter (branch.Model) ?? ""; var sb = new StringBuilder (); diff --git a/Terminal.Gui/Views/TreeView/Branch.cs b/Terminal.Gui/Views/TreeView/Branch.cs index e7a5eb4ca..4c348eaff 100644 --- a/Terminal.Gui/Views/TreeView/Branch.cs +++ b/Terminal.Gui/Views/TreeView/Branch.cs @@ -1,8 +1,10 @@ -namespace Terminal.Gui; +#nullable enable + +namespace Terminal.Gui; internal class Branch where T : class { - private readonly TreeView tree; + private readonly TreeView _tree; /// /// Declares a new branch of in which the users object is @@ -11,9 +13,9 @@ internal class Branch where T : class /// The UI control in which the branch resides. /// Pass null for root level branches, otherwise pass the parent. /// The user's object that should be displayed. - public Branch (TreeView tree, Branch parentBranchIfAny, T model) + public Branch (TreeView tree, Branch? parentBranchIfAny, T model) { - this.tree = tree; + _tree = tree; Model = model; if (parentBranchIfAny is { }) @@ -27,7 +29,7 @@ internal class Branch where T : class /// The children of the current branch. This is null until the first call to to avoid /// enumerating the entire underlying hierarchy. /// - public Dictionary> ChildBranches { get; set; } + public List>? ChildBranches { get; set; } /// The depth of the current branch. Depth of 0 indicates root level branches. public int Depth { get; } @@ -39,7 +41,7 @@ internal class Branch where T : class public T Model { get; private set; } /// The parent or null if it is a root. - public Branch Parent { get; } + public Branch? Parent { get; } /// /// Returns true if the current branch can be expanded according to the or cached @@ -52,13 +54,13 @@ internal class Branch where T : class if (ChildBranches is null) { //if there is a rapid method for determining whether there are children - if (tree.TreeBuilder.SupportsCanExpand) + if (_tree.TreeBuilder.SupportsCanExpand) { - return tree.TreeBuilder.CanExpand (Model); + return _tree.TreeBuilder.CanExpand (Model); } //there is no way of knowing whether we can expand without fetching the children - FetchChildren (); + ChildBranches = FetchChildren (); } //we fetched or already know the children, so return whether we have any @@ -69,32 +71,30 @@ internal class Branch where T : class public void Collapse () { IsExpanded = false; } /// Renders the current on the specified line . - /// - /// /// /// - public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, int availableWidth) + public virtual void Draw (int y, int availableWidth) { List cells = new (); int? indexOfExpandCollapseSymbol = null; int indexOfModelText; // true if the current line of the tree is the selected one and control has focus - bool isSelected = tree.IsSelected (Model); + bool isSelected = _tree.IsSelected (Model); Attribute textColor = - isSelected ? tree.HasFocus ? colorScheme.Focus : colorScheme.HotNormal : colorScheme.Normal; - Attribute symbolColor = tree.Style.HighlightModelTextOnly ? colorScheme.Normal : textColor; + isSelected ? _tree.HasFocus ? _tree.GetFocusColor () : _tree.GetHotNormalColor () : _tree.GetNormalColor (); + Attribute symbolColor = _tree.Style.HighlightModelTextOnly ? _tree.GetNormalColor () : textColor; // Everything on line before the expansion run and branch text - Rune [] prefix = GetLinePrefix (driver).ToArray (); - Rune expansion = GetExpandableSymbol (driver); - string lineBody = tree.AspectGetter (Model) ?? ""; + Rune [] prefix = GetLinePrefix ().ToArray (); + Rune expansion = GetExpandableSymbol (); + string lineBody = _tree.AspectGetter (Model) ?? ""; - tree.Move (0, y); + _tree.Move (0, y); // if we have scrolled to the right then bits of the prefix will have disappeared off the screen - int toSkip = tree.ScrollOffsetHorizontal; + int toSkip = _tree.ScrollOffsetHorizontal; Attribute attr = symbolColor; // Draw the line prefix (all parallel lanes or whitespace and an expand/collapse/leaf symbol) @@ -112,20 +112,20 @@ internal class Branch where T : class } // pick color for expanded symbol - if (tree.Style.ColorExpandSymbol || tree.Style.InvertExpandSymbolColors) + if (_tree.Style.ColorExpandSymbol || _tree.Style.InvertExpandSymbolColors) { - Attribute color = symbolColor; + Attribute color; - if (tree.Style.ColorExpandSymbol) + if (_tree.Style.ColorExpandSymbol) { if (isSelected) { - color = tree.Style.HighlightModelTextOnly ? colorScheme.HotNormal : - tree.HasFocus ? tree.ColorScheme.HotFocus : tree.ColorScheme.HotNormal; + color = _tree.Style.HighlightModelTextOnly ? _tree.GetHotNormalColor () : + _tree.HasFocus ? _tree.GetHotFocusColor () : _tree.GetHotNormalColor (); } else { - color = tree.ColorScheme.HotNormal; + color = _tree.GetHotNormalColor (); } } else @@ -133,9 +133,9 @@ internal class Branch where T : class color = symbolColor; } - if (tree.Style.InvertExpandSymbolColors) + if (_tree.Style.InvertExpandSymbolColors) { - color = new Attribute (color.Background, color.Foreground); + color = new (color.Background, color.Foreground); } attr = color; @@ -177,10 +177,10 @@ internal class Branch where T : class if (lineBody.EnumerateRunes ().Sum (l => l.GetColumns ()) > availableWidth) { // remaining space is zero and truncate the line - lineBody = new string ( - lineBody.TakeWhile (c => (availableWidth -= ((Rune)c).GetColumns ()) >= 0) - .ToArray () - ); + lineBody = new ( + lineBody.TakeWhile (c => (availableWidth -= ((Rune)c).GetColumns ()) >= 0) + .ToArray () + ); availableWidth = 0; } else @@ -194,9 +194,9 @@ internal class Branch where T : class Attribute modelColor = textColor; // if custom color delegate invoke it - if (tree.ColorGetter is { }) + if (_tree.ColorGetter is { }) { - ColorScheme modelScheme = tree.ColorGetter (Model); + ColorScheme modelScheme = _tree.ColorGetter (Model); // if custom color scheme is defined for this Model if (modelScheme is { }) @@ -206,12 +206,12 @@ internal class Branch where T : class } else { - modelColor = new Attribute (); + modelColor = new (); } } attr = modelColor; - cells.AddRange (lineBody.Select (r => NewCell (attr, new Rune (r)))); + cells.AddRange (lineBody.Select (r => NewCell (attr, new (r)))); if (availableWidth > 0) { @@ -219,7 +219,7 @@ internal class Branch where T : class cells.AddRange ( Enumerable.Repeat ( - NewCell (attr, new Rune (' ')), + NewCell (attr, new (' ')), availableWidth ) ); @@ -230,32 +230,29 @@ internal class Branch where T : class Model = Model, Y = y, Cells = cells, - Tree = tree, + Tree = _tree, IndexOfExpandCollapseSymbol = indexOfExpandCollapseSymbol, IndexOfModelText = indexOfModelText }; - tree.OnDrawLine (e); + _tree.OnDrawLine (e); - if (!e.Handled && driver != null) + if (!e.Handled) { foreach (Cell cell in cells) { - driver.SetAttribute ((Attribute)cell.Attribute!); - driver.AddRune (cell.Rune); + _tree.SetAttribute ((Attribute)cell.Attribute!); + _tree.AddRune (cell.Rune); } } - driver?.SetAttribute (colorScheme.Normal); + _tree.SetAttribute (_tree.GetNormalColor()); } /// Expands the current branch if possible. public void Expand () { - if (ChildBranches is null) - { - FetchChildren (); - } + ChildBranches ??= FetchChildren (); if (ChildBranches.Any ()) { @@ -264,45 +261,44 @@ internal class Branch where T : class } /// Fetch the children of this branch. This method populates . - public virtual void FetchChildren () + private List> FetchChildren () { - if (tree.TreeBuilder is null) + if (_tree.TreeBuilder is null) { - return; + return []; } IEnumerable children; - if (Depth >= tree.MaxDepth) + if (Depth >= _tree.MaxDepth) { - children = Enumerable.Empty (); + children = []; } else { - children = tree.TreeBuilder.GetChildren (Model) ?? Enumerable.Empty (); + children = _tree.TreeBuilder.GetChildren (Model) ?? []; } - ChildBranches = children.ToDictionary (k => k, val => new Branch (tree, this, val)); + return children.Select (o => new Branch (_tree, this, o)).ToList (); } /// /// Returns an appropriate symbol for displaying next to the string representation of the /// object to indicate whether it or not (or it is a leaf). /// - /// /// - public Rune GetExpandableSymbol (IConsoleDriver driver) + public Rune GetExpandableSymbol () { - Rune leafSymbol = tree.Style.ShowBranchLines ? Glyphs.HLine : (Rune)' '; + Rune leafSymbol = _tree.Style.ShowBranchLines ? Glyphs.HLine : (Rune)' '; if (IsExpanded) { - return tree.Style.CollapseableSymbol ?? leafSymbol; + return _tree.Style.CollapseableSymbol ?? leafSymbol; } if (CanExpand ()) { - return tree.Style.ExpandableSymbol ?? leafSymbol; + return _tree.Style.ExpandableSymbol ?? leafSymbol; } return leafSymbol; @@ -313,10 +309,10 @@ internal class Branch where T : class /// line body). /// /// - public virtual int GetWidth (IConsoleDriver driver) + public virtual int GetWidth () { return - GetLinePrefix (driver).Sum (r => r.GetColumns ()) + GetExpandableSymbol (driver).GetColumns () + (tree.AspectGetter (Model) ?? "").Length; + GetLinePrefix ().Sum (r => r.GetColumns ()) + GetExpandableSymbol ().GetColumns () + (_tree.AspectGetter (Model) ?? "").Length; } /// Refreshes cached knowledge in this branch e.g. what children an object has. @@ -333,41 +329,46 @@ internal class Branch where T : class //if we don't know about any children yet just use the normal method if (ChildBranches is null) { - FetchChildren (); + ChildBranches = FetchChildren (); } else { // we already knew about some children so preserve the state of the old children // first gather the new Children - IEnumerable newChildren = tree.TreeBuilder?.GetChildren (Model) ?? Enumerable.Empty (); + T [] newChildren = _tree.TreeBuilder?.GetChildren (Model).ToArray () ?? []; // Children who no longer appear need to go - foreach (T toRemove in ChildBranches.Keys.Except (newChildren).ToArray ()) + foreach (Branch toRemove in ChildBranches.Where (b => !newChildren.Contains (b.Model)).ToArray ()) { ChildBranches.Remove (toRemove); //also if the user has this node selected (its disappearing) so lets change selection to us (the parent object) to be helpful - if (Equals (tree.SelectedObject, toRemove)) + if (Equals (_tree.SelectedObject, toRemove.Model)) { - tree.SelectedObject = Model; + _tree.SelectedObject = Model; } } // New children need to be added foreach (T newChild in newChildren) { + Branch? existingBranch = ChildBranches.FirstOrDefault (b => b.Model.Equals (newChild)); + // If we don't know about the child, yet we need a new branch - if (!ChildBranches.ContainsKey (newChild)) + if (existingBranch == null) { - ChildBranches.Add (newChild, new Branch (tree, this, newChild)); + ChildBranches.Add (new (_tree, this, newChild)); } else { //we already have this object but update the reference anyway in case Equality match but the references are new - ChildBranches [newChild].Model = newChild; + existingBranch.Model = newChild; } } + + // Order the list + ChildBranches = ChildBranches.OrderBy (b => newChildren.IndexOf (b.Model)).ToList (); } } @@ -381,9 +382,9 @@ internal class Branch where T : class if (ChildBranches is { }) { - foreach (KeyValuePair> child in ChildBranches) + foreach (Branch child in ChildBranches) { - child.Value.CollapseAll (); + child.CollapseAll (); } } } @@ -395,9 +396,9 @@ internal class Branch where T : class if (ChildBranches is { }) { - foreach (KeyValuePair> child in ChildBranches) + foreach (Branch child in ChildBranches) { - child.Value.ExpandAll (); + child.ExpandAll (); } } } @@ -406,16 +407,15 @@ internal class Branch where T : class /// Gets all characters to render prior to the current branches line. This includes indentation whitespace and /// any tree branches (if enabled). /// - /// /// - internal IEnumerable GetLinePrefix (IConsoleDriver driver) + internal IEnumerable GetLinePrefix () { // If not showing line branches or this is a root object. - if (!tree.Style.ShowBranchLines) + if (!_tree.Style.ShowBranchLines) { for (var i = 0; i < Depth; i++) { - yield return new Rune (' '); + yield return new (' '); } yield break; @@ -426,14 +426,14 @@ internal class Branch where T : class { if (cur.IsLast ()) { - yield return new Rune (' '); + yield return new (' '); } else { yield return Glyphs.VLine; } - yield return new Rune (' '); + yield return new (' '); } if (IsLast ()) @@ -462,15 +462,15 @@ internal class Branch where T : class } // if we could theoretically expand - if (!IsExpanded && tree.Style.ExpandableSymbol != default (Rune?)) + if (!IsExpanded && _tree.Style.ExpandableSymbol != default (Rune?)) { - return x == GetLinePrefix (driver).Count (); + return x == GetLinePrefix ().Count (); } // if we could theoretically collapse - if (IsExpanded && tree.Style.CollapseableSymbol != default (Rune?)) + if (IsExpanded && _tree.Style.CollapseableSymbol != default (Rune?)) { - return x == GetLinePrefix (driver).Count (); + return x == GetLinePrefix ().Count (); } return false; @@ -487,9 +487,9 @@ internal class Branch where T : class if (IsExpanded) { // if we are expanded we need to update the visible children - foreach (KeyValuePair> child in ChildBranches) + foreach (Branch child in ChildBranches) { - child.Value.Rebuild (); + child.Rebuild (); } } else @@ -504,7 +504,7 @@ internal class Branch where T : class /// private IEnumerable> GetParentBranches () { - Branch cur = Parent; + Branch? cur = Parent; while (cur is { }) { @@ -523,11 +523,13 @@ internal class Branch where T : class { if (Parent is null) { - return this == tree.roots.Values.LastOrDefault (); + return this == _tree.roots.Values.LastOrDefault (); } - return Parent.ChildBranches.Values.LastOrDefault () == this; + Parent.ChildBranches ??= Parent.FetchChildren (); + + return Parent.ChildBranches.LastOrDefault () == this; } - private static Cell NewCell (Attribute attr, Rune r) { return new Cell { Rune = r, Attribute = new (attr) }; } + private static Cell NewCell (Attribute attr, Rune r) { return new() { Rune = r, Attribute = new (attr) }; } } diff --git a/Terminal.Gui/Views/TreeView/TreeView.cs b/Terminal.Gui/Views/TreeView/TreeView.cs index 4ff0a3c89..af2245efa 100644 --- a/Terminal.Gui/Views/TreeView/TreeView.cs +++ b/Terminal.Gui/Views/TreeView/TreeView.cs @@ -847,7 +847,7 @@ public class TreeView : View, ITreeView where T : class return new T [0]; } - return branch.ChildBranches?.Values?.Select (b => b.Model)?.ToArray () ?? new T [0]; + return branch.ChildBranches?.Select (b => b.Model)?.ToArray () ?? new T [0]; } /// Returns the maximum width line in the tree including prefix and expansion symbols. @@ -879,10 +879,10 @@ public class TreeView : View, ITreeView where T : class return 0; } - return map.Skip (ScrollOffsetVertical).Take (Viewport.Height).Max (b => b.GetWidth (Driver)); + return map.Skip (ScrollOffsetVertical).Take (Viewport.Height).Max (b => b.GetWidth ()); } - return map.Max (b => b.GetWidth (Driver)); + return map.Max (b => b.GetWidth ()); } /// @@ -1171,7 +1171,7 @@ public class TreeView : View, ITreeView where T : class if (idxToRender < map.Count) { // Render the line - map.ElementAt (idxToRender).Draw (Driver, ColorScheme, line, Viewport.Width); + map.ElementAt (idxToRender).Draw (line, Viewport.Width); } else { @@ -1488,7 +1488,7 @@ public class TreeView : View, ITreeView where T : class if (currentBranch.IsExpanded) { - foreach (Branch subBranch in currentBranch.ChildBranches.Values) + foreach (Branch subBranch in currentBranch.ChildBranches) { foreach (Branch sub in AddToLineMap (subBranch, weMatch, out bool childMatch)) { diff --git a/Terminal.sln b/Terminal.sln index e15d8f360..b255d9459 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -65,6 +65,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.Parallelizable", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerminalGuiFluentTesting", "TerminalGuiFluentTesting\TerminalGuiFluentTesting.csproj", "{2DBA7BDC-17AE-474B-A507-00807D087607}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerminalGuiFluentTesting.Xunit", "TerminalGuiFluentTesting.Xunit\TerminalGuiFluentTesting.Xunit.csproj", "{231B9723-10F3-46DB-8EAE-50C0C0375AD3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -123,6 +125,10 @@ Global {2DBA7BDC-17AE-474B-A507-00807D087607}.Debug|Any CPU.Build.0 = Debug|Any CPU {2DBA7BDC-17AE-474B-A507-00807D087607}.Release|Any CPU.ActiveCfg = Release|Any CPU {2DBA7BDC-17AE-474B-A507-00807D087607}.Release|Any CPU.Build.0 = Release|Any CPU + {231B9723-10F3-46DB-8EAE-50C0C0375AD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {231B9723-10F3-46DB-8EAE-50C0C0375AD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {231B9723-10F3-46DB-8EAE-50C0C0375AD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {231B9723-10F3-46DB-8EAE-50C0C0375AD3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TerminalGuiFluentTesting.Xunit/TerminalGuiFluentTesting.Xunit.csproj b/TerminalGuiFluentTesting.Xunit/TerminalGuiFluentTesting.Xunit.csproj new file mode 100644 index 000000000..03c8b09d9 --- /dev/null +++ b/TerminalGuiFluentTesting.Xunit/TerminalGuiFluentTesting.Xunit.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/TerminalGuiFluentTesting.Xunit/XunitContextExtensions.cs b/TerminalGuiFluentTesting.Xunit/XunitContextExtensions.cs new file mode 100644 index 000000000..53f81e37b --- /dev/null +++ b/TerminalGuiFluentTesting.Xunit/XunitContextExtensions.cs @@ -0,0 +1,25 @@ +using Xunit; + +namespace TerminalGuiFluentTesting; + +public static class XunitContextExtensions +{ + public static GuiTestContext AssertTrue (this GuiTestContext context, bool? condition) + { + context.Then ( + () => + { + Assert.True (condition); + }); + return context; + } + public static GuiTestContext AssertEqual (this GuiTestContext context, object? expected, object? actual) + { + context.Then ( + () => + { + Assert.Equal (expected,actual); + }); + return context; + } +} diff --git a/TerminalGuiFluentTesting/GuiTestContext.cs b/TerminalGuiFluentTesting/GuiTestContext.cs index bd3f8d974..1a3274cd3 100644 --- a/TerminalGuiFluentTesting/GuiTestContext.cs +++ b/TerminalGuiFluentTesting/GuiTestContext.cs @@ -243,7 +243,18 @@ public class GuiTestContext : IDisposable /// public GuiTestContext Then (Action doAction) { - doAction (); + try + { + doAction (); + } + catch(Exception) + { + Stop (); + _hardStop.Cancel(); + + throw; + + } return this; } @@ -360,6 +371,7 @@ public class GuiTestContext : IDisposable { SendNetKey (k); } + WaitIteration (); break; default: throw new ArgumentOutOfRangeException (); @@ -550,4 +562,25 @@ public class GuiTestContext : IDisposable WaitIteration (); } + + /// + /// Sets the input focus to the given . + /// Throws if focus did not change due to system + /// constraints e.g. + /// is + /// + /// + /// + /// + public GuiTestContext Focus (View toFocus) + { + toFocus.FocusDeepest (NavigationDirection.Forward, TabBehavior.TabStop); + + if (!toFocus.HasFocus) + { + throw new ArgumentException ("Failed to set focus, FocusDeepest did not result in HasFocus becoming true. Ensure view is added and focusable"); + } + + return WaitIteration (); + } } diff --git a/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs b/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs index 93dff6a86..f79325319 100644 --- a/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs +++ b/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs @@ -1,5 +1,4 @@ -using System.Text; -using Terminal.Gui; +using Terminal.Gui; using TerminalGuiFluentTesting; using Xunit.Abstractions; @@ -9,17 +8,6 @@ public class BasicFluentAssertionTests { private readonly TextWriter _out; - public class TestOutputWriter : TextWriter - { - private readonly ITestOutputHelper _output; - - public TestOutputWriter (ITestOutputHelper output) { _output = output; } - - public override void WriteLine (string? value) { _output.WriteLine (value ?? string.Empty); } - - public override Encoding Encoding => Encoding.UTF8; - } - public BasicFluentAssertionTests (ITestOutputHelper outputHelper) { _out = new TestOutputWriter (outputHelper); } [Theory] diff --git a/Tests/IntegrationTests/FluentTests/TestOutputWriter.cs b/Tests/IntegrationTests/FluentTests/TestOutputWriter.cs new file mode 100644 index 000000000..62e40e5ae --- /dev/null +++ b/Tests/IntegrationTests/FluentTests/TestOutputWriter.cs @@ -0,0 +1,15 @@ +using System.Text; +using Xunit.Abstractions; + +namespace IntegrationTests.FluentTests; + +public class TestOutputWriter : TextWriter +{ + private readonly ITestOutputHelper _output; + + public TestOutputWriter (ITestOutputHelper output) { _output = output; } + + public override void WriteLine (string? value) { _output.WriteLine (value ?? string.Empty); } + + public override Encoding Encoding => Encoding.UTF8; +} diff --git a/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs b/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs new file mode 100644 index 000000000..f73514797 --- /dev/null +++ b/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs @@ -0,0 +1,162 @@ +using Terminal.Gui; +using TerminalGuiFluentTesting; +using Xunit.Abstractions; + +namespace IntegrationTests.FluentTests; + +public class TreeViewFluentTests +{ + private readonly TextWriter _out; + + public TreeViewFluentTests (ITestOutputHelper outputHelper) { _out = new TestOutputWriter (outputHelper); } + + [Theory] + [ClassData (typeof (V2TestDrivers))] + public void TreeView_AllowReOrdering (V2TestDriver d) + { + var tv = new TreeView + { + Width = Dim.Fill (), + Height = Dim.Fill () + }; + + TreeNode car; + TreeNode lorry; + TreeNode bike; + + var root = new TreeNode ("Root") + { + Children = + [ + car = new ("Car"), + lorry = new ("Lorry"), + bike = new ("Bike") + ] + }; + + tv.AddObject (root); + + using GuiTestContext context = + With.A (40, 10, d) + .Add (tv) + .Focus (tv) + .WaitIteration () + .ScreenShot ("Before expanding", _out) + .AssertEqual (root, tv.GetObjectOnRow (0)) + .Then (() => Assert.Null (tv.GetObjectOnRow (1))) + .Right () + .ScreenShot ("After expanding", _out) + .AssertEqual (root, tv.GetObjectOnRow (0)) + .AssertEqual (car, tv.GetObjectOnRow (1)) + .AssertEqual (lorry, tv.GetObjectOnRow (2)) + .AssertEqual (bike, tv.GetObjectOnRow (3)) + .Then ( + () => + { + // Re order + root.Children = [bike, car, lorry]; + tv.RefreshObject (root); + }) + .WaitIteration () + .ScreenShot ("After re-order", _out) + .AssertEqual (root, tv.GetObjectOnRow (0)) + .AssertEqual (bike, tv.GetObjectOnRow (1)) + .AssertEqual (car, tv.GetObjectOnRow (2)) + .AssertEqual (lorry, tv.GetObjectOnRow (3)) + .WriteOutLogs (_out); + + context.Stop (); + } + + [Theory] + [ClassData (typeof (V2TestDrivers))] + public void TreeViewReOrder_PreservesExpansion (V2TestDriver d) + { + var tv = new TreeView + { + Width = Dim.Fill (), + Height = Dim.Fill () + }; + + TreeNode car; + TreeNode lorry; + TreeNode bike; + + TreeNode mrA; + TreeNode mrB; + + TreeNode mrC; + + TreeNode mrD; + TreeNode mrE; + + var root = new TreeNode ("Root") + { + Children = + [ + car = new ("Car") + { + Children = + [ + mrA = new ("Mr A"), + mrB = new ("Mr B") + ] + }, + lorry = new ("Lorry") + { + Children = + [ + mrC = new ("Mr C") + ] + }, + bike = new ("Bike") + { + Children = + [ + mrD = new ("Mr D"), + mrE = new ("Mr E") + ] + } + ] + }; + + tv.AddObject (root); + tv.ExpandAll (); + + using GuiTestContext context = + With.A (40, 13, d) + .Add (tv) + .WaitIteration () + .ScreenShot ("Initial State", _out) + .AssertEqual (root, tv.GetObjectOnRow (0)) + .AssertEqual (car, tv.GetObjectOnRow (1)) + .AssertEqual (mrA, tv.GetObjectOnRow (2)) + .AssertEqual (mrB, tv.GetObjectOnRow (3)) + .AssertEqual (lorry, tv.GetObjectOnRow (4)) + .AssertEqual (mrC, tv.GetObjectOnRow (5)) + .AssertEqual (bike, tv.GetObjectOnRow (6)) + .AssertEqual (mrD, tv.GetObjectOnRow (7)) + .AssertEqual (mrE, tv.GetObjectOnRow (8)) + .Then ( + () => + { + // Re order + root.Children = [bike, car, lorry]; + tv.RefreshObject (root); + }) + .WaitIteration () + .ScreenShot ("After re-order", _out) + .AssertEqual (root, tv.GetObjectOnRow (0)) + .AssertEqual (bike, tv.GetObjectOnRow (1)) + .AssertEqual (mrD, tv.GetObjectOnRow (2)) + .AssertEqual (mrE, tv.GetObjectOnRow (3)) + .AssertEqual (car, tv.GetObjectOnRow (4)) + .AssertEqual (mrA, tv.GetObjectOnRow (5)) + .AssertEqual (mrB, tv.GetObjectOnRow (6)) + .AssertEqual (lorry, tv.GetObjectOnRow (7)) + .AssertEqual (mrC, tv.GetObjectOnRow (8)) + .WriteOutLogs (_out); + + context.Stop (); + } +} diff --git a/Tests/IntegrationTests/IntegrationTests.csproj b/Tests/IntegrationTests/IntegrationTests.csproj index f279e21df..80f067bf7 100644 --- a/Tests/IntegrationTests/IntegrationTests.csproj +++ b/Tests/IntegrationTests/IntegrationTests.csproj @@ -26,6 +26,7 @@ +