Skip to content

Commit

Permalink
Fixed overlay popups not automatically closing (#16564)
Browse files Browse the repository at this point in the history
* Fixed overlay popups not automatically closing

* Fix overlay tooltip tests not actually generating overlay tooltips
Verify popup type whenever we verify that the popup is open

* Fixed overlay tooltips not being attached to the visual tree in tests
  • Loading branch information
TomEdwardsEnscape authored Aug 4, 2024
1 parent e0e99b3 commit d1cdb29
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/Avalonia.Controls/ToolTipService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public void Update(IInputRoot root, Visual? candidateToolTipHost)
{
var currentToolTip = _tipControl?.GetValue(ToolTip.ToolTipProperty);

if (root == currentToolTip?.VisualRoot)
if (root == currentToolTip?.PopupHost?.HostedVisualTreeRoot)
{
// Don't update while the pointer is over a tooltip
return;
Expand Down
97 changes: 77 additions & 20 deletions tests/Avalonia.Controls.UnitTests/ToolTipTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
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;
Expand All @@ -15,18 +18,65 @@ 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
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]
Expand All @@ -46,7 +96,7 @@ public void Should_Close_When_Control_Detaches()

SetupWindowAndActivateToolTip(panel, target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

panel.Children.Remove(target);

Expand Down Expand Up @@ -74,7 +124,7 @@ public void Should_Close_When_Tip_Is_Opened_And_Detached_From_Visual_Tree()

mouseEnter(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

panel.Children.Remove(target);

Expand All @@ -95,7 +145,7 @@ public void Should_Open_On_Pointer_Enter()

SetupWindowAndActivateToolTip(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);
}
}

Expand All @@ -112,7 +162,7 @@ public void Content_Should_Update_When_Tip_Property_Changes_And_Already_Open()

SetupWindowAndActivateToolTip(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);
Assert.Equal("Tip", target.GetValue(ToolTip.ToolTipProperty).Content);

ToolTip.SetTip(target, "Tip1");
Expand All @@ -139,7 +189,7 @@ public void Should_Open_On_Pointer_Enter_With_Delay()

timer.ForceFire();

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);
}
}

Expand Down Expand Up @@ -188,6 +238,7 @@ public void Setting_IsOpen_Should_Add_Open_Class()
ToolTip.SetIsOpen(decorator, true);

Assert.Equal(new[] { ":open" }, toolTip.Classes);
VerifyToolTipType(decorator);
}
}

Expand All @@ -197,8 +248,11 @@ public void Clearing_IsOpen_Should_Remove_Open_Class()
using (UnitTestApplication.Start(ConfigureServices(TestServices.StyledWindow)))
{
var toolTip = new ToolTip();
var window = new Window();

var windowImpl = MockWindowingPlatform.CreateWindowMock();
SetupWindowMock(windowImpl);
var window = new Window(windowImpl.Object);

var decorator = new Decorator()
{
[ToolTip.TipProperty] = toolTip
Expand All @@ -211,6 +265,7 @@ public void Clearing_IsOpen_Should_Remove_Open_Class()
window.Presenter.ApplyTemplate();

ToolTip.SetIsOpen(decorator, true);
AssertToolTipOpen(decorator);
ToolTip.SetIsOpen(decorator, false);

Assert.Empty(toolTip.Classes);
Expand All @@ -230,7 +285,7 @@ public void Should_Close_On_Null_Tip()

SetupWindowAndActivateToolTip(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

target[ToolTip.TipProperty] = null;

Expand All @@ -253,13 +308,13 @@ public void Should_Not_Close_When_Pointer_Is_Moved_Over_ToolTip()

mouseEnter(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

var tooltip = Assert.IsType<ToolTip>(target.GetValue(ToolTip.ToolTipProperty));

mouseEnter(tooltip);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);
}
}

Expand All @@ -277,16 +332,16 @@ public void Should_Not_Close_When_Pointer_Is_Moved_From_ToolTip_To_Original_Cont
var mouseEnter = SetupWindowAndGetMouseEnterAction(target);

mouseEnter(target);
Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

var tooltip = Assert.IsType<ToolTip>(target.GetValue(ToolTip.ToolTipProperty));
mouseEnter(tooltip);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

mouseEnter(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);
}
}

Expand All @@ -311,12 +366,12 @@ public void Should_Close_When_Pointer_Is_Moved_From_ToolTip_To_Another_Control()
var mouseEnter = SetupWindowAndGetMouseEnterAction(panel);

mouseEnter(target);
Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

var tooltip = Assert.IsType<ToolTip>(target.GetValue(ToolTip.ToolTipProperty));
mouseEnter(tooltip);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

mouseEnter(other);

Expand Down Expand Up @@ -352,15 +407,15 @@ public void New_ToolTip_Replaces_Other_ToolTip_Immediately()
Assert.False(ToolTip.GetIsOpen(other)); // long delay

mouseEnter(target);
Assert.True(ToolTip.GetIsOpen(target)); // no delay
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);
Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

ToolTip.SetBetweenShowDelay(other, -1);

Expand Down Expand Up @@ -389,7 +444,7 @@ public void ToolTip_Events_Order_Is_Defined()

SetupWindowAndActivateToolTip(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

target[ToolTip.TipProperty] = null;

Expand Down Expand Up @@ -442,7 +497,7 @@ public void ToolTip_Can_Be_Replaced_On_The_Fly_Via_Opening_Event()

SetupWindowAndActivateToolTip(target);

Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

target[ToolTip.TipProperty] = null;

Expand All @@ -463,7 +518,7 @@ public void Should_Close_When_Pointer_Leaves_Window()
var mouseEnter = SetupWindowAndGetMouseEnterAction(target);

mouseEnter(target);
Assert.True(ToolTip.GetIsOpen(target));
AssertToolTipOpen(target);

var topLevel = TopLevel.GetTopLevel(target);
topLevel.PlatformImpl.Input(new RawPointerEventArgs(s_mouseDevice, (ulong)DateTime.Now.Ticks, topLevel,
Expand All @@ -476,6 +531,8 @@ public void Should_Close_When_Pointer_Leaves_Window()
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)
Expand Down

0 comments on commit d1cdb29

Please sign in to comment.