Files
Terminal.Gui/Tests/UnitTestsParallelizable/Views/ListViewTests.cs
BDisp cd75a20c60 Fixes #4387. Runes should not be used on a cell, but rather should use a single grapheme rendering 1 or 2 columns (#4388)
* Fixes #4382. StringExtensions.GetColumns method should only return the total text width and not the sum of all runes width

* Trying to fix unit test error

* Update StringExtensions.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Resolving merge conflicts

* Prevents Runes throwing if Grapheme is null

* Add unit test to prove that null and empty string doesn't not throws anything.

* Fix unit test failure

* Fix IsValidLocation for wide graphemes

* Add more combining

* Prevent set invalid graphemes

* Fix unit tests

* Grapheme doesn't support invalid code points like lone surrogates

* Fixes more unit tests

* Fix unit test

* Seems all test are fixed now

* Adjust CharMap scenario with graphemes

* Upgrade Wcwidth to version 4.0.0

* Reformat

* Trying fix CheckDefaultState assertion

* Revert "Trying fix CheckDefaultState assertion"

This reverts commit c9b46b796a.

* Forgot to include driver.End in the test

* Reapply "Trying fix CheckDefaultState assertion"

This reverts commit 1060ac9b63.

* Remove ToString

* Fix merge errors

* Change to conditional expression

* Assertion to prove that no exception throws during cell initialization.

* Remove unnecessary assignment

* Remove assignment to end

* Replace string concatenation with 'StringBuilder'.

* Replace more string concatenation with 'StringBuilder'

* Remove redundant call to 'ToString' because Rune cast to a String object.

* Replace foreach loop with Sum linq

---------

Co-authored-by: Tig <tig@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-20 13:45:13 -05:00

1586 lines
48 KiB
C#

#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<string> source = ["apricot", "arm", "bat", "batman", "bates hotel", "candle"];
var lv = new ListView { Source = new ListWrapper<string> (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<string> source = ["apricot", "arm", "bat", "batman", "bates hotel", "candle"];
var lv = new ListView { Source = new ListWrapper<string> (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<string> source = ["apricot", "arm", "bat", "batman", "bates hotel", "candle"];
var lv = new ListView { Source = new ListWrapper<string> (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<string> source = ["apricot", "arm", "bat", "batman", "bates hotel", "candle"];
var lv = new ListView { Source = new ListWrapper<string> (source) };
Mock<ICollectionNavigatorMatcher> matchNone = new ();
matchNone.Setup (m => m.IsCompatibleKey (It.IsAny<Key> ()))
.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<string> (), It.IsAny<object> ()), Times.Never ());
}
[Fact]
public void ListViewCollectionNavigatorMatcher_OverrideMatching ()
{
ObservableCollection<string> source = ["apricot", "arm", "bat", "batman", "bates hotel", "candle"];
var lv = new ListView { Source = new ListWrapper<string> (source) };
Mock<ICollectionNavigatorMatcher> matchNone = new ();
matchNone.Setup (m => m.IsCompatibleKey (It.IsAny<Key> ()))
.Returns (true);
// Match any string starting with b to "candle" (psych!)
matchNone.Setup (m => m.IsMatch (It.IsAny<string> (), It.IsAny<object> ()))
.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<string> (["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<string> (["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<string> { "One", "Two", "Three" }; }
public void Dispose () { throw new NotImplementedException (); }
}
[Fact]
public void KeyBindings_Command ()
{
ObservableCollection<string> source = ["One", "Two", "Three"];
var lv = new ListView { Height = 2, AllowsMarking = true, Source = new ListWrapper<string> (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<string> source = ["One", "Two", "Three"];
var listView = new ListView { Source = new ListWrapper<string> (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<string> source = ["One", "Two", "Three"];
var listView = new ListView { Source = new ListWrapper<string> (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<string> (["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<string> (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<string> (["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<string> (["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<string> 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<string> (["One", "Two", "Three"]) };
Assert.Null (lv.SelectedItem);
Assert.Throws<ArgumentException> (() => 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<string> (["One", "Two"]) };
Assert.NotNull (lv.Source);
lv.SetSource<string> (null);
Assert.NotNull (lv.Source);
lv.Source = null;
Assert.Null (lv.Source);
lv = new () { Source = new ListWrapper<string> (["One", "Two"]) };
Assert.NotNull (lv.Source);
lv.SetSourceAsync<string> (null);
Assert.NotNull (lv.Source);
}
[Fact]
public void SettingEmptyKeybindingThrows ()
{
var lv = new ListView { Source = new ListWrapper<string> (["One", "Two", "Three"]) };
Assert.Throws<ArgumentException> (() => lv.KeyBindings.Add (Key.Space));
}
[Fact]
public void CollectionChanged_Event ()
{
var added = 0;
var removed = 0;
ObservableCollection<string> source = [];
var lv = new ListView { Source = new ListWrapper<string> (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<string> source1 = [];
var lv = new ListView { Source = new ListWrapper<string> (new (source1)) };
lv.CollectionChanged += (sender, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Add)
{
added++;
}
else if (args.Action == NotifyCollectionChangedAction.Remove)
{
removed++;
}
else
{
otherActions++;
}
};
ObservableCollection<string> source2 = [];
lv.Source = new ListWrapper<string> (source2);
ObservableCollection<string> source3 = [];
lv.Source = new ListWrapper<string> (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<string> source1 = [];
var lv = new ListView { Source = new ListWrapper<string> (source1) };
lv.CollectionChanged += (sender, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Add)
{
added++;
}
else if (args.Action == NotifyCollectionChangedAction.Remove)
{
removed++;
}
else
{
otherActions++;
}
};
lv.Source = new ListWrapper<string> (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<string> source1 = [];
ListWrapper<string> lw = new (source1);
lw.CollectionChanged += (sender, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Add)
{
added++;
}
else if (args.Action == NotifyCollectionChangedAction.Remove)
{
removed++;
}
else
{
otherActions++;
}
};
ObservableCollection<string> source2 = [];
lw = new (source2);
ObservableCollection<string> 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<string> source1 = [];
ListWrapper<string> 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<string> source = [];
ListWrapper<string> 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<string> source = [];
var lv = new ListView { Source = new ListWrapper<string> (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<string> 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<string> (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<string> source = [];
for (var i = 0; i < 10; i++)
{
source.Add ($"Item {i}");
}
var lv = new ListView { Width = 10, Height = 5, Source = new ListWrapper<string> (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<string> source = ["First", "Second"];
var lv = new ListView { Width = Dim.Fill (), Height = 1, Source = new ListWrapper<string> (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<string> source = [];
for (var i = 0; i < 5; i++)
{
source.Add ($"Item {i}");
}
var lv = new ListView
{
X = 1,
Source = new ListWrapper<string> (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<string> 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<string> { "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<string> (["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<string> { "One", "Two", "Three" };
var lv = new ListView { Source = new ListWrapper<string> (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);
}
}