#nullable enable using System.Collections; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Text; using Moq; using Terminal.Gui; using UnitTests; using Xunit; using Xunit.Abstractions; // ReSharper disable AccessToModifiedClosure namespace UnitTests_Parallelizable.ViewsTests; public class ListViewTests (ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; [Fact] public void CollectionNavigatorMatcher_KeybindingsOverrideNavigator () { ObservableCollection source = ["apricot", "arm", "bat", "batman", "bates hotel", "candle"]; var lv = new ListView { Source = new ListWrapper (source) }; lv.SetFocus (); lv.KeyBindings.Add (Key.B, Command.Down); Assert.Null (lv.SelectedItem); // Keys should be consumed to move down the navigation i.e. to apricot Assert.True (lv.NewKeyDownEvent (Key.B)); Assert.Equal (0, lv.SelectedItem); Assert.True (lv.NewKeyDownEvent (Key.B)); Assert.Equal (1, lv.SelectedItem); // There is no keybinding for Key.C so it hits collection navigator i.e. we jump to candle Assert.True (lv.NewKeyDownEvent (Key.C)); Assert.Equal (5, lv.SelectedItem); } [Fact] public void ListView_CollectionNavigatorMatcher_KeybindingsOverrideNavigator () { ObservableCollection source = ["apricot", "arm", "bat", "batman", "bates hotel", "candle"]; var lv = new ListView { Source = new ListWrapper (source) }; lv.SetFocus (); lv.KeyBindings.Add (Key.B, Command.Down); Assert.Null (lv.SelectedItem); // Keys should be consumed to move down the navigation i.e. to apricot Assert.True (lv.NewKeyDownEvent (Key.B)); Assert.Equal (0, lv.SelectedItem); Assert.True (lv.NewKeyDownEvent (Key.B)); Assert.Equal (1, lv.SelectedItem); // There is no keybinding for Key.C so it hits collection navigator i.e. we jump to candle Assert.True (lv.NewKeyDownEvent (Key.C)); Assert.Equal (5, lv.SelectedItem); } [Fact] public void ListViewCollectionNavigatorMatcher_DefaultBehaviour () { ObservableCollection source = ["apricot", "arm", "bat", "batman", "bates hotel", "candle"]; var lv = new ListView { Source = new ListWrapper (source) }; // Keys are consumed during navigation Assert.True (lv.NewKeyDownEvent (Key.B)); Assert.True (lv.NewKeyDownEvent (Key.A)); Assert.True (lv.NewKeyDownEvent (Key.T)); Assert.Equal ("bat", (string)lv.Source.ToList () [lv.SelectedItem!.Value]!); } [Fact] public void ListViewCollectionNavigatorMatcher_IgnoreKeys () { ObservableCollection source = ["apricot", "arm", "bat", "batman", "bates hotel", "candle"]; var lv = new ListView { Source = new ListWrapper (source) }; Mock matchNone = new (); matchNone.Setup (m => m.IsCompatibleKey (It.IsAny ())) .Returns (false); lv.KeystrokeNavigator.Matcher = matchNone.Object; // Keys are ignored because IsCompatibleKey returned false i.e. don't use these keys for navigation Assert.False (lv.NewKeyDownEvent (Key.B)); Assert.False (lv.NewKeyDownEvent (Key.A)); Assert.False (lv.NewKeyDownEvent (Key.T)); // assert IsMatch never called matchNone.Verify (m => m.IsMatch (It.IsAny (), It.IsAny ()), Times.Never ()); } [Fact] public void ListViewCollectionNavigatorMatcher_OverrideMatching () { ObservableCollection source = ["apricot", "arm", "bat", "batman", "bates hotel", "candle"]; var lv = new ListView { Source = new ListWrapper (source) }; Mock matchNone = new (); matchNone.Setup (m => m.IsCompatibleKey (It.IsAny ())) .Returns (true); // Match any string starting with b to "candle" (psych!) matchNone.Setup (m => m.IsMatch (It.IsAny (), It.IsAny ())) .Returns ((string s, object key) => s.StartsWith ('B') && key?.ToString () == "candle"); lv.KeystrokeNavigator.Matcher = matchNone.Object; // Keys are consumed during navigation Assert.True (lv.NewKeyDownEvent (Key.B)); Assert.Equal (5, lv.SelectedItem); Assert.True (lv.NewKeyDownEvent (Key.A)); Assert.Equal (5, lv.SelectedItem); Assert.True (lv.NewKeyDownEvent (Key.T)); Assert.Equal (5, lv.SelectedItem); Assert.Equal ("candle", (string)lv.Source.ToList () [lv.SelectedItem!.Value]!); } #region ListView Tests (from ListViewTests.cs - parallelizable) [Fact] public void Constructors_Defaults () { var lv = new ListView (); Assert.Null (lv.Source); Assert.True (lv.CanFocus); Assert.Null (lv.SelectedItem); Assert.False (lv.AllowsMultipleSelection); lv = new () { Source = new ListWrapper (["One", "Two", "Three"]) }; Assert.NotNull (lv.Source); Assert.Null (lv.SelectedItem); lv = new () { Source = new NewListDataSource () }; Assert.NotNull (lv.Source); Assert.Null (lv.SelectedItem); lv = new () { Y = 1, Width = 10, Height = 20, Source = new ListWrapper (["One", "Two", "Three"]) }; Assert.NotNull (lv.Source); Assert.Null (lv.SelectedItem); Assert.Equal (new (0, 1, 10, 20), lv.Frame); lv = new () { Y = 1, Width = 10, Height = 20, Source = new NewListDataSource () }; Assert.NotNull (lv.Source); Assert.Null (lv.SelectedItem); Assert.Equal (new (0, 1, 10, 20), lv.Frame); } private class NewListDataSource : IListDataSource { #pragma warning disable CS0067 public event NotifyCollectionChangedEventHandler? CollectionChanged; #pragma warning restore CS0067 public int Count => 0; public int Length => 0; public bool SuspendCollectionChangedEvent { get => throw new NotImplementedException (); set => throw new NotImplementedException (); } public bool IsMarked (int item) { throw new NotImplementedException (); } public void Render ( ListView container, bool selected, int item, int col, int line, int width, int viewportX = 0 ) { throw new NotImplementedException (); } public void SetMark (int item, bool value) { throw new NotImplementedException (); } public IList ToList () { return new List { "One", "Two", "Three" }; } public void Dispose () { throw new NotImplementedException (); } } [Fact] public void KeyBindings_Command () { ObservableCollection source = ["One", "Two", "Three"]; var lv = new ListView { Height = 2, AllowsMarking = true, Source = new ListWrapper (source) }; lv.BeginInit (); lv.EndInit (); Assert.Null (lv.SelectedItem); Assert.True (lv.NewKeyDownEvent (Key.CursorDown)); Assert.Equal (0, lv.SelectedItem); Assert.True (lv.NewKeyDownEvent (Key.CursorUp)); Assert.Equal (0, lv.SelectedItem); Assert.True (lv.NewKeyDownEvent (Key.PageDown)); Assert.Equal (2, lv.SelectedItem); Assert.Equal (2, lv.TopItem); Assert.True (lv.NewKeyDownEvent (Key.PageUp)); Assert.Equal (0, lv.SelectedItem); Assert.Equal (0, lv.TopItem); Assert.False (lv.Source.IsMarked (lv.SelectedItem!.Value)); Assert.True (lv.NewKeyDownEvent (Key.Space)); Assert.True (lv.Source.IsMarked (lv.SelectedItem!.Value)); var opened = false; lv.OpenSelectedItem += (s, _) => opened = true; Assert.True (lv.NewKeyDownEvent (Key.Enter)); Assert.True (opened); Assert.True (lv.NewKeyDownEvent (Key.End)); Assert.Equal (2, lv.SelectedItem); Assert.True (lv.NewKeyDownEvent (Key.Home)); Assert.Equal (0, lv.SelectedItem); } [Fact] public void HotKey_Command_SetsFocus () { var view = new ListView (); view.CanFocus = true; Assert.False (view.HasFocus); view.InvokeCommand (Command.HotKey); Assert.True (view.HasFocus); } [Fact] public void HotKey_Command_Does_Not_Accept () { var listView = new ListView (); var accepted = false; listView.Accepting += OnAccepted; listView.InvokeCommand (Command.HotKey); Assert.False (accepted); return; void OnAccepted (object? sender, CommandEventArgs e) { accepted = true; } } [Fact] public void Accept_Command_Accepts_and_Opens_Selected_Item () { ObservableCollection source = ["One", "Two", "Three"]; var listView = new ListView { Source = new ListWrapper (source) }; listView.SelectedItem = 0; var accepted = false; var opened = false; var selectedValue = string.Empty; listView.Accepting += Accepted; listView.OpenSelectedItem += OpenSelectedItem; listView.InvokeCommand (Command.Accept); Assert.True (accepted); Assert.True (opened); Assert.Equal (source [0], selectedValue); return; void OpenSelectedItem (object? sender, ListViewItemEventArgs e) { opened = true; selectedValue = e.Value!.ToString (); } void Accepted (object? sender, CommandEventArgs e) { accepted = true; } } [Fact] public void Accept_Cancel_Event_Prevents_OpenSelectedItem () { ObservableCollection source = ["One", "Two", "Three"]; var listView = new ListView { Source = new ListWrapper (source) }; listView.SelectedItem = 0; var accepted = false; var opened = false; var selectedValue = string.Empty; listView.Accepting += Accepted; listView.OpenSelectedItem += OpenSelectedItem; listView.InvokeCommand (Command.Accept); Assert.True (accepted); Assert.False (opened); Assert.Equal (string.Empty, selectedValue); return; void OpenSelectedItem (object? sender, ListViewItemEventArgs e) { opened = true; selectedValue = e.Value!.ToString (); } void Accepted (object? sender, CommandEventArgs e) { accepted = true; e.Handled = true; } } [Fact] public void ListViewProcessKeyReturnValue_WithMultipleCommands () { var lv = new ListView { Source = new ListWrapper (["One", "Two", "Three", "Four"]) }; Assert.NotNull (lv.Source); // first item should be deselected by default Assert.Null (lv.SelectedItem); // bind shift down to move down twice in control lv.KeyBindings.Add (Key.CursorDown.WithShift, Command.Down, Command.Down); Key ev = Key.CursorDown.WithShift; Assert.True (lv.NewKeyDownEvent (ev), "The first time we move down 2 it should be possible"); // After moving down twice from null we should be at 'Two' Assert.Equal (1, lv.SelectedItem); // clear the items lv.SetSource (null); // Press key combo again - return should be false this time as none of the Commands are allowable Assert.False (lv.NewKeyDownEvent (ev), "We cannot move down so will not respond to this"); } [Fact] public void AllowsMarking_True_SpaceWithShift_SelectsThenDown_SingleSelection () { var lv = new ListView { Source = new ListWrapper (["One", "Two", "Three"]) }; lv.AllowsMarking = true; lv.AllowsMultipleSelection = false; Assert.NotNull (lv.Source); // first item should be deselected by default Assert.Null (lv.SelectedItem); // nothing is ticked Assert.False (lv.Source.IsMarked (0)); Assert.False (lv.Source.IsMarked (1)); Assert.False (lv.Source.IsMarked (2)); // view should indicate that it has accepted and consumed the event Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift)); // first item should now be selected Assert.Equal (0, lv.SelectedItem); // none of the items should be ticked Assert.False (lv.Source.IsMarked (0)); Assert.False (lv.Source.IsMarked (1)); Assert.False (lv.Source.IsMarked (2)); // Press key combo again Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift)); // second item should now be selected Assert.Equal (1, lv.SelectedItem); // first item only should be ticked Assert.True (lv.Source.IsMarked (0)); Assert.False (lv.Source.IsMarked (1)); Assert.False (lv.Source.IsMarked (2)); // Press key combo again Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift)); Assert.Equal (2, lv.SelectedItem); Assert.False (lv.Source.IsMarked (0)); Assert.True (lv.Source.IsMarked (1)); Assert.False (lv.Source.IsMarked (2)); // Press key combo again Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift)); Assert.Equal (2, lv.SelectedItem); // cannot move down any further Assert.False (lv.Source.IsMarked (0)); Assert.False (lv.Source.IsMarked (1)); Assert.True (lv.Source.IsMarked (2)); // but can toggle marked // Press key combo again Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift)); Assert.Equal (2, lv.SelectedItem); // cannot move down any further Assert.False (lv.Source.IsMarked (0)); Assert.False (lv.Source.IsMarked (1)); Assert.False (lv.Source.IsMarked (2)); // untoggle toggle marked } [Fact] public void AllowsMarking_True_SpaceWithShift_SelectsThenDown_MultipleSelection () { var lv = new ListView { Source = new ListWrapper (["One", "Two", "Three"]) }; lv.AllowsMarking = true; lv.AllowsMultipleSelection = true; Assert.NotNull (lv.Source); // first item should be deselected by default Assert.Null (lv.SelectedItem); // nothing is ticked Assert.False (lv.Source.IsMarked (0)); Assert.False (lv.Source.IsMarked (1)); Assert.False (lv.Source.IsMarked (2)); // view should indicate that it has accepted and consumed the event Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift)); // first item should now be selected Assert.Equal (0, lv.SelectedItem); // none of the items should be ticked Assert.False (lv.Source.IsMarked (0)); Assert.False (lv.Source.IsMarked (1)); Assert.False (lv.Source.IsMarked (2)); // Press key combo again Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift)); // second item should now be selected Assert.Equal (1, lv.SelectedItem); // first item only should be ticked Assert.True (lv.Source.IsMarked (0)); Assert.False (lv.Source.IsMarked (1)); Assert.False (lv.Source.IsMarked (2)); // Press key combo again Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift)); Assert.Equal (2, lv.SelectedItem); Assert.True (lv.Source.IsMarked (0)); Assert.True (lv.Source.IsMarked (1)); Assert.False (lv.Source.IsMarked (2)); // Press key combo again Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift)); Assert.Equal (2, lv.SelectedItem); // cannot move down any further Assert.True (lv.Source.IsMarked (0)); Assert.True (lv.Source.IsMarked (1)); Assert.True (lv.Source.IsMarked (2)); // but can toggle marked // Press key combo again Assert.True (lv.NewKeyDownEvent (Key.Space.WithShift)); Assert.Equal (2, lv.SelectedItem); // cannot move down any further Assert.True (lv.Source.IsMarked (0)); Assert.True (lv.Source.IsMarked (1)); Assert.False (lv.Source.IsMarked (2)); // untoggle toggle marked } [Fact] public void ListWrapper_StartsWith () { ListWrapper lw = new (["One", "Two", "Three"]); Assert.Equal (1, lw.StartsWith ("t")); Assert.Equal (1, lw.StartsWith ("tw")); Assert.Equal (2, lw.StartsWith ("th")); Assert.Equal (1, lw.StartsWith ("T")); Assert.Equal (1, lw.StartsWith ("TW")); Assert.Equal (2, lw.StartsWith ("TH")); lw = new (["One", "Two", "Three"]); Assert.Equal (1, lw.StartsWith ("t")); Assert.Equal (1, lw.StartsWith ("tw")); Assert.Equal (2, lw.StartsWith ("th")); Assert.Equal (1, lw.StartsWith ("T")); Assert.Equal (1, lw.StartsWith ("TW")); Assert.Equal (2, lw.StartsWith ("TH")); } [Fact] public void OnEnter_Does_Not_Throw_Exception () { var lv = new ListView (); var top = new View (); top.Add (lv); Exception exception = Record.Exception (() => lv.SetFocus ()); Assert.Null (exception); } [Fact] public void SelectedItem_Get_Set () { var lv = new ListView { Source = new ListWrapper (["One", "Two", "Three"]) }; Assert.Null (lv.SelectedItem); Assert.Throws (() => lv.SelectedItem = 3); Exception exception = Record.Exception (() => lv.SelectedItem = null); Assert.Null (exception); } [Fact] public void SetSource_Preserves_ListWrapper_Instance_If_Not_Null () { var lv = new ListView { Source = new ListWrapper (["One", "Two"]) }; Assert.NotNull (lv.Source); lv.SetSource (null); Assert.NotNull (lv.Source); lv.Source = null; Assert.Null (lv.Source); lv = new () { Source = new ListWrapper (["One", "Two"]) }; Assert.NotNull (lv.Source); lv.SetSourceAsync (null); Assert.NotNull (lv.Source); } [Fact] public void SettingEmptyKeybindingThrows () { var lv = new ListView { Source = new ListWrapper (["One", "Two", "Three"]) }; Assert.Throws (() => lv.KeyBindings.Add (Key.Space)); } [Fact] public void CollectionChanged_Event () { var added = 0; var removed = 0; ObservableCollection source = []; var lv = new ListView { Source = new ListWrapper (source) }; lv.CollectionChanged += (sender, args) => { if (args.Action == NotifyCollectionChangedAction.Add) { added++; } else if (args.Action == NotifyCollectionChangedAction.Remove) { removed++; } }; for (var i = 0; i < 3; i++) { source.Add ($"Item{i}"); } Assert.Equal (3, added); Assert.Equal (0, removed); added = 0; for (var i = 0; i < 3; i++) { source.Remove (source [0]); } Assert.Equal (0, added); Assert.Equal (3, removed); Assert.Empty (source); } [Fact] public void CollectionChanged_Event_Is_Only_Subscribed_Once () { var added = 0; var removed = 0; var otherActions = 0; IList source1 = []; var lv = new ListView { Source = new ListWrapper (new (source1)) }; lv.CollectionChanged += (sender, args) => { if (args.Action == NotifyCollectionChangedAction.Add) { added++; } else if (args.Action == NotifyCollectionChangedAction.Remove) { removed++; } else { otherActions++; } }; ObservableCollection source2 = []; lv.Source = new ListWrapper (source2); ObservableCollection source3 = []; lv.Source = new ListWrapper (source3); Assert.Equal (0, added); Assert.Equal (0, removed); Assert.Equal (0, otherActions); for (var i = 0; i < 3; i++) { source1.Add ($"Item{i}"); source2.Add ($"Item{i}"); source3.Add ($"Item{i}"); } Assert.Equal (3, added); Assert.Equal (0, removed); Assert.Equal (0, otherActions); added = 0; for (var i = 0; i < 3; i++) { source1.Remove (source1 [0]); source2.Remove (source2 [0]); source3.Remove (source3 [0]); } Assert.Equal (0, added); Assert.Equal (3, removed); Assert.Equal (0, otherActions); Assert.Empty (source1); Assert.Empty (source2); Assert.Empty (source3); } [Fact] public void CollectionChanged_Event_UnSubscribe_Previous_If_New_Is_Null () { var added = 0; var removed = 0; var otherActions = 0; ObservableCollection source1 = []; var lv = new ListView { Source = new ListWrapper (source1) }; lv.CollectionChanged += (sender, args) => { if (args.Action == NotifyCollectionChangedAction.Add) { added++; } else if (args.Action == NotifyCollectionChangedAction.Remove) { removed++; } else { otherActions++; } }; lv.Source = new ListWrapper (null); Assert.Equal (0, added); Assert.Equal (0, removed); Assert.Equal (0, otherActions); for (var i = 0; i < 3; i++) { source1.Add ($"Item{i}"); } Assert.Equal (0, added); Assert.Equal (0, removed); Assert.Equal (0, otherActions); for (var i = 0; i < 3; i++) { source1.Remove (source1 [0]); } Assert.Equal (0, added); Assert.Equal (0, removed); Assert.Equal (0, otherActions); Assert.Empty (source1); } [Fact] public void ListWrapper_CollectionChanged_Event_Is_Only_Subscribed_Once () { var added = 0; var removed = 0; var otherActions = 0; ObservableCollection source1 = []; ListWrapper lw = new (source1); lw.CollectionChanged += (sender, args) => { if (args.Action == NotifyCollectionChangedAction.Add) { added++; } else if (args.Action == NotifyCollectionChangedAction.Remove) { removed++; } else { otherActions++; } }; ObservableCollection source2 = []; lw = new (source2); ObservableCollection source3 = []; lw = new (source3); Assert.Equal (0, added); Assert.Equal (0, removed); Assert.Equal (0, otherActions); for (var i = 0; i < 3; i++) { source1.Add ($"Item{i}"); source2.Add ($"Item{i}"); source3.Add ($"Item{i}"); } Assert.Equal (3, added); Assert.Equal (0, removed); Assert.Equal (0, otherActions); added = 0; for (var i = 0; i < 3; i++) { source1.Remove (source1 [0]); source2.Remove (source2 [0]); source3.Remove (source3 [0]); } Assert.Equal (0, added); Assert.Equal (3, removed); Assert.Equal (0, otherActions); Assert.Empty (source1); Assert.Empty (source2); Assert.Empty (source3); } [Fact] public void ListWrapper_CollectionChanged_Event_UnSubscribe_Previous_Is_Disposed () { var added = 0; var removed = 0; var otherActions = 0; ObservableCollection source1 = []; ListWrapper lw = new (source1); lw.CollectionChanged += Lw_CollectionChanged; lw.Dispose (); lw = new (null); Assert.Equal (0, lw.Count); Assert.Equal (0, added); Assert.Equal (0, removed); Assert.Equal (0, otherActions); for (var i = 0; i < 3; i++) { source1.Add ($"Item{i}"); } Assert.Equal (0, added); Assert.Equal (0, removed); Assert.Equal (0, otherActions); for (var i = 0; i < 3; i++) { source1.Remove (source1 [0]); } Assert.Equal (0, added); Assert.Equal (0, removed); Assert.Equal (0, otherActions); Assert.Empty (source1); void Lw_CollectionChanged (object? sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add) { added++; } } } [Fact] public void ListWrapper_SuspendCollectionChangedEvent_ResumeSuspendCollectionChangedEvent_Tests () { var added = 0; ObservableCollection source = []; ListWrapper lw = new (source); lw.CollectionChanged += Lw_CollectionChanged; lw.SuspendCollectionChangedEvent = true; for (var i = 0; i < 3; i++) { source.Add ($"Item{i}"); } Assert.Equal (0, added); Assert.Equal (3, lw.Count); Assert.Equal (3, source.Count); lw.SuspendCollectionChangedEvent = false; for (var i = 3; i < 6; i++) { source.Add ($"Item{i}"); } Assert.Equal (3, added); Assert.Equal (6, lw.Count); Assert.Equal (6, source.Count); void Lw_CollectionChanged (object? sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add) { added++; } } } [Fact] public void ListView_SuspendCollectionChangedEvent_ResumeSuspendCollectionChangedEvent_Tests () { var added = 0; ObservableCollection source = []; var lv = new ListView { Source = new ListWrapper (source) }; lv.CollectionChanged += Lw_CollectionChanged; lv.SuspendCollectionChangedEvent (); for (var i = 0; i < 3; i++) { source.Add ($"Item{i}"); } Assert.Equal (0, added); Assert.Equal (3, lv.Source.Count); Assert.Equal (3, source.Count); lv.ResumeSuspendCollectionChangedEvent (); for (var i = 3; i < 6; i++) { source.Add ($"Item{i}"); } Assert.Equal (3, added); Assert.Equal (6, lv.Source.Count); Assert.Equal (6, source.Count); void Lw_CollectionChanged (object? sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add) { added++; } } } #endregion [Fact] public void Clicking_On_Border_Is_Ignored () { IApplication? app = Application.Create (); app.Init ("fake"); var selected = ""; var lv = new ListView { Height = 5, Width = 7, BorderStyle = LineStyle.Single }; lv.SetSource (["One", "Two", "Three", "Four"]); lv.SelectedItemChanged += (s, e) => selected = e.Value!.ToString (); var top = new Toplevel (); top.Add (lv); app.Begin (top); //AutoInitShutdownAttribute.RunIteration (); Assert.Equal (new (1), lv.Border!.Thickness); Assert.Null (lv.SelectedItem); Assert.Equal ("", lv.Text); DriverAssert.AssertDriverContentsWithFrameAre ( @" ┌─────┐ │One │ │Two │ │Three│ └─────┘", _output, app?.Driver); app?.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Clicked }); Assert.Equal ("", selected); Assert.Null (lv.SelectedItem); app?.Mouse.RaiseMouseEvent ( new () { ScreenPosition = new (1, 1), Flags = MouseFlags.Button1Clicked }); Assert.Equal ("One", selected); Assert.Equal (0, lv.SelectedItem); app?.Mouse.RaiseMouseEvent ( new () { ScreenPosition = new (1, 2), Flags = MouseFlags.Button1Clicked }); Assert.Equal ("Two", selected); Assert.Equal (1, lv.SelectedItem); app?.Mouse.RaiseMouseEvent ( new () { ScreenPosition = new (1, 3), Flags = MouseFlags.Button1Clicked }); Assert.Equal ("Three", selected); Assert.Equal (2, lv.SelectedItem); app?.Mouse.RaiseMouseEvent ( new () { ScreenPosition = new (1, 4), Flags = MouseFlags.Button1Clicked }); Assert.Equal ("Three", selected); Assert.Equal (2, lv.SelectedItem); top.Dispose (); app?.Shutdown (); } [Fact] public void Ensures_Visibility_SelectedItem_On_MoveDown_And_MoveUp () { IApplication? app = Application.Create (); app.Init ("fake"); app.Driver?.SetScreenSize (12, 12); ObservableCollection source = []; for (var i = 0; i < 20; i++) { source.Add ($"Line{i}"); } var lv = new ListView { Width = Dim.Fill (), Height = Dim.Fill (), Source = new ListWrapper (source) }; var win = new Window (); win.Add (lv); var top = new Toplevel (); top.Add (win); app.Begin (top); Assert.Null (lv.SelectedItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" ┌──────────┐ │Line0 │ │Line1 │ │Line2 │ │Line3 │ │Line4 │ │Line5 │ │Line6 │ │Line7 │ │Line8 │ │Line9 │ └──────────┘", _output, app.Driver ); Assert.True (lv.ScrollVertical (10)); app.LayoutAndDraw (); Assert.Null (lv.SelectedItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" ┌──────────┐ │Line10 │ │Line11 │ │Line12 │ │Line13 │ │Line14 │ │Line15 │ │Line16 │ │Line17 │ │Line18 │ │Line19 │ └──────────┘", _output, app.Driver ); Assert.True (lv.MoveDown ()); app.LayoutAndDraw (); Assert.Equal (0, lv.SelectedItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" ┌──────────┐ │Line0 │ │Line1 │ │Line2 │ │Line3 │ │Line4 │ │Line5 │ │Line6 │ │Line7 │ │Line8 │ │Line9 │ └──────────┘", _output, app.Driver ); Assert.True (lv.MoveEnd ()); app.LayoutAndDraw (); Assert.Equal (19, lv.SelectedItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" ┌──────────┐ │Line10 │ │Line11 │ │Line12 │ │Line13 │ │Line14 │ │Line15 │ │Line16 │ │Line17 │ │Line18 │ │Line19 │ └──────────┘", _output, app.Driver ); Assert.True (lv.ScrollVertical (-20)); app.LayoutAndDraw (); Assert.Equal (19, lv.SelectedItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" ┌──────────┐ │Line0 │ │Line1 │ │Line2 │ │Line3 │ │Line4 │ │Line5 │ │Line6 │ │Line7 │ │Line8 │ │Line9 │ └──────────┘", _output, app.Driver ); Assert.True (lv.MoveDown ()); app.LayoutAndDraw (); Assert.Equal (19, lv.SelectedItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" ┌──────────┐ │Line10 │ │Line11 │ │Line12 │ │Line13 │ │Line14 │ │Line15 │ │Line16 │ │Line17 │ │Line18 │ │Line19 │ └──────────┘", _output, app.Driver ); Assert.True (lv.ScrollVertical (-20)); app.LayoutAndDraw (); Assert.Equal (19, lv.SelectedItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" ┌──────────┐ │Line0 │ │Line1 │ │Line2 │ │Line3 │ │Line4 │ │Line5 │ │Line6 │ │Line7 │ │Line8 │ │Line9 │ └──────────┘", _output, app.Driver ); Assert.True (lv.MoveDown ()); app.LayoutAndDraw (); Assert.Equal (19, lv.SelectedItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" ┌──────────┐ │Line10 │ │Line11 │ │Line12 │ │Line13 │ │Line14 │ │Line15 │ │Line16 │ │Line17 │ │Line18 │ │Line19 │ └──────────┘", _output, app.Driver ); Assert.True (lv.MoveHome ()); app.LayoutAndDraw (); Assert.Equal (0, lv.SelectedItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" ┌──────────┐ │Line0 │ │Line1 │ │Line2 │ │Line3 │ │Line4 │ │Line5 │ │Line6 │ │Line7 │ │Line8 │ │Line9 │ └──────────┘", _output, app.Driver ); Assert.True (lv.ScrollVertical (20)); app.LayoutAndDraw (); Assert.Equal (0, lv.SelectedItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" ┌──────────┐ │Line19 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──────────┘", _output, app.Driver ); Assert.True (lv.MoveUp ()); app.LayoutAndDraw (); Assert.Equal (0, lv.SelectedItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" ┌──────────┐ │Line0 │ │Line1 │ │Line2 │ │Line3 │ │Line4 │ │Line5 │ │Line6 │ │Line7 │ │Line8 │ │Line9 │ └──────────┘", _output, app.Driver ); top.Dispose (); app.Shutdown (); } [Fact] public void EnsureSelectedItemVisible_SelectedItem () { IApplication? app = Application.Create (); app.Init ("fake"); app.Driver?.SetScreenSize (12, 12); ObservableCollection source = []; for (var i = 0; i < 10; i++) { source.Add ($"Item {i}"); } var lv = new ListView { Width = 10, Height = 5, Source = new ListWrapper (source) }; var top = new Toplevel (); top.Add (lv); app.Begin (top); DriverAssert.AssertDriverContentsWithFrameAre ( @" Item 0 Item 1 Item 2 Item 3 Item 4", _output, app.Driver ); // EnsureSelectedItemVisible is auto enabled on the OnSelectedChanged lv.SelectedItem = 6; app.LayoutAndDraw (); DriverAssert.AssertDriverContentsWithFrameAre ( @" Item 2 Item 3 Item 4 Item 5 Item 6", _output, app.Driver ); top.Dispose (); app.Shutdown (); } [Fact] public void EnsureSelectedItemVisible_Top () { IApplication? app = Application.Create (); app.Init ("fake"); IDriver? driver = app.Driver; driver?.SetScreenSize (8, 2); ObservableCollection source = ["First", "Second"]; var lv = new ListView { Width = Dim.Fill (), Height = 1, Source = new ListWrapper (source) }; lv.SelectedItem = 1; var top = new Toplevel (); top.Add (lv); app.Begin (top); Assert.Equal ("Second ", GetContents (0)); Assert.Equal (new (' ', 7), GetContents (1)); lv.MoveUp (); lv.Draw (); Assert.Equal ("First ", GetContents (0)); Assert.Equal (new (' ', 7), GetContents (1)); string GetContents (int line) { var sb = new StringBuilder (); for (var i = 0; i < 7; i++) { sb.Append ((app?.Driver?.Contents!) [line, i].Grapheme); } return sb.ToString (); } top.Dispose (); app.Shutdown (); } [Fact] public void LeftItem_TopItem_Tests () { IApplication? app = Application.Create (); app.Init ("fake"); app.Driver?.SetScreenSize (12, 12); ObservableCollection source = []; for (var i = 0; i < 5; i++) { source.Add ($"Item {i}"); } var lv = new ListView { X = 1, Source = new ListWrapper (source) }; lv.Height = lv.Source.Count; lv.Width = lv.MaxLength; var top = new Toplevel (); top.Add (lv); app.Begin (top); DriverAssert.AssertDriverContentsWithFrameAre ( @" Item 0 Item 1 Item 2 Item 3 Item 4", _output, app.Driver); lv.LeftItem = 1; lv.TopItem = 1; app.LayoutAndDraw (); DriverAssert.AssertDriverContentsWithFrameAre ( @" tem 1 tem 2 tem 3 tem 4", _output, app.Driver); top.Dispose (); app.Shutdown (); } [Fact] public void RowRender_Event () { IApplication? app = Application.Create (); app.Init ("fake"); var rendered = false; ObservableCollection source = ["one", "two", "three"]; var lv = new ListView { Width = Dim.Fill (), Height = Dim.Fill () }; lv.RowRender += (s, _) => rendered = true; var top = new Toplevel (); top.Add (lv); app.Begin (top); Assert.False (rendered); lv.SetSource (source); lv.Draw (); Assert.True (rendered); top.Dispose (); app.Shutdown (); } [Fact] public void Vertical_ScrollBar_Hides_And_Shows_As_Needed () { IApplication? app = Application.Create (); app.Init ("fake"); var lv = new ListView { Width = 10, Height = 3 }; lv.VerticalScrollBar.AutoShow = true; lv.SetSource (["One", "Two", "Three", "Four", "Five"]); var top = new Toplevel (); top.Add (lv); app.Begin (top); Assert.True (lv.VerticalScrollBar.Visible); DriverAssert.AssertDriverContentsWithFrameAre ( @" One ▲ Two █ Three ▼", _output, app?.Driver); lv.Height = 5; app?.LayoutAndDraw (); Assert.False (lv.VerticalScrollBar.Visible); DriverAssert.AssertDriverContentsWithFrameAre ( @" One Two Three Four Five ", _output, app?.Driver); top.Dispose (); app?.Shutdown (); } [Fact] public void Mouse_Wheel_Scrolls () { IApplication? app = Application.Create (); app.Init ("fake"); var lv = new ListView { Width = 10, Height = 3, }; lv.SetSource (["One", "Two", "Three", "Four", "Five"]); var top = new Toplevel (); top.Add (lv); app.Begin (top); // Initially, we are at the top. Assert.Equal (0, lv.TopItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" One Two Three", _output, app?.Driver); // Scroll down app?.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.WheeledDown }); app?.LayoutAndDraw (); Assert.Equal (1, lv.TopItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" Two Three Four ", _output, app?.Driver); // Scroll up app?.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.WheeledUp }); app?.LayoutAndDraw (); Assert.Equal (0, lv.TopItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" One Two Three", _output, app?.Driver); top.Dispose (); app?.Shutdown (); } [Fact] public void SelectedItem_With_Source_Null_Does_Nothing () { var lv = new ListView (); Assert.Null (lv.Source); // should not throw lv.SelectedItem = 0; Assert.Null (lv.SelectedItem); } [Fact] public void Horizontal_Scroll () { IApplication? app = Application.Create (); app.Init ("fake"); var lv = new ListView { Width = 10, Height = 3, }; lv.SetSource (["One", "Two", "Three - long", "Four", "Five"]); var top = new Toplevel (); top.Add (lv); app.Begin (top); Assert.Equal (0, lv.LeftItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" One Two Three - lo", _output, app?.Driver); lv.ScrollHorizontal (1); app?.LayoutAndDraw (); Assert.Equal (1, lv.LeftItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" ne wo hree - lon", _output, app?.Driver); // Scroll right with mouse app?.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.WheeledRight }); app?.LayoutAndDraw (); Assert.Equal (2, lv.LeftItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" e o ree - long", _output, app?.Driver); // Scroll left with mouse app?.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.WheeledLeft }); app?.LayoutAndDraw (); Assert.Equal (1, lv.LeftItem); DriverAssert.AssertDriverContentsWithFrameAre ( @" ne wo hree - lon", _output, app?.Driver); top.Dispose (); app?.Shutdown (); } [Fact] public async Task SetSourceAsync_SetsSource () { var lv = new ListView (); var source = new ObservableCollection { "One", "Two", "Three" }; await lv.SetSourceAsync (source); Assert.NotNull (lv.Source); Assert.Equal (3, lv.Source.Count); } [Fact] public void AllowsMultipleSelection_Set_To_False_Unmarks_All_But_Selected () { var lv = new ListView { AllowsMarking = true, AllowsMultipleSelection = true }; var source = new ListWrapper (["One", "Two", "Three"]); lv.Source = source; lv.SelectedItem = 0; source.SetMark (0, true); source.SetMark (1, true); source.SetMark (2, true); Assert.True (source.IsMarked (0)); Assert.True (source.IsMarked (1)); Assert.True (source.IsMarked (2)); lv.AllowsMultipleSelection = false; Assert.True (source.IsMarked (0)); Assert.False (source.IsMarked (1)); Assert.False (source.IsMarked (2)); } [Fact] public void Source_CollectionChanged_Remove () { var source = new ObservableCollection { "One", "Two", "Three" }; var lv = new ListView { Source = new ListWrapper (source) }; lv.SelectedItem = 2; Assert.Equal (2, lv.SelectedItem); Assert.Equal (3, lv.Source.Count); source.RemoveAt (0); Assert.Equal (2, lv.Source.Count); Assert.Equal (1, lv.SelectedItem); source.RemoveAt (1); Assert.Equal (1, lv.Source.Count); Assert.Equal (0, lv.SelectedItem); } }