TextBoxTests.cs 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169
  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. private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
  871. focusManager: new FocusManager(),
  872. keyboardDevice: () => new KeyboardDevice(),
  873. keyboardNavigation: new KeyboardNavigationHandler(),
  874. inputManager: new InputManager(),
  875. standardCursorFactory: Mock.Of<ICursorFactory>(),
  876. textShaperImpl: new MockTextShaperImpl(),
  877. fontManagerImpl: new MockFontManagerImpl());
  878. private static TestServices Services => TestServices.MockThreadingInterface.With(
  879. standardCursorFactory: Mock.Of<ICursorFactory>(),
  880. renderInterface: new MockPlatformRenderInterface(),
  881. textShaperImpl: new MockTextShaperImpl(),
  882. fontManagerImpl: new MockFontManagerImpl());
  883. private IControlTemplate CreateTemplate()
  884. {
  885. return new FuncControlTemplate<TextBox>((control, scope) =>
  886. new TextPresenter
  887. {
  888. Name = "PART_TextPresenter",
  889. [!!TextPresenter.TextProperty] = new Binding
  890. {
  891. Path = nameof(TextPresenter.Text),
  892. Mode = BindingMode.TwoWay,
  893. Priority = BindingPriority.Template,
  894. RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
  895. },
  896. [!!TextPresenter.CaretIndexProperty] = new Binding
  897. {
  898. Path = nameof(TextPresenter.CaretIndex),
  899. Mode = BindingMode.TwoWay,
  900. Priority = BindingPriority.Template,
  901. RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
  902. }
  903. }.RegisterInNameScope(scope));
  904. }
  905. private static void RaiseKeyEvent(TextBox textBox, Key key, KeyModifiers inputModifiers)
  906. {
  907. textBox.RaiseEvent(new KeyEventArgs
  908. {
  909. RoutedEvent = InputElement.KeyDownEvent,
  910. KeyModifiers = inputModifiers,
  911. Key = key
  912. });
  913. }
  914. private static void RaiseTextEvent(TextBox textBox, string text)
  915. {
  916. textBox.RaiseEvent(new TextInputEventArgs
  917. {
  918. RoutedEvent = InputElement.TextInputEvent,
  919. Text = text
  920. });
  921. }
  922. private class Class1 : NotifyingBase
  923. {
  924. private int _foo;
  925. private string _bar;
  926. public int Foo
  927. {
  928. get { return _foo; }
  929. set { _foo = value; RaisePropertyChanged(); }
  930. }
  931. public string Bar
  932. {
  933. get { return _bar; }
  934. set { _bar = value; RaisePropertyChanged(); }
  935. }
  936. }
  937. private class ClipboardStub : IClipboard // in order to get tests working that use the clipboard
  938. {
  939. private string _text;
  940. public Task<string> GetTextAsync() => Task.FromResult(_text);
  941. public Task SetTextAsync(string text)
  942. {
  943. _text = text;
  944. return Task.CompletedTask;
  945. }
  946. public Task ClearAsync()
  947. {
  948. _text = null;
  949. return Task.CompletedTask;
  950. }
  951. public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask;
  952. public Task<string[]> GetFormatsAsync() => Task.FromResult(Array.Empty<string>());
  953. public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
  954. }
  955. private class TestContextMenu : ContextMenu
  956. {
  957. public TestContextMenu()
  958. {
  959. IsOpen = true;
  960. }
  961. }
  962. }
  963. }