mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* Add constructor Key(int) and operator for handled with non-Bmp. * Fix TextField non-BMP issues * Fix TextField PositionCursor. * Reformat * Add IsValidInput method to handle clipboard paste when pressing CTRL+V in WT * Add handle IsValidInput in FakeDriver and unit tests * Fixes #3984 - `Margin` w/out shadow should not force draw (#3985) * shortcut tests * Generic demos * Optimize Margin to not defer draw if there's no shadow * Fixes #4041. WSLClipboard doesn't handles well with surrogate pairs * Avoid running Clipboard.Contents twice * Fixes #4042. Microsoft.VisualStudio.TestPlatform.ObjectModel.TestPlatformException: Could not find testhost * Moving tests to the parallelizable unit tests * Remove unused folder * Prevent warnings about not installed nuget packages * Using Toplevel instead of Application.Top * Cleanup code --------- Co-authored-by: Tig <tig@users.noreply.github.com>
This commit is contained in:
@@ -22,7 +22,7 @@ public abstract class ClipboardBase : IClipboard
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return GetClipboardDataImpl ();
|
||||
return result;
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
|
||||
@@ -691,6 +691,40 @@ public abstract class ConsoleDriver : IConsoleDriver
|
||||
/// <param name="ctrl">If <see langword="true"/> simulates the Ctrl key being pressed.</param>
|
||||
public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl);
|
||||
|
||||
internal char _highSurrogate = '\0';
|
||||
|
||||
internal bool IsValidInput (KeyCode keyCode, out KeyCode result)
|
||||
{
|
||||
result = keyCode;
|
||||
|
||||
if (char.IsHighSurrogate ((char)keyCode))
|
||||
{
|
||||
_highSurrogate = (char)keyCode;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_highSurrogate > 0 && char.IsLowSurrogate ((char)keyCode))
|
||||
{
|
||||
result = (KeyCode)new Rune (_highSurrogate, (char)keyCode).Value;
|
||||
_highSurrogate = '\0';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (char.IsSurrogate ((char)keyCode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_highSurrogate > 0)
|
||||
{
|
||||
_highSurrogate = '\0';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private AnsiRequestScheduler? _scheduler;
|
||||
|
||||
@@ -203,7 +203,7 @@ internal class WSLClipboard : ClipboardBase
|
||||
}
|
||||
|
||||
(int exitCode, string output) =
|
||||
ClipboardProcessRunner.Process (_powershellPath, "-noprofile -command \"Get-Clipboard\"");
|
||||
ClipboardProcessRunner.Process (_powershellPath, "-noprofile -command \"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Clipboard\"");
|
||||
|
||||
if (exitCode == 0)
|
||||
{
|
||||
|
||||
@@ -924,8 +924,11 @@ internal class CursesDriver : ConsoleDriver
|
||||
k &= ~KeyCode.Space;
|
||||
}
|
||||
|
||||
OnKeyDown (new Key (k));
|
||||
OnKeyUp (new Key (k));
|
||||
if (IsValidInput (k, out k))
|
||||
{
|
||||
OnKeyDown (new (k));
|
||||
OnKeyUp (new (k));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -352,8 +352,12 @@ public class FakeDriver : ConsoleDriver
|
||||
}
|
||||
|
||||
KeyCode map = MapKey (consoleKeyInfo);
|
||||
OnKeyDown (new Key (map));
|
||||
OnKeyUp (new Key (map));
|
||||
|
||||
if (IsValidInput (map, out map))
|
||||
{
|
||||
OnKeyDown (new (map));
|
||||
OnKeyUp (new (map));
|
||||
}
|
||||
|
||||
//OnKeyPressed (new KeyEventArgs (map));
|
||||
}
|
||||
|
||||
@@ -321,8 +321,11 @@ internal class NetDriver : ConsoleDriver
|
||||
break;
|
||||
}
|
||||
|
||||
OnKeyDown (new (map));
|
||||
OnKeyUp (new (map));
|
||||
if (IsValidInput (map, out map))
|
||||
{
|
||||
OnKeyDown (new (map));
|
||||
OnKeyUp (new (map));
|
||||
}
|
||||
|
||||
break;
|
||||
case EventType.Mouse:
|
||||
|
||||
@@ -507,9 +507,12 @@ internal class WindowsDriver : ConsoleDriver
|
||||
break;
|
||||
}
|
||||
|
||||
// This follows convention in NetDriver
|
||||
OnKeyDown (new Key (map));
|
||||
OnKeyUp (new Key (map));
|
||||
if (IsValidInput (map, out map))
|
||||
{
|
||||
// This follows convention in NetDriver
|
||||
OnKeyDown (new (map));
|
||||
OnKeyUp (new (map));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
|
||||
@@ -138,6 +138,44 @@ public class Key : EventArgs, IEquatable<Key>
|
||||
KeyCode = key.KeyCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new Key from an integer describing the key.
|
||||
/// It parses the integer as Key by calling the constructor with a char or calls the constructor with a
|
||||
/// KeyCode.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Don't rely on <paramref name="value"/> passed from <see cref="KeyCode.A"/> to <see cref="KeyCode.Z"/> because
|
||||
/// would not return the expected keys from 'a' to 'z'.
|
||||
/// </remarks>
|
||||
/// <param name="value">The integer describing the key.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public Key (int value)
|
||||
{
|
||||
if (value < 0 || value > RuneExtensions.MaxUnicodeCodePoint)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException (@$"Invalid key value: {value}", nameof (value));
|
||||
}
|
||||
|
||||
if (char.IsSurrogate ((char)value))
|
||||
{
|
||||
throw new ArgumentException (@$"Surrogate key not allowed: {value}", nameof (value));
|
||||
}
|
||||
|
||||
Key key;
|
||||
|
||||
if (((Rune)value).IsBmp)
|
||||
{
|
||||
key = new ((char)value);
|
||||
}
|
||||
else
|
||||
{
|
||||
key = new ((KeyCode)value);
|
||||
}
|
||||
|
||||
KeyCode = key.KeyCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers.
|
||||
/// Useful for determining if a key represents is a printable character.
|
||||
@@ -388,6 +426,11 @@ public class Key : EventArgs, IEquatable<Key>
|
||||
/// <param name="str"></param>
|
||||
public static implicit operator Key (string str) { return new (str); }
|
||||
|
||||
/// <summary>Cast <see langword="int"/> to a <see cref="Key"/>.</summary>
|
||||
/// <remarks>See <see cref="Key(int)"/> for more information.</remarks>
|
||||
/// <param name="value"></param>
|
||||
public static implicit operator Key (int value) { return new (value); }
|
||||
|
||||
/// <summary>Cast a <see cref="Key"/> to a <see langword="string"/>.</summary>
|
||||
/// <remarks>See <see cref="Key(string)"/> for more information.</remarks>
|
||||
/// <param name="key"></param>
|
||||
@@ -550,7 +593,7 @@ public class Key : EventArgs, IEquatable<Key>
|
||||
// "Ctrl+" (trim)
|
||||
// "Ctrl++" (trim)
|
||||
|
||||
if (input.Length > 1 && new Rune (input [^1]) == separator && new Rune (input [^2]) != separator)
|
||||
if (input.Length > 1 && !char.IsHighSurrogate (input [^2]) && new Rune (input [^1]) == separator && new Rune (input [^2]) != separator)
|
||||
{
|
||||
return input [..^1];
|
||||
}
|
||||
@@ -640,6 +683,13 @@ public class Key : EventArgs, IEquatable<Key>
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text.Length == 2 && char.IsHighSurrogate (text [^2]) && char.IsLowSurrogate (text [^1]))
|
||||
{
|
||||
// It's a surrogate pair and there is no modifiers
|
||||
key = new (new Rune (text [^2], text [^1]).Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
// e.g. "Ctrl++"
|
||||
if ((Rune)text [^1] != separator && parts.Any (string.IsNullOrEmpty))
|
||||
{
|
||||
|
||||
@@ -729,21 +729,11 @@ public class TextField : View
|
||||
/// <param name="useOldCursorPos">Use the previous cursor position.</param>
|
||||
public void InsertText (string toAdd, bool useOldCursorPos = true)
|
||||
{
|
||||
foreach (char ch in toAdd)
|
||||
foreach (Rune rune in toAdd.EnumerateRunes ())
|
||||
{
|
||||
Key key;
|
||||
|
||||
try
|
||||
{
|
||||
key = ch;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ArgumentException (
|
||||
$"Cannot insert character '{ch}' because it does not map to a Key"
|
||||
);
|
||||
}
|
||||
|
||||
// All rune can be mapped to a Key and no exception will throw here because
|
||||
// EnumerateRunes will replace a surrogate char with the Rune.ReplacementChar
|
||||
Key key = rune.Value;
|
||||
InsertText (key, useOldCursorPos);
|
||||
}
|
||||
}
|
||||
@@ -1072,14 +1062,20 @@ public class TextField : View
|
||||
/// <summary>Paste the selected text from the clipboard.</summary>
|
||||
public virtual void Paste ()
|
||||
{
|
||||
if (ReadOnly || string.IsNullOrEmpty (Clipboard.Contents))
|
||||
if (ReadOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string cbTxt = Clipboard.Contents.Split ("\n") [0] ?? "";
|
||||
|
||||
if (string.IsNullOrEmpty (cbTxt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetSelectedStartSelectedLength ();
|
||||
int selStart = _start == -1 ? CursorPosition : _start;
|
||||
string cbTxt = Clipboard.Contents.Split ("\n") [0] ?? "";
|
||||
|
||||
Text = StringExtensions.ToString (_text.GetRange (0, selStart))
|
||||
+ cbTxt
|
||||
@@ -1114,7 +1110,7 @@ public class TextField : View
|
||||
TextModel.SetCol (ref col, Viewport.Width - 1, cols);
|
||||
}
|
||||
|
||||
int pos = _cursorPosition - ScrollOffset + Math.Min (Viewport.X, 0);
|
||||
int pos = col - ScrollOffset + Math.Min (Viewport.X, 0);
|
||||
Move (pos, 0);
|
||||
|
||||
return new Point (pos, 0);
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TerminalGuiFluentTesting\TerminalGuiFluentTesting.csproj" />
|
||||
<PackageReference Include="xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,25 +0,0 @@
|
||||
using Xunit;
|
||||
|
||||
namespace TerminalGuiFluentTesting;
|
||||
|
||||
public static class XunitContextExtensions
|
||||
{
|
||||
public static GuiTestContext AssertTrue (this GuiTestContext context, bool? condition)
|
||||
{
|
||||
context.Then (
|
||||
() =>
|
||||
{
|
||||
Assert.True (condition);
|
||||
});
|
||||
return context;
|
||||
}
|
||||
public static GuiTestContext AssertEqual (this GuiTestContext context, object? expected, object? actual)
|
||||
{
|
||||
context.Then (
|
||||
() =>
|
||||
{
|
||||
Assert.Equal (expected,actual);
|
||||
});
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,9 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TerminalGuiFluentTestingXunit.Generator\TerminalGuiFluentTestingXunit.Generator.csproj" OutputItemType="Analyzer" />
|
||||
<ProjectReference Include="..\TerminalGuiFluentTesting\TerminalGuiFluentTesting.csproj" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -283,4 +283,115 @@ public class ConsoleDriverTests
|
||||
// Application.Run (win);
|
||||
// Application.Shutdown ();
|
||||
// }
|
||||
|
||||
[Theory]
|
||||
[InlineData ('\ud83d', '\udcc4')] // This seems right sequence but Stack is LIFO
|
||||
[InlineData ('\ud83d', '\ud83d')]
|
||||
[InlineData ('\udcc4', '\udcc4')]
|
||||
public void FakeDriver_IsValidInput_Wrong_Surrogate_Sequence (char c1, char c2)
|
||||
{
|
||||
var driver = (IConsoleDriver)Activator.CreateInstance (typeof (FakeDriver));
|
||||
Application.Init (driver);
|
||||
|
||||
Stack<ConsoleKeyInfo> mKeys = new (
|
||||
[
|
||||
new ('a', ConsoleKey.A, false, false, false),
|
||||
new (c1, ConsoleKey.None, false, false, false),
|
||||
new (c2, ConsoleKey.None, false, false, false)
|
||||
]);
|
||||
|
||||
Console.MockKeyPresses = mKeys;
|
||||
|
||||
Toplevel top = new ();
|
||||
var view = new View { CanFocus = true };
|
||||
var rText = "";
|
||||
var idx = 0;
|
||||
|
||||
view.KeyDown += (s, e) =>
|
||||
{
|
||||
Assert.Equal (new ('a'), e.AsRune);
|
||||
Assert.Equal ("a", e.AsRune.ToString ());
|
||||
rText += e.AsRune;
|
||||
e.Handled = true;
|
||||
idx++;
|
||||
};
|
||||
top.Add (view);
|
||||
|
||||
Application.Iteration += (s, a) =>
|
||||
{
|
||||
if (mKeys.Count == 0)
|
||||
{
|
||||
Application.RequestStop ();
|
||||
}
|
||||
};
|
||||
|
||||
Application.Run (top);
|
||||
|
||||
Assert.Equal ("a", rText);
|
||||
Assert.Equal (1, idx);
|
||||
Assert.Equal (0, ((FakeDriver)driver)._highSurrogate);
|
||||
|
||||
top.Dispose ();
|
||||
|
||||
// Shutdown must be called to safely clean up Application if Init has been called
|
||||
Application.Shutdown ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeDriver_IsValidInput_Correct_Surrogate_Sequence ()
|
||||
{
|
||||
var driver = (IConsoleDriver)Activator.CreateInstance (typeof (FakeDriver));
|
||||
Application.Init (driver);
|
||||
|
||||
Stack<ConsoleKeyInfo> mKeys = new (
|
||||
[
|
||||
new ('a', ConsoleKey.A, false, false, false),
|
||||
new ('\udcc4', ConsoleKey.None, false, false, false),
|
||||
new ('\ud83d', ConsoleKey.None, false, false, false)
|
||||
]);
|
||||
|
||||
Console.MockKeyPresses = mKeys;
|
||||
|
||||
Toplevel top = new ();
|
||||
var view = new View { CanFocus = true };
|
||||
var rText = "";
|
||||
var idx = 0;
|
||||
|
||||
view.KeyDown += (s, e) =>
|
||||
{
|
||||
if (idx == 0)
|
||||
{
|
||||
Assert.Equal (new (0x1F4C4), e.AsRune);
|
||||
Assert.Equal ("📄", e.AsRune.ToString ());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal (new ('a'), e.AsRune);
|
||||
Assert.Equal ("a", e.AsRune.ToString ());
|
||||
}
|
||||
|
||||
rText += e.AsRune;
|
||||
e.Handled = true;
|
||||
idx++;
|
||||
};
|
||||
top.Add (view);
|
||||
|
||||
Application.Iteration += (s, a) =>
|
||||
{
|
||||
if (mKeys.Count == 0)
|
||||
{
|
||||
Application.RequestStop ();
|
||||
}
|
||||
};
|
||||
|
||||
Application.Run (top);
|
||||
|
||||
Assert.Equal ("📄a", rText);
|
||||
Assert.Equal (2, idx);
|
||||
|
||||
top.Dispose ();
|
||||
|
||||
// Shutdown must be called to safely clean up Application if Init has been called
|
||||
Application.Shutdown ();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,27 +78,6 @@ public class TextFieldTests (ITestOutputHelper output)
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cancel_TextChanging_ThenBackspace ()
|
||||
{
|
||||
var tf = new TextField ();
|
||||
tf.SetFocus ();
|
||||
tf.NewKeyDownEvent (Key.A.WithShift);
|
||||
Assert.Equal ("A", tf.Text);
|
||||
|
||||
// cancel the next keystroke
|
||||
tf.TextChanging += (s, e) => e.Cancel = e.NewValue == "AB";
|
||||
tf.NewKeyDownEvent (Key.B.WithShift);
|
||||
|
||||
// B was canceled so should just be A
|
||||
Assert.Equal ("A", tf.Text);
|
||||
|
||||
// now delete the A
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
|
||||
Assert.Equal ("", tf.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[TextFieldTestsAutoInitShutdown]
|
||||
public void CanFocus_False_Wont_Focus_With_Mouse ()
|
||||
@@ -506,77 +485,6 @@ public class TextFieldTests (ITestOutputHelper output)
|
||||
top.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HistoryText_IsDirty_ClearHistoryChanges ()
|
||||
{
|
||||
var text = "Testing";
|
||||
var tf = new TextField { Text = text };
|
||||
tf.BeginInit ();
|
||||
tf.EndInit ();
|
||||
|
||||
Assert.Equal (text, tf.Text);
|
||||
tf.ClearHistoryChanges ();
|
||||
Assert.False (tf.IsDirty);
|
||||
|
||||
Assert.True (tf.NewKeyDownEvent (Key.A.WithShift));
|
||||
Assert.Equal ($"{text}A", tf.Text);
|
||||
Assert.True (tf.IsDirty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Space_Does_Not_Raise_Selected ()
|
||||
{
|
||||
TextField tf = new ();
|
||||
|
||||
tf.Selecting += (sender, args) => Assert.Fail ("Selected should not be raied.");
|
||||
|
||||
Application.Top = new ();
|
||||
Application.Top.Add (tf);
|
||||
tf.SetFocus ();
|
||||
Application.RaiseKeyDownEvent (Key.Space);
|
||||
|
||||
Application.Top.Dispose ();
|
||||
Application.ResetState (true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Enter_Does_Not_Raise_Selected ()
|
||||
{
|
||||
TextField tf = new ();
|
||||
|
||||
var selectingCount = 0;
|
||||
tf.Selecting += (sender, args) => selectingCount++;
|
||||
|
||||
Application.Top = new ();
|
||||
Application.Top.Add (tf);
|
||||
tf.SetFocus ();
|
||||
Application.RaiseKeyDownEvent (Key.Enter);
|
||||
|
||||
Assert.Equal (0, selectingCount);
|
||||
|
||||
Application.Top.Dispose ();
|
||||
Application.ResetState (true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Enter_Raises_Accepted ()
|
||||
{
|
||||
TextField tf = new ();
|
||||
|
||||
var acceptedCount = 0;
|
||||
tf.Accepting += (sender, args) => acceptedCount++;
|
||||
|
||||
Application.Top = new ();
|
||||
Application.Top.Add (tf);
|
||||
tf.SetFocus ();
|
||||
Application.RaiseKeyDownEvent (Key.Enter);
|
||||
|
||||
Assert.Equal (1, acceptedCount);
|
||||
|
||||
Application.Top.Dispose ();
|
||||
Application.ResetState (true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[AutoInitShutdown (useFakeClipboard: true)]
|
||||
public void KeyBindings_Command ()
|
||||
@@ -811,47 +719,6 @@ public class TextFieldTests (ITestOutputHelper output)
|
||||
Assert.Equal ("", tf.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HotKey_Command_SetsFocus ()
|
||||
{
|
||||
var view = new TextField ();
|
||||
|
||||
view.CanFocus = true;
|
||||
Assert.False (view.HasFocus);
|
||||
view.InvokeCommand (Command.HotKey);
|
||||
Assert.True (view.HasFocus);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HotKey_Command_Does_Not_Accept ()
|
||||
{
|
||||
var view = new TextField ();
|
||||
var accepted = false;
|
||||
view.Accepting += OnAccept;
|
||||
view.InvokeCommand (Command.HotKey);
|
||||
|
||||
Assert.False (accepted);
|
||||
|
||||
return;
|
||||
|
||||
void OnAccept (object sender, CommandEventArgs e) { accepted = true; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accepted_Command_Fires_Accept ()
|
||||
{
|
||||
var view = new TextField ();
|
||||
|
||||
var accepted = false;
|
||||
view.Accepting += Accept;
|
||||
view.InvokeCommand (Command.Accept);
|
||||
Assert.True (accepted);
|
||||
|
||||
return;
|
||||
|
||||
void Accept (object sender, CommandEventArgs e) { accepted = true; }
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (false, 1)]
|
||||
[InlineData (true, 0)]
|
||||
@@ -904,87 +771,6 @@ public class TextFieldTests (ITestOutputHelper output)
|
||||
void ButtonAccept (object sender, CommandEventArgs e) { buttonAccept++; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accepted_No_Handler_Enables_Default_Button_Accept ()
|
||||
{
|
||||
var superView = new Window
|
||||
{
|
||||
Id = "superView"
|
||||
};
|
||||
|
||||
var tf = new TextField
|
||||
{
|
||||
Id = "tf"
|
||||
};
|
||||
|
||||
var button = new Button
|
||||
{
|
||||
Id = "button",
|
||||
IsDefault = true
|
||||
};
|
||||
|
||||
superView.Add (tf, button);
|
||||
|
||||
var buttonAccept = 0;
|
||||
button.Accepting += ButtonAccept;
|
||||
|
||||
tf.SetFocus ();
|
||||
Assert.True (tf.HasFocus);
|
||||
|
||||
superView.NewKeyDownEvent (Key.Enter);
|
||||
Assert.Equal (1, buttonAccept);
|
||||
|
||||
button.SetFocus ();
|
||||
superView.NewKeyDownEvent (Key.Enter);
|
||||
Assert.Equal (2, buttonAccept);
|
||||
|
||||
return;
|
||||
|
||||
void ButtonAccept (object sender, CommandEventArgs e) { buttonAccept++; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accepted_Cancel_Event_HandlesCommand ()
|
||||
{
|
||||
//var super = new View ();
|
||||
var view = new TextField ();
|
||||
|
||||
//super.Add (view);
|
||||
|
||||
//var superAcceptedInvoked = false;
|
||||
|
||||
var tfAcceptedInvoked = false;
|
||||
var handle = false;
|
||||
view.Accepting += TextViewAccept;
|
||||
Assert.False (view.InvokeCommand (Command.Accept));
|
||||
Assert.True (tfAcceptedInvoked);
|
||||
|
||||
tfAcceptedInvoked = false;
|
||||
handle = true;
|
||||
view.Accepting += TextViewAccept;
|
||||
Assert.True (view.InvokeCommand (Command.Accept));
|
||||
Assert.True (tfAcceptedInvoked);
|
||||
|
||||
return;
|
||||
|
||||
void TextViewAccept (object sender, CommandEventArgs e)
|
||||
{
|
||||
tfAcceptedInvoked = true;
|
||||
e.Cancel = handle;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnEnter_Does_Not_Throw_If_Not_IsInitialized_SetCursorVisibility ()
|
||||
{
|
||||
var top = new Toplevel ();
|
||||
var tf = new TextField { Width = 10 };
|
||||
top.Add (tf);
|
||||
|
||||
Exception exception = Record.Exception (() => tf.SetFocus ());
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[TextFieldTestsAutoInitShutdown]
|
||||
public void Paste_Always_Clear_The_SelectedText ()
|
||||
@@ -997,59 +783,6 @@ public class TextFieldTests (ITestOutputHelper output)
|
||||
Assert.Null (_textField.SelectedText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Backspace_From_End ()
|
||||
{
|
||||
var tf = new TextField { Text = "ABC" };
|
||||
tf.SetFocus ();
|
||||
Assert.Equal ("ABC", tf.Text);
|
||||
tf.BeginInit ();
|
||||
tf.EndInit ();
|
||||
|
||||
Assert.Equal (3, tf.CursorPosition);
|
||||
|
||||
// now delete the C
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
Assert.Equal ("AB", tf.Text);
|
||||
Assert.Equal (2, tf.CursorPosition);
|
||||
|
||||
// then delete the B
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
Assert.Equal ("A", tf.Text);
|
||||
Assert.Equal (1, tf.CursorPosition);
|
||||
|
||||
// then delete the A
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
Assert.Equal ("", tf.Text);
|
||||
Assert.Equal (0, tf.CursorPosition);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Backspace_From_Middle ()
|
||||
{
|
||||
var tf = new TextField { Text = "ABC" };
|
||||
tf.SetFocus ();
|
||||
tf.CursorPosition = 2;
|
||||
Assert.Equal ("ABC", tf.Text);
|
||||
|
||||
// now delete the B
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
Assert.Equal ("AC", tf.Text);
|
||||
|
||||
// then delete the A
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
Assert.Equal ("C", tf.Text);
|
||||
|
||||
// then delete nothing
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
Assert.Equal ("C", tf.Text);
|
||||
|
||||
// now delete the C
|
||||
tf.CursorPosition = 1;
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
Assert.Equal ("", tf.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[AutoInitShutdown]
|
||||
public void ScrollOffset_Initialize ()
|
||||
@@ -1142,36 +875,6 @@ public class TextFieldTests (ITestOutputHelper output)
|
||||
Assert.Null (_textField.SelectedText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyDown_Handled_Prevents_Input ()
|
||||
{
|
||||
var tf = new TextField ();
|
||||
tf.KeyDown += HandleJKey;
|
||||
|
||||
tf.NewKeyDownEvent (Key.A);
|
||||
Assert.Equal ("a", tf.Text);
|
||||
|
||||
// SuppressKey suppresses the 'j' key
|
||||
tf.NewKeyDownEvent (Key.J);
|
||||
Assert.Equal ("a", tf.Text);
|
||||
|
||||
tf.KeyDown -= HandleJKey;
|
||||
|
||||
// Now that the delegate has been removed we can type j again
|
||||
tf.NewKeyDownEvent (Key.J);
|
||||
Assert.Equal ("aj", tf.Text);
|
||||
|
||||
return;
|
||||
|
||||
void HandleJKey (object s, Key arg)
|
||||
{
|
||||
if (arg.AsRune == new Rune ('j'))
|
||||
{
|
||||
arg.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[AutoInitShutdown]
|
||||
public void MouseEvent_Handled_Prevents_RightClick ()
|
||||
@@ -1222,33 +925,6 @@ public class TextFieldTests (ITestOutputHelper output)
|
||||
}
|
||||
}
|
||||
|
||||
[InlineData ("a")] // Lower than selection
|
||||
[InlineData ("aaaaaaaaaaa")] // Greater than selection
|
||||
[InlineData ("aaaa")] // Equal than selection
|
||||
[Theory]
|
||||
public void SetTextAndMoveCursorToEnd_WhenExistingSelection (string newText)
|
||||
{
|
||||
var tf = new TextField ();
|
||||
tf.Text = "fish";
|
||||
tf.CursorPosition = tf.Text.Length;
|
||||
|
||||
tf.NewKeyDownEvent (Key.CursorLeft);
|
||||
|
||||
tf.NewKeyDownEvent (Key.CursorLeft.WithShift);
|
||||
tf.NewKeyDownEvent (Key.CursorLeft.WithShift);
|
||||
|
||||
Assert.Equal (1, tf.CursorPosition);
|
||||
Assert.Equal (2, tf.SelectedLength);
|
||||
Assert.Equal ("is", tf.SelectedText);
|
||||
|
||||
tf.Text = newText;
|
||||
tf.CursorPosition = tf.Text.Length;
|
||||
|
||||
Assert.Equal (newText.Length, tf.CursorPosition);
|
||||
Assert.Equal (0, tf.SelectedLength);
|
||||
Assert.Null (tf.SelectedText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[TextFieldTestsAutoInitShutdown]
|
||||
public void Text_Replaces_Tabs_With_Empty_String ()
|
||||
@@ -1296,22 +972,6 @@ public class TextFieldTests (ITestOutputHelper output)
|
||||
Assert.Equal ("changing", _textField.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpaceHandling ()
|
||||
{
|
||||
var tf = new TextField { Width = 10, Text = " " };
|
||||
|
||||
var ev = new MouseEventArgs { Position = new (0, 0), Flags = MouseFlags.Button1DoubleClicked };
|
||||
|
||||
tf.NewMouseEvent (ev);
|
||||
Assert.Equal (1, tf.SelectedLength);
|
||||
|
||||
ev = new () { Position = new (1, 0), Flags = MouseFlags.Button1DoubleClicked };
|
||||
|
||||
tf.NewMouseEvent (ev);
|
||||
Assert.Equal (1, tf.SelectedLength);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[TextFieldTestsAutoInitShutdown]
|
||||
public void Used_Is_False ()
|
||||
@@ -1631,77 +1291,6 @@ public class TextFieldTests (ITestOutputHelper output)
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WordBackward_WordForward_Mixed ()
|
||||
{
|
||||
var tf = new TextField { Width = 30, Text = "Test with0. and!.?;-@+" };
|
||||
tf.BeginInit ();
|
||||
tf.EndInit ();
|
||||
|
||||
tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
|
||||
Assert.Equal (15, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
|
||||
Assert.Equal (12, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
|
||||
Assert.Equal (10, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
|
||||
Assert.Equal (5, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
|
||||
Assert.Equal (0, tf.CursorPosition);
|
||||
|
||||
tf.NewKeyDownEvent (Key.CursorRight.WithCtrl);
|
||||
Assert.Equal (5, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorRight.WithCtrl);
|
||||
Assert.Equal (10, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorRight.WithCtrl);
|
||||
Assert.Equal (12, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorRight.WithCtrl);
|
||||
Assert.Equal (15, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorRight.WithCtrl);
|
||||
Assert.Equal (22, tf.CursorPosition);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WordBackward_WordForward_SelectedText_With_Accent ()
|
||||
{
|
||||
var text = "Les Misérables movie.";
|
||||
var tf = new TextField { Width = 30, Text = text };
|
||||
|
||||
Assert.Equal (21, text.Length);
|
||||
Assert.Equal (21, tf.Text.GetRuneCount ());
|
||||
Assert.Equal (21, tf.Text.GetColumns ());
|
||||
|
||||
List<Rune> runes = tf.Text.ToRuneList ();
|
||||
Assert.Equal (21, runes.Count);
|
||||
Assert.Equal (21, tf.Text.Length);
|
||||
|
||||
for (var i = 0; i < runes.Count; i++)
|
||||
{
|
||||
char cs = text [i];
|
||||
var cus = (char)runes [i].Value;
|
||||
Assert.Equal (cs, cus);
|
||||
}
|
||||
|
||||
var idx = 15;
|
||||
Assert.Equal ('m', text [idx]);
|
||||
Assert.Equal ('m', (char)runes [idx].Value);
|
||||
Assert.Equal ("m", runes [idx].ToString ());
|
||||
|
||||
Assert.True (
|
||||
tf.NewMouseEvent (
|
||||
new () { Position = new (idx, 1), Flags = MouseFlags.Button1DoubleClicked, View = tf }
|
||||
)
|
||||
);
|
||||
Assert.Equal ("movie.", tf.SelectedText);
|
||||
|
||||
Assert.True (
|
||||
tf.NewMouseEvent (
|
||||
new () { Position = new (idx + 1, 1), Flags = MouseFlags.Button1DoubleClicked, View = tf }
|
||||
)
|
||||
);
|
||||
Assert.Equal ("movie.", tf.SelectedText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[TextFieldTestsAutoInitShutdown]
|
||||
public void WordForward_With_No_Selection ()
|
||||
@@ -2056,115 +1645,6 @@ Les Miśerables",
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Autocomplete_Popup_Added_To_SuperView_On_Init ()
|
||||
{
|
||||
View superView = new ()
|
||||
{
|
||||
CanFocus = true
|
||||
};
|
||||
|
||||
TextField t = new ();
|
||||
|
||||
superView.Add (t);
|
||||
Assert.Single (superView.SubViews);
|
||||
|
||||
superView.BeginInit ();
|
||||
superView.EndInit ();
|
||||
|
||||
Assert.Equal (2, superView.SubViews.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Autocomplete__Added_To_SuperView_On_Add ()
|
||||
{
|
||||
View superView = new ()
|
||||
{
|
||||
CanFocus = true,
|
||||
Id = "superView"
|
||||
};
|
||||
|
||||
superView.BeginInit ();
|
||||
superView.EndInit ();
|
||||
Assert.Empty (superView.SubViews);
|
||||
|
||||
TextField t = new ()
|
||||
{
|
||||
Id = "t"
|
||||
};
|
||||
|
||||
superView.Add (t);
|
||||
|
||||
Assert.Equal (2, superView.SubViews.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Right_CursorAtEnd_WithSelection_ShouldClearSelection ()
|
||||
{
|
||||
var tf = new TextField
|
||||
{
|
||||
Text = "Hello",
|
||||
};
|
||||
tf.SetFocus ();
|
||||
tf.SelectAll ();
|
||||
tf.CursorPosition = 5;
|
||||
|
||||
// When there is selected text and the cursor is at the end of the text field
|
||||
Assert.Equal ("Hello", tf.SelectedText);
|
||||
|
||||
// Pressing right should not move focus, instead it should clear selection
|
||||
Assert.True (tf.NewKeyDownEvent (Key.CursorRight));
|
||||
Assert.Null (tf.SelectedText);
|
||||
|
||||
// Now that the selection is cleared another right keypress should move focus
|
||||
Assert.False (tf.NewKeyDownEvent (Key.CursorRight));
|
||||
}
|
||||
[Fact]
|
||||
public void Left_CursorAtStart_WithSelection_ShouldClearSelection ()
|
||||
{
|
||||
var tf = new TextField
|
||||
{
|
||||
Text = "Hello",
|
||||
};
|
||||
tf.SetFocus ();
|
||||
|
||||
tf.CursorPosition = 2;
|
||||
Assert.True (tf.NewKeyDownEvent (Key.CursorLeft.WithShift));
|
||||
Assert.True (tf.NewKeyDownEvent (Key.CursorLeft.WithShift));
|
||||
|
||||
// When there is selected text and the cursor is at the start of the text field
|
||||
Assert.Equal ("He", tf.SelectedText);
|
||||
|
||||
// Pressing left should not move focus, instead it should clear selection
|
||||
Assert.True (tf.NewKeyDownEvent (Key.CursorLeft));
|
||||
Assert.Null (tf.SelectedText);
|
||||
|
||||
// When clearing selected text with left the cursor should be at the start of the selection
|
||||
Assert.Equal (0, tf.CursorPosition);
|
||||
|
||||
// Now that the selection is cleared another left keypress should move focus
|
||||
Assert.False (tf.NewKeyDownEvent (Key.CursorLeft));
|
||||
}
|
||||
[Fact]
|
||||
public void Autocomplete_Visible_False_By_Default ()
|
||||
{
|
||||
View superView = new ()
|
||||
{
|
||||
CanFocus = true
|
||||
};
|
||||
|
||||
TextField t = new ();
|
||||
|
||||
superView.Add (t);
|
||||
superView.BeginInit ();
|
||||
superView.EndInit ();
|
||||
|
||||
Assert.Equal (2, superView.SubViews.Count);
|
||||
|
||||
Assert.True (t.Visible);
|
||||
Assert.False (t.Autocomplete.Visible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[AutoInitShutdown]
|
||||
public void Draw_Esc_Rune ()
|
||||
|
||||
@@ -52,7 +52,8 @@ public class KeyTests
|
||||
{ "Ctrl-A", Key.A.WithCtrl },
|
||||
{ "Alt-A", Key.A.WithAlt },
|
||||
{ "A-Ctrl", Key.A.WithCtrl },
|
||||
{ "Alt-A-Ctrl", Key.A.WithCtrl.WithAlt }
|
||||
{ "Alt-A-Ctrl", Key.A.WithCtrl.WithAlt },
|
||||
{ "📄", (KeyCode)0x1F4C4 }
|
||||
};
|
||||
|
||||
[Theory]
|
||||
@@ -120,10 +121,13 @@ public class KeyTests
|
||||
[InlineData ('\'', (KeyCode)'\'')]
|
||||
[InlineData ('\xFFFF', (KeyCode)0xFFFF)]
|
||||
[InlineData ('\x0', (KeyCode)0x0)]
|
||||
public void Cast_Char_To_Key (char ch, KeyCode expectedKeyCode)
|
||||
public void Cast_Char_Int_To_Key (char ch, KeyCode expectedKeyCode)
|
||||
{
|
||||
var key = (Key)ch;
|
||||
Assert.Equal (expectedKeyCode, key.KeyCode);
|
||||
|
||||
key = (int)ch;
|
||||
Assert.Equal (expectedKeyCode, key.KeyCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -140,23 +144,25 @@ public class KeyTests
|
||||
[InlineData (KeyCode.A | KeyCode.ShiftMask, KeyCode.A | KeyCode.ShiftMask)]
|
||||
[InlineData (KeyCode.Z, KeyCode.Z)]
|
||||
[InlineData (KeyCode.Space, KeyCode.Space)]
|
||||
public void Cast_KeyCode_To_Key (KeyCode cdk, KeyCode expected)
|
||||
public void Cast_KeyCode_Int_To_Key (KeyCode cdk, KeyCode expected)
|
||||
{
|
||||
// explicit
|
||||
// KeyCode
|
||||
var key = (Key)cdk;
|
||||
Assert.Equal (((Key)expected).ToString (), key.ToString ());
|
||||
|
||||
// implicit
|
||||
key = cdk;
|
||||
// Int
|
||||
key = key.AsRune.Value;
|
||||
Assert.Equal (((Key)expected).ToString (), key.ToString ());
|
||||
}
|
||||
|
||||
// string cast operators
|
||||
[Fact]
|
||||
public void Cast_String_To_Key ()
|
||||
[Theory]
|
||||
[InlineData ("Ctrl+Q", KeyCode.Q | KeyCode.CtrlMask)]
|
||||
[InlineData ("📄", (KeyCode)0x1F4C4)]
|
||||
public void Cast_String_To_Key (string str, KeyCode expectedKeyCode)
|
||||
{
|
||||
var key = (Key)"Ctrl+Q";
|
||||
Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, key.KeyCode);
|
||||
var key = (Key)str;
|
||||
Assert.Equal (expectedKeyCode, key.KeyCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -190,12 +196,37 @@ public class KeyTests
|
||||
[InlineData ('\'', (KeyCode)'\'')]
|
||||
[InlineData ('\xFFFF', (KeyCode)0xFFFF)]
|
||||
[InlineData ('\x0', (KeyCode)0x0)]
|
||||
public void Constructor_Char (char ch, KeyCode expectedKeyCode)
|
||||
public void Constructor_Char_Int (char ch, KeyCode expectedKeyCode)
|
||||
{
|
||||
var key = new Key (ch);
|
||||
Assert.Equal (expectedKeyCode, key.KeyCode);
|
||||
|
||||
key = new ((int)ch);
|
||||
Assert.Equal (expectedKeyCode, key.KeyCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (0x1F4C4, (KeyCode)0x1F4C4, "📄")]
|
||||
[InlineData (0x1F64B, (KeyCode)0x1F64B, "🙋")]
|
||||
[InlineData (0x1F46A, (KeyCode)0x1F46A, "👪")]
|
||||
public void Constructor_Int_Non_Bmp (int value, KeyCode expectedKeyCode, string expectedString)
|
||||
{
|
||||
var key = new Key (value);
|
||||
Assert.Equal (expectedKeyCode, key.KeyCode);
|
||||
Assert.Equal (expectedString, key.AsRune.ToString ());
|
||||
Assert.Equal (expectedString, key.ToString ());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (-1)]
|
||||
[InlineData (0x11FFFF)]
|
||||
public void Constructor_Int_Invalid_Throws (int keyInt) { Assert.Throws<ArgumentOutOfRangeException> (() => new Key (keyInt)); }
|
||||
|
||||
[Theory]
|
||||
[InlineData ('\ud83d')]
|
||||
[InlineData ('\udcc4')]
|
||||
public void Constructor_Int_Surrogate_Throws (int keyInt) { Assert.Throws<ArgumentException> (() => new Key (keyInt)); }
|
||||
|
||||
[Fact]
|
||||
public void Constructor_Default_ShouldSetKeyToNull ()
|
||||
{
|
||||
|
||||
554
Tests/UnitTestsParallelizable/Views/TextFieldTests.cs
Normal file
554
Tests/UnitTestsParallelizable/Views/TextFieldTests.cs
Normal file
@@ -0,0 +1,554 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Terminal.Gui.ViewsTests;
|
||||
|
||||
public class TextFieldTests
|
||||
{
|
||||
[Fact]
|
||||
public void Cancel_TextChanging_ThenBackspace ()
|
||||
{
|
||||
var tf = new TextField ();
|
||||
tf.SetFocus ();
|
||||
tf.NewKeyDownEvent (Key.A.WithShift);
|
||||
Assert.Equal ("A", tf.Text);
|
||||
|
||||
// cancel the next keystroke
|
||||
tf.TextChanging += (s, e) => e.Cancel = e.NewValue == "AB";
|
||||
tf.NewKeyDownEvent (Key.B.WithShift);
|
||||
|
||||
// B was canceled so should just be A
|
||||
Assert.Equal ("A", tf.Text);
|
||||
|
||||
// now delete the A
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
|
||||
Assert.Equal ("", tf.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HistoryText_IsDirty_ClearHistoryChanges ()
|
||||
{
|
||||
var text = "Testing";
|
||||
var tf = new TextField { Text = text };
|
||||
tf.BeginInit ();
|
||||
tf.EndInit ();
|
||||
|
||||
Assert.Equal (text, tf.Text);
|
||||
tf.ClearHistoryChanges ();
|
||||
Assert.False (tf.IsDirty);
|
||||
|
||||
Assert.True (tf.NewKeyDownEvent (Key.A.WithShift));
|
||||
Assert.Equal ($"{text}A", tf.Text);
|
||||
Assert.True (tf.IsDirty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Space_Does_Not_Raise_Selected ()
|
||||
{
|
||||
TextField tf = new ();
|
||||
|
||||
tf.Selecting += (sender, args) => Assert.Fail ("Selected should not be raied.");
|
||||
|
||||
Toplevel top = new ();
|
||||
top.Add (tf);
|
||||
tf.SetFocus ();
|
||||
top.NewKeyDownEvent (Key.Space);
|
||||
|
||||
top.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Enter_Does_Not_Raise_Selected ()
|
||||
{
|
||||
TextField tf = new ();
|
||||
|
||||
var selectingCount = 0;
|
||||
tf.Selecting += (sender, args) => selectingCount++;
|
||||
|
||||
Toplevel top = new ();
|
||||
top.Add (tf);
|
||||
tf.SetFocus ();
|
||||
top.NewKeyDownEvent (Key.Enter);
|
||||
|
||||
Assert.Equal (0, selectingCount);
|
||||
|
||||
top.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Enter_Raises_Accepted ()
|
||||
{
|
||||
TextField tf = new ();
|
||||
|
||||
var acceptedCount = 0;
|
||||
tf.Accepting += (sender, args) => acceptedCount++;
|
||||
|
||||
Toplevel top = new ();
|
||||
top.Add (tf);
|
||||
tf.SetFocus ();
|
||||
top.NewKeyDownEvent (Key.Enter);
|
||||
|
||||
Assert.Equal (1, acceptedCount);
|
||||
|
||||
top.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HotKey_Command_SetsFocus ()
|
||||
{
|
||||
var view = new TextField ();
|
||||
|
||||
view.CanFocus = true;
|
||||
Assert.False (view.HasFocus);
|
||||
view.InvokeCommand (Command.HotKey);
|
||||
Assert.True (view.HasFocus);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HotKey_Command_Does_Not_Accept ()
|
||||
{
|
||||
var view = new TextField ();
|
||||
var accepted = false;
|
||||
view.Accepting += OnAccept;
|
||||
view.InvokeCommand (Command.HotKey);
|
||||
|
||||
Assert.False (accepted);
|
||||
|
||||
return;
|
||||
|
||||
void OnAccept (object sender, CommandEventArgs e) { accepted = true; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accepted_Command_Fires_Accept ()
|
||||
{
|
||||
var view = new TextField ();
|
||||
|
||||
var accepted = false;
|
||||
view.Accepting += Accept;
|
||||
view.InvokeCommand (Command.Accept);
|
||||
Assert.True (accepted);
|
||||
|
||||
return;
|
||||
|
||||
void Accept (object sender, CommandEventArgs e) { accepted = true; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accepted_No_Handler_Enables_Default_Button_Accept ()
|
||||
{
|
||||
var superView = new Window
|
||||
{
|
||||
Id = "superView"
|
||||
};
|
||||
|
||||
var tf = new TextField
|
||||
{
|
||||
Id = "tf"
|
||||
};
|
||||
|
||||
var button = new Button
|
||||
{
|
||||
Id = "button",
|
||||
IsDefault = true
|
||||
};
|
||||
|
||||
superView.Add (tf, button);
|
||||
|
||||
var buttonAccept = 0;
|
||||
button.Accepting += ButtonAccept;
|
||||
|
||||
tf.SetFocus ();
|
||||
Assert.True (tf.HasFocus);
|
||||
|
||||
superView.NewKeyDownEvent (Key.Enter);
|
||||
Assert.Equal (1, buttonAccept);
|
||||
|
||||
button.SetFocus ();
|
||||
superView.NewKeyDownEvent (Key.Enter);
|
||||
Assert.Equal (2, buttonAccept);
|
||||
|
||||
return;
|
||||
|
||||
void ButtonAccept (object sender, CommandEventArgs e) { buttonAccept++; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accepted_Cancel_Event_HandlesCommand ()
|
||||
{
|
||||
//var super = new View ();
|
||||
var view = new TextField ();
|
||||
|
||||
//super.Add (view);
|
||||
|
||||
//var superAcceptedInvoked = false;
|
||||
|
||||
var tfAcceptedInvoked = false;
|
||||
var handle = false;
|
||||
view.Accepting += TextViewAccept;
|
||||
Assert.False (view.InvokeCommand (Command.Accept));
|
||||
Assert.True (tfAcceptedInvoked);
|
||||
|
||||
tfAcceptedInvoked = false;
|
||||
handle = true;
|
||||
view.Accepting += TextViewAccept;
|
||||
Assert.True (view.InvokeCommand (Command.Accept));
|
||||
Assert.True (tfAcceptedInvoked);
|
||||
|
||||
return;
|
||||
|
||||
void TextViewAccept (object sender, CommandEventArgs e)
|
||||
{
|
||||
tfAcceptedInvoked = true;
|
||||
e.Cancel = handle;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnEnter_Does_Not_Throw_If_Not_IsInitialized_SetCursorVisibility ()
|
||||
{
|
||||
var top = new Toplevel ();
|
||||
var tf = new TextField { Width = 10 };
|
||||
top.Add (tf);
|
||||
|
||||
Exception exception = Record.Exception (() => tf.SetFocus ());
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Backspace_From_End ()
|
||||
{
|
||||
var tf = new TextField { Text = "ABC" };
|
||||
tf.SetFocus ();
|
||||
Assert.Equal ("ABC", tf.Text);
|
||||
tf.BeginInit ();
|
||||
tf.EndInit ();
|
||||
|
||||
Assert.Equal (3, tf.CursorPosition);
|
||||
|
||||
// now delete the C
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
Assert.Equal ("AB", tf.Text);
|
||||
Assert.Equal (2, tf.CursorPosition);
|
||||
|
||||
// then delete the B
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
Assert.Equal ("A", tf.Text);
|
||||
Assert.Equal (1, tf.CursorPosition);
|
||||
|
||||
// then delete the A
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
Assert.Equal ("", tf.Text);
|
||||
Assert.Equal (0, tf.CursorPosition);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Backspace_From_Middle ()
|
||||
{
|
||||
var tf = new TextField { Text = "ABC" };
|
||||
tf.SetFocus ();
|
||||
tf.CursorPosition = 2;
|
||||
Assert.Equal ("ABC", tf.Text);
|
||||
|
||||
// now delete the B
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
Assert.Equal ("AC", tf.Text);
|
||||
|
||||
// then delete the A
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
Assert.Equal ("C", tf.Text);
|
||||
|
||||
// then delete nothing
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
Assert.Equal ("C", tf.Text);
|
||||
|
||||
// now delete the C
|
||||
tf.CursorPosition = 1;
|
||||
tf.NewKeyDownEvent (Key.Backspace);
|
||||
Assert.Equal ("", tf.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyDown_Handled_Prevents_Input ()
|
||||
{
|
||||
var tf = new TextField ();
|
||||
tf.KeyDown += HandleJKey;
|
||||
|
||||
tf.NewKeyDownEvent (Key.A);
|
||||
Assert.Equal ("a", tf.Text);
|
||||
|
||||
// SuppressKey suppresses the 'j' key
|
||||
tf.NewKeyDownEvent (Key.J);
|
||||
Assert.Equal ("a", tf.Text);
|
||||
|
||||
tf.KeyDown -= HandleJKey;
|
||||
|
||||
// Now that the delegate has been removed we can type j again
|
||||
tf.NewKeyDownEvent (Key.J);
|
||||
Assert.Equal ("aj", tf.Text);
|
||||
|
||||
return;
|
||||
|
||||
void HandleJKey (object s, Key arg)
|
||||
{
|
||||
if (arg.AsRune == new Rune ('j'))
|
||||
{
|
||||
arg.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[InlineData ("a")] // Lower than selection
|
||||
[InlineData ("aaaaaaaaaaa")] // Greater than selection
|
||||
[InlineData ("aaaa")] // Equal than selection
|
||||
[Theory]
|
||||
public void SetTextAndMoveCursorToEnd_WhenExistingSelection (string newText)
|
||||
{
|
||||
var tf = new TextField ();
|
||||
tf.Text = "fish";
|
||||
tf.CursorPosition = tf.Text.Length;
|
||||
|
||||
tf.NewKeyDownEvent (Key.CursorLeft);
|
||||
|
||||
tf.NewKeyDownEvent (Key.CursorLeft.WithShift);
|
||||
tf.NewKeyDownEvent (Key.CursorLeft.WithShift);
|
||||
|
||||
Assert.Equal (1, tf.CursorPosition);
|
||||
Assert.Equal (2, tf.SelectedLength);
|
||||
Assert.Equal ("is", tf.SelectedText);
|
||||
|
||||
tf.Text = newText;
|
||||
tf.CursorPosition = tf.Text.Length;
|
||||
|
||||
Assert.Equal (newText.Length, tf.CursorPosition);
|
||||
Assert.Equal (0, tf.SelectedLength);
|
||||
Assert.Null (tf.SelectedText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpaceHandling ()
|
||||
{
|
||||
var tf = new TextField { Width = 10, Text = " " };
|
||||
|
||||
var ev = new MouseEventArgs { Position = new (0, 0), Flags = MouseFlags.Button1DoubleClicked };
|
||||
|
||||
tf.NewMouseEvent (ev);
|
||||
Assert.Equal (1, tf.SelectedLength);
|
||||
|
||||
ev = new () { Position = new (1, 0), Flags = MouseFlags.Button1DoubleClicked };
|
||||
|
||||
tf.NewMouseEvent (ev);
|
||||
Assert.Equal (1, tf.SelectedLength);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WordBackward_WordForward_Mixed ()
|
||||
{
|
||||
var tf = new TextField { Width = 30, Text = "Test with0. and!.?;-@+" };
|
||||
tf.BeginInit ();
|
||||
tf.EndInit ();
|
||||
|
||||
tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
|
||||
Assert.Equal (15, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
|
||||
Assert.Equal (12, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
|
||||
Assert.Equal (10, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
|
||||
Assert.Equal (5, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
|
||||
Assert.Equal (0, tf.CursorPosition);
|
||||
|
||||
tf.NewKeyDownEvent (Key.CursorRight.WithCtrl);
|
||||
Assert.Equal (5, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorRight.WithCtrl);
|
||||
Assert.Equal (10, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorRight.WithCtrl);
|
||||
Assert.Equal (12, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorRight.WithCtrl);
|
||||
Assert.Equal (15, tf.CursorPosition);
|
||||
tf.NewKeyDownEvent (Key.CursorRight.WithCtrl);
|
||||
Assert.Equal (22, tf.CursorPosition);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WordBackward_WordForward_SelectedText_With_Accent ()
|
||||
{
|
||||
var text = "Les Misérables movie.";
|
||||
var tf = new TextField { Width = 30, Text = text };
|
||||
|
||||
Assert.Equal (21, text.Length);
|
||||
Assert.Equal (21, tf.Text.GetRuneCount ());
|
||||
Assert.Equal (21, tf.Text.GetColumns ());
|
||||
|
||||
List<Rune> runes = tf.Text.ToRuneList ();
|
||||
Assert.Equal (21, runes.Count);
|
||||
Assert.Equal (21, tf.Text.Length);
|
||||
|
||||
for (var i = 0; i < runes.Count; i++)
|
||||
{
|
||||
char cs = text [i];
|
||||
var cus = (char)runes [i].Value;
|
||||
Assert.Equal (cs, cus);
|
||||
}
|
||||
|
||||
var idx = 15;
|
||||
Assert.Equal ('m', text [idx]);
|
||||
Assert.Equal ('m', (char)runes [idx].Value);
|
||||
Assert.Equal ("m", runes [idx].ToString ());
|
||||
|
||||
Assert.True (
|
||||
tf.NewMouseEvent (
|
||||
new () { Position = new (idx, 1), Flags = MouseFlags.Button1DoubleClicked, View = tf }
|
||||
)
|
||||
);
|
||||
Assert.Equal ("movie.", tf.SelectedText);
|
||||
|
||||
Assert.True (
|
||||
tf.NewMouseEvent (
|
||||
new () { Position = new (idx + 1, 1), Flags = MouseFlags.Button1DoubleClicked, View = tf }
|
||||
)
|
||||
);
|
||||
Assert.Equal ("movie.", tf.SelectedText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Autocomplete_Popup_Added_To_SuperView_On_Init ()
|
||||
{
|
||||
View superView = new ()
|
||||
{
|
||||
CanFocus = true
|
||||
};
|
||||
|
||||
TextField t = new ();
|
||||
|
||||
superView.Add (t);
|
||||
Assert.Single (superView.SubViews);
|
||||
|
||||
superView.BeginInit ();
|
||||
superView.EndInit ();
|
||||
|
||||
Assert.Equal (2, superView.SubViews.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Autocomplete__Added_To_SuperView_On_Add ()
|
||||
{
|
||||
View superView = new ()
|
||||
{
|
||||
CanFocus = true,
|
||||
Id = "superView"
|
||||
};
|
||||
|
||||
superView.BeginInit ();
|
||||
superView.EndInit ();
|
||||
Assert.Empty (superView.SubViews);
|
||||
|
||||
TextField t = new ()
|
||||
{
|
||||
Id = "t"
|
||||
};
|
||||
|
||||
superView.Add (t);
|
||||
|
||||
Assert.Equal (2, superView.SubViews.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Right_CursorAtEnd_WithSelection_ShouldClearSelection ()
|
||||
{
|
||||
var tf = new TextField
|
||||
{
|
||||
Text = "Hello"
|
||||
};
|
||||
tf.SetFocus ();
|
||||
tf.SelectAll ();
|
||||
tf.CursorPosition = 5;
|
||||
|
||||
// When there is selected text and the cursor is at the end of the text field
|
||||
Assert.Equal ("Hello", tf.SelectedText);
|
||||
|
||||
// Pressing right should not move focus, instead it should clear selection
|
||||
Assert.True (tf.NewKeyDownEvent (Key.CursorRight));
|
||||
Assert.Null (tf.SelectedText);
|
||||
|
||||
// Now that the selection is cleared another right keypress should move focus
|
||||
Assert.False (tf.NewKeyDownEvent (Key.CursorRight));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Left_CursorAtStart_WithSelection_ShouldClearSelection ()
|
||||
{
|
||||
var tf = new TextField
|
||||
{
|
||||
Text = "Hello"
|
||||
};
|
||||
tf.SetFocus ();
|
||||
|
||||
tf.CursorPosition = 2;
|
||||
Assert.True (tf.NewKeyDownEvent (Key.CursorLeft.WithShift));
|
||||
Assert.True (tf.NewKeyDownEvent (Key.CursorLeft.WithShift));
|
||||
|
||||
// When there is selected text and the cursor is at the start of the text field
|
||||
Assert.Equal ("He", tf.SelectedText);
|
||||
|
||||
// Pressing left should not move focus, instead it should clear selection
|
||||
Assert.True (tf.NewKeyDownEvent (Key.CursorLeft));
|
||||
Assert.Null (tf.SelectedText);
|
||||
|
||||
// When clearing selected text with left the cursor should be at the start of the selection
|
||||
Assert.Equal (0, tf.CursorPosition);
|
||||
|
||||
// Now that the selection is cleared another left keypress should move focus
|
||||
Assert.False (tf.NewKeyDownEvent (Key.CursorLeft));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Autocomplete_Visible_False_By_Default ()
|
||||
{
|
||||
View superView = new ()
|
||||
{
|
||||
CanFocus = true
|
||||
};
|
||||
|
||||
TextField t = new ();
|
||||
|
||||
superView.Add (t);
|
||||
superView.BeginInit ();
|
||||
superView.EndInit ();
|
||||
|
||||
Assert.Equal (2, superView.SubViews.Count);
|
||||
|
||||
Assert.True (t.Visible);
|
||||
Assert.False (t.Autocomplete.Visible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InsertText_Bmp_SurrogatePair_Non_Bmp_Invalid_SurrogatePair ()
|
||||
{
|
||||
var tf = new TextField ();
|
||||
|
||||
//📄 == \ud83d\udcc4 == \U0001F4C4
|
||||
// <20> == Rune.ReplacementChar
|
||||
tf.InsertText ("aA,;\ud83d\udcc4\U0001F4C4\udcc4\ud83d");
|
||||
Assert.Equal ("aA,;📄📄<F09F9384><F09F9384>", tf.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PositionCursor_Respect_GetColumns ()
|
||||
{
|
||||
var tf = new TextField { Width = 5 };
|
||||
tf.BeginInit ();
|
||||
tf.EndInit ();
|
||||
|
||||
tf.NewKeyDownEvent (new ("📄"));
|
||||
Assert.Equal (1, tf.CursorPosition);
|
||||
Assert.Equal (new (2, 0), tf.PositionCursor ());
|
||||
Assert.Equal ("📄", tf.Text);
|
||||
|
||||
tf.NewKeyDownEvent (new (KeyCode.A));
|
||||
Assert.Equal (2, tf.CursorPosition);
|
||||
Assert.Equal (new (3, 0), tf.PositionCursor ());
|
||||
Assert.Equal ("📄a", tf.Text);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user