2
0

TextViewNavigationTests.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. using UnitTests;
  2. namespace UnitTests_Parallelizable.ViewsTests;
  3. /// <summary>
  4. /// Tests for TextView navigation, tabs, and cursor positioning.
  5. /// These replace the old non-parallelizable tests that had hard-coded viewport dimensions.
  6. /// </summary>
  7. public class TextViewNavigationTests : FakeDriverBase
  8. {
  9. [Fact]
  10. public void Tab_And_BackTab_Navigation_Without_Text ()
  11. {
  12. var textView = new TextView
  13. {
  14. Width = 30,
  15. Height = 10,
  16. Text = ""
  17. };
  18. textView.BeginInit ();
  19. textView.EndInit ();
  20. // Add 100 tabs
  21. for (var i = 0; i < 100; i++)
  22. {
  23. textView.Text += "\t";
  24. }
  25. // Move to end
  26. textView.MoveEnd ();
  27. Assert.Equal (new Point (100, 0), textView.CursorPosition);
  28. // Test BackTab (Shift+Tab) navigation backwards
  29. for (var i = 99; i >= 0; i--)
  30. {
  31. Assert.True (textView.NewKeyDownEvent (Key.Tab.WithShift));
  32. Assert.Equal (new Point (i, 0), textView.CursorPosition);
  33. }
  34. // Test Tab navigation forwards
  35. for (var i = 1; i <= 100; i++)
  36. {
  37. Assert.True (textView.NewKeyDownEvent (Key.Tab));
  38. Assert.Equal (new Point (i, 0), textView.CursorPosition);
  39. }
  40. }
  41. [Fact]
  42. public void Tab_And_BackTab_Navigation_With_Text ()
  43. {
  44. var textView = new TextView
  45. {
  46. Width = 30,
  47. Height = 10,
  48. Text = "TAB to jump between text fields."
  49. };
  50. textView.BeginInit ();
  51. textView.EndInit ();
  52. Assert.Equal (new Point (0, 0), textView.CursorPosition);
  53. // Navigate forward with Tab
  54. for (var i = 1; i <= 100; i++)
  55. {
  56. Assert.True (textView.NewKeyDownEvent (Key.Tab));
  57. Assert.Equal (new Point (i, 0), textView.CursorPosition);
  58. }
  59. // Navigate backward with BackTab
  60. for (var i = 99; i >= 0; i--)
  61. {
  62. Assert.True (textView.NewKeyDownEvent (Key.Tab.WithShift));
  63. Assert.Equal (new Point (i, 0), textView.CursorPosition);
  64. }
  65. }
  66. [Fact]
  67. public void Tab_With_CursorLeft_And_CursorRight_Without_Text ()
  68. {
  69. var textView = new TextView
  70. {
  71. Width = 30,
  72. Height = 10,
  73. Text = ""
  74. };
  75. textView.BeginInit ();
  76. textView.EndInit ();
  77. // Navigate forward with Tab
  78. for (var i = 1; i <= 100; i++)
  79. {
  80. Assert.True (textView.NewKeyDownEvent (Key.Tab));
  81. Assert.Equal (new Point (i, 0), textView.CursorPosition);
  82. }
  83. // Navigate backward with CursorLeft
  84. for (var i = 99; i >= 0; i--)
  85. {
  86. Assert.True (textView.NewKeyDownEvent (Key.CursorLeft));
  87. Assert.Equal (new Point (i, 0), textView.CursorPosition);
  88. }
  89. // Navigate forward with CursorRight
  90. for (var i = 1; i <= 100; i++)
  91. {
  92. Assert.True (textView.NewKeyDownEvent (Key.CursorRight));
  93. Assert.Equal (new Point (i, 0), textView.CursorPosition);
  94. }
  95. }
  96. [Fact]
  97. public void Tab_With_CursorLeft_And_CursorRight_With_Text ()
  98. {
  99. var textView = new TextView
  100. {
  101. Width = 30,
  102. Height = 10,
  103. Text = "TAB to jump between text fields."
  104. };
  105. textView.BeginInit ();
  106. textView.EndInit ();
  107. Assert.Equal (32, textView.Text.Length);
  108. Assert.Equal (new Point (0, 0), textView.CursorPosition);
  109. // Navigate forward with Tab
  110. for (var i = 1; i <= 100; i++)
  111. {
  112. Assert.True (textView.NewKeyDownEvent (Key.Tab));
  113. Assert.Equal (new Point (i, 0), textView.CursorPosition);
  114. }
  115. // Navigate backward with CursorLeft
  116. for (var i = 99; i >= 0; i--)
  117. {
  118. Assert.True (textView.NewKeyDownEvent (Key.CursorLeft));
  119. Assert.Equal (new Point (i, 0), textView.CursorPosition);
  120. }
  121. // Navigate forward with CursorRight
  122. for (var i = 1; i <= 100; i++)
  123. {
  124. Assert.True (textView.NewKeyDownEvent (Key.CursorRight));
  125. Assert.Equal (new Point (i, 0), textView.CursorPosition);
  126. }
  127. }
  128. [Fact]
  129. public void Tab_With_Home_End_And_BackTab ()
  130. {
  131. var textView = new TextView
  132. {
  133. Width = 30,
  134. Height = 10,
  135. Text = "TAB to jump between text fields."
  136. };
  137. textView.BeginInit ();
  138. textView.EndInit ();
  139. Assert.Equal (32, textView.Text.Length);
  140. Assert.Equal (new Point (0, 0), textView.CursorPosition);
  141. // Navigate forward with Tab to column 100
  142. for (var i = 1; i <= 100; i++)
  143. {
  144. Assert.True (textView.NewKeyDownEvent (Key.Tab));
  145. Assert.Equal (new Point (i, 0), textView.CursorPosition);
  146. }
  147. // Test Length increased due to tabs
  148. Assert.Equal (132, textView.Text.Length);
  149. // Press Home to go to beginning
  150. Assert.True (textView.NewKeyDownEvent (Key.Home));
  151. Assert.Equal (new Point (0, 0), textView.CursorPosition);
  152. // Press End to go to end
  153. Assert.True (textView.NewKeyDownEvent (Key.End));
  154. Assert.Equal (132, textView.Text.Length);
  155. Assert.Equal (new Point (132, 0), textView.CursorPosition);
  156. // Find the position just before the last tab
  157. string txt = textView.Text;
  158. var col = txt.Length;
  159. // Find the last tab position
  160. while (col > 1 && txt [col - 1] != '\t')
  161. {
  162. col--;
  163. // Safety check to prevent infinite loop
  164. if (col == 0)
  165. {
  166. break;
  167. }
  168. }
  169. // Set cursor to that position
  170. textView.CursorPosition = new Point (col, 0);
  171. // Navigate backward with BackTab (removes tabs, going back to original text)
  172. while (col > 0)
  173. {
  174. col--;
  175. Assert.True (textView.NewKeyDownEvent (Key.Tab.WithShift));
  176. Assert.Equal (new Point (col, 0), textView.CursorPosition);
  177. }
  178. // Should be back at the original text
  179. Assert.Equal ("TAB to jump between text fields.", textView.Text);
  180. Assert.Equal (32, textView.Text.Length);
  181. }
  182. [Fact]
  183. public void BackTab_Then_Tab_Navigation ()
  184. {
  185. var textView = new TextView
  186. {
  187. Width = 30,
  188. Height = 10,
  189. Text = ""
  190. };
  191. textView.BeginInit ();
  192. textView.EndInit ();
  193. // Add 100 tabs at end
  194. for (var i = 0; i < 100; i++)
  195. {
  196. textView.Text += "\t";
  197. }
  198. textView.MoveEnd ();
  199. Assert.Equal (new Point (100, 0), textView.CursorPosition);
  200. // Navigate backward with BackTab
  201. for (var i = 99; i >= 0; i--)
  202. {
  203. Assert.True (textView.NewKeyDownEvent (Key.Tab.WithShift));
  204. Assert.Equal (new Point (i, 0), textView.CursorPosition);
  205. }
  206. // Navigate forward with Tab
  207. for (var i = 1; i <= 100; i++)
  208. {
  209. Assert.True (textView.NewKeyDownEvent (Key.Tab));
  210. Assert.Equal (new Point (i, 0), textView.CursorPosition);
  211. }
  212. }
  213. [Fact]
  214. public void TabWidth_Setting_To_Zero_Keeps_AllowsTab ()
  215. {
  216. var textView = new TextView
  217. {
  218. Width = 30,
  219. Height = 10,
  220. Text = "TAB to jump between text fields."
  221. };
  222. textView.BeginInit ();
  223. textView.EndInit ();
  224. // Verify initial state
  225. Assert.Equal (4, textView.TabWidth);
  226. Assert.True (textView.AllowsTab);
  227. Assert.True (textView.AllowsReturn);
  228. Assert.True (textView.Multiline);
  229. // Set TabWidth to -1 (should clamp to 0)
  230. textView.TabWidth = -1;
  231. Assert.Equal (0, textView.TabWidth);
  232. Assert.True (textView.AllowsTab);
  233. Assert.True (textView.AllowsReturn);
  234. Assert.True (textView.Multiline);
  235. // Insert a tab
  236. Assert.True (textView.NewKeyDownEvent (Key.Tab));
  237. Assert.Equal ("\tTAB to jump between text fields.", textView.Text);
  238. // Change TabWidth back to 4
  239. textView.TabWidth = 4;
  240. Assert.Equal (4, textView.TabWidth);
  241. // Remove the tab with BackTab
  242. Assert.True (textView.NewKeyDownEvent (Key.Tab.WithShift));
  243. Assert.Equal ("TAB to jump between text fields.", textView.Text);
  244. }
  245. [Fact]
  246. public void KeyBindings_Command_Navigation ()
  247. {
  248. var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
  249. var textView = new TextView
  250. {
  251. Width = 10,
  252. Height = 2,
  253. Text = text
  254. };
  255. textView.BeginInit ();
  256. textView.EndInit ();
  257. Assert.Equal (
  258. $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
  259. textView.Text
  260. );
  261. Assert.Equal (3, textView.Lines);
  262. Assert.Equal (Point.Empty, textView.CursorPosition);
  263. Assert.False (textView.ReadOnly);
  264. Assert.True (textView.CanFocus);
  265. Assert.False (textView.IsSelecting);
  266. // Test that CursorLeft doesn't move when at beginning
  267. textView.CanFocus = false;
  268. Assert.True (textView.NewKeyDownEvent (Key.CursorLeft));
  269. Assert.False (textView.IsSelecting);
  270. textView.CanFocus = true;
  271. Assert.False (textView.NewKeyDownEvent (Key.CursorLeft));
  272. Assert.False (textView.IsSelecting);
  273. // Move right
  274. Assert.True (textView.NewKeyDownEvent (Key.CursorRight));
  275. Assert.Equal (new Point (1, 0), textView.CursorPosition);
  276. Assert.False (textView.IsSelecting);
  277. // Move to end of document
  278. Assert.True (textView.NewKeyDownEvent (Key.End.WithCtrl));
  279. Assert.Equal (2, textView.CurrentRow);
  280. Assert.Equal (23, textView.CurrentColumn);
  281. Assert.Equal (textView.CurrentColumn, textView.GetCurrentLine ().Count);
  282. Assert.Equal (new Point (23, 2), textView.CursorPosition);
  283. Assert.False (textView.IsSelecting);
  284. // Try to move right (should fail, at end)
  285. Assert.False (textView.NewKeyDownEvent (Key.CursorRight));
  286. Assert.False (textView.IsSelecting);
  287. // Type a character
  288. Assert.True (textView.NewKeyDownEvent (Key.F.WithShift));
  289. Assert.Equal (
  290. $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.F",
  291. textView.Text
  292. );
  293. Assert.Equal (new Point (24, 2), textView.CursorPosition);
  294. Assert.False (textView.IsSelecting);
  295. // Undo
  296. Assert.True (textView.NewKeyDownEvent (Key.Z.WithCtrl));
  297. Assert.Equal (
  298. $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
  299. textView.Text
  300. );
  301. Assert.Equal (new Point (23, 2), textView.CursorPosition);
  302. Assert.False (textView.IsSelecting);
  303. // Redo
  304. Assert.True (textView.NewKeyDownEvent (Key.R.WithCtrl));
  305. Assert.Equal (
  306. $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.F",
  307. textView.Text
  308. );
  309. Assert.Equal (new Point (24, 2), textView.CursorPosition);
  310. Assert.False (textView.IsSelecting);
  311. }
  312. [Fact]
  313. public void UnwrappedCursorPosition_Event_Fires_Correctly ()
  314. {
  315. Point unwrappedPosition = Point.Empty;
  316. var textView = new TextView
  317. {
  318. Width = 25,
  319. Height = 25,
  320. Text = "This is the first line.\nThis is the second line.\n"
  321. };
  322. textView.UnwrappedCursorPosition += (s, e) => { unwrappedPosition = e; };
  323. textView.BeginInit ();
  324. textView.EndInit ();
  325. // Initially no word wrap
  326. Assert.False (textView.WordWrap);
  327. Assert.Equal (Point.Empty, textView.CursorPosition);
  328. Assert.Equal (Point.Empty, unwrappedPosition);
  329. // Enable word wrap and move cursor
  330. textView.WordWrap = true;
  331. textView.CursorPosition = new Point (12, 0);
  332. Assert.Equal (new Point (12, 0), textView.CursorPosition);
  333. Assert.Equal (new Point (12, 0), unwrappedPosition);
  334. // Move right and verify unwrapped position updates
  335. var currentUnwrapped = unwrappedPosition;
  336. Assert.True (textView.NewKeyDownEvent (Key.CursorRight));
  337. // The unwrapped position should have updated
  338. Assert.True (unwrappedPosition.X >= currentUnwrapped.X);
  339. // Move several more times to verify tracking continues to work
  340. for (int i = 0; i < 5; i++)
  341. {
  342. Assert.True (textView.NewKeyDownEvent (Key.CursorRight));
  343. }
  344. // Unwrapped position should track the actual position in the text
  345. Assert.True (unwrappedPosition.X > 12);
  346. }
  347. [Fact]
  348. public void Horizontal_Scrolling_Adjusts_LeftColumn ()
  349. {
  350. var textView = new TextView
  351. {
  352. Width = 20,
  353. Height = 5,
  354. Text = "This is a very long line that will require horizontal scrolling to see all of it",
  355. WordWrap = false
  356. };
  357. textView.BeginInit ();
  358. textView.EndInit ();
  359. // Initially at the start
  360. Assert.Equal (0, textView.LeftColumn);
  361. Assert.Equal (new Point (0, 0), textView.CursorPosition);
  362. // Move to the end of the line
  363. textView.MoveEnd ();
  364. // LeftColumn should have adjusted to show the cursor
  365. Assert.True (textView.LeftColumn > 0);
  366. Assert.Equal (textView.Text.Length, textView.CurrentColumn);
  367. // Move back to the start
  368. textView.MoveHome ();
  369. // LeftColumn should be back to 0
  370. Assert.Equal (0, textView.LeftColumn);
  371. Assert.Equal (0, textView.CurrentColumn);
  372. }
  373. [Fact]
  374. public void Vertical_Scrolling_Adjusts_TopRow ()
  375. {
  376. var lines = string.Join ("\n", Enumerable.Range (1, 100).Select (i => $"Line {i}"));
  377. var textView = new TextView
  378. {
  379. Width = 20,
  380. Height = 5,
  381. Text = lines
  382. };
  383. textView.BeginInit ();
  384. textView.EndInit ();
  385. // Initially at the top
  386. Assert.Equal (0, textView.TopRow);
  387. Assert.Equal (new Point (0, 0), textView.CursorPosition);
  388. // Move down many lines
  389. for (var i = 0; i < 50; i++)
  390. {
  391. textView.NewKeyDownEvent (Key.CursorDown);
  392. }
  393. // TopRow should have adjusted to show the cursor
  394. Assert.True (textView.TopRow > 0);
  395. Assert.Equal (50, textView.CurrentRow);
  396. // Move back to the top
  397. textView.NewKeyDownEvent (Key.Home.WithCtrl);
  398. // TopRow should be back to 0
  399. Assert.Equal (0, textView.TopRow);
  400. Assert.Equal (0, textView.CurrentRow);
  401. }
  402. }