using Avalonia; using Avalonia.Headless; using Avalonia.Media; using DodoSSH.Client.App.Layout.Tests; [assembly: AvaloniaTestApplication(typeof(HeadlessApp))] namespace DodoSSH.Client.App.Layout.Tests; /// /// Builds the real application for a headless renderer. /// /// /// /// itself, not a stand-in, because the thing being measured is how the shipped /// styles lay out — a harness with its own theme would measure a window nobody sees. Its /// OnFrameworkInitializationCompleted only composes the object graph when the lifetime is a classic /// desktop one, and a headless session's is not, so loading it here brings the Fluent theme and the dark /// variant and starts no vault, no listener and no browser. /// /// /// Skia rather than the headless drawing stub, which is the one deliberate cost in this file. The stub's /// font manager returns invented glyph metrics, and text height is an input to every stacked panel in this /// window — measuring against it would produce numbers that are self-consistent and unrelated to the /// application. With Skia and the same Inter font the application registers, a measured height is the /// height a user gets. /// /// internal static class HeadlessApp { /// Found by name; is the contract. /// /// /// The default family is named explicitly, because the paragraph above was describing an intention /// rather than what happened. WithInterFont registers a font collection; it does not make it /// the default. Avalonia resolves that from the platform — which is to say from whatever the machine /// has installed — so a suite whose whole job is measuring text was measuring Segoe UI on a /// developer's Windows and DejaVu on their Linux, and treating the two numbers as one. /// /// /// On a machine with no fonts at all it does not merely differ, it stops: FontManager throws /// "Default font family name can't be null or empty" during AppBuilder.SetupUnsafe, before any /// test body runs. That is how a container CI runner reports all sixty-eight of these as failures with /// not one assertion among them. Naming Inter costs the runner nothing, because the font travels /// inside the package rather than on the host. /// /// public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UseSkia() .UseHeadless(new AvaloniaHeadlessPlatformOptions { UseHeadlessDrawing = false }) .WithInterFont() .With(new FontManagerOptions { DefaultFamilyName = InterFamily }); /// The family WithInterFont registers. /// /// Kept in step with Program.BuildAvaloniaApp by hand, and it matters that they agree: a /// harness pinning a different default from the one the application runs with would certify heights /// for a window nobody sees, which is the failure the opening paragraph of this file is about. /// internal const string InterFamily = "avares://Avalonia.Fonts.Inter/Assets#Inter"; }