TextBoxTests.cs 37 KB

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