HotKeyManagerTests.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. using System;
  2. using System.Collections.Generic;
  3. using Avalonia.Collections;
  4. using Avalonia.Controls.Presenters;
  5. using Avalonia.Controls.Templates;
  6. using Avalonia.Data;
  7. using Avalonia.Input;
  8. using Avalonia.Platform;
  9. using Avalonia.Styling;
  10. using Avalonia.UnitTests;
  11. using Moq;
  12. using Xunit;
  13. using Avalonia.Input.Raw;
  14. using Factory = System.Func<int, System.Action<object>, Avalonia.Controls.Window, Avalonia.AvaloniaObject>;
  15. using Avalonia.Threading;
  16. namespace Avalonia.Controls.UnitTests.Utils
  17. {
  18. public class HotKeyManagerTests
  19. {
  20. [Fact]
  21. public void HotKeyManager_Should_Register_And_Unregister_Key_Binding()
  22. {
  23. using (AvaloniaLocator.EnterScope())
  24. {
  25. var styler = new Mock<Styler>();
  26. AvaloniaLocator.CurrentMutable
  27. .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock())
  28. .Bind<IStyler>().ToConstant(styler.Object);
  29. var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control);
  30. var gesture2 = new KeyGesture(Key.B, KeyModifiers.Control);
  31. var tl = new Window();
  32. var button = new Button();
  33. tl.Content = button;
  34. tl.Template = CreateWindowTemplate();
  35. tl.ApplyTemplate();
  36. tl.Presenter.ApplyTemplate();
  37. HotKeyManager.SetHotKey(button, gesture1);
  38. Assert.Equal(gesture1, tl.KeyBindings[0].Gesture);
  39. HotKeyManager.SetHotKey(button, gesture2);
  40. Assert.Equal(gesture2, tl.KeyBindings[0].Gesture);
  41. tl.Content = null;
  42. tl.Presenter.ApplyTemplate();
  43. Assert.Empty(tl.KeyBindings);
  44. tl.Content = button;
  45. tl.Presenter.ApplyTemplate();
  46. Assert.Equal(gesture2, tl.KeyBindings[0].Gesture);
  47. HotKeyManager.SetHotKey(button, null);
  48. Assert.Empty(tl.KeyBindings);
  49. }
  50. }
  51. [Theory]
  52. [MemberData(nameof(ElementsFactory), parameters: true)]
  53. public void HotKeyManager_Should_Use_CommandParameter(string factoryName, Factory factory)
  54. {
  55. using (AvaloniaLocator.EnterScope())
  56. {
  57. var styler = new Mock<Styler>();
  58. var target = new KeyboardDevice();
  59. var commandResult = 0;
  60. var expectedParameter = 1;
  61. AvaloniaLocator.CurrentMutable
  62. .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock())
  63. .Bind<IStyler>().ToConstant(styler.Object);
  64. var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
  65. var action = new Action<object>(parameter =>
  66. {
  67. if (parameter is int value)
  68. {
  69. commandResult = value;
  70. }
  71. });
  72. var root = new Window();
  73. var element = factory(expectedParameter, action, root);
  74. root.Template = CreateWindowTemplate();
  75. root.ApplyTemplate();
  76. root.Presenter.ApplyTemplate();
  77. HotKeyManager.SetHotKey(element, gesture);
  78. target.ProcessRawEvent(new RawKeyEventArgs(target,
  79. 0,
  80. root,
  81. RawKeyEventType.KeyDown,
  82. Key.A,
  83. RawInputModifiers.Control));
  84. Assert.True(expectedParameter == commandResult, $"{factoryName} HotKey did not carry the CommandParameter.");
  85. }
  86. }
  87. [Theory]
  88. [MemberData(nameof(ElementsFactory), parameters: true)]
  89. public void HotKeyManager_Should_Do_Not_Executed_When_IsEnabled_False(string factoryName, Factory factory)
  90. {
  91. using (AvaloniaLocator.EnterScope())
  92. {
  93. var styler = new Mock<Styler>();
  94. var target = new KeyboardDevice();
  95. var isExecuted = false;
  96. AvaloniaLocator.CurrentMutable
  97. .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock())
  98. .Bind<IStyler>().ToConstant(styler.Object);
  99. var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
  100. var action = new Action<object>(parameter =>
  101. {
  102. isExecuted = true;
  103. });
  104. var root = new Window();
  105. var element = factory(0, action, root) as InputElement;
  106. element.IsEnabled = false;
  107. root.Template = CreateWindowTemplate();
  108. root.ApplyTemplate();
  109. root.Presenter.ApplyTemplate();
  110. HotKeyManager.SetHotKey(element, gesture);
  111. target.ProcessRawEvent(new RawKeyEventArgs(target,
  112. 0,
  113. root,
  114. RawKeyEventType.KeyDown,
  115. Key.A,
  116. RawInputModifiers.Control));
  117. Assert.True(isExecuted == false, $"{factoryName} Execution raised when IsEnabled is false.");
  118. }
  119. }
  120. [Theory]
  121. [MemberData(nameof(ElementsFactory), parameters:false)]
  122. public void HotKeyManager_Should_Invoke_Event_Click_When_Command_Is_Null(string factoryName, Factory factory)
  123. {
  124. using (AvaloniaLocator.EnterScope())
  125. {
  126. var styler = new Mock<Styler>();
  127. var target = new KeyboardDevice();
  128. var clickExecutedCount = 0;
  129. AvaloniaLocator.CurrentMutable
  130. .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock())
  131. .Bind<IStyler>().ToConstant(styler.Object);
  132. var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
  133. void Clickable_Click(object sender, Interactivity.RoutedEventArgs e)
  134. {
  135. clickExecutedCount++;
  136. }
  137. var root = new Window();
  138. var element = factory(0, default, root) as InputElement;
  139. if (element is IClickableControl clickable)
  140. {
  141. clickable.Click += Clickable_Click;
  142. }
  143. root.Template = CreateWindowTemplate();
  144. root.ApplyTemplate();
  145. root.Presenter.ApplyTemplate();
  146. HotKeyManager.SetHotKey(element, gesture);
  147. target.ProcessRawEvent(new RawKeyEventArgs(target,
  148. 0,
  149. root,
  150. RawKeyEventType.KeyDown,
  151. Key.A,
  152. RawInputModifiers.Control));
  153. element.IsEnabled = false;
  154. target.ProcessRawEvent(new RawKeyEventArgs(target,
  155. 0,
  156. root,
  157. RawKeyEventType.KeyDown,
  158. Key.A,
  159. RawInputModifiers.Control));
  160. Assert.True(clickExecutedCount == 1, $"{factoryName} Execution raised when IsEnabled is false.");
  161. }
  162. }
  163. [Theory]
  164. [MemberData(nameof(ElementsFactory), parameters: true)]
  165. public void HotKeyManager_Should_Not_Invoke_Event_Click_When_Command_Is_Not_Null(string factoryName, Factory factory)
  166. {
  167. using (AvaloniaLocator.EnterScope())
  168. {
  169. var styler = new Mock<Styler>();
  170. var target = new KeyboardDevice();
  171. var clickExecutedCount = 0;
  172. var commandExecutedCount = 0;
  173. AvaloniaLocator.CurrentMutable
  174. .Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformMock())
  175. .Bind<IStyler>().ToConstant(styler.Object);
  176. var gesture = new KeyGesture(Key.A, KeyModifiers.Control);
  177. void DoExecute(object parameter)
  178. {
  179. commandExecutedCount++;
  180. }
  181. void Clickable_Click(object sender, Interactivity.RoutedEventArgs e)
  182. {
  183. clickExecutedCount++;
  184. }
  185. var root = new Window();
  186. var element = factory(0, DoExecute, root) as InputElement;
  187. if (element is IClickableControl clickable)
  188. {
  189. clickable.Click += Clickable_Click;
  190. }
  191. root.Template = CreateWindowTemplate();
  192. root.ApplyTemplate();
  193. root.Presenter.ApplyTemplate();
  194. HotKeyManager.SetHotKey(element, gesture);
  195. target.ProcessRawEvent(new RawKeyEventArgs(target,
  196. 0,
  197. root,
  198. RawKeyEventType.KeyDown,
  199. Key.A,
  200. RawInputModifiers.Control));
  201. element.IsEnabled = false;
  202. target.ProcessRawEvent(new RawKeyEventArgs(target,
  203. 0,
  204. root,
  205. RawKeyEventType.KeyDown,
  206. Key.A,
  207. RawInputModifiers.Control));
  208. Assert.True(commandExecutedCount == 1, $"{factoryName} Execution raised when IsEnabled is false.");
  209. Assert.True(clickExecutedCount == 0, $"{factoryName} Execution raised event Click.");
  210. }
  211. }
  212. public static TheoryData<string, Factory> ElementsFactory(bool withCommand) =>
  213. new TheoryData<string, Factory>()
  214. {
  215. {nameof(Button), withCommand ? MakeButton : MakeButtonWithoutCommand},
  216. {nameof(MenuItem),withCommand ? MakeMenu : MakeMenuWithoutCommand},
  217. };
  218. private static AvaloniaObject MakeMenu(int expectedParameter, Action<object> action, Window root)
  219. {
  220. var menuitem = new MenuItem()
  221. {
  222. Command = new Command(action),
  223. CommandParameter = expectedParameter,
  224. };
  225. var rootMenu = new Menu();
  226. rootMenu.Items = new[] { menuitem };
  227. root.Content = rootMenu;
  228. return menuitem;
  229. }
  230. private static AvaloniaObject MakeButton(int expectedParameter, Action<object> action, Window root)
  231. {
  232. var button = new Button()
  233. {
  234. Command = new Command(action),
  235. CommandParameter = expectedParameter,
  236. };
  237. root.Content = button;
  238. return button;
  239. }
  240. private static AvaloniaObject MakeMenuWithoutCommand(int expectedParameter, Action<object> action, Window root)
  241. {
  242. var menuitem = new MenuItem()
  243. {
  244. };
  245. var rootMenu = new Menu();
  246. rootMenu.Items = new[] { menuitem };
  247. root.Content = rootMenu;
  248. return menuitem;
  249. }
  250. private static AvaloniaObject MakeButtonWithoutCommand(int expectedParameter, Action<object> action, Window root)
  251. {
  252. var button = new Button()
  253. {
  254. };
  255. root.Content = button;
  256. return button;
  257. }
  258. private FuncControlTemplate CreateWindowTemplate()
  259. {
  260. return new FuncControlTemplate<Window>((parent, scope) =>
  261. {
  262. return new ContentPresenter
  263. {
  264. Name = "PART_ContentPresenter",
  265. [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
  266. }.RegisterInNameScope(scope);
  267. });
  268. }
  269. class Command : System.Windows.Input.ICommand
  270. {
  271. private readonly Action<object> _execeute;
  272. #pragma warning disable 67 // Event not used
  273. public event EventHandler CanExecuteChanged;
  274. #pragma warning restore 67 // Event not used
  275. public Command(Action<object> execeute)
  276. {
  277. _execeute = execeute;
  278. }
  279. public bool CanExecute(object parameter) => true;
  280. public void Execute(object parameter) => _execeute?.Invoke(parameter);
  281. }
  282. }
  283. }