using Avalonia.Controls; using DodoSSH.Client.App.Views; namespace DodoSSH.Client.App.Layout.Tests; /// /// The splash, and the one thing about it that can go wrong. /// /// /// /// Three stacked elements in a fixed-width window is not a layout with much to get wrong — except in one /// state. SplashWindow.CouldNotStart carries an exception's own message, whose length nothing in this /// application decides, and that message is the whole reason that window is still on screen. Inside a fixed /// height it would wrap and be clipped at exactly the wrong moment. /// /// /// So what is measured here is that the message grows the card, and that the window is declared in a way /// that lets the growth through. LayoutHarness.Unreachable is no help: it reports interactive controls /// arranged where they cannot be used, and the splash has none — which is why these two assert on measured /// heights instead. /// /// public sealed class SplashCardTests { private static CancellationToken Token => TestContext.Current.CancellationToken; /// A message about as long as a real one gets: a path, a reason and a suggestion. private const string LongFailure = "The process cannot access the file 'C:\\Users\\somebody\\AppData\\Local\\DodoSSH\\cache.db' " + "because it is being used by another process, and this build cannot open a second copy of it."; /// /// Pins the two constants and the mechanism against the XAML, the way /// LayoutHarnessTests.TheHarnessMeasuresTheSizeTheWindowDeclares pins the main window's minimum. /// SizeToContent is the one that matters: a window that lost it would clip the message and this /// suite's other test would go on passing, because the card would still be measuring the taller size it /// was never given. /// [Fact] public async Task TheSplashIsDeclaredTheWayTheHarnessMeasuresIt() { await LayoutHarness.OnTheUiThreadAsync( () => { var window = new SplashWindow(); try { window.Width.ShouldBe(LayoutHarness.SplashWidth); window.MinHeight.ShouldBe(LayoutHarness.SplashMinimumHeight); window.SizeToContent.ShouldBe( SizeToContent.Height, "a fixed height would clip a long failure message"); } finally { window.Close(); } }, Token); } /// /// The ordinary state has to fit the floor, or the window opens taller than its own minimum for the one /// line it normally shows — which would make a number /// that never applies to anything. /// [Fact] public async Task TheStartingMessageFitsTheHeightTheSplashOpensAt() { await LayoutHarness.OnTheUiThreadAsync( () => MessageHeight(card => { }).ShouldBeLessThanOrEqualTo( LayoutHarness.SplashMinimumHeight, "the splash opens at its minimum and the one line it starts with has to fit"), Token); } /// /// ◆ The test with something behind it. A message that wraps to three lines has to make the card ask for /// more room; if it did not, the text would be sitting under the bottom edge of the window and the only /// thing the splash exists to carry at that point would be the part nobody could read. /// [Fact] public async Task ALongFailureMessageAsksForMoreRoomRatherThanBeingClipped() { await LayoutHarness.OnTheUiThreadAsync( () => { var starting = MessageHeight(card => { }); var failed = MessageHeight(card => card.CouldNotStart(LongFailure)); failed.ShouldBeGreaterThan( starting, "the message wraps, so the card has to want the height the window then gives it"); }, Token); } /// /// What one card wants to be, at the splash's width, after the caller has had its say. /// /// /// Hosted in a window before being measured, because the heading and the hint take their size and their /// wrapping from App.axaml — a card measured detached would be measuring unstyled text. Measured /// again afterwards against an unbounded height, which is what the real window's SizeToContent /// gives it and what the host window's own fixed height would otherwise clamp away. /// private static double MessageHeight(Action say) { var card = new SplashCard(); var window = LayoutHarness.HostAtMinimumSize( card, LayoutHarness.SplashWidth, LayoutHarness.SplashMinimumHeight); try { say(card); card.Measure(new Avalonia.Size(LayoutHarness.SplashWidth, double.PositiveInfinity)); return card.DesiredSize.Height; } finally { window.Close(); } } }