mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-29 07:16:33 +08:00
180 lines
6.3 KiB
C#
180 lines
6.3 KiB
C#
using System.Reflection;
|
|
using AtomUI.Controls.Primitives;
|
|
using AtomUI.MotionScene;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Threading;
|
|
using Avalonia.VisualTree;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace AtomUI.Desktop.Controls.Tests.Dialog;
|
|
|
|
public class DialogMotionAnchorTests
|
|
{
|
|
static DialogMotionAnchorTests()
|
|
{
|
|
AvaloniaTestApp.EnsureInitialized();
|
|
}
|
|
|
|
[Fact]
|
|
public void Dialog_Without_Explicit_PlacementTarget_Uses_Fade_Motion()
|
|
{
|
|
var placementTarget = new Border();
|
|
var dialog = new AtomUI.Desktop.Controls.Dialog();
|
|
var presenter = new OverlayDialogPresenter(dialog, placementTarget);
|
|
|
|
CreateSurfaceMotion(presenter, isOpening: true).ShouldBeOfType<FadeInMotion>();
|
|
CreateSurfaceMotion(presenter, isOpening: false).ShouldBeOfType<FadeOutMotion>();
|
|
}
|
|
|
|
[Fact]
|
|
public void Dialog_With_Explicit_PlacementTarget_Uses_Anchored_Zoom_Motion()
|
|
{
|
|
var placementTarget = new Border();
|
|
var dialog = new AtomUI.Desktop.Controls.Dialog
|
|
{
|
|
PlacementTarget = placementTarget
|
|
};
|
|
var presenter = new OverlayDialogPresenter(dialog, placementTarget);
|
|
|
|
CreateSurfaceMotion(presenter, isOpening: true).ShouldBeOfType<DialogZoomInMotion>();
|
|
CreateSurfaceMotion(presenter, isOpening: false).ShouldBeOfType<DialogZoomOutMotion>();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false, false)]
|
|
[InlineData(true, true)]
|
|
public void Static_Dialog_Only_Uses_An_Explicit_Target_As_Motion_Anchor(
|
|
bool hasExplicitTarget,
|
|
bool shouldUseAnchor)
|
|
{
|
|
var fallbackTarget = new Border();
|
|
var explicitTarget = new Border();
|
|
var dialog = CreateStaticDialog(
|
|
fallbackTarget,
|
|
hasExplicitTarget
|
|
? new DialogOptions { PlacementTarget = explicitTarget }
|
|
: null);
|
|
var presenter = new OverlayDialogPresenter(
|
|
dialog,
|
|
hasExplicitTarget ? explicitTarget : fallbackTarget);
|
|
|
|
var openingMotion = CreateSurfaceMotion(presenter, isOpening: true);
|
|
|
|
if (shouldUseAnchor)
|
|
{
|
|
openingMotion.ShouldBeOfType<DialogZoomInMotion>();
|
|
}
|
|
else
|
|
{
|
|
openingMotion.ShouldBeOfType<FadeInMotion>();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Closing_Motion_Keeps_Footer_Buttons_Until_The_Presenter_Is_Removed()
|
|
{
|
|
RunOnUIThread(() =>
|
|
{
|
|
var placementTarget = new Border { Width = 80, Height = 32 };
|
|
var root = new ScopeAwareOverlayLayerPanel
|
|
{
|
|
Width = 480,
|
|
Height = 360,
|
|
Children = { placementTarget }
|
|
};
|
|
var window = new AtomUI.Desktop.Controls.Window
|
|
{
|
|
Width = 480,
|
|
Height = 360,
|
|
Content = root
|
|
};
|
|
var dialog = new AtomUI.Desktop.Controls.Dialog
|
|
{
|
|
Content = "Dialog",
|
|
StandardButtons = DialogStandardButton.Ok,
|
|
IsMotionEnabled = false,
|
|
HostWidth = 240,
|
|
HostHeight = 160
|
|
};
|
|
var presenter = new OverlayDialogPresenter(dialog, placementTarget);
|
|
|
|
try
|
|
{
|
|
window.Show();
|
|
WaitWithDispatcherPump(presenter.ShowAsync(CancellationToken.None).AsTask());
|
|
var buttonBox = presenter.GetVisualDescendants()
|
|
.OfType<DialogButtonBox>()
|
|
.ShouldHaveSingleItem();
|
|
var button = buttonBox.GetVisualDescendants()
|
|
.OfType<AtomUI.Desktop.Controls.DialogButton>()
|
|
.ShouldHaveSingleItem();
|
|
var contentLayer = presenter.Surface.SurfaceContentLayer.ShouldNotBeNull();
|
|
var originalSurfaceBounds = presenter.Surface.Bounds;
|
|
|
|
presenter.IsMotionEnabled = true;
|
|
presenter.MotionDuration = TimeSpan.FromMilliseconds(80);
|
|
var closeTask = presenter.CloseAsync().AsTask();
|
|
|
|
closeTask.IsCompleted.ShouldBeFalse();
|
|
presenter.Parent.ShouldBeOfType<DialogOverlayLayer>();
|
|
button.IsAttachedToVisualTree().ShouldBeTrue();
|
|
contentLayer.IsAttachedToVisualTree().ShouldBeTrue();
|
|
presenter.Surface.Bounds.ShouldBe(originalSurfaceBounds);
|
|
buttonBox.GetVisualDescendants()
|
|
.OfType<AtomUI.Desktop.Controls.DialogButton>()
|
|
.Count()
|
|
.ShouldBe(1);
|
|
|
|
WaitWithDispatcherPump(closeTask);
|
|
presenter.Parent.ShouldBeNull();
|
|
WaitWithDispatcherPump(presenter.DisposeAsync().AsTask());
|
|
}
|
|
finally
|
|
{
|
|
window.Close();
|
|
}
|
|
});
|
|
}
|
|
|
|
private static object CreateSurfaceMotion(OverlayDialogPresenter presenter, bool isOpening)
|
|
{
|
|
var method = typeof(OverlayDialogPresenter).GetMethod(
|
|
"CreateSurfaceMotion",
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
method.ShouldNotBeNull();
|
|
return method.Invoke(presenter, [isOpening]).ShouldNotBeNull();
|
|
}
|
|
|
|
private static AtomUI.Desktop.Controls.Dialog CreateStaticDialog(
|
|
Control placementTarget,
|
|
DialogOptions? options)
|
|
{
|
|
var createDialog = typeof(AtomUI.Desktop.Controls.Dialog).GetMethod(
|
|
"CreateDialog",
|
|
BindingFlags.Static | BindingFlags.NonPublic);
|
|
createDialog.ShouldNotBeNull();
|
|
return (AtomUI.Desktop.Controls.Dialog)createDialog.Invoke(
|
|
null,
|
|
[new AtomUI.Desktop.Controls.TextBlock { Text = "Dialog" }, null, options, placementTarget])!;
|
|
}
|
|
|
|
private static void WaitWithDispatcherPump(Task task)
|
|
{
|
|
var timeoutAt = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(5);
|
|
while (!task.IsCompleted && DateTimeOffset.UtcNow < timeoutAt)
|
|
{
|
|
Dispatcher.UIThread.RunJobs();
|
|
Thread.Sleep(1);
|
|
}
|
|
|
|
task.IsCompleted.ShouldBeTrue();
|
|
task.GetAwaiter().GetResult();
|
|
}
|
|
|
|
private static void RunOnUIThread(Action action)
|
|
{
|
|
Dispatcher.UIThread.Invoke(action);
|
|
}
|
|
}
|