DateFieldTests.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. #nullable enable
  2. using System.Globalization;
  3. using System.Runtime.InteropServices;
  4. using UnitTests_Parallelizable;
  5. namespace ViewsTests;
  6. public class DateFieldTests
  7. {
  8. [Fact]
  9. [TestDate]
  10. public void Constructors_Defaults ()
  11. {
  12. var df = new DateField ();
  13. df.Layout ();
  14. Assert.Equal (DateTime.MinValue, df.Date);
  15. Assert.Equal (1, df.CursorPosition);
  16. Assert.Equal (new (0, 0, 12, 1), df.Frame);
  17. Assert.Equal (" 01/01/0001", df.Text);
  18. DateTime date = DateTime.Now;
  19. df = new (date);
  20. df.Layout ();
  21. Assert.Equal (date, df.Date);
  22. Assert.Equal (1, df.CursorPosition);
  23. Assert.Equal (new (0, 0, 12, 1), df.Frame);
  24. Assert.Equal ($" {date.ToString (CultureInfo.InvariantCulture.DateTimeFormat.ShortDatePattern)}", df.Text);
  25. df = new (date) { X = 1, Y = 2 };
  26. df.Layout ();
  27. Assert.Equal (date, df.Date);
  28. Assert.Equal (1, df.CursorPosition);
  29. Assert.Equal (new (1, 2, 12, 1), df.Frame);
  30. Assert.Equal ($" {date.ToString (CultureInfo.InvariantCulture.DateTimeFormat.ShortDatePattern)}", df.Text);
  31. }
  32. [Fact]
  33. [TestDate]
  34. public void Copy_Paste ()
  35. {
  36. IApplication app = Application.Create();
  37. app.Init("fake");
  38. try
  39. {
  40. var df1 = new DateField (DateTime.Parse ("12/12/1971")) { App = app };
  41. var df2 = new DateField (DateTime.Parse ("12/31/2023")) { App = app };
  42. // Select all text
  43. Assert.True (df2.NewKeyDownEvent (Key.End.WithShift));
  44. Assert.Equal (1, df2.SelectedStart);
  45. Assert.Equal (10, df2.SelectedLength);
  46. Assert.Equal (11, df2.CursorPosition);
  47. // Copy from df2
  48. Assert.True (df2.NewKeyDownEvent (Key.C.WithCtrl));
  49. // Paste into df1
  50. Assert.True (df1.NewKeyDownEvent (Key.V.WithCtrl));
  51. Assert.Equal (" 12/31/2023", df1.Text);
  52. Assert.Equal (11, df1.CursorPosition);
  53. }
  54. finally
  55. {
  56. app.Dispose ();
  57. }
  58. }
  59. [Fact]
  60. [TestDate]
  61. public void CursorPosition_Min_Is_Always_One_Max_Is_Always_Max_Format ()
  62. {
  63. var df = new DateField ();
  64. Assert.Equal (1, df.CursorPosition);
  65. df.CursorPosition = 0;
  66. Assert.Equal (1, df.CursorPosition);
  67. df.CursorPosition = 11;
  68. Assert.Equal (10, df.CursorPosition);
  69. }
  70. [Fact]
  71. [TestDate]
  72. public void CursorPosition_Min_Is_Always_One_Max_Is_Always_Max_Format_After_Selection ()
  73. {
  74. var df = new DateField ();
  75. // Start selection
  76. Assert.True (df.NewKeyDownEvent (Key.CursorLeft.WithShift));
  77. Assert.Equal (1, df.SelectedStart);
  78. Assert.Equal (1, df.SelectedLength);
  79. Assert.Equal (0, df.CursorPosition);
  80. // Without selection
  81. Assert.True (df.NewKeyDownEvent (Key.CursorLeft));
  82. Assert.Equal (-1, df.SelectedStart);
  83. Assert.Equal (0, df.SelectedLength);
  84. Assert.Equal (1, df.CursorPosition);
  85. df.CursorPosition = 10;
  86. Assert.True (df.NewKeyDownEvent (Key.CursorRight.WithShift));
  87. Assert.Equal (10, df.SelectedStart);
  88. Assert.Equal (1, df.SelectedLength);
  89. Assert.Equal (11, df.CursorPosition);
  90. Assert.True (df.NewKeyDownEvent (Key.CursorRight));
  91. Assert.Equal (-1, df.SelectedStart);
  92. Assert.Equal (0, df.SelectedLength);
  93. Assert.Equal (10, df.CursorPosition);
  94. }
  95. [Fact]
  96. [TestDate]
  97. public void Date_Start_From_01_01_0001_And_End_At_12_31_9999 ()
  98. {
  99. var df = new DateField (DateTime.Parse ("01/01/0001"));
  100. Assert.Equal (" 01/01/0001", df.Text);
  101. df.Date = DateTime.Parse ("12/31/9999");
  102. Assert.Equal (" 12/31/9999", df.Text);
  103. }
  104. [Fact]
  105. [TestDate]
  106. public void KeyBindings_Command ()
  107. {
  108. var df = new DateField (DateTime.Parse ("12/12/1971")) { ReadOnly = true };
  109. Assert.True (df.NewKeyDownEvent (Key.Delete));
  110. Assert.Equal (" 12/12/1971", df.Text);
  111. df.ReadOnly = false;
  112. Assert.True (df.NewKeyDownEvent (Key.D.WithCtrl));
  113. Assert.Equal (" 02/12/1971", df.Text);
  114. df.CursorPosition = 4;
  115. df.ReadOnly = true;
  116. Assert.True (df.NewKeyDownEvent (Key.Delete));
  117. Assert.Equal (" 02/12/1971", df.Text);
  118. df.ReadOnly = false;
  119. Assert.True (df.NewKeyDownEvent (Key.Backspace));
  120. Assert.Equal (" 02/02/1971", df.Text);
  121. Assert.True (df.NewKeyDownEvent (Key.Home));
  122. Assert.Equal (1, df.CursorPosition);
  123. Assert.True (df.NewKeyDownEvent (Key.End));
  124. Assert.Equal (10, df.CursorPosition);
  125. Assert.True (df.NewKeyDownEvent (Key.E.WithCtrl));
  126. Assert.Equal (10, df.CursorPosition);
  127. Assert.True (df.NewKeyDownEvent (Key.CursorLeft));
  128. Assert.Equal (9, df.CursorPosition);
  129. Assert.True (df.NewKeyDownEvent (Key.CursorRight));
  130. Assert.Equal (10, df.CursorPosition);
  131. // Non-numerics are ignored
  132. Assert.False (df.NewKeyDownEvent (Key.A));
  133. df.ReadOnly = true;
  134. df.CursorPosition = 1;
  135. Assert.True (df.NewKeyDownEvent (Key.D1));
  136. Assert.Equal (" 02/02/1971", df.Text);
  137. df.ReadOnly = false;
  138. Assert.True (df.NewKeyDownEvent (Key.D1));
  139. Assert.Equal (" 12/02/1971", df.Text);
  140. Assert.Equal (2, df.CursorPosition);
  141. #if UNIX_KEY_BINDINGS
  142. Assert.True (df.NewKeyDownEvent (Key.D.WithAlt));
  143. Assert.Equal (" 10/02/1971", df.Text);
  144. #endif
  145. }
  146. [Fact]
  147. [TestDate]
  148. public void Typing_With_Selection_Normalize_Format ()
  149. {
  150. var df = new DateField (DateTime.Parse ("12/12/1971"))
  151. {
  152. // Start selection at before the first separator /
  153. CursorPosition = 2
  154. };
  155. // Now select the separator /
  156. Assert.True (df.NewKeyDownEvent (Key.CursorRight.WithShift));
  157. Assert.Equal (2, df.SelectedStart);
  158. Assert.Equal (1, df.SelectedLength);
  159. Assert.Equal (3, df.CursorPosition);
  160. // Type 3 over the separator
  161. Assert.True (df.NewKeyDownEvent (Key.D3));
  162. // The format was normalized and replaced again with /
  163. Assert.Equal (" 12/12/1971", df.Text);
  164. Assert.Equal (4, df.CursorPosition);
  165. }
  166. [Fact]
  167. [TestDate]
  168. public void Culture_Pt_Portuguese ()
  169. {
  170. CultureInfo cultureBackup = CultureInfo.CurrentCulture;
  171. try
  172. {
  173. CultureInfo.CurrentCulture = new ("pt-PT");
  174. var df = new DateField (DateTime.Parse ("12/12/1971"))
  175. {
  176. // Move to the first 2
  177. CursorPosition = 2
  178. };
  179. // Type 3 over the separator
  180. Assert.True (df.NewKeyDownEvent (Key.D3));
  181. // If InvariantCulture was used this will fail but not with PT culture
  182. Assert.Equal (" 13/12/1971", df.Text);
  183. Assert.Equal ("13/12/1971", df.Date!.Value.ToString (CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern));
  184. Assert.Equal (4, df.CursorPosition);
  185. }
  186. finally
  187. {
  188. CultureInfo.CurrentCulture = cultureBackup;
  189. }
  190. }
  191. /// <summary>
  192. /// Tests specific culture date formatting edge cases.
  193. /// Split from the monolithic culture test for better isolation and maintainability.
  194. /// </summary>
  195. [Theory]
  196. [TestDate]
  197. [InlineData ("en-US", "01/01/1971", '/')]
  198. [InlineData ("en-GB", "01/01/1971", '/')]
  199. [InlineData ("de-DE", "01.01.1971", '.')]
  200. [InlineData ("fr-FR", "01/01/1971", '/')]
  201. [InlineData ("es-ES", "01/01/1971", '/')]
  202. [InlineData ("it-IT", "01/01/1971", '/')]
  203. [InlineData ("ja-JP", "1971/01/01", '/')]
  204. [InlineData ("zh-CN", "1971/01/01", '/')]
  205. [InlineData ("ko-KR", "1971.01.01", '.')]
  206. [InlineData ("pt-PT", "01/01/1971", '/')]
  207. [InlineData ("pt-BR", "01/01/1971", '/')]
  208. [InlineData ("ru-RU", "01.01.1971", '.')]
  209. [InlineData ("nl-NL", "01-01-1971", '-')]
  210. [InlineData ("sv-SE", "1971-01-01", '-')]
  211. [InlineData ("pl-PL", "01.01.1971", '.')]
  212. [InlineData ("tr-TR", "01.01.1971", '.')]
  213. public void Culture_SpecificCultures_ProducesExpectedFormat (string cultureName, string expectedDate, char expectedSeparator)
  214. {
  215. // Skip cultures that may have platform-specific issues
  216. if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
  217. {
  218. // macOS has known issues with certain cultures - see #3592
  219. string [] problematicOnMac = { "ar-SA", "en-SA", "en-TH", "th", "th-TH" };
  220. if (problematicOnMac.Contains (cultureName))
  221. {
  222. return;
  223. }
  224. }
  225. CultureInfo cultureBackup = CultureInfo.CurrentCulture;
  226. try
  227. {
  228. var culture = new CultureInfo (cultureName);
  229. // Parse date using InvariantCulture BEFORE changing CurrentCulture
  230. DateTime date = DateTime.Parse ("1/1/1971", CultureInfo.InvariantCulture);
  231. CultureInfo.CurrentCulture = culture;
  232. var df = new DateField (date);
  233. // Verify the text contains the expected separator
  234. Assert.Contains (expectedSeparator, df.Text);
  235. // Verify the date is formatted correctly (accounting for leading space)
  236. Assert.Equal ($" {expectedDate}", df.Text);
  237. }
  238. catch (CultureNotFoundException)
  239. {
  240. // Skip cultures not available on this system
  241. }
  242. finally
  243. {
  244. CultureInfo.CurrentCulture = cultureBackup;
  245. }
  246. }
  247. /// <summary>
  248. /// Tests right-to-left cultures separately due to their complexity.
  249. /// </summary>
  250. [Theory]
  251. [TestDate]
  252. [InlineData ("ar-SA")] // Arabic (Saudi Arabia)
  253. [InlineData ("he-IL")] // Hebrew (Israel)
  254. [InlineData ("fa-IR")] // Persian (Iran)
  255. public void Culture_RightToLeft_HandlesFormatting (string cultureName)
  256. {
  257. if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
  258. {
  259. // macOS has known issues with RTL cultures - see #3592
  260. return;
  261. }
  262. CultureInfo cultureBackup = CultureInfo.CurrentCulture;
  263. try
  264. {
  265. var culture = new CultureInfo (cultureName);
  266. // Parse date using InvariantCulture BEFORE changing CurrentCulture
  267. // This is critical because RTL cultures may use different calendars
  268. DateTime date = DateTime.Parse ("1/1/1971", CultureInfo.InvariantCulture);
  269. CultureInfo.CurrentCulture = culture;
  270. var df = new DateField (date);
  271. // Just verify DateField doesn't crash with RTL cultures
  272. // and produces some text
  273. Assert.NotEmpty (df.Text);
  274. Assert.NotNull (df.Date);
  275. }
  276. catch (CultureNotFoundException)
  277. {
  278. // Skip cultures not available on this system
  279. }
  280. finally
  281. {
  282. CultureInfo.CurrentCulture = cultureBackup;
  283. }
  284. }
  285. /// <summary>
  286. /// Tests that DateField handles calendar systems that differ from Gregorian.
  287. /// </summary>
  288. [Theory]
  289. [TestDate]
  290. [InlineData ("th-TH")] // Thai Buddhist calendar
  291. public void Culture_NonGregorianCalendar_HandlesFormatting (string cultureName)
  292. {
  293. if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
  294. {
  295. // macOS has known issues with certain calendars - see #3592
  296. return;
  297. }
  298. CultureInfo cultureBackup = CultureInfo.CurrentCulture;
  299. try
  300. {
  301. var culture = new CultureInfo (cultureName);
  302. // Parse date using InvariantCulture BEFORE changing CurrentCulture
  303. DateTime date = DateTime.Parse ("1/1/1971", CultureInfo.InvariantCulture);
  304. CultureInfo.CurrentCulture = culture;
  305. var df = new DateField (date);
  306. // Buddhist calendar is 543 years ahead (1971 + 543 = 2514)
  307. // Just verify it doesn't crash and produces valid output
  308. Assert.NotEmpty (df.Text);
  309. Assert.NotNull (df.Date);
  310. }
  311. catch (CultureNotFoundException)
  312. {
  313. // Skip cultures not available on this system
  314. }
  315. finally
  316. {
  317. CultureInfo.CurrentCulture = cultureBackup;
  318. }
  319. }
  320. }