TextBoxTests_Input.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. #nullable enable
  2. using Avalonia.Controls.Presenters;
  3. using Avalonia.Controls.Templates;
  4. using Avalonia.Data;
  5. using Avalonia.Harfbuzz;
  6. using Avalonia.Headless;
  7. using Avalonia.Input;
  8. using Avalonia.Input.Platform;
  9. using Avalonia.Platform;
  10. using Avalonia.Threading;
  11. using Avalonia.UnitTests;
  12. using Moq;
  13. using Xunit;
  14. namespace Avalonia.Controls.UnitTests
  15. {
  16. public class TextBoxTests_Input : ScopedTestBase
  17. {
  18. [Fact]
  19. public void Touch_Tap_Moves_Caret()
  20. {
  21. using (UnitTestApplication.Start(Services))
  22. {
  23. var target = new TextBox
  24. {
  25. Template = CreateTemplate(),
  26. Text = "12 12345678"
  27. };
  28. var root = new TestRoot()
  29. {
  30. Child = target
  31. };
  32. target.ApplyTemplate();
  33. root.LayoutManager.ExecuteInitialLayoutPass();
  34. var touch = new TouchTestHelper();
  35. Assert.Equal(target.CaretIndex, 0);
  36. // Move to index 8
  37. touch.Down(target, new Point(50, 0));
  38. touch.Up(target, new Point(50, 0));
  39. Assert.Equal(target.CaretIndex, 8);
  40. }
  41. }
  42. [Fact]
  43. public void Touch_Double_Tap_Selects_Word()
  44. {
  45. using (UnitTestApplication.Start(Services))
  46. {
  47. var target = new TextBox
  48. {
  49. Template = CreateTemplate(),
  50. Text = "12 12345678"
  51. };
  52. var root = new TestRoot()
  53. {
  54. Child = target
  55. };
  56. target.ApplyTemplate();
  57. root.LayoutManager.ExecuteInitialLayoutPass();
  58. var touch = new TouchTestHelper();
  59. Assert.Equal(target.CaretIndex, 0);
  60. // Move to index 8
  61. touch.Down(target, new Point(50, 0));
  62. touch.Up(target, new Point(50, 0));
  63. // Double tap
  64. touch.Down(target, new Point(50, 0));
  65. touch.Up(target, new Point(50, 0));
  66. Assert.Equal(target.SelectionStart, 3);
  67. Assert.Equal(target.SelectionEnd, 11);
  68. Assert.Equal(target.CaretIndex, 8);
  69. }
  70. }
  71. [Fact]
  72. public void Touch_Hold_Selects_Word()
  73. {
  74. using (UnitTestApplication.Start(Services))
  75. {
  76. var target = new TextBox
  77. {
  78. Template = CreateTemplate(),
  79. Text = "12 12345678"
  80. };
  81. var root = new TestRoot()
  82. {
  83. Child = target
  84. };
  85. target.ApplyTemplate();
  86. root.LayoutManager.ExecuteInitialLayoutPass();
  87. var touch = new TouchTestHelper();
  88. Assert.Equal(target.CaretIndex, 0);
  89. // Move to index 8
  90. touch.Down(target, new Point(50, 0));
  91. var timer = Assert.Single(Dispatcher.SnapshotTimersForUnitTests());
  92. timer.ForceFire();
  93. touch.Up(target, new Point(50, 0));
  94. Assert.Equal(target.SelectionStart, 3);
  95. Assert.Equal(target.SelectionEnd, 11);
  96. Assert.Equal(target.CaretIndex, 8);
  97. }
  98. }
  99. [Fact]
  100. public void Touch_Hold_On_Selection_Requests_Context()
  101. {
  102. using (UnitTestApplication.Start(Services))
  103. {
  104. var target = new TextBox
  105. {
  106. Template = CreateTemplate(),
  107. Text = "12 12345678"
  108. };
  109. var root = new TestRoot()
  110. {
  111. Child = target
  112. };
  113. target.ApplyTemplate();
  114. bool requested = false;
  115. target.SelectionStart = 3;
  116. target.SelectionEnd = 11;
  117. target.ContextRequested += Target_ContextRequested;
  118. root.LayoutManager.ExecuteInitialLayoutPass();
  119. var touch = new TouchTestHelper();
  120. Assert.Equal(target.CaretIndex, 0);
  121. // Move to index 8
  122. touch.Down(target, new Point(50, 0));
  123. var timer = Assert.Single(Dispatcher.SnapshotTimersForUnitTests());
  124. timer.ForceFire();
  125. touch.Up(target, new Point(50, 0));
  126. Assert.True(requested);
  127. void Target_ContextRequested(object? sender, ContextRequestedEventArgs e)
  128. {
  129. requested = true;
  130. }
  131. }
  132. }
  133. private static TestServices Services => TestServices.MockThreadingInterface.With(
  134. standardCursorFactory: Mock.Of<ICursorFactory>(),
  135. renderInterface: new HeadlessPlatformRenderInterface(),
  136. textShaperImpl: new HarfBuzzTextShaper(),
  137. fontManagerImpl: new TestFontManager(),
  138. assetLoader: new StandardAssetLoader());
  139. internal static IControlTemplate CreateTemplate()
  140. {
  141. return new FuncControlTemplate<TextBox>((control, scope) =>
  142. new ScrollViewer
  143. {
  144. Name = "PART_ScrollViewer",
  145. Template = new FuncControlTemplate<ScrollViewer>(ScrollViewerTests.CreateTemplate),
  146. Content = new TextPresenter
  147. {
  148. Name = "PART_TextPresenter",
  149. [!!TextPresenter.TextProperty] = new Binding
  150. {
  151. Path = nameof(TextPresenter.Text),
  152. Mode = BindingMode.TwoWay,
  153. Priority = BindingPriority.Template,
  154. RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
  155. },
  156. [!!TextPresenter.CaretIndexProperty] = new Binding
  157. {
  158. Path = nameof(TextPresenter.CaretIndex),
  159. Mode = BindingMode.TwoWay,
  160. Priority = BindingPriority.Template,
  161. RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
  162. }
  163. }.RegisterInNameScope(scope)
  164. }.RegisterInNameScope(scope));
  165. }
  166. private static void RaiseKeyEvent(TextBox textBox, Key key, KeyModifiers inputModifiers)
  167. {
  168. textBox.RaiseEvent(new KeyEventArgs
  169. {
  170. RoutedEvent = InputElement.KeyDownEvent,
  171. KeyModifiers = inputModifiers,
  172. Key = key
  173. });
  174. }
  175. private static void RaiseTextEvent(TextBox textBox, string text)
  176. {
  177. textBox.RaiseEvent(new TextInputEventArgs
  178. {
  179. RoutedEvent = InputElement.TextInputEvent,
  180. Text = text
  181. });
  182. }
  183. private class Class1 : NotifyingBase
  184. {
  185. private int _foo;
  186. private string? _bar;
  187. public int Foo
  188. {
  189. get { return _foo; }
  190. set { _foo = value; RaisePropertyChanged(); }
  191. }
  192. public string? Bar
  193. {
  194. get { return _bar; }
  195. set { _bar = value; RaisePropertyChanged(); }
  196. }
  197. }
  198. private class TestTopLevel(ITopLevelImpl impl) : TopLevel(impl)
  199. {
  200. }
  201. private static Mock<ITopLevelImpl> CreateMockTopLevelImpl()
  202. {
  203. var clipboard = new Mock<ITopLevelImpl>();
  204. clipboard.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor());
  205. clipboard.Setup(r => r.TryGetFeature(typeof(IClipboard)))
  206. .Returns(new Clipboard(new HeadlessClipboardImplStub()));
  207. clipboard.SetupGet(x => x.RenderScaling).Returns(1);
  208. return clipboard;
  209. }
  210. private static FuncControlTemplate<TestTopLevel> CreateTopLevelTemplate()
  211. {
  212. return new FuncControlTemplate<TestTopLevel>((x, scope) =>
  213. new ContentPresenter
  214. {
  215. Name = "PART_ContentPresenter",
  216. [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty],
  217. }.RegisterInNameScope(scope));
  218. }
  219. private class TestContextMenu : ContextMenu
  220. {
  221. public TestContextMenu()
  222. {
  223. IsOpen = true;
  224. }
  225. }
  226. }
  227. }