A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

603 lines
19 KiB

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";
}
}