ContextMenuTests.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. using System;
  2. using Avalonia.Controls.Primitives;
  3. using Avalonia.Input;
  4. using Avalonia.Markup.Xaml;
  5. using Avalonia.Platform;
  6. using Avalonia.Rendering;
  7. using Avalonia.UnitTests;
  8. using Avalonia.VisualTree;
  9. using Moq;
  10. using Xunit;
  11. namespace Avalonia.Controls.UnitTests
  12. {
  13. public class ContextMenuTests
  14. {
  15. private Mock<IPopupImpl> popupImpl;
  16. private MouseTestHelper _mouse = new MouseTestHelper();
  17. [Fact]
  18. public void Opening_Raises_Single_Opened_Event()
  19. {
  20. using (Application())
  21. {
  22. var sut = new ContextMenu();
  23. var target = new Panel
  24. {
  25. ContextMenu = sut
  26. };
  27. var window = new Window { Content = target };
  28. window.ApplyTemplate();
  29. window.Presenter.ApplyTemplate();
  30. int openedCount = 0;
  31. sut.MenuOpened += (sender, args) =>
  32. {
  33. openedCount++;
  34. };
  35. sut.Open(target);
  36. Assert.Equal(1, openedCount);
  37. }
  38. }
  39. [Fact]
  40. public void Open_Should_Use_Default_Control()
  41. {
  42. using (Application())
  43. {
  44. var sut = new ContextMenu();
  45. var target = new Panel
  46. {
  47. ContextMenu = sut
  48. };
  49. var window = new Window { Content = target };
  50. window.ApplyTemplate();
  51. window.Presenter.ApplyTemplate();
  52. bool opened = false;
  53. sut.MenuOpened += (sender, args) =>
  54. {
  55. opened = true;
  56. };
  57. sut.Open();
  58. Assert.True(opened);
  59. }
  60. }
  61. [Fact]
  62. public void Open_Should_Raise_Exception_If_AlreadyDetached()
  63. {
  64. using (Application())
  65. {
  66. var sut = new ContextMenu();
  67. var target = new Panel
  68. {
  69. ContextMenu = sut
  70. };
  71. var window = new Window { Content = target };
  72. window.ApplyTemplate();
  73. window.Presenter.ApplyTemplate();
  74. target.ContextMenu = null;
  75. Assert.ThrowsAny<Exception>(()=> sut.Open());
  76. }
  77. }
  78. [Fact]
  79. public void Closing_Raises_Single_Closed_Event()
  80. {
  81. using (Application())
  82. {
  83. var sut = new ContextMenu();
  84. var target = new Panel
  85. {
  86. ContextMenu = sut
  87. };
  88. var window = new Window { Content = target };
  89. window.ApplyTemplate();
  90. window.Presenter.ApplyTemplate();
  91. sut.Open(target);
  92. int closedCount = 0;
  93. sut.MenuClosed += (sender, args) =>
  94. {
  95. closedCount++;
  96. };
  97. sut.Close();
  98. Assert.Equal(1, closedCount);
  99. }
  100. }
  101. [Fact]
  102. public void Cancel_Light_Dismiss_Closing_Keeps_Flyout_Open()
  103. {
  104. using (Application())
  105. {
  106. popupImpl.Setup(x => x.Show(true)).Verifiable();
  107. popupImpl.Setup(x => x.Hide()).Verifiable();
  108. var window = PreparedWindow();
  109. window.Width = 100;
  110. window.Height = 100;
  111. var button = new Button
  112. {
  113. Height = 10,
  114. Width = 10,
  115. HorizontalAlignment = Layout.HorizontalAlignment.Left,
  116. VerticalAlignment = Layout.VerticalAlignment.Top
  117. };
  118. window.Content = button;
  119. window.ApplyTemplate();
  120. window.Show();
  121. var tracker = 0;
  122. var c = new ContextMenu();
  123. c.ContextMenuClosing += (s, e) =>
  124. {
  125. tracker++;
  126. e.Cancel = true;
  127. };
  128. button.ContextMenu = c;
  129. c.Open(button);
  130. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  131. _mouse.Down(overlay, MouseButton.Left, new Point(90, 90));
  132. _mouse.Up(button, MouseButton.Left, new Point(90, 90));
  133. Assert.Equal(1, tracker);
  134. Assert.True(c.IsOpen);
  135. popupImpl.Verify(x => x.Hide(), Times.Never);
  136. popupImpl.Verify(x => x.Show(true), Times.Exactly(1));
  137. }
  138. }
  139. [Fact]
  140. public void Light_Dismiss_Closes_Flyout()
  141. {
  142. using (Application())
  143. {
  144. popupImpl.Setup(x => x.Show(true)).Verifiable();
  145. popupImpl.Setup(x => x.Hide()).Verifiable();
  146. var window = PreparedWindow();
  147. window.Width = 100;
  148. window.Height = 100;
  149. var button = new Button
  150. {
  151. Height = 10,
  152. Width = 10,
  153. HorizontalAlignment = Layout.HorizontalAlignment.Left,
  154. VerticalAlignment = Layout.VerticalAlignment.Top
  155. };
  156. window.Content = button;
  157. window.ApplyTemplate();
  158. window.Show();
  159. var c = new ContextMenu();
  160. c.PlacementMode = PlacementMode.Bottom;
  161. c.Open(button);
  162. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  163. _mouse.Down(overlay, MouseButton.Left, new Point(90, 90));
  164. _mouse.Up(button, MouseButton.Left, new Point(90, 90));
  165. Assert.False(c.IsOpen);
  166. popupImpl.Verify(x => x.Hide(), Times.Exactly(1));
  167. popupImpl.Verify(x => x.Show(true), Times.Exactly(1));
  168. }
  169. }
  170. [Fact]
  171. public void Clicking_On_Control_Toggles_ContextMenu()
  172. {
  173. using (Application())
  174. {
  175. popupImpl.Setup(x => x.Show(true)).Verifiable();
  176. popupImpl.Setup(x => x.Hide()).Verifiable();
  177. var sut = new ContextMenu();
  178. var target = new Panel
  179. {
  180. ContextMenu = sut
  181. };
  182. var window = PreparedWindow(target);
  183. window.ApplyTemplate();
  184. window.Presenter.ApplyTemplate();
  185. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  186. _mouse.Click(target, MouseButton.Right);
  187. Assert.True(sut.IsOpen);
  188. _mouse.Down(overlay);
  189. _mouse.Up(target);
  190. Assert.False(sut.IsOpen);
  191. popupImpl.Verify(x => x.Show(true), Times.Once);
  192. popupImpl.Verify(x => x.Hide(), Times.Once);
  193. }
  194. }
  195. [Fact]
  196. public void Right_Clicking_On_Control_Twice_Re_Opens_ContextMenu()
  197. {
  198. using (Application())
  199. {
  200. popupImpl.Setup(x => x.Show(true)).Verifiable();
  201. popupImpl.Setup(x => x.Hide()).Verifiable();
  202. var sut = new ContextMenu();
  203. var target = new Panel
  204. {
  205. ContextMenu = sut
  206. };
  207. var window = PreparedWindow(target);
  208. window.ApplyTemplate();
  209. window.Presenter.ApplyTemplate();
  210. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  211. _mouse.Click(target, MouseButton.Right);
  212. Assert.True(sut.IsOpen);
  213. _mouse.Down(overlay, MouseButton.Right);
  214. _mouse.Up(target, MouseButton.Right);
  215. Assert.True(sut.IsOpen);
  216. popupImpl.Verify(x => x.Hide(), Times.Once);
  217. popupImpl.Verify(x => x.Show(true), Times.Exactly(2));
  218. }
  219. }
  220. [Fact]
  221. public void Context_Menu_Can_Be_Shared_Between_Controls_Even_After_A_Control_Is_Removed_From_Visual_Tree()
  222. {
  223. using (Application())
  224. {
  225. var sut = new ContextMenu();
  226. var target1 = new Panel
  227. {
  228. ContextMenu = sut
  229. };
  230. var target2 = new Panel
  231. {
  232. ContextMenu = sut
  233. };
  234. var sp = new StackPanel { Children = { target1, target2 } };
  235. var window = new Window { Content = sp };
  236. window.ApplyTemplate();
  237. window.Presenter.ApplyTemplate();
  238. _mouse.Click(target1, MouseButton.Right);
  239. Assert.True(sut.IsOpen);
  240. sp.Children.Remove(target1);
  241. Assert.False(sut.IsOpen);
  242. _mouse.Click(target2, MouseButton.Right);
  243. Assert.True(sut.IsOpen);
  244. }
  245. }
  246. [Fact]
  247. public void Cancelling_Opening_Does_Not_Show_ContextMenu()
  248. {
  249. using (Application())
  250. {
  251. popupImpl.Setup(x => x.Show(true)).Verifiable();
  252. bool eventCalled = false;
  253. var sut = new ContextMenu();
  254. var target = new Panel
  255. {
  256. ContextMenu = sut
  257. };
  258. new Window { Content = target };
  259. sut.ContextMenuOpening += (c, e) => { eventCalled = true; e.Cancel = true; };
  260. _mouse.Click(target, MouseButton.Right);
  261. Assert.True(eventCalled);
  262. Assert.False(sut.IsOpen);
  263. popupImpl.Verify(x => x.Show(true), Times.Never);
  264. }
  265. }
  266. [Fact]
  267. public void Can_Set_Clear_ContextMenu_Property()
  268. {
  269. using (Application())
  270. {
  271. var target = new ContextMenu();
  272. var control = new Panel();
  273. control.ContextMenu = target;
  274. control.ContextMenu = null;
  275. }
  276. }
  277. [Fact]
  278. public void Context_Menu_In_Resources_Can_Be_Shared()
  279. {
  280. using (Application())
  281. {
  282. var xaml = @"
  283. <Window xmlns='https://github.com/avaloniaui'
  284. xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
  285. <Window.Resources>
  286. <ContextMenu x:Key='contextMenu'>
  287. <MenuItem>Foo</MenuItem>
  288. </ContextMenu>
  289. </Window.Resources>
  290. <StackPanel>
  291. <TextBlock Name='target1' ContextMenu='{StaticResource contextMenu}'/>
  292. <TextBlock Name='target2' ContextMenu='{StaticResource contextMenu}'/>
  293. </StackPanel>
  294. </Window>";
  295. var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
  296. var target1 = window.Find<TextBlock>("target1");
  297. var target2 = window.Find<TextBlock>("target2");
  298. var mouse = new MouseTestHelper();
  299. Assert.NotNull(target1.ContextMenu);
  300. Assert.NotNull(target2.ContextMenu);
  301. Assert.Same(target1.ContextMenu, target2.ContextMenu);
  302. window.Show();
  303. var menu = target1.ContextMenu;
  304. mouse.Click(target1, MouseButton.Right);
  305. Assert.True(menu.IsOpen);
  306. mouse.Click(target2, MouseButton.Right);
  307. Assert.True(menu.IsOpen);
  308. }
  309. }
  310. [Fact]
  311. public void Context_Menu_Can_Be_Set_In_Style()
  312. {
  313. using (Application())
  314. {
  315. var xaml = @"
  316. <Window xmlns='https://github.com/avaloniaui'
  317. xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
  318. <Window.Styles>
  319. <Style Selector='TextBlock'>
  320. <Setter Property='ContextMenu'>
  321. <ContextMenu>
  322. <MenuItem>Foo</MenuItem>
  323. </ContextMenu>
  324. </Setter>
  325. </Style>
  326. </Window.Styles>
  327. <StackPanel>
  328. <TextBlock Name='target1'/>
  329. <TextBlock Name='target2'/>
  330. </StackPanel>
  331. </Window>";
  332. var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
  333. var target1 = window.Find<TextBlock>("target1");
  334. var target2 = window.Find<TextBlock>("target2");
  335. var mouse = new MouseTestHelper();
  336. Assert.NotNull(target1.ContextMenu);
  337. Assert.NotNull(target2.ContextMenu);
  338. Assert.Same(target1.ContextMenu, target2.ContextMenu);
  339. window.Show();
  340. var menu = target1.ContextMenu;
  341. mouse.Click(target1, MouseButton.Right);
  342. Assert.True(menu.IsOpen);
  343. mouse.Click(target2, MouseButton.Right);
  344. Assert.True(menu.IsOpen);
  345. }
  346. }
  347. [Fact]
  348. public void Cancelling_Closing_Leaves_ContextMenuOpen()
  349. {
  350. using (Application())
  351. {
  352. popupImpl.Setup(x => x.Show(true)).Verifiable();
  353. popupImpl.Setup(x => x.Hide()).Verifiable();
  354. bool eventCalled = false;
  355. var sut = new ContextMenu();
  356. var target = new Panel
  357. {
  358. ContextMenu = sut
  359. };
  360. var window = PreparedWindow(target);
  361. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  362. sut.ContextMenuClosing += (c, e) => { eventCalled = true; e.Cancel = true; };
  363. window.Show();
  364. _mouse.Click(target, MouseButton.Right);
  365. Assert.True(sut.IsOpen);
  366. _mouse.Down(overlay, MouseButton.Right);
  367. _mouse.Up(target, MouseButton.Right);
  368. Assert.True(eventCalled);
  369. Assert.True(sut.IsOpen);
  370. popupImpl.Verify(x => x.Show(true), Times.Once());
  371. popupImpl.Verify(x => x.Hide(), Times.Never);
  372. }
  373. }
  374. private Window PreparedWindow(object content = null)
  375. {
  376. var renderer = new Mock<IRenderer>();
  377. var platform = AvaloniaLocator.Current.GetService<IWindowingPlatform>();
  378. var windowImpl = Mock.Get(platform.CreateWindow());
  379. windowImpl.Setup(x => x.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer.Object);
  380. var w = new Window(windowImpl.Object) { Content = content };
  381. w.ApplyTemplate();
  382. return w;
  383. }
  384. private IDisposable Application()
  385. {
  386. var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100));
  387. var screenImpl = new Mock<IScreenImpl>();
  388. screenImpl.Setup(x => x.ScreenCount).Returns(1);
  389. screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(1, screen, screen, true) });
  390. var windowImpl = MockWindowingPlatform.CreateWindowMock();
  391. popupImpl = MockWindowingPlatform.CreatePopupMock(windowImpl.Object);
  392. popupImpl.SetupGet(x => x.RenderScaling).Returns(1);
  393. windowImpl.Setup(x => x.CreatePopup()).Returns(popupImpl.Object);
  394. windowImpl.Setup(x => x.Screen).Returns(screenImpl.Object);
  395. var services = TestServices.StyledWindow.With(
  396. inputManager: new InputManager(),
  397. windowImpl: windowImpl.Object,
  398. windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object, x => popupImpl.Object));
  399. return UnitTestApplication.Start(services);
  400. }
  401. }
  402. }