Skip to content

Commit d1cdb29

Browse files
Fixed overlay popups not automatically closing (#16564)
* 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
1 parent e0e99b3 commit d1cdb29

File tree

2 files changed

+78
-21
lines changed

2 files changed

+78
-21
lines changed

src/Avalonia.Controls/ToolTipService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void Update(IInputRoot root, Visual? candidateToolTipHost)
7878
{
7979
var currentToolTip = _tipControl?.GetValue(ToolTip.ToolTipProperty);
8080

81-
if (root == currentToolTip?.VisualRoot)
81+
if (root == currentToolTip?.PopupHost?.HostedVisualTreeRoot)
8282
{
8383
// Don't update while the pointer is over a tooltip
8484
return;

tests/Avalonia.Controls.UnitTests/ToolTipTests.cs

Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Reactive;
34
using System.Runtime.CompilerServices;
5+
using Avalonia.Controls.Primitives;
46
using Avalonia.Data;
57
using Avalonia.Input;
68
using Avalonia.Input.Raw;
9+
using Avalonia.Platform;
710
using Avalonia.Rendering;
811
using Avalonia.Threading;
912
using Avalonia.UnitTests;
@@ -15,18 +18,65 @@ namespace Avalonia.Controls.UnitTests
1518
public class ToolTipTests_Popup : ToolTipTests
1619
{
1720
protected override TestServices ConfigureServices(TestServices baseServices) => baseServices;
21+
22+
protected override void SetupWindowMock(Mock<IWindowImpl> windowImpl) { }
23+
24+
protected override void VerifyToolTipType(Control control)
25+
{
26+
var toolTip = control.GetValue(ToolTip.ToolTipProperty);
27+
Assert.IsType<PopupRoot>(toolTip.PopupHost);
28+
Assert.Same(toolTip.VisualRoot, toolTip.PopupHost);
29+
}
1830
}
1931

20-
public class ToolTipTests_Overlay : ToolTipTests
32+
public class ToolTipTests_Overlay : ToolTipTests, IDisposable
2133
{
34+
private readonly IDisposable _toolTipOpenSubscription;
35+
36+
public ToolTipTests_Overlay()
37+
{
38+
_toolTipOpenSubscription = ToolTip.IsOpenProperty.Changed.Subscribe(new AnonymousObserver<AvaloniaPropertyChangedEventArgs<bool>>(e =>
39+
{
40+
if (e.Sender is Visual { VisualRoot: {} root } visual)
41+
OverlayLayer.GetOverlayLayer(visual).Measure(root.ClientSize);
42+
}));
43+
}
44+
45+
public void Dispose()
46+
{
47+
_toolTipOpenSubscription.Dispose();
48+
}
49+
2250
protected override TestServices ConfigureServices(TestServices baseServices) =>
2351
baseServices.With(windowingPlatform: new MockWindowingPlatform(popupImpl: window => null));
52+
53+
protected override void SetupWindowMock(Mock<IWindowImpl> windowImpl)
54+
{
55+
windowImpl.Setup(x => x.CreatePopup()).Returns(default(IPopupImpl));
56+
}
57+
58+
protected override void VerifyToolTipType(Control control)
59+
{
60+
var toolTip = control.GetValue(ToolTip.ToolTipProperty);
61+
Assert.IsType<OverlayPopupHost>(toolTip.PopupHost);
62+
Assert.Same(toolTip.VisualRoot, control.VisualRoot);
63+
}
2464
}
2565

2666
public abstract class ToolTipTests
2767
{
2868
protected abstract TestServices ConfigureServices(TestServices baseServices);
2969

70+
protected abstract void SetupWindowMock(Mock<IWindowImpl> windowImpl);
71+
72+
protected abstract void VerifyToolTipType(Control control);
73+
74+
private void AssertToolTipOpen(Control control)
75+
{
76+
Assert.True(ToolTip.GetIsOpen(control));
77+
VerifyToolTipType(control);
78+
}
79+
3080
private static readonly MouseDevice s_mouseDevice = new(new Pointer(0, PointerType.Mouse, true));
3181

3282
[Fact]
@@ -46,7 +96,7 @@ public void Should_Close_When_Control_Detaches()
4696

4797
SetupWindowAndActivateToolTip(panel, target);
4898

49-
Assert.True(ToolTip.GetIsOpen(target));
99+
AssertToolTipOpen(target);
50100

51101
panel.Children.Remove(target);
52102

@@ -74,7 +124,7 @@ public void Should_Close_When_Tip_Is_Opened_And_Detached_From_Visual_Tree()
74124

75125
mouseEnter(target);
76126

77-
Assert.True(ToolTip.GetIsOpen(target));
127+
AssertToolTipOpen(target);
78128

79129
panel.Children.Remove(target);
80130

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

96146
SetupWindowAndActivateToolTip(target);
97147

98-
Assert.True(ToolTip.GetIsOpen(target));
148+
AssertToolTipOpen(target);
99149
}
100150
}
101151

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

113163
SetupWindowAndActivateToolTip(target);
114164

115-
Assert.True(ToolTip.GetIsOpen(target));
165+
AssertToolTipOpen(target);
116166
Assert.Equal("Tip", target.GetValue(ToolTip.ToolTipProperty).Content);
117167

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

140190
timer.ForceFire();
141191

142-
Assert.True(ToolTip.GetIsOpen(target));
192+
AssertToolTipOpen(target);
143193
}
144194
}
145195

@@ -188,6 +238,7 @@ public void Setting_IsOpen_Should_Add_Open_Class()
188238
ToolTip.SetIsOpen(decorator, true);
189239

190240
Assert.Equal(new[] { ":open" }, toolTip.Classes);
241+
VerifyToolTipType(decorator);
191242
}
192243
}
193244

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

252+
var windowImpl = MockWindowingPlatform.CreateWindowMock();
253+
SetupWindowMock(windowImpl);
254+
var window = new Window(windowImpl.Object);
255+
202256
var decorator = new Decorator()
203257
{
204258
[ToolTip.TipProperty] = toolTip
@@ -211,6 +265,7 @@ public void Clearing_IsOpen_Should_Remove_Open_Class()
211265
window.Presenter.ApplyTemplate();
212266

213267
ToolTip.SetIsOpen(decorator, true);
268+
AssertToolTipOpen(decorator);
214269
ToolTip.SetIsOpen(decorator, false);
215270

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

231286
SetupWindowAndActivateToolTip(target);
232287

233-
Assert.True(ToolTip.GetIsOpen(target));
288+
AssertToolTipOpen(target);
234289

235290
target[ToolTip.TipProperty] = null;
236291

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

254309
mouseEnter(target);
255310

256-
Assert.True(ToolTip.GetIsOpen(target));
311+
AssertToolTipOpen(target);
257312

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

260315
mouseEnter(tooltip);
261316

262-
Assert.True(ToolTip.GetIsOpen(target));
317+
AssertToolTipOpen(target);
263318
}
264319
}
265320

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

279334
mouseEnter(target);
280-
Assert.True(ToolTip.GetIsOpen(target));
335+
AssertToolTipOpen(target);
281336

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

285-
Assert.True(ToolTip.GetIsOpen(target));
340+
AssertToolTipOpen(target);
286341

287342
mouseEnter(target);
288343

289-
Assert.True(ToolTip.GetIsOpen(target));
344+
AssertToolTipOpen(target);
290345
}
291346
}
292347

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

313368
mouseEnter(target);
314-
Assert.True(ToolTip.GetIsOpen(target));
369+
AssertToolTipOpen(target);
315370

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

319-
Assert.True(ToolTip.GetIsOpen(target));
374+
AssertToolTipOpen(target);
320375

321376
mouseEnter(other);
322377

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

354409
mouseEnter(target);
355-
Assert.True(ToolTip.GetIsOpen(target)); // no delay
410+
AssertToolTipOpen(target); // no delay
356411

357412
mouseEnter(other);
358413
Assert.True(ToolTip.GetIsOpen(other)); // delay skipped, a tooltip was already open
359414

360415
// Now disable the between-show system
361416

362417
mouseEnter(target);
363-
Assert.True(ToolTip.GetIsOpen(target));
418+
AssertToolTipOpen(target);
364419

365420
ToolTip.SetBetweenShowDelay(other, -1);
366421

@@ -389,7 +444,7 @@ public void ToolTip_Events_Order_Is_Defined()
389444

390445
SetupWindowAndActivateToolTip(target);
391446

392-
Assert.True(ToolTip.GetIsOpen(target));
447+
AssertToolTipOpen(target);
393448

394449
target[ToolTip.TipProperty] = null;
395450

@@ -442,7 +497,7 @@ public void ToolTip_Can_Be_Replaced_On_The_Fly_Via_Opening_Event()
442497

443498
SetupWindowAndActivateToolTip(target);
444499

445-
Assert.True(ToolTip.GetIsOpen(target));
500+
AssertToolTipOpen(target);
446501

447502
target[ToolTip.TipProperty] = null;
448503

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

465520
mouseEnter(target);
466-
Assert.True(ToolTip.GetIsOpen(target));
521+
AssertToolTipOpen(target);
467522

468523
var topLevel = TopLevel.GetTopLevel(target);
469524
topLevel.PlatformImpl.Input(new RawPointerEventArgs(s_mouseDevice, (ulong)DateTime.Now.Ticks, topLevel,
@@ -476,6 +531,8 @@ public void Should_Close_When_Pointer_Leaves_Window()
476531
private Action<Control> SetupWindowAndGetMouseEnterAction(Control windowContent, [CallerMemberName] string testName = null)
477532
{
478533
var windowImpl = MockWindowingPlatform.CreateWindowMock();
534+
SetupWindowMock(windowImpl);
535+
479536
var hitTesterMock = new Mock<IHitTester>();
480537

481538
var window = new Window(windowImpl.Object)

0 commit comments

Comments
 (0)