FlyoutTests.cs 18 KB

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