using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Input;
using DodoSSH.Client.App.Views;
namespace DodoSSH.Client.App.Layout.Tests;
///
/// The harness measuring itself.
///
///
/// A clipping detector that never fires is worse than no detector, because it reads as a guarantee. So the
/// deliberately-broken window below is the most important test in this file: it is the one that proves the
/// green ones mean something. This is the same practice §5 of the handoff describes — a test proves nothing
/// until it has been seen to fail — applied to the instrument rather than to the code.
///
public sealed class LayoutHarnessTests
{
private static CancellationToken Token => TestContext.Current.CancellationToken;
[Fact]
public async Task AWindowWithRoomToSpare_ReportsNothing()
{
await LayoutHarness.OnTheUiThreadAsync(
() =>
{
var window = LayoutHarness.HostAtMinimumSize(
new Button { Content = "Save" },
LayoutHarness.MinimumWidth,
LayoutHarness.MinimumHeight);
try
{
LayoutHarness.Unreachable(window).ShouldBeEmpty();
}
finally
{
window.Close();
}
},
Token);
}
[Fact]
public async Task AButtonPushedPastTheBottomEdge_IsReportedByName()
{
// The instrument's own calibration. A stack taller than its window is exactly the shape of the
// defect the vault column is one editor away from, and if this passes silently the harness is
// decoration.
await LayoutHarness.OnTheUiThreadAsync(
() =>
{
var stack = new StackPanel();
for (var i = 0; i < 8; i++)
{
stack.Children.Add(new Button { Content = i == 7 ? "Save" : $"filler {i}" });
}
var window = LayoutHarness.HostAtMinimumSize(stack, 300, 120);
try
{
var faults = LayoutHarness.Unreachable(window);
faults.ShouldNotBeEmpty();
faults.ShouldContain(fault => fault.Contains("'Save'", StringComparison.Ordinal));
}
finally
{
window.Close();
}
},
Token);
}
[Fact]
public async Task AListLongerThanItsViewport_IsNotAFault()
{
// The exemption that keeps this harness usable. Scrolling is how a list is supposed to handle more
// rows than fit; without this the host list would fail the moment it had content.
await LayoutHarness.OnTheUiThreadAsync(
() =>
{
var stack = new StackPanel();
for (var i = 0; i < 40; i++)
{
stack.Children.Add(new Button { Content = $"row {i}" });
}
var window = LayoutHarness.HostAtMinimumSize(
new ScrollViewer { Content = stack },
300,
120);
try
{
LayoutHarness.Unreachable(window).ShouldBeEmpty();
}
finally
{
window.Close();
}
},
Token);
}
[Fact]
public async Task TheWholeWindowsXamlParses()
{
// Constructing it runs InitializeComponent, so this is what catches malformed XAML, a style selector
// that no longer resolves or a converter reference that has gone stale. Cheap, and it covers the
// whole file including the setup cards no other test here touches.
//
// Constructed and never shown, deliberately — see WhyTheWindowItselfIsNeverShown.
await LayoutHarness.OnTheUiThreadAsync(
() =>
{
var window = new MainWindow();
try
{
window.Content.ShouldNotBeNull();
}
finally
{
window.Close();
}
},
Token);
}
[Fact]
public async Task WhyTheWindowItselfIsNeverShown()
{
// Measured, not assumed, and pinned here so nobody spends an afternoon rediscovering it.
//
// Showing MainWindow attaches the terminal's NativeWebView, whose Win32 adapter initialises WebView2
// on attach — and WebView2 refuses an MTA thread, which is exactly why Program.Main is [STAThread].
// A HeadlessUnitTestSession owns its dispatcher thread and does not offer an apartment choice, so
// the whole window cannot be laid out here at any size.
//
// That is the reason this harness measures the extracted controls rather than MainWindow: each of
// them is a part with a height budget to blow, and none of them has a native child window in it. If a
// future Avalonia makes the adapter lazy, this test starts failing and the harness can be widened.
await LayoutHarness.OnTheUiThreadAsync(
() =>
{
var window = new MainWindow();
try
{
var showing = Should.Throw(() => LayoutHarness.Settle(
window,
LayoutHarness.MinimumWidth,
LayoutHarness.MinimumHeight));
// RPC_E_CHANGED_MODE. Asserted on the code rather than the message so a localised
// Windows does not break the build.
showing.HResult.ShouldBe(unchecked((int)0x80010106));
}
finally
{
window.Close();
}
},
Token);
}
[Fact]
public async Task TheHarnessMeasuresTheSizeTheWindowDeclares()
{
// Pins the two constants against the XAML. A harness measuring a size the window lets itself
// be dragged to something smaller would be certifying a size no user is held to.
await LayoutHarness.OnTheUiThreadAsync(
() =>
{
var window = new MainWindow();
try
{
window.MinWidth.ShouldBe(LayoutHarness.MinimumWidth);
window.MinHeight.ShouldBe(LayoutHarness.MinimumHeight);
}
finally
{
window.Close();
}
},
Token);
}
}