mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-18 14:37:19 +08:00
328 lines
11 KiB
C#
328 lines
11 KiB
C#
using System.ComponentModel;
|
|
using AtomUI.Controls;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Controls.Presenters;
|
|
using Avalonia.Controls.Primitives.PopupPositioning;
|
|
using Avalonia.Controls.Templates;
|
|
using Avalonia.Threading;
|
|
using Avalonia.VisualTree;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace AtomUI.Desktop.Controls.Tests.NavMenu;
|
|
|
|
public class NavMenuTooltipTests
|
|
{
|
|
static NavMenuTooltipTests()
|
|
{
|
|
AvaloniaTestApp.EnsureInitialized();
|
|
}
|
|
|
|
[Fact]
|
|
public void Collapsed_Leaf_Uses_Explicit_Tooltip_And_Menu_Tooltip_Settings()
|
|
{
|
|
var node = new NavMenuNode
|
|
{
|
|
Header = "Dashboard",
|
|
Tooltip = "Open dashboard"
|
|
};
|
|
var menu = CreateCollapsedMenu(node);
|
|
menu.CollapsedTooltipPlacement = PlacementMode.Left;
|
|
menu.CollapsedTooltipShowDelay = 250;
|
|
menu.CollapsedTooltipBetweenShowDelay = 75;
|
|
|
|
ShowInWindow(menu, () =>
|
|
{
|
|
var container = menu.ContainerFromItem(node).ShouldBeOfType<NavMenuItem>();
|
|
var header = container.ItemHeader.ShouldNotBeNull();
|
|
|
|
container.EffectiveCollapsedTooltip.ShouldBe("Open dashboard");
|
|
ToolTip.GetTip(header).ShouldBe("Open dashboard");
|
|
ToolTip.GetPlacement(header).ShouldBe(PlacementMode.Left);
|
|
ToolTip.GetShowDelay(header).ShouldBe(250);
|
|
ToolTip.GetBetweenShowDelay(header).ShouldBe(75);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Collapsed_Leaf_Falls_Back_To_Node_Header_When_Tooltip_Is_Unset()
|
|
{
|
|
var node = new NavMenuNode { Header = "Settings" };
|
|
var menu = CreateCollapsedMenu(node);
|
|
|
|
ShowInWindow(menu, () =>
|
|
{
|
|
var container = menu.ContainerFromItem(node).ShouldBeOfType<NavMenuItem>();
|
|
var header = container.ItemHeader.ShouldBeOfType<VerticalNavMenuItemHeader>();
|
|
var collapsedTitle = header.GetVisualDescendants()
|
|
.OfType<ContentPresenter>()
|
|
.Single(presenter => presenter.Name == "CollapsedTitlePresenter");
|
|
|
|
container.NodeHeader.ShouldBe("Settings");
|
|
container.EffectiveCollapsedTooltip.ShouldBe("Settings");
|
|
ToolTip.GetTip(header).ShouldBe("Settings");
|
|
collapsedTitle.Content.ShouldBe("S");
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Node_Tooltip_ToolTip_Instance_Passes_Through_To_Header()
|
|
{
|
|
// ToolTip 实例作为节点 Tooltip:实例自身显式设置的呈现类附加属性
|
|
// 在打开时优先于宿主(PART_Header)的值,NavMenu 只负责原样透传
|
|
var tip = new ToolTip { Content = "Open dashboard" };
|
|
ToolTip.SetPlacement(tip, PlacementMode.Left);
|
|
var node = new NavMenuNode
|
|
{
|
|
Header = "Dashboard",
|
|
Tooltip = tip
|
|
};
|
|
var menu = CreateCollapsedMenu(node);
|
|
|
|
ShowInWindow(menu, () =>
|
|
{
|
|
var container = menu.ContainerFromItem(node).ShouldBeOfType<NavMenuItem>();
|
|
var header = container.ItemHeader.ShouldNotBeNull();
|
|
|
|
container.EffectiveCollapsedTooltip.ShouldBeSameAs(tip);
|
|
ToolTip.GetTip(header).ShouldBeSameAs(tip);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Tooltip_Is_Suppressed_For_Disabled_Expanded_And_Submenu_Items()
|
|
{
|
|
var disabledNode = new NavMenuNode
|
|
{
|
|
Header = "Disabled",
|
|
Tooltip = "Disabled tooltip",
|
|
IsTooltipEnabled = false
|
|
};
|
|
var parentNode = new NavMenuNode
|
|
{
|
|
Header = "Parent",
|
|
Tooltip = "Parent tooltip"
|
|
};
|
|
parentNode.Children.Add(new NavMenuNode { Header = "Child" });
|
|
|
|
var menu = CreateCollapsedMenu(disabledNode, parentNode);
|
|
|
|
ShowInWindow(menu, () =>
|
|
{
|
|
var disabledContainer = menu.ContainerFromItem(disabledNode).ShouldBeOfType<NavMenuItem>();
|
|
var parentContainer = menu.ContainerFromItem(parentNode).ShouldBeOfType<NavMenuItem>();
|
|
|
|
disabledContainer.EffectiveCollapsedTooltip.ShouldBeNull();
|
|
ToolTip.GetTip(disabledContainer.ItemHeader.ShouldNotBeNull()).ShouldBeNull();
|
|
parentContainer.HasSubMenu.ShouldBeTrue();
|
|
parentContainer.EffectiveCollapsedTooltip.ShouldBeNull();
|
|
ToolTip.GetTip(parentContainer.ItemHeader.ShouldNotBeNull()).ShouldBeNull();
|
|
|
|
menu.IsInlineCollapsed = false;
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
disabledNode.IsTooltipEnabled = true;
|
|
Dispatcher.UIThread.RunJobs();
|
|
disabledContainer.EffectiveCollapsedTooltip.ShouldBeNull();
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Runtime_Node_And_Menu_Changes_Recompute_Effective_Tooltip()
|
|
{
|
|
var node = new NavMenuNode { Header = "Initial" };
|
|
var menu = CreateCollapsedMenu(node);
|
|
|
|
ShowInWindow(menu, () =>
|
|
{
|
|
var container = menu.ContainerFromItem(node).ShouldBeOfType<NavMenuItem>();
|
|
|
|
container.EffectiveCollapsedTooltip.ShouldBe("Initial");
|
|
|
|
node.Header = "Updated header";
|
|
Dispatcher.UIThread.RunJobs();
|
|
container.EffectiveCollapsedTooltip.ShouldBe("Updated header");
|
|
|
|
node.Tooltip = "Explicit tooltip";
|
|
Dispatcher.UIThread.RunJobs();
|
|
container.EffectiveCollapsedTooltip.ShouldBe("Explicit tooltip");
|
|
|
|
menu.IsCollapsedTooltipEnabled = false;
|
|
Dispatcher.UIThread.RunJobs();
|
|
container.EffectiveCollapsedTooltip.ShouldBeNull();
|
|
|
|
menu.IsCollapsedTooltipEnabled = true;
|
|
node.IsTooltipEnabled = false;
|
|
Dispatcher.UIThread.RunJobs();
|
|
container.EffectiveCollapsedTooltip.ShouldBeNull();
|
|
|
|
node.IsTooltipEnabled = true;
|
|
node.Tooltip = null;
|
|
Dispatcher.UIThread.RunJobs();
|
|
container.EffectiveCollapsedTooltip.ShouldBe("Updated header");
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Observable_Custom_Node_Updates_Projected_Tooltip_State()
|
|
{
|
|
var node = new ObservableTooltipNode("Initial");
|
|
var menu = new AtomUI.Desktop.Controls.NavMenu
|
|
{
|
|
Mode = NavMenuMode.Inline,
|
|
IsInlineCollapsed = true,
|
|
IsMotionEnabled = false
|
|
};
|
|
menu.Items.Add(node);
|
|
|
|
ShowInWindow(menu, () =>
|
|
{
|
|
var container = menu.ContainerFromItem(node).ShouldBeOfType<NavMenuItem>();
|
|
|
|
container.EffectiveCollapsedTooltip.ShouldBe("Initial");
|
|
|
|
node.SetTooltip("Custom tooltip");
|
|
container.EffectiveCollapsedTooltip.ShouldBe("Custom tooltip");
|
|
|
|
node.SetTooltipEnabled(false);
|
|
container.EffectiveCollapsedTooltip.ShouldBeNull();
|
|
|
|
node.SetTooltipEnabled(true);
|
|
node.SetTooltip(null);
|
|
node.SetHeader("Updated");
|
|
container.EffectiveCollapsedTooltip.ShouldBe("Updated");
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Removed_Container_Releases_All_Tooltip_Bindings()
|
|
{
|
|
var node = new NavMenuNode
|
|
{
|
|
Header = "Initial",
|
|
Tooltip = "Initial tooltip"
|
|
};
|
|
var menu = CreateCollapsedMenu(node);
|
|
|
|
ShowInWindow(menu, () =>
|
|
{
|
|
var container = menu.ContainerFromItem(node).ShouldBeOfType<NavMenuItem>();
|
|
var header = container.ItemHeader.ShouldNotBeNull();
|
|
container.EffectiveCollapsedTooltip.ShouldBe("Initial tooltip");
|
|
|
|
menu.Items.Remove(node);
|
|
Dispatcher.UIThread.RunJobs();
|
|
menu.UpdateLayout();
|
|
|
|
menu.ContainerFromItem(node).ShouldBeNull();
|
|
container.OwnerMenu.ShouldBeNull();
|
|
|
|
node.Header = "Detached header";
|
|
node.Tooltip = "Detached tooltip";
|
|
node.IsTooltipEnabled = false;
|
|
menu.IsCollapsedTooltipEnabled = false;
|
|
menu.CollapsedTooltipPlacement = PlacementMode.Left;
|
|
menu.CollapsedTooltipShowDelay = 10;
|
|
menu.CollapsedTooltipBetweenShowDelay = 20;
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
container.NodeHeader.ShouldBeNull();
|
|
container.Tooltip.ShouldBeNull();
|
|
container.IsTooltipEnabled.ShouldBeTrue();
|
|
container.IsCollapsedTooltipEnabled.ShouldBeTrue();
|
|
container.CollapsedTooltipPlacement.ShouldBe(PlacementMode.Right);
|
|
container.CollapsedTooltipShowDelay.ShouldBe(400);
|
|
container.CollapsedTooltipBetweenShowDelay.ShouldBe(100);
|
|
container.EffectiveCollapsedTooltip.ShouldBeNull();
|
|
ToolTip.GetTip(header).ShouldBeNull();
|
|
});
|
|
}
|
|
|
|
private static AtomUI.Desktop.Controls.NavMenu CreateCollapsedMenu(params NavMenuNode[] nodes)
|
|
{
|
|
var menu = new AtomUI.Desktop.Controls.NavMenu
|
|
{
|
|
Mode = NavMenuMode.Inline,
|
|
IsInlineCollapsed = true,
|
|
IsMotionEnabled = false
|
|
};
|
|
|
|
foreach (var node in nodes)
|
|
{
|
|
menu.Items.Add(node);
|
|
}
|
|
|
|
return menu;
|
|
}
|
|
|
|
private static void ShowInWindow(Control content, Action assertion)
|
|
{
|
|
var window = new Avalonia.Controls.Window
|
|
{
|
|
Width = 320,
|
|
Height = 240,
|
|
Content = content
|
|
};
|
|
|
|
try
|
|
{
|
|
window.Show();
|
|
Dispatcher.UIThread.RunJobs();
|
|
window.UpdateLayout();
|
|
assertion();
|
|
}
|
|
finally
|
|
{
|
|
window.Close();
|
|
}
|
|
}
|
|
|
|
private sealed class ObservableTooltipNode : INavMenuNode, INotifyPropertyChanged
|
|
{
|
|
private object? _header;
|
|
private object? _tooltip;
|
|
private bool _isTooltipEnabled = true;
|
|
|
|
public ObservableTooltipNode(object? header)
|
|
{
|
|
_header = header;
|
|
}
|
|
|
|
public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
|
public ITreeNode<INavMenuNode>? ParentNode { get; private set; }
|
|
public object? Header => _header;
|
|
public IDataTemplate? HeaderTemplate => null;
|
|
public object? Tooltip => _tooltip;
|
|
public bool IsTooltipEnabled => _isTooltipEnabled;
|
|
public EntityKey? ItemKey => null;
|
|
public PathIcon? Icon => null;
|
|
public bool IsEnabled => true;
|
|
public IEnumerable<INavMenuNode> Children => [];
|
|
public IEnumerable<INavMenuEntry> Entries => [];
|
|
|
|
public void UpdateParentNode(INavMenuNode? parentNode)
|
|
{
|
|
ParentNode = parentNode;
|
|
}
|
|
|
|
public void SetHeader(object? value)
|
|
{
|
|
_header = value;
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Header)));
|
|
}
|
|
|
|
public void SetTooltip(object? value)
|
|
{
|
|
_tooltip = value;
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Tooltip)));
|
|
}
|
|
|
|
public void SetTooltipEnabled(bool value)
|
|
{
|
|
_isTooltipEnabled = value;
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsTooltipEnabled)));
|
|
}
|
|
}
|
|
}
|