TextBoxTests.cs 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243
  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.Input;
  10. using Avalonia.Input.Platform;
  11. using Avalonia.Layout;
  12. using Avalonia.Media;
  13. using Avalonia.Platform;
  14. using Avalonia.Rendering;
  15. using Avalonia.UnitTests;
  16. using Moq;
  17. using Xunit;
  18. namespace Avalonia.Controls.UnitTests
  19. {
  20. public class TextBoxTests
  21. {
  22. [Fact]
  23. public void Opening_Context_Menu_Does_not_Lose_Selection()
  24. {
  25. using (UnitTestApplication.Start(FocusServices))
  26. {
  27. var target1 = new TextBox
  28. {
  29. Template = CreateTemplate(),
  30. Text = "1234",
  31. ContextMenu = new TestContextMenu()
  32. };
  33. var target2 = new TextBox
  34. {
  35. Template = CreateTemplate(),
  36. Text = "5678"
  37. };
  38. var sp = new StackPanel();
  39. sp.Children.Add(target1);
  40. sp.Children.Add(target2);
  41. target1.ApplyTemplate();
  42. target2.ApplyTemplate();
  43. var root = new TestRoot() { Child = sp };
  44. target1.SelectionStart = 0;
  45. target1.SelectionEnd = 3;
  46. target1.Focus();
  47. Assert.False(target2.IsFocused);
  48. Assert.True(target1.IsFocused);
  49. target2.Focus();
  50. Assert.Equal("123", target1.SelectedText);
  51. }
  52. }
  53. [Fact]
  54. public void TextBox_Should_Lose_Focus_When_Disabled()
  55. {
  56. using (UnitTestApplication.Start(FocusServices))
  57. {
  58. var target = new TextBox
  59. {
  60. Template = CreateTemplate()
  61. };
  62. target.ApplyTemplate();
  63. var root = new TestRoot() { Child = target };
  64. target.Focus();
  65. Assert.True(target.IsFocused);
  66. target.IsEnabled = false;
  67. Assert.False(target.IsFocused);
  68. Assert.False(target.IsEnabled);
  69. }
  70. }
  71. [Fact]
  72. public void Opening_Context_Flyout_Does_not_Lose_Selection()
  73. {
  74. using (UnitTestApplication.Start(FocusServices))
  75. {
  76. var target1 = new TextBox
  77. {
  78. Template = CreateTemplate(),
  79. Text = "1234",
  80. ContextFlyout = new MenuFlyout
  81. {
  82. Items = new List<MenuItem>
  83. {
  84. new MenuItem { Header = "Item 1" },
  85. new MenuItem {Header = "Item 2" },
  86. new MenuItem {Header = "Item 3" }
  87. }
  88. }
  89. };
  90. target1.ApplyTemplate();
  91. var root = new TestRoot() { Child = target1 };
  92. target1.SelectionStart = 0;
  93. target1.SelectionEnd = 3;
  94. target1.Focus();
  95. Assert.True(target1.IsFocused);
  96. target1.ContextFlyout.ShowAt(target1);
  97. Assert.Equal("123", target1.SelectedText);
  98. }
  99. }
  100. [Fact]
  101. public void DefaultBindingMode_Should_Be_TwoWay()
  102. {
  103. Assert.Equal(
  104. BindingMode.TwoWay,
  105. TextBox.TextProperty.GetMetadata(typeof(TextBox)).DefaultBindingMode);
  106. }
  107. [Fact]
  108. public void CaretIndex_Can_Moved_To_Position_After_The_End_Of_Text_With_Arrow_Key()
  109. {
  110. using (UnitTestApplication.Start(Services))
  111. {
  112. var target = new TextBox
  113. {
  114. Template = CreateTemplate(),
  115. Text = "1234"
  116. };
  117. target.ApplyTemplate();
  118. target.Measure(Size.Infinity);
  119. target.CaretIndex = 3;
  120. RaiseKeyEvent(target, Key.Right, 0);
  121. Assert.Equal(4, target.CaretIndex);
  122. }
  123. }
  124. [Fact]
  125. public void Press_Ctrl_A_Select_All_Text()
  126. {
  127. using (UnitTestApplication.Start(Services))
  128. {
  129. var target = new TextBox
  130. {
  131. Template = CreateTemplate(),
  132. Text = "1234"
  133. };
  134. target.ApplyTemplate();
  135. RaiseKeyEvent(target, Key.A, KeyModifiers.Control);
  136. Assert.Equal(0, target.SelectionStart);
  137. Assert.Equal(4, target.SelectionEnd);
  138. }
  139. }
  140. [Fact]
  141. public void Press_Ctrl_A_Select_All_Null_Text()
  142. {
  143. using (UnitTestApplication.Start(Services))
  144. {
  145. var target = new TextBox
  146. {
  147. Template = CreateTemplate()
  148. };
  149. RaiseKeyEvent(target, Key.A, KeyModifiers.Control);
  150. Assert.Equal(0, target.SelectionStart);
  151. Assert.Equal(0, target.SelectionEnd);
  152. }
  153. }
  154. [Fact]
  155. public void Press_Ctrl_Z_Will_Not_Modify_Text()
  156. {
  157. using (UnitTestApplication.Start(Services))
  158. {
  159. var target = new TextBox
  160. {
  161. Template = CreateTemplate(),
  162. Text = "1234"
  163. };
  164. RaiseKeyEvent(target, Key.Z, KeyModifiers.Control);
  165. Assert.Equal("1234", target.Text);
  166. }
  167. }
  168. [Fact]
  169. public void Control_Backspace_Should_Remove_The_Word_Before_The_Caret_If_There_Is_No_Selection()
  170. {
  171. using (UnitTestApplication.Start(Services))
  172. {
  173. TextBox textBox = new TextBox
  174. {
  175. Template = CreateTemplate(),
  176. Text = "First Second Third Fourth",
  177. SelectionStart = 5,
  178. SelectionEnd = 5
  179. };
  180. textBox.ApplyTemplate();
  181. // (First| Second Third Fourth)
  182. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  183. Assert.Equal(" Second Third Fourth", textBox.Text);
  184. // ( Second |Third Fourth)
  185. textBox.CaretIndex = 8;
  186. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  187. Assert.Equal(" Third Fourth", textBox.Text);
  188. // ( Thi|rd Fourth)
  189. textBox.CaretIndex = 4;
  190. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  191. Assert.Equal(" rd Fourth", textBox.Text);
  192. // ( rd F[ou]rth)
  193. textBox.SelectionStart = 5;
  194. textBox.SelectionEnd = 7;
  195. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  196. Assert.Equal(" rd Frth", textBox.Text);
  197. // ( |rd Frth)
  198. textBox.CaretIndex = 1;
  199. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  200. Assert.Equal("rd Frth", textBox.Text);
  201. }
  202. }
  203. [Fact]
  204. public void Control_Delete_Should_Remove_The_Word_After_The_Caret_If_There_Is_No_Selection()
  205. {
  206. using (UnitTestApplication.Start(Services))
  207. {
  208. TextBox textBox = new TextBox
  209. {
  210. Template = CreateTemplate(),
  211. Text = "First Second Third Fourth",
  212. CaretIndex = 19,
  213. };
  214. textBox.ApplyTemplate();
  215. // (First Second Third |Fourth)
  216. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  217. Assert.Equal("First Second Third ", textBox.Text);
  218. // (First Second |Third )
  219. textBox.CaretIndex = 13;
  220. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  221. Assert.Equal("First Second ", textBox.Text);
  222. // (First Sec|ond )
  223. textBox.CaretIndex = 9;
  224. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  225. Assert.Equal("First Sec", textBox.Text);
  226. // (Fi[rs]t Sec )
  227. textBox.SelectionStart = 2;
  228. textBox.SelectionEnd = 4;
  229. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  230. Assert.Equal("Fit Sec", textBox.Text);
  231. // (Fit Sec| )
  232. textBox.Text += " ";
  233. textBox.CaretIndex = 7;
  234. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  235. Assert.Equal("Fit Sec", textBox.Text);
  236. }
  237. }
  238. [Fact]
  239. public void Setting_SelectionStart_To_SelectionEnd_Sets_CaretPosition_To_SelectionStart()
  240. {
  241. using (UnitTestApplication.Start(Services))
  242. {
  243. var textBox = new TextBox
  244. {
  245. Text = "0123456789"
  246. };
  247. textBox.SelectionStart = 2;
  248. textBox.SelectionEnd = 2;
  249. Assert.Equal(2, textBox.CaretIndex);
  250. }
  251. }
  252. [Fact]
  253. public void Setting_Text_Updates_CaretPosition()
  254. {
  255. using (UnitTestApplication.Start(Services))
  256. {
  257. var target = new TextBox
  258. {
  259. Text = "Initial Text",
  260. CaretIndex = 11
  261. };
  262. var invoked = false;
  263. target.GetObservable(TextBox.TextProperty).Skip(1).Subscribe(_ =>
  264. {
  265. // Caret index should be set before Text changed notification, as we don't want
  266. // to notify with an invalid CaretIndex.
  267. Assert.Equal(7, target.CaretIndex);
  268. invoked = true;
  269. });
  270. target.Text = "Changed";
  271. Assert.True(invoked);
  272. }
  273. }
  274. [Fact]
  275. public void Press_Enter_Does_Not_Accept_Return()
  276. {
  277. using (UnitTestApplication.Start(Services))
  278. {
  279. var target = new TextBox
  280. {
  281. Template = CreateTemplate(),
  282. AcceptsReturn = false,
  283. Text = "1234"
  284. };
  285. target.ApplyTemplate();
  286. RaiseKeyEvent(target, Key.Enter, 0);
  287. Assert.Equal("1234", target.Text);
  288. }
  289. }
  290. [Fact]
  291. public void Press_Enter_Add_Default_Newline()
  292. {
  293. using (UnitTestApplication.Start(Services))
  294. {
  295. var target = new TextBox
  296. {
  297. Template = CreateTemplate(),
  298. AcceptsReturn = true
  299. };
  300. target.ApplyTemplate();
  301. RaiseKeyEvent(target, Key.Enter, 0);
  302. Assert.Equal(Environment.NewLine, target.Text);
  303. }
  304. }
  305. [Fact]
  306. public void Press_Enter_Add_Custom_Newline()
  307. {
  308. using (UnitTestApplication.Start(Services))
  309. {
  310. var target = new TextBox
  311. {
  312. Template = CreateTemplate(),
  313. AcceptsReturn = true,
  314. NewLine = "Test"
  315. };
  316. target.ApplyTemplate();
  317. RaiseKeyEvent(target, Key.Enter, 0);
  318. Assert.Equal("Test", target.Text);
  319. }
  320. }
  321. [Theory]
  322. [InlineData(new object[] { false, TextWrapping.NoWrap, ScrollBarVisibility.Hidden })]
  323. [InlineData(new object[] { false, TextWrapping.Wrap, ScrollBarVisibility.Disabled })]
  324. [InlineData(new object[] { true, TextWrapping.NoWrap, ScrollBarVisibility.Auto })]
  325. [InlineData(new object[] { true, TextWrapping.Wrap, ScrollBarVisibility.Disabled })]
  326. public void Has_Correct_Horizontal_ScrollBar_Visibility(
  327. bool acceptsReturn,
  328. TextWrapping wrapping,
  329. ScrollBarVisibility expected)
  330. {
  331. using (UnitTestApplication.Start(Services))
  332. {
  333. var target = new TextBox
  334. {
  335. AcceptsReturn = acceptsReturn,
  336. TextWrapping = wrapping,
  337. };
  338. Assert.Equal(expected, ScrollViewer.GetHorizontalScrollBarVisibility(target));
  339. }
  340. }
  341. [Fact]
  342. public void SelectionEnd_Doesnt_Cause_Exception()
  343. {
  344. using (UnitTestApplication.Start(Services))
  345. {
  346. var target = new TextBox
  347. {
  348. Template = CreateTemplate(),
  349. Text = "0123456789"
  350. };
  351. target.ApplyTemplate();
  352. target.SelectionStart = 0;
  353. target.SelectionEnd = 9;
  354. target.Text = "123";
  355. RaiseTextEvent(target, "456");
  356. Assert.True(true);
  357. }
  358. }
  359. [Fact]
  360. public void SelectionStart_Doesnt_Cause_Exception()
  361. {
  362. using (UnitTestApplication.Start(Services))
  363. {
  364. var target = new TextBox
  365. {
  366. Template = CreateTemplate(),
  367. Text = "0123456789"
  368. };
  369. target.ApplyTemplate();
  370. target.SelectionStart = 8;
  371. target.SelectionEnd = 9;
  372. target.Text = "123";
  373. RaiseTextEvent(target, "456");
  374. Assert.True(true);
  375. }
  376. }
  377. [Fact]
  378. public void SelectionStartEnd_Are_Valid_AterTextChange()
  379. {
  380. using (UnitTestApplication.Start(Services))
  381. {
  382. var target = new TextBox
  383. {
  384. Template = CreateTemplate(),
  385. Text = "0123456789"
  386. };
  387. target.SelectionStart = 8;
  388. target.SelectionEnd = 9;
  389. target.Text = "123";
  390. Assert.True(target.SelectionStart <= "123".Length);
  391. Assert.True(target.SelectionEnd <= "123".Length);
  392. }
  393. }
  394. [Fact]
  395. public void SelectedText_Changes_OnSelectionChange()
  396. {
  397. using (UnitTestApplication.Start(Services))
  398. {
  399. var target = new TextBox
  400. {
  401. Template = CreateTemplate(),
  402. Text = "0123456789"
  403. };
  404. target.ApplyTemplate();
  405. Assert.True(target.SelectedText == "");
  406. target.SelectionStart = 2;
  407. target.SelectionEnd = 4;
  408. Assert.True(target.SelectedText == "23");
  409. }
  410. }
  411. [Fact]
  412. public void SelectedText_EditsText()
  413. {
  414. using (UnitTestApplication.Start(Services))
  415. {
  416. var target = new TextBox
  417. {
  418. Template = CreateTemplate(),
  419. Text = "0123"
  420. };
  421. target.ApplyTemplate();
  422. target.SelectedText = "AA";
  423. Assert.True(target.Text == "AA0123");
  424. target.SelectionStart = 1;
  425. target.SelectionEnd = 3;
  426. target.SelectedText = "BB";
  427. Assert.True(target.Text == "ABB123");
  428. }
  429. }
  430. [Fact]
  431. public void SelectedText_CanClearText()
  432. {
  433. using (UnitTestApplication.Start(Services))
  434. {
  435. var target = new TextBox
  436. {
  437. Template = CreateTemplate(),
  438. Text = "0123"
  439. };
  440. target.SelectionStart = 1;
  441. target.SelectionEnd = 3;
  442. target.SelectedText = "";
  443. Assert.True(target.Text == "03");
  444. }
  445. }
  446. [Fact]
  447. public void SelectedText_NullClearsText()
  448. {
  449. using (UnitTestApplication.Start(Services))
  450. {
  451. var target = new TextBox
  452. {
  453. Template = CreateTemplate(),
  454. Text = "0123"
  455. };
  456. target.SelectionStart = 1;
  457. target.SelectionEnd = 3;
  458. target.SelectedText = null;
  459. Assert.True(target.Text == "03");
  460. }
  461. }
  462. [Fact]
  463. public void CoerceCaretIndex_Doesnt_Cause_Exception_with_malformed_line_ending()
  464. {
  465. using (UnitTestApplication.Start(Services))
  466. {
  467. var target = new TextBox
  468. {
  469. Template = CreateTemplate(),
  470. Text = "0123456789\r"
  471. };
  472. target.CaretIndex = 11;
  473. Assert.True(true);
  474. }
  475. }
  476. [Theory]
  477. [InlineData(Key.Up)]
  478. [InlineData(Key.Down)]
  479. [InlineData(Key.Home)]
  480. [InlineData(Key.End)]
  481. public void Textbox_doesnt_crash_when_Receives_input_and_template_not_applied(Key key)
  482. {
  483. using (UnitTestApplication.Start(FocusServices))
  484. {
  485. var target1 = new TextBox
  486. {
  487. Template = CreateTemplate(),
  488. Text = "1234",
  489. IsVisible = false
  490. };
  491. var root = new TestRoot { Child = target1 };
  492. target1.Focus();
  493. Assert.True(target1.IsFocused);
  494. RaiseKeyEvent(target1, key, KeyModifiers.None);
  495. }
  496. }
  497. [Fact]
  498. public void TextBox_GotFocus_And_LostFocus_Work_Properly()
  499. {
  500. using (UnitTestApplication.Start(FocusServices))
  501. {
  502. var target1 = new TextBox
  503. {
  504. Template = CreateTemplate(),
  505. Text = "1234"
  506. };
  507. var target2 = new TextBox
  508. {
  509. Template = CreateTemplate(),
  510. Text = "5678"
  511. };
  512. var sp = new StackPanel();
  513. sp.Children.Add(target1);
  514. sp.Children.Add(target2);
  515. target1.ApplyTemplate();
  516. target2.ApplyTemplate();
  517. var root = new TestRoot { Child = sp };
  518. var gfcount = 0;
  519. var lfcount = 0;
  520. target1.GotFocus += (s, e) => gfcount++;
  521. target2.LostFocus += (s, e) => lfcount++;
  522. target2.Focus();
  523. Assert.False(target1.IsFocused);
  524. Assert.True(target2.IsFocused);
  525. target1.Focus();
  526. Assert.False(target2.IsFocused);
  527. Assert.True(target1.IsFocused);
  528. Assert.Equal(1, gfcount);
  529. Assert.Equal(1, lfcount);
  530. }
  531. }
  532. [Fact]
  533. public void TextBox_CaretIndex_Persists_When_Focus_Lost()
  534. {
  535. using (UnitTestApplication.Start(FocusServices))
  536. {
  537. var target1 = new TextBox
  538. {
  539. Template = CreateTemplate(),
  540. Text = "1234"
  541. };
  542. var target2 = new TextBox
  543. {
  544. Template = CreateTemplate(),
  545. Text = "5678"
  546. };
  547. var sp = new StackPanel();
  548. sp.Children.Add(target1);
  549. sp.Children.Add(target2);
  550. target1.ApplyTemplate();
  551. target2.ApplyTemplate();
  552. var root = new TestRoot { Child = sp };
  553. target2.Focus();
  554. target2.CaretIndex = 2;
  555. Assert.False(target1.IsFocused);
  556. Assert.True(target2.IsFocused);
  557. target1.Focus();
  558. Assert.Equal(2, target2.CaretIndex);
  559. }
  560. }
  561. [Fact]
  562. public void TextBox_Reveal_Password_Reset_When_Lost_Focus()
  563. {
  564. using (UnitTestApplication.Start(FocusServices))
  565. {
  566. var target1 = new TextBox
  567. {
  568. Template = CreateTemplate(),
  569. Text = "1234",
  570. PasswordChar = '*'
  571. };
  572. var target2 = new TextBox
  573. {
  574. Template = CreateTemplate(),
  575. Text = "5678"
  576. };
  577. var sp = new StackPanel();
  578. sp.Children.Add(target1);
  579. sp.Children.Add(target2);
  580. target1.ApplyTemplate();
  581. target2.ApplyTemplate();
  582. var root = new TestRoot { Child = sp };
  583. target1.Focus();
  584. target1.RevealPassword = true;
  585. target2.Focus();
  586. Assert.False(target1.RevealPassword);
  587. }
  588. }
  589. [Fact]
  590. public void Setting_Bound_Text_To_Null_Works()
  591. {
  592. using (UnitTestApplication.Start(Services))
  593. {
  594. var source = new Class1 { Bar = "bar" };
  595. var target = new TextBox { Template = CreateTemplate(), DataContext = source };
  596. target.ApplyTemplate();
  597. target.Bind(TextBox.TextProperty, new Binding("Bar"));
  598. Assert.Equal("bar", target.Text);
  599. source.Bar = null;
  600. Assert.Null(target.Text);
  601. }
  602. }
  603. [Theory]
  604. [InlineData("abc", "d", 3, 0, 0, false, "abc")]
  605. [InlineData("abc", "dd", 4, 3, 3, false, "abcd")]
  606. [InlineData("abc", "ddd", 3, 0, 2, true, "ddc")]
  607. [InlineData("abc", "dddd", 4, 1, 3, true, "addd")]
  608. [InlineData("abc", "ddddd", 5, 3, 3, true, "abcdd")]
  609. public void MaxLength_Works_Properly(
  610. string initalText,
  611. string textInput,
  612. int maxLength,
  613. int selectionStart,
  614. int selectionEnd,
  615. bool fromClipboard,
  616. string expected)
  617. {
  618. using (UnitTestApplication.Start(Services))
  619. {
  620. var target = new TextBox
  621. {
  622. Template = CreateTemplate(),
  623. Text = initalText,
  624. MaxLength = maxLength,
  625. SelectionStart = selectionStart,
  626. SelectionEnd = selectionEnd
  627. };
  628. var impl = CreateMockTopLevelImpl();
  629. var topLevel = new TestTopLevel(impl.Object)
  630. {
  631. Template = CreateTopLevelTemplate()
  632. };
  633. topLevel.Content = target;
  634. topLevel.ApplyTemplate();
  635. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  636. target.Measure(Size.Infinity);
  637. if (fromClipboard)
  638. {
  639. topLevel.Clipboard?.SetTextAsync(textInput).GetAwaiter().GetResult();
  640. RaiseKeyEvent(target, Key.V, KeyModifiers.Control);
  641. topLevel.Clipboard?.ClearAsync().GetAwaiter().GetResult();
  642. }
  643. else
  644. {
  645. RaiseTextEvent(target, textInput);
  646. }
  647. Assert.Equal(expected, target.Text);
  648. }
  649. }
  650. [Theory]
  651. [InlineData(Key.X, KeyModifiers.Control)]
  652. [InlineData(Key.Back, KeyModifiers.None)]
  653. [InlineData(Key.Delete, KeyModifiers.None)]
  654. [InlineData(Key.Tab, KeyModifiers.None)]
  655. [InlineData(Key.Enter, KeyModifiers.None)]
  656. public void Keys_Allow_Undo(Key key, KeyModifiers modifiers)
  657. {
  658. using (UnitTestApplication.Start(Services))
  659. {
  660. var target = new TextBox
  661. {
  662. Template = CreateTemplate(),
  663. Text = "0123",
  664. AcceptsReturn = true,
  665. AcceptsTab = true
  666. };
  667. var impl = CreateMockTopLevelImpl();
  668. var topLevel = new TestTopLevel(impl.Object)
  669. {
  670. Template = CreateTopLevelTemplate()
  671. };
  672. topLevel.Content = target;
  673. topLevel.ApplyTemplate();
  674. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  675. target.ApplyTemplate();
  676. target.SelectionStart = 1;
  677. target.SelectionEnd = 3;
  678. RaiseKeyEvent(target, key, modifiers);
  679. RaiseKeyEvent(target, Key.Z, KeyModifiers.Control); // undo
  680. Assert.True(target.Text == "0123");
  681. }
  682. }
  683. [Fact]
  684. public void Setting_SelectedText_Should_Fire_Single_Text_Changed_Notification()
  685. {
  686. using (UnitTestApplication.Start(Services))
  687. {
  688. var target = new TextBox
  689. {
  690. Template = CreateTemplate(),
  691. Text = "0123",
  692. AcceptsReturn = true,
  693. AcceptsTab = true,
  694. SelectionStart = 1,
  695. SelectionEnd = 3,
  696. };
  697. var values = new List<string>();
  698. target.GetObservable(TextBox.TextProperty).Subscribe(x => values.Add(x));
  699. target.SelectedText = "A";
  700. Assert.Equal(new[] { "0123", "0A3" }, values);
  701. }
  702. }
  703. [Fact]
  704. public void Entering_Text_With_SelectedText_Should_Fire_Single_Text_Changed_Notification()
  705. {
  706. using (UnitTestApplication.Start(Services))
  707. {
  708. var target = new TextBox
  709. {
  710. Template = CreateTemplate(),
  711. Text = "0123",
  712. AcceptsReturn = true,
  713. AcceptsTab = true,
  714. SelectionStart = 1,
  715. SelectionEnd = 3,
  716. };
  717. var values = new List<string>();
  718. target.GetObservable(TextBox.TextProperty).Subscribe(x => values.Add(x));
  719. RaiseTextEvent(target, "A");
  720. Assert.Equal(new[] { "0123", "0A3" }, values);
  721. }
  722. }
  723. [Fact]
  724. public void Should_Fullfill_MaxLines_Contraint()
  725. {
  726. using (UnitTestApplication.Start(Services))
  727. {
  728. var target = new TextBox
  729. {
  730. Template = CreateTemplate(),
  731. Text = "ABC",
  732. MaxLines = 1,
  733. AcceptsReturn= true
  734. };
  735. var impl = CreateMockTopLevelImpl();
  736. var topLevel = new TestTopLevel(impl.Object)
  737. {
  738. Template = CreateTopLevelTemplate()
  739. };
  740. topLevel.Content = target;
  741. topLevel.ApplyTemplate();
  742. topLevel.LayoutManager.ExecuteInitialLayoutPass();
  743. target.Measure(Size.Infinity);
  744. topLevel.Clipboard?.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult();
  745. RaiseKeyEvent(target, Key.V, KeyModifiers.Control);
  746. topLevel.Clipboard?.ClearAsync().GetAwaiter().GetResult();
  747. RaiseTextEvent(target, Environment.NewLine);
  748. Assert.Equal("ABC", target.Text);
  749. }
  750. }
  751. [Fact]
  752. public void CanUndo_CanRedo_Is_False_When_Initialized()
  753. {
  754. using (UnitTestApplication.Start(Services))
  755. {
  756. var tb = new TextBox
  757. {
  758. Template = CreateTemplate(),
  759. Text = "New Text"
  760. };
  761. tb.Measure(Size.Infinity);
  762. Assert.False(tb.CanUndo);
  763. Assert.False(tb.CanRedo);
  764. }
  765. }
  766. [Fact]
  767. public void CanUndo_CanRedo_and_Programmatic_Undo_Redo_Works()
  768. {
  769. using (UnitTestApplication.Start(Services))
  770. {
  771. var tb = new TextBox
  772. {
  773. Template = CreateTemplate(),
  774. };
  775. tb.Measure(Size.Infinity);
  776. // See GH #6024 for a bit more insight on when Undo/Redo snapshots are taken:
  777. // - Every 'Space', but only when space is handled in OnKeyDown - Spaces in TextInput event won't work
  778. // - Every 7 chars in a long word
  779. RaiseTextEvent(tb, "ABC");
  780. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  781. RaiseTextEvent(tb, "DEF");
  782. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  783. RaiseTextEvent(tb, "123");
  784. // NOTE: the spaces won't actually add spaces b/c they're sent only as key events and not Text events
  785. // so our final text is without spaces
  786. Assert.Equal("ABCDEF123", tb.Text);
  787. Assert.True(tb.CanUndo);
  788. tb.Undo();
  789. // Undo will take us back one step
  790. Assert.Equal("ABCDEF", tb.Text);
  791. Assert.True(tb.CanRedo);
  792. tb.Redo();
  793. // Redo should restore us
  794. Assert.Equal("ABCDEF123", tb.Text);
  795. }
  796. }
  797. [Fact]
  798. public void Setting_UndoLimit_Clears_Undo_Redo()
  799. {
  800. using (UnitTestApplication.Start(Services))
  801. {
  802. var tb = new TextBox
  803. {
  804. Template = CreateTemplate(),
  805. };
  806. tb.Measure(Size.Infinity);
  807. // This is all the same as the above test (CanUndo_CanRedo_and_Programmatic_Undo_Redo_Works)
  808. // We do this to get the undo/redo stacks in a state where both are active
  809. RaiseTextEvent(tb, "ABC");
  810. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  811. RaiseTextEvent(tb, "DEF");
  812. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  813. RaiseTextEvent(tb, "123");
  814. Assert.Equal("ABCDEF123", tb.Text);
  815. Assert.True(tb.CanUndo);
  816. tb.Undo();
  817. // Undo will take us back one step
  818. Assert.Equal("ABCDEF", tb.Text);
  819. Assert.True(tb.CanRedo);
  820. tb.Redo();
  821. // Redo should restore us
  822. Assert.Equal("ABCDEF123", tb.Text);
  823. // Change the undo limit, this should clear both stacks setting CanUndo and CanRedo to false
  824. tb.UndoLimit = 1;
  825. Assert.False(tb.CanUndo);
  826. Assert.False(tb.CanRedo);
  827. }
  828. }
  829. [Fact]
  830. public void Setting_IsUndoEnabled_To_False_Clears_Undo_Redo()
  831. {
  832. using (UnitTestApplication.Start(Services))
  833. {
  834. var tb = new TextBox
  835. {
  836. Template = CreateTemplate(),
  837. };
  838. tb.Measure(Size.Infinity);
  839. // This is all the same as the above test (CanUndo_CanRedo_and_Programmatic_Undo_Redo_Works)
  840. // We do this to get the undo/redo stacks in a state where both are active
  841. RaiseTextEvent(tb, "ABC");
  842. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  843. RaiseTextEvent(tb, "DEF");
  844. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  845. RaiseTextEvent(tb, "123");
  846. Assert.Equal("ABCDEF123", tb.Text);
  847. Assert.True(tb.CanUndo);
  848. tb.Undo();
  849. // Undo will take us back one step
  850. Assert.Equal("ABCDEF", tb.Text);
  851. Assert.True(tb.CanRedo);
  852. tb.Redo();
  853. // Redo should restore us
  854. Assert.Equal("ABCDEF123", tb.Text);
  855. // Disable Undo/Redo, this should clear both stacks setting CanUndo and CanRedo to false
  856. tb.IsUndoEnabled = false;
  857. Assert.False(tb.CanUndo);
  858. Assert.False(tb.CanRedo);
  859. }
  860. }
  861. [Fact]
  862. public void UndoLimit_Count_Is_Respected()
  863. {
  864. using (UnitTestApplication.Start(Services))
  865. {
  866. var tb = new TextBox
  867. {
  868. Template = CreateTemplate(),
  869. UndoLimit = 3 // Something small for this test
  870. };
  871. tb.Measure(Size.Infinity);
  872. // Push 3 undoable actions, we should only be able to recover 2
  873. RaiseTextEvent(tb, "ABC");
  874. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  875. RaiseTextEvent(tb, "DEF");
  876. RaiseKeyEvent(tb, Key.Space, KeyModifiers.None);
  877. RaiseTextEvent(tb, "123");
  878. Assert.Equal("ABCDEF123", tb.Text);
  879. // Undo will take us back one step
  880. tb.Undo();
  881. Assert.Equal("ABCDEF", tb.Text);
  882. // Undo again
  883. tb.Undo();
  884. Assert.Equal("ABC", tb.Text);
  885. // We now should not be able to undo again
  886. Assert.False(tb.CanUndo);
  887. }
  888. }
  889. [Fact]
  890. public void Should_Move_Caret_To_EndOfLine()
  891. {
  892. using (UnitTestApplication.Start(Services))
  893. {
  894. var tb = new TextBox
  895. {
  896. Template = CreateTemplate(),
  897. Text = "AB\nAB"
  898. };
  899. tb.Measure(Size.Infinity);
  900. RaiseKeyEvent(tb, Key.End, KeyModifiers.Shift);
  901. Assert.Equal(2, tb.CaretIndex);
  902. }
  903. }
  904. private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
  905. focusManager: new FocusManager(),
  906. keyboardDevice: () => new KeyboardDevice(),
  907. keyboardNavigation: new KeyboardNavigationHandler(),
  908. inputManager: new InputManager(),
  909. standardCursorFactory: Mock.Of<ICursorFactory>(),
  910. textShaperImpl: new MockTextShaperImpl(),
  911. fontManagerImpl: new MockFontManagerImpl());
  912. private static TestServices Services => TestServices.MockThreadingInterface.With(
  913. standardCursorFactory: Mock.Of<ICursorFactory>(),
  914. renderInterface: new MockPlatformRenderInterface(),
  915. textShaperImpl: new MockTextShaperImpl(),
  916. fontManagerImpl: new MockFontManagerImpl());
  917. private IControlTemplate CreateTemplate()
  918. {
  919. return new FuncControlTemplate<TextBox>((control, scope) =>
  920. new TextPresenter
  921. {
  922. Name = "PART_TextPresenter",
  923. [!!TextPresenter.TextProperty] = new Binding
  924. {
  925. Path = nameof(TextPresenter.Text),
  926. Mode = BindingMode.TwoWay,
  927. Priority = BindingPriority.Template,
  928. RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
  929. },
  930. [!!TextPresenter.CaretIndexProperty] = new Binding
  931. {
  932. Path = nameof(TextPresenter.CaretIndex),
  933. Mode = BindingMode.TwoWay,
  934. Priority = BindingPriority.Template,
  935. RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
  936. }
  937. }.RegisterInNameScope(scope));
  938. }
  939. private static void RaiseKeyEvent(TextBox textBox, Key key, KeyModifiers inputModifiers)
  940. {
  941. textBox.RaiseEvent(new KeyEventArgs
  942. {
  943. RoutedEvent = InputElement.KeyDownEvent,
  944. KeyModifiers = inputModifiers,
  945. Key = key
  946. });
  947. }
  948. private static void RaiseTextEvent(TextBox textBox, string text)
  949. {
  950. textBox.RaiseEvent(new TextInputEventArgs
  951. {
  952. RoutedEvent = InputElement.TextInputEvent,
  953. Text = text
  954. });
  955. }
  956. private class Class1 : NotifyingBase
  957. {
  958. private int _foo;
  959. private string _bar;
  960. public int Foo
  961. {
  962. get { return _foo; }
  963. set { _foo = value; RaisePropertyChanged(); }
  964. }
  965. public string Bar
  966. {
  967. get { return _bar; }
  968. set { _bar = value; RaisePropertyChanged(); }
  969. }
  970. }
  971. private class ClipboardStub : IClipboard // in order to get tests working that use the clipboard
  972. {
  973. private string _text;
  974. public Task<string> GetTextAsync() => Task.FromResult(_text);
  975. public Task SetTextAsync(string text)
  976. {
  977. _text = text;
  978. return Task.CompletedTask;
  979. }
  980. public Task ClearAsync()
  981. {
  982. _text = null;
  983. return Task.CompletedTask;
  984. }
  985. public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask;
  986. public Task<string[]> GetFormatsAsync() => Task.FromResult(Array.Empty<string>());
  987. public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
  988. }
  989. private class TestTopLevel : TopLevel
  990. {
  991. private readonly ILayoutManager _layoutManager;
  992. public TestTopLevel(ITopLevelImpl impl, ILayoutManager layoutManager = null)
  993. : base(impl)
  994. {
  995. _layoutManager = layoutManager ?? new LayoutManager(this);
  996. }
  997. protected override ILayoutManager CreateLayoutManager() => _layoutManager;
  998. }
  999. private static Mock<ITopLevelImpl> CreateMockTopLevelImpl()
  1000. {
  1001. var clipboard = new Mock<ITopLevelImpl>();
  1002. clipboard.Setup(r => r.CreateRenderer(It.IsAny<IRenderRoot>()))
  1003. .Returns(RendererMocks.CreateRenderer().Object);
  1004. clipboard.Setup(r => r.TryGetFeature(typeof(IClipboard)))
  1005. .Returns(new ClipboardStub());
  1006. clipboard.SetupGet(x => x.RenderScaling).Returns(1);
  1007. return clipboard;
  1008. }
  1009. private static FuncControlTemplate<TestTopLevel> CreateTopLevelTemplate()
  1010. {
  1011. return new FuncControlTemplate<TestTopLevel>((x, scope) =>
  1012. new ContentPresenter
  1013. {
  1014. Name = "PART_ContentPresenter",
  1015. [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty],
  1016. }.RegisterInNameScope(scope));
  1017. }
  1018. private class TestContextMenu : ContextMenu
  1019. {
  1020. public TestContextMenu()
  1021. {
  1022. IsOpen = true;
  1023. }
  1024. }
  1025. }
  1026. }