mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-27 00:07:58 +01:00
* Add collection search matcher * Fix naming * fix naming * Move FileDialogCollectionNavigator to its own file (no longer private class) Add class diagram for collectionNavigation * Add ICollectionNavigator interface * Move to separate file IListCollectionNavigator * Update class diagram * update class diagram * Add tests for overriding ICollectionNavigatorMatcher * xmldoc and nullability warning fixes * Code Cleanup * Make requested changes to naming and terminology * Move to seperate namespace * Update class diagram and change TreeView to reference the interface not concrete class * Switch to implicit new * highlight that this class also works with tree view * Apply tig patch to ensure keybindings get priority over navigator See: https://github.com/gui-cs/Terminal.Gui/issues/4027#issuecomment-2810020893 * Apply 'keybinding has priority' fix to TreeView too * Apply 'keybindngs priority over navigation' fix to TableView * Remove entire branch for selectively returning false now that it is default when there is a keybinding collision * Make classes internal and remove 'custom' navigator that was configured in UICatlaogToplevel * Change logging in collection navigator from Trace to Debug * Switch to NewKeyDownEvent and directly setting HasFocus * Remove application top dependency * Remove references to application * Remove Application * Move new tests to parallel --------- Co-authored-by: Tig <tig@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Xunit.Abstractions;
|
||||
using Moq;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Terminal.Gui.TextTests;
|
||||
|
||||
@@ -6,7 +7,7 @@ public class CollectionNavigatorTests
|
||||
{
|
||||
private static readonly string [] simpleStrings =
|
||||
{
|
||||
"appricot", // 0
|
||||
"apricot", // 0
|
||||
"arm", // 1
|
||||
"bat", // 2
|
||||
"batman", // 3
|
||||
@@ -20,7 +21,7 @@ public class CollectionNavigatorTests
|
||||
[Fact]
|
||||
public void AtSymbol ()
|
||||
{
|
||||
var strings = new [] { "appricot", "arm", "ta", "@bob", "@bb", "text", "egg", "candle" };
|
||||
var strings = new [] { "apricot", "arm", "ta", "@bob", "@bb", "text", "egg", "candle" };
|
||||
|
||||
var n = new CollectionNavigator (strings);
|
||||
Assert.Equal (3, n.GetNextMatchingItem (0, '@'));
|
||||
@@ -44,19 +45,19 @@ public class CollectionNavigatorTests
|
||||
Assert.Equal (0, n.GetNextMatchingItem (-1, 'a'));
|
||||
Assert.Equal (1, n.GetNextMatchingItem (0, 'a'));
|
||||
|
||||
// if 4 (candle) is selected it should loop back to appricot
|
||||
// if 4 (candle) is selected it should loop back to apricot
|
||||
Assert.Equal (0, n.GetNextMatchingItem (4, 'a'));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Delay ()
|
||||
{
|
||||
var strings = new [] { "$$", "$100.00", "$101.00", "$101.10", "$200.00", "appricot" };
|
||||
var strings = new [] { "$$", "$100.00", "$101.00", "$101.10", "$200.00", "apricot" };
|
||||
var current = 0;
|
||||
var n = new CollectionNavigator (strings);
|
||||
|
||||
// No delay
|
||||
Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a'));
|
||||
Assert.Equal (strings.IndexOf ("apricot"), current = n.GetNextMatchingItem (current, 'a'));
|
||||
Assert.Equal ("a", n.SearchString);
|
||||
Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, '$'));
|
||||
Assert.Equal ("$", n.SearchString);
|
||||
@@ -65,7 +66,7 @@ public class CollectionNavigatorTests
|
||||
|
||||
// Delay
|
||||
Thread.Sleep (n.TypingDelay + 10);
|
||||
Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a'));
|
||||
Assert.Equal (strings.IndexOf ("apricot"), current = n.GetNextMatchingItem (current, 'a'));
|
||||
Assert.Equal ("a", n.SearchString);
|
||||
|
||||
Thread.Sleep (n.TypingDelay + 10);
|
||||
@@ -92,7 +93,7 @@ public class CollectionNavigatorTests
|
||||
[Fact]
|
||||
public void FullText ()
|
||||
{
|
||||
var strings = new [] { "appricot", "arm", "ta", "target", "text", "egg", "candle" };
|
||||
var strings = new [] { "apricot", "arm", "ta", "target", "text", "egg", "candle" };
|
||||
|
||||
var n = new CollectionNavigator (strings);
|
||||
var current = 0;
|
||||
@@ -104,13 +105,13 @@ public class CollectionNavigatorTests
|
||||
// still matches text
|
||||
Assert.Equal (strings.IndexOf ("text"), current = n.GetNextMatchingItem (current, 'x'));
|
||||
|
||||
// nothing starts texa so it should NOT jump to appricot
|
||||
// nothing starts texa so it should NOT jump to apricot
|
||||
Assert.Equal (strings.IndexOf ("text"), current = n.GetNextMatchingItem (current, 'a'));
|
||||
|
||||
Thread.Sleep (n.TypingDelay + 100);
|
||||
|
||||
// nothing starts "texa". Since were past timedelay we DO jump to appricot
|
||||
Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a'));
|
||||
// nothing starts "texa". Since were past timedelay we DO jump to apricot
|
||||
Assert.Equal (strings.IndexOf ("apricot"), current = n.GetNextMatchingItem (current, 'a'));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -128,13 +129,14 @@ public class CollectionNavigatorTests
|
||||
[InlineData (KeyCode.ShiftMask, false)]
|
||||
public void IsCompatibleKey_Does_Not_Allow_Alt_And_Ctrl_Keys (KeyCode keyCode, bool compatible)
|
||||
{
|
||||
Assert.Equal (compatible, CollectionNavigatorBase.IsCompatibleKey (keyCode));
|
||||
var m = new DefaultCollectionNavigatorMatcher ();
|
||||
Assert.Equal (compatible, m.IsCompatibleKey (keyCode));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinimizeMovement_False_ShouldMoveIfMultipleMatches ()
|
||||
{
|
||||
var strings = new [] { "$$", "$100.00", "$101.00", "$101.10", "$200.00", "appricot", "c", "car", "cart" };
|
||||
var strings = new [] { "$$", "$100.00", "$101.00", "$101.10", "$200.00", "apricot", "c", "car", "cart" };
|
||||
var current = 0;
|
||||
var n = new CollectionNavigator (strings);
|
||||
Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$$"));
|
||||
@@ -146,7 +148,7 @@ public class CollectionNavigatorTests
|
||||
Assert.Equal (strings.IndexOf ("$200.00"), current = n.GetNextMatchingItem (current, "$"));
|
||||
|
||||
Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$")); // back to top
|
||||
Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, "a"));
|
||||
Assert.Equal (strings.IndexOf ("apricot"), current = n.GetNextMatchingItem (current, "a"));
|
||||
Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$")); // back to top
|
||||
|
||||
Assert.Equal (strings.IndexOf ("$100.00"), current = n.GetNextMatchingItem (current, "$100.00"));
|
||||
@@ -170,7 +172,7 @@ public class CollectionNavigatorTests
|
||||
[Fact]
|
||||
public void MinimizeMovement_True_ShouldStayOnCurrentIfMultipleMatches ()
|
||||
{
|
||||
var strings = new [] { "$$", "$100.00", "$101.00", "$101.10", "$200.00", "appricot", "c", "car", "cart" };
|
||||
var strings = new [] { "$$", "$100.00", "$101.00", "$101.10", "$200.00", "apricot", "c", "car", "cart" };
|
||||
var current = 0;
|
||||
var n = new CollectionNavigator (strings);
|
||||
Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, "$$", true));
|
||||
@@ -250,10 +252,10 @@ public class CollectionNavigatorTests
|
||||
[Fact]
|
||||
public void Symbols ()
|
||||
{
|
||||
var strings = new [] { "$$", "$100.00", "$101.00", "$101.10", "$200.00", "appricot" };
|
||||
var strings = new [] { "$$", "$100.00", "$101.00", "$101.10", "$200.00", "apricot" };
|
||||
var current = 0;
|
||||
var n = new CollectionNavigator (strings);
|
||||
Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a'));
|
||||
Assert.Equal (strings.IndexOf ("apricot"), current = n.GetNextMatchingItem (current, 'a'));
|
||||
Assert.Equal ("a", n.SearchString);
|
||||
|
||||
Assert.Equal (strings.IndexOf ("$$"), current = n.GetNextMatchingItem (current, '$'));
|
||||
@@ -288,7 +290,7 @@ public class CollectionNavigatorTests
|
||||
[Fact]
|
||||
public void Unicode ()
|
||||
{
|
||||
var strings = new [] { "appricot", "arm", "ta", "丗丙业丞", "丗丙丛", "text", "egg", "candle" };
|
||||
var strings = new [] { "apricot", "arm", "ta", "丗丙业丞", "丗丙丛", "text", "egg", "candle" };
|
||||
|
||||
var n = new CollectionNavigator (strings);
|
||||
var current = 0;
|
||||
@@ -304,19 +306,19 @@ public class CollectionNavigatorTests
|
||||
// so we should move to the new match
|
||||
Assert.Equal (strings.IndexOf ("丗丙丛"), current = n.GetNextMatchingItem (current, '丛'));
|
||||
|
||||
// nothing starts "丗丙丛a". Since were still in the timedelay we do not jump to appricot
|
||||
// nothing starts "丗丙丛a". Since were still in the timedelay we do not jump to apricot
|
||||
Assert.Equal (strings.IndexOf ("丗丙丛"), current = n.GetNextMatchingItem (current, 'a'));
|
||||
|
||||
Thread.Sleep (n.TypingDelay + 100);
|
||||
|
||||
// nothing starts "丗丙丛a". Since were past timedelay we DO jump to appricot
|
||||
Assert.Equal (strings.IndexOf ("appricot"), current = n.GetNextMatchingItem (current, 'a'));
|
||||
// nothing starts "丗丙丛a". Since were past timedelay we DO jump to apricot
|
||||
Assert.Equal (strings.IndexOf ("apricot"), current = n.GetNextMatchingItem (current, 'a'));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Word ()
|
||||
{
|
||||
var strings = new [] { "appricot", "arm", "bat", "batman", "bates hotel", "candle" };
|
||||
var strings = new [] { "apricot", "arm", "bat", "batman", "bates hotel", "candle" };
|
||||
var current = 0;
|
||||
var n = new CollectionNavigator (strings);
|
||||
Assert.Equal (strings.IndexOf ("bat"), current = n.GetNextMatchingItem (current, 'b')); // match bat
|
||||
@@ -338,4 +340,22 @@ public class CollectionNavigatorTests
|
||||
current = n.GetNextMatchingItem (current, ' ')
|
||||
); // match bates hotel
|
||||
}
|
||||
[Fact]
|
||||
public void CustomMatcher_NeverMatches ()
|
||||
{
|
||||
var strings = new [] { "apricot", "arm", "bat", "batman", "bates hotel", "candle" };
|
||||
var current = 0;
|
||||
var n = new CollectionNavigator (strings);
|
||||
|
||||
var matchNone = new Mock<ICollectionNavigatorMatcher> ();
|
||||
|
||||
matchNone.Setup (m => m.IsMatch (It.IsAny<string> (), It.IsAny<object> ()))
|
||||
.Returns (false);
|
||||
|
||||
n.Matcher = matchNone.Object;
|
||||
|
||||
Assert.Equal (0, current = n.GetNextMatchingItem (current, 'b')); // no matches
|
||||
Assert.Equal (0, current = n.GetNextMatchingItem (current, 'a')); // no matches
|
||||
Assert.Equal (0, current = n.GetNextMatchingItem (current, 't')); // no matches
|
||||
}
|
||||
}
|
||||
|
||||
120
Tests/UnitTestsParallelizable/Views/ListViewTests.cs
Normal file
120
Tests/UnitTestsParallelizable/Views/ListViewTests.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Moq;
|
||||
|
||||
namespace Terminal.Gui.ViewsTests;
|
||||
|
||||
public class ListViewTests
|
||||
{
|
||||
[Fact]
|
||||
public void ListViewCollectionNavigatorMatcher_DefaultBehaviour ()
|
||||
{
|
||||
ObservableCollection<string> source = new () { "apricot", "arm", "bat", "batman", "bates hotel", "candle" };
|
||||
ListView 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]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListViewCollectionNavigatorMatcher_IgnoreKeys ()
|
||||
{
|
||||
ObservableCollection<string> source = new () { "apricot", "arm", "bat", "batman", "bates hotel", "candle" };
|
||||
ListView lv = new ListView { Source = new ListWrapper<string> (source) };
|
||||
|
||||
|
||||
var matchNone = new Mock<ICollectionNavigatorMatcher> ();
|
||||
|
||||
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 = new () { "apricot", "arm", "bat", "batman", "bates hotel", "candle" };
|
||||
ListView lv = new ListView { Source = new ListWrapper<string> (source) };
|
||||
|
||||
|
||||
var matchNone = new Mock<ICollectionNavigatorMatcher> ();
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListView_CollectionNavigatorMatcher_KeybindingsOverrideNavigator ()
|
||||
{
|
||||
ObservableCollection<string> source = new () { "apricot", "arm", "bat", "batman", "bates hotel", "candle" };
|
||||
ListView lv = new ListView { Source = new ListWrapper<string> (source) };
|
||||
|
||||
lv.SetFocus ();
|
||||
|
||||
lv.KeyBindings.Add (Key.B, Command.Down);
|
||||
|
||||
Assert.Equal (-1, 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 CollectionNavigatorMatcher_KeybindingsOverrideNavigator ()
|
||||
{
|
||||
ObservableCollection<string> source = new () { "apricot", "arm", "bat", "batman", "bates hotel", "candle" };
|
||||
ListView lv = new ListView { Source = new ListWrapper<string> (source) };
|
||||
|
||||
lv.SetFocus ();
|
||||
|
||||
lv.KeyBindings.Add (Key.B, Command.Down);
|
||||
|
||||
Assert.Equal (-1, 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);
|
||||
}
|
||||
}
|
||||
45
Tests/UnitTestsParallelizable/Views/TableViewTests.cs
Normal file
45
Tests/UnitTestsParallelizable/Views/TableViewTests.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Terminal.Gui.ViewsTests;
|
||||
|
||||
[TestSubject (typeof (TableView))]
|
||||
public class TableViewTests
|
||||
{
|
||||
[Fact]
|
||||
public void TableView_CollectionNavigatorMatcher_KeybindingsOverrideNavigator ()
|
||||
{
|
||||
var dt = new DataTable ();
|
||||
dt.Columns.Add ("blah");
|
||||
|
||||
dt.Rows.Add ("apricot");
|
||||
dt.Rows.Add ("arm");
|
||||
dt.Rows.Add ("bat");
|
||||
dt.Rows.Add ("batman");
|
||||
dt.Rows.Add ("bates hotel");
|
||||
dt.Rows.Add ("candle");
|
||||
|
||||
var tableView = new TableView ();
|
||||
tableView.Table = new DataTableSource (dt);
|
||||
tableView.HasFocus = true;
|
||||
tableView.KeyBindings.Add (Key.B, Command.Down);
|
||||
|
||||
Assert.Equal (0, tableView.SelectedRow);
|
||||
|
||||
// Keys should be consumed to move down the navigation i.e. to apricot
|
||||
Assert.True (tableView.NewKeyDownEvent (Key.B));
|
||||
Assert.Equal (1, tableView.SelectedRow);
|
||||
|
||||
Assert.True (tableView.NewKeyDownEvent (Key.B));
|
||||
Assert.Equal (2, tableView.SelectedRow);
|
||||
|
||||
// There is no keybinding for Key.C so it hits collection navigator i.e. we jump to candle
|
||||
Assert.True (tableView.NewKeyDownEvent (Key.C));
|
||||
Assert.Equal (5, tableView.SelectedRow);
|
||||
}
|
||||
}
|
||||
40
Tests/UnitTestsParallelizable/Views/TreeViewTests.cs
Normal file
40
Tests/UnitTestsParallelizable/Views/TreeViewTests.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Terminal.Gui.ViewsTests;
|
||||
|
||||
[TestSubject (typeof (TreeView))]
|
||||
public class TreeViewTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public void TreeView_CollectionNavigatorMatcher_KeybindingsOverrideNavigator ()
|
||||
{
|
||||
var tree = new TreeView ();
|
||||
tree.AddObjects ([
|
||||
new TreeNode(){ Text="apricot" },
|
||||
new TreeNode(){ Text="arm" },
|
||||
new TreeNode(){ Text="bat" },
|
||||
new TreeNode(){ Text="batman" },
|
||||
new TreeNode(){ Text="bates hotel" },
|
||||
new TreeNode(){ Text="candle" },
|
||||
]);
|
||||
|
||||
tree.SetFocus ();
|
||||
|
||||
tree.KeyBindings.Add (Key.B, Command.Down);
|
||||
|
||||
Assert.Equal ("apricot", tree.SelectedObject.Text);
|
||||
|
||||
// Keys should be consumed to move down the navigation i.e. to apricot
|
||||
Assert.True (tree.NewKeyDownEvent (Key.B));
|
||||
Assert.NotNull (tree.SelectedObject);
|
||||
Assert.Equal ("arm", tree.SelectedObject.Text);
|
||||
|
||||
Assert.True (tree.NewKeyDownEvent (Key.B));
|
||||
Assert.Equal ("bat", tree.SelectedObject.Text);
|
||||
|
||||
// There is no keybinding for Key.C so it hits collection navigator i.e. we jump to candle
|
||||
Assert.True (tree.NewKeyDownEvent (Key.C));
|
||||
Assert.Equal ("candle", tree.SelectedObject.Text);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user