using Avalonia; using Avalonia.Controls; using Avalonia.Headless.XUnit; using Avalonia.Threading; using AvParser.UI.Responsive; namespace AvParser.UI.HeadlessTests; public class ResponsiveTests { [Theory] [InlineData(320, Breakpoint.Compact)] [InlineData(719, Breakpoint.Compact)] [InlineData(720, Breakpoint.Medium)] [InlineData(1000, Breakpoint.Medium)] [InlineData(1100, Breakpoint.Expanded)] [InlineData(1920, Breakpoint.Expanded)] public void Classify_maps_width_to_a_breakpoint(double width, Breakpoint expected) => ResponsiveLayout.Classify(width).ShouldBe(expected); [Fact] public void Hysteresis_widens_whichever_band_we_are_already_in() { // Sitting just under the medium threshold, a nudge upward must not flip the layout... ResponsiveLayout .Classify(ResponsiveLayout.MediumMinWidth + 10, Breakpoint.Compact) .ShouldBe(Breakpoint.Compact); // ...but a decisive move past the deadband must. ResponsiveLayout .Classify(ResponsiveLayout.MediumMinWidth + ResponsiveLayout.Hysteresis, Breakpoint.Compact) .ShouldBe(Breakpoint.Medium); // Symmetrically on the way down from expanded. ResponsiveLayout .Classify(ResponsiveLayout.ExpandedMinWidth - 10, Breakpoint.Expanded) .ShouldBe(Breakpoint.Expanded); ResponsiveLayout .Classify(ResponsiveLayout.ExpandedMinWidth - ResponsiveLayout.Hysteresis - 1, Breakpoint.Expanded) .ShouldBe(Breakpoint.Medium); } [AvaloniaTheory] [InlineData(500, Breakpoint.Compact, ":compact")] [InlineData(900, Breakpoint.Medium, ":medium")] [InlineData(1400, Breakpoint.Expanded, ":expanded")] public void Laying_out_a_control_sets_the_breakpoint_and_its_pseudoclass( double width, Breakpoint expected, string pseudoClass ) { var host = new Border(); ResponsiveLayout.SetIsEnabled(host, true); // Measure/Arrange directly rather than resizing a Window: the headless window manager's // resize path is the flakiest thing available, and this is what actually drives Bounds. host.Measure(new Size(width, 800)); host.Arrange(new Rect(0, 0, width, 800)); Dispatcher.UIThread.RunJobs(); ResponsiveLayout.GetBreakpoint(host).ShouldBe(expected); host.Classes.Contains(pseudoClass).ShouldBeTrue(); } [AvaloniaFact] public void Only_one_breakpoint_pseudoclass_is_active_at_a_time() { var host = new Border(); ResponsiveLayout.SetIsEnabled(host, true); host.Measure(new Size(400, 600)); host.Arrange(new Rect(0, 0, 400, 600)); Dispatcher.UIThread.RunJobs(); host.Classes.Contains(":compact").ShouldBeTrue(); host.Classes.Contains(":medium").ShouldBeFalse(); host.Classes.Contains(":expanded").ShouldBeFalse(); } [AvaloniaFact] public void Disabling_the_behaviour_stops_further_updates() { var host = new Border(); ResponsiveLayout.SetIsEnabled(host, true); host.Measure(new Size(400, 600)); host.Arrange(new Rect(0, 0, 400, 600)); Dispatcher.UIThread.RunJobs(); ResponsiveLayout.SetIsEnabled(host, false); host.Measure(new Size(1400, 600)); host.Arrange(new Rect(0, 0, 1400, 600)); Dispatcher.UIThread.RunJobs(); ResponsiveLayout.GetBreakpoint(host).ShouldBe(Breakpoint.Compact); } }