TextBoxTests.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Reactive.Linq;
  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.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 CoerceCaretIndex_Doesnt_Cause_Exception_with_malformed_line_ending()
  359. {
  360. using (UnitTestApplication.Start(Services))
  361. {
  362. var target = new TextBox
  363. {
  364. Template = CreateTemplate(),
  365. Text = "0123456789\r"
  366. };
  367. target.CaretIndex = 11;
  368. Assert.True(true);
  369. }
  370. }
  371. [Fact]
  372. public void TextBox_GotFocus_And_LostFocus_Work_Properly()
  373. {
  374. using (UnitTestApplication.Start(FocusServices))
  375. {
  376. var target1 = new TextBox
  377. {
  378. Template = CreateTemplate(),
  379. Text = "1234"
  380. };
  381. var target2 = new TextBox
  382. {
  383. Template = CreateTemplate(),
  384. Text = "5678"
  385. };
  386. var sp = new StackPanel();
  387. sp.Children.Add(target1);
  388. sp.Children.Add(target2);
  389. target1.ApplyTemplate();
  390. target2.ApplyTemplate();
  391. var root = new TestRoot { Child = sp };
  392. var gfcount = 0;
  393. var lfcount = 0;
  394. target1.GotFocus += (s, e) => gfcount++;
  395. target2.LostFocus += (s, e) => lfcount++;
  396. target2.Focus();
  397. Assert.False(target1.IsFocused);
  398. Assert.True(target2.IsFocused);
  399. target1.Focus();
  400. Assert.False(target2.IsFocused);
  401. Assert.True(target1.IsFocused);
  402. Assert.Equal(1, gfcount);
  403. Assert.Equal(1, lfcount);
  404. }
  405. }
  406. [Fact]
  407. public void Setting_Bound_Text_To_Null_Works()
  408. {
  409. using (UnitTestApplication.Start(Services))
  410. {
  411. var source = new Class1 { Bar = "bar" };
  412. var target = new TextBox { DataContext = source };
  413. target.Bind(TextBox.TextProperty, new Binding("Bar"));
  414. Assert.Equal("bar", target.Text);
  415. source.Bar = null;
  416. Assert.Null(target.Text);
  417. }
  418. }
  419. [Fact]
  420. public void Text_Box_MaxLength_Work_Properly()
  421. {
  422. using (UnitTestApplication.Start(Services))
  423. {
  424. var target = new TextBox
  425. {
  426. Template = CreateTemplate(),
  427. Text = "abc",
  428. MaxLength = 3,
  429. };
  430. RaiseKeyEvent(target, Key.D, KeyModifiers.None);
  431. Assert.Equal("abc", target.Text);
  432. }
  433. }
  434. private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
  435. focusManager: new FocusManager(),
  436. keyboardDevice: () => new KeyboardDevice(),
  437. keyboardNavigation: new KeyboardNavigationHandler(),
  438. inputManager: new InputManager(),
  439. standardCursorFactory: Mock.Of<IStandardCursorFactory>());
  440. private static TestServices Services => TestServices.MockThreadingInterface.With(
  441. standardCursorFactory: Mock.Of<IStandardCursorFactory>());
  442. private IControlTemplate CreateTemplate()
  443. {
  444. return new FuncControlTemplate<TextBox>((control, scope) =>
  445. new TextPresenter
  446. {
  447. Name = "PART_TextPresenter",
  448. [!!TextPresenter.TextProperty] = new Binding
  449. {
  450. Path = "Text",
  451. Mode = BindingMode.TwoWay,
  452. Priority = BindingPriority.TemplatedParent,
  453. RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
  454. },
  455. }.RegisterInNameScope(scope));
  456. }
  457. private void RaiseKeyEvent(TextBox textBox, Key key, KeyModifiers inputModifiers)
  458. {
  459. textBox.RaiseEvent(new KeyEventArgs
  460. {
  461. RoutedEvent = InputElement.KeyDownEvent,
  462. KeyModifiers = inputModifiers,
  463. Key = key
  464. });
  465. }
  466. private void RaiseTextEvent(TextBox textBox, string text)
  467. {
  468. textBox.RaiseEvent(new TextInputEventArgs
  469. {
  470. RoutedEvent = InputElement.TextInputEvent,
  471. Text = text
  472. });
  473. }
  474. private class Class1 : NotifyingBase
  475. {
  476. private int _foo;
  477. private string _bar;
  478. public int Foo
  479. {
  480. get { return _foo; }
  481. set { _foo = value; RaisePropertyChanged(); }
  482. }
  483. public string Bar
  484. {
  485. get { return _bar; }
  486. set { _bar = value; RaisePropertyChanged(); }
  487. }
  488. }
  489. }
  490. }