From 1f1b78e50b973cea09a56f4b4ddf935cb002a1e6 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 28 Aug 2026 16:37:39 +0200 Subject: [PATCH] test(app): add app test scaffolding and headless test harness --- .../Vantage.App.Tests/Fakes/StubProviders.cs | 31 ++++++++++++++++ tests/Vantage.App.Tests/HeadlessTestApp.cs | 15 ++++++++ .../Vantage.App.Tests/HeadlessWindowTests.cs | 37 +++++++++++++++++++ .../Vantage.App.Tests.csproj | 22 +++++++++++ 4 files changed, 105 insertions(+) create mode 100644 tests/Vantage.App.Tests/Fakes/StubProviders.cs create mode 100644 tests/Vantage.App.Tests/HeadlessTestApp.cs create mode 100644 tests/Vantage.App.Tests/HeadlessWindowTests.cs create mode 100644 tests/Vantage.App.Tests/Vantage.App.Tests.csproj diff --git a/tests/Vantage.App.Tests/Fakes/StubProviders.cs b/tests/Vantage.App.Tests/Fakes/StubProviders.cs new file mode 100644 index 0000000..4b9ffbb --- /dev/null +++ b/tests/Vantage.App.Tests/Fakes/StubProviders.cs @@ -0,0 +1,31 @@ +using Vantage.Core.Health; +using Vantage.Core.Metrics; +using Vantage.Core.Models; + +namespace Vantage.App.Tests.Fakes; + +public sealed class ThrowingMetricsProvider : IMetricsProvider +{ + public const string Message = "host unreachable"; + + public Task GetMetricsAsync(HostConfig host, CancellationToken cancellationToken) => + throw new InvalidOperationException(Message); +} + +public sealed class FakeHealthChecker : IHealthChecker +{ + private readonly HealthCheckResult result; + + public FakeHealthChecker(HealthCheckResult result) + { + this.result = result; + } + + public int Calls { get; private set; } + + public Task CheckAsync(HealthCheckConfig check, CancellationToken cancellationToken) + { + Calls++; + return Task.FromResult(result); + } +} diff --git a/tests/Vantage.App.Tests/HeadlessTestApp.cs b/tests/Vantage.App.Tests/HeadlessTestApp.cs new file mode 100644 index 0000000..88b6e95 --- /dev/null +++ b/tests/Vantage.App.Tests/HeadlessTestApp.cs @@ -0,0 +1,15 @@ +using Avalonia; +using Avalonia.Headless; +using Vantage.App; +using Vantage.App.Tests; + +[assembly: AvaloniaTestApplication(typeof(HeadlessTestApp))] + +namespace Vantage.App.Tests; + +public static class HeadlessTestApp +{ + public static AppBuilder BuildAvaloniaApp() => AppBuilder + .Configure() + .UseHeadless(new AvaloniaHeadlessPlatformOptions()); +} diff --git a/tests/Vantage.App.Tests/HeadlessWindowTests.cs b/tests/Vantage.App.Tests/HeadlessWindowTests.cs new file mode 100644 index 0000000..46663cd --- /dev/null +++ b/tests/Vantage.App.Tests/HeadlessWindowTests.cs @@ -0,0 +1,37 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Headless.XUnit; +using Avalonia.VisualTree; +using Microsoft.Extensions.DependencyInjection; +using Vantage.App; +using Vantage.App.ViewModels; +using Vantage.App.Views; +using Xunit; + +namespace Vantage.App.Tests; + +public class HeadlessWindowTests +{ + [AvaloniaFact] + public async Task MainWindow_LoadsXamlAndBindsViewModel() + { + var services = new ServiceCollection(); + services.AddVantageApp(useMockProviders: true); + var provider = services.BuildServiceProvider(); + + var viewModel = provider.GetRequiredService(); + await viewModel.InitializeAsync(); + + var window = new MainWindow { DataContext = viewModel }; + window.Show(); + + Assert.Same(viewModel, window.DataContext); + Assert.NotEmpty(viewModel.Hosts); + + var tabControl = window.GetVisualDescendants().OfType().FirstOrDefault(); + Assert.NotNull(tabControl); + + var listBox = window.GetVisualDescendants().OfType().FirstOrDefault(); + Assert.NotNull(listBox); + } +} diff --git a/tests/Vantage.App.Tests/Vantage.App.Tests.csproj b/tests/Vantage.App.Tests/Vantage.App.Tests.csproj new file mode 100644 index 0000000..3a40616 --- /dev/null +++ b/tests/Vantage.App.Tests/Vantage.App.Tests.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + enable + latest + false + + + + + + + + + + + + + +