TextBoxTests.cs 39 KB

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