123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603 |
- using System;
- using System.Collections.Generic;
- using System.Reactive;
- using System.Runtime.CompilerServices;
- using Avalonia.Controls.Primitives;
- using Avalonia.Data;
- using Avalonia.Input;
- using Avalonia.Input.Raw;
- using Avalonia.Platform;
- using Avalonia.Rendering;
- using Avalonia.Threading;
- using Avalonia.UnitTests;
- using Moq;
- using Xunit;
- namespace Avalonia.Controls.UnitTests
- {
- public class ToolTipTests_Popup : ToolTipTests
- {
- protected override TestServices ConfigureServices(TestServices baseServices) => baseServices;
- protected override void SetupWindowMock(Mock<IWindowImpl> windowImpl) { }
- protected override void VerifyToolTipType(Control control)
- {
- var toolTip = control.GetValue(ToolTip.ToolTipProperty);
- Assert.IsType<PopupRoot>(toolTip.PopupHost);
- Assert.Same(toolTip.VisualRoot, toolTip.PopupHost);
- }
- }
- public class ToolTipTests_Overlay : ToolTipTests, IDisposable
- {
- private readonly IDisposable _toolTipOpenSubscription;
- public ToolTipTests_Overlay()
- {
- _toolTipOpenSubscription = ToolTip.IsOpenProperty.Changed.Subscribe(new AnonymousObserver<AvaloniaPropertyChangedEventArgs<bool>>(e =>
- {
- if (e.Sender is Visual { VisualRoot: {} root } visual)
- OverlayLayer.GetOverlayLayer(visual).Measure(root.ClientSize);
- }));
- }
- public void Dispose()
- {
- _toolTipOpenSubscription.Dispose();
- }
- protected override TestServices ConfigureServices(TestServices baseServices) =>
- baseServices.With(windowingPlatform: new MockWindowingPlatform(popupImpl: window => null));
- protected override void SetupWindowMock(Mock<IWindowImpl> windowImpl)
- {
- windowImpl.Setup(x => x.CreatePopup()).Returns(default(IPopupImpl));
- }
- protected override void VerifyToolTipType(Control control)
- {
- var toolTip = control.GetValue(ToolTip.ToolTipProperty);
- Assert.IsType<OverlayPopupHost>(toolTip.PopupHost);
- Assert.Same(toolTip.VisualRoot, control.VisualRoot);
- }
- }
- public abstract class ToolTipTests : ScopedTestBase
- {
- protected abstract TestServices ConfigureServices(TestServices baseServices);
- protected abstract void SetupWindowMock(Mock<IWindowImpl> windowImpl);
- protected abstract void VerifyToolTipType(Control control);
- private void AssertToolTipOpen(Control control)
- {
- Assert.True(ToolTip.GetIsOpen(control));
- VerifyToolTipType(control);
- }
- private static readonly MouseDevice s_mouseDevice = new(new Pointer(0, PointerType.Mouse, true));
- [Fact]
- public void Should_Close_When_Control_Detaches()
- {
- using (UnitTestApplication.Start(ConfigureServices(TestServices.FocusableWindow)))
- {
- var panel = new Panel();
- var target = new Decorator()
- {
- [ToolTip.TipProperty] = "Tip",
- [ToolTip.ShowDelayProperty] = 0
- };
- panel.Children.Add(target);
- SetupWindowAndActivateToolTip(panel, target);
- AssertToolTipOpen(target);
- panel.Children.Remove(target);
- Assert.False(ToolTip.GetIsOpen(target));
- }
- }
- [Fact]
- public void Should_Close_When_Tip_Is_Opened_And_Detached_From_Visual_Tree()
- {
- using (UnitTestApplication.Start(ConfigureServices(TestServices.FocusableWindow)))
- {
- var target = new Decorator
- {
- [!ToolTip.TipProperty] = new Binding("Tip"),
- [ToolTip.ShowDelayProperty] = 0,
- };
- var panel = new Panel();
- panel.Children.Add(target);
- var mouseEnter = SetupWindowAndGetMouseEnterAction(panel);
- panel.DataContext = new ToolTipViewModel();
- mouseEnter(target);
- AssertToolTipOpen(target);
- panel.Children.Remove(target);
- Assert.False(ToolTip.GetIsOpen(target));
- }
- }
- [Fact]
- public void Should_Open_On_Pointer_Enter()
- {
- using (UnitTestApplication.Start(ConfigureServices(TestServices.FocusableWindow)))
- {
- var target = new Decorator()
- {
- [ToolTip.TipProperty] = "Tip",
- [ToolTip.ShowDelayProperty] = 0
- };
- SetupWindowAndActivateToolTip(target);
- AssertToolTipOpen(target);
- }
- }
- [Fact]
- public void Content_Should_Update_When_Tip_Property_Changes_And_Already_Open()
- {
- using (UnitTestApplication.Start(ConfigureServices(TestServices.FocusableWindow)))
- {
- var target = new Decorator()
- {
- [ToolTip.TipProperty] = "Tip",
- [ToolTip.ShowDelayProperty] = 0
- };
- SetupWindowAndActivateToolTip(target);
- AssertToolTipOpen(target);
- Assert.Equal("Tip", target.GetValue(ToolTip.ToolTipProperty).Content);
- ToolTip.SetTip(target, "Tip1");
- Assert.Equal("Tip1", target.GetValue(ToolTip.ToolTipProperty).Content);
- }
- }
- [Fact]
- public void Should_Open_On_Pointer_Enter_With_Delay()
- {
- using (UnitTestApplication.Start(ConfigureServices(TestServices.FocusableWindow)))
- {
- var target = new Decorator()
- {
- [ToolTip.TipProperty] = "Tip",
- [ToolTip.ShowDelayProperty] = 1
- };
- SetupWindowAndActivateToolTip(target);
- var timer = Assert.Single(Dispatcher.SnapshotTimersForUnitTests());
- Assert.Equal(TimeSpan.FromMilliseconds(1), timer.Interval);
- Assert.False(ToolTip.GetIsOpen(target));
- timer.ForceFire();
- AssertToolTipOpen(target);
- }
- }
- [Fact]
- public void Open_Class_Should_Not_Initially_Be_Added()
- {
- using (UnitTestApplication.Start(ConfigureServices(TestServices.StyledWindow)))
- {
- var toolTip = new ToolTip();
- var window = new Window();
- var decorator = new Decorator()
- {
- [ToolTip.TipProperty] = toolTip
- };
- window.Content = decorator;
- window.ApplyStyling();
- window.ApplyTemplate();
- window.Presenter.ApplyTemplate();
- Assert.Empty(toolTip.Classes);
- }
- }
- [Fact]
- public void Setting_IsOpen_Should_Add_Open_Class()
- {
- using (UnitTestApplication.Start(ConfigureServices(TestServices.StyledWindow)))
- {
- var toolTip = new ToolTip();
- var window = new Window();
- var decorator = new Decorator()
- {
- [ToolTip.TipProperty] = toolTip
- };
- window.Content = decorator;
- window.ApplyStyling();
- window.ApplyTemplate();
- window.Presenter.ApplyTemplate();
- ToolTip.SetIsOpen(decorator, true);
- Assert.Equal(new[] { ":open" }, toolTip.Classes);
- VerifyToolTipType(decorator);
- }
- }
- [Fact]
- public void Clearing_IsOpen_Should_Remove_Open_Class()
- {
- using (UnitTestApplication.Start(ConfigureServices(TestServices.StyledWindow)))
- {
- var toolTip = new ToolTip();
- var windowImpl = MockWindowingPlatform.CreateWindowMock();
- SetupWindowMock(windowImpl);
- var window = new Window(windowImpl.Object);
-
- var decorator = new Decorator()
- {
- [ToolTip.TipProperty] = toolTip
- };
- window.Content = decorator;
- window.ApplyStyling();
- window.ApplyTemplate();
- window.Presenter.ApplyTemplate();
- ToolTip.SetIsOpen(decorator, true);
- AssertToolTipOpen(decorator);
- ToolTip.SetIsOpen(decorator, false);
- Assert.Empty(toolTip.Classes);
- }
- }
- [Fact]
- public void Should_Close_On_Null_Tip()
- {
- using (UnitTestApplication.Start(ConfigureServices(TestServices.FocusableWindow)))
- {
- var target = new Decorator()
- {
- [ToolTip.TipProperty] = "Tip",
- [ToolTip.ShowDelayProperty] = 0
- };
- SetupWindowAndActivateToolTip(target);
- AssertToolTipOpen(target);
- target[ToolTip.TipProperty] = null;
- Assert.False(ToolTip.GetIsOpen(target));
- }
- }
- [Fact]
- public void Should_Not_Close_When_Pointer_Is_Moved_Over_ToolTip()
- {
- using (UnitTestApplication.Start(ConfigureServices(TestServices.FocusableWindow)))
- {
- var target = new Decorator()
- {
- [ToolTip.TipProperty] = "Tip",
- [ToolTip.ShowDelayProperty] = 0
- };
- var mouseEnter = SetupWindowAndGetMouseEnterAction(target);
- mouseEnter(target);
- AssertToolTipOpen(target);
- var tooltip = Assert.IsType<ToolTip>(target.GetValue(ToolTip.ToolTipProperty));
- mouseEnter(tooltip);
- AssertToolTipOpen(target);
- }
- }
- [Fact]
- public void Should_Not_Close_When_Pointer_Is_Moved_From_ToolTip_To_Original_Control()
- {
- using (UnitTestApplication.Start(ConfigureServices(TestServices.FocusableWindow)))
- {
- var target = new Decorator()
- {
- [ToolTip.TipProperty] = "Tip",
- [ToolTip.ShowDelayProperty] = 0
- };
- var mouseEnter = SetupWindowAndGetMouseEnterAction(target);
- mouseEnter(target);
- AssertToolTipOpen(target);
- var tooltip = Assert.IsType<ToolTip>(target.GetValue(ToolTip.ToolTipProperty));
- mouseEnter(tooltip);
- AssertToolTipOpen(target);
- mouseEnter(target);
- AssertToolTipOpen(target);
- }
- }
- [Fact]
- public void Should_Close_When_Pointer_Is_Moved_From_ToolTip_To_Another_Control()
- {
- using (UnitTestApplication.Start(ConfigureServices(TestServices.FocusableWindow)))
- {
- var target = new Decorator()
- {
- [ToolTip.TipProperty] = "Tip",
- [ToolTip.ShowDelayProperty] = 0
- };
- var other = new Decorator();
- var panel = new StackPanel
- {
- Children = { target, other }
- };
- var mouseEnter = SetupWindowAndGetMouseEnterAction(panel);
- mouseEnter(target);
- AssertToolTipOpen(target);
- var tooltip = Assert.IsType<ToolTip>(target.GetValue(ToolTip.ToolTipProperty));
- mouseEnter(tooltip);
- AssertToolTipOpen(target);
- mouseEnter(other);
- Assert.False(ToolTip.GetIsOpen(target));
- }
- }
- [Fact]
- public void New_ToolTip_Replaces_Other_ToolTip_Immediately()
- {
- using var app = UnitTestApplication.Start(ConfigureServices(TestServices.FocusableWindow));
-
- var target = new Decorator()
- {
- [ToolTip.TipProperty] = "Tip",
- [ToolTip.ShowDelayProperty] = 0
- };
- var other = new Decorator()
- {
- [ToolTip.TipProperty] = "Tip",
- [ToolTip.ShowDelayProperty] = (int) TimeSpan.FromHours(1).TotalMilliseconds,
- };
- var panel = new StackPanel
- {
- Children = { target, other }
- };
- var mouseEnter = SetupWindowAndGetMouseEnterAction(panel);
-
- mouseEnter(other);
- Assert.False(ToolTip.GetIsOpen(other)); // long delay
- mouseEnter(target);
- AssertToolTipOpen(target); // no delay
- mouseEnter(other);
- Assert.True(ToolTip.GetIsOpen(other)); // delay skipped, a tooltip was already open
- // Now disable the between-show system
- mouseEnter(target);
- AssertToolTipOpen(target);
- ToolTip.SetBetweenShowDelay(other, -1);
- mouseEnter(other);
- Assert.False(ToolTip.GetIsOpen(other));
- }
- [Fact]
- public void ToolTip_Events_Order_Is_Defined()
- {
- using var app = UnitTestApplication.Start(ConfigureServices(TestServices.FocusableWindow));
- var tip = new ToolTip() { Content = "Tip" };
- var target = new Decorator()
- {
- [ToolTip.TipProperty] = tip,
- [ToolTip.ShowDelayProperty] = 0
- };
- var eventsOrder = new List<(string eventName, object sender, object source)>();
- ToolTip.AddToolTipOpeningHandler(target,
- (sender, args) => eventsOrder.Add(("Opening", sender, args.Source)));
- ToolTip.AddToolTipClosingHandler(target,
- (sender, args) => eventsOrder.Add(("Closing", sender, args.Source)));
- SetupWindowAndActivateToolTip(target);
- AssertToolTipOpen(target);
- target[ToolTip.TipProperty] = null;
- Assert.False(ToolTip.GetIsOpen(target));
- Assert.Equal(
- new[]
- {
- ("Opening", (object)target, (object)target),
- ("Closing", target, target)
- },
- eventsOrder);
- }
-
- [Fact]
- public void ToolTip_Is_Not_Opened_If_Opening_Event_Handled()
- {
- using var app = UnitTestApplication.Start(ConfigureServices(TestServices.FocusableWindow));
- var tip = new ToolTip() { Content = "Tip" };
- var target = new Decorator()
- {
- [ToolTip.TipProperty] = tip,
- [ToolTip.ShowDelayProperty] = 0
- };
- ToolTip.AddToolTipOpeningHandler(target,
- (sender, args) => args.Cancel = true);
- SetupWindowAndActivateToolTip(target);
- Assert.False(ToolTip.GetIsOpen(target));
- }
- [Fact]
- public void ToolTip_Can_Be_Replaced_On_The_Fly_Via_Opening_Event()
- {
- using var app = UnitTestApplication.Start(ConfigureServices(TestServices.FocusableWindow));
- var tip1 = new ToolTip() { Content = "Hi" };
- var tip2 = new ToolTip() { Content = "Bye" };
- var target = new Decorator()
- {
- [ToolTip.TipProperty] = tip1,
- [ToolTip.ShowDelayProperty] = 0
- };
- ToolTip.AddToolTipOpeningHandler(target,
- (sender, args) => target[ToolTip.TipProperty] = tip2);
- SetupWindowAndActivateToolTip(target);
- AssertToolTipOpen(target);
- target[ToolTip.TipProperty] = null;
- Assert.False(ToolTip.GetIsOpen(target));
- }
- [Fact]
- public void Should_Close_When_Pointer_Leaves_Window()
- {
- using (UnitTestApplication.Start(TestServices.FocusableWindow))
- {
- var target = new Decorator()
- {
- [ToolTip.TipProperty] = "Tip",
- [ToolTip.ShowDelayProperty] = 0
- };
- var mouseEnter = SetupWindowAndGetMouseEnterAction(target);
- mouseEnter(target);
- AssertToolTipOpen(target);
- var topLevel = TopLevel.GetTopLevel(target);
- topLevel.PlatformImpl.Input(new RawPointerEventArgs(s_mouseDevice, (ulong)DateTime.Now.Ticks, topLevel,
- RawPointerEventType.LeaveWindow, default(RawPointerPoint), RawInputModifiers.None));
- Assert.False(ToolTip.GetIsOpen(target));
- }
- }
- private Action<Control> SetupWindowAndGetMouseEnterAction(Control windowContent, [CallerMemberName] string testName = null)
- {
- var windowImpl = MockWindowingPlatform.CreateWindowMock();
- SetupWindowMock(windowImpl);
- var hitTesterMock = new Mock<IHitTester>();
- var window = new Window(windowImpl.Object)
- {
- HitTesterOverride = hitTesterMock.Object,
- Content = windowContent,
- Title = testName,
- };
- window.ApplyStyling();
- window.ApplyTemplate();
- window.Presenter.ApplyTemplate();
- window.Show();
- Assert.True(windowContent.IsAttachedToVisualTree);
- Assert.True(windowContent.IsMeasureValid);
- Assert.True(windowContent.IsVisible);
- var controlIds = new Dictionary<Control, int>();
- IInputRoot lastRoot = null;
- return control =>
- {
- Point point;
- if (control == null)
- {
- point = default;
- }
- else
- {
- if (!controlIds.TryGetValue(control, out int id))
- {
- id = controlIds[control] = controlIds.Count;
- }
- point = new Point(id, int.MaxValue);
- }
- hitTesterMock.Setup(m => m.HitTestFirst(point, window, It.IsAny<Func<Visual, bool>>()))
- .Returns(control);
- var root = (IInputRoot)control?.VisualRoot ?? window;
- var timestamp = (ulong)DateTime.Now.Ticks;
- windowImpl.Object.Input(new RawPointerEventArgs(s_mouseDevice, timestamp, root,
- RawPointerEventType.Move, point, RawInputModifiers.None));
- if (lastRoot != null && lastRoot != root)
- {
- ((TopLevel)lastRoot).PlatformImpl?.Input(new RawPointerEventArgs(s_mouseDevice, timestamp,
- lastRoot, RawPointerEventType.LeaveWindow, new Point(-1,-1), RawInputModifiers.None));
- }
- lastRoot = root;
- Assert.True(control == null || control.IsPointerOver);
- };
- }
- private void SetupWindowAndActivateToolTip(Control windowContent, Control targetOverride = null, [CallerMemberName] string testName = null) =>
- SetupWindowAndGetMouseEnterAction(windowContent, testName)(targetOverride ?? windowContent);
- }
- internal class ToolTipViewModel
- {
- public string Tip => "Tip";
- }
- }
|