TextBoxTests.cs 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reactive.Linq;
  4. using System.Threading.Tasks;
  5. using Avalonia.Controls.Presenters;
  6. using Avalonia.Controls.Primitives;
  7. using Avalonia.Controls.Templates;
  8. using Avalonia.Data;
  9. using Avalonia.Headless;
  10. using Avalonia.Input;
  11. using Avalonia.Input.Platform;
  12. using Avalonia.Input.TextInput;
  13. using Avalonia.Layout;
  14. using Avalonia.Media;
  15. using Avalonia.Platform;
  16. using Avalonia.UnitTests;
  17. using Avalonia.VisualTree;
  18. using Moq;
  19. using Xunit;
  20. namespace Avalonia.Controls.UnitTests
  21. {
  22. public class TextBoxTests
  23. {
  24. [Fact]
  25. public void Opening_Context_Menu_Does_not_Lose_Selection()
  26. {
  27. using (UnitTestApplication.Start(FocusServices))
  28. {
  29. var target1 = new TextBox
  30. {
  31. Template = CreateTemplate(),
  32. Text = "1234",
  33. ContextMenu = new TestContextMenu()
  34. };
  35. var target2 = new TextBox
  36. {
  37. Template = CreateTemplate(),
  38. Text = "5678"
  39. };
  40. var sp = new StackPanel();
  41. sp.Children.Add(target1);
  42. sp.Children.Add(target2);
  43. target1.ApplyTemplate();
  44. target2.ApplyTemplate();
  45. var root = new TestRoot() { Child = sp };
  46. target1.SelectionStart = 0;
  47. target1.SelectionEnd = 3;
  48. target1.Focus();
  49. Assert.False(target2.IsFocused);
  50. Assert.True(target1.IsFocused);
  51. target2.Focus();
  52. Assert.Equal("123", target1.SelectedText);
  53. }
  54. }
  55. [Fact]
  56. public void TextBox_Should_Lose_Focus_When_Disabled()
  57. {
  58. using (UnitTestApplication.Start(FocusServices))
  59. {
  60. var target = new TextBox
  61. {
  62. Template = CreateTemplate()
  63. };
  64. target.ApplyTemplate();
  65. var root = new TestRoot() { Child = target };
  66. target.Focus();
  67. Assert.True(target.IsFocused);
  68. target.IsEnabled = false;
  69. Assert.False(target.IsFocused);
  70. Assert.False(target.IsEnabled);
  71. }
  72. }
  73. [Fact]
  74. public void Opening_Context_Flyout_Does_not_Lose_Selection()
  75. {
  76. using (UnitTestApplication.Start(FocusServices))
  77. {
  78. var target1 = new TextBox
  79. {
  80. Template = CreateTemplate(),
  81. Text = "1234",
  82. ContextFlyout = new MenuFlyout
  83. {
  84. Items =
  85. {
  86. new MenuItem { Header = "Item 1" },
  87. new MenuItem {Header = "Item 2" },
  88. new MenuItem {Header = "Item 3" }
  89. }
  90. }
  91. };
  92. target1.ApplyTemplate();
  93. var root = new TestRoot() { Child = target1 };
  94. target1.SelectionStart = 0;
  95. target1.SelectionEnd = 3;
  96. target1.Focus();
  97. Assert.True(target1.IsFocused);
  98. target1.ContextFlyout.ShowAt(target1);
  99. Assert.Equal("123", target1.SelectedText);
  100. }
  101. }
  102. [Fact]
  103. public void DefaultBindingMode_Should_Be_TwoWay()
  104. {
  105. Assert.Equal(
  106. BindingMode.TwoWay,
  107. TextBox.TextProperty.GetMetadata(typeof(TextBox)).DefaultBindingMode);
  108. }
  109. [Fact]
  110. public void TextBox_Ignore_Word_Move_In_Password_Field()
  111. {
  112. using (UnitTestApplication.Start(Services))
  113. {
  114. var target = new TextBox
  115. {
  116. Template = CreateTemplate(),
  117. PasswordChar = '*',
  118. Text = "passw0rd"
  119. };
  120. target.ApplyTemplate();
  121. target.Measure(Size.Infinity);
  122. target.CaretIndex = 8;
  123. RaiseKeyEvent(target, Key.Left, KeyModifiers.Control);
  124. Assert.Equal(7, target.CaretIndex);
  125. }
  126. }
  127. [Fact]
  128. public void CaretIndex_Can_Moved_To_Position_After_The_End_Of_Text_With_Arrow_Key()
  129. {
  130. using (UnitTestApplication.Start(Services))
  131. {
  132. var target = new TextBox
  133. {
  134. Template = CreateTemplate(),
  135. Text = "1234"
  136. };
  137. target.ApplyTemplate();
  138. target.Measure(Size.Infinity);
  139. target.CaretIndex = 3;
  140. RaiseKeyEvent(target, Key.Right, 0);
  141. Assert.Equal(4, target.CaretIndex);
  142. }
  143. }
  144. [Fact]
  145. public void Control_Backspace_Should_Set_Caret_Position_To_The_Start_Of_The_Deletion()
  146. {
  147. using (UnitTestApplication.Start(Services))
  148. {
  149. var target = new TextBox
  150. {
  151. Template = CreateTemplate(),
  152. Text = "First Second Third",
  153. SelectionStart = 13,
  154. SelectionEnd = 13
  155. };
  156. target.CaretIndex = 10;
  157. target.ApplyTemplate();
  158. // (First Second |Third)
  159. RaiseKeyEvent(target, Key.Back, KeyModifiers.Control);
  160. // (First |Third)
  161. Assert.Equal(6, target.CaretIndex);
  162. }
  163. }
  164. [Fact]
  165. public void Control_Backspace_Should_Remove_The_Double_Whitespace_If_Caret_Index_Was_At_The_End_Of_A_Word()
  166. {
  167. using (UnitTestApplication.Start(Services))
  168. {
  169. var target = new TextBox
  170. {
  171. Template = CreateTemplate(),
  172. Text = "First Second Third",
  173. SelectionStart = 12,
  174. SelectionEnd = 12
  175. };
  176. target.ApplyTemplate();
  177. // (First Second| Third)
  178. RaiseKeyEvent(target, Key.Back, KeyModifiers.Control);
  179. // (First| Third)
  180. Assert.Equal("First Third", target.Text);
  181. }
  182. }
  183. [Fact]
  184. public void Control_Backspace_Undo_Should_Return_Caret_Position()
  185. {
  186. using (UnitTestApplication.Start(Services))
  187. {
  188. var target = new TextBox
  189. {
  190. Template = CreateTemplate(),
  191. Text = "First Second Third",
  192. SelectionStart = 9,
  193. SelectionEnd = 9
  194. };
  195. target.ApplyTemplate();
  196. // (First Second| Third)
  197. RaiseKeyEvent(target, Key.Back, KeyModifiers.Control);
  198. // (First| Third)
  199. target.Undo();
  200. // (First Second| Third)
  201. Assert.Equal(9, target.CaretIndex);
  202. }
  203. }
  204. [Fact]
  205. public void Press_Ctrl_A_Select_All_Text()
  206. {
  207. using (UnitTestApplication.Start(Services))
  208. {
  209. var target = new TextBox
  210. {
  211. Template = CreateTemplate(),
  212. Text = "1234"
  213. };
  214. target.ApplyTemplate();
  215. RaiseKeyEvent(target, Key.A, KeyModifiers.Control);
  216. Assert.Equal(0, target.SelectionStart);
  217. Assert.Equal(4, target.SelectionEnd);
  218. }
  219. }
  220. [Fact]
  221. public void Press_Ctrl_A_Select_All_Null_Text()
  222. {
  223. using (UnitTestApplication.Start(Services))
  224. {
  225. var target = new TextBox
  226. {
  227. Template = CreateTemplate()
  228. };
  229. RaiseKeyEvent(target, Key.A, KeyModifiers.Control);
  230. Assert.Equal(0, target.SelectionStart);
  231. Assert.Equal(0, target.SelectionEnd);
  232. }
  233. }
  234. [Fact]
  235. public void Press_Ctrl_Z_Will_Not_Modify_Text()
  236. {
  237. using (UnitTestApplication.Start(Services))
  238. {
  239. var target = new TextBox
  240. {
  241. Template = CreateTemplate(),
  242. Text = "1234"
  243. };
  244. RaiseKeyEvent(target, Key.Z, KeyModifiers.Control);
  245. Assert.Equal("1234", target.Text);
  246. }
  247. }
  248. [Fact]
  249. public void Control_Backspace_Should_Remove_The_Word_Before_The_Caret_If_There_Is_No_Selection()
  250. {
  251. using (UnitTestApplication.Start(Services))
  252. {
  253. TextBox textBox = new TextBox
  254. {
  255. Template = CreateTemplate(),
  256. Text = "First Second Third Fourth",
  257. SelectionStart = 5,
  258. SelectionEnd = 5
  259. };
  260. textBox.ApplyTemplate();
  261. // (First| Second Third Fourth)
  262. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  263. Assert.Equal(" Second Third Fourth", textBox.Text);
  264. // ( Second |Third Fourth)
  265. textBox.CaretIndex = 8;
  266. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  267. Assert.Equal(" Third Fourth", textBox.Text);
  268. // ( Thi|rd Fourth)
  269. textBox.CaretIndex = 4;
  270. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  271. Assert.Equal(" rd Fourth", textBox.Text);
  272. // ( rd F[ou]rth)
  273. textBox.SelectionStart = 5;
  274. textBox.SelectionEnd = 7;
  275. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  276. Assert.Equal(" rd Frth", textBox.Text);
  277. // ( |rd Frth)
  278. textBox.CaretIndex = 1;
  279. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  280. Assert.Equal("rd Frth", textBox.Text);
  281. }
  282. }
  283. [Fact]
  284. public void Control_Delete_Should_Remove_The_Word_After_The_Caret_If_There_Is_No_Selection()
  285. {
  286. using (UnitTestApplication.Start(Services))
  287. {
  288. TextBox textBox = new TextBox
  289. {
  290. Template = CreateTemplate(),
  291. Text = "First Second Third Fourth",
  292. CaretIndex = 19,
  293. };
  294. textBox.ApplyTemplate();
  295. // (First Second Third |Fourth)
  296. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  297. Assert.Equal("First Second Third ", textBox.Text);
  298. // (First Second |Third )
  299. textBox.CaretIndex = 13;
  300. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  301. Assert.Equal("First Second ", textBox.Text);
  302. // (First Sec|ond )
  303. textBox.CaretIndex = 9;
  304. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  305. Assert.Equal("First Sec", textBox.Text);
  306. // (Fi[rs]t Sec )
  307. textBox.SelectionStart = 2;
  308. textBox.SelectionEnd = 4;
  309. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  310. Assert.Equal("Fit Sec", textBox.Text);
  311. // (Fit Sec| )
  312. textBox.Text += " ";
  313. textBox.CaretIndex = 7;
  314. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  315. Assert.Equal("Fit Sec", textBox.Text);
  316. }
  317. }
  318. [Fact]
  319. public void Setting_SelectionStart_To_SelectionEnd_Sets_CaretPosition_To_SelectionStart()
  320. {
  321. using (UnitTestApplication.Start(Services))
  322. {
  323. var textBox = new TextBox
  324. {
  325. Text = "0123456789"
  326. };
  327. textBox.SelectionStart = 2;
  328. textBox.SelectionEnd = 2;
  329. Assert.Equal(2, textBox.CaretIndex);
  330. }
  331. }
  332. [Fact]
  333. public void Setting_Text_Updates_CaretPosition()
  334. {
  335. using (UnitTestApplication.Start(Services))
  336. {
  337. var target = new TextBox
  338. {
  339. Text = "Initial Text",
  340. CaretIndex = 11
  341. };
  342. var invoked = false;
  343. target.GetObservable(TextBox.TextProperty).Skip(1).Subscribe(_ =>
  344. {
  345. // Caret index should be set before Text changed notification, as we don't want
  346. // to notify with an invalid CaretIndex.
  347. Assert.Equal(7, target.CaretIndex);
  348. invoked = true;
  349. });
  350. target.Text = "Changed";
  351. Assert.True(invoked);
  352. }
  353. }
  354. [Fact]
  355. public void Press_Enter_Does_Not_Accept_Return()
  356. {
  357. using (UnitTestApplication.Start(Services))
  358. {
  359. var target = new TextBox
  360. {
  361. Template = CreateTemplate(),
  362. AcceptsReturn = false,
  363. Text = "1234"
  364. };
  365. target.ApplyTemplate();
  366. RaiseKeyEvent(target, Key.Enter, 0);
  367. Assert.Equal("1234", target.Text);
  368. }
  369. }
  370. [Fact]
  371. public void Press_Enter_Add_Default_Newline()
  372. {
  373. using (UnitTestApplication.Start(Services))
  374. {
  375. var target = new TextBox
  376. {
  377. Template = CreateTemplate(),
  378. AcceptsReturn = true
  379. };
  380. target.ApplyTemplate();
  381. RaiseKeyEvent(target, Key.Enter, 0);
  382. Assert.Equal(Environment.NewLine, target.Text);
  383. }
  384. }
  385. [Fact]
  386. public void Press_Enter_Add_Custom_Newline()
  387. {
  388. using (UnitTestApplication.Start(Services))
  389. {
  390. var target = new TextBox
  391. {
  392. Template = CreateTemplate(),
  393. AcceptsReturn = true,
  394. NewLine = "Test"
  395. };
  396. target.ApplyTemplate();
  397. RaiseKeyEvent(target, Key.Enter, 0);
  398. Assert.Equal("Test", target.Text);
  399. }
  400. }
  401. [Theory]
  402. [InlineData(new object[] { false, TextWrapping.NoWrap, ScrollBarVisibility.Hidden })]
  403. [InlineData(new object[] { false, TextWrapping.Wrap, ScrollBarVisibility.Disabled })]
  404. [InlineData(new object[] { true, TextWrapping.NoWrap, ScrollBarVisibility.Auto })]
  405. [InlineData(new object[] { true, TextWrapping.Wrap, ScrollBarVisibility.Disabled })]
  406. public void Has_Correct_Horizontal_ScrollBar_Visibility(
  407. bool acceptsReturn,
  408. TextWrapping wrapping,
  409. ScrollBarVisibility expected)
  410. {
  411. using (UnitTestApplication.Start(Services))
  412. {
  413. var target = new TextBox
  414. {
  415. AcceptsReturn = acceptsReturn,
  416. TextWrapping = wrapping,
  417. };
  418. Assert.Equal(expected, ScrollViewer.GetHorizontalScrollBarVisibility(target));
  419. }
  420. }
  421. [Fact]
  422. public void SelectionEnd_Doesnt_Cause_Exception()
  423. {
  424. using (UnitTestApplication.Start(Services))
  425. {
  426. var target = new TextBox
  427. {
  428. Template = CreateTemplate(),
  429. Text = "0123456789"
  430. };
  431. target.ApplyTemplate();
  432. target.SelectionStart = 0;
  433. target.SelectionEnd = 9;
  434. target.Text = "123";
  435. RaiseTextEvent(target, "456");
  436. Assert.True(true);
  437. }
  438. }
  439. [Fact]
  440. public void SelectionStart_Doesnt_Cause_Exception()
  441. {
  442. using (UnitTestApplication.Start(Services))
  443. {
  444. var target = new TextBox
  445. {
  446. Template = CreateTemplate(),
  447. Text = "0123456789"
  448. };
  449. target.ApplyTemplate();
  450. target.SelectionStart = 8;
  451. target.SelectionEnd = 9;
  452. target.Text = "123";
  453. RaiseTextEvent(target, "456");
  454. Assert.True(true);
  455. }
  456. }
  457. [Fact]
  458. public void SelectionStartEnd_Are_Valid_AterTextChange()
  459. {
  460. using (UnitTestApplication.Start(Services))
  461. {
  462. var target = new TextBox
  463. {
  464. Template = CreateTemplate(),
  465. Text = "0123456789"
  466. };
  467. target.SelectionStart = 8;
  468. target.SelectionEnd = 9;
  469. target.Text = "123";
  470. Assert.True(target.SelectionStart <= "123".Length);
  471. Assert.True(target.SelectionEnd <= "123".Length);
  472. }
  473. }
  474. [Fact]
  475. public void SelectedText_Changes_OnSelectionChange()
  476. {
  477. using (UnitTestApplication.Start(Services))
  478. {
  479. var target = new TextBox
  480. {
  481. Template = CreateTemplate(),
  482. Text = "0123456789"
  483. };
  484. target.ApplyTemplate();
  485. Assert.True(target.SelectedText == "");
  486. target.SelectionStart = 2;
  487. target.SelectionEnd = 4;
  488. Assert.True(target.SelectedText == "23");
  489. }
  490. }
  491. [Fact]
  492. public void SelectedText_EditsText()
  493. {
  494. using (UnitTestApplication.Start(Services))
  495. {
  496. var target = new TextBox
  497. {
  498. Template = CreateTemplate(),
  499. Text = "0123"
  500. };
  501. target.ApplyTemplate();
  502. target.SelectedText = "AA";
  503. Assert.True(target.Text == "AA0123");
  504. target.SelectionStart = 1;
  505. target.SelectionEnd = 3;
  506. target.SelectedText = "BB";
  507. Assert.True(target.Text == "ABB123");
  508. }
  509. }
  510. [Fact]
  511. public void SelectedText_CanClearText()
  512. {
  513. using (UnitTestApplication.Start(Services))
  514. {
  515. var target = new TextBox
  516. {
  517. Template = CreateTemplate(),
  518. Text = "0123"
  519. };
  520. target.SelectionStart = 1;
  521. target.SelectionEnd = 3;
  522. target.SelectedText = "";
  523. Assert.True(target.Text == "03");
  524. }
  525. }
  526. [Fact]
  527. public void SelectedText_NullClearsText()
  528. {
  529. using (UnitTestApplication.Start(Services))
  530. {
  531. var target = new TextBox
  532. {
  533. Template = CreateTemplate(),
  534. Text = "0123"
  535. };
  536. target.SelectionStart = 1;
  537. target.SelectionEnd = 3;
  538. target.SelectedText = null;
  539. Assert.True(target.Text == "03");
  540. }
  541. }
  542. [Fact]
  543. public void CoerceCaretIndex_Doesnt_Cause_Exception_with_malformed_line_ending()
  544. {
  545. using (UnitTestApplication.Start(Services))
  546. {
  547. var target = new TextBox
  548. {
  549. Template = CreateTemplate(),
  550. Text = "0123456789\r"
  551. };
  552. target.CaretIndex = 11;
  553. Assert.True(true);
  554. }
  555. }
  556. [Theory]
  557. [InlineData(Key.Up)]
  558. [InlineData(Key.Down)]
  559. [InlineData(Key.Home)]
  560. [InlineData(Key.End)]
  561. public void Textbox_doesnt_crash_when_Receives_input_and_template_not_applied(Key key)
  562. {
  563. using (UnitTestApplication.Start(FocusServices))
  564. {
  565. var target1 = new TextBox
  566. {
  567. Template = CreateTemplate(),
  568. Text = "1234",
  569. };
  570. var root = new TestRoot { Child = target1 };
  571. target1.Focus();
  572. Assert.True(target1.IsFocused);
  573. RaiseKeyEvent(target1, key, KeyModifiers.None);
  574. }
  575. }
  576. [Fact]
  577. public void TextBox_GotFocus_And_LostFocus_Work_Properly()
  578. {
  579. using (UnitTestApplication.Start(FocusServices))
  580. {
  581. var target1 = new TextBox
  582. {
  583. Template = CreateTemplate(),
  584. Text = "1234"
  585. };
  586. var target2 = new TextBox
  587. {
  588. Template = CreateTemplate(),
  589. Text = "5678"
  590. };
  591. var sp = new StackPanel();
  592. sp.Children.Add(target1);
  593. sp.Children.Add(target2);
  594. target1.ApplyTemplate();
  595. target2.ApplyTemplate();
  596. var root = new TestRoot { Child = sp };
  597. var gfcount = 0;
  598. var lfcount = 0;
  599. target1.GotFocus += (s, e) => gfcount++;
  600. target2.LostFocus += (s, e) => lfcount++;
  601. target2.Focus();
  602. Assert.False(target1.IsFocused);
  603. Assert.True(target2.IsFocused);
  604. target1.Focus();
  605. Assert.False(target2.IsFocused);
  606. Assert.True(target1.IsFocused);
  607. Assert.Equal(1, gfcount);
  608. Assert.Equal(1, lfcount);
  609. }
  610. }
  611. [Fact]
  612. public void TextBox_CaretIndex_Persists_When_Focus_Lost()
  613. {
  614. using (UnitTestApplication.Start(FocusServices))
  615. {
  616. var target1 = new TextBox
  617. {
  618. Template = CreateTemplate(),
  619. Text = "1234"
  620. };
  621. var target2 = new TextBox
  622. {
  623. Template = CreateTemplate(),
  624. Text = "5678"
  625. };
  626. var sp = new StackPanel();
  627. sp.Children.Add(target1);
  628. sp.Children.Add(target2);
  629. target1.ApplyTemplate();
  630. target2.ApplyTemplate();
  631. var root = new TestRoot { Child = sp };
  632. target2.Focus();
  633. target2.CaretIndex = 2;
  634. Assert.False(target1.IsFocused);
  635. Assert.True(target2.IsFocused);
  636. target1.Focus();
  637. Assert.Equal(2, target2.CaretIndex);
  638. }
  639. }
  640. [Fact]
  641. public void TextBox_Reveal_Password_Reset_When_Lost_Focus()
  642. {
  643. using (UnitTestApplication.Start(FocusServices))
  644. {
  645. var target1 = new TextBox
  646. {
  647. Template = CreateTemplate(),
  648. Text = "1234",
  649. PasswordChar = '*'
  650. };
  651. var target2 = new TextBox
  652. {
  653. Template = CreateTemplate(),
  654. Text = "5678"
  655. };
  656. var sp = new StackPanel();
  657. sp.Children.Add(target1);
  658. sp.Children.Add(target2);
  659. target1.ApplyTemplate();
  660. target2.ApplyTemplate();
  661. var root = new TestRoot { Child = sp };
  662. target1.Focus();
  663. target1.RevealPassword = true;
  664. target2.Focus();
  665. Assert.False(target1.RevealPassword);
  666. }
  667. }
  668. [Fact]
  669. public void Setting_Bound_Text_To_Null_Works()
  670. {
  671. using (UnitTestApplication.Start(Services))
  672. {
  673. var source = new Class1 { Bar = "bar" };
  674. var target = new TextBox { Template = CreateTemplate(), DataContext = source };
  675. target.ApplyTemplate();
  676. target.Bind(TextBox.TextProperty, new Binding("Bar"));
  677. Assert.Equal("bar", target.Text);
  678. source.Bar = null;
  679. Assert.Null(target.Text);
  680. }
  681. }
  682. [Theory]
  683. [InlineData("abc", "d", 3, 0, 0, false, "abc")]
  684. [InlineData("abc", "dd", 4, 3, 3, false, "abcd")]
  685. [InlineData("abc", "ddd", 3, 0, 2, true, "ddc")]
  686. [InlineData("abc", "dddd", 4, 1, 3, true, "addd")]
  687. [InlineData("abc", "ddddd", 5, 3, 3, true, "abcdd")]
  688. public void MaxLength_Works_Properly(
  689. string initalText,
  690. string textInput,
  691. int maxLength,
  692. int selectionStart,
  693. int selectionEnd,
  694. bool fromClipboard,
  695. string expected)
  696. {
  697. using (UnitTestApplication.Start(Services))
  698. {
  699. var target = new TextBox
  700. {
  701. Template = CreateTemplate(),
  702. Text = initalText,
  703. MaxLength = maxLength,
  704. SelectionStart = selectionStart,
  705. SelectionEnd = selectionEnd
  706. };
  707. var impl = CreateMockTopLevelImpl();
  708. var topLevel = new TestTopLevel(impl.Object)
  709. {
  710. Template = CreateTopLevelTemplate()
  711. };
  712. topLevel.Content = target;
  713. topLevel.ApplyTemplate();
  714. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  715. target.Measure(Size.Infinity);
  716. if (fromClipboard)
  717. {
  718. topLevel.Clipboard?.SetTextAsync(textInput).GetAwaiter().GetResult();
  719. RaiseKeyEvent(target, Key.V, KeyModifiers.Control);
  720. topLevel.Clipboard?.ClearAsync().GetAwaiter().GetResult();
  721. }
  722. else
  723. {
  724. RaiseTextEvent(target, textInput);
  725. }
  726. Assert.Equal(expected, target.Text);
  727. }
  728. }
  729. [Theory]
  730. [InlineData(Key.X, KeyModifiers.Control)]
  731. [InlineData(Key.Back, KeyModifiers.None)]
  732. [InlineData(Key.Delete, KeyModifiers.None)]
  733. [InlineData(Key.Tab, KeyModifiers.None)]
  734. [InlineData(Key.Enter, KeyModifiers.None)]
  735. public void Keys_Allow_Undo(Key key, KeyModifiers modifiers)
  736. {
  737. using (UnitTestApplication.Start(Services))
  738. {
  739. var target = new TextBox
  740. {
  741. Template = CreateTemplate(),
  742. Text = "0123",
  743. AcceptsReturn = true,
  744. AcceptsTab = true
  745. };
  746. var impl = CreateMockTopLevelImpl();
  747. var topLevel = new TestTopLevel(impl.Object)
  748. {
  749. Template = CreateTopLevelTemplate()
  750. };
  751. topLevel.Content = target;
  752. topLevel.ApplyTemplate();
  753. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  754. target.ApplyTemplate();
  755. target.SelectionStart = 1;
  756. target.SelectionEnd = 3;
  757. RaiseKeyEvent(target, key, modifiers);
  758. RaiseKeyEvent(target, Key.Z, KeyModifiers.Control); // undo
  759. Assert.True(target.Text == "0123");
  760. }
  761. }
  762. [Fact]
  763. public void Setting_SelectedText_Should_Fire_Single_Text_Changed_Notification()
  764. {
  765. using (UnitTestApplication.Start(Services))
  766. {
  767. var target = new TextBox
  768. {
  769. Template = CreateTemplate(),
  770. Text = "0123",
  771. AcceptsReturn = true,
  772. AcceptsTab = true,
  773. SelectionStart = 1,
  774. SelectionEnd = 3,
  775. };
  776. var values = new List<string>();
  777. target.GetObservable(TextBox.TextProperty).Subscribe(x => values.Add(x));
  778. target.SelectedText = "A";
  779. Assert.Equal(new[] { "0123", "0A3" }, values);
  780. }
  781. }
  782. [Fact]
  783. public void Entering_Text_With_SelectedText_Should_Fire_Single_Text_Changed_Notification()
  784. {
  785. using (UnitTestApplication.Start(Services))
  786. {
  787. var target = new TextBox
  788. {
  789. Template = CreateTemplate(),
  790. Text = "0123",
  791. AcceptsReturn = true,
  792. AcceptsTab = true,
  793. SelectionStart = 1,
  794. SelectionEnd = 3,
  795. };
  796. var values = new List<string>();
  797. target.GetObservable(TextBox.TextProperty).Subscribe(x => values.Add(x));
  798. RaiseTextEvent(target, "A");
  799. Assert.Equal(new[] { "0123", "0A3" }, values);
  800. }
  801. }
  802. [Fact]
  803. public void Insert_Multiline_Text_Should_Accept_Extra_Lines_When_AcceptsReturn_Is_True()
  804. {
  805. using (UnitTestApplication.Start(Services))
  806. {
  807. var target = new TextBox
  808. {
  809. AcceptsReturn = true
  810. };
  811. RaiseTextEvent(target, $"123 {Environment.NewLine}456");
  812. Assert.Equal($"123 {Environment.NewLine}456", target.Text);
  813. }
  814. }
  815. [Fact]
  816. public void Insert_Multiline_Text_Should_Discard_Extra_Lines_When_AcceptsReturn_Is_False()
  817. {
  818. using (UnitTestApplication.Start(Services))
  819. {
  820. var target = new TextBox
  821. {
  822. AcceptsReturn = false
  823. };
  824. RaiseTextEvent(target, $"123 {"\r"}456");
  825. Assert.Equal("123 ", target.Text);
  826. target.Text = "";
  827. RaiseTextEvent(target, $"123 {"\r\n"}456");
  828. Assert.Equal("123 ", target.Text);
  829. }
  830. }
  831. [Fact]
  832. public void Should_Fullfill_MaxLines_Contraint()
  833. {
  834. using (UnitTestApplication.Start(Services))
  835. {
  836. var target = new TextBox
  837. {
  838. Template = CreateTemplate(),
  839. Text = "ABC",
  840. MaxLines = 1,
  841. AcceptsReturn = true
  842. };
  843. var impl = CreateMockTopLevelImpl();
  844. var topLevel = new TestTopLevel(impl.Object)
  845. {
  846. Template = CreateTopLevelTemplate()
  847. };
  848. topLevel.Content = target;
  849. topLevel.ApplyTemplate();
  850. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  851. target.ApplyTemplate();
  852. target.Measure(Size.Infinity);
  853. var initialHeight = target.DesiredSize.Height;
  854. topLevel.Clipboard?.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult();
  855. RaiseKeyEvent(target, Key.V, KeyModifiers.Control);
  856. topLevel.Clipboard?.ClearAsync().GetAwaiter().GetResult();
  857. RaiseTextEvent(target, Environment.NewLine);
  858. target.InvalidateMeasure();
  859. target.Measure(Size.Infinity);
  860. Assert.Equal(initialHeight, target.DesiredSize.Height);
  861. }
  862. }
  863. [Theory]
  864. [InlineData(1)]
  865. [InlineData(2)]
  866. [InlineData(3)]
  867. public void MaxLines_Sets_ScrollViewer_MaxHeight(int maxLines)
  868. {
  869. using (UnitTestApplication.Start(Services))
  870. {
  871. var target = new TextBox
  872. {
  873. Template = CreateTemplate(),
  874. MaxLines = maxLines,
  875. // Define explicit whole number line height for predictable calculations
  876. LineHeight = 20
  877. };
  878. var impl = CreateMockTopLevelImpl();
  879. var topLevel = new TestTopLevel(impl.Object)
  880. {
  881. Template = CreateTopLevelTemplate(),
  882. Content = target
  883. };
  884. topLevel.ApplyTemplate();
  885. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  886. var textPresenter = target.FindDescendantOfType<TextPresenter>();
  887. Assert.Equal("PART_TextPresenter", textPresenter.Name);
  888. Assert.Equal(new Thickness(0), textPresenter.Margin); // Test assumes no margin on TextPresenter
  889. var scrollViewer = target.FindDescendantOfType<ScrollViewer>();
  890. Assert.Equal("PART_ScrollViewer", scrollViewer.Name);
  891. Assert.Equal(maxLines * target.LineHeight, scrollViewer.MaxHeight);
  892. }
  893. }
  894. [Theory]
  895. [InlineData(1)]
  896. [InlineData(2)]
  897. [InlineData(3)]
  898. public void MaxLines_Sets_ScrollViewer_MaxHeight_With_TextPresenter_Margin(int maxLines)
  899. {
  900. using (UnitTestApplication.Start(Services))
  901. {
  902. var target = new TextBox
  903. {
  904. Template = CreateTemplate(),
  905. MaxLines = maxLines,
  906. // Define explicit whole number line height for predictable calculations
  907. LineHeight = 20
  908. };
  909. var impl = CreateMockTopLevelImpl();
  910. var topLevel = new TestTopLevel(impl.Object)
  911. {
  912. Template = CreateTopLevelTemplate(),
  913. Content = target
  914. };
  915. topLevel.ApplyTemplate();
  916. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  917. var textPresenter = target.FindDescendantOfType<TextPresenter>();
  918. Assert.Equal("PART_TextPresenter", textPresenter.Name);
  919. var textPresenterMargin = new Thickness(horizontal: 0, vertical: 3);
  920. textPresenter.Margin = textPresenterMargin;
  921. target.InvalidateMeasure();
  922. target.Measure(Size.Infinity);
  923. var scrollViewer = target.FindDescendantOfType<ScrollViewer>();
  924. Assert.Equal("PART_ScrollViewer", scrollViewer.Name);
  925. Assert.Equal((maxLines * target.LineHeight) + textPresenterMargin.Top + textPresenterMargin.Bottom, scrollViewer.MaxHeight);
  926. }
  927. }
  928. [Fact]
  929. public void Should_Fullfill_MinLines_Contraint()
  930. {
  931. using (UnitTestApplication.Start(Services))
  932. {
  933. var target = new TextBox
  934. {
  935. Template = CreateTemplate(),
  936. Text = "ABC \n DEF \n GHI",
  937. MinLines = 3,
  938. AcceptsReturn = true
  939. };
  940. var impl = CreateMockTopLevelImpl();
  941. var topLevel = new TestTopLevel(impl.Object)
  942. {
  943. Template = CreateTopLevelTemplate()
  944. };
  945. topLevel.Content = target;
  946. topLevel.ApplyTemplate();
  947. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  948. target.ApplyTemplate();
  949. target.Measure(Size.Infinity);
  950. var initialHeight = target.DesiredSize.Height;
  951. target.Text = "";
  952. target.InvalidateMeasure();
  953. target.Measure(Size.Infinity);
  954. Assert.Equal(initialHeight, target.DesiredSize.Height);
  955. }
  956. }
  957. [Theory]
  958. [InlineData(1)]
  959. [InlineData(2)]
  960. [InlineData(3)]
  961. public void MinLines_Sets_ScrollViewer_MinHeight(int minLines)
  962. {
  963. using (UnitTestApplication.Start(Services))
  964. {
  965. var target = new TextBox
  966. {
  967. Template = CreateTemplate(),
  968. MinLines = minLines,
  969. // Define explicit whole number line height for predictable calculations
  970. LineHeight = 20
  971. };
  972. var impl = CreateMockTopLevelImpl();
  973. var topLevel = new TestTopLevel(impl.Object)
  974. {
  975. Template = CreateTopLevelTemplate(),
  976. Content = target
  977. };
  978. topLevel.ApplyTemplate();
  979. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  980. var textPresenter = target.FindDescendantOfType<TextPresenter>();
  981. Assert.Equal("PART_TextPresenter", textPresenter.Name);
  982. Assert.Equal(new Thickness(0), textPresenter.Margin); // Test assumes no margin on TextPresenter
  983. var scrollViewer = target.FindDescendantOfType<ScrollViewer>();
  984. Assert.Equal("PART_ScrollViewer", scrollViewer.Name);
  985. Assert.Equal(minLines * target.LineHeight, scrollViewer.MinHeight);
  986. }
  987. }
  988. [Theory]
  989. [InlineData(1)]
  990. [InlineData(2)]
  991. [InlineData(3)]
  992. public void MinLines_Sets_ScrollViewer_MinHeight_With_TextPresenter_Margin(int minLines)
  993. {
  994. using (UnitTestApplication.Start(Services))
  995. {
  996. var target = new TextBox
  997. {
  998. Template = CreateTemplate(),
  999. MinLines = minLines,
  1000. // Define explicit whole number line height for predictable calculations
  1001. LineHeight = 20
  1002. };
  1003. var impl = CreateMockTopLevelImpl();
  1004. var topLevel = new TestTopLevel(impl.Object)
  1005. {
  1006. Template = CreateTopLevelTemplate(),
  1007. Content = target
  1008. };
  1009. topLevel.ApplyTemplate();
  1010. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  1011. var textPresenter = target.FindDescendantOfType<TextPresenter>();
  1012. Assert.Equal("PART_TextPresenter", textPresenter.Name);
  1013. var textPresenterMargin = new Thickness(horizontal: 0, vertical: 3);
  1014. textPresenter.Margin = textPresenterMargin;
  1015. target.InvalidateMeasure();
  1016. target.Measure(Size.Infinity);
  1017. var scrollViewer = target.FindDescendantOfType<ScrollViewer>();
  1018. Assert.Equal("PART_ScrollViewer", scrollViewer.Name);
  1019. Assert.Equal((minLines * target.LineHeight) + textPresenterMargin.Top + textPresenterMargin.Bottom, scrollViewer.MinHeight);
  1020. }
  1021. }
  1022. [Theory]
  1023. [InlineData(null, 1)]
  1024. [InlineData("", 1)]
  1025. [InlineData("Hello", 1)]
  1026. [InlineData("Hello\r\nWorld", 2)]
  1027. public void LineCount_Is_Correct(string? text, int lineCount)
  1028. {
  1029. using (UnitTestApplication.Start(Services))
  1030. {
  1031. var target = new TextBox
  1032. {
  1033. Template = CreateTemplate(),
  1034. Text = text,
  1035. AcceptsReturn = true
  1036. };
  1037. var impl = CreateMockTopLevelImpl();
  1038. var topLevel = new TestTopLevel(impl.Object)
  1039. {
  1040. Template = CreateTopLevelTemplate()
  1041. };
  1042. topLevel.Content = target;
  1043. topLevel.ApplyTemplate();
  1044. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  1045. target.ApplyTemplate();
  1046. target.Measure(Size.Infinity);
  1047. Assert.Equal(lineCount, target.GetLineCount());
  1048. }
  1049. }
  1050. [Fact]
  1051. public void Unmeasured_TextBox_Has_Negative_LineCount()
  1052. {
  1053. var b = new TextBox();
  1054. Assert.Equal(-1, b.GetLineCount());
  1055. }
  1056. [Fact]
  1057. public void LineCount_Is_Correct_After_Text_Change()
  1058. {
  1059. using (UnitTestApplication.Start(Services))
  1060. {
  1061. var target = new TextBox
  1062. {
  1063. Template = CreateTemplate(),
  1064. Text = "Hello",
  1065. AcceptsReturn = true
  1066. };
  1067. var impl = CreateMockTopLevelImpl();
  1068. var topLevel = new TestTopLevel(impl.Object)
  1069. {
  1070. Template = CreateTopLevelTemplate()
  1071. };
  1072. topLevel.Content = target;
  1073. topLevel.ApplyTemplate();
  1074. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  1075. target.ApplyTemplate();
  1076. target.Measure(Size.Infinity);
  1077. Assert.Equal(1, target.GetLineCount());
  1078. target.Text = "Hello\r\nWorld";
  1079. Assert.Equal(2, target.GetLineCount());
  1080. }
  1081. }
  1082. [Fact]
  1083. public void Visible_LineCount_DoesNot_Affect_LineCount()
  1084. {
  1085. using (UnitTestApplication.Start(Services))
  1086. {
  1087. var target = new TextBox
  1088. {
  1089. Template = CreateTemplate(),
  1090. Text = "Hello\r\nWorld\r\nHello\r\nAvalonia",
  1091. AcceptsReturn = true,
  1092. MaxLines = 2,
  1093. };
  1094. var impl = CreateMockTopLevelImpl();
  1095. var topLevel = new TestTopLevel(impl.Object)
  1096. {
  1097. Template = CreateTopLevelTemplate()
  1098. };
  1099. topLevel.Content = target;
  1100. topLevel.ApplyTemplate();
  1101. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  1102. target.ApplyTemplate();
  1103. target.Measure(Size.Infinity);
  1104. Assert.Equal(4, target.GetLineCount());
  1105. }
  1106. }
  1107. [Fact]
  1108. public void CanUndo_CanRedo_Is_False_When_Initialized()
  1109. {
  1110. using (UnitTestApplication.Start(Services))
  1111. {
  1112. var tb = new TextBox
  1113. {
  1114. Template = CreateTemplate(),
  1115. Text = "New Text"
  1116. };
  1117. tb.Measure(Size.Infinity);
  1118. Assert.False(tb.CanUndo);
  1119. Assert.False(tb.CanRedo);
  1120. }
  1121. }
  1122. [Fact]
  1123. public void CanUndo_CanRedo_and_Programmatic_Undo_Redo_Works()
  1124. {
  1125. using (UnitTestApplication.Start(Services))
  1126. {
  1127. var tb = new TextBox
  1128. {
  1129. Template = CreateTemplate(),
  1130. };
  1131. tb.Measure(Size.Infinity);
  1132. // See GH #6024 for a bit more insight on when Undo/Redo snapshots are taken:
  1133. // - Every 'Space', but only when space is handled in OnKeyDown - Spaces in TextInput event won't work
  1134. // - Every 7 chars in a long word
  1135. RaiseTextEvent(tb, "ABC");
  1136. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  1137. RaiseTextEvent(tb, "DEF");
  1138. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  1139. RaiseTextEvent(tb, "123");
  1140. // NOTE: the spaces won't actually add spaces b/c they're sent only as key events and not Text events
  1141. // so our final text is without spaces
  1142. Assert.Equal("ABCDEF123", tb.Text);
  1143. Assert.True(tb.CanUndo);
  1144. tb.Undo();
  1145. // Undo will take us back one step
  1146. Assert.Equal("ABCDEF", tb.Text);
  1147. Assert.True(tb.CanRedo);
  1148. tb.Redo();
  1149. // Redo should restore us
  1150. Assert.Equal("ABCDEF123", tb.Text);
  1151. }
  1152. }
  1153. [Fact]
  1154. public void Setting_UndoLimit_Clears_Undo_Redo()
  1155. {
  1156. using (UnitTestApplication.Start(Services))
  1157. {
  1158. var tb = new TextBox
  1159. {
  1160. Template = CreateTemplate(),
  1161. };
  1162. tb.Measure(Size.Infinity);
  1163. // This is all the same as the above test (CanUndo_CanRedo_and_Programmatic_Undo_Redo_Works)
  1164. // We do this to get the undo/redo stacks in a state where both are active
  1165. RaiseTextEvent(tb, "ABC");
  1166. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  1167. RaiseTextEvent(tb, "DEF");
  1168. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  1169. RaiseTextEvent(tb, "123");
  1170. Assert.Equal("ABCDEF123", tb.Text);
  1171. Assert.True(tb.CanUndo);
  1172. tb.Undo();
  1173. // Undo will take us back one step
  1174. Assert.Equal("ABCDEF", tb.Text);
  1175. Assert.True(tb.CanRedo);
  1176. tb.Redo();
  1177. // Redo should restore us
  1178. Assert.Equal("ABCDEF123", tb.Text);
  1179. // Change the undo limit, this should clear both stacks setting CanUndo and CanRedo to false
  1180. tb.UndoLimit = 1;
  1181. Assert.False(tb.CanUndo);
  1182. Assert.False(tb.CanRedo);
  1183. }
  1184. }
  1185. [Fact]
  1186. public void Setting_IsUndoEnabled_To_False_Clears_Undo_Redo()
  1187. {
  1188. using (UnitTestApplication.Start(Services))
  1189. {
  1190. var tb = new TextBox
  1191. {
  1192. Template = CreateTemplate(),
  1193. };
  1194. tb.Measure(Size.Infinity);
  1195. // This is all the same as the above test (CanUndo_CanRedo_and_Programmatic_Undo_Redo_Works)
  1196. // We do this to get the undo/redo stacks in a state where both are active
  1197. RaiseTextEvent(tb, "ABC");
  1198. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  1199. RaiseTextEvent(tb, "DEF");
  1200. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  1201. RaiseTextEvent(tb, "123");
  1202. Assert.Equal("ABCDEF123", tb.Text);
  1203. Assert.True(tb.CanUndo);
  1204. tb.Undo();
  1205. // Undo will take us back one step
  1206. Assert.Equal("ABCDEF", tb.Text);
  1207. Assert.True(tb.CanRedo);
  1208. tb.Redo();
  1209. // Redo should restore us
  1210. Assert.Equal("ABCDEF123", tb.Text);
  1211. // Disable Undo/Redo, this should clear both stacks setting CanUndo and CanRedo to false
  1212. tb.IsUndoEnabled = false;
  1213. Assert.False(tb.CanUndo);
  1214. Assert.False(tb.CanRedo);
  1215. }
  1216. }
  1217. [Fact]
  1218. public void UndoLimit_Count_Is_Respected()
  1219. {
  1220. using (UnitTestApplication.Start(Services))
  1221. {
  1222. var tb = new TextBox
  1223. {
  1224. Template = CreateTemplate(),
  1225. UndoLimit = 3 // Something small for this test
  1226. };
  1227. tb.Measure(Size.Infinity);
  1228. // Push 3 undoable actions, we should only be able to recover 2
  1229. RaiseTextEvent(tb, "ABC");
  1230. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  1231. RaiseTextEvent(tb, "DEF");
  1232. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  1233. RaiseTextEvent(tb, "123");
  1234. Assert.Equal("ABCDEF123", tb.Text);
  1235. // Undo will take us back one step
  1236. tb.Undo();
  1237. Assert.Equal("ABCDEF", tb.Text);
  1238. // Undo again
  1239. tb.Undo();
  1240. Assert.Equal("ABC", tb.Text);
  1241. // We now should not be able to undo again
  1242. Assert.False(tb.CanUndo);
  1243. }
  1244. }
  1245. [Fact]
  1246. public void Should_Move_Caret_To_EndOfLine()
  1247. {
  1248. using (UnitTestApplication.Start(Services))
  1249. {
  1250. var tb = new TextBox
  1251. {
  1252. Template = CreateTemplate(),
  1253. Text = "AB\nAB"
  1254. };
  1255. tb.Measure(Size.Infinity);
  1256. RaiseKeyEvent(tb, Key.End, KeyModifiers.Shift);
  1257. Assert.Equal(2, tb.CaretIndex);
  1258. }
  1259. }
  1260. [Fact]
  1261. public void TextBox_In_AdornerLayer_Will_Not_Cause_Collection_Modified_In_VisualLayerManager_Measure()
  1262. {
  1263. using (UnitTestApplication.Start(Services))
  1264. {
  1265. var button = new Button();
  1266. var root = new TestRoot()
  1267. {
  1268. Child = new VisualLayerManager()
  1269. {
  1270. Child = button
  1271. }
  1272. };
  1273. var adorner = new TextBox { Template = CreateTemplate(), Text = "a" };
  1274. var adornerLayer = AdornerLayer.GetAdornerLayer(button);
  1275. adornerLayer.Children.Add(adorner);
  1276. AdornerLayer.SetAdornedElement(adorner, button);
  1277. root.Measure(Size.Infinity);
  1278. }
  1279. }
  1280. [Fact]
  1281. public void TextBox_In_AdornerLayer_Will_Not_Cause_Collection_Modified_In_VisualLayerManager_Arrange()
  1282. {
  1283. using (UnitTestApplication.Start(Services))
  1284. {
  1285. var button = new Button();
  1286. var visualLayerManager = new VisualLayerManager() { Child = button };
  1287. var root = new TestRoot()
  1288. {
  1289. Child = visualLayerManager
  1290. };
  1291. var adorner = new TextBox { Template = CreateTemplate(), Text = "a" };
  1292. var adornerLayer = AdornerLayer.GetAdornerLayer(button);
  1293. root.Measure(new Size(10, 10));
  1294. adornerLayer.Children.Add(adorner);
  1295. AdornerLayer.SetAdornedElement(adorner, button);
  1296. root.Arrange(new Rect(0, 0, 10, 10));
  1297. }
  1298. }
  1299. [Theory]
  1300. [InlineData("A\nBB\nCCC\nDDDD", 0, 0)]
  1301. [InlineData("A\nBB\nCCC\nDDDD", 1, 2)]
  1302. [InlineData("A\nBB\nCCC\nDDDD", 2, 5)]
  1303. [InlineData("A\nBB\nCCC\nDDDD", 3, 9)]
  1304. [InlineData("واحد\nاثنين\nثلاثة\nأربعة", 0, 0)]
  1305. [InlineData("واحد\nاثنين\nثلاثة\nأربعة", 1, 5)]
  1306. [InlineData("واحد\nاثنين\nثلاثة\nأربعة", 2, 11)]
  1307. [InlineData("واحد\nاثنين\nثلاثة\nأربعة", 3, 17)]
  1308. public void Should_Scroll_Caret_To_Line(string text, int targetLineIndex, int expectedCaretIndex)
  1309. {
  1310. using (UnitTestApplication.Start(Services))
  1311. {
  1312. var tb = new TextBox
  1313. {
  1314. Template = CreateTemplate(),
  1315. Text = text
  1316. };
  1317. tb.ApplyTemplate();
  1318. tb.ScrollToLine(targetLineIndex);
  1319. Assert.Equal(expectedCaretIndex, tb.CaretIndex);
  1320. }
  1321. }
  1322. [Fact]
  1323. public void Should_Throw_ArgumentOutOfRange()
  1324. {
  1325. using (UnitTestApplication.Start(Services))
  1326. {
  1327. var tb = new TextBox
  1328. {
  1329. Template = CreateTemplate(),
  1330. Text = string.Empty
  1331. };
  1332. tb.ApplyTemplate();
  1333. Assert.Throws<ArgumentOutOfRangeException>(() => tb.ScrollToLine(-1));
  1334. Assert.Throws<ArgumentOutOfRangeException>(() => tb.ScrollToLine(1));
  1335. }
  1336. }
  1337. [Fact]
  1338. public void InputMethodClient_SurroundingText_Returns_Empty_For_Empty_Line()
  1339. {
  1340. using var _ = UnitTestApplication.Start(Services);
  1341. var textBox = new TextBox
  1342. {
  1343. Template = CreateTemplate(),
  1344. Text = "",
  1345. CaretIndex = 0
  1346. };
  1347. textBox.ApplyTemplate();
  1348. var eventArgs = new TextInputMethodClientRequestedEventArgs
  1349. {
  1350. RoutedEvent = InputElement.TextInputMethodClientRequestedEvent
  1351. };
  1352. textBox.RaiseEvent(eventArgs);
  1353. var client = eventArgs.Client;
  1354. Assert.NotNull(client);
  1355. Assert.Equal(string.Empty, client.SurroundingText);
  1356. }
  1357. [Fact]
  1358. public void Backspace_Should_Delete_Last_Character_In_Line_And_Keep_Caret_On_Same_Line()
  1359. {
  1360. using var _ = UnitTestApplication.Start(Services);
  1361. var textBox = new TextBox
  1362. {
  1363. Template = CreateTemplate(),
  1364. Text = "a\nb",
  1365. CaretIndex = 3
  1366. };
  1367. textBox.ApplyTemplate();
  1368. var topLevel = new TestTopLevel(CreateMockTopLevelImpl().Object)
  1369. {
  1370. Template = CreateTopLevelTemplate(),
  1371. Content = textBox
  1372. };
  1373. topLevel.ApplyTemplate();
  1374. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  1375. var textPresenter = textBox.FindDescendantOfType<TextPresenter>();
  1376. Assert.NotNull(textPresenter);
  1377. var oldCaretY = textPresenter.GetCursorRectangle().Top;
  1378. Assert.NotEqual(0, oldCaretY);
  1379. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.None);
  1380. Assert.Equal("a\n", textBox.Text);
  1381. Assert.Equal(2, textBox.CaretIndex);
  1382. Assert.Equal(2, textPresenter.CaretIndex);
  1383. var caretY = textPresenter.GetCursorRectangle().Top;
  1384. Assert.Equal(oldCaretY, caretY);
  1385. }
  1386. [Fact]
  1387. public void Losing_Focus_Should_Not_Reset_Selection()
  1388. {
  1389. using (UnitTestApplication.Start(FocusServices))
  1390. {
  1391. var target1 = new TextBox
  1392. {
  1393. Template = CreateTemplate(),
  1394. Text = "1234",
  1395. ClearSelectionOnLostFocus = false
  1396. };
  1397. target1.ApplyTemplate();
  1398. var target2 = new TextBox
  1399. {
  1400. Template = CreateTemplate(),
  1401. };
  1402. target2.ApplyTemplate();
  1403. var sp = new StackPanel();
  1404. sp.Children.Add(target1);
  1405. sp.Children.Add(target2);
  1406. var root = new TestRoot() { Child = sp };
  1407. target1.SelectionStart = 0;
  1408. target1.SelectionEnd = 4;
  1409. target1.Focus();
  1410. Assert.True(target1.IsFocused);
  1411. Assert.Equal("1234", target1.SelectedText);
  1412. target2.Focus();
  1413. Assert.Equal("1234", target1.SelectedText);
  1414. }
  1415. }
  1416. private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
  1417. focusManager: new FocusManager(),
  1418. keyboardDevice: () => new KeyboardDevice(),
  1419. keyboardNavigation: () => new KeyboardNavigationHandler(),
  1420. inputManager: new InputManager(),
  1421. standardCursorFactory: Mock.Of<ICursorFactory>(),
  1422. textShaperImpl: new HeadlessTextShaperStub(),
  1423. fontManagerImpl: new HeadlessFontManagerStub());
  1424. private static TestServices Services => TestServices.MockThreadingInterface.With(
  1425. standardCursorFactory: Mock.Of<ICursorFactory>(),
  1426. renderInterface: new HeadlessPlatformRenderInterface(),
  1427. textShaperImpl: new HeadlessTextShaperStub(),
  1428. fontManagerImpl: new HeadlessFontManagerStub());
  1429. internal static IControlTemplate CreateTemplate()
  1430. {
  1431. return new FuncControlTemplate<TextBox>((control, scope) =>
  1432. new ScrollViewer
  1433. {
  1434. Name = "PART_ScrollViewer",
  1435. Template = new FuncControlTemplate<ScrollViewer>(ScrollViewerTests.CreateTemplate),
  1436. Content = new TextPresenter
  1437. {
  1438. Name = "PART_TextPresenter",
  1439. [!!TextPresenter.TextProperty] = new Binding
  1440. {
  1441. Path = nameof(TextPresenter.Text),
  1442. Mode = BindingMode.TwoWay,
  1443. Priority = BindingPriority.Template,
  1444. RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
  1445. },
  1446. [!!TextPresenter.CaretIndexProperty] = new Binding
  1447. {
  1448. Path = nameof(TextPresenter.CaretIndex),
  1449. Mode = BindingMode.TwoWay,
  1450. Priority = BindingPriority.Template,
  1451. RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
  1452. }
  1453. }.RegisterInNameScope(scope)
  1454. }.RegisterInNameScope(scope));
  1455. }
  1456. private static void RaiseKeyEvent(TextBox textBox, Key key, KeyModifiers inputModifiers)
  1457. {
  1458. textBox.RaiseEvent(new KeyEventArgs
  1459. {
  1460. RoutedEvent = InputElement.KeyDownEvent,
  1461. KeyModifiers = inputModifiers,
  1462. Key = key
  1463. });
  1464. }
  1465. private static void RaiseTextEvent(TextBox textBox, string text)
  1466. {
  1467. textBox.RaiseEvent(new TextInputEventArgs
  1468. {
  1469. RoutedEvent = InputElement.TextInputEvent,
  1470. Text = text
  1471. });
  1472. }
  1473. private class Class1 : NotifyingBase
  1474. {
  1475. private int _foo;
  1476. private string _bar;
  1477. public int Foo
  1478. {
  1479. get { return _foo; }
  1480. set { _foo = value; RaisePropertyChanged(); }
  1481. }
  1482. public string Bar
  1483. {
  1484. get { return _bar; }
  1485. set { _bar = value; RaisePropertyChanged(); }
  1486. }
  1487. }
  1488. private class ClipboardStub : IClipboard // in order to get tests working that use the clipboard
  1489. {
  1490. private string _text;
  1491. public Task<string> GetTextAsync() => Task.FromResult(_text);
  1492. public Task SetTextAsync(string text)
  1493. {
  1494. _text = text;
  1495. return Task.CompletedTask;
  1496. }
  1497. public Task ClearAsync()
  1498. {
  1499. _text = null;
  1500. return Task.CompletedTask;
  1501. }
  1502. public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask;
  1503. public Task<string[]> GetFormatsAsync() => Task.FromResult(Array.Empty<string>());
  1504. public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
  1505. }
  1506. private class TestTopLevel : TopLevel
  1507. {
  1508. private readonly ILayoutManager _layoutManager;
  1509. public TestTopLevel(ITopLevelImpl impl, ILayoutManager layoutManager = null)
  1510. : base(impl)
  1511. {
  1512. _layoutManager = layoutManager ?? new LayoutManager(this);
  1513. }
  1514. private protected override ILayoutManager CreateLayoutManager() => _layoutManager;
  1515. }
  1516. private static Mock<ITopLevelImpl> CreateMockTopLevelImpl()
  1517. {
  1518. var clipboard = new Mock<ITopLevelImpl>();
  1519. clipboard.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor());
  1520. clipboard.Setup(r => r.TryGetFeature(typeof(IClipboard)))
  1521. .Returns(new ClipboardStub());
  1522. clipboard.SetupGet(x => x.RenderScaling).Returns(1);
  1523. return clipboard;
  1524. }
  1525. private static FuncControlTemplate<TestTopLevel> CreateTopLevelTemplate()
  1526. {
  1527. return new FuncControlTemplate<TestTopLevel>((x, scope) =>
  1528. new ContentPresenter
  1529. {
  1530. Name = "PART_ContentPresenter",
  1531. [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty],
  1532. }.RegisterInNameScope(scope));
  1533. }
  1534. private class TestContextMenu : ContextMenu
  1535. {
  1536. public TestContextMenu()
  1537. {
  1538. IsOpen = true;
  1539. }
  1540. }
  1541. }
  1542. }