SelectorBaseTests.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. #nullable enable
  2. namespace UnitTests_Parallelizable.ViewsTests;
  3. /// <summary>
  4. /// Tests for <see cref="SelectorBase"/> functionality that applies to all selector implementations.
  5. /// These tests use <see cref="OptionSelector"/> as a concrete implementation to test the base class.
  6. /// </summary>
  7. public class SelectorBaseTests
  8. {
  9. #region Initialization Tests
  10. [Fact]
  11. public void Constructor_SetsDefaults ()
  12. {
  13. var selector = new OptionSelector ();
  14. Assert.True (selector.CanFocus);
  15. Assert.Equal (Dim.Auto (DimAutoStyle.Content), selector.Width);
  16. Assert.Equal (Dim.Auto (DimAutoStyle.Content), selector.Height);
  17. Assert.Equal (Orientation.Vertical, selector.Orientation);
  18. Assert.Null (selector.Labels);
  19. Assert.Null (selector.Values);
  20. Assert.False (selector.AssignHotKeys);
  21. Assert.Empty (selector.UsedHotKeys);
  22. Assert.Equal (SelectorStyles.None, selector.Styles);
  23. Assert.True (selector.DoubleClickAccepts);
  24. Assert.Equal (2, selector.HorizontalSpace);
  25. }
  26. #endregion
  27. #region Value Property Tests
  28. [Fact]
  29. public void Value_Set_ValidValue_UpdatesValue ()
  30. {
  31. var selector = new OptionSelector ();
  32. selector.Labels = ["Option1", "Option2"];
  33. selector.Value = 1;
  34. Assert.Equal (1, selector.Value);
  35. }
  36. [Fact]
  37. public void Value_Set_InvalidValue_ThrowsArgumentOutOfRangeException ()
  38. {
  39. var selector = new OptionSelector ();
  40. selector.Labels = ["Option1", "Option2"];
  41. Assert.Throws<ArgumentOutOfRangeException> (() => selector.Value = 5);
  42. Assert.Throws<ArgumentOutOfRangeException> (() => selector.Value = -1);
  43. }
  44. [Fact]
  45. public void Value_Set_Null_Succeeds ()
  46. {
  47. var selector = new OptionSelector ();
  48. selector.Labels = ["Option1", "Option2"];
  49. selector.Value = null;
  50. Assert.Null (selector.Value);
  51. }
  52. [Fact]
  53. public void Value_Set_SameValue_DoesNotRaiseEvent ()
  54. {
  55. var selector = new OptionSelector ();
  56. selector.Labels = ["Option1", "Option2"];
  57. selector.Value = 1;
  58. var eventRaisedCount = 0;
  59. selector.ValueChanged += (s, e) => eventRaisedCount++;
  60. selector.Value = 1; // Set to same value
  61. Assert.Equal (0, eventRaisedCount);
  62. }
  63. [Fact]
  64. public void Value_Changed_RaisesValueChangedEvent ()
  65. {
  66. var selector = new OptionSelector ();
  67. selector.Labels = ["Option1", "Option2"];
  68. int? capturedValue = null;
  69. selector.ValueChanged += (s, e) => capturedValue = e.Value;
  70. selector.Value = 1;
  71. Assert.Equal (1, capturedValue);
  72. }
  73. #endregion
  74. #region Values Property Tests
  75. [Fact]
  76. public void Values_Get_WhenNull_ReturnsSequentialValues ()
  77. {
  78. var selector = new OptionSelector ();
  79. selector.Labels = ["Option1", "Option2", "Option3"];
  80. IReadOnlyList<int>? values = selector.Values;
  81. Assert.NotNull (values);
  82. Assert.Equal (3, values.Count);
  83. Assert.Equal ([0, 1, 2], values);
  84. }
  85. [Fact]
  86. public void Values_Set_UpdatesValues ()
  87. {
  88. var selector = new OptionSelector ();
  89. selector.Labels = ["Option1", "Option2"];
  90. selector.Values = [10, 20];
  91. Assert.Equal ([10, 20], selector.Values);
  92. }
  93. [Fact]
  94. public void Values_Set_SetsDefaultValue ()
  95. {
  96. var selector = new OptionSelector ();
  97. selector.Value = null;
  98. selector.Labels = ["Option1", "Option2"];
  99. selector.Values = [10, 20];
  100. Assert.Equal (10, selector.Value); // Should default to first value
  101. }
  102. #endregion
  103. #region Labels Property Tests
  104. [Fact]
  105. public void Labels_Set_CreatesSubViews ()
  106. {
  107. var selector = new OptionSelector ();
  108. selector.Labels = ["Option1", "Option2"];
  109. Assert.Equal (2, selector.SubViews.OfType<CheckBox> ().Count ());
  110. }
  111. [Fact]
  112. public void Labels_Set_Null_RemovesSubViews ()
  113. {
  114. var selector = new OptionSelector ();
  115. selector.Labels = ["Option1", "Option2"];
  116. selector.Labels = null;
  117. Assert.Empty (selector.SubViews.OfType<CheckBox> ());
  118. }
  119. [Fact]
  120. public void Labels_Values_CountMismatch_DoesNotCreateSubViews ()
  121. {
  122. var selector = new OptionSelector ();
  123. selector.Values = [0, 1, 2];
  124. selector.Labels = ["Option1", "Option2"]; // Mismatch
  125. Assert.Empty (selector.SubViews.OfType<CheckBox> ());
  126. }
  127. #endregion
  128. #region SetValuesAndLabels Tests
  129. [Fact]
  130. public void SetValuesAndLabels_FromEnum_SetsValuesAndLabels ()
  131. {
  132. var selector = new OptionSelector ();
  133. selector.SetValuesAndLabels<SelectorStyles> ();
  134. Assert.NotNull (selector.Values);
  135. Assert.NotNull (selector.Labels);
  136. Assert.Equal (Enum.GetValues<SelectorStyles> ().Length, selector.Values.Count);
  137. Assert.Equal (Enum.GetNames<SelectorStyles> (), selector.Labels);
  138. }
  139. [Fact]
  140. public void SetValuesAndLabels_SetsCorrectIntegerValues ()
  141. {
  142. var selector = new OptionSelector ();
  143. selector.SetValuesAndLabels<SelectorStyles> ();
  144. // Verify values match enum integer values
  145. var expectedValues = Enum.GetValues<SelectorStyles> ().Select (e => (int)e).ToList ();
  146. Assert.Equal (expectedValues, selector.Values);
  147. }
  148. #endregion
  149. #region Styles Property Tests
  150. [Fact]
  151. public void Styles_Set_None_NoExtraSubViews ()
  152. {
  153. var selector = new OptionSelector ();
  154. selector.Labels = ["Option1", "Option2"];
  155. selector.Styles = SelectorStyles.None;
  156. Assert.Equal (2, selector.SubViews.Count);
  157. Assert.Null (selector.SubViews.FirstOrDefault (v => v.Id == "valueField"));
  158. }
  159. [Fact]
  160. public void Styles_Set_ShowValue_AddsValueField ()
  161. {
  162. var selector = new OptionSelector ();
  163. selector.Labels = ["Option1", "Option2"];
  164. selector.Styles = SelectorStyles.ShowValue;
  165. View? valueField = selector.SubViews.FirstOrDefault (v => v.Id == "valueField");
  166. Assert.NotNull (valueField);
  167. Assert.IsType<TextField> (valueField);
  168. }
  169. [Fact]
  170. public void Styles_Set_ShowValue_ValueFieldDisplaysCurrentValue ()
  171. {
  172. var selector = new OptionSelector ();
  173. selector.Labels = ["Option1", "Option2"];
  174. selector.Value = 1;
  175. selector.Styles = SelectorStyles.ShowValue;
  176. var valueField = (TextField?)selector.SubViews.FirstOrDefault (v => v.Id == "valueField");
  177. Assert.NotNull (valueField);
  178. Assert.Equal ("1", valueField.Text);
  179. }
  180. [Fact]
  181. public void Styles_Set_ShowValue_ValueFieldUpdatesOnValueChange ()
  182. {
  183. var selector = new OptionSelector ();
  184. selector.Labels = ["Option1", "Option2"];
  185. selector.Styles = SelectorStyles.ShowValue;
  186. selector.Value = 1;
  187. var valueField = (TextField?)selector.SubViews.FirstOrDefault (v => v.Id == "valueField");
  188. Assert.NotNull (valueField);
  189. Assert.Equal ("1", valueField.Text);
  190. }
  191. [Fact]
  192. public void Styles_Set_SameValue_DoesNotRecreateSubViews ()
  193. {
  194. var selector = new OptionSelector ();
  195. selector.Labels = ["Option1", "Option2"];
  196. selector.Styles = SelectorStyles.ShowValue;
  197. CheckBox firstCheckBox = selector.SubViews.OfType<CheckBox> ().First ();
  198. selector.Styles = SelectorStyles.ShowValue; // Set to same value
  199. // Should be the same instance
  200. Assert.Same (firstCheckBox, selector.SubViews.OfType<CheckBox> ().First ());
  201. }
  202. #endregion
  203. #region AssignHotKeys Tests
  204. [Fact]
  205. public void AssignHotKeys_True_AssignsHotKeysToCheckBoxes ()
  206. {
  207. var selector = new OptionSelector { AssignHotKeys = true };
  208. selector.Labels = ["Option1", "Option2"];
  209. CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
  210. Assert.NotEqual (Key.Empty, checkBoxes [0].HotKey);
  211. Assert.NotEqual (Key.Empty, checkBoxes [1].HotKey);
  212. }
  213. [Fact]
  214. public void AssignHotKeys_True_AddsHotKeySpecifierToTitles ()
  215. {
  216. var selector = new OptionSelector { AssignHotKeys = true };
  217. selector.Labels = ["Option1", "Option2"];
  218. CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
  219. Assert.Contains ('_', checkBoxes [0].Title);
  220. Assert.Contains ('_', checkBoxes [1].Title);
  221. }
  222. [Fact]
  223. public void AssignHotKeys_True_AssignsUniqueHotKeys ()
  224. {
  225. var selector = new OptionSelector { AssignHotKeys = true };
  226. selector.Labels = ["Option1", "Option2", "Option3"];
  227. CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
  228. var hotKeys = checkBoxes.Select (cb => cb.HotKey).ToList ();
  229. Assert.Equal (3, hotKeys.Distinct ().Count ()); // All unique
  230. }
  231. [Fact]
  232. public void AssignHotKeys_False_DoesNotAssignHotKeys ()
  233. {
  234. var selector = new OptionSelector { AssignHotKeys = false };
  235. selector.Labels = ["Option1", "Option2"];
  236. CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
  237. Assert.Equal (Key.Empty, checkBoxes [0].HotKey);
  238. Assert.Equal (Key.Empty, checkBoxes [1].HotKey);
  239. }
  240. [Fact]
  241. public void AssignHotKeys_PreservesExistingHotKeys ()
  242. {
  243. var selector = new OptionSelector { AssignHotKeys = true };
  244. selector.Labels = ["_Alt Option", "Option2"];
  245. CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
  246. // Should use 'A' from "_Alt"
  247. Assert.Equal (Key.A, checkBoxes [0].HotKey);
  248. }
  249. #endregion
  250. #region UsedHotKeys Tests
  251. [Fact]
  252. public void UsedHotKeys_SkipsMarkedKeys ()
  253. {
  254. var selector = new OptionSelector { AssignHotKeys = true };
  255. selector.UsedHotKeys.Add (Key.O); // Mark 'O' as used
  256. selector.Labels = ["Option1", "Option2"];
  257. CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
  258. // Should skip 'O' and use next available character
  259. Assert.NotEqual (Key.O, checkBoxes [0].HotKey);
  260. }
  261. [Fact]
  262. public void UsedHotKeys_PopulatedWhenHotKeysAssigned ()
  263. {
  264. var selector = new OptionSelector { AssignHotKeys = true };
  265. selector.Labels = ["Option1", "Option2"];
  266. // UsedHotKeys should contain the assigned hotkeys
  267. Assert.NotEmpty (selector.UsedHotKeys);
  268. CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
  269. foreach (CheckBox cb in checkBoxes)
  270. {
  271. Assert.Contains (cb.HotKey, selector.UsedHotKeys);
  272. }
  273. }
  274. #endregion
  275. #region Orientation Tests
  276. [Fact]
  277. public void Orientation_Vertical_CheckBoxesStackedVertically ()
  278. {
  279. var selector = new OptionSelector ();
  280. selector.Labels = ["Option1", "Option2"];
  281. selector.Orientation = Orientation.Vertical;
  282. selector.Layout ();
  283. CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
  284. Assert.Equal (0, checkBoxes [0].Frame.Y);
  285. Assert.True (checkBoxes [1].Frame.Y > checkBoxes [0].Frame.Y);
  286. }
  287. [Fact]
  288. public void Orientation_Horizontal_CheckBoxesArrangedHorizontally ()
  289. {
  290. var selector = new OptionSelector ();
  291. selector.Labels = ["Option1", "Option2"];
  292. selector.Orientation = Orientation.Horizontal;
  293. selector.Layout ();
  294. CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
  295. Assert.Equal (0, checkBoxes [0].Frame.Y);
  296. Assert.Equal (0, checkBoxes [1].Frame.Y);
  297. Assert.True (checkBoxes [1].Frame.X > checkBoxes [0].Frame.X);
  298. }
  299. [Fact]
  300. public void Orientation_Change_TriggersLayout ()
  301. {
  302. var selector = new OptionSelector ();
  303. selector.Labels = ["Option1", "Option2"];
  304. selector.Layout ();
  305. CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
  306. int originalYDiff = checkBoxes [1].Frame.Y - checkBoxes [0].Frame.Y;
  307. selector.Orientation = Orientation.Horizontal;
  308. selector.Layout ();
  309. int newYDiff = checkBoxes [1].Frame.Y - checkBoxes [0].Frame.Y;
  310. Assert.NotEqual (originalYDiff, newYDiff);
  311. Assert.Equal (0, newYDiff); // Both should be at Y=0 now
  312. }
  313. #endregion
  314. #region HorizontalSpace Tests
  315. [Fact]
  316. public void HorizontalSpace_Default_Is2 ()
  317. {
  318. var selector = new OptionSelector ();
  319. Assert.Equal (2, selector.HorizontalSpace);
  320. }
  321. [Fact]
  322. public void HorizontalSpace_Set_UpdatesSpacing ()
  323. {
  324. var selector = new OptionSelector { Orientation = Orientation.Horizontal };
  325. selector.Labels = ["Option1", "Option2"];
  326. selector.HorizontalSpace = 2;
  327. selector.Layout ();
  328. CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
  329. // HorizontalSpace is applied via Margin.Thickness.Right
  330. int spacing2 = checkBoxes [0].Margin!.Thickness.Right;
  331. selector.HorizontalSpace = 5;
  332. selector.Layout ();
  333. int spacing5 = checkBoxes [0].Margin!.Thickness.Right;
  334. Assert.True (spacing5 > spacing2);
  335. Assert.Equal (2, spacing2);
  336. Assert.Equal (5, spacing5);
  337. }
  338. [Fact]
  339. public void HorizontalSpace_OnlyAppliesToHorizontalOrientation ()
  340. {
  341. var selector = new OptionSelector { Orientation = Orientation.Vertical };
  342. selector.Labels = ["Option1", "Option2"];
  343. selector.HorizontalSpace = 10;
  344. selector.Layout ();
  345. CheckBox [] checkBoxes = selector.SubViews.OfType<CheckBox> ().ToArray ();
  346. // In vertical mode, checkboxes should be at same X
  347. Assert.Equal (checkBoxes [0].Frame.X, checkBoxes [1].Frame.X);
  348. }
  349. #endregion
  350. #region DoubleClickAccepts Tests
  351. [Fact]
  352. public void DoubleClickAccepts_Default_IsTrue ()
  353. {
  354. var selector = new OptionSelector ();
  355. Assert.True (selector.DoubleClickAccepts);
  356. }
  357. [Fact]
  358. public void DoubleClickAccepts_True_AcceptOnDoubleClick ()
  359. {
  360. var selector = new OptionSelector { DoubleClickAccepts = true };
  361. selector.Labels = ["Option1", "Option2"];
  362. selector.Layout ();
  363. var acceptCount = 0;
  364. selector.Accepting += (s, e) => acceptCount++;
  365. CheckBox checkBox = selector.SubViews.OfType<CheckBox> ().First ();
  366. checkBox.NewMouseEvent (new () { Position = Point.Empty, Flags = MouseFlags.Button1Clicked });
  367. checkBox.NewMouseEvent (new () { Position = Point.Empty, Flags = MouseFlags.Button1DoubleClicked });
  368. Assert.Equal (1, acceptCount);
  369. }
  370. [Fact]
  371. public void DoubleClickAccepts_False_DoesNotAcceptOnDoubleClick ()
  372. {
  373. var selector = new OptionSelector { DoubleClickAccepts = false };
  374. selector.Labels = ["Option1", "Option2"];
  375. selector.Layout ();
  376. var acceptCount = 0;
  377. selector.Accepting += (s, e) => acceptCount++;
  378. CheckBox checkBox = selector.SubViews.OfType<CheckBox> ().First ();
  379. checkBox.NewMouseEvent (new () { Position = Point.Empty, Flags = MouseFlags.Button1Clicked });
  380. checkBox.NewMouseEvent (new () { Position = Point.Empty, Flags = MouseFlags.Button1DoubleClicked });
  381. Assert.Equal (0, acceptCount);
  382. }
  383. #endregion
  384. #region CreateSubViews Tests
  385. [Fact]
  386. public void CreateSubViews_RemovesOldSubViewsAndCreatesNew ()
  387. {
  388. var selector = new OptionSelector ();
  389. selector.Labels = ["Option1", "Option2"];
  390. int oldCount = selector.SubViews.Count;
  391. selector.Labels = ["New1", "New2", "New3"];
  392. Assert.NotEqual (oldCount, selector.SubViews.Count);
  393. Assert.Equal (3, selector.SubViews.OfType<CheckBox> ().Count ());
  394. Assert.Contains (selector.SubViews.OfType<CheckBox> (), cb => cb.Title == "New1");
  395. }
  396. [Fact]
  397. public void CreateSubViews_SetsCheckBoxProperties ()
  398. {
  399. var selector = new OptionSelector ();
  400. selector.Labels = ["Test Option"];
  401. selector.Values = [42];
  402. CheckBox checkBox = selector.SubViews.OfType<CheckBox> ().First ();
  403. Assert.Equal ("Test Option", checkBox.Title);
  404. Assert.Equal ("Test Option", checkBox.Id);
  405. Assert.Equal (42, checkBox.Data);
  406. Assert.True (checkBox.CanFocus);
  407. }
  408. #endregion
  409. #region HotKey Command Tests
  410. [Fact]
  411. public void HotKey_Command_DoesNotFireAccept ()
  412. {
  413. var selector = new OptionSelector ();
  414. selector.Labels = ["Option1", "Option2"];
  415. var acceptCount = 0;
  416. selector.Accepting += (s, e) => acceptCount++;
  417. selector.InvokeCommand (Command.HotKey);
  418. Assert.Equal (0, acceptCount);
  419. }
  420. [Fact]
  421. public void Accept_Command_FiresAccept ()
  422. {
  423. var selector = new OptionSelector ();
  424. selector.Labels = ["Option1", "Option2"];
  425. var acceptCount = 0;
  426. selector.Accepting += (s, e) => acceptCount++;
  427. selector.InvokeCommand (Command.Accept);
  428. Assert.Equal (1, acceptCount);
  429. }
  430. #endregion
  431. #region Edge Cases
  432. [Fact]
  433. public void EmptyLabels_CreatesNoSubViews ()
  434. {
  435. var selector = new OptionSelector ();
  436. selector.Labels = [];
  437. Assert.Empty (selector.SubViews);
  438. }
  439. [Fact]
  440. public void Value_WithNoLabels_CanBeSet ()
  441. {
  442. var selector = new OptionSelector ();
  443. // This should work even without labels
  444. var exception = Record.Exception (() => selector.Value = null);
  445. Assert.Null (exception);
  446. Assert.Null (selector.Value);
  447. }
  448. #endregion
  449. }