AdornmentNavigationTests.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. namespace ViewBaseTests.Navigation;
  2. /// <summary>
  3. /// Tests for navigation into and out of Adornments (Padding, Border, Margin).
  4. /// These tests prove that navigation to/from adornments is broken and need to be fixed.
  5. /// </summary>
  6. public class AdornmentNavigationTests
  7. {
  8. #region Padding Navigation Tests
  9. [Fact]
  10. [Trait ("Category", "Adornment")]
  11. [Trait ("Category", "Navigation")]
  12. public void AdvanceFocus_Into_Padding_With_Focusable_SubView ()
  13. {
  14. // Setup: View with a focusable subview in Padding
  15. View view = new ()
  16. {
  17. Id = "view",
  18. Width = 10,
  19. Height = 10,
  20. CanFocus = true
  21. };
  22. view.Padding!.Thickness = new Thickness (1);
  23. View paddingButton = new ()
  24. {
  25. Id = "paddingButton",
  26. CanFocus = true,
  27. TabStop = TabBehavior.TabStop,
  28. X = 0,
  29. Y = 0,
  30. Width = 5,
  31. Height = 1
  32. };
  33. view.Padding.Add (paddingButton);
  34. View contentButton = new ()
  35. {
  36. Id = "contentButton",
  37. CanFocus = true,
  38. TabStop = TabBehavior.TabStop,
  39. X = 0,
  40. Y = 0,
  41. Width = 5,
  42. Height = 1
  43. };
  44. view.Add (contentButton);
  45. view.BeginInit ();
  46. view.EndInit ();
  47. // Test: Advance focus should navigate to content first
  48. view.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
  49. // Expected: contentButton should have focus
  50. // This test documents the expected behavior for navigation into padding
  51. Assert.True (contentButton.HasFocus, "Content view should receive focus first");
  52. Assert.False (paddingButton.HasFocus, "Padding subview should not have focus yet");
  53. // Test: Advance focus again should go to padding
  54. view.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
  55. // Expected: paddingButton should now have focus
  56. // This will likely FAIL, proving the bug exists
  57. Assert.True (paddingButton.HasFocus, "Padding subview should receive focus after content");
  58. Assert.False (contentButton.HasFocus, "Content view should no longer have focus");
  59. view.Dispose ();
  60. }
  61. [Fact]
  62. [Trait ("Category", "Adornment")]
  63. [Trait ("Category", "Navigation")]
  64. public void AdvanceFocus_Out_Of_Padding_To_Content ()
  65. {
  66. // Setup: View with focusable padding that has focus
  67. View view = new ()
  68. {
  69. Id = "view",
  70. Width = 10,
  71. Height = 10,
  72. CanFocus = true
  73. };
  74. view.Padding!.Thickness = new Thickness (1);
  75. View paddingButton = new ()
  76. {
  77. Id = "paddingButton",
  78. CanFocus = true,
  79. TabStop = TabBehavior.TabStop,
  80. X = 0,
  81. Y = 0,
  82. Width = 5,
  83. Height = 1
  84. };
  85. view.Padding.Add (paddingButton);
  86. View contentButton = new ()
  87. {
  88. Id = "contentButton",
  89. CanFocus = true,
  90. TabStop = TabBehavior.TabStop,
  91. X = 0,
  92. Y = 0,
  93. Width = 5,
  94. Height = 1
  95. };
  96. view.Add (contentButton);
  97. view.BeginInit ();
  98. view.EndInit ();
  99. // Set focus to padding button
  100. paddingButton.SetFocus ();
  101. Assert.True (paddingButton.HasFocus, "Setup: Padding button should have focus");
  102. // Test: Advance focus should navigate from padding to content
  103. view.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
  104. // Expected: Should navigate to content
  105. // This will likely FAIL, proving the bug exists
  106. Assert.True (contentButton.HasFocus, "Content view should receive focus after padding");
  107. Assert.False (paddingButton.HasFocus, "Padding button should no longer have focus");
  108. view.Dispose ();
  109. }
  110. [Fact]
  111. [Trait ("Category", "Adornment")]
  112. [Trait ("Category", "Navigation")]
  113. public void AdvanceFocus_Backward_Into_Padding ()
  114. {
  115. // Setup: View with focusable subviews in both content and padding
  116. View view = new ()
  117. {
  118. Id = "view",
  119. Width = 10,
  120. Height = 10,
  121. CanFocus = true
  122. };
  123. view.Padding!.Thickness = new Thickness (1);
  124. View paddingButton = new ()
  125. {
  126. Id = "paddingButton",
  127. CanFocus = true,
  128. TabStop = TabBehavior.TabStop,
  129. X = 0,
  130. Y = 0,
  131. Width = 5,
  132. Height = 1
  133. };
  134. view.Padding.Add (paddingButton);
  135. View contentButton = new ()
  136. {
  137. Id = "contentButton",
  138. CanFocus = true,
  139. TabStop = TabBehavior.TabStop,
  140. X = 0,
  141. Y = 0,
  142. Width = 5,
  143. Height = 1
  144. };
  145. view.Add (contentButton);
  146. view.BeginInit ();
  147. view.EndInit ();
  148. // Set focus to content
  149. contentButton.SetFocus ();
  150. Assert.True (contentButton.HasFocus, "Setup: Content button should have focus");
  151. // Test: Advance focus backward should go to padding
  152. view.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop);
  153. // Expected: Should navigate to padding
  154. // This will likely FAIL, proving the bug exists
  155. Assert.True (paddingButton.HasFocus, "Padding button should receive focus when navigating backward");
  156. Assert.False (contentButton.HasFocus, "Content button should no longer have focus");
  157. view.Dispose ();
  158. }
  159. [Fact]
  160. [Trait ("Category", "Adornment")]
  161. [Trait ("Category", "Navigation")]
  162. public void Padding_CanFocus_True_TabStop_TabStop_Should_Be_In_FocusChain ()
  163. {
  164. // Setup: View with focusable Padding
  165. View view = new ()
  166. {
  167. Id = "view",
  168. Width = 10,
  169. Height = 10,
  170. CanFocus = true
  171. };
  172. view.Padding!.Thickness = new (1);
  173. view.Padding.CanFocus = true;
  174. view.Padding.TabStop = TabBehavior.TabStop;
  175. view.BeginInit ();
  176. view.EndInit ();
  177. // Test: Get focus chain
  178. View [] focusChain = view.GetFocusChain (NavigationDirection.Forward, TabBehavior.TabStop);
  179. // Expected: Padding should be in the focus chain
  180. // This should pass based on the GetFocusChain code
  181. Assert.Contains (view.Padding, focusChain);
  182. view.Dispose ();
  183. }
  184. #endregion
  185. #region Border Navigation Tests
  186. [Fact]
  187. [Trait ("Category", "Adornment")]
  188. [Trait ("Category", "Navigation")]
  189. public void AdvanceFocus_Into_Border_With_Focusable_SubView ()
  190. {
  191. // Setup: View with a focusable subview in Border
  192. View view = new ()
  193. {
  194. Id = "view",
  195. Width = 10,
  196. Height = 10,
  197. CanFocus = true
  198. };
  199. view.Border!.Thickness = new Thickness (1);
  200. View borderButton = new ()
  201. {
  202. Id = "borderButton",
  203. CanFocus = true,
  204. TabStop = TabBehavior.TabGroup,
  205. X = 0,
  206. Y = 0,
  207. Width = 5,
  208. Height = 1
  209. };
  210. view.Border.Add (borderButton);
  211. View contentButton = new ()
  212. {
  213. Id = "contentButton",
  214. CanFocus = true,
  215. TabStop = TabBehavior.TabGroup,
  216. X = 0,
  217. Y = 0,
  218. Width = 5,
  219. Height = 1
  220. };
  221. view.Add (contentButton);
  222. view.BeginInit ();
  223. view.EndInit ();
  224. // Test: Advance focus should navigate between content and border
  225. view.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup);
  226. // Expected: One of them should have focus
  227. var hasFocus = contentButton.HasFocus || borderButton.HasFocus;
  228. Assert.True (hasFocus, "Either content or border button should have focus");
  229. // Advance again
  230. view.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup);
  231. // Expected: The other one should now have focus
  232. // This will likely FAIL, proving the bug exists
  233. if (contentButton.HasFocus)
  234. {
  235. // If content has focus now, border should have had it before
  236. Assert.False (borderButton.HasFocus, "Only one should have focus at a time");
  237. }
  238. else
  239. {
  240. Assert.True (borderButton.HasFocus, "Border should have focus if content doesn't");
  241. }
  242. view.Dispose ();
  243. }
  244. [Fact]
  245. [Trait ("Category", "Adornment")]
  246. [Trait ("Category", "Navigation")]
  247. public void Border_CanFocus_True_TabStop_TabGroup_Should_NOT_Be_In_FocusChain ()
  248. {
  249. // Setup: View with focusable Border (default TabStop is TabGroup for Border)
  250. View view = new ()
  251. {
  252. Id = "view",
  253. Width = 10,
  254. Height = 10,
  255. CanFocus = true
  256. };
  257. view.Border!.Thickness = new Thickness (1);
  258. view.Border.CanFocus = true;
  259. view.BeginInit ();
  260. view.EndInit ();
  261. // Test: Get focus chain for TabGroup
  262. View [] focusChain = view.GetFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
  263. // Expected: Border should be in the focus chain
  264. Assert.DoesNotContain (view.Border, focusChain);
  265. view.Dispose ();
  266. }
  267. #endregion
  268. #region Margin Navigation Tests
  269. [Fact]
  270. [Trait ("Category", "Adornment")]
  271. [Trait ("Category", "Navigation")]
  272. public void Margin_CanFocus_True_Should_NOT_Be_In_FocusChain ()
  273. {
  274. // Setup: View with focusable Margin
  275. View view = new ()
  276. {
  277. Id = "view",
  278. Width = 10,
  279. Height = 10,
  280. CanFocus = true
  281. };
  282. view.Margin!.Thickness = new Thickness (1);
  283. view.Margin.CanFocus = true;
  284. view.Margin.TabStop = TabBehavior.TabStop;
  285. view.BeginInit ();
  286. view.EndInit ();
  287. // Test: Get focus chain
  288. View [] focusChain = view.GetFocusChain (NavigationDirection.Forward, TabBehavior.TabStop);
  289. // Expected: Margin should be in the focus chain
  290. Assert.DoesNotContain (view.Margin, focusChain);
  291. view.Dispose ();
  292. }
  293. #endregion
  294. #region Mixed Scenarios
  295. [Fact]
  296. [Trait ("Category", "Adornment")]
  297. [Trait ("Category", "Navigation")]
  298. public void AdvanceFocus_Nested_Views_With_Adornment_SubViews ()
  299. {
  300. // Setup: Nested views where parent has adornment subviews
  301. View parent = new ()
  302. {
  303. Id = "parent",
  304. Width = 30,
  305. Height = 30,
  306. CanFocus = true
  307. };
  308. parent.Padding!.Thickness = new Thickness (2);
  309. View parentPaddingButton = new ()
  310. {
  311. Id = "parentPaddingButton",
  312. CanFocus = true,
  313. TabStop = TabBehavior.TabStop,
  314. X = 0,
  315. Y = 0,
  316. Width = 8,
  317. Height = 1
  318. };
  319. parent.Padding.Add (parentPaddingButton);
  320. View child = new ()
  321. {
  322. Id = "child",
  323. Width = 10,
  324. Height = 10,
  325. CanFocus = true,
  326. TabStop = TabBehavior.TabStop
  327. };
  328. parent.Add (child);
  329. child.Padding!.Thickness = new Thickness (1);
  330. View childPaddingButton = new ()
  331. {
  332. Id = "childPaddingButton",
  333. CanFocus = true,
  334. TabStop = TabBehavior.TabStop,
  335. X = 0,
  336. Y = 0,
  337. Width = 5,
  338. Height = 1
  339. };
  340. child.Padding.Add (childPaddingButton);
  341. parent.BeginInit ();
  342. parent.EndInit ();
  343. // Test: Advance focus should navigate through parent padding, child, and child padding
  344. parent.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
  345. // Track which views receive focus
  346. List<string> focusedIds = new ();
  347. // Navigate multiple times to test nested navigation (extra iteration to allow for wrapping)
  348. for (var i = 0; i < 5; i++)
  349. {
  350. if (parentPaddingButton.HasFocus)
  351. {
  352. focusedIds.Add ("parentPaddingButton");
  353. }
  354. else if (child.HasFocus)
  355. {
  356. focusedIds.Add ("child");
  357. }
  358. else if (childPaddingButton.HasFocus)
  359. {
  360. focusedIds.Add ("childPaddingButton");
  361. }
  362. parent.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
  363. }
  364. // Expected: Navigation should reach all elements including adornment subviews
  365. // This will likely show incomplete navigation, proving the bug exists
  366. Assert.True (
  367. focusedIds.Count > 0,
  368. "At least some navigation should occur (this test documents current behavior)"
  369. );
  370. parent.Dispose ();
  371. }
  372. #endregion
  373. #region TabGroup Behavior Tests
  374. #endregion
  375. #region Edge Cases
  376. [Fact]
  377. [Trait ("Category", "Adornment")]
  378. [Trait ("Category", "Navigation")]
  379. public void AdvanceFocus_Padding_With_No_Thickness_Should_Not_Participate ()
  380. {
  381. // Setup: View with Padding that has no thickness but has subviews
  382. View view = new ()
  383. {
  384. Id = "view",
  385. Width = 10,
  386. Height = 10,
  387. CanFocus = true
  388. };
  389. // Padding has default Thickness.Empty
  390. View paddingButton = new ()
  391. {
  392. Id = "paddingButton",
  393. CanFocus = true,
  394. TabStop = TabBehavior.TabStop,
  395. X = 0,
  396. Y = 0,
  397. Width = 5,
  398. Height = 1
  399. };
  400. view.Padding!.Add (paddingButton);
  401. View contentButton = new ()
  402. {
  403. Id = "contentButton",
  404. CanFocus = true,
  405. TabStop = TabBehavior.TabStop,
  406. X = 0,
  407. Y = 0,
  408. Width = 5,
  409. Height = 1
  410. };
  411. view.Add (contentButton);
  412. view.BeginInit ();
  413. view.EndInit ();
  414. // Test: Navigate - should only focus content since Padding has no thickness
  415. view.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
  416. Assert.True (contentButton.HasFocus, "Content should get focus");
  417. view.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
  418. // Expected: Should wrap back to content, not go to padding
  419. Assert.True (contentButton.HasFocus, "Should stay in content when Padding has no thickness");
  420. Assert.False (paddingButton.HasFocus, "Padding button should not receive focus");
  421. view.Dispose ();
  422. }
  423. [Fact]
  424. [Trait ("Category", "Adornment")]
  425. [Trait ("Category", "Navigation")]
  426. public void AdvanceFocus_Disabled_Adornment_SubView_Should_Be_Skipped ()
  427. {
  428. // Setup: View with disabled subview in Padding
  429. View view = new ()
  430. {
  431. Id = "view",
  432. Width = 10,
  433. Height = 10,
  434. CanFocus = true
  435. };
  436. view.Padding!.Thickness = new Thickness (1);
  437. View paddingButton = new ()
  438. {
  439. Id = "paddingButton",
  440. CanFocus = true,
  441. TabStop = TabBehavior.TabStop,
  442. Enabled = false, // Disabled
  443. X = 0,
  444. Y = 0,
  445. Width = 5,
  446. Height = 1
  447. };
  448. view.Padding.Add (paddingButton);
  449. View contentButton = new ()
  450. {
  451. Id = "contentButton",
  452. CanFocus = true,
  453. TabStop = TabBehavior.TabStop,
  454. X = 0,
  455. Y = 0,
  456. Width = 5,
  457. Height = 1
  458. };
  459. view.Add (contentButton);
  460. view.BeginInit ();
  461. view.EndInit ();
  462. // Test: Navigate - disabled padding button should be skipped
  463. view.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
  464. Assert.True (contentButton.HasFocus, "Content should get focus");
  465. view.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
  466. // Expected: Should wrap back to content, skipping disabled padding button
  467. Assert.True (contentButton.HasFocus, "Should skip disabled padding button");
  468. Assert.False (paddingButton.HasFocus, "Disabled padding button should not receive focus");
  469. view.Dispose ();
  470. }
  471. #endregion
  472. }