diff --git a/Terminal.Gui/Views/TextValidateField.cs b/Terminal.Gui/Views/TextValidateField.cs new file mode 100644 index 000000000..942683a5a --- /dev/null +++ b/Terminal.Gui/Views/TextValidateField.cs @@ -0,0 +1,610 @@ +// +// TextValidateField.cs: single-line text editor with validation through providers. +// +// Authors: +// José Miguel Perricone (jmperricone@hotmail.com) +// + +using NStack; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text.RegularExpressions; +using Terminal.Gui.TextValidateProviders; + +namespace Terminal.Gui { + + namespace TextValidateProviders { + /// + /// TextValidateField Providers Interface. + /// All TextValidateField are created with a ITextValidateProvider. + /// + public interface ITextValidateProvider { + /// + /// Set that this provider uses a fixed width. + /// e.g. Masked ones are fixed. + /// + bool Fixed { get; } + + /// + /// Set Cursor position to . + /// + /// + /// Return first valid position. + int Cursor (int pos); + + /// + /// First valid position before . + /// + /// + /// New cursor position if any, otherwise returns + int CursorLeft (int pos); + + /// + /// First valid position after . + /// + /// Current position. + /// New cursor position if any, otherwise returns + int CursorRight (int pos); + + /// + /// Find the first valid character position. + /// + /// New cursor position. + int CursorStart (); + + /// + /// Find the last valid character position. + /// + /// New cursor position. + int CursorEnd (); + + /// + /// Deletes the current character in . + /// + /// + /// true if the character was successfully removed, otherwise false. + bool Delete (int pos); + + /// + /// Insert character in position . + /// + /// + /// + /// true if the character was successfully inserted, otherwise false. + bool InsertAt (char ch, int pos); + + /// + /// True if the input is valid, otherwise false. + /// + bool IsValid { get; } + + /// + /// Set the input text, and get the formatted string for display. + /// + ustring Text { get; set; } + + /// + /// Mask used for validation. + /// Not always a mask, can by a regex expression. + /// TODO: Maybe we can change the name. + /// + ustring Mask { get; set; } + } + + ////////////////////////////////////////////////////////////////////////////// + // PROVIDERS + ////////////////////////////////////////////////////////////////////////////// + + #region NetMaskedTextProvider + + /// + /// .Net MaskedTextProvider Provider for TextValidateField. + /// + /// Wrapper around MaskedTextProvider + /// Masking elements + /// + public class NetMaskedTextProvider : ITextValidateProvider { + MaskedTextProvider provider; + string text; + + /// + /// Empty Constructor + /// + public NetMaskedTextProvider () { } + + /// + public ustring Mask { + get { + return provider?.Mask; + } + set { + provider = new MaskedTextProvider (value == ustring.Empty ? "&&&&&&" : value.ToString ()); + if (string.IsNullOrEmpty (text) == false) { + provider.Set (text); + } + } + } + + /// + public ustring Text { + get { + return provider.ToDisplayString (); + } + set { + text = value.ToString (); + provider.Set (value.ToString ()); + } + } + + /// + public bool IsValid => provider.MaskCompleted; + + /// + public bool Fixed => true; + + /// + public int Cursor (int pos) + { + if (pos < 0) { + return CursorStart (); + } else if (pos > provider.Length) { + return CursorEnd (); + } else { + var p = provider.FindEditPositionFrom (pos, false); + if (p == -1) p = provider.FindEditPositionFrom (pos, true); + return p; + } + } + + /// + public int CursorStart () + { + return + provider.IsEditPosition (0) + ? 0 + : provider.FindEditPositionFrom (0, true); + } + + /// + public int CursorEnd () + { + return + provider.IsEditPosition (provider.Length - 1) + ? provider.Length - 1 + : provider.FindEditPositionFrom (provider.Length, false); + } + + /// + public int CursorLeft (int pos) + { + var c = provider.FindEditPositionFrom (pos - 1, false); + return c == -1 ? pos : c; + } + + /// + public int CursorRight (int pos) + { + var c = provider.FindEditPositionFrom (pos + 1, true); + return c == -1 ? pos : c; + } + + /// + public bool Delete (int pos) + { + return provider.Replace (' ', pos);// .RemoveAt (pos); + } + + /// + public bool InsertAt (char ch, int pos) + { + return provider.Replace (ch, pos); + } + } + #endregion + + #region TextRegexProvider + + /// + /// Regex Provider for TextValidateField. + /// + public class TextRegexProvider : ITextValidateProvider { + Regex regex; + List text; + List mask; + + /// + /// Empty Constructor + /// + public TextRegexProvider () { } + + /// + public ustring Mask { + get { + return ustring.Make (mask); + } + set { + mask = value.ToRuneList (); + CompileMask (); + SetupText (); + } + } + + /// + public ustring Text { + get { + return ustring.Make (text); + } + set { + text = value != ustring.Empty ? value.ToRuneList () : null; + SetupText (); + } + } + + /// + public bool IsValid { + get { + return Validate (text); + } + } + + /// + public bool Fixed => false; + + /// + /// When true, validates with the regex pattern on each input, preventing the input if it's not valid. + /// + public bool ValidateOnInput { get; set; } = true; + + bool Validate (List text) + { + var match = regex.Match (ustring.Make (text).ToString ()); + return match.Success; + } + + /// + public int Cursor (int pos) + { + if (pos < 0) { + return CursorStart (); + } else if (pos >= text.Count) { + return CursorEnd (); + } else { + return pos; + } + } + + /// + public int CursorStart () + { + return 0; + } + + /// + public int CursorEnd () + { + return text.Count; + } + + /// + public int CursorLeft (int pos) + { + if (pos > 0) { + return pos - 1; + } + return pos; + } + + /// + public int CursorRight (int pos) + { + if (pos < text.Count) { + return pos + 1; + } + return pos; + } + + /// + public bool Delete (int pos) + { + if (text.Count > 0 && pos < text.Count) { + text.RemoveAt (pos); + } + return true; + } + + /// + public bool InsertAt (char ch, int pos) + { + var aux = text.ToList (); + aux.Insert (pos, ch); + if (Validate (aux) || ValidateOnInput == false) { + text.Insert (pos, ch); + return true; + } + return false; + } + + void SetupText () + { + if (text != null && IsValid) { + return; + } + + text = new List (); + } + + /// + /// Compiles the regex pattern for validation./> + /// + private void CompileMask () + { + regex = new Regex (ustring.Make (mask).ToString (), RegexOptions.Compiled); + } + } + #endregion + } + + /// + /// Text field that validates input through a + /// + /// + public class TextValidateField : View where T : ITextValidateProvider { + + ITextValidateProvider provider; + int cursorPosition = 0; + + /// + /// Initializes a new instance of the class using positioning. + /// + public TextValidateField () : this (ustring.Empty) + { + } + + /// + /// Initializes a new instance of the class using positioning. + /// + /// Mask + public TextValidateField (ustring mask) : this (mask, ustring.Empty) { } + + /// + /// Initializes a new instance of the class using positioning. + /// + /// + /// Initial Value + public TextValidateField (ustring mask, ustring text) : base () + { + provider = Activator.CreateInstance (typeof (T)) as ITextValidateProvider; + + Mask = mask; + Text = text; + + this.Width = text == ustring.Empty ? 20 : Text.Length; + this.Height = 1; + this.CanFocus = true; + } + + /// + /// Get the Provider + /// + public T Provider => (T)provider; + + /// + public override bool MouseEvent (MouseEvent mouseEvent) + { + var c = provider.Cursor (mouseEvent.X - GetMargins (Frame.Width).left); + if (provider.Fixed == false && TextAlignment == TextAlignment.Right && Text.Length > 0) { + c += 1; + } + cursorPosition = c; + SetFocus (); + SetNeedsDisplay (); + return true; + } + + /// + /// Text + /// + public new ustring Text { + get { + return provider.Text; + } + set { + provider.Text = value; + + SetNeedsDisplay (); + } + } + + /// + /// Mask + /// + public ustring Mask { + get { + return provider.Mask; + } + set { + provider.Mask = value; + + cursorPosition = provider.CursorStart (); + + SetNeedsDisplay (); + } + } + + ///inheritdoc/> + public override void PositionCursor () + { + var (left, _) = GetMargins (Frame.Width); + + // Fixed = true, is for inputs thar have fixed width, like masked ones. + // Fixed = false, is for normal input. + // When it's right-aligned and it's a normal input, the cursor behaves differently. + if (provider.Fixed == false && TextAlignment == TextAlignment.Right) { + Move (cursorPosition + left - 1, 0); + } else { + Move (cursorPosition + left, 0); + } + } + + /// + /// Margins for text alignment. + /// + /// Total width + /// Left and right margins + (int left, int right) GetMargins (int width) + { + var count = Text.Length; + var total = width - count; + switch (TextAlignment) { + case TextAlignment.Left: + return (0, total); + case TextAlignment.Centered: + return (total / 2, (total / 2) + (total % 2)); + case TextAlignment.Right: + return (total, 0); + default: + return (0, total); + } + } + + /// + public override void Redraw (Rect bounds) + { + var bgcolor = !IsValid ? Color.BrightRed : ColorScheme.Focus.Background; + var textColor = new Attribute (ColorScheme.Focus.Foreground, bgcolor); + + var (margin_left, margin_right) = GetMargins (bounds.Width); + + Move (0, 0); + + // Left Margin + Driver.SetAttribute (textColor); + for (int i = 0; i < margin_left; i++) { + Driver.AddRune (' '); + } + + // Content + Driver.SetAttribute (textColor); + // Content + for (int i = 0; i < provider.Text.Length; i++) { + Driver.AddRune (provider.Text [i]); + } + + // Right Margin + Driver.SetAttribute (textColor); + for (int i = 0; i < margin_right; i++) { + Driver.AddRune (' '); + } + } + + /// + /// Try to move the cursor to the left. + /// + /// True if moved. + bool CursorLeft () + { + var current = cursorPosition; + cursorPosition = provider.CursorLeft (cursorPosition); + return current != cursorPosition; + } + + /// + /// Try to move the cursor to the right. + /// + /// True if moved. + bool CursorRight () + { + var current = cursorPosition; + cursorPosition = provider.CursorRight (cursorPosition); + return current != cursorPosition; + } + + /// + /// Delete char at cursor position - 1, moving the cursor. + /// + /// + bool BackspaceKeyHandler () + { + if (provider.Fixed == false && TextAlignment == TextAlignment.Right && cursorPosition <= 1) { + return false; + } + cursorPosition = provider.CursorLeft (cursorPosition); + provider.Delete (cursorPosition); + return true; + } + + /// + /// Deletes char at current position. + /// + /// + bool DeleteKeyHandler () + { + if (provider.Fixed == false && TextAlignment == TextAlignment.Right) { + cursorPosition = provider.CursorLeft (cursorPosition); + } + provider.Delete (cursorPosition); + return true; + } + + /// + /// Moves the cursor to first char. + /// + /// + bool HomeKeyHandler () + { + cursorPosition = provider.CursorStart (); + return true; + } + + /// + /// Moves the cursor to the last char. + /// + /// + bool EndKeyHandler () + { + cursorPosition = provider.CursorEnd (); + return true; + } + + /// + public override bool ProcessKey (KeyEvent kb) + { + switch (kb.Key) { + case Key.Home: HomeKeyHandler (); break; + case Key.End: EndKeyHandler (); break; + case Key.Delete: + case Key.DeleteChar: DeleteKeyHandler (); break; + case Key.Backspace: BackspaceKeyHandler (); break; + case Key.CursorLeft: CursorLeft (); break; + case Key.CursorRight: CursorRight (); break; + default: + if (kb.Key < Key.Space || kb.Key > Key.CharMask) + return false; + + var key = new Rune ((uint)kb.KeyValue); + + var inserted = provider.InsertAt ((char)key, cursorPosition); + + if (inserted) { + CursorRight (); + } + + break; + } + + SetNeedsDisplay (); + return true; + } + + /// + /// This property returns true if the input is valid. + /// + public virtual bool IsValid { + get { + return provider.IsValid; + } + } + } +} diff --git a/UICatalog/Scenarios/Text.cs b/UICatalog/Scenarios/Text.cs index 86f505a14..c05555aea 100644 --- a/UICatalog/Scenarios/Text.cs +++ b/UICatalog/Scenarios/Text.cs @@ -1,6 +1,9 @@ using System; using System.Text; using Terminal.Gui; +using Terminal.Gui.TextValidateProviders; + + namespace UICatalog { [ScenarioMetadata (Name: "Text Input Controls", Description: "Tests all text input controls")] @@ -101,6 +104,38 @@ namespace UICatalog { _timeField.TimeChanged += TimeChanged; + // MaskedTextProvider + var netProvider = new Label (".Net MaskedTextProvider [ 999 000 LLL >LLL| AAA aaa ]") { + X = Pos.Left (dateField), + Y = Pos.Bottom (dateField) + 1 + }; + Win.Add (netProvider); + + var netProviderField = new TextValidateField ("999 000 LLL >LLL| AAA aaa") { + X = Pos.Right (netProvider) + 1, + Y = Pos.Y (netProvider), + Width = 40, + TextAlignment = TextAlignment.Centered + }; + Win.Add (netProviderField); + + // TextRegexProvider + var regexProvider = new Label ("Gui.cs TextRegexProvider [ ^([0-9]?[0-9]?[0-9]|1000)$ ]") { + X = Pos.Left (netProvider), + Y = Pos.Bottom (netProvider) + 1 + }; + Win.Add (regexProvider); + + var regexProviderField = new TextValidateField ("^([0-9]?[0-9]?[0-9]|1000)$") { + X = Pos.Right (regexProvider) + 1, + Y = Pos.Y (regexProvider), + Width = 40, + TextAlignment = TextAlignment.Centered + }; + // Access the inner Provider to configure. + regexProviderField.Provider.ValidateOnInput = false; + + Win.Add (regexProviderField); } TimeField _timeField; @@ -109,7 +144,6 @@ namespace UICatalog { private void TimeChanged (DateTimeEventArgs e) { _labelMirroringTimeField.Text = _timeField.Text; - } } -} \ No newline at end of file +} diff --git a/UnitTests/TextValidateFieldTests.cs b/UnitTests/TextValidateFieldTests.cs new file mode 100644 index 000000000..536847495 --- /dev/null +++ b/UnitTests/TextValidateFieldTests.cs @@ -0,0 +1,560 @@ +using System.Text.RegularExpressions; +using Terminal.Gui.TextValidateProviders; + +using Xunit; + +namespace Terminal.Gui.Views { + public class TextValidateField_NET_Provider_Tests { + public TextValidateField_NET_Provider_Tests () + { + Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); + } + + [Fact] + public void Initialized_With_Cursor_On_First_Editable_Character () + { + // * + // 0123456789 + var field = new TextValidateField ("--(0000)--") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + + field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { })); + + Assert.Equal ("--(1___)--", field.Text); + } + + [Fact] + public void Input_Ilegal_Character () + { + // * + // 0123456789 + var field = new TextValidateField ("--(0000)--") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + + field.ProcessKey (new KeyEvent (Key.A, new KeyModifiers { })); + + Assert.Equal ("--(____)--", field.Text); + Assert.False (field.IsValid); + } + + [Fact] + public void Home_Key_First_Editable_Character () + { + // * + // 0123456789 + var field = new TextValidateField ("--(0000)--") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + + field.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers { })); + field.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers { })); + field.ProcessKey (new KeyEvent (Key.Home, new KeyModifiers { })); + + field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { })); + + Assert.Equal ("--(1___)--", field.Text); + Assert.False (field.IsValid); + } + + [Fact] + public void End_Key_Last_Editable_Character () + { + // * + // 0123456789 + var field = new TextValidateField ("--(0000)--") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + + field.ProcessKey (new KeyEvent (Key.End, new KeyModifiers { })); + + field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { })); + Assert.Equal ("--(___1)--", field.Text); + Assert.False (field.IsValid); + } + + [Fact] + public void Right_Key_Stops_In_Last_Editable_Character () + { + // * + // 0123456789 + var field = new TextValidateField ("--(0000)--") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + + for (int i = 0; i < 10; i++) { + field.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers { })); + } + field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { })); + + Assert.Equal ("--(___1)--", field.Text); + Assert.False (field.IsValid); + } + + [Fact] + public void Left_Key_Stops_In_First_Editable_Character () + { + // * + // 0123456789 + var field = new TextValidateField ("--(0000)--") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + + for (int i = 0; i < 10; i++) { + field.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers { })); + } + field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { })); + + Assert.Equal ("--(1___)--", field.Text); + Assert.False (field.IsValid); + } + + [Fact] + public void When_Valid_Is_Valid_True () + { + // **** + // 0123456789 + var field = new TextValidateField ("--(0000)--") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + + field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { })); + Assert.Equal ("--(1___)--", field.Text); + Assert.False (field.IsValid); + + field.ProcessKey (new KeyEvent (Key.D2, new KeyModifiers { })); + Assert.Equal ("--(12__)--", field.Text); + Assert.False (field.IsValid); + + field.ProcessKey (new KeyEvent (Key.D3, new KeyModifiers { })); + Assert.Equal ("--(123_)--", field.Text); + Assert.False (field.IsValid); + + field.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers { })); + Assert.Equal ("--(1234)--", field.Text); + Assert.True (field.IsValid); + } + + [Fact] + public void Insert_Skips_Non_Editable_Characters () + { + // ** ** + // 01234567890 + var field = new TextValidateField ("--(00-00)--") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + + field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { })); + Assert.Equal ("--(1_-__)--", field.Text); + Assert.False (field.IsValid); + + field.ProcessKey (new KeyEvent (Key.D2, new KeyModifiers { })); + Assert.Equal ("--(12-__)--", field.Text); + Assert.False (field.IsValid); + + field.ProcessKey (new KeyEvent (Key.D3, new KeyModifiers { })); + Assert.Equal ("--(12-3_)--", field.Text); + Assert.False (field.IsValid); + + field.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers { })); + Assert.Equal ("--(12-34)--", field.Text); + Assert.True (field.IsValid); + } + + + [Fact] + public void Initial_Value_Exact_Valid () + { + // **** + // 0123456789 + var field = new TextValidateField ("--(0000)--", "1234") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + + Assert.Equal ("--(1234)--", field.Text); + Assert.True (field.IsValid); + } + + [Fact] + public void Initial_Value_Bigger_Than_Mask_Discarded () + { + // **** + // 0123456789 + var field = new TextValidateField ("--(0000)--", "12345") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + + Assert.Equal ("--(____)--", field.Text); + Assert.False (field.IsValid); + } + + [Fact] + public void Initial_Value_Smaller_Than_Mask_Accepted () + { + // **** + // 0123456789 + var field = new TextValidateField ("--(0000)--", "123") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + + Assert.Equal ("--(123_)--", field.Text); + Assert.False (field.IsValid); + } + + [Fact] + public void Delete_Key_Dosent_Move_Cursor () + { + // **** + // 0123456789 + var field = new TextValidateField ("--(0000)--", "1234") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + + Assert.Equal ("--(1234)--", field.Text); + Assert.True (field.IsValid); + + field.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers { })); + field.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers { })); + field.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers { })); + + Assert.Equal ("--(_234)--", field.Text); + Assert.False (field.IsValid); + + field.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers { })); + field.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers { })); + + field.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers { })); + field.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers { })); + field.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers { })); + + Assert.Equal ("--(_2_4)--", field.Text); + Assert.False (field.IsValid); + } + + [Fact] + public void Backspace_Key_Deletes_Previous_Character () + { + // **** + // 0123456789 + var field = new TextValidateField ("--(0000)--", "1234") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + + // Go to the end. + field.ProcessKey (new KeyEvent (Key.End, new KeyModifiers { })); + + field.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers { })); + Assert.Equal ("--(12_4)--", field.Text); + Assert.False (field.IsValid); + + field.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers { })); + Assert.Equal ("--(1__4)--", field.Text); + Assert.False (field.IsValid); + + field.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers { })); + Assert.Equal ("--(___4)--", field.Text); + Assert.False (field.IsValid); + + // One more + field.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers { })); + Assert.Equal ("--(___4)--", field.Text); + Assert.False (field.IsValid); + } + + + [Fact] + public void Set_Text_After_Initialization () + { + // **** + // 0123456789 + var field = new TextValidateField ("--(0000)--") { + TextAlignment = TextAlignment.Left, + Width = 30 + }; + + field.Text = "1234"; + + Assert.Equal ("--(1234)--", field.Text); + Assert.True (field.IsValid); + } + + [Fact] + public void Changing_The_Mask_Tries_To_Keep_The_Previous_Text () + { + // **** + // 0123456789 + var field = new TextValidateField ("--(0000)--") { + TextAlignment = TextAlignment.Left, + Width = 30 + }; + + field.Text = "1234"; + Assert.Equal ("--(1234)--", field.Text); + Assert.True (field.IsValid); + + field.Mask = "--------(00000000)--------"; + Assert.Equal ("--------(1234____)--------", field.Text); + Assert.False (field.IsValid); + } + + [Fact] + public void MouseClick_Right_X_Greater_Than_Text_Width_Goes_To_Last_Editable_Position () + { + // **** + // 0123456789 + var field = new TextValidateField ("--(0000)--") { + TextAlignment = TextAlignment.Left, + Width = 30 + }; + + field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { })); + + Assert.Equal ("--(1___)--", field.Text); + Assert.False (field.IsValid); + + field.MouseEvent (new MouseEvent () { X = 25, Flags = MouseFlags.Button1Clicked }); + + field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { })); + + Assert.Equal ("--(1__1)--", field.Text); + Assert.False (field.IsValid); + } + } + + public class TextValidateField_Regex_Provider_Tests { + public TextValidateField_Regex_Provider_Tests () + { + Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); + } + + [Fact] + public void Input_Without_Validate_On_Input () + { + var field = new TextValidateField ("^[0-9][0-9][0-9]$") { + Width = 20 + }; + + // Let you input + field.Provider.ValidateOnInput = false; + + field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { })); + Assert.Equal ("1", field.Text); + Assert.False (field.IsValid); + + field.ProcessKey (new KeyEvent (Key.D2, new KeyModifiers { })); + Assert.Equal ("12", field.Text); + Assert.False (field.IsValid); + + field.ProcessKey (new KeyEvent (Key.D3, new KeyModifiers { })); + Assert.Equal ("123", field.Text); + Assert.True (field.IsValid); + + field.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers { })); + Assert.Equal ("1234", field.Text); + Assert.False (field.IsValid); + } + + [Fact] + public void Input_With_Validate_On_Input_Set_Text () + { + var field = new TextValidateField ("^[0-9][0-9][0-9]$") { + Width = 20 + }; + + // Input dosen't validates the pattern. + field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { })); + Assert.Equal ("", field.Text); + Assert.False (field.IsValid); + + // Dosen't match + field.Text = "12356"; + Assert.Equal ("", field.Text); + Assert.False (field.IsValid); + + // Yes. + field.Text = "123"; + Assert.Equal ("123", field.Text); + Assert.True (field.IsValid); + } + + [Fact] + public void Empty_Mask_Validates_Everything () + { + // Maybe it's not the right behaviour. + + var field = new TextValidateField () { + Width = 20 + }; + + field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { })); + Assert.Equal ("1", field.Text); + Assert.True (field.IsValid); + } + + [Fact] + public void Text_With_All_Charset () + { + var field = new TextValidateField ("^[0-9][0-9][0-9]$") { + Width = 20 + }; + + var text = ""; + for (int i = 0; i < 255; i++) { + text += (char)i; + } + + field.Text = text; + + Assert.False (field.IsValid); + } + + [Fact] + public void Mask_With_Invalid_Pattern_Exception () + { + // Regex Exception + // Maybe it's not the right behaviour. + + var mask = ""; + for (int i = 0; i < 255; i++) { + mask += (char)i; + } + + try { + var field = new TextValidateField (mask) { + Width = 20 + }; + } catch (RegexParseException ex) { + Assert.True (true, ex.Message); + return; + } + Assert.True (false); + } + + [Fact] + public void Home_Key_First_Editable_Character () + { + // Range 0 to 1000 + // Accepts 001 too. + var field = new TextValidateField ("^[0-9]?[0-9]?[0-9]|1000$") { + Width = 20 + }; + + field.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers { })); + field.ProcessKey (new KeyEvent (Key.D0, new KeyModifiers { })); + field.ProcessKey (new KeyEvent (Key.D0, new KeyModifiers { })); + field.ProcessKey (new KeyEvent (Key.D0, new KeyModifiers { })); + + Assert.Equal ("1000", field.Text); + Assert.True (field.IsValid); + + // HOME KEY + field.ProcessKey (new KeyEvent (Key.Home, new KeyModifiers { })); + + // DELETE + field.ProcessKey (new KeyEvent (Key.Delete, new KeyModifiers { })); + + Assert.Equal ("000", field.Text); + Assert.True (field.IsValid); + } + + [Fact] + public void End_Key_End_Of_Input () + { + // Exactly 5 numbers + var field = new TextValidateField ("^[0-9]{5}$") { + Width = 20 + }; + + field.Provider.ValidateOnInput = false; + + for (int i = 0; i < 4; i++) { + field.ProcessKey (new KeyEvent (Key.D0, new KeyModifiers { })); + } + + Assert.Equal ("0000", field.Text); + Assert.False (field.IsValid); + + // HOME KEY + field.ProcessKey (new KeyEvent (Key.Home, new KeyModifiers { })); + + // END KEY + field.ProcessKey (new KeyEvent (Key.End, new KeyModifiers { })); + + // Insert 9 + field.ProcessKey (new KeyEvent (Key.D9, new KeyModifiers { })); + + Assert.Equal ("00009", field.Text); + Assert.True (field.IsValid); + + // Insert 9 + field.ProcessKey (new KeyEvent (Key.D9, new KeyModifiers { })); + + Assert.Equal ("000099", field.Text); + Assert.False (field.IsValid); + } + + [Fact] + public void Right_Key_Stops_At_End_And_Insert () + { + var field = new TextValidateField ("^[0-9][0-9][0-9]$") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + field.Provider.ValidateOnInput = false; + + field.Text = "123"; + + for (int i = 0; i < 10; i++) { + field.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers { })); + } + + Assert.Equal ("123", field.Text); + Assert.True (field.IsValid); + + // Insert 4 + field.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers { })); + + Assert.Equal ("1234", field.Text); + Assert.False (field.IsValid); + } + + [Fact] + public void Left_Key_Stops_At_Start_And_Insert () + { + var field = new TextValidateField ("^[0-9][0-9][0-9]$") { + TextAlignment = TextAlignment.Centered, + Width = 20 + }; + field.Provider.ValidateOnInput = false; + + field.Text = "123"; + + for (int i = 0; i < 10; i++) { + field.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers { })); + } + + Assert.Equal ("123", field.Text); + Assert.True (field.IsValid); + + // Insert 4 + field.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers { })); + + Assert.Equal ("4123", field.Text); + Assert.False (field.IsValid); + } + } +} \ No newline at end of file