ContextMenuTests.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  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 Moq;
  9. using Xunit;
  10. namespace Avalonia.Controls.UnitTests
  11. {
  12. public class ContextMenuTests
  13. {
  14. private Mock<IPopupImpl> popupImpl;
  15. private MouseTestHelper _mouse = new MouseTestHelper();
  16. [Fact]
  17. public void ContextRequested_Opens_ContextMenu()
  18. {
  19. using (Application())
  20. {
  21. var sut = new ContextMenu();
  22. var target = new Panel
  23. {
  24. ContextMenu = sut
  25. };
  26. var window = new Window { Content = target };
  27. window.ApplyStyling();
  28. window.ApplyTemplate();
  29. ((Control)window.Presenter).ApplyTemplate();
  30. int openedCount = 0;
  31. sut.MenuOpened += (sender, args) =>
  32. {
  33. openedCount++;
  34. };
  35. target.RaiseEvent(new ContextRequestedEventArgs());
  36. Assert.True(sut.IsOpen);
  37. Assert.Equal(1, openedCount);
  38. }
  39. }
  40. [Fact]
  41. public void ContextMenu_Is_Opened_When_ContextFlyout_Is_Also_Set()
  42. {
  43. // We have this test for backwards compatability with the code that already sets custom ContextMenu.
  44. using (Application())
  45. {
  46. var sut = new ContextMenu();
  47. var flyout = new Flyout();
  48. var target = new Panel
  49. {
  50. ContextMenu = sut,
  51. ContextFlyout = flyout
  52. };
  53. var window = new Window { Content = target };
  54. window.ApplyStyling();
  55. window.ApplyTemplate();
  56. ((Control)window.Presenter).ApplyTemplate();
  57. target.RaiseEvent(new ContextRequestedEventArgs());
  58. Assert.True(sut.IsOpen);
  59. Assert.False(flyout.IsOpen);
  60. }
  61. }
  62. [Fact]
  63. public void KeyUp_Raised_On_Target_Opens_ContextFlyout()
  64. {
  65. using (Application())
  66. {
  67. var sut = new ContextMenu();
  68. var target = new Panel
  69. {
  70. ContextMenu = sut
  71. };
  72. var contextRequestedCount = 0;
  73. target.AddHandler(Control.ContextRequestedEvent, (s, a) => contextRequestedCount++, Interactivity.RoutingStrategies.Tunnel);
  74. var window = PreparedWindow(target);
  75. window.Show();
  76. target.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyUpEvent, Key = Key.Apps, Source = window });
  77. Assert.True(sut.IsOpen);
  78. Assert.Equal(1, contextRequestedCount);
  79. }
  80. }
  81. [Fact]
  82. public void KeyUp_Raised_On_Flyout_Closes_Opened_ContextMenu()
  83. {
  84. using (Application())
  85. {
  86. var sut = new ContextMenu();
  87. var target = new Panel
  88. {
  89. ContextMenu = sut
  90. };
  91. var window = PreparedWindow(target);
  92. window.Show();
  93. target.RaiseEvent(new ContextRequestedEventArgs());
  94. Assert.True(sut.IsOpen);
  95. sut.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyUpEvent, Key = Key.Apps, Source = window });
  96. Assert.False(sut.IsOpen);
  97. }
  98. }
  99. [Fact]
  100. public void Opening_Raises_Single_Opened_Event()
  101. {
  102. using (Application())
  103. {
  104. var sut = new ContextMenu();
  105. var target = new Panel
  106. {
  107. ContextMenu = sut
  108. };
  109. var window = new Window { Content = target };
  110. window.ApplyStyling();
  111. window.ApplyTemplate();
  112. ((Control)window.Presenter).ApplyTemplate();
  113. int openedCount = 0;
  114. sut.MenuOpened += (sender, args) =>
  115. {
  116. openedCount++;
  117. };
  118. sut.Open(target);
  119. Assert.Equal(1, openedCount);
  120. }
  121. }
  122. [Fact]
  123. public void Open_Should_Use_Default_Control()
  124. {
  125. using (Application())
  126. {
  127. var sut = new ContextMenu();
  128. var target = new Panel
  129. {
  130. ContextMenu = sut
  131. };
  132. var window = new Window { Content = target };
  133. window.ApplyStyling();
  134. window.ApplyTemplate();
  135. ((Control)window.Presenter).ApplyTemplate();
  136. bool opened = false;
  137. sut.MenuOpened += (sender, args) =>
  138. {
  139. opened = true;
  140. };
  141. sut.Open();
  142. Assert.True(opened);
  143. }
  144. }
  145. [Fact]
  146. public void Open_Should_Raise_Exception_If_AlreadyDetached()
  147. {
  148. using (Application())
  149. {
  150. var sut = new ContextMenu();
  151. var target = new Panel
  152. {
  153. ContextMenu = sut
  154. };
  155. var window = new Window { Content = target };
  156. window.ApplyStyling();
  157. window.ApplyTemplate();
  158. ((Control)window.Presenter).ApplyTemplate();
  159. target.ContextMenu = null;
  160. Assert.ThrowsAny<Exception>(()=> sut.Open());
  161. }
  162. }
  163. [Fact]
  164. public void Closing_Raises_Single_Closed_Event()
  165. {
  166. using (Application())
  167. {
  168. var sut = new ContextMenu();
  169. var target = new Panel
  170. {
  171. ContextMenu = sut
  172. };
  173. var window = new Window { Content = target };
  174. window.ApplyStyling();
  175. window.ApplyTemplate();
  176. ((Control)window.Presenter).ApplyTemplate();
  177. sut.Open(target);
  178. int closedCount = 0;
  179. sut.MenuClosed += (sender, args) =>
  180. {
  181. closedCount++;
  182. };
  183. sut.Close();
  184. Assert.Equal(1, closedCount);
  185. }
  186. }
  187. [Fact]
  188. public void Cancel_Light_Dismiss_Closing_Keeps_Flyout_Open()
  189. {
  190. using (Application())
  191. {
  192. popupImpl.Setup(x => x.Show(true, false)).Verifiable();
  193. popupImpl.Setup(x => x.Hide()).Verifiable();
  194. var window = PreparedWindow();
  195. window.Width = 100;
  196. window.Height = 100;
  197. var button = new Button
  198. {
  199. Height = 10,
  200. Width = 10,
  201. HorizontalAlignment = Layout.HorizontalAlignment.Left,
  202. VerticalAlignment = Layout.VerticalAlignment.Top
  203. };
  204. window.Content = button;
  205. window.ApplyTemplate();
  206. window.Show();
  207. var tracker = 0;
  208. var c = new ContextMenu();
  209. c.ContextMenuClosing += (s, e) =>
  210. {
  211. tracker++;
  212. e.Cancel = true;
  213. };
  214. button.ContextMenu = c;
  215. c.Open(button);
  216. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  217. _mouse.Down(overlay, MouseButton.Left, new Point(90, 90));
  218. _mouse.Up(button, MouseButton.Left, new Point(90, 90));
  219. Assert.Equal(1, tracker);
  220. Assert.True(c.IsOpen);
  221. popupImpl.Verify(x => x.Hide(), Times.Never);
  222. popupImpl.Verify(x => x.Show(true, false), Times.Exactly(1));
  223. }
  224. }
  225. [Fact]
  226. public void Light_Dismiss_Closes_Flyout()
  227. {
  228. using (Application())
  229. {
  230. popupImpl.Setup(x => x.Show(true, false)).Verifiable();
  231. popupImpl.Setup(x => x.Hide()).Verifiable();
  232. var window = PreparedWindow();
  233. window.Width = 100;
  234. window.Height = 100;
  235. var button = new Button
  236. {
  237. Height = 10,
  238. Width = 10,
  239. HorizontalAlignment = Layout.HorizontalAlignment.Left,
  240. VerticalAlignment = Layout.VerticalAlignment.Top
  241. };
  242. window.Content = button;
  243. window.ApplyTemplate();
  244. window.Show();
  245. var c = new ContextMenu();
  246. c.Placement = PlacementMode.Bottom;
  247. c.Open(button);
  248. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  249. _mouse.Down(overlay, MouseButton.Left, new Point(90, 90));
  250. _mouse.Up(button, MouseButton.Left, new Point(90, 90));
  251. Assert.False(c.IsOpen);
  252. popupImpl.Verify(x => x.Hide(), Times.Exactly(1));
  253. popupImpl.Verify(x => x.Show(true, false), Times.Exactly(1));
  254. }
  255. }
  256. [Fact]
  257. public void Clicking_On_Control_Toggles_ContextMenu()
  258. {
  259. using (Application())
  260. {
  261. popupImpl.Setup(x => x.Show(true, false)).Verifiable();
  262. popupImpl.Setup(x => x.Hide()).Verifiable();
  263. var sut = new ContextMenu();
  264. var target = new Panel
  265. {
  266. ContextMenu = sut
  267. };
  268. var window = PreparedWindow(target);
  269. window.Show();
  270. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  271. _mouse.Click(target, MouseButton.Right);
  272. Assert.True(sut.IsOpen);
  273. _mouse.Down(overlay);
  274. _mouse.Up(target);
  275. Assert.False(sut.IsOpen);
  276. popupImpl.Verify(x => x.Show(true, false), Times.Once);
  277. popupImpl.Verify(x => x.Hide(), Times.Once);
  278. }
  279. }
  280. [Fact]
  281. public void Right_Clicking_On_Control_Twice_Re_Opens_ContextMenu()
  282. {
  283. using (Application())
  284. {
  285. popupImpl.Setup(x => x.Show(true, false)).Verifiable();
  286. popupImpl.Setup(x => x.Hide()).Verifiable();
  287. var sut = new ContextMenu();
  288. var target = new Panel
  289. {
  290. ContextMenu = sut
  291. };
  292. var window = PreparedWindow(target);
  293. window.Show();
  294. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  295. _mouse.Click(target, MouseButton.Right);
  296. Assert.True(sut.IsOpen);
  297. _mouse.Down(overlay, MouseButton.Right);
  298. _mouse.Up(target, MouseButton.Right);
  299. Assert.True(sut.IsOpen);
  300. popupImpl.Verify(x => x.Hide(), Times.Once);
  301. popupImpl.Verify(x => x.Show(true, false), Times.Exactly(2));
  302. }
  303. }
  304. [Fact]
  305. public void Context_Menu_Can_Be_Shared_Between_Controls_Even_After_A_Control_Is_Removed_From_Visual_Tree()
  306. {
  307. using (Application())
  308. {
  309. var sut = new ContextMenu();
  310. var target1 = new Panel
  311. {
  312. ContextMenu = sut
  313. };
  314. var target2 = new Panel
  315. {
  316. ContextMenu = sut
  317. };
  318. var sp = new StackPanel { Children = { target1, target2 } };
  319. var window = new Window { Content = sp };
  320. window.ApplyStyling();
  321. window.ApplyTemplate();
  322. ((Control)window.Presenter).ApplyTemplate();
  323. _mouse.Click(target1, MouseButton.Right);
  324. Assert.True(sut.IsOpen);
  325. sp.Children.Remove(target1);
  326. Assert.False(sut.IsOpen);
  327. _mouse.Click(target2, MouseButton.Right);
  328. Assert.True(sut.IsOpen);
  329. }
  330. }
  331. [Fact]
  332. public void Cancelling_Opening_Does_Not_Show_ContextMenu()
  333. {
  334. using (Application())
  335. {
  336. popupImpl.Setup(x => x.Show(true, false)).Verifiable();
  337. bool eventCalled = false;
  338. var sut = new ContextMenu();
  339. var target = new Panel
  340. {
  341. ContextMenu = sut
  342. };
  343. new Window { Content = target };
  344. sut.ContextMenuOpening += (c, e) => { eventCalled = true; e.Cancel = true; };
  345. _mouse.Click(target, MouseButton.Right);
  346. Assert.True(eventCalled);
  347. Assert.False(sut.IsOpen);
  348. popupImpl.Verify(x => x.Show(true, false), Times.Never);
  349. }
  350. }
  351. [Fact]
  352. public void Can_Set_Clear_ContextMenu_Property()
  353. {
  354. using (Application())
  355. {
  356. var target = new ContextMenu();
  357. var control = new Panel();
  358. control.ContextMenu = target;
  359. control.ContextMenu = null;
  360. }
  361. }
  362. [Fact]
  363. public void Should_Reset_Popup_Parent_On_Target_Detached()
  364. {
  365. using (Application())
  366. {
  367. var userControl = new UserControl();
  368. var window = PreparedWindow(userControl);
  369. window.Show();
  370. var menu = new ContextMenu();
  371. userControl.ContextMenu = menu;
  372. menu.Open();
  373. var popup = Assert.IsType<Popup>(menu.Parent);
  374. Assert.NotNull(popup.Parent);
  375. window.Content = null;
  376. Assert.Null(popup.Parent);
  377. }
  378. }
  379. [Fact]
  380. public void Context_Menu_In_Resources_Can_Be_Shared()
  381. {
  382. using (Application())
  383. {
  384. var xaml = @"
  385. <Window xmlns='https://github.com/avaloniaui'
  386. xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
  387. <Window.Resources>
  388. <ContextMenu x:Key='contextMenu'>
  389. <MenuItem>Foo</MenuItem>
  390. </ContextMenu>
  391. </Window.Resources>
  392. <StackPanel>
  393. <TextBlock Name='target1' ContextMenu='{StaticResource contextMenu}'/>
  394. <TextBlock Name='target2' ContextMenu='{StaticResource contextMenu}'/>
  395. </StackPanel>
  396. </Window>";
  397. var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
  398. var target1 = window.Find<TextBlock>("target1");
  399. var target2 = window.Find<TextBlock>("target2");
  400. var mouse = new MouseTestHelper();
  401. Assert.NotNull(target1.ContextMenu);
  402. Assert.NotNull(target2.ContextMenu);
  403. Assert.Same(target1.ContextMenu, target2.ContextMenu);
  404. window.Show();
  405. var menu = target1.ContextMenu;
  406. mouse.Click(target1, MouseButton.Right);
  407. Assert.True(menu.IsOpen);
  408. mouse.Click(target2, MouseButton.Right);
  409. Assert.True(menu.IsOpen);
  410. }
  411. }
  412. [Fact]
  413. public void Context_Menu_Can_Be_Set_In_Style()
  414. {
  415. using (Application())
  416. {
  417. var xaml = @"
  418. <Window xmlns='https://github.com/avaloniaui'
  419. xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
  420. <Window.Styles>
  421. <Style Selector='TextBlock'>
  422. <Setter Property='ContextMenu'>
  423. <ContextMenu>
  424. <MenuItem>Foo</MenuItem>
  425. </ContextMenu>
  426. </Setter>
  427. </Style>
  428. </Window.Styles>
  429. <StackPanel>
  430. <TextBlock Name='target1'/>
  431. <TextBlock Name='target2'/>
  432. </StackPanel>
  433. </Window>";
  434. var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
  435. var target1 = window.Find<TextBlock>("target1");
  436. var target2 = window.Find<TextBlock>("target2");
  437. var mouse = new MouseTestHelper();
  438. Assert.NotNull(target1.ContextMenu);
  439. Assert.NotNull(target2.ContextMenu);
  440. Assert.Same(target1.ContextMenu, target2.ContextMenu);
  441. window.Show();
  442. var menu = target1.ContextMenu;
  443. mouse.Click(target1, MouseButton.Right);
  444. Assert.True(menu.IsOpen);
  445. mouse.Click(target2, MouseButton.Right);
  446. Assert.True(menu.IsOpen);
  447. }
  448. }
  449. [Fact]
  450. public void Cancelling_Closing_Leaves_ContextMenuOpen()
  451. {
  452. using (Application())
  453. {
  454. popupImpl.Setup(x => x.Show(true, false)).Verifiable();
  455. popupImpl.Setup(x => x.Hide()).Verifiable();
  456. bool eventCalled = false;
  457. var sut = new ContextMenu();
  458. var target = new Panel
  459. {
  460. ContextMenu = sut
  461. };
  462. var window = PreparedWindow(target);
  463. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  464. sut.ContextMenuClosing += (c, e) => { eventCalled = true; e.Cancel = true; };
  465. window.Show();
  466. _mouse.Click(target, MouseButton.Right);
  467. Assert.True(sut.IsOpen);
  468. _mouse.Down(overlay, MouseButton.Right);
  469. _mouse.Up(target, MouseButton.Right);
  470. Assert.True(eventCalled);
  471. Assert.True(sut.IsOpen);
  472. popupImpl.Verify(x => x.Show(true, false), Times.Once());
  473. popupImpl.Verify(x => x.Hide(), Times.Never);
  474. }
  475. }
  476. private static Window PreparedWindow(object content = null)
  477. {
  478. var renderer = RendererMocks.CreateRenderer();
  479. var platform = AvaloniaLocator.Current.GetRequiredService<IWindowingPlatform>();
  480. var windowImpl = Mock.Get(platform.CreateWindow());
  481. windowImpl.Setup(x => x.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer.Object);
  482. var w = new Window(windowImpl.Object) { Content = content };
  483. w.ApplyStyling();
  484. w.ApplyTemplate();
  485. ((Control)w.Presenter).ApplyTemplate();
  486. return w;
  487. }
  488. private IDisposable Application()
  489. {
  490. var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100));
  491. var screenImpl = new Mock<IScreenImpl>();
  492. screenImpl.Setup(x => x.ScreenCount).Returns(1);
  493. screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(1, screen, screen, true) });
  494. var windowImpl = MockWindowingPlatform.CreateWindowMock();
  495. popupImpl = MockWindowingPlatform.CreatePopupMock(windowImpl.Object);
  496. popupImpl.SetupGet(x => x.RenderScaling).Returns(1);
  497. windowImpl.Setup(x => x.CreatePopup()).Returns(popupImpl.Object);
  498. windowImpl.Setup(x => x.Screen).Returns(screenImpl.Object);
  499. var services = TestServices.StyledWindow.With(
  500. inputManager: new InputManager(),
  501. windowImpl: windowImpl.Object,
  502. windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object, x => popupImpl.Object));
  503. return UnitTestApplication.Start(services);
  504. }
  505. }
  506. }