diff --git a/StabilityMatrix.UITests/Attributes/TestPriorityAttribute.cs b/StabilityMatrix.UITests/Attributes/TestPriorityAttribute.cs
new file mode 100644
index 00000000..5736d2d1
--- /dev/null
+++ b/StabilityMatrix.UITests/Attributes/TestPriorityAttribute.cs
@@ -0,0 +1,12 @@
+namespace StabilityMatrix.UITests.Attributes;
+
+[AttributeUsage(AttributeTargets.Method)]
+public class TestPriorityAttribute : Attribute
+{
+ public int Priority { get; private set; }
+
+ public TestPriorityAttribute(int priority)
+ {
+ Priority = priority;
+ }
+}
diff --git a/StabilityMatrix.UITests/Extensions/VisualExtensions.cs b/StabilityMatrix.UITests/Extensions/VisualExtensions.cs
new file mode 100644
index 00000000..dc57d373
--- /dev/null
+++ b/StabilityMatrix.UITests/Extensions/VisualExtensions.cs
@@ -0,0 +1,17 @@
+using Avalonia.Controls;
+
+namespace StabilityMatrix.UITests.Extensions;
+
+public static class VisualExtensions
+{
+ public static Rect GetRelativeBounds(this Visual visual, TopLevel topLevel)
+ {
+ var origin =
+ visual.TranslatePoint(new Point(0, 0), topLevel)
+ ?? throw new NullReferenceException("Origin is null");
+
+ var bounds = new Rect(origin, visual.Bounds.Size);
+
+ return bounds;
+ }
+}
diff --git a/StabilityMatrix.UITests/Extensions/WindowExtensions.cs b/StabilityMatrix.UITests/Extensions/WindowExtensions.cs
new file mode 100644
index 00000000..922ea147
--- /dev/null
+++ b/StabilityMatrix.UITests/Extensions/WindowExtensions.cs
@@ -0,0 +1,73 @@
+using Avalonia.Controls;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+
+namespace StabilityMatrix.UITests.Extensions;
+
+///
+/// Window extensions for UI tests
+///
+public static class WindowExtensions
+{
+ public static void ClickTarget(this TopLevel topLevel, Control target)
+ {
+ // Check target is part of the visual tree
+ var targetVisualRoot = target.GetVisualRoot();
+ if (targetVisualRoot is not TopLevel)
+ {
+ throw new ArgumentException("Target is not part of the visual tree");
+ }
+ if (targetVisualRoot.Equals(topLevel))
+ {
+ throw new ArgumentException(
+ "Target is not part of the same visual tree as the top level"
+ );
+ }
+
+ var point =
+ target.TranslatePoint(
+ new Point(target.Bounds.Width / 2, target.Bounds.Height / 2),
+ topLevel
+ ) ?? throw new NullReferenceException("Point is null");
+
+ topLevel.MouseMove(point);
+ topLevel.MouseDown(point, MouseButton.Left);
+ topLevel.MouseUp(point, MouseButton.Left);
+
+ // Return mouse to outside of window
+ topLevel.MouseMove(new Point(-50, -50));
+ }
+
+ public static async Task ClickTargetAsync(this TopLevel topLevel, Control target)
+ {
+ // Check target is part of the visual tree
+ var targetVisualRoot = target.GetVisualRoot();
+ if (targetVisualRoot is not TopLevel)
+ {
+ throw new ArgumentException("Target is not part of the visual tree");
+ }
+ if (!targetVisualRoot.Equals(topLevel))
+ {
+ throw new ArgumentException(
+ "Target is not part of the same visual tree as the top level"
+ );
+ }
+
+ var point =
+ target.TranslatePoint(
+ new Point(target.Bounds.Width / 2, target.Bounds.Height / 2),
+ topLevel
+ ) ?? throw new NullReferenceException("Point is null");
+
+ topLevel.MouseMove(point);
+ topLevel.MouseDown(point, MouseButton.Left);
+ topLevel.MouseUp(point, MouseButton.Left);
+
+ await Task.Delay(40);
+
+ // Return mouse to outside of window
+ topLevel.MouseMove(new Point(-50, -50));
+
+ Dispatcher.UIThread.Invoke(() => Dispatcher.UIThread.RunJobs());
+ }
+}
diff --git a/StabilityMatrix.UITests/MainWindowTests.cs b/StabilityMatrix.UITests/MainWindowTests.cs
index 50614ed0..4c890b7a 100644
--- a/StabilityMatrix.UITests/MainWindowTests.cs
+++ b/StabilityMatrix.UITests/MainWindowTests.cs
@@ -1,5 +1,6 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
+using Avalonia.Threading;
using Avalonia.VisualTree;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Windowing;
@@ -9,15 +10,19 @@ using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.ViewModels;
using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Avalonia.Views.Dialogs;
+using StabilityMatrix.UITests.Extensions;
namespace StabilityMatrix.UITests;
[UsesVerify]
[Collection("TempDir")]
+[TestCaseOrderer("StabilityMatrix.UITests.PriorityOrderer", "StabilityMatrix.UITests")]
public class MainWindowTests
{
private static IServiceProvider Services => App.Services;
+ private static (AppWindow, MainWindowViewModel)? currentMainWindow;
+
private static VerifySettings Settings
{
get
@@ -28,23 +33,32 @@ public class MainWindowTests
vm => vm.FooterPages,
vm => vm.CurrentPage
);
+ settings.DisableDiff();
return settings;
}
}
private static (AppWindow, MainWindowViewModel) GetMainWindow()
{
+ if (currentMainWindow is not null)
+ {
+ return currentMainWindow.Value;
+ }
+
var window = Services.GetRequiredService();
var viewModel = Services.GetRequiredService();
window.DataContext = viewModel;
window.SetDefaultFonts();
+ window.Width = 1400;
+ window.Height = 900;
App.VisualRoot = window;
App.StorageProvider = window.StorageProvider;
App.Clipboard = window.Clipboard ?? throw new NullReferenceException("Clipboard is null");
- return (window, viewModel);
+ currentMainWindow = (window, viewModel);
+ return currentMainWindow.Value;
}
private static BetterContentDialog? GetWindowDialog(Visual window)
@@ -58,22 +72,46 @@ public class MainWindowTests
?.FindDescendantOfType();
}
- [AvaloniaFact]
- public Task MainWindowViewModel_ShouldOk()
+ private static IEnumerable EnumerateWindowDialogs(Visual window)
{
- var viewModel = Services.GetRequiredService();
+ return window
+ .FindDescendantOfType()
+ ?.FindDescendantOfType()
+ ?.FindDescendantOfType()
+ ?.FindDescendantOfType()
+ ?.FindDescendantOfType()
+ ?.GetVisualDescendants()
+ .OfType() ?? Enumerable.Empty();
+ }
- return Verify(viewModel, Settings);
+ private async Task<(BetterContentDialog, T)> WaitForDialog(Visual window)
+ where T : Control
+ {
+ var dialogs = await WaitHelper.WaitForConditionAsync(
+ () => EnumerateWindowDialogs(window).ToList(),
+ list => list.Any(dialog => dialog.Content is T)
+ );
+
+ if (dialogs.Count == 0)
+ {
+ throw new InvalidOperationException("No dialogs found");
+ }
+
+ var contentDialog = dialogs.First(dialog => dialog.Content is T);
+
+ return (contentDialog, contentDialog.Content as T)!;
}
- [AvaloniaFact]
+ [AvaloniaFact, TestPriority(1)]
public async Task MainWindow_ShouldOpen()
{
- var (window, vm) = GetMainWindow();
+ var (window, _) = GetMainWindow();
window.Show();
- await Task.Delay(800);
+ await Task.Delay(300);
+
+ Dispatcher.UIThread.RunJobs();
// Find the select data directory dialog
var selectDataDirectoryDialog = await WaitHelper.WaitForNotNullAsync(
@@ -86,7 +124,8 @@ public class MainWindowTests
.GetVisualDescendants()
.OfType