diff --git a/api/Avalonia.Headless.nupkg.xml b/api/Avalonia.Headless.nupkg.xml
new file mode 100644
index 0000000000..96c70a7793
--- /dev/null
+++ b/api/Avalonia.Headless.nupkg.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ CP0002
+ M:Avalonia.Headless.HeadlessWindowExtensions.GetOpenPopups(Avalonia.Controls.TopLevel)
+ baseline/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll
+ current/Avalonia.Headless/lib/net10.0/Avalonia.Headless.dll
+
+
+ CP0002
+ M:Avalonia.Headless.HeadlessWindowExtensions.GetOpenPopups(Avalonia.Controls.TopLevel)
+ baseline/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll
+ current/Avalonia.Headless/lib/net8.0/Avalonia.Headless.dll
+
+
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 75dd723fe3..ba9ca5b572 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Reactive;
@@ -153,6 +154,7 @@ namespace Avalonia.Controls.Primitives
private bool _isUsingOverlayLayer;
private PopupOpenState? _openState;
private Action? _popupHostChangedHandler;
+ private List? _openedPopups;
///
/// Initializes static members of the class.
@@ -177,6 +179,11 @@ namespace Avalonia.Controls.Primitives
internal IPopupHost? Host => _openState?.PopupHost;
+ ///
+ /// Gets the popups that are currently open directly inside this popup, in the order they were opened.
+ ///
+ public IReadOnlyList OpenedPopups => _openedPopups ?? (IReadOnlyList)[];
+
///
/// Gets or sets a hint to the window manager that a shadow should be added to the popup.
///
@@ -575,7 +582,12 @@ namespace Avalonia.Controls.Primitives
}
}
- _openState = new PopupOpenState(placementTarget, topLevel, popupHost, cleanupPopup);
+ _openState = new PopupOpenState(placementTarget, topLevel, popupHost, cleanupPopup, FindParentPopup(placementTarget));
+
+ if (_openState.ParentPopup is { } parentPopup)
+ parentPopup.AddOpenedPopup(this);
+ else
+ topLevel.AddOpenedPopup(this);
WindowManagerAddShadowHintChanged(popupHost, WindowManagerAddShadowHint);
@@ -844,6 +856,11 @@ namespace Avalonia.Controls.Primitives
return;
}
+ if (_openState.ParentPopup is { } parentPopup)
+ parentPopup.RemoveOpenedPopup(this);
+ else
+ _openState.TopLevel.RemoveOpenedPopup(this);
+
_openState.Dispose();
_openState = null;
@@ -1063,22 +1080,41 @@ namespace Avalonia.Controls.Primitives
}
}
+ internal void AddOpenedPopup(Popup popup) => (_openedPopups ??= new List(capacity: 2)).Add(popup);
+
+ internal void RemoveOpenedPopup(Popup popup) => _openedPopups?.Remove(popup);
+
+ private static Popup? FindParentPopup(Visual placementTarget)
+ {
+ foreach (var visual in placementTarget.GetSelfAndVisualAncestors())
+ {
+ if (visual is IPopupHost)
+ return (visual as StyledElement)?.Parent as Popup;
+ }
+
+ return null;
+ }
+
private class PopupOpenState : IDisposable
{
private readonly IDisposable _cleanup;
private IDisposable? _presenterCleanup;
private Control _placementTarget;
- public PopupOpenState(Control placementTarget, TopLevel topLevel, IPopupHost popupHost, IDisposable cleanup)
+ public PopupOpenState(Control placementTarget, TopLevel topLevel, IPopupHost popupHost, IDisposable cleanup,
+ Popup? parentPopup)
{
PlacementTarget = placementTarget;
TopLevel = topLevel;
+ ParentPopup = parentPopup;
PopupHost = popupHost;
_cleanup = cleanup;
}
public TopLevel TopLevel { get; }
+ public Popup? ParentPopup { get; }
+
public Control PlacementTarget
{
get => _placementTarget;
diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs
index 234f47258d..890bc09cb7 100644
--- a/src/Avalonia.Controls/Primitives/PopupRoot.cs
+++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Diagnostics;
@@ -122,6 +123,8 @@ namespace Avalonia.Controls.Primitives
public TopLevel ParentTopLevel { get; }
+ public override IReadOnlyList OpenedPopups => (Parent as Popup)?.OpenedPopups ?? [];
+
///
public void Dispose()
{
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 2fd1524cef..721b20ccab 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -132,6 +132,7 @@ namespace Avalonia.Controls
private TargetWeakEventSubscriber? _resourcesChangesSubscriber;
private IStorageProvider? _storageProvider;
private Screens? _screens;
+ private List? _openedPopups;
private readonly PresentationSource _source;
private readonly TopLevelHost _topLevelHost;
internal TopLevelHost TopLevelHost => _topLevelHost;
@@ -559,6 +560,14 @@ namespace Avalonia.Controls
// TODO: Un-private
private IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService();
+ ///
+ /// Gets the popups that are currently open directly in this top level, in the order they were opened.
+ ///
+ ///
+ /// Use for nested popups.
+ ///
+ public virtual IReadOnlyList OpenedPopups => _openedPopups ?? (IReadOnlyList)[];
+
///
/// Gets the for which the given is hosted in.
///
@@ -708,6 +717,7 @@ namespace Avalonia.Controls
LayoutManager.Dispose();
_platformImplBindings.Clear();
+ _openedPopups = null;
}
///
@@ -724,6 +734,10 @@ namespace Avalonia.Controls
Renderer.Resized(clientSize);
}
+ internal void AddOpenedPopup(Popup popup) => (_openedPopups ??= new List(capacity: 2)).Add(popup);
+
+ internal void RemoveOpenedPopup(Popup popup) => _openedPopups?.Remove(popup);
+
///
/// Handles a window scaling change notification from
/// .
diff --git a/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
index 4e14dc0f0b..2d39a9bc6b 100644
--- a/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
+++ b/src/Headless/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
@@ -121,8 +121,6 @@ namespace Avalonia.Headless
///
/// Embeds popups to the window when set to true. The default value is true.
- /// When disabled, popups are hosted in dedicated headless top-levels that are not part of
- /// the parent's visual tree; use to access them.
///
// TODO13: Change the default to false to match the other desktop platforms.
public bool OverlayPopups { get; set; } = true;
diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs b/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs
index 17fe5aa199..7f7232359b 100644
--- a/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs
+++ b/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs
@@ -115,18 +115,6 @@ public static class HeadlessWindowExtensions
DragDropEffects effects, RawInputModifiers modifiers = RawInputModifiers.None) =>
RunJobsOnImpl(topLevel, w => w.DragDrop(point, type, data, effects, modifiers));
- ///
- /// Returns the popups currently open directly above this toplevel, in z-order (bottom to top).
- /// For popups nested in another popup, call this method on that popup's toplevel.
- ///
- ///
- /// Only popups hosted in dedicated headless top-levels are returned, which requires disabling
- /// . Popups hosted in the overlay
- /// layer are part of the parent's visual tree and are not tracked by the platform.
- ///
- public static IReadOnlyList GetOpenPopups(this TopLevel topLevel) =>
- GetImpl(topLevel).GetOpenPopups();
-
///
/// Changes the render scaling (DPI) of the headless window/toplevel.
/// This simulates a DPI change, triggering scaling changed notifications and a layout pass.
diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
index 97a24d66b0..1ddaa4165e 100644
--- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
+++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
@@ -27,7 +27,6 @@ namespace Avalonia.Headless
private readonly AvaloniaHeadlessPlatformOptions _options;
private readonly HeadlessWindowImpl? _popupParent;
private readonly IPopupPositioner? _popupPositioner;
- private readonly List _openPopups = new();
public bool IsPopup { get; }
public HeadlessWindowImpl(AvaloniaHeadlessPlatformOptions options)
@@ -60,7 +59,6 @@ namespace Avalonia.Headless
public void Dispose()
{
- _popupParent?._openPopups.Remove(this);
Closed?.Invoke();
_lastRenderedFrame?.Dispose();
_lastRenderedFrame = null;
@@ -101,9 +99,6 @@ namespace Avalonia.Headless
public void Show(bool activate, bool isDialog)
{
- if (_popupParent != null && !_popupParent._openPopups.Contains(this))
- _popupParent._openPopups.Add(this);
-
if (activate)
{
ZOrder = _nextGlobalZOrder++;
@@ -113,7 +108,6 @@ namespace Avalonia.Headless
public void Hide()
{
- _popupParent?._openPopups.Remove(this);
Dispatcher.UIThread.Post(() => Deactivated?.Invoke(), DispatcherPriority.Input);
}
@@ -403,21 +397,6 @@ namespace Avalonia.Headless
public IPopupImpl? CreatePopup() => _options.OverlayPopups ? null : new HeadlessWindowImpl(this);
- public IReadOnlyList GetOpenPopups()
- {
- if (_openPopups.Count == 0)
- return Array.Empty();
-
- var result = new List(_openPopups.Count);
- foreach (var popup in _openPopups)
- {
- if (popup.InputRoot is PresentationSource { FocusRoot: TopLevel topLevel })
- result.Add(topLevel);
- }
-
- return result;
- }
-
public void SetWindowManagerAddShadowHint(bool enabled)
{
diff --git a/src/Headless/Avalonia.Headless/IHeadlessWindow.cs b/src/Headless/Avalonia.Headless/IHeadlessWindow.cs
index 5e2dd6a13d..3fe06d619c 100644
--- a/src/Headless/Avalonia.Headless/IHeadlessWindow.cs
+++ b/src/Headless/Avalonia.Headless/IHeadlessWindow.cs
@@ -19,6 +19,5 @@ namespace Avalonia.Headless
void MouseWheel(Point point, Vector delta, RawInputModifiers modifiers = RawInputModifiers.None);
void DragDrop(Point point, RawDragEventType type, IDataTransfer data, DragDropEffects effects, RawInputModifiers modifiers = RawInputModifiers.None);
void SetRenderScaling(double scaling);
- IReadOnlyList GetOpenPopups();
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
index 3cca86d3b4..3548cd614f 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
@@ -1392,6 +1392,109 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
+ [Fact]
+ public void Opened_Popup_Should_Be_In_OpenedPopups()
+ {
+ using (CreateServices())
+ {
+ var target = new Popup();
+ var window = PreparedWindow(target);
+
+ target.Open();
+
+ Assert.Equal(new[] { target }, window.OpenedPopups);
+
+ target.Close();
+
+ Assert.Empty(window.OpenedPopups);
+ }
+ }
+
+ [Fact]
+ public void Closing_Popup_With_IsOpen_Should_Remove_It_From_OpenedPopups()
+ {
+ using (CreateServices())
+ {
+ var target = new Popup();
+ var window = PreparedWindow(target);
+
+ target.IsOpen = true;
+
+ Assert.Equal(new[] { target }, window.OpenedPopups);
+
+ target.IsOpen = false;
+
+ Assert.Empty(window.OpenedPopups);
+ }
+ }
+
+ [Fact]
+ public void Closing_Window_Should_Clear_OpenedPopups()
+ {
+ using (CreateServices())
+ {
+ var target = new Popup();
+ var window = PreparedWindow(target);
+
+ target.Open();
+ window.Close();
+
+ Assert.Empty(window.OpenedPopups);
+ }
+ }
+
+ [Fact]
+ public void Nested_Popup_Should_Be_In_Parent_Popup_OpenedPopups()
+ {
+ using (CreateServices())
+ {
+ var nestedTarget = new Border { Width = 20, Height = 20 };
+ var nestedPopup = new Popup
+ {
+ PlacementTarget = nestedTarget,
+ Child = new Border { Width = 10, Height = 10 }
+ };
+ var target = new Border();
+ var popup = new Popup
+ {
+ PlacementTarget = target,
+ Child = new Panel { Children = { nestedTarget, nestedPopup } }
+ };
+ var window = PreparedWindow(new Panel { Children = { target, popup } });
+
+ popup.Open();
+
+ if (popup.Host is OverlayPopupHost host)
+ {
+ //Need to measure/arrange for visual children to show up
+ //in OverlayPopupHost
+ host.Measure(Size.Infinity);
+ host.Arrange(new Rect(host.DesiredSize));
+ }
+
+ nestedPopup.Open();
+
+ Assert.Equal([popup], window.OpenedPopups);
+ Assert.Equal([nestedPopup], popup.OpenedPopups);
+ Assert.Empty(nestedPopup.OpenedPopups);
+
+ if (popup.Host is PopupRoot popupRoot)
+ {
+ // A popup root exposes the popups opened by its own popup.
+ Assert.Equal([nestedPopup], popupRoot.OpenedPopups);
+ }
+
+ nestedPopup.Close();
+
+ Assert.Equal([popup], window.OpenedPopups);
+ Assert.Empty(popup.OpenedPopups);
+
+ popup.Close();
+
+ Assert.Empty(window.OpenedPopups);
+ }
+ }
+
private IDisposable CreateServices()
{
return UnitTestApplication.Start(TestServices.StyledWindow.With(
@@ -1430,13 +1533,22 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
if (UsePopupHost)
return null;
- return MockWindowingPlatform.CreatePopupMock(mock.Object).Object;
+ return CreatePopupMock(mock.Object);
});
return mock.Object;
}, null);
}
+ private static IPopupImpl CreatePopupMock(IWindowBaseImpl parent)
+ {
+ var mock = MockWindowingPlatform.CreatePopupMock(parent);
+
+ mock.Setup(x => x.CreatePopup()).Returns(() => CreatePopupMock(mock.Object));
+
+ return mock.Object;
+ }
+
private static Window PreparedWindow(object? content = null)
{
var w = new Window { Content = content };
diff --git a/tests/Avalonia.Headless.UnitTests/AssertHelper.cs b/tests/Avalonia.Headless.UnitTests/AssertHelper.cs
index 1c98afcb9a..c3ba56d05f 100644
--- a/tests/Avalonia.Headless.UnitTests/AssertHelper.cs
+++ b/tests/Avalonia.Headless.UnitTests/AssertHelper.cs
@@ -1,5 +1,7 @@
#nullable enable
+using System.Diagnostics.CodeAnalysis;
+
namespace Avalonia.Headless.UnitTests;
internal static class AssertHelper
@@ -22,14 +24,26 @@ internal static class AssertHelper
#endif
}
- public static void NotNull(object? value)
+ public static void Null(object? value)
+ {
+#if NUNIT
+ Assert.That(value, Is.Null);
+#elif XUNIT
+ Assert.Null(value);
+#endif
+ }
+
+ public static void NotNull([NotNull] object? value)
{
#if NUNIT
Assert.That(value, Is.Not.Null);
#elif XUNIT
Assert.NotNull(value);
#endif
+ // NUnit doesn't suppress CS8777 warning on its own
+#pragma warning disable CS8777 // Parameter must have a non-null value when exiting.
}
+#pragma warning restore CS8777 // Parameter must have a non-null value when exiting.
public static void Equal(T expected, T actual)
{
diff --git a/tests/Avalonia.Headless.UnitTests/MouseDeviceTests.cs b/tests/Avalonia.Headless.UnitTests/MouseDeviceTests.cs
index d501635fbe..8c2fa92bb7 100644
--- a/tests/Avalonia.Headless.UnitTests/MouseDeviceTests.cs
+++ b/tests/Avalonia.Headless.UnitTests/MouseDeviceTests.cs
@@ -65,7 +65,11 @@ public class MouseDeviceTests
// Pressing captures the pointer implicitly on the window's border.
window.MouseDown(new Point(50, 50), MouseButton.Left);
- window.GetOpenPopups()[0].MouseMove(new Point(40, 15));
+
+ var popupRoot = PopupTests.GetPopupTopLevel(popup);
+ AssertHelper.NotNull(popupRoot);
+
+ popupRoot.MouseMove(new Point(40, 15));
AssertHelper.Same(TestApplication.UsesSharedMouseDevice ? target : popupChild, moveTarget);
diff --git a/tests/Avalonia.Headless.UnitTests/PopupTests.cs b/tests/Avalonia.Headless.UnitTests/PopupTests.cs
index 2a1536ce50..2eef10a0c4 100644
--- a/tests/Avalonia.Headless.UnitTests/PopupTests.cs
+++ b/tests/Avalonia.Headless.UnitTests/PopupTests.cs
@@ -32,7 +32,7 @@ public class PopupTests
#elif XUNIT
[AvaloniaFact]
#endif
- public void Popup_Uses_Dedicated_TopLevel_And_Is_Discoverable()
+ public void Popup_Uses_Dedicated_TopLevel()
{
var target = new Border { Background = Brushes.Red };
var popup = new Popup
@@ -49,22 +49,22 @@ public class PopupTests
window.Show();
Dispatcher.UIThread.RunJobs();
- AssertHelper.Equal(0, window.GetOpenPopups().Count);
+ AssertHelper.False(popup.IsOpen);
+ AssertHelper.False(popup.IsUsingOverlayLayer);
+ AssertHelper.Null(GetPopupTopLevel(popup));
popup.Open();
Dispatcher.UIThread.RunJobs();
AssertHelper.True(popup.IsOpen);
AssertHelper.False(popup.IsUsingOverlayLayer);
- AssertHelper.Equal(1, window.GetOpenPopups().Count);
- AssertHelper.True(window.GetOpenPopups()[0] is PopupRoot);
-
- popup.Close();
- Dispatcher.UIThread.RunJobs();
-
- AssertHelper.Equal(0, window.GetOpenPopups().Count);
+ AssertHelper.True(GetPopupTopLevel(popup) is PopupRoot);
window.Close();
+
+ AssertHelper.False(popup.IsOpen);
+ AssertHelper.False(popup.IsUsingOverlayLayer);
+ AssertHelper.Null(GetPopupTopLevel(popup));
}
#if NUNIT
@@ -72,31 +72,40 @@ public class PopupTests
#elif XUNIT
[AvaloniaFact]
#endif
- public void Can_Click_Button_Inside_Platform_Popup()
+ public void Popup_Placement_Respects_Window_Position()
{
- var clickCount = 0;
- var button = new Button { Width = 80, Height = 30 };
- button.Click += (_, _) => clickCount++;
-
- var target = new Border { Background = Brushes.Red };
- var popup = new Popup { PlacementTarget = target, Child = button };
+ var target = new Border
+ {
+ Width = 20,
+ Height = 20,
+ HorizontalAlignment = HorizontalAlignment.Left,
+ VerticalAlignment = VerticalAlignment.Top,
+ Background = Brushes.Red
+ };
+ var popup = new Popup
+ {
+ PlacementTarget = target,
+ Placement = PlacementMode.Bottom,
+ Child = new Border { Width = 20, Height = 20 }
+ };
var window = new Window
{
Width = 100,
Height = 100,
Content = new Panel { Children = { target, popup } }
};
+ window.Position = new PixelPoint(100, 200);
window.Show();
Dispatcher.UIThread.RunJobs();
popup.Open();
Dispatcher.UIThread.RunJobs();
- var popupRoot = window.GetOpenPopups()[0];
- popupRoot.MouseDown(new Point(40, 15), MouseButton.Left);
- popupRoot.MouseUp(new Point(40, 15), MouseButton.Left);
+ var popupRoot = GetPopupTopLevel(popup);
+ AssertHelper.NotNull(popupRoot);
- AssertHelper.Equal(1, clickCount);
+ var expected = target.PointToScreen(new Point(0, target.Bounds.Height));
+ AssertHelper.Equal(expected, popupRoot.PointToScreen(default));
window.Close();
}
@@ -106,38 +115,33 @@ public class PopupTests
#elif XUNIT
[AvaloniaFact]
#endif
- public void Popup_Placement_Respects_Window_Position()
+ public void Can_Click_Button_Inside_Platform_Popup()
{
- var target = new Border
- {
- Width = 20,
- Height = 20,
- HorizontalAlignment = HorizontalAlignment.Left,
- VerticalAlignment = VerticalAlignment.Top,
- Background = Brushes.Red
- };
- var popup = new Popup
- {
- PlacementTarget = target,
- Placement = PlacementMode.Bottom,
- Child = new Border { Width = 20, Height = 20 }
- };
+ var clickCount = 0;
+ var button = new Button { Width = 80, Height = 30 };
+ button.Click += (_, _) => clickCount++;
+
+ var target = new Border { Background = Brushes.Red };
+ var popup = new Popup { PlacementTarget = target, Child = button };
var window = new Window
{
Width = 100,
Height = 100,
Content = new Panel { Children = { target, popup } }
};
- window.Position = new PixelPoint(100, 200);
window.Show();
Dispatcher.UIThread.RunJobs();
popup.Open();
Dispatcher.UIThread.RunJobs();
- var popupRoot = window.GetOpenPopups()[0];
- var expected = target.PointToScreen(new Point(0, target.Bounds.Height));
- AssertHelper.Equal(expected, popupRoot.PointToScreen(default));
+ var popupRoot = GetPopupTopLevel(popup);
+ AssertHelper.NotNull(popupRoot);
+
+ popupRoot.MouseDown(new Point(40, 15), MouseButton.Left);
+ popupRoot.MouseUp(new Point(40, 15), MouseButton.Left);
+
+ AssertHelper.Equal(1, clickCount);
window.Close();
}
@@ -147,7 +151,7 @@ public class PopupTests
#elif XUNIT
[AvaloniaFact]
#endif
- public void Nested_Popup_Is_Child_Of_Popup_Root()
+ public void Nested_Popup_Is_Owned_By_Parent_Popup()
{
var nestedTarget = new Border { Width = 20, Height = 20, Background = Brushes.Green };
var nestedPopup = new Popup
@@ -175,11 +179,34 @@ public class PopupTests
nestedPopup.Open();
Dispatcher.UIThread.RunJobs();
- var popupRoot = window.GetOpenPopups()[0];
- AssertHelper.Equal(1, window.GetOpenPopups().Count);
- AssertHelper.Equal(1, popupRoot.GetOpenPopups().Count);
- AssertHelper.True(popupRoot.GetOpenPopups()[0] is PopupRoot);
+ AssertHelper.Equal(1, window.OpenedPopups.Count);
+ AssertHelper.Same(popup, window.OpenedPopups[0]);
+
+ AssertHelper.Equal(1, popup.OpenedPopups.Count);
+ AssertHelper.Same(nestedPopup, popup.OpenedPopups[0]);
+ AssertHelper.Equal(0, nestedPopup.OpenedPopups.Count);
+
+ // The nested popup is hosted in the parent popup's own top level.
+ AssertHelper.Same(GetPopupTopLevel(popup), TopLevel.GetTopLevel(nestedTarget));
+
+ nestedPopup.Close();
+ Dispatcher.UIThread.RunJobs();
+
+ AssertHelper.Equal(0, popup.OpenedPopups.Count);
+ AssertHelper.Equal(1, window.OpenedPopups.Count);
+
+ popup.Close();
+ Dispatcher.UIThread.RunJobs();
+
+ AssertHelper.Equal(0, window.OpenedPopups.Count);
window.Close();
}
+
+ internal static TopLevel GetPopupTopLevel(Popup popup)
+ {
+ AssertHelper.NotNull(popup.Child);
+ var topLevel = TopLevel.GetTopLevel(popup.Child);
+ return topLevel;
+ }
}
diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
index 298785df32..eef60186d3 100644
--- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
+++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
@@ -101,6 +101,7 @@ namespace Avalonia.UnitTests
popupImpl.Setup(x => x.Compositor).Returns(compositor);
popupImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
popupImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize);
+ popupImpl.Setup(x => x.DesktopScaling).Returns(1);
popupImpl.Setup(x => x.RenderScaling).Returns(1);
popupImpl.Setup(x => x.PopupPositioner).Returns(positioner);
popupImpl.Setup(x => x.Position).Returns(()=>position);