From ee8f9a8e45aa19c380d92c1b7d0ce9018a3cf877 Mon Sep 17 00:00:00 2001 From: Thomas Nind <31306100+tznind@users.noreply.github.com> Date: Sun, 11 May 2025 23:29:22 +0100 Subject: [PATCH] Fixes #4076 cursor text field (#4077) * Add test for TextField cursor position * Add comment and one more assert * Fix cursor position at the end * Remove unused local field --------- Co-authored-by: BDisp --- Terminal.Gui/FileServices/FileDialogStyle.cs | 1 - Terminal.Gui/Views/TextField.cs | 2 +- .../FluentTests/TextFieldFluentTests.cs | 51 +++++++++++++++++++ Tests/TerminalGuiFluentTesting/FakeOutput.cs | 7 ++- .../GuiTestContext.cs | 15 +++++- .../XunitContextExtensions.cs | 26 +++++++++- 6 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 Tests/IntegrationTests/FluentTests/TextFieldFluentTests.cs diff --git a/Terminal.Gui/FileServices/FileDialogStyle.cs b/Terminal.Gui/FileServices/FileDialogStyle.cs index 761af2e64..9726d9959 100644 --- a/Terminal.Gui/FileServices/FileDialogStyle.cs +++ b/Terminal.Gui/FileServices/FileDialogStyle.cs @@ -10,7 +10,6 @@ namespace Terminal.Gui; public class FileDialogStyle { private readonly IFileSystem _fileSystem; - private bool _preserveFilenameOnDirectoryChanges; /// Creates a new instance of the class. public FileDialogStyle (IFileSystem fileSystem) diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 07adca219..97c7154cd 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -1110,7 +1110,7 @@ public class TextField : View TextModel.SetCol (ref col, Viewport.Width - 1, cols); } - int pos = col - ScrollOffset + Math.Min (Viewport.X, 0); + int pos = col + Math.Min (Viewport.X, 0); Move (pos, 0); return new Point (pos, 0); diff --git a/Tests/IntegrationTests/FluentTests/TextFieldFluentTests.cs b/Tests/IntegrationTests/FluentTests/TextFieldFluentTests.cs new file mode 100644 index 000000000..0197f7f24 --- /dev/null +++ b/Tests/IntegrationTests/FluentTests/TextFieldFluentTests.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO.Abstractions; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Terminal.Gui; +using TerminalGuiFluentTesting; +using TerminalGuiFluentTestingXunit; +using Xunit.Abstractions; + +namespace IntegrationTests.FluentTests; +public class TextFieldFluentTests +{ + private readonly TextWriter _out; + + public TextFieldFluentTests (ITestOutputHelper outputHelper) + { + _out = new TestOutputWriter (outputHelper); + } + + [Theory] + [ClassData (typeof (V2TestDrivers))] + public void TextField_Cursor_AtEnd_WhenTyping (V2TestDriver d) + { + // Simulates typing abcd into a TextField with width 3 (wide enough to render 2 characters only) + using var c = With.A (100, 20, d) + .Add (new TextField () { Width = 3 }) + .Focus () + .WaitIteration () + .AssertCursorPosition (new Point (1, 1)) // Initial cursor position (because Window has border) + .RaiseKeyDownEvent (Key.A) + .WaitIteration () + .ScreenShot ("After typing first letter", _out) + .AssertCursorPosition (new Point (2, 1)) // Cursor moves along as letter is pressed + .RaiseKeyDownEvent (Key.B) + .WaitIteration () + .AssertCursorPosition (new Point (3, 1)) // Cursor moves along as letter is pressed + .RaiseKeyDownEvent (Key.C) + .WaitIteration () + .ScreenShot ("After typing all letters",_out) + .AssertCursorPosition (new Point (3, 1)) // Cursor stays where it is because we are at end of TextField + .RaiseKeyDownEvent (Key.D) + .WaitIteration () + .ScreenShot ("Typing one more letter", _out) + .AssertCursorPosition (new Point (3, 1)) // Cursor still stays at end of TextField + .WriteOutLogs (_out) + .Stop (); + } +} diff --git a/Tests/TerminalGuiFluentTesting/FakeOutput.cs b/Tests/TerminalGuiFluentTesting/FakeOutput.cs index 5f2b43bce..b20f91fa4 100644 --- a/Tests/TerminalGuiFluentTesting/FakeOutput.cs +++ b/Tests/TerminalGuiFluentTesting/FakeOutput.cs @@ -24,5 +24,10 @@ internal class FakeOutput : IConsoleOutput public void SetCursorVisibility (CursorVisibility visibility) { } /// - public void SetCursorPosition (int col, int row) { } + public void SetCursorPosition (int col, int row) { CursorPosition = new Point (col, row); } + + /// + /// The last value set by calling + /// + public Point CursorPosition { get; private set; } } diff --git a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs index 5efc590e7..29083ffab 100644 --- a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs +++ b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs @@ -706,10 +706,14 @@ public class GuiTestContext : IDisposable /// is found (of Type T) or all views are looped through (back to the beginning) /// in which case triggers hard stop and Exception /// + /// Delegate that returns true if the passed View is the one + /// you are trying to focus. Leave to focus the first view of type + /// /// /// - public GuiTestContext Focus (Func evaluator) where T : View + public GuiTestContext Focus (Func? evaluator = null) where T : View { + evaluator ??= _ => true; Toplevel? t = Application.Top; HashSet seen = new (); @@ -816,4 +820,13 @@ public class GuiTestContext : IDisposable return this; } + + /// + /// Returns the last set position of the cursor. + /// + /// + public Point GetCursorPosition () + { + return _output.CursorPosition; + } } diff --git a/Tests/TerminalGuiFluentTestingXunit/XunitContextExtensions.cs b/Tests/TerminalGuiFluentTestingXunit/XunitContextExtensions.cs index a007dbbc1..e1da262cf 100644 --- a/Tests/TerminalGuiFluentTestingXunit/XunitContextExtensions.cs +++ b/Tests/TerminalGuiFluentTestingXunit/XunitContextExtensions.cs @@ -1,4 +1,5 @@ -using TerminalGuiFluentTesting; +using System.Drawing; +using TerminalGuiFluentTesting; using Xunit; namespace TerminalGuiFluentTestingXunit; @@ -6,4 +7,27 @@ namespace TerminalGuiFluentTestingXunit; public static partial class XunitContextExtensions { // Placeholder + + + /// + /// Asserts that the last set cursor position matches + /// + /// + /// + /// + public static GuiTestContext AssertCursorPosition (this GuiTestContext context, Point expected) + { + try + { + Assert.Equal (expected, context.GetCursorPosition ()); + } + catch (Exception) + { + context.HardStop (); + + throw; + } + + return context; + } }