mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-18 06:21:52 +08:00
324 lines
12 KiB
C#
324 lines
12 KiB
C#
using System.Collections.ObjectModel;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Controls.Templates;
|
|
using Avalonia.Headless;
|
|
using Avalonia.Layout;
|
|
using Avalonia.Threading;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace AtomUI.Desktop.Controls.Tests.NavMenu;
|
|
|
|
public class NavMenuEntryContainerTests
|
|
{
|
|
static NavMenuEntryContainerTests()
|
|
{
|
|
AvaloniaTestApp.EnsureInitialized();
|
|
}
|
|
|
|
[Fact]
|
|
public void Pure_Node_Menu_Still_Realizes_Nested_Children_From_The_Canonical_Entry_Source()
|
|
{
|
|
var child = new NavMenuNode { Header = "Child" };
|
|
var parent = new NavMenuNode { Header = "Parent" };
|
|
parent.Children.Add(child);
|
|
var menu = new AtomUI.Desktop.Controls.NavMenu
|
|
{
|
|
Mode = NavMenuMode.Inline,
|
|
IsMotionEnabled = false
|
|
};
|
|
menu.Items.Add(parent);
|
|
|
|
var window = new Avalonia.Controls.Window { Width = 320, Height = 240, Content = menu };
|
|
try
|
|
{
|
|
window.Show();
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
var parentContainer = menu.ContainerFromItem(parent).ShouldBeOfType<NavMenuItem>();
|
|
menu.ItemTemplate.ShouldNotBeNull();
|
|
parentContainer.Header.ShouldBeSameAs(parent);
|
|
parentContainer.HeaderTemplate.ShouldNotBeNull();
|
|
parentContainer.DataContext.ShouldBeSameAs(parent);
|
|
parentContainer.ItemsSource.ShouldBeSameAs(parent.Entries);
|
|
parentContainer.ItemCount.ShouldBe(1);
|
|
parentContainer.IsSubMenuOpen = true;
|
|
Dispatcher.UIThread.RunJobs();
|
|
window.UpdateLayout();
|
|
parentContainer.ContainerFromItem(child).ShouldBeOfType<NavMenuItem>();
|
|
}
|
|
finally
|
|
{
|
|
window.Close();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Root_Entry_Composition_Uses_Dedicated_Containers_And_Keeps_Groups_Semantically_Transparent()
|
|
{
|
|
var directNode = new NavMenuNode { Header = "Direct" };
|
|
var groupedNode = new NavMenuNode { Header = "Grouped" };
|
|
var groupedDivider = new NavMenuDivider();
|
|
var group = new NavMenuGroup { Header = "Section" };
|
|
group.Entries.Add(groupedNode);
|
|
group.Entries.Add(groupedDivider);
|
|
var rootDivider = new NavMenuDivider();
|
|
var menu = new AtomUI.Desktop.Controls.NavMenu
|
|
{
|
|
Mode = NavMenuMode.Inline,
|
|
IsMotionEnabled = false
|
|
};
|
|
menu.Items.Add(directNode);
|
|
menu.Items.Add(group);
|
|
menu.Items.Add(rootDivider);
|
|
|
|
ShowInWindow(menu, _ =>
|
|
{
|
|
menu.ContainerFromItem(directNode).ShouldBeOfType<NavMenuItem>();
|
|
var groupContainer = menu.ContainerFromItem(group).ShouldBeOfType<NavMenuGroupItem>();
|
|
menu.ContainerFromItem(rootDivider).ShouldBeOfType<NavMenuDividerItem>();
|
|
|
|
groupContainer.Header.ShouldBe("Section");
|
|
groupContainer.ItemsSource.ShouldBeSameAs(group.Entries);
|
|
var groupedNodeContainer = groupContainer.ContainerFromItem(groupedNode).ShouldBeOfType<NavMenuItem>();
|
|
groupContainer.ContainerFromItem(groupedDivider).ShouldBeOfType<NavMenuDividerItem>();
|
|
groupedNodeContainer.Level.ShouldBe(0);
|
|
groupedNodeContainer.IsTopLevel.ShouldBeTrue();
|
|
((INavMenuItem)groupedNodeContainer).Parent.ShouldBeSameAs(menu);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Nested_Group_Inherits_The_Closest_Node_Semantic_Context()
|
|
{
|
|
var child = new NavMenuNode { Header = "Child" };
|
|
var group = new NavMenuGroup { Header = "Nested" };
|
|
group.Entries.Add(child);
|
|
var parent = new NavMenuNode { Header = "Parent" };
|
|
parent.Entries.Add(group);
|
|
var menu = new AtomUI.Desktop.Controls.NavMenu
|
|
{
|
|
Mode = NavMenuMode.Inline,
|
|
IsMotionEnabled = false
|
|
};
|
|
menu.Items.Add(parent);
|
|
|
|
ShowInWindow(menu, window =>
|
|
{
|
|
var parentContainer = menu.ContainerFromItem(parent).ShouldBeOfType<NavMenuItem>();
|
|
parentContainer.IsSubMenuOpen = true;
|
|
Dispatcher.UIThread.RunJobs();
|
|
window.UpdateLayout();
|
|
|
|
var groupContainer = parentContainer.ContainerFromItem(group).ShouldBeOfType<NavMenuGroupItem>();
|
|
var childContainer = groupContainer.ContainerFromItem(child).ShouldBeOfType<NavMenuItem>();
|
|
childContainer.Level.ShouldBe(1);
|
|
childContainer.IsTopLevel.ShouldBeFalse();
|
|
((INavMenuItem)childContainer).Parent.ShouldBeSameAs(parentContainer);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Replacing_Entry_Kinds_Does_Not_Recycle_An_Incompatible_Container()
|
|
{
|
|
var source = new ObservableCollection<INavMenuEntry> { new NavMenuNode() };
|
|
var menu = new AtomUI.Desktop.Controls.NavMenu
|
|
{
|
|
Mode = NavMenuMode.Inline,
|
|
IsMotionEnabled = false,
|
|
ItemsSource = source
|
|
};
|
|
|
|
ShowInWindow(menu, window =>
|
|
{
|
|
menu.ContainerFromIndex(0).ShouldBeOfType<NavMenuItem>();
|
|
|
|
source[0] = new NavMenuGroup();
|
|
Dispatcher.UIThread.RunJobs();
|
|
window.UpdateLayout();
|
|
menu.ContainerFromIndex(0).ShouldBeOfType<NavMenuGroupItem>();
|
|
|
|
source[0] = new NavMenuDivider();
|
|
Dispatcher.UIThread.RunJobs();
|
|
window.UpdateLayout();
|
|
menu.ContainerFromIndex(0).ShouldBeOfType<NavMenuDividerItem>();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Removed_Node_Container_Releases_All_Owner_Bindings()
|
|
{
|
|
var firstTemplate = new FuncDataTemplate<object?>((_, _) => new TextBlock { Text = "First" });
|
|
var replacementTemplate = new FuncDataTemplate<object?>((_, _) => new TextBlock { Text = "Replacement" });
|
|
var node = new NavMenuNode { Header = "Node" };
|
|
var menu = new AtomUI.Desktop.Controls.NavMenu
|
|
{
|
|
Mode = NavMenuMode.Inline,
|
|
IsDarkStyle = false,
|
|
IsItemBackgroundEnabled = true,
|
|
IsMotionEnabled = true,
|
|
ShouldUseOverlayPopup = true,
|
|
ItemTemplate = firstTemplate
|
|
};
|
|
menu.Items.Add(node);
|
|
|
|
ShowInWindow(menu, window =>
|
|
{
|
|
var container = menu.ContainerFromItem(node).ShouldBeOfType<NavMenuItem>();
|
|
container.HeaderTemplate.ShouldBeSameAs(firstTemplate);
|
|
|
|
menu.Items.Remove(node);
|
|
Dispatcher.UIThread.RunJobs();
|
|
window.UpdateLayout();
|
|
menu.ContainerFromItem(node).ShouldBeNull();
|
|
|
|
menu.Mode = NavMenuMode.Horizontal;
|
|
menu.IsDarkStyle = true;
|
|
menu.IsItemBackgroundEnabled = false;
|
|
menu.IsMotionEnabled = false;
|
|
menu.ShouldUseOverlayPopup = false;
|
|
menu.ItemTemplate = replacementTemplate;
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
container.OwnerMenu.ShouldBeNull();
|
|
container.Mode.ShouldBe(NavMenuMode.Vertical);
|
|
container.IsDarkStyle.ShouldBeFalse();
|
|
container.IsItemBackgroundEnabled.ShouldBeTrue();
|
|
container.IsMotionEnabled.ShouldBeFalse();
|
|
container.ShouldUseOverlayPopup.ShouldBeFalse();
|
|
container.HeaderTemplate.ShouldBeNull();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Clear_Node_Container_Resets_Transient_Selection_State()
|
|
{
|
|
var menu = new AtomUI.Desktop.Controls.NavMenu();
|
|
var container = new NavMenuItem
|
|
{
|
|
IsSelected = true,
|
|
IsInSelectedPath = true,
|
|
IsSubMenuOpen = true
|
|
};
|
|
|
|
NavMenuEntryContainerCoordinator.ClearContainer(menu, container);
|
|
|
|
container.IsSelected.ShouldBeFalse();
|
|
container.IsInSelectedPath.ShouldBeFalse();
|
|
container.IsSubMenuOpen.ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void Removed_Group_Container_Releases_All_Owner_Bindings()
|
|
{
|
|
var firstTemplate = new FuncDataTemplate<object?>((_, _) => new TextBlock { Text = "First" });
|
|
var replacementTemplate = new FuncDataTemplate<object?>((_, _) => new TextBlock { Text = "Replacement" });
|
|
var group = new NavMenuGroup { Header = "Group" };
|
|
var menu = new AtomUI.Desktop.Controls.NavMenu
|
|
{
|
|
Mode = NavMenuMode.Inline,
|
|
IsDarkStyle = false,
|
|
IsItemBackgroundEnabled = true,
|
|
IsMotionEnabled = true,
|
|
ShouldUseOverlayPopup = true,
|
|
ItemTemplate = firstTemplate
|
|
};
|
|
menu.Items.Add(group);
|
|
|
|
ShowInWindow(menu, window =>
|
|
{
|
|
var container = menu.ContainerFromItem(group).ShouldBeOfType<NavMenuGroupItem>();
|
|
container.ItemTemplate.ShouldBeSameAs(firstTemplate);
|
|
|
|
menu.Items.Remove(group);
|
|
Dispatcher.UIThread.RunJobs();
|
|
window.UpdateLayout();
|
|
|
|
menu.Mode = NavMenuMode.Horizontal;
|
|
menu.IsDarkStyle = true;
|
|
menu.IsItemBackgroundEnabled = false;
|
|
menu.IsMotionEnabled = false;
|
|
menu.ShouldUseOverlayPopup = false;
|
|
menu.ItemTemplate = replacementTemplate;
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
container.OwnerMenu.ShouldBeNull();
|
|
container.Mode.ShouldBe(NavMenuMode.Vertical);
|
|
container.IsDarkStyle.ShouldBeFalse();
|
|
container.IsItemBackgroundEnabled.ShouldBeTrue();
|
|
container.IsMotionEnabled.ShouldBeFalse();
|
|
container.ShouldUseOverlayPopup.ShouldBeFalse();
|
|
container.ItemTemplate.ShouldBeNull();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Removed_Divider_Container_Releases_All_Owner_Bindings()
|
|
{
|
|
var divider = new NavMenuDivider();
|
|
var menu = new AtomUI.Desktop.Controls.NavMenu
|
|
{
|
|
Mode = NavMenuMode.Inline,
|
|
IsDarkStyle = false
|
|
};
|
|
menu.Items.Add(divider);
|
|
|
|
ShowInWindow(menu, window =>
|
|
{
|
|
var container = menu.ContainerFromItem(divider).ShouldBeOfType<NavMenuDividerItem>();
|
|
|
|
menu.Items.Remove(divider);
|
|
Dispatcher.UIThread.RunJobs();
|
|
window.UpdateLayout();
|
|
|
|
menu.Mode = NavMenuMode.Horizontal;
|
|
menu.IsDarkStyle = true;
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
container.Mode.ShouldBe(NavMenuMode.Vertical);
|
|
container.IsDarkStyle.ShouldBeFalse();
|
|
container.Orientation.ShouldBe(Orientation.Horizontal);
|
|
});
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(NavMenuMode.Inline, Orientation.Horizontal)]
|
|
[InlineData(NavMenuMode.Vertical, Orientation.Horizontal)]
|
|
[InlineData(NavMenuMode.Horizontal, Orientation.Vertical)]
|
|
public void Root_Divider_Orientation_Follows_The_Menu_Mode(
|
|
NavMenuMode mode,
|
|
Orientation expectedOrientation)
|
|
{
|
|
var divider = new NavMenuDivider();
|
|
var menu = new AtomUI.Desktop.Controls.NavMenu
|
|
{
|
|
Mode = mode,
|
|
IsMotionEnabled = false
|
|
};
|
|
menu.Items.Add(divider);
|
|
|
|
ShowInWindow(menu, _ =>
|
|
{
|
|
var container = menu.ContainerFromItem(divider).ShouldBeOfType<NavMenuDividerItem>();
|
|
container.Orientation.ShouldBe(expectedOrientation);
|
|
});
|
|
}
|
|
|
|
private static void ShowInWindow(
|
|
Control content,
|
|
Action<Avalonia.Controls.Window> assertion)
|
|
{
|
|
var window = new Avalonia.Controls.Window { Width = 320, Height = 240, Content = content };
|
|
try
|
|
{
|
|
window.Show();
|
|
Dispatcher.UIThread.RunJobs();
|
|
assertion(window);
|
|
}
|
|
finally
|
|
{
|
|
window.Close();
|
|
}
|
|
}
|
|
}
|