using System.Reflection; using AtomUI.Controls.Data; using Avalonia.Controls; using Avalonia.Threading; using Avalonia.VisualTree; using Shouldly; using Xunit; using AtomUIWindow = AtomUI.Desktop.Controls.Window; namespace AtomUI.Desktop.Controls.Tests.Transfer; public class TransferPinnedOpenTests { static TransferPinnedOpenTests() { AvaloniaTestApp.EnsureInitialized(); } [Fact] public void Pinned_Transfer_Opens_One_Source_Dropdown_And_Rejects_Normal_Hide() { var transfer = CreateTransfer(); var window = CreateWindow(transfer); try { window.Show(); Dispatcher.UIThread.RunJobs(); var dropdowns = GetDropdowns(transfer); var source = dropdowns.Single(dropdown => dropdown.ViewType == TransferViewType.Source); var target = dropdowns.Single(dropdown => dropdown.ViewType == TransferViewType.Target); SetPopupPinnedOpen(transfer, true); Dispatcher.UIThread.RunJobs(); GetPopupPinnedOpen(source).ShouldBeTrue(); GetPopupPinnedOpen(target).ShouldBeFalse(); var flyout = source.Flyout.ShouldBeOfType(); flyout.IsPopupPinnedOpen.ShouldBeTrue(); flyout.IsOpen.ShouldBeTrue(); flyout.Popup.ShouldBeOfType().IsPopupPinnedOpen.ShouldBeTrue(); flyout.Hide(); Dispatcher.UIThread.RunJobs(); flyout.IsOpen.ShouldBeTrue(); flyout.Popup.IsOpen.ShouldBeTrue(); } finally { SetPopupPinnedOpenIfAvailable(transfer, false); window.Close(); Dispatcher.UIThread.RunJobs(); } } [Fact] public void Pinned_Transfer_Detach_Closes_Old_And_Reattach_Reopens_Source_Dropdown() { var transfer = CreateTransfer(); var window = CreateWindow(transfer); try { window.Show(); Dispatcher.UIThread.RunJobs(); var source = GetSourceDropdown(transfer); SetPopupPinnedOpen(transfer, true); Dispatcher.UIThread.RunJobs(); var oldFlyout = source.Flyout.ShouldBeOfType(); oldFlyout.IsOpen.ShouldBeTrue(); window.Content = new Border(); Dispatcher.UIThread.RunJobs(); GetPopupPinnedOpen(source).ShouldBeFalse(); oldFlyout.IsPopupPinnedOpen.ShouldBeFalse(); oldFlyout.IsOpen.ShouldBeFalse(); oldFlyout.Popup.IsOpen.ShouldBeFalse(); window.Content = transfer; Dispatcher.UIThread.RunJobs(); var reattachedSource = GetSourceDropdown(transfer); GetPopupPinnedOpen(reattachedSource).ShouldBeTrue(); var newFlyout = reattachedSource.Flyout.ShouldBeOfType(); newFlyout.ShouldNotBeSameAs(oldFlyout); newFlyout.IsOpen.ShouldBeTrue(); } finally { SetPopupPinnedOpenIfAvailable(transfer, false); window.Close(); Dispatcher.UIThread.RunJobs(); } } [Fact] public void Transfer_Registers_A_Dropdown_Only_While_A_Pin_Request_Is_Active() { var transfer = CreateTransfer(); var window = CreateWindow(transfer); try { window.Show(); Dispatcher.UIThread.RunJobs(); GetPrivateField(transfer, "_pinnedOpenDropdown").ShouldBeNull(); SetPopupPinnedOpen(transfer, true); Dispatcher.UIThread.RunJobs(); var source = GetSourceDropdown(transfer); GetPrivateField(transfer, "_pinnedOpenDropdown").ShouldBeSameAs(source); var flyout = source.Flyout.ShouldBeOfType(); flyout.IsOpen.ShouldBeTrue(); SetPopupPinnedOpen(transfer, false); Dispatcher.UIThread.RunJobs(); GetPrivateField(transfer, "_pinnedOpenDropdown").ShouldBeNull(); flyout.IsPopupPinnedOpen.ShouldBeFalse(); flyout.IsOpen.ShouldBeTrue(); } finally { SetPopupPinnedOpenIfAvailable(transfer, false); window.Close(); Dispatcher.UIThread.RunJobs(); } } private static ListTransfer CreateTransfer() { return new ListTransfer { Width = 520, Height = 260, IsMotionEnabled = false, ItemsSource = [ new ListItemData { ItemKey = "first", Content = "First" }, new ListItemData { ItemKey = "second", Content = "Second" } ] }; } private static AtomUIWindow CreateWindow(Control content) { return new AtomUIWindow { Width = 640, Height = 360, Content = content }; } private static TransferSelectDropdown[] GetDropdowns(AbstractTransfer transfer) { return transfer.GetVisualDescendants() .OfType() .ToArray(); } private static TransferSelectDropdown GetSourceDropdown(AbstractTransfer transfer) { return GetDropdowns(transfer) .Single(dropdown => dropdown.ViewType == TransferViewType.Source); } private static void SetPopupPinnedOpen(object target, bool value) { var property = target.GetType().GetProperty( "IsPopupPinnedOpen", BindingFlags.Instance | BindingFlags.NonPublic); property.ShouldNotBeNull($"{target.GetType().Name} must own the internal popup pin contract.") .SetValue(target, value); } private static void SetPopupPinnedOpenIfAvailable(object target, bool value) { target.GetType() .GetProperty("IsPopupPinnedOpen", BindingFlags.Instance | BindingFlags.NonPublic)? .SetValue(target, value); } private static bool GetPopupPinnedOpen(object target) { var property = target.GetType().GetProperty( "IsPopupPinnedOpen", BindingFlags.Instance | BindingFlags.NonPublic); return property.ShouldNotBeNull($"{target.GetType().Name} must expose the internal popup pin adapter.") .GetValue(target) .ShouldBeOfType(); } private static T GetPrivateField(object target, string fieldName) { for (var type = target.GetType(); type != null; type = type.BaseType) { var field = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); if (field != null) { return (T)field.GetValue(target)!; } } throw new MissingFieldException(target.GetType().FullName, fieldName); } }