csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
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
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
|
|
{
|
|
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";
|
|
}
|
|
}
|
|
|