|
|
@@ -0,0 +1,504 @@
|
|
|
+using UnitTests;
|
|
|
+
|
|
|
+namespace UnitTests_Parallelizable.ViewsTests;
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// Tests for TextView navigation, tabs, and cursor positioning.
|
|
|
+/// These replace the old non-parallelizable tests that had hard-coded viewport dimensions.
|
|
|
+/// </summary>
|
|
|
+public class TextViewNavigationTests : FakeDriverBase
|
|
|
+{
|
|
|
+ [Fact]
|
|
|
+ public void Tab_And_BackTab_Navigation_Without_Text ()
|
|
|
+ {
|
|
|
+ IDriver driver = CreateFakeDriver ();
|
|
|
+
|
|
|
+ var textView = new TextView
|
|
|
+ {
|
|
|
+ Width = 30,
|
|
|
+ Height = 10,
|
|
|
+ Text = "",
|
|
|
+ Driver = driver
|
|
|
+ };
|
|
|
+ textView.BeginInit ();
|
|
|
+ textView.EndInit ();
|
|
|
+
|
|
|
+ // Add 100 tabs
|
|
|
+ for (var i = 0; i < 100; i++)
|
|
|
+ {
|
|
|
+ textView.Text += "\t";
|
|
|
+ }
|
|
|
+
|
|
|
+ // Move to end
|
|
|
+ textView.MoveEnd ();
|
|
|
+ Assert.Equal (new Point (100, 0), textView.CursorPosition);
|
|
|
+
|
|
|
+ // Test BackTab (Shift+Tab) navigation backwards
|
|
|
+ for (var i = 99; i >= 0; i--)
|
|
|
+ {
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.Tab.WithShift));
|
|
|
+ Assert.Equal (new Point (i, 0), textView.CursorPosition);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test Tab navigation forwards
|
|
|
+ for (var i = 1; i <= 100; i++)
|
|
|
+ {
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.Tab));
|
|
|
+ Assert.Equal (new Point (i, 0), textView.CursorPosition);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void Tab_And_BackTab_Navigation_With_Text ()
|
|
|
+ {
|
|
|
+ IDriver driver = CreateFakeDriver ();
|
|
|
+
|
|
|
+ var textView = new TextView
|
|
|
+ {
|
|
|
+ Width = 30,
|
|
|
+ Height = 10,
|
|
|
+ Text = "TAB to jump between text fields.",
|
|
|
+ Driver = driver
|
|
|
+ };
|
|
|
+ textView.BeginInit ();
|
|
|
+ textView.EndInit ();
|
|
|
+
|
|
|
+ Assert.Equal (new Point (0, 0), textView.CursorPosition);
|
|
|
+
|
|
|
+ // Navigate forward with Tab
|
|
|
+ for (var i = 1; i <= 100; i++)
|
|
|
+ {
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.Tab));
|
|
|
+ Assert.Equal (new Point (i, 0), textView.CursorPosition);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Navigate backward with BackTab
|
|
|
+ for (var i = 99; i >= 0; i--)
|
|
|
+ {
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.Tab.WithShift));
|
|
|
+ Assert.Equal (new Point (i, 0), textView.CursorPosition);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void Tab_With_CursorLeft_And_CursorRight_Without_Text ()
|
|
|
+ {
|
|
|
+ IDriver driver = CreateFakeDriver ();
|
|
|
+
|
|
|
+ var textView = new TextView
|
|
|
+ {
|
|
|
+ Width = 30,
|
|
|
+ Height = 10,
|
|
|
+ Text = "",
|
|
|
+ Driver = driver
|
|
|
+ };
|
|
|
+ textView.BeginInit ();
|
|
|
+ textView.EndInit ();
|
|
|
+
|
|
|
+ // Navigate forward with Tab
|
|
|
+ for (var i = 1; i <= 100; i++)
|
|
|
+ {
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.Tab));
|
|
|
+ Assert.Equal (new Point (i, 0), textView.CursorPosition);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Navigate backward with CursorLeft
|
|
|
+ for (var i = 99; i >= 0; i--)
|
|
|
+ {
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.CursorLeft));
|
|
|
+ Assert.Equal (new Point (i, 0), textView.CursorPosition);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Navigate forward with CursorRight
|
|
|
+ for (var i = 1; i <= 100; i++)
|
|
|
+ {
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.CursorRight));
|
|
|
+ Assert.Equal (new Point (i, 0), textView.CursorPosition);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void Tab_With_CursorLeft_And_CursorRight_With_Text ()
|
|
|
+ {
|
|
|
+ IDriver driver = CreateFakeDriver ();
|
|
|
+
|
|
|
+ var textView = new TextView
|
|
|
+ {
|
|
|
+ Width = 30,
|
|
|
+ Height = 10,
|
|
|
+ Text = "TAB to jump between text fields.",
|
|
|
+ Driver = driver
|
|
|
+ };
|
|
|
+ textView.BeginInit ();
|
|
|
+ textView.EndInit ();
|
|
|
+
|
|
|
+ Assert.Equal (32, textView.Text.Length);
|
|
|
+ Assert.Equal (new Point (0, 0), textView.CursorPosition);
|
|
|
+
|
|
|
+ // Navigate forward with Tab
|
|
|
+ for (var i = 1; i <= 100; i++)
|
|
|
+ {
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.Tab));
|
|
|
+ Assert.Equal (new Point (i, 0), textView.CursorPosition);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Navigate backward with CursorLeft
|
|
|
+ for (var i = 99; i >= 0; i--)
|
|
|
+ {
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.CursorLeft));
|
|
|
+ Assert.Equal (new Point (i, 0), textView.CursorPosition);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Navigate forward with CursorRight
|
|
|
+ for (var i = 1; i <= 100; i++)
|
|
|
+ {
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.CursorRight));
|
|
|
+ Assert.Equal (new Point (i, 0), textView.CursorPosition);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void Tab_With_Home_End_And_BackTab ()
|
|
|
+ {
|
|
|
+ IDriver driver = CreateFakeDriver ();
|
|
|
+
|
|
|
+ var textView = new TextView
|
|
|
+ {
|
|
|
+ Width = 30,
|
|
|
+ Height = 10,
|
|
|
+ Text = "TAB to jump between text fields.",
|
|
|
+ Driver = driver
|
|
|
+ };
|
|
|
+ textView.BeginInit ();
|
|
|
+ textView.EndInit ();
|
|
|
+
|
|
|
+ Assert.Equal (32, textView.Text.Length);
|
|
|
+ Assert.Equal (new Point (0, 0), textView.CursorPosition);
|
|
|
+
|
|
|
+ // Navigate forward with Tab to column 100
|
|
|
+ for (var i = 1; i <= 100; i++)
|
|
|
+ {
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.Tab));
|
|
|
+ Assert.Equal (new Point (i, 0), textView.CursorPosition);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test Length increased due to tabs
|
|
|
+ Assert.Equal (132, textView.Text.Length);
|
|
|
+
|
|
|
+ // Press Home to go to beginning
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.Home));
|
|
|
+ Assert.Equal (new Point (0, 0), textView.CursorPosition);
|
|
|
+
|
|
|
+ // Press End to go to end
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.End));
|
|
|
+ Assert.Equal (132, textView.Text.Length);
|
|
|
+ Assert.Equal (new Point (132, 0), textView.CursorPosition);
|
|
|
+
|
|
|
+ // Find the position just before the last tab
|
|
|
+ string txt = textView.Text;
|
|
|
+ var col = txt.Length;
|
|
|
+
|
|
|
+ while (col - 1 > 0 && txt [col - 1] != '\t')
|
|
|
+ {
|
|
|
+ col--;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set cursor to that position
|
|
|
+ textView.CursorPosition = new Point (col, 0);
|
|
|
+
|
|
|
+ // Navigate backward with BackTab (removes tabs, going back to original text)
|
|
|
+ while (col > 0)
|
|
|
+ {
|
|
|
+ col--;
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.Tab.WithShift));
|
|
|
+ Assert.Equal (new Point (col, 0), textView.CursorPosition);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Should be back at the original text
|
|
|
+ Assert.Equal ("TAB to jump between text fields.", textView.Text);
|
|
|
+ Assert.Equal (32, textView.Text.Length);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void BackTab_Then_Tab_Navigation ()
|
|
|
+ {
|
|
|
+ IDriver driver = CreateFakeDriver ();
|
|
|
+
|
|
|
+ var textView = new TextView
|
|
|
+ {
|
|
|
+ Width = 30,
|
|
|
+ Height = 10,
|
|
|
+ Text = "",
|
|
|
+ Driver = driver
|
|
|
+ };
|
|
|
+ textView.BeginInit ();
|
|
|
+ textView.EndInit ();
|
|
|
+
|
|
|
+ // Add 100 tabs at end
|
|
|
+ for (var i = 0; i < 100; i++)
|
|
|
+ {
|
|
|
+ textView.Text += "\t";
|
|
|
+ }
|
|
|
+
|
|
|
+ textView.MoveEnd ();
|
|
|
+ Assert.Equal (new Point (100, 0), textView.CursorPosition);
|
|
|
+
|
|
|
+ // Navigate backward with BackTab
|
|
|
+ for (var i = 99; i >= 0; i--)
|
|
|
+ {
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.Tab.WithShift));
|
|
|
+ Assert.Equal (new Point (i, 0), textView.CursorPosition);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Navigate forward with Tab
|
|
|
+ for (var i = 1; i <= 100; i++)
|
|
|
+ {
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.Tab));
|
|
|
+ Assert.Equal (new Point (i, 0), textView.CursorPosition);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void TabWidth_Setting_To_Zero_Keeps_AllowsTab ()
|
|
|
+ {
|
|
|
+ IDriver driver = CreateFakeDriver ();
|
|
|
+
|
|
|
+ var textView = new TextView
|
|
|
+ {
|
|
|
+ Width = 30,
|
|
|
+ Height = 10,
|
|
|
+ Text = "TAB to jump between text fields.",
|
|
|
+ Driver = driver
|
|
|
+ };
|
|
|
+ textView.BeginInit ();
|
|
|
+ textView.EndInit ();
|
|
|
+
|
|
|
+ // Verify initial state
|
|
|
+ Assert.Equal (4, textView.TabWidth);
|
|
|
+ Assert.True (textView.AllowsTab);
|
|
|
+ Assert.True (textView.AllowsReturn);
|
|
|
+ Assert.True (textView.Multiline);
|
|
|
+
|
|
|
+ // Set TabWidth to -1 (should clamp to 0)
|
|
|
+ textView.TabWidth = -1;
|
|
|
+ Assert.Equal (0, textView.TabWidth);
|
|
|
+ Assert.True (textView.AllowsTab);
|
|
|
+ Assert.True (textView.AllowsReturn);
|
|
|
+ Assert.True (textView.Multiline);
|
|
|
+
|
|
|
+ // Insert a tab
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.Tab));
|
|
|
+ Assert.Equal ("\tTAB to jump between text fields.", textView.Text);
|
|
|
+
|
|
|
+ // Change TabWidth back to 4
|
|
|
+ textView.TabWidth = 4;
|
|
|
+ Assert.Equal (4, textView.TabWidth);
|
|
|
+
|
|
|
+ // Remove the tab with BackTab
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.Tab.WithShift));
|
|
|
+ Assert.Equal ("TAB to jump between text fields.", textView.Text);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void KeyBindings_Command_Navigation ()
|
|
|
+ {
|
|
|
+ IDriver driver = CreateFakeDriver ();
|
|
|
+
|
|
|
+ var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
|
|
|
+ var textView = new TextView
|
|
|
+ {
|
|
|
+ Width = 10,
|
|
|
+ Height = 2,
|
|
|
+ Text = text,
|
|
|
+ Driver = driver
|
|
|
+ };
|
|
|
+ textView.BeginInit ();
|
|
|
+ textView.EndInit ();
|
|
|
+
|
|
|
+ Assert.Equal (
|
|
|
+ $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
|
|
|
+ textView.Text
|
|
|
+ );
|
|
|
+ Assert.Equal (3, textView.Lines);
|
|
|
+ Assert.Equal (Point.Empty, textView.CursorPosition);
|
|
|
+ Assert.False (textView.ReadOnly);
|
|
|
+ Assert.True (textView.CanFocus);
|
|
|
+ Assert.False (textView.IsSelecting);
|
|
|
+
|
|
|
+ // Test that CursorLeft doesn't move when at beginning
|
|
|
+ textView.CanFocus = false;
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.CursorLeft));
|
|
|
+ Assert.False (textView.IsSelecting);
|
|
|
+
|
|
|
+ textView.CanFocus = true;
|
|
|
+ Assert.False (textView.NewKeyDownEvent (Key.CursorLeft));
|
|
|
+ Assert.False (textView.IsSelecting);
|
|
|
+
|
|
|
+ // Move right
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.CursorRight));
|
|
|
+ Assert.Equal (new Point (1, 0), textView.CursorPosition);
|
|
|
+ Assert.False (textView.IsSelecting);
|
|
|
+
|
|
|
+ // Move to end of document
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.End.WithCtrl));
|
|
|
+ Assert.Equal (2, textView.CurrentRow);
|
|
|
+ Assert.Equal (23, textView.CurrentColumn);
|
|
|
+ Assert.Equal (textView.CurrentColumn, textView.GetCurrentLine ().Count);
|
|
|
+ Assert.Equal (new Point (23, 2), textView.CursorPosition);
|
|
|
+ Assert.False (textView.IsSelecting);
|
|
|
+
|
|
|
+ // Try to move right (should fail, at end)
|
|
|
+ Assert.False (textView.NewKeyDownEvent (Key.CursorRight));
|
|
|
+ Assert.False (textView.IsSelecting);
|
|
|
+
|
|
|
+ // Type a character
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.F.WithShift));
|
|
|
+ Assert.Equal (
|
|
|
+ $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.F",
|
|
|
+ textView.Text
|
|
|
+ );
|
|
|
+ Assert.Equal (new Point (24, 2), textView.CursorPosition);
|
|
|
+ Assert.False (textView.IsSelecting);
|
|
|
+
|
|
|
+ // Undo
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.Z.WithCtrl));
|
|
|
+ Assert.Equal (
|
|
|
+ $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
|
|
|
+ textView.Text
|
|
|
+ );
|
|
|
+ Assert.Equal (new Point (23, 2), textView.CursorPosition);
|
|
|
+ Assert.False (textView.IsSelecting);
|
|
|
+
|
|
|
+ // Redo
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.R.WithCtrl));
|
|
|
+ Assert.Equal (
|
|
|
+ $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.F",
|
|
|
+ textView.Text
|
|
|
+ );
|
|
|
+ Assert.Equal (new Point (24, 2), textView.CursorPosition);
|
|
|
+ Assert.False (textView.IsSelecting);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void UnwrappedCursorPosition_Event_Fires_Correctly ()
|
|
|
+ {
|
|
|
+ IDriver driver = CreateFakeDriver (25, 25);
|
|
|
+
|
|
|
+ Point unwrappedPosition = Point.Empty;
|
|
|
+
|
|
|
+ var textView = new TextView
|
|
|
+ {
|
|
|
+ Width = Dim.Fill (),
|
|
|
+ Height = Dim.Fill (),
|
|
|
+ Text = "This is the first line.\nThis is the second line.\n",
|
|
|
+ Driver = driver
|
|
|
+ };
|
|
|
+
|
|
|
+ textView.UnwrappedCursorPosition += (s, e) => { unwrappedPosition = e; };
|
|
|
+
|
|
|
+ textView.BeginInit ();
|
|
|
+ textView.EndInit ();
|
|
|
+
|
|
|
+ // Initially no word wrap
|
|
|
+ Assert.False (textView.WordWrap);
|
|
|
+ Assert.Equal (Point.Empty, textView.CursorPosition);
|
|
|
+ Assert.Equal (Point.Empty, unwrappedPosition);
|
|
|
+
|
|
|
+ // Enable word wrap and move cursor
|
|
|
+ textView.WordWrap = true;
|
|
|
+ textView.CursorPosition = new Point (12, 0);
|
|
|
+ Assert.Equal (new Point (12, 0), textView.CursorPosition);
|
|
|
+ Assert.Equal (new Point (12, 0), unwrappedPosition);
|
|
|
+
|
|
|
+ // Resize to narrow width to force wrapping
|
|
|
+ textView.Width = 6;
|
|
|
+ textView.Height = 25;
|
|
|
+ textView.SetRelativeLayout (new Size (6, 25));
|
|
|
+
|
|
|
+ // The wrapped cursor position should differ from unwrapped
|
|
|
+ // After wrap, column 12 might be on a different visual line
|
|
|
+ // but unwrapped position should still be (12, 0)
|
|
|
+ Assert.Equal (new Point (12, 0), unwrappedPosition);
|
|
|
+
|
|
|
+ // Move right - the unwrapped position event may not fire immediately
|
|
|
+ // or may be based on previous cursor position
|
|
|
+ // Just verify the event mechanism works
|
|
|
+ var currentUnwrapped = unwrappedPosition;
|
|
|
+ Assert.True (textView.NewKeyDownEvent (Key.CursorRight));
|
|
|
+ // The unwrapped position should have updated (either to 13 or still tracking properly)
|
|
|
+ Assert.True (unwrappedPosition.X >= currentUnwrapped.X);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void Horizontal_Scrolling_Adjusts_LeftColumn ()
|
|
|
+ {
|
|
|
+ IDriver driver = CreateFakeDriver ();
|
|
|
+
|
|
|
+ var textView = new TextView
|
|
|
+ {
|
|
|
+ Width = 20,
|
|
|
+ Height = 5,
|
|
|
+ Text = "This is a very long line that will require horizontal scrolling to see all of it",
|
|
|
+ WordWrap = false,
|
|
|
+ Driver = driver
|
|
|
+ };
|
|
|
+ textView.BeginInit ();
|
|
|
+ textView.EndInit ();
|
|
|
+
|
|
|
+ // Initially at the start
|
|
|
+ Assert.Equal (0, textView.LeftColumn);
|
|
|
+ Assert.Equal (new Point (0, 0), textView.CursorPosition);
|
|
|
+
|
|
|
+ // Move to the end of the line
|
|
|
+ textView.MoveEnd ();
|
|
|
+
|
|
|
+ // LeftColumn should have adjusted to show the cursor
|
|
|
+ Assert.True (textView.LeftColumn > 0);
|
|
|
+ Assert.Equal (textView.Text.Length, textView.CurrentColumn);
|
|
|
+
|
|
|
+ // Move back to the start
|
|
|
+ textView.MoveHome ();
|
|
|
+
|
|
|
+ // LeftColumn should be back to 0
|
|
|
+ Assert.Equal (0, textView.LeftColumn);
|
|
|
+ Assert.Equal (0, textView.CurrentColumn);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void Vertical_Scrolling_Adjusts_TopRow ()
|
|
|
+ {
|
|
|
+ IDriver driver = CreateFakeDriver ();
|
|
|
+
|
|
|
+ var lines = string.Join ("\n", Enumerable.Range (1, 100).Select (i => $"Line {i}"));
|
|
|
+ var textView = new TextView
|
|
|
+ {
|
|
|
+ Width = 20,
|
|
|
+ Height = 5,
|
|
|
+ Text = lines,
|
|
|
+ Driver = driver
|
|
|
+ };
|
|
|
+ textView.BeginInit ();
|
|
|
+ textView.EndInit ();
|
|
|
+
|
|
|
+ // Initially at the top
|
|
|
+ Assert.Equal (0, textView.TopRow);
|
|
|
+ Assert.Equal (new Point (0, 0), textView.CursorPosition);
|
|
|
+
|
|
|
+ // Move down many lines
|
|
|
+ for (var i = 0; i < 50; i++)
|
|
|
+ {
|
|
|
+ textView.NewKeyDownEvent (Key.CursorDown);
|
|
|
+ }
|
|
|
+
|
|
|
+ // TopRow should have adjusted to show the cursor
|
|
|
+ Assert.True (textView.TopRow > 0);
|
|
|
+ Assert.Equal (50, textView.CurrentRow);
|
|
|
+
|
|
|
+ // Move back to the top
|
|
|
+ textView.NewKeyDownEvent (Key.Home.WithCtrl);
|
|
|
+
|
|
|
+ // TopRow should be back to 0
|
|
|
+ Assert.Equal (0, textView.TopRow);
|
|
|
+ Assert.Equal (0, textView.CurrentRow);
|
|
|
+ }
|
|
|
+}
|