TextBoxTests.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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 CoerceCaretIndex_Doesnt_Cause_Exception_with_malformed_line_ending()
  325. {
  326. using (UnitTestApplication.Start(Services))
  327. {
  328. var target = new TextBox
  329. {
  330. Template = CreateTemplate(),
  331. Text = "0123456789\r"
  332. };
  333. target.CaretIndex = 11;
  334. Assert.True(true);
  335. }
  336. }
  337. [Fact]
  338. public void TextBox_GotFocus_And_LostFocus_Work_Properly()
  339. {
  340. using (UnitTestApplication.Start(FocusServices))
  341. {
  342. var target1 = new TextBox
  343. {
  344. Template = CreateTemplate(),
  345. Text = "1234"
  346. };
  347. var target2 = new TextBox
  348. {
  349. Template = CreateTemplate(),
  350. Text = "5678"
  351. };
  352. var sp = new StackPanel();
  353. sp.Children.Add(target1);
  354. sp.Children.Add(target2);
  355. target1.ApplyTemplate();
  356. target2.ApplyTemplate();
  357. var root = new TestRoot { Child = sp };
  358. var gfcount = 0;
  359. var lfcount = 0;
  360. target1.GotFocus += (s, e) => gfcount++;
  361. target2.LostFocus += (s, e) => lfcount++;
  362. target2.Focus();
  363. Assert.False(target1.IsFocused);
  364. Assert.True(target2.IsFocused);
  365. target1.Focus();
  366. Assert.False(target2.IsFocused);
  367. Assert.True(target1.IsFocused);
  368. Assert.Equal(1, gfcount);
  369. Assert.Equal(1, lfcount);
  370. }
  371. }
  372. [Fact]
  373. public void Setting_Bound_Text_To_Null_Works()
  374. {
  375. using (UnitTestApplication.Start(Services))
  376. {
  377. var source = new Class1 { Bar = "bar" };
  378. var target = new TextBox { DataContext = source };
  379. target.Bind(TextBox.TextProperty, new Binding("Bar"));
  380. Assert.Equal("bar", target.Text);
  381. source.Bar = null;
  382. Assert.Null(target.Text);
  383. }
  384. }
  385. private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
  386. focusManager: new FocusManager(),
  387. keyboardDevice: () => new KeyboardDevice(),
  388. keyboardNavigation: new KeyboardNavigationHandler(),
  389. inputManager: new InputManager(),
  390. standardCursorFactory: Mock.Of<IStandardCursorFactory>());
  391. private static TestServices Services => TestServices.MockThreadingInterface.With(
  392. standardCursorFactory: Mock.Of<IStandardCursorFactory>());
  393. private IControlTemplate CreateTemplate()
  394. {
  395. return new FuncControlTemplate<TextBox>((control, scope) =>
  396. new TextPresenter
  397. {
  398. Name = "PART_TextPresenter",
  399. [!!TextPresenter.TextProperty] = new Binding
  400. {
  401. Path = "Text",
  402. Mode = BindingMode.TwoWay,
  403. Priority = BindingPriority.TemplatedParent,
  404. RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
  405. },
  406. }.RegisterInNameScope(scope));
  407. }
  408. private void RaiseKeyEvent(TextBox textBox, Key key, KeyModifiers inputModifiers)
  409. {
  410. textBox.RaiseEvent(new KeyEventArgs
  411. {
  412. RoutedEvent = InputElement.KeyDownEvent,
  413. KeyModifiers = inputModifiers,
  414. Key = key
  415. });
  416. }
  417. private void RaiseTextEvent(TextBox textBox, string text)
  418. {
  419. textBox.RaiseEvent(new TextInputEventArgs
  420. {
  421. RoutedEvent = InputElement.TextInputEvent,
  422. Text = text
  423. });
  424. }
  425. private class Class1 : NotifyingBase
  426. {
  427. private int _foo;
  428. private string _bar;
  429. public int Foo
  430. {
  431. get { return _foo; }
  432. set { _foo = value; RaisePropertyChanged(); }
  433. }
  434. public string Bar
  435. {
  436. get { return _bar; }
  437. set { _bar = value; RaisePropertyChanged(); }
  438. }
  439. }
  440. }
  441. }