FlyoutTests.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. using System;
  2. using System.ComponentModel;
  3. using System.Linq;
  4. using Avalonia.Controls.Primitives;
  5. using Avalonia.Input;
  6. using Avalonia.Markup.Xaml;
  7. using Avalonia.Media;
  8. using Avalonia.Platform;
  9. using Avalonia.Rendering;
  10. using Avalonia.Rendering.Composition;
  11. using Avalonia.UnitTests;
  12. using Avalonia.VisualTree;
  13. using Moq;
  14. using Xunit;
  15. namespace Avalonia.Controls.UnitTests
  16. {
  17. public class FlyoutTests : ScopedTestBase
  18. {
  19. protected bool UseOverlayPopups { get; set; }
  20. [Fact]
  21. public void Opening_Raises_Single_Opening_Event()
  22. {
  23. using (CreateServicesWithFocus())
  24. {
  25. var window = PreparedWindow();
  26. window.Show();
  27. int tracker = 0;
  28. Flyout f = new Flyout();
  29. f.Opening += (s, e) =>
  30. {
  31. tracker++;
  32. };
  33. f.ShowAt(window);
  34. Assert.Equal(1, tracker);
  35. Assert.True(f.IsOpen);
  36. }
  37. }
  38. [Fact]
  39. public void Opening_Raises_Single_Opened_Event()
  40. {
  41. using (CreateServicesWithFocus())
  42. {
  43. var window = PreparedWindow();
  44. window.Show();
  45. int tracker = 0;
  46. Flyout f = new Flyout();
  47. f.Opened += (s, e) =>
  48. {
  49. tracker++;
  50. };
  51. f.ShowAt(window);
  52. Assert.Equal(1, tracker);
  53. }
  54. }
  55. [Fact]
  56. public void Opening_Is_Cancellable()
  57. {
  58. using (CreateServicesWithFocus())
  59. {
  60. var window = PreparedWindow();
  61. window.Show();
  62. int tracker = 0;
  63. Flyout f = new Flyout();
  64. f.Opening += (s, e) =>
  65. {
  66. tracker++;
  67. if (e is CancelEventArgs cancelEventArgs)
  68. {
  69. cancelEventArgs.Cancel = true;
  70. }
  71. };
  72. f.ShowAt(window);
  73. Assert.Equal(1, tracker);
  74. Assert.False(f.IsOpen);
  75. }
  76. }
  77. [Fact]
  78. public void Closing_Raises_Single_Closing_Event()
  79. {
  80. using (CreateServicesWithFocus())
  81. {
  82. var window = PreparedWindow();
  83. window.Show();
  84. int tracker = 0;
  85. Flyout f = new Flyout();
  86. f.Closing += (s, e) =>
  87. {
  88. tracker++;
  89. };
  90. f.ShowAt(window);
  91. f.Hide();
  92. Assert.Equal(1, tracker);
  93. }
  94. }
  95. [Fact]
  96. public void Closing_Raises_Single_Closed_Event()
  97. {
  98. using (CreateServicesWithFocus())
  99. {
  100. var window = PreparedWindow();
  101. window.Show();
  102. int tracker = 0;
  103. Flyout f = new Flyout();
  104. f.Closed += (s, e) =>
  105. {
  106. tracker++;
  107. };
  108. f.ShowAt(window);
  109. f.Hide();
  110. Assert.Equal(1, tracker);
  111. }
  112. }
  113. [Fact]
  114. public void Cancel_Closing_Keeps_Flyout_Open()
  115. {
  116. using (CreateServicesWithFocus())
  117. {
  118. var window = PreparedWindow();
  119. window.Show();
  120. var tracker = 0;
  121. var f = new Flyout();
  122. f.Closing += (s, e) =>
  123. {
  124. tracker++;
  125. e.Cancel = true;
  126. };
  127. f.ShowAt(window);
  128. f.Hide();
  129. Assert.True(f.IsOpen);
  130. Assert.Equal(1, tracker);
  131. }
  132. }
  133. [Fact]
  134. public void Cancel_Light_Dismiss_Closing_Keeps_Flyout_Open()
  135. {
  136. using (CreateServicesWithFocus())
  137. {
  138. var window = PreparedWindow();
  139. window.Width = 100;
  140. window.Height = 100;
  141. var button = new Button
  142. {
  143. Height = 10,
  144. Width = 10,
  145. HorizontalAlignment = Layout.HorizontalAlignment.Left,
  146. VerticalAlignment = Layout.VerticalAlignment.Top
  147. };
  148. window.Content = button;
  149. window.Show();
  150. var tracker = 0;
  151. var f = new Flyout();
  152. f.Content = new Border { Width = 10, Height = 10 };
  153. f.Closing += (s, e) =>
  154. {
  155. tracker++;
  156. e.Cancel = true;
  157. };
  158. f.ShowAt(window);
  159. var e = CreatePointerPressedEventArgs(window, new Point(90, 90));
  160. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  161. overlay.RaiseEvent(e);
  162. Assert.Equal(1, tracker);
  163. Assert.True(f.IsOpen);
  164. }
  165. }
  166. [Fact]
  167. public void Light_Dismiss_Closes_Flyout()
  168. {
  169. using (CreateServicesWithFocus())
  170. {
  171. var window = PreparedWindow();
  172. window.Width = 100;
  173. window.Height = 100;
  174. var button = new Button
  175. {
  176. Height = 10,
  177. Width = 10,
  178. HorizontalAlignment = Layout.HorizontalAlignment.Left,
  179. VerticalAlignment = Layout.VerticalAlignment.Top
  180. };
  181. window.Content = button;
  182. window.Show();
  183. var f = new Flyout();
  184. f.Content = new Border { Width = 10, Height = 10 };
  185. f.ShowAt(window);
  186. var e = CreatePointerPressedEventArgs(window, new Point(90, 90));
  187. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  188. overlay.RaiseEvent(e);
  189. Assert.False(f.IsOpen);
  190. }
  191. }
  192. [Fact]
  193. public void Light_Dismiss_No_Event_Pass_Through_To_Button()
  194. {
  195. using (CreateServicesWithFocus())
  196. {
  197. var window = PreparedWindow();
  198. window.Width = 100;
  199. window.Height = 100;
  200. bool buttonClicked = false;
  201. var button = new Button()
  202. {
  203. ClickMode = ClickMode.Press
  204. };
  205. button.Click += (s, e) =>
  206. {
  207. buttonClicked = true;
  208. };
  209. window.Content = button;
  210. window.Show();
  211. var f = new Flyout();
  212. f.OverlayDismissEventPassThrough = false; // Focus of test
  213. f.Content = new Border { Width = 10, Height = 10 };
  214. f.ShowAt(window);
  215. var hitTester = new Mock<IHitTester>();
  216. window.HitTesterOverride = hitTester.Object;
  217. hitTester.Setup(x =>
  218. x.HitTestFirst(new Point(90, 90), window, It.IsAny<Func<Visual, bool>>()))
  219. .Returns(button);
  220. var e = CreatePointerPressedEventArgs(window, new Point(90, 90));
  221. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  222. overlay.RaiseEvent(e);
  223. Assert.False(f.IsOpen);
  224. Assert.False(buttonClicked); // Button is NOT clicked
  225. }
  226. }
  227. [Fact]
  228. public void Light_Dismiss_Event_Pass_Through_To_Button()
  229. {
  230. using (CreateServicesWithFocus())
  231. {
  232. var window = PreparedWindow();
  233. window.Width = 100;
  234. window.Height = 100;
  235. bool buttonClicked = false;
  236. var button = new Button()
  237. {
  238. ClickMode = ClickMode.Press
  239. };
  240. button.Click += (s, e) =>
  241. {
  242. buttonClicked = true;
  243. };
  244. window.Content = button;
  245. window.Show();
  246. var f = new Flyout();
  247. f.OverlayDismissEventPassThrough = true; // Focus of test
  248. f.Content = new Border { Width = 10, Height = 10 };
  249. f.ShowAt(window);
  250. var hitTester = new Mock<IHitTester>();
  251. window.HitTesterOverride = hitTester.Object;
  252. hitTester.Setup(x =>
  253. x.HitTestFirst(new Point(90, 90), window, It.IsAny<Func<Visual, bool>>()))
  254. .Returns(button);
  255. var e = CreatePointerPressedEventArgs(window, new Point(90, 90));
  256. var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
  257. overlay.RaiseEvent(e);
  258. Assert.False(f.IsOpen);
  259. Assert.True(buttonClicked); // Button is clicked
  260. }
  261. }
  262. [Fact]
  263. public void Flyout_Has_Uncancellable_Close_Before_Showing_On_A_Different_Target()
  264. {
  265. using (CreateServicesWithFocus())
  266. {
  267. var window = PreparedWindow();
  268. Button target1 = new Button();
  269. Button target2 = new Button();
  270. window.Content = new StackPanel
  271. {
  272. Children =
  273. {
  274. target1,
  275. target2
  276. }
  277. };
  278. window.Show();
  279. bool closingFired = false;
  280. bool closedFired = false;
  281. Flyout f = new Flyout();
  282. f.Closing += (s, e) =>
  283. {
  284. closingFired = true; //This shouldn't happen
  285. };
  286. f.Closed += (s, e) =>
  287. {
  288. closedFired = true;
  289. };
  290. f.ShowAt(target1);
  291. f.ShowAt(target2);
  292. Assert.False(closingFired);
  293. Assert.True(closedFired);
  294. }
  295. }
  296. [Fact]
  297. public void ShowMode_Standard_Attemps_Focus_Flyout_Content()
  298. {
  299. using (CreateServicesWithFocus())
  300. {
  301. var window = PreparedWindow();
  302. var flyoutTextBox = new TextBox();
  303. var button = new Button
  304. {
  305. Flyout = new Flyout
  306. {
  307. ShowMode = FlyoutShowMode.Standard,
  308. Content = new Panel
  309. {
  310. Children =
  311. {
  312. flyoutTextBox
  313. }
  314. }
  315. }
  316. };
  317. window.Content = button;
  318. window.Show();
  319. button.Focus();
  320. Assert.Same(button, window.FocusManager!.GetFocusedElement());
  321. button.Flyout.ShowAt(button);
  322. Assert.False(button.IsFocused);
  323. Assert.Same(flyoutTextBox, window.FocusManager!.GetFocusedElement());
  324. }
  325. }
  326. [Fact]
  327. public void ShowMode_Transient_Does_Not_Move_Focus_From_Target()
  328. {
  329. using (CreateServicesWithFocus())
  330. {
  331. var window = PreparedWindow();
  332. var flyoutTextBox = new TextBox();
  333. var button = new Button
  334. {
  335. Flyout = new Flyout
  336. {
  337. ShowMode = FlyoutShowMode.Transient,
  338. Content = new Panel
  339. {
  340. Children =
  341. {
  342. flyoutTextBox
  343. }
  344. }
  345. },
  346. Content = "Test"
  347. };
  348. window.Content = button;
  349. window.Show();
  350. button.Focus();
  351. Assert.True(window.FocusManager.GetFocusedElement() == button);
  352. button.Flyout.ShowAt(button);
  353. Assert.True(window.FocusManager.GetFocusedElement() == button);
  354. }
  355. }
  356. [Fact]
  357. public void ContextRequested_Opens_ContextFlyout()
  358. {
  359. using (CreateServicesWithFocus())
  360. {
  361. var flyout = new Flyout();
  362. var target = new Panel
  363. {
  364. ContextFlyout = flyout
  365. };
  366. var window = PreparedWindow(target);
  367. window.Show();
  368. int openedCount = 0;
  369. flyout.Opened += (sender, args) =>
  370. {
  371. openedCount++;
  372. };
  373. target.RaiseEvent(new ContextRequestedEventArgs());
  374. Assert.True(flyout.IsOpen);
  375. Assert.Equal(1, openedCount);
  376. }
  377. }
  378. [Fact]
  379. public void KeyUp_Raised_On_Target_Opens_ContextFlyout()
  380. {
  381. using (CreateServicesWithFocus())
  382. {
  383. var flyout = new Flyout();
  384. var target = new Panel
  385. {
  386. ContextFlyout = flyout
  387. };
  388. var contextRequestedCount = 0;
  389. target.AddHandler(Control.ContextRequestedEvent, (s, a) => contextRequestedCount++, Interactivity.RoutingStrategies.Tunnel);
  390. var window = PreparedWindow(target);
  391. window.Show();
  392. target.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyUpEvent, Key = Key.Apps, Source = window });
  393. Assert.True(flyout.IsOpen);
  394. Assert.Equal(1, contextRequestedCount);
  395. }
  396. }
  397. [Fact]
  398. public void KeyUp_Raised_On_Target_Closes_Opened_ContextFlyout()
  399. {
  400. using (CreateServicesWithFocus())
  401. {
  402. var flyout = new Flyout();
  403. var target = new Panel
  404. {
  405. ContextFlyout = flyout
  406. };
  407. var window = PreparedWindow(target);
  408. window.Show();
  409. target.RaiseEvent(new ContextRequestedEventArgs());
  410. Assert.True(flyout.IsOpen);
  411. target.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyUpEvent, Key = Key.Apps, Source = window });
  412. Assert.False(flyout.IsOpen);
  413. }
  414. }
  415. [Fact]
  416. public void KeyUp_Raised_On_Flyout_Closes_Opened_ContextFlyout()
  417. {
  418. using (CreateServicesWithFocus())
  419. {
  420. var flyoutContent = new Button();
  421. var flyout = new Flyout()
  422. {
  423. Content = flyoutContent
  424. };
  425. var target = new Panel
  426. {
  427. ContextFlyout = flyout
  428. };
  429. var window = PreparedWindow(target);
  430. window.Show();
  431. target.RaiseEvent(new ContextRequestedEventArgs());
  432. Assert.True(flyout.IsOpen);
  433. flyoutContent.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyUpEvent, Key = Key.Apps, Source = window });
  434. Assert.False(flyout.IsOpen);
  435. }
  436. }
  437. [Fact]
  438. public void Should_Reset_Popup_Parent_On_Target_Detached()
  439. {
  440. using (CreateServicesWithFocus())
  441. {
  442. var userControl = new UserControl();
  443. var window = PreparedWindow(userControl);
  444. window.Show();
  445. var flyout = new TestFlyout();
  446. flyout.ShowAt(userControl);
  447. var popup = Assert.IsType<Popup>(flyout.Popup);
  448. Assert.NotNull(popup.Parent);
  449. window.Content = null;
  450. Assert.Null(popup.Parent);
  451. }
  452. }
  453. [Fact]
  454. public void Should_Reset_Popup_Parent_On_Target_Attach_Following_Detach()
  455. {
  456. using (CreateServicesWithFocus())
  457. {
  458. var userControl = new UserControl();
  459. var window = PreparedWindow(userControl);
  460. window.Show();
  461. var flyout = new TestFlyout();
  462. flyout.ShowAt(userControl);
  463. var popup = Assert.IsType<Popup>(flyout.Popup);
  464. Assert.NotNull(popup.Parent);
  465. flyout.Hide();
  466. flyout.ShowAt(userControl);
  467. Assert.NotNull(popup.Parent);
  468. }
  469. }
  470. [Fact]
  471. public void ContextFlyout_Can_Be_Set_In_Styles()
  472. {
  473. using (CreateServicesWithFocus())
  474. {
  475. var xaml = @"
  476. <Window xmlns='https://github.com/avaloniaui'
  477. xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
  478. <Window.Styles>
  479. <Style Selector='TextBlock'>
  480. <Setter Property='ContextFlyout'>
  481. <MenuFlyout>
  482. <MenuItem>Foo</MenuItem>
  483. </MenuFlyout>
  484. </Setter>
  485. </Style>
  486. </Window.Styles>
  487. <StackPanel>
  488. <TextBlock Name='target1'/>
  489. <TextBlock Name='target2'/>
  490. </StackPanel>
  491. </Window>";
  492. var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
  493. var target1 = window.Find<TextBlock>("target1");
  494. var target2 = window.Find<TextBlock>("target2");
  495. var mouse = new MouseTestHelper();
  496. Assert.NotNull(target1.ContextFlyout);
  497. Assert.NotNull(target2.ContextFlyout);
  498. Assert.Same(target1.ContextFlyout, target2.ContextFlyout);
  499. window.Show();
  500. var menu = target1.ContextFlyout;
  501. mouse.Click(target1, MouseButton.Right);
  502. Assert.True(menu.IsOpen);
  503. mouse.Click(target2, MouseButton.Right);
  504. Assert.True(menu.IsOpen);
  505. }
  506. }
  507. [Fact]
  508. public void Setting_FlyoutPresenterClasses_Sets_Classes_On_FlyoutPresenter()
  509. {
  510. using (CreateServicesWithFocus())
  511. {
  512. var xaml = @"
  513. <Window xmlns='https://github.com/avaloniaui'
  514. xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
  515. <Window.Styles>
  516. <Style Selector='FlyoutPresenter.TestClass'>
  517. <Setter Property='Background' Value='Red' />
  518. </Style>
  519. </Window.Styles>
  520. </Window>";
  521. var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
  522. var flyoutPanel = new Panel();
  523. var button = new Button
  524. {
  525. Content = "Test",
  526. Flyout = new Flyout
  527. {
  528. Content = flyoutPanel
  529. }
  530. };
  531. window.Content = button;
  532. window.Show();
  533. (button.Flyout as Flyout).FlyoutPresenterClasses.Add("TestClass");
  534. button.Flyout.ShowAt(button);
  535. var presenter = flyoutPanel.GetVisualAncestors().OfType<FlyoutPresenter>().FirstOrDefault();
  536. Assert.NotNull(presenter);
  537. Assert.True((presenter.Background as ISolidColorBrush).Color == Colors.Red);
  538. }
  539. }
  540. private IDisposable CreateServicesWithFocus()
  541. {
  542. return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:
  543. new MockWindowingPlatform(null,
  544. x => UseOverlayPopups ? null : MockWindowingPlatform.CreatePopupMock(x).Object),
  545. focusManager: new FocusManager(),
  546. keyboardDevice: () => new KeyboardDevice()));
  547. }
  548. private static Window PreparedWindow(object content = null)
  549. {
  550. var platform = AvaloniaLocator.Current.GetRequiredService<IWindowingPlatform>();
  551. var windowImpl = Mock.Get(platform.CreateWindow());
  552. windowImpl.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor());
  553. var w = new Window(windowImpl.Object) { Content = content };
  554. w.ApplyTemplate();
  555. return w;
  556. }
  557. private static PointerPressedEventArgs CreatePointerPressedEventArgs(Window source, Point p)
  558. {
  559. var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
  560. return new PointerPressedEventArgs(
  561. source,
  562. pointer,
  563. source,
  564. p,
  565. 0,
  566. new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
  567. KeyModifiers.None);
  568. }
  569. public class TestFlyout : Flyout
  570. {
  571. public new Popup Popup => base.Popup;
  572. }
  573. }
  574. public class OverlayPopupFlyoutTests : FlyoutTests
  575. {
  576. public OverlayPopupFlyoutTests()
  577. => UseOverlayPopups = true;
  578. }
  579. }