From 43b889d1b15a87ec5aee5dde04ff45cbb8ae5d28 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 14 Jan 2024 22:54:07 +0000 Subject: [PATCH] Fixes #3157. DateField is validating wrong the date on different culture than us-US. (#3165) * Remove the old short two digits year and done some cleanup. * Fixes #3160. TextField doesn't update correctly the CursorPosition on Paste. * Using TextChanging instead of TextChanged event. * Removes old IsShortFormat. * Removing unnecessary private fields and done code cleanup. * Removes unnecessary GetShortDatePattern method, * Fix AdjCursorPosition method. * Create TestDateAttribute. * Reduces indentation and removes unused using. * Remove location from constructors parameters. --- Terminal.Gui/Views/DateField.cs | 201 +++++------------- Terminal.Gui/Views/DatePicker.cs | 3 +- Terminal.Gui/Views/TextField.cs | 10 +- UICatalog/Scenarios/Text.cs | 13 +- UICatalog/Scenarios/TimeAndDate.cs | 7 +- UnitTests/TestHelpers.cs | 22 ++ UnitTests/Views/DateFieldTests.cs | 328 ++++++++++++++--------------- UnitTests/Views/TextFieldTests.cs | 25 +++ 8 files changed, 274 insertions(+), 335 deletions(-) diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs index 5fc87abf3..389911769 100644 --- a/Terminal.Gui/Views/DateField.cs +++ b/Terminal.Gui/Views/DateField.cs @@ -20,16 +20,9 @@ namespace Terminal.Gui; /// public class DateField : TextField { DateTime _date; - bool _isShort; - int _longFieldLen = 10; - int _shortFieldLen = 8; + int _fieldLen = 10; string _sepChar; - string _longFormat; - string _shortFormat; - - int _fieldLen => _isShort ? _shortFieldLen : _longFieldLen; - - string _format => _isShort ? _shortFormat : _longFormat; + string _format; /// /// DateChanged event, raised when the property has changed. @@ -42,15 +35,6 @@ public class DateField : TextField { /// public event EventHandler> DateChanged; - /// - /// Initializes a new instance of using layout. - /// - /// The x coordinate. - /// The y coordinate. - /// Initial date contents. - /// If true, shows only two digits for the year. - public DateField (int x, int y, DateTime date, bool isShort = false) : base (x, y, isShort ? 10 : 12, "") => SetInitialProperties (date, isShort); - /// /// Initializes a new instance of using layout. /// @@ -66,16 +50,14 @@ public class DateField : TextField { SetInitialProperties (date); } - void SetInitialProperties (DateTime date, bool isShort = false) + void SetInitialProperties (DateTime date) { var cultureInfo = CultureInfo.CurrentCulture; _sepChar = cultureInfo.DateTimeFormat.DateSeparator; - _longFormat = GetLongFormat (cultureInfo.DateTimeFormat.ShortDatePattern); - _shortFormat = GetShortFormat (_longFormat); - this._isShort = isShort; + _format = $" {cultureInfo.DateTimeFormat.ShortDatePattern}"; Date = date; CursorPosition = 1; - TextChanged += DateField_Changed; + TextChanging += DateField_Changing; // Things this view knows how to do AddCommand (Command.DeleteCharRight, () => { @@ -109,7 +91,6 @@ public class DateField : TextField { KeyBindings.Add (Key.CursorRight, Command.Right); KeyBindings.Add (Key.F.WithCtrl, Command.Right); - } /// @@ -127,45 +108,33 @@ public class DateField : TextField { return false; } - void DateField_Changed (object sender, TextChangedEventArgs e) + void DateField_Changing (object sender, TextChangingEventArgs e) { try { - var date = GetInvarianteDate (Text, _isShort); - if ($" {date}" != Text) { - Text = $" {date}"; + var cultureInfo = CultureInfo.CurrentCulture; + DateTimeFormatInfo ccFmt = cultureInfo.DateTimeFormat; + int spaces = 0; + for (int i = 0; i < e.NewText.Length; i++) { + if (e.NewText [i] == ' ') { + spaces++; + } else { + break; + } } - if (_isShort) { - date = GetInvarianteDate (Text, false); - } - if (!DateTime.TryParseExact (date, GetInvarianteFormat (), CultureInfo.CurrentCulture, DateTimeStyles.None, out var result)) { - Text = e.OldValue; + spaces += _fieldLen; + string trimedText = e.NewText [..spaces]; + spaces -= _fieldLen; + trimedText = trimedText.Replace (new string (' ', spaces), " "); + var date = Convert.ToDateTime (trimedText, ccFmt).ToString (ccFmt.ShortDatePattern); + if ($" {date}" != e.NewText) { + e.NewText = $" {date}"; } + AdjCursorPosition (CursorPosition, true); } catch (Exception) { - Text = e.OldValue; + e.Cancel = true; } } - string GetInvarianteFormat () => $"MM{_sepChar}dd{_sepChar}yyyy"; - - string GetLongFormat (string lf) - { - string [] frm = lf.Split (_sepChar); - for (int i = 0; i < frm.Length; i++) { - if (frm [i].Contains ("M") && frm [i].GetRuneCount () < 2) { - lf = lf.Replace ("M", "MM"); - } - if (frm [i].Contains ("d") && frm [i].GetRuneCount () < 2) { - lf = lf.Replace ("d", "dd"); - } - if (frm [i].Contains ("y") && frm [i].GetRuneCount () < 4) { - lf = lf.Replace ("yy", "yyyy"); - } - } - return $" {lf}"; - } - - string GetShortFormat (string lf) => lf.Replace ("yyyy", "yy"); - /// /// Gets or sets the date of the . /// @@ -188,28 +157,6 @@ public class DateField : TextField { } } - /// - /// Get or set the date format for the widget. - /// - public bool IsShortFormat { - get => _isShort; - set { - _isShort = value; - if (_isShort) { - Width = 10; - } else { - Width = 12; - } - bool ro = ReadOnly; - if (ro) { - ReadOnly = false; - } - SetText (Text); - ReadOnly = ro; - SetNeedsDisplay (); - } - } - /// public override int CursorPosition { get => base.CursorPosition; @@ -230,7 +177,7 @@ public class DateField : TextField { var newText = text.GetRange (0, CursorPosition); newText.Add (key); if (CursorPosition < _fieldLen) { - newText = newText.Concat (text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList (); + newText = [.. newText, .. text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))]; } return SetText (StringExtensions.ToString (newText)); } @@ -310,18 +257,12 @@ public class DateField : TextField { { string date = " "; for (int i = 0; i < fm.Length; i++) { - if (fm [i].Contains ("M")) { + if (fm [i].Contains ('M')) { date += $"{month,2:00}"; - } else if (fm [i].Contains ("d")) { + } else if (fm [i].Contains ('d')) { date += $"{day,2:00}"; } else { - if (_isShort && year.ToString ().Length == 4) { - date += $"{year.ToString ().Substring (2, 2)}"; - } else if (_isShort) { - date += $"{year,2:00}"; - } else { - date += $"{year,4:0000}"; - } + date += $"{year,4:0000}"; } if (i < 2) { date += $"{_sepChar}"; @@ -330,40 +271,7 @@ public class DateField : TextField { return date; } - string GetInvarianteDate (string text, bool isShort) - { - string [] vals = text.Split (_sepChar); - string [] frm = (isShort ? $"MM{_sepChar}dd{_sepChar}yy" : GetInvarianteFormat ()).Split (_sepChar); - string [] date = { null, null, null }; - - for (int i = 0; i < frm.Length; i++) { - if (frm [i].Contains ("M")) { - date [0] = vals [i].Trim (); - } else if (frm [i].Contains ("d")) { - date [1] = vals [i].Trim (); - } else { - string yearString; - if (isShort && vals [i].Length > 2) { - yearString = vals [i].Substring (0, 2); - } else if (!isShort && vals [i].Length > 4) { - yearString = vals [i].Substring (0, 4); - } else { - yearString = vals [i].Trim (); - } - var year = int.Parse (yearString); - if (isShort && year.ToString ().Length == 4) { - date [2] = year.ToString ().Substring (2, 2); - } else if (isShort) { - date [2] = year.ToString (); - } else { - date [2] = $"{year,4:0000}"; - } - } - } - return $"{date [0]}{_sepChar}{date [1]}{_sepChar}{date [2]}"; - } - - int GetFormatIndex (string [] fm, string t) + static int GetFormatIndex (string [] fm, string t) { int idx = -1; for (int i = 0; i < fm.Length; i++) { @@ -381,9 +289,8 @@ public class DateField : TextField { CursorPosition = _fieldLen; return; } - if (Text [++CursorPosition] == _sepChar.ToCharArray () [0]) { - CursorPosition++; - } + CursorPosition++; + AdjCursorPosition (CursorPosition); } void DecCursorPosition () @@ -392,15 +299,29 @@ public class DateField : TextField { CursorPosition = 1; return; } - if (Text [--CursorPosition] == _sepChar.ToCharArray () [0]) { - CursorPosition--; - } + CursorPosition--; + AdjCursorPosition (CursorPosition, false); } - void AdjCursorPosition () + void AdjCursorPosition (int point, bool increment = true) { - if (Text [CursorPosition] == _sepChar.ToCharArray () [0]) { - CursorPosition++; + var newPoint = point; + if (point > _fieldLen) { + newPoint = _fieldLen; + } + if (point < 1) { + newPoint = 1; + } + if (newPoint != point) { + CursorPosition = newPoint; + } + + while (Text [CursorPosition] == _sepChar [0]) { + if (increment) { + CursorPosition++; + } else { + CursorPosition--; + } } } @@ -461,23 +382,13 @@ public class DateField : TextField { /// public override bool MouseEvent (MouseEvent ev) { - if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked)) { - return false; - } - if (!HasFocus) { - SetFocus (); - } + var result = base.MouseEvent (ev); - int point = ev.X; - if (point > _fieldLen) { - point = _fieldLen; + if (result && SelectedLength == 0 && ev.Flags.HasFlag (MouseFlags.Button1Pressed)) { + int point = ev.X; + AdjCursorPosition (point, true); } - if (point < 1) { - point = 1; - } - CursorPosition = point; - AdjCursorPosition (); - return true; + return result; } /// diff --git a/Terminal.Gui/Views/DatePicker.cs b/Terminal.Gui/Views/DatePicker.cs index 0ee994568..07e6b9e99 100644 --- a/Terminal.Gui/Views/DatePicker.cs +++ b/Terminal.Gui/Views/DatePicker.cs @@ -77,8 +77,7 @@ public class DatePicker : View { X = Pos.Right (_dateLabel), Y = 0, Width = Dim.Fill (1), - Height = 1, - IsShortFormat = false + Height = 1 }; _calendar = new TableView () { diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 2c1ef50af..4d736c78d 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -1375,11 +1375,11 @@ public class TextField : View { cbTxt + StringExtensions.ToString (_text.GetRange (selStart + SelectedLength, _text.Count - (selStart + SelectedLength))); - _cursorPosition = selStart + cbTxt.GetRuneCount (); - ClearAllSelection (); - SetNeedsDisplay (); - Adjust (); - } + _cursorPosition = Math.Min (selStart + cbTxt.GetRuneCount (), _text.Count); + ClearAllSelection (); + SetNeedsDisplay (); + Adjust (); + } /// /// Virtual method that invoke the event if it's defined. diff --git a/UICatalog/Scenarios/Text.cs b/UICatalog/Scenarios/Text.cs index 203e2514b..f0133dbb7 100644 --- a/UICatalog/Scenarios/Text.cs +++ b/UICatalog/Scenarios/Text.cs @@ -161,13 +161,12 @@ public class Text : Scenario { }; Win.Add (labelMirroringHexEditor); - var dateField = new DateField (DateTime.Now) { - X = 1, - Y = Pos.Bottom (hexEditor) + 1, - Width = 20, - IsShortFormat = false - }; - Win.Add (dateField); + var dateField = new DateField (System.DateTime.Now) { + X = 1, + Y = Pos.Bottom (hexEditor) + 1, + Width = 20 + }; + Win.Add (dateField); var labelMirroringDateField = new Label (dateField.Text) { X = Pos.Right (dateField) + 1, diff --git a/UICatalog/Scenarios/TimeAndDate.cs b/UICatalog/Scenarios/TimeAndDate.cs index 87983eddb..0b1e69312 100644 --- a/UICatalog/Scenarios/TimeAndDate.cs +++ b/UICatalog/Scenarios/TimeAndDate.cs @@ -35,7 +35,6 @@ namespace UICatalog.Scenarios { var shortDate = new DateField (DateTime.Now) { X = Pos.Center (), Y = Pos.Bottom (shortTime) + 1, - IsShortFormat = true, ReadOnly = true, }; shortDate.DateChanged += DateChanged; @@ -44,8 +43,7 @@ namespace UICatalog.Scenarios { var longDate = new DateField (DateTime.Now) { X = Pos.Center (), Y = Pos.Bottom (shortDate) + 1, - IsShortFormat = false, - ReadOnly = true, + ReadOnly = false, }; longDate.DateChanged += DateChanged; Win.Add (longDate); @@ -111,9 +109,6 @@ namespace UICatalog.Scenarios { longDate.ReadOnly = !longDate.ReadOnly; shortDate.ReadOnly = !shortDate.ReadOnly; - - longDate.IsShortFormat = !longDate.IsShortFormat; - shortDate.IsShortFormat = !shortDate.IsShortFormat; }; Win.Add (swapButton); } diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs index 80d5a7838..7efaea02e 100644 --- a/UnitTests/TestHelpers.cs +++ b/UnitTests/TestHelpers.cs @@ -147,6 +147,28 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute { } } +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] +public class TestDateAttribute : Xunit.Sdk.BeforeAfterTestAttribute +{ + CultureInfo _currentCulture = CultureInfo.CurrentCulture; + + public TestDateAttribute() + { + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; + } + + public override void Before(MethodInfo methodUnderTest) + { + Assert.Equal(CultureInfo.CurrentCulture, CultureInfo.InvariantCulture); + } + + public override void After(MethodInfo methodUnderTest) + { + CultureInfo.CurrentCulture = _currentCulture; + Assert.Equal(CultureInfo.CurrentCulture, _currentCulture); + } +} + partial class TestHelpers { [GeneratedRegex ("\\s+$", RegexOptions.Multiline)] private static partial Regex TrailingWhiteSpaceRegEx (); diff --git a/UnitTests/Views/DateFieldTests.cs b/UnitTests/Views/DateFieldTests.cs index a44daa173..993362c79 100644 --- a/UnitTests/Views/DateFieldTests.cs +++ b/UnitTests/Views/DateFieldTests.cs @@ -1,185 +1,173 @@ using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; -namespace Terminal.Gui.ViewsTests { - public class DateFieldTests { - [Fact] - public void Constructors_Defaults () - { - var df = new DateField (); - Assert.False (df.IsShortFormat); - Assert.Equal (DateTime.MinValue, df.Date); - Assert.Equal (1, df.CursorPosition); - Assert.Equal (new Rect (0, 0, 12, 1), df.Frame); +namespace Terminal.Gui.ViewsTests; +public class DateFieldTests { + [Fact, TestDate] + public void Constructors_Defaults () + { + var df = new DateField (); + Assert.Equal (DateTime.MinValue, df.Date); + Assert.Equal (1, df.CursorPosition); + Assert.Equal (new Rect (0, 0, 12, 1), df.Frame); + Assert.Equal (" 01/01/0001", df.Text); - var date = DateTime.Now; - df = new DateField (date); - Assert.False (df.IsShortFormat); - Assert.Equal (date, df.Date); - Assert.Equal (1, df.CursorPosition); - Assert.Equal (new Rect (0, 0, 12, 1), df.Frame); + var date = DateTime.Now; + df = new DateField (date); + Assert.Equal (date, df.Date); + Assert.Equal (1, df.CursorPosition); + Assert.Equal (new Rect (0, 0, 12, 1), df.Frame); + Assert.Equal ($" {date.ToString (CultureInfo.InvariantCulture.DateTimeFormat.ShortDatePattern)}", df.Text); - df = new DateField (1, 2, date); - Assert.False (df.IsShortFormat); - Assert.Equal (date, df.Date); - Assert.Equal (1, df.CursorPosition); - Assert.Equal (new Rect (1, 2, 12, 1), df.Frame); + df = new DateField (date) { X = 1, Y = 2 }; + Assert.Equal (date, df.Date); + Assert.Equal (1, df.CursorPosition); + Assert.Equal (new Rect (1, 2, 12, 1), df.Frame); + Assert.Equal ($" {date.ToString (CultureInfo.InvariantCulture.DateTimeFormat.ShortDatePattern)}", df.Text); + } - df = new DateField (3, 4, date, true); - Assert.True (df.IsShortFormat); - Assert.Equal (date, df.Date); - Assert.Equal (1, df.CursorPosition); - Assert.Equal (new Rect (3, 4, 10, 1), df.Frame); + [Fact, TestDate] + public void CursorPosition_Min_Is_Always_One_Max_Is_Always_Max_Format () + { + var df = new DateField (); + Assert.Equal (1, df.CursorPosition); + df.CursorPosition = 0; + Assert.Equal (1, df.CursorPosition); + df.CursorPosition = 11; + Assert.Equal (10, df.CursorPosition); + } - df.IsShortFormat = false; - Assert.Equal (new Rect (3, 4, 12, 1), df.Frame); - Assert.Equal (12, df.Width); - } + [Fact, TestDate] + public void CursorPosition_Min_Is_Always_One_Max_Is_Always_Max_Format_After_Selection () + { + var df = new DateField (); + // Start selection + Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorLeft | KeyCode.ShiftMask))); + Assert.Equal (1, df.SelectedStart); + Assert.Equal (1, df.SelectedLength); + Assert.Equal (0, df.CursorPosition); + // Without selection + Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorLeft))); + Assert.Equal (-1, df.SelectedStart); + Assert.Equal (0, df.SelectedLength); + Assert.Equal (1, df.CursorPosition); + df.CursorPosition = 10; + Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorRight | KeyCode.ShiftMask))); + Assert.Equal (10, df.SelectedStart); + Assert.Equal (1, df.SelectedLength); + Assert.Equal (11, df.CursorPosition); + Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorRight))); + Assert.Equal (-1, df.SelectedStart); + Assert.Equal (0, df.SelectedLength); + Assert.Equal (10, df.CursorPosition); + } - [Fact] - public void CursorPosition_Min_Is_Always_One_Max_Is_Always_Max_Format () - { - var df = new DateField (); - Assert.Equal (1, df.CursorPosition); - df.CursorPosition = 0; - Assert.Equal (1, df.CursorPosition); - df.CursorPosition = 11; - Assert.Equal (10, df.CursorPosition); - df.IsShortFormat = true; - df.CursorPosition = 0; - Assert.Equal (1, df.CursorPosition); - df.CursorPosition = 9; - Assert.Equal (8, df.CursorPosition); - } + [Fact, TestDate] + public void KeyBindings_Command () + { + DateField df = new DateField (DateTime.Parse ("12/12/1971")) { + ReadOnly = true + }; + Assert.True (df.NewKeyDownEvent (new (KeyCode.Delete))); + Assert.Equal (" 12/12/1971", df.Text); + df.ReadOnly = false; + Assert.True (df.NewKeyDownEvent (new (KeyCode.D | KeyCode.CtrlMask))); + Assert.Equal (" 02/12/1971", df.Text); + df.CursorPosition = 4; + df.ReadOnly = true; + Assert.True (df.NewKeyDownEvent (new (KeyCode.Delete))); + Assert.Equal (" 02/12/1971", df.Text); + df.ReadOnly = false; + Assert.True (df.NewKeyDownEvent (new (KeyCode.Backspace))); + Assert.Equal (" 02/02/1971", df.Text); + Assert.True (df.NewKeyDownEvent (new (KeyCode.Home))); + Assert.Equal (1, df.CursorPosition); + Assert.True (df.NewKeyDownEvent (new (KeyCode.End))); + Assert.Equal (10, df.CursorPosition); + Assert.True (df.NewKeyDownEvent (new (KeyCode.A | KeyCode.CtrlMask))); + Assert.Equal (1, df.CursorPosition); + Assert.True (df.NewKeyDownEvent (new (KeyCode.E | KeyCode.CtrlMask))); + Assert.Equal (10, df.CursorPosition); + Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorLeft))); + Assert.Equal (9, df.CursorPosition); + Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorRight))); + Assert.Equal (10, df.CursorPosition); + // Non-numerics are ignored + Assert.False (df.NewKeyDownEvent (new (KeyCode.A))); + df.ReadOnly = true; + df.CursorPosition = 1; + Assert.True (df.NewKeyDownEvent (new (KeyCode.D1))); + Assert.Equal (" 02/02/1971", df.Text); + df.ReadOnly = false; + Assert.True (df.NewKeyDownEvent (new (KeyCode.D1))); + Assert.Equal (" 12/02/1971", df.Text); + Assert.Equal (2, df.CursorPosition); + Assert.True (df.NewKeyDownEvent (new (KeyCode.D | KeyCode.AltMask))); + Assert.Equal (" 10/02/1971", df.Text); + } - [Fact] - public void CursorPosition_Min_Is_Always_One_Max_Is_Always_Max_Format_After_Selection () - { - var df = new DateField (); - // Start selection - Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorLeft | KeyCode.ShiftMask))); - Assert.Equal (1, df.SelectedStart); - Assert.Equal (1, df.SelectedLength); - Assert.Equal (0, df.CursorPosition); - // Without selection - Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorLeft))); - Assert.Equal (-1, df.SelectedStart); - Assert.Equal (0, df.SelectedLength); - Assert.Equal (1, df.CursorPosition); - df.CursorPosition = 10; - Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorRight | KeyCode.ShiftMask))); - Assert.Equal (10, df.SelectedStart); - Assert.Equal (1, df.SelectedLength); - Assert.Equal (11, df.CursorPosition); - Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorRight))); - Assert.Equal (-1, df.SelectedStart); - Assert.Equal (0, df.SelectedLength); - Assert.Equal (10, df.CursorPosition); - } - - [Fact] - public void KeyBindings_Command () - { - CultureInfo cultureBackup = CultureInfo.CurrentCulture; - CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; - DateField df = new DateField (DateTime.Parse ("12/12/1971")); - df.ReadOnly = true; - Assert.True (df.NewKeyDownEvent (new (KeyCode.Delete))); - Assert.Equal (" 12/12/1971", df.Text); - df.ReadOnly = false; - Assert.True (df.NewKeyDownEvent (new (KeyCode.D | KeyCode.CtrlMask))); - Assert.Equal (" 02/12/1971", df.Text); - df.CursorPosition = 4; - df.ReadOnly = true; - Assert.True (df.NewKeyDownEvent (new (KeyCode.Delete))); - Assert.Equal (" 02/12/1971", df.Text); - df.ReadOnly = false; - Assert.True (df.NewKeyDownEvent (new (KeyCode.Backspace))); - Assert.Equal (" 02/02/1971", df.Text); - Assert.True (df.NewKeyDownEvent (new (KeyCode.Home))); - Assert.Equal (1, df.CursorPosition); - Assert.True (df.NewKeyDownEvent (new (KeyCode.End))); - Assert.Equal (10, df.CursorPosition); - Assert.True (df.NewKeyDownEvent (new (KeyCode.A | KeyCode.CtrlMask))); - Assert.Equal (1, df.CursorPosition); - Assert.True (df.NewKeyDownEvent (new (KeyCode.E | KeyCode.CtrlMask))); - Assert.Equal (10, df.CursorPosition); - Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorLeft))); - Assert.Equal (9, df.CursorPosition); - Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorRight))); - Assert.Equal (10, df.CursorPosition); - // Non-numerics are ignored - Assert.False (df.NewKeyDownEvent (new (KeyCode.A))); - df.ReadOnly = true; - df.CursorPosition = 1; - Assert.True (df.NewKeyDownEvent (new (KeyCode.D1))); - Assert.Equal (" 02/02/1971", df.Text); - df.ReadOnly = false; - Assert.True (df.NewKeyDownEvent (new (KeyCode.D1))); - Assert.Equal (" 12/02/1971", df.Text); - Assert.Equal (2, df.CursorPosition); - Assert.True (df.NewKeyDownEvent (new (KeyCode.D | KeyCode.AltMask))); - Assert.Equal (" 10/02/1971", df.Text); - CultureInfo.CurrentCulture = cultureBackup; - } - - [Fact] - public void Typing_With_Selection_Normalize_Format () - { - CultureInfo cultureBackup = CultureInfo.CurrentCulture; - CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; - DateField df = new DateField (DateTime.Parse ("12/12/1971")); + [Fact, TestDate] + public void Typing_With_Selection_Normalize_Format () + { + DateField df = new DateField (DateTime.Parse ("12/12/1971")) { // Start selection at before the first separator / - df.CursorPosition = 2; - // Now select the separator / - Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorRight | KeyCode.ShiftMask))); - Assert.Equal (2, df.SelectedStart); - Assert.Equal (1, df.SelectedLength); - Assert.Equal (3, df.CursorPosition); - // Type 3 over the separator - Assert.True (df.NewKeyDownEvent (new (KeyCode.D3))); - // The format was normalized and replaced again with / - Assert.Equal (" 12/12/1971", df.Text); - Assert.Equal (4, df.CursorPosition); - CultureInfo.CurrentCulture = cultureBackup; - } + CursorPosition = 2 + }; + // Now select the separator / + Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorRight | KeyCode.ShiftMask))); + Assert.Equal (2, df.SelectedStart); + Assert.Equal (1, df.SelectedLength); + Assert.Equal (3, df.CursorPosition); + // Type 3 over the separator + Assert.True (df.NewKeyDownEvent (new (KeyCode.D3))); + // The format was normalized and replaced again with / + Assert.Equal (" 12/12/1971", df.Text); + Assert.Equal (4, df.CursorPosition); + } - [Fact, AutoInitShutdown] - public void Copy_Paste () - { - CultureInfo cultureBackup = CultureInfo.CurrentCulture; - CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; - DateField df1 = new DateField (DateTime.Parse ("12/12/1971")); - DateField df2 = new DateField (DateTime.Parse ("12/31/2023")); - // Select all text - Assert.True (df2.NewKeyDownEvent (new (KeyCode.End | KeyCode.ShiftMask))); - Assert.Equal (1, df2.SelectedStart); - Assert.Equal (10, df2.SelectedLength); - Assert.Equal (11, df2.CursorPosition); - // Copy from df2 - Assert.True (df2.NewKeyDownEvent (new (KeyCode.C | KeyCode.CtrlMask))); - // Paste into df1 - Assert.True (df1.NewKeyDownEvent (new (KeyCode.V | KeyCode.CtrlMask))); - Assert.Equal (" 12/31/2023", df1.Text); - Assert.Equal (11, df1.CursorPosition); - CultureInfo.CurrentCulture = cultureBackup; - } + [Fact, TestDate, AutoInitShutdown] + public void Copy_Paste () + { + DateField df1 = new DateField (DateTime.Parse ("12/12/1971")); + DateField df2 = new DateField (DateTime.Parse ("12/31/2023")); + // Select all text + Assert.True (df2.NewKeyDownEvent (new (KeyCode.End | KeyCode.ShiftMask))); + Assert.Equal (1, df2.SelectedStart); + Assert.Equal (10, df2.SelectedLength); + Assert.Equal (11, df2.CursorPosition); + // Copy from df2 + Assert.True (df2.NewKeyDownEvent (new (KeyCode.C | KeyCode.CtrlMask))); + // Paste into df1 + Assert.True (df1.NewKeyDownEvent (new (KeyCode.V | KeyCode.CtrlMask))); + Assert.Equal (" 12/31/2023", df1.Text); + Assert.Equal (11, df1.CursorPosition); + } - [Fact] - public void Date_Start_From_01_01_0001_And_End_At_12_31_9999 () - { - CultureInfo cultureBackup = CultureInfo.CurrentCulture; - CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; - DateField df = new DateField (DateTime.Parse ("01/01/0001")); - Assert.Equal (" 01/01/0001", df.Text); - df.Date = DateTime.Parse ("12/31/9999"); - Assert.Equal (" 12/31/9999", df.Text); - CultureInfo.CurrentCulture = cultureBackup; - } + [Fact, TestDate] + public void Date_Start_From_01_01_0001_And_End_At_12_31_9999 () + { + DateField df = new DateField (DateTime.Parse ("01/01/0001")); + Assert.Equal (" 01/01/0001", df.Text); + df.Date = DateTime.Parse ("12/31/9999"); + Assert.Equal (" 12/31/9999", df.Text); + } + + [Fact] + public void Using_Pt_Culture () + { + CultureInfo cultureBackup = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = new CultureInfo ("pt-PT"); + DateField df = new DateField (DateTime.Parse ("12/12/1971")) { + // Move to the first 2 + CursorPosition = 2 + }; + // Type 3 over the separator + Assert.True (df.NewKeyDownEvent (new (KeyCode.D3))); + // If InvariantCulture was used this will fail but not with PT culture + Assert.Equal (" 13/12/1971", df.Text); + Assert.Equal ("13/12/1971", df.Date.ToString (CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern)); + Assert.Equal (4, df.CursorPosition); + CultureInfo.CurrentCulture = cultureBackup; } } diff --git a/UnitTests/Views/TextFieldTests.cs b/UnitTests/Views/TextFieldTests.cs index d12fac58b..1da7e4449 100644 --- a/UnitTests/Views/TextFieldTests.cs +++ b/UnitTests/Views/TextFieldTests.cs @@ -1634,4 +1634,29 @@ Les Miśerables", output); _textField.Paste (); Assert.Equal ("TextField with some more test text. Unicode shouldn't 𝔹Aℝ𝔽!", _textField.Text); } + + [Fact, TextFieldTestsAutoInitShutdown] + public void Copy_Paste_Text_Changing_Updates_Cursor_Position () + { + _textField.TextChanging += _textField_TextChanging; + + void _textField_TextChanging (object sender, TextChangingEventArgs e) + { + if (e.NewText.GetRuneCount () > 11) { + e.NewText = e.NewText [..11]; + } + } + + Assert.Equal (32, _textField.CursorPosition); + _textField.SelectAll (); + _textField.Cut (); + Assert.Equal ("TAB to jump between text fields.", Application.Driver.Clipboard.GetClipboardData ()); + Assert.Equal (string.Empty, _textField.Text); + Assert.Equal (0, _textField.CursorPosition); + _textField.Paste (); + Assert.Equal ("TAB to jump", _textField.Text); + Assert.Equal (11, _textField.CursorPosition); + + _textField.TextChanging -= _textField_TextChanging; + } }