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); } }