using AtomUI.Controls.Primitives; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Threading; using Avalonia.VisualTree; using Shouldly; using Xunit; using AtomUIMenu = AtomUI.Desktop.Controls.Menu; using AtomUIMenuItem = AtomUI.Desktop.Controls.MenuItem; using AtomUIPopup = AtomUI.Desktop.Controls.Popup; using AtomUIWindow = AtomUI.Desktop.Controls.Window; namespace AtomUI.Desktop.Controls.Tests.Menu; public class MenuPinnedOpenTests { static MenuPinnedOpenTests() { AvaloniaTestApp.EnsureInitialized(); } [Fact] public void Popup_Pin_Opens_Menu_Active_Submenu_And_Physical_Popup() { var parentItem = new AtomUIMenuItem { Header = "File" }; parentItem.Items.Add(new AtomUIMenuItem { Header = "Open" }); var menu = new AtomUIMenu { IsMotionEnabled = false, Width = 320 }; menu.Items.Add(parentItem); var window = ShowInWindow(menu); try { menu.IsPopupPinnedOpen = true; Dispatcher.UIThread.RunJobs(); var popup = FindPopup(parentItem); menu.IsOpen.ShouldBeTrue(); parentItem.IsSubMenuOpen.ShouldBeTrue(); popup.IsPopupPinnedOpen.ShouldBeTrue(); popup.IsOpen.ShouldBeTrue(); } finally { window.Close(); Dispatcher.UIThread.RunJobs(); } } [Theory] [InlineData(false)] [InlineData(true)] public void Popup_Pin_Only_Pins_The_Active_Submenu_Chain(bool grouped) { var parentItem = new AtomUIMenuItem { Header = "File" }; var firstChild = new AtomUIMenuItem { Header = "Open", IsSubMenuOpen = true }; firstChild.Items.Add(new AtomUIMenuItem { Header = "Recent" }); var secondChild = new AtomUIMenuItem { Header = "Edit" }; secondChild.Items.Add(new AtomUIMenuItem { Header = "Copy" }); if (grouped) { parentItem.Items.Add(new MenuItemGroup { Header = "Group", Items = { firstChild, secondChild } }); } else { parentItem.Items.Add(firstChild); parentItem.Items.Add(secondChild); } var menu = new AtomUIMenu { IsMotionEnabled = false, Width = 320 }; menu.Items.Add(parentItem); var window = ShowInWindow(menu); try { menu.IsPopupPinnedOpen = true; Dispatcher.UIThread.RunJobs(); firstChild.IsSubMenuOpen.ShouldBeTrue(); firstChild.IsPopupPinnedOpen.ShouldBeTrue(); secondChild.IsSubMenuOpen.ShouldBeFalse(); secondChild.IsPopupPinnedOpen.ShouldBeFalse(); // Navigation must release the old pin before Avalonia closes siblings. for (var i = 0; i < 3; i++) { secondChild.Open(); Dispatcher.UIThread.RunJobs(); firstChild.IsSubMenuOpen.ShouldBeFalse(); firstChild.IsPopupPinnedOpen.ShouldBeFalse(); FindPopup(firstChild).IsOpen.ShouldBeFalse(); secondChild.IsSubMenuOpen.ShouldBeTrue(); secondChild.IsPopupPinnedOpen.ShouldBeTrue(); secondChild.Close(); Dispatcher.UIThread.RunJobs(); secondChild.IsSubMenuOpen.ShouldBeTrue(); FindPopup(secondChild).IsOpen.ShouldBeTrue(); firstChild.Open(); Dispatcher.UIThread.RunJobs(); firstChild.IsSubMenuOpen.ShouldBeTrue(); secondChild.IsSubMenuOpen.ShouldBeFalse(); } menu.IsPopupPinnedOpen = false; firstChild.IsPopupPinnedOpen.ShouldBeFalse(); firstChild.Close(); Dispatcher.UIThread.RunJobs(); firstChild.IsSubMenuOpen.ShouldBeFalse(); } finally { window.Close(); Dispatcher.UIThread.RunJobs(); } } [Fact] public void Popup_Pin_Rejects_Menu_Close_Requests() { var parentItem = new AtomUIMenuItem { Header = "File" }; parentItem.Items.Add(new AtomUIMenuItem { Header = "Open" }); var menu = new AtomUIMenu { IsMotionEnabled = false, Width = 320 }; menu.Items.Add(parentItem); var window = ShowInWindow(menu); try { menu.IsPopupPinnedOpen = true; Dispatcher.UIThread.RunJobs(); var popup = FindPopup(parentItem); menu.Close(); Dispatcher.UIThread.RunJobs(); menu.IsOpen.ShouldBeTrue(); parentItem.IsSubMenuOpen.ShouldBeTrue(); popup.IsOpen.ShouldBeTrue(); menu.CloseImmediately(); Dispatcher.UIThread.RunJobs(); menu.IsOpen.ShouldBeTrue(); parentItem.IsSubMenuOpen.ShouldBeTrue(); popup.IsOpen.ShouldBeTrue(); } finally { window.Close(); Dispatcher.UIThread.RunJobs(); } } [Fact] public void Popup_Pin_Rejects_Active_Submenu_Collapse() { var parentItem = new AtomUIMenuItem { Header = "File" }; parentItem.Items.Add(new AtomUIMenuItem { Header = "Open" }); var menu = new AtomUIMenu { IsMotionEnabled = false, Width = 320 }; menu.Items.Add(parentItem); var window = ShowInWindow(menu); try { menu.IsPopupPinnedOpen = true; Dispatcher.UIThread.RunJobs(); parentItem.SetCurrentValue(MenuItem.IsSubMenuOpenProperty, false); Dispatcher.UIThread.RunJobs(); parentItem.IsSubMenuOpen.ShouldBeTrue(); FindPopup(parentItem).IsOpen.ShouldBeTrue(); } finally { window.Close(); Dispatcher.UIThread.RunJobs(); } } [Fact] public void Popup_Pin_Closes_For_Detach_And_Reopens_After_Reattach() { var parentItem = new AtomUIMenuItem { Header = "File" }; parentItem.Items.Add(new AtomUIMenuItem { Header = "Open" }); var menu = new AtomUIMenu { IsMotionEnabled = false, Width = 320 }; menu.Items.Add(parentItem); var window = ShowInWindow(menu); try { menu.IsPopupPinnedOpen = true; Dispatcher.UIThread.RunJobs(); var popup = FindPopup(parentItem); var windowContent = window.Content; window.Content = null; Dispatcher.UIThread.RunJobs(); menu.IsOpen.ShouldBeFalse(); parentItem.IsSubMenuOpen.ShouldBeFalse(); parentItem.IsPopupPinnedOpen.ShouldBeFalse(); popup.IsPopupPinnedOpen.ShouldBeFalse(); popup.IsOpen.ShouldBeFalse(); window.Content = windowContent; Dispatcher.UIThread.RunJobs(); menu.IsOpen.ShouldBeTrue(); parentItem.IsSubMenuOpen.ShouldBeTrue(); popup.IsOpen.ShouldBeTrue(); } finally { window.Close(); Dispatcher.UIThread.RunJobs(); } } [Fact] public void Popup_Unpin_Leaves_Menu_Open_And_Restores_Normal_Close() { var parentItem = new AtomUIMenuItem { Header = "File" }; parentItem.Items.Add(new AtomUIMenuItem { Header = "Open" }); var menu = new AtomUIMenu { IsMotionEnabled = false, Width = 320 }; menu.Items.Add(parentItem); var window = ShowInWindow(menu); try { menu.IsPopupPinnedOpen = true; Dispatcher.UIThread.RunJobs(); var popup = FindPopup(parentItem); parentItem.Close(); Dispatcher.UIThread.RunJobs(); parentItem.IsSubMenuOpen.ShouldBeTrue(); popup.IsOpen.ShouldBeTrue(); menu.IsPopupPinnedOpen = false; Dispatcher.UIThread.RunJobs(); menu.IsOpen.ShouldBeTrue(); parentItem.IsSubMenuOpen.ShouldBeTrue(); parentItem.IsPopupPinnedOpen.ShouldBeFalse(); popup.IsPopupPinnedOpen.ShouldBeFalse(); popup.IsOpen.ShouldBeTrue(); menu.Close(); Dispatcher.UIThread.RunJobs(); menu.IsOpen.ShouldBeFalse(); parentItem.IsSubMenuOpen.ShouldBeFalse(); popup.IsOpen.ShouldBeFalse(); } finally { window.Close(); Dispatcher.UIThread.RunJobs(); } } [Fact] public void Popup_Pin_Releases_Removed_Item_And_Transfers_To_The_Next_Submenu() { var firstItem = new AtomUIMenuItem { Header = "File" }; firstItem.Items.Add(new AtomUIMenuItem { Header = "Open" }); var secondItem = new AtomUIMenuItem { Header = "Edit" }; secondItem.Items.Add(new AtomUIMenuItem { Header = "Copy" }); var menu = new AtomUIMenu { IsMotionEnabled = false, Width = 320 }; menu.Items.Add(firstItem); menu.Items.Add(secondItem); var window = ShowInWindow(menu); try { menu.IsPopupPinnedOpen = true; Dispatcher.UIThread.RunJobs(); var firstPopup = FindPopup(firstItem); firstPopup.IsOpen.ShouldBeTrue(); menu.Items.Remove(firstItem); Dispatcher.UIThread.RunJobs(); window.UpdateLayout(); firstItem.IsPopupPinnedOpen.ShouldBeFalse(); firstItem.IsSubMenuOpen.ShouldBeFalse(); firstPopup.IsPopupPinnedOpen.ShouldBeFalse(); firstPopup.IsOpen.ShouldBeFalse(); secondItem.IsPopupPinnedOpen.ShouldBeTrue(); secondItem.IsSubMenuOpen.ShouldBeTrue(); FindPopup(secondItem).IsOpen.ShouldBeTrue(); } finally { window.Close(); Dispatcher.UIThread.RunJobs(); } } [Fact] public void Pinned_Flyout_Preserves_One_Declared_Branch_And_Transfers_Pin_On_Navigation() { var first = new AtomUIMenuItem { Header = "First", IsSubMenuOpen = true }; first.Items.Add(new AtomUIMenuItem { Header = "Leaf" }); var second = new AtomUIMenuItem { Header = "Second" }; second.Items.Add(new AtomUIMenuItem { Header = "Leaf" }); var flyout = new MenuFlyout { IsMotionEnabled = false, Items = { first, second } }; var button = new SplitButton { Content = "Menu", Flyout = flyout, IsMotionEnabled = false }; var window = ShowInWindow(button); try { button.IsPopupPinnedOpen = true; Dispatcher.UIThread.RunJobs(); window.UpdateLayout(); Dispatcher.UIThread.RunJobs(); first.IsPopupPinnedOpen.ShouldBeTrue(); first.IsSubMenuOpen.ShouldBeTrue(); second.IsPopupPinnedOpen.ShouldBeFalse(); second.IsSubMenuOpen.ShouldBeFalse(); second.Open(); Dispatcher.UIThread.RunJobs(); first.IsSubMenuOpen.ShouldBeFalse(); first.IsPopupPinnedOpen.ShouldBeFalse(); second.IsSubMenuOpen.ShouldBeTrue(); second.IsPopupPinnedOpen.ShouldBeTrue(); flyout.IsOpen.ShouldBeTrue(); } finally { button.IsPopupPinnedOpen = false; window.Close(); Dispatcher.UIThread.RunJobs(); } } [Fact] public void Top_Level_Pinned_Menu_Transfers_Pin_Before_Closing_Previous_Submenu() { var first = new AtomUIMenuItem { Header = "First", Items = { new AtomUIMenuItem { Header = "Leaf" } } }; var second = new AtomUIMenuItem { Header = "Second", Items = { new AtomUIMenuItem { Header = "Leaf" } } }; var menu = new AtomUIMenu { IsMotionEnabled = false, Width = 320, Items = { first, second } }; var window = ShowInWindow(menu); try { menu.IsPopupPinnedOpen = true; Dispatcher.UIThread.RunJobs(); second.Open(); Dispatcher.UIThread.RunJobs(); first.IsSubMenuOpen.ShouldBeFalse(); first.IsPopupPinnedOpen.ShouldBeFalse(); second.IsSubMenuOpen.ShouldBeTrue(); second.IsPopupPinnedOpen.ShouldBeTrue(); } finally { menu.IsPopupPinnedOpen = false; window.Close(); Dispatcher.UIThread.RunJobs(); } } private static AtomUIPopup FindPopup(Control root) { return root.GetVisualDescendants() .OfType() .Single(item => item.Name == "PART_Popup"); } private static AtomUIWindow ShowInWindow(Control content) { var overlayPanel = new ScopeAwareOverlayLayerPanel { Width = 420, Height = 240 }; overlayPanel.Children.Add(content); var visualLayerManager = new VisualLayerManager { Child = overlayPanel }; var window = new AtomUIWindow { Width = 420, Height = 240, Content = visualLayerManager }; window.Show(); Dispatcher.UIThread.RunJobs(); return window; } }