TextBoxTests.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. using System;
  2. using System.Reactive.Linq;
  3. using System.Threading.Tasks;
  4. using Avalonia.Controls.Presenters;
  5. using Avalonia.Controls.Primitives;
  6. using Avalonia.Controls.Templates;
  7. using Avalonia.Data;
  8. using Avalonia.Input;
  9. using Avalonia.Input.Platform;
  10. using Avalonia.Media;
  11. using Avalonia.Platform;
  12. using Avalonia.UnitTests;
  13. using Moq;
  14. using Xunit;
  15. namespace Avalonia.Controls.UnitTests
  16. {
  17. public class TextBoxTests
  18. {
  19. [Fact]
  20. public void DefaultBindingMode_Should_Be_TwoWay()
  21. {
  22. Assert.Equal(
  23. BindingMode.TwoWay,
  24. TextBox.TextProperty.GetMetadata(typeof(TextBox)).DefaultBindingMode);
  25. }
  26. [Fact]
  27. public void CaretIndex_Can_Moved_To_Position_After_The_End_Of_Text_With_Arrow_Key()
  28. {
  29. using (UnitTestApplication.Start(Services))
  30. {
  31. var target = new TextBox
  32. {
  33. Template = CreateTemplate(),
  34. Text = "1234"
  35. };
  36. target.CaretIndex = 3;
  37. RaiseKeyEvent(target, Key.Right, 0);
  38. Assert.Equal(4, target.CaretIndex);
  39. }
  40. }
  41. [Fact]
  42. public void Press_Ctrl_A_Select_All_Text()
  43. {
  44. using (UnitTestApplication.Start(Services))
  45. {
  46. var target = new TextBox
  47. {
  48. Template = CreateTemplate(),
  49. Text = "1234"
  50. };
  51. RaiseKeyEvent(target, Key.A, KeyModifiers.Control);
  52. Assert.Equal(0, target.SelectionStart);
  53. Assert.Equal(4, target.SelectionEnd);
  54. }
  55. }
  56. [Fact]
  57. public void Press_Ctrl_A_Select_All_Null_Text()
  58. {
  59. using (UnitTestApplication.Start(Services))
  60. {
  61. var target = new TextBox
  62. {
  63. Template = CreateTemplate()
  64. };
  65. RaiseKeyEvent(target, Key.A, KeyModifiers.Control);
  66. Assert.Equal(0, target.SelectionStart);
  67. Assert.Equal(0, target.SelectionEnd);
  68. }
  69. }
  70. [Fact]
  71. public void Press_Ctrl_Z_Will_Not_Modify_Text()
  72. {
  73. using (UnitTestApplication.Start(Services))
  74. {
  75. var target = new TextBox
  76. {
  77. Template = CreateTemplate(),
  78. Text = "1234"
  79. };
  80. RaiseKeyEvent(target, Key.Z, KeyModifiers.Control);
  81. Assert.Equal("1234", target.Text);
  82. }
  83. }
  84. [Fact]
  85. public void Typing_Beginning_With_0_Should_Not_Modify_Text_When_Bound_To_Int()
  86. {
  87. using (UnitTestApplication.Start(Services))
  88. {
  89. var source = new Class1();
  90. var target = new TextBox
  91. {
  92. DataContext = source,
  93. Template = CreateTemplate(),
  94. };
  95. target.ApplyTemplate();
  96. target.Bind(TextBox.TextProperty, new Binding(nameof(Class1.Foo), BindingMode.TwoWay));
  97. Assert.Equal("0", target.Text);
  98. target.CaretIndex = 1;
  99. target.RaiseEvent(new TextInputEventArgs
  100. {
  101. RoutedEvent = InputElement.TextInputEvent,
  102. Text = "2",
  103. });
  104. Assert.Equal("02", target.Text);
  105. }
  106. }
  107. [Fact]
  108. public void Control_Backspace_Should_Remove_The_Word_Before_The_Caret_If_There_Is_No_Selection()
  109. {
  110. using (UnitTestApplication.Start(Services))
  111. {
  112. TextBox textBox = new TextBox
  113. {
  114. Text = "First Second Third Fourth",
  115. CaretIndex = 5
  116. };
  117. // (First| Second Third Fourth)
  118. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  119. Assert.Equal(" Second Third Fourth", textBox.Text);
  120. // ( Second |Third Fourth)
  121. textBox.CaretIndex = 8;
  122. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  123. Assert.Equal(" Third Fourth", textBox.Text);
  124. // ( Thi|rd Fourth)
  125. textBox.CaretIndex = 4;
  126. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  127. Assert.Equal(" rd Fourth", textBox.Text);
  128. // ( rd F[ou]rth)
  129. textBox.SelectionStart = 5;
  130. textBox.SelectionEnd = 7;
  131. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  132. Assert.Equal(" rd Frth", textBox.Text);
  133. // ( |rd Frth)
  134. textBox.CaretIndex = 1;
  135. RaiseKeyEvent(textBox, Key.Back, KeyModifiers.Control);
  136. Assert.Equal("rd Frth", textBox.Text);
  137. }
  138. }
  139. [Fact]
  140. public void Control_Delete_Should_Remove_The_Word_After_The_Caret_If_There_Is_No_Selection()
  141. {
  142. using (UnitTestApplication.Start(Services))
  143. {
  144. TextBox textBox = new TextBox
  145. {
  146. Text = "First Second Third Fourth",
  147. CaretIndex = 19
  148. };
  149. // (First Second Third |Fourth)
  150. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  151. Assert.Equal("First Second Third ", textBox.Text);
  152. // (First Second |Third )
  153. textBox.CaretIndex = 13;
  154. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  155. Assert.Equal("First Second ", textBox.Text);
  156. // (First Sec|ond )
  157. textBox.CaretIndex = 9;
  158. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  159. Assert.Equal("First Sec", textBox.Text);
  160. // (Fi[rs]t Sec )
  161. textBox.SelectionStart = 2;
  162. textBox.SelectionEnd = 4;
  163. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  164. Assert.Equal("Fit Sec", textBox.Text);
  165. // (Fit Sec| )
  166. textBox.Text += " ";
  167. textBox.CaretIndex = 7;
  168. RaiseKeyEvent(textBox, Key.Delete, KeyModifiers.Control);
  169. Assert.Equal("Fit Sec", textBox.Text);
  170. }
  171. }
  172. [Fact]
  173. public void Setting_SelectionStart_To_SelectionEnd_Sets_CaretPosition_To_SelectionStart()
  174. {
  175. using (UnitTestApplication.Start(Services))
  176. {
  177. var textBox = new TextBox
  178. {
  179. Text = "0123456789"
  180. };
  181. textBox.SelectionStart = 2;
  182. textBox.SelectionEnd = 2;
  183. Assert.Equal(2, textBox.CaretIndex);
  184. }
  185. }
  186. [Fact]
  187. public void Setting_Text_Updates_CaretPosition()
  188. {
  189. using (UnitTestApplication.Start(Services))
  190. {
  191. var target = new TextBox
  192. {
  193. Text = "Initial Text",
  194. CaretIndex = 11
  195. };
  196. var invoked = false;
  197. target.GetObservable(TextBox.TextProperty).Skip(1).Subscribe(_ =>
  198. {
  199. // Caret index should be set before Text changed notification, as we don't want
  200. // to notify with an invalid CaretIndex.
  201. Assert.Equal(7, target.CaretIndex);
  202. invoked = true;
  203. });
  204. target.Text = "Changed";
  205. Assert.True(invoked);
  206. }
  207. }
  208. [Fact]
  209. public void Press_Enter_Does_Not_Accept_Return()
  210. {
  211. using (UnitTestApplication.Start(Services))
  212. {
  213. var target = new TextBox
  214. {
  215. Template = CreateTemplate(),
  216. AcceptsReturn = false,
  217. Text = "1234"
  218. };
  219. RaiseKeyEvent(target, Key.Enter, 0);
  220. Assert.Equal("1234", target.Text);
  221. }
  222. }
  223. [Fact]
  224. public void Press_Enter_Add_Default_Newline()
  225. {
  226. using (UnitTestApplication.Start(Services))
  227. {
  228. var target = new TextBox
  229. {
  230. Template = CreateTemplate(),
  231. AcceptsReturn = true
  232. };
  233. RaiseKeyEvent(target, Key.Enter, 0);
  234. Assert.Equal(Environment.NewLine, target.Text);
  235. }
  236. }
  237. [Fact]
  238. public void Press_Enter_Add_Custom_Newline()
  239. {
  240. using (UnitTestApplication.Start(Services))
  241. {
  242. var target = new TextBox
  243. {
  244. Template = CreateTemplate(),
  245. AcceptsReturn = true,
  246. NewLine = "Test"
  247. };
  248. RaiseKeyEvent(target, Key.Enter, 0);
  249. Assert.Equal("Test", target.Text);
  250. }
  251. }
  252. [Theory]
  253. [InlineData(new object[] { false, TextWrapping.NoWrap, ScrollBarVisibility.Hidden })]
  254. [InlineData(new object[] { false, TextWrapping.Wrap, ScrollBarVisibility.Hidden })]
  255. [InlineData(new object[] { true, TextWrapping.NoWrap, ScrollBarVisibility.Auto })]
  256. [InlineData(new object[] { true, TextWrapping.Wrap, ScrollBarVisibility.Disabled })]
  257. public void Has_Correct_Horizontal_ScrollBar_Visibility(
  258. bool acceptsReturn,
  259. TextWrapping wrapping,
  260. ScrollBarVisibility expected)
  261. {
  262. using (UnitTestApplication.Start(Services))
  263. {
  264. var target = new TextBox
  265. {
  266. AcceptsReturn = acceptsReturn,
  267. TextWrapping = wrapping,
  268. };
  269. Assert.Equal(expected, ScrollViewer.GetHorizontalScrollBarVisibility(target));
  270. }
  271. }
  272. [Fact]
  273. public void SelectionEnd_Doesnt_Cause_Exception()
  274. {
  275. using (UnitTestApplication.Start(Services))
  276. {
  277. var target = new TextBox
  278. {
  279. Template = CreateTemplate(),
  280. Text = "0123456789"
  281. };
  282. target.SelectionStart = 0;
  283. target.SelectionEnd = 9;
  284. target.Text = "123";
  285. RaiseTextEvent(target, "456");
  286. Assert.True(true);
  287. }
  288. }
  289. [Fact]
  290. public void SelectionStart_Doesnt_Cause_Exception()
  291. {
  292. using (UnitTestApplication.Start(Services))
  293. {
  294. var target = new TextBox
  295. {
  296. Template = CreateTemplate(),
  297. Text = "0123456789"
  298. };
  299. target.SelectionStart = 8;
  300. target.SelectionEnd = 9;
  301. target.Text = "123";
  302. RaiseTextEvent(target, "456");
  303. Assert.True(true);
  304. }
  305. }
  306. [Fact]
  307. public void SelectionStartEnd_Are_Valid_AterTextChange()
  308. {
  309. using (UnitTestApplication.Start(Services))
  310. {
  311. var target = new TextBox
  312. {
  313. Template = CreateTemplate(),
  314. Text = "0123456789"
  315. };
  316. target.SelectionStart = 8;
  317. target.SelectionEnd = 9;
  318. target.Text = "123";
  319. Assert.True(target.SelectionStart <= "123".Length);
  320. Assert.True(target.SelectionEnd <= "123".Length);
  321. }
  322. }
  323. [Fact]
  324. public void SelectedText_Changes_OnSelectionChange()
  325. {
  326. using (UnitTestApplication.Start(Services))
  327. {
  328. var target = new TextBox
  329. {
  330. Template = CreateTemplate(),
  331. Text = "0123456789"
  332. };
  333. Assert.True(target.SelectedText == "");
  334. target.SelectionStart = 2;
  335. target.SelectionEnd = 4;
  336. Assert.True(target.SelectedText == "23");
  337. }
  338. }
  339. [Fact]
  340. public void SelectedText_EditsText()
  341. {
  342. using (UnitTestApplication.Start(Services))
  343. {
  344. var target = new TextBox
  345. {
  346. Template = CreateTemplate(),
  347. Text = "0123"
  348. };
  349. target.SelectedText = "AA";
  350. Assert.True(target.Text == "AA0123");
  351. target.SelectionStart = 1;
  352. target.SelectionEnd = 3;
  353. target.SelectedText = "BB";
  354. Assert.True(target.Text == "ABB123");
  355. }
  356. }
  357. [Fact]
  358. public void SelectedText_CanClearText()
  359. {
  360. using (UnitTestApplication.Start(Services))
  361. {
  362. var target = new TextBox
  363. {
  364. Template = CreateTemplate(),
  365. Text = "0123"
  366. };
  367. target.SelectionStart = 1;
  368. target.SelectionEnd = 3;
  369. target.SelectedText = "";
  370. Assert.True(target.Text == "03");
  371. }
  372. }
  373. [Fact]
  374. public void SelectedText_NullClearsText()
  375. {
  376. using (UnitTestApplication.Start(Services))
  377. {
  378. var target = new TextBox
  379. {
  380. Template = CreateTemplate(),
  381. Text = "0123"
  382. };
  383. target.SelectionStart = 1;
  384. target.SelectionEnd = 3;
  385. target.SelectedText = null;
  386. Assert.True(target.Text == "03");
  387. }
  388. }
  389. [Fact]
  390. public void CoerceCaretIndex_Doesnt_Cause_Exception_with_malformed_line_ending()
  391. {
  392. using (UnitTestApplication.Start(Services))
  393. {
  394. var target = new TextBox
  395. {
  396. Template = CreateTemplate(),
  397. Text = "0123456789\r"
  398. };
  399. target.CaretIndex = 11;
  400. Assert.True(true);
  401. }
  402. }
  403. [Fact]
  404. public void TextBox_GotFocus_And_LostFocus_Work_Properly()
  405. {
  406. using (UnitTestApplication.Start(FocusServices))
  407. {
  408. var target1 = new TextBox
  409. {
  410. Template = CreateTemplate(),
  411. Text = "1234"
  412. };
  413. var target2 = new TextBox
  414. {
  415. Template = CreateTemplate(),
  416. Text = "5678"
  417. };
  418. var sp = new StackPanel();
  419. sp.Children.Add(target1);
  420. sp.Children.Add(target2);
  421. target1.ApplyTemplate();
  422. target2.ApplyTemplate();
  423. var root = new TestRoot { Child = sp };
  424. var gfcount = 0;
  425. var lfcount = 0;
  426. target1.GotFocus += (s, e) => gfcount++;
  427. target2.LostFocus += (s, e) => lfcount++;
  428. target2.Focus();
  429. Assert.False(target1.IsFocused);
  430. Assert.True(target2.IsFocused);
  431. target1.Focus();
  432. Assert.False(target2.IsFocused);
  433. Assert.True(target1.IsFocused);
  434. Assert.Equal(1, gfcount);
  435. Assert.Equal(1, lfcount);
  436. }
  437. }
  438. [Fact]
  439. public void Setting_Bound_Text_To_Null_Works()
  440. {
  441. using (UnitTestApplication.Start(Services))
  442. {
  443. var source = new Class1 { Bar = "bar" };
  444. var target = new TextBox { DataContext = source };
  445. target.Bind(TextBox.TextProperty, new Binding("Bar"));
  446. Assert.Equal("bar", target.Text);
  447. source.Bar = null;
  448. Assert.Null(target.Text);
  449. }
  450. }
  451. [Fact]
  452. public void Text_Box_MaxLength_Work_Properly()
  453. {
  454. using (UnitTestApplication.Start(Services))
  455. {
  456. var target = new TextBox
  457. {
  458. Template = CreateTemplate(),
  459. Text = "abc",
  460. MaxLength = 3,
  461. };
  462. RaiseKeyEvent(target, Key.D, KeyModifiers.None);
  463. Assert.Equal("abc", target.Text);
  464. }
  465. }
  466. [Fact]
  467. public void Cut_Allows_Undo()
  468. {
  469. using (UnitTestApplication.Start(Services))
  470. {
  471. var target = new TextBox
  472. {
  473. Template = CreateTemplate(),
  474. Text = "0123"
  475. };
  476. target.SelectionStart = 1;
  477. target.SelectionEnd = 3;
  478. AvaloniaLocator.CurrentMutable
  479. .Bind<Input.Platform.IClipboard>().ToSingleton<ClipboardStub>();
  480. RaiseKeyEvent(target, Key.X, KeyModifiers.Control); // cut
  481. Assert.True(target.Text == "03");
  482. RaiseKeyEvent(target, Key.Z, KeyModifiers.Control); // undo
  483. Assert.True(target.Text == "0123");
  484. }
  485. }
  486. private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
  487. focusManager: new FocusManager(),
  488. keyboardDevice: () => new KeyboardDevice(),
  489. keyboardNavigation: new KeyboardNavigationHandler(),
  490. inputManager: new InputManager(),
  491. standardCursorFactory: Mock.Of<IStandardCursorFactory>());
  492. private static TestServices Services => TestServices.MockThreadingInterface.With(
  493. standardCursorFactory: Mock.Of<IStandardCursorFactory>());
  494. private IControlTemplate CreateTemplate()
  495. {
  496. return new FuncControlTemplate<TextBox>((control, scope) =>
  497. new TextPresenter
  498. {
  499. Name = "PART_TextPresenter",
  500. [!!TextPresenter.TextProperty] = new Binding
  501. {
  502. Path = "Text",
  503. Mode = BindingMode.TwoWay,
  504. Priority = BindingPriority.TemplatedParent,
  505. RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
  506. },
  507. }.RegisterInNameScope(scope));
  508. }
  509. private void RaiseKeyEvent(TextBox textBox, Key key, KeyModifiers inputModifiers)
  510. {
  511. textBox.RaiseEvent(new KeyEventArgs
  512. {
  513. RoutedEvent = InputElement.KeyDownEvent,
  514. KeyModifiers = inputModifiers,
  515. Key = key
  516. });
  517. }
  518. private void RaiseTextEvent(TextBox textBox, string text)
  519. {
  520. textBox.RaiseEvent(new TextInputEventArgs
  521. {
  522. RoutedEvent = InputElement.TextInputEvent,
  523. Text = text
  524. });
  525. }
  526. private class Class1 : NotifyingBase
  527. {
  528. private int _foo;
  529. private string _bar;
  530. public int Foo
  531. {
  532. get { return _foo; }
  533. set { _foo = value; RaisePropertyChanged(); }
  534. }
  535. public string Bar
  536. {
  537. get { return _bar; }
  538. set { _bar = value; RaisePropertyChanged(); }
  539. }
  540. }
  541. private class ClipboardStub : IClipboard // in order to get tests working that use the clipboard
  542. {
  543. public Task<string> GetTextAsync() => Task.FromResult("");
  544. public Task SetTextAsync(string text) => Task.CompletedTask;
  545. public Task ClearAsync() => Task.CompletedTask;
  546. }
  547. }
  548. }