using Annulus; using Azimuth; using System; using UnitTestSharp; namespace Glass.UnitTests { public class ScrollViewTests : TestFixture { public const int Thickness = 20; public static readonly Vector ViewSize = new Vector(200, 100); /// /// Renders like any solid fill, but reports a preferred size, which gives a scroll bar a thickness. /// public record SizedSolidMaterial : SolidColorMaterial { private readonly Vector size; public SizedSolidMaterial(Vector size) { this.size = size; } protected override Vector PreferredSizeInModelSpace(Constraints constraints) => constraints.Constrain(size); } [IgnoreTest] public static Rect Box(Scalar x, Scalar y, Scalar width, Scalar height) { return Rect.FromPositionAndSize(new Vector(x, y), new Vector(width, height)); } [IgnoreTest] public static ScrollViewMaterial WithBars(int thickness) { ButtonMaterial arrow = new SizedSolidMaterial(new Vector(thickness, thickness)); var bar = new ScrollBarMaterial { DecrementButton = arrow, IncrementButton = arrow }; return new ScrollViewMaterial { HorizontalScrollBar = bar, VerticalScrollBar = bar }; } [IgnoreTest] public static Material Bordered(int border, string debugName) { return new Material { DebugName = debugName, BorderSize = new Rect.BorderSize { Top = border, Bottom = border, Left = border, Right = border }, }; } [IgnoreTest] private static ScrollView NewView(Vector boundsSize, Vector contentSize) { return new ScrollView { ScrollViewMaterial = WithBars(Thickness), ContentSize = contentSize, Bounds = Rect.FromPositionAndSize(Vector.Zero, boundsSize), }; } public class CtorTests : TestFixture { public void KeepsTheCanvasAndBarsOutOfCanvasChildren() { CheckEqual(0, new ScrollView().CanvasChildren.Count); } public void ScrollsBothAxesWithTheBarsAtTheBottomAndRight() { var view = new ScrollView(); CheckTrue(view.ScrollsHorizontally); CheckTrue(view.ScrollsVertically); CheckEqual(Anchor.Bottom, view.HorizontalScrollBarAnchor); CheckEqual(Anchor.Right, view.VerticalScrollBarAnchor); } public void GivesEachBarItsOwnOrientation() { var view = new ScrollView(); CheckEqual(Orientation.Horizontal, view.HorizontalScrollBar.Orientation); CheckEqual(Orientation.Vertical, view.VerticalScrollBar.Orientation); } public void StartsWithBothBarsHidden() { var view = new ScrollView(); CheckFalse(view.HorizontalScrollBar.Visible); CheckFalse(view.VerticalScrollBar.Visible); } } public class CanvasChildrenTests : TestFixture { [IgnoreTest] private static MouseState LeftButtonAt(Vector position) { return new MouseState { Position = position, Buttons = MouseButtonFlags.Left }; } [IgnoreTest] private static int DrawsOf(RendererTests.TestRenderer renderer, Material material) { int returnMe = 0; foreach (RendererTests.TestRenderer.RenderArguments call in renderer.renderCalls) { if (ReferenceEquals(call.material, material)) { ++returnMe; } } return returnMe; } public void AddedControls_AreUpdatedWithTheView() { ScrollView view = NewView(ViewSize, Vector.Zero); var content = new Control(); view.CanvasChildren.Add(content); view.UpdateInSubtree(TimeSpan.FromSeconds(1), new UpdateState()); CheckEqual(TimeSpan.FromSeconds(1), content.Age); } public void TheScrollBars_AreUpdatedWithTheView() { ScrollView view = NewView(ViewSize, Vector.Zero); view.UpdateInSubtree(TimeSpan.FromSeconds(1), new UpdateState()); CheckEqual(TimeSpan.FromSeconds(1), view.HorizontalScrollBar.Age); CheckEqual(TimeSpan.FromSeconds(1), view.VerticalScrollBar.Age); } public void CancellingMouseInput_ReachesTheContentAndTheBars() { ScrollView view = NewView(ViewSize, Vector.Zero); var content = new Control(); view.CanvasChildren.Add(content); int horizontalBarVersion = view.HorizontalScrollBar.MouseInputVersion; int verticalBarVersion = view.VerticalScrollBar.MouseInputVersion; view.CancelMouseInputInSubtree(); CheckEqual(1, content.MouseInputVersion); CheckEqual(horizontalBarVersion + 1, view.HorizontalScrollBar.MouseInputVersion); CheckEqual(verticalBarVersion + 1, view.VerticalScrollBar.MouseInputVersion); } public void Tooltip_IsUpdatedWithTheViewButKeptOutOfTheContent() { ScrollView view = NewView(ViewSize, Vector.Zero); var tooltip = new Tooltip(Material.Invisible, new Panel()); view.Tooltip = tooltip; view.UpdateInSubtree(TimeSpan.FromSeconds(1), new UpdateState()); CheckEqual(0, view.CanvasChildren.Count); CheckEqual(TimeSpan.FromSeconds(1), tooltip.Age); } public void PointerOverScrolledContent_ReachesTheChildInContentCoordinates() { ScrollView view = NewView(ViewSize, new Vector(180, 260)); var child = new MouseManagerTests.RecordingControl("child") { Handles = true }; child.Bounds = Box(0, 60, 180, 200); view.CanvasChildren.Add(child); var manager = new MouseManager(); manager.Controls.Add(view); view.ScrollTo(new Vector(0, 50)); Control reached = manager.MouseDown(LeftButtonAt(new Vector(20, 20)), MouseButtonFlags.Left); CheckEqual(child, reached); CheckEqual(new Vector(20, 70), child.LastPosition); } public void PointerOverAScrollBar_ReachesTheBar() { ScrollView view = NewView(ViewSize, new Vector(180, 260)); var manager = new MouseManager(); manager.Controls.Add(view); Control reached = manager.MouseDown(LeftButtonAt(new Vector(190, 10)), MouseButtonFlags.Left); CheckEqual(view.VerticalScrollBar.DecrementButton, reached); } public void Content_IsDrawn() { ScrollView view = NewView(ViewSize, Vector.Zero); var contentMaterial = new SolidColorMaterial { DebugName = "content" }; view.CanvasChildren.Add(new Panel { Material = contentMaterial, Bounds = Box(0, 0, 50, 50) }); var renderer = new RendererTests.NullTestRenderer(); renderer.RenderSubtree(context: 0, view, new DrawState()); CheckEqual(1, DrawsOf(renderer, contentMaterial)); } public void AVisibleScrollBar_IsDrawn() { var arrow = new SizedSolidMaterial(new Vector(Thickness, Thickness)) { DebugName = "arrow" }; var view = new ScrollView { ScrollViewMaterial = new ScrollViewMaterial { VerticalScrollBar = new ScrollBarMaterial { DecrementButton = arrow, IncrementButton = arrow }, }, ContentSize = new Vector(180, 500), Bounds = Rect.FromPositionAndSize(Vector.Zero, ViewSize), }; var renderer = new RendererTests.NullTestRenderer(); renderer.RenderSubtree(context: 0, view, new DrawState()); CheckEqual(2, DrawsOf(renderer, arrow)); } public void AHiddenScrollBar_IsNotDrawn() { var arrow = new SizedSolidMaterial(new Vector(Thickness, Thickness)) { DebugName = "arrow" }; var view = new ScrollView { ScrollViewMaterial = new ScrollViewMaterial { VerticalScrollBar = new ScrollBarMaterial { DecrementButton = arrow, IncrementButton = arrow }, }, ContentSize = new Vector(100, 50), Bounds = Rect.FromPositionAndSize(Vector.Zero, ViewSize), }; var renderer = new RendererTests.NullTestRenderer(); renderer.RenderSubtree(context: 0, view, new DrawState()); CheckEqual(0, DrawsOf(renderer, arrow)); } } public class ScrollsHorizontallyTests : TestFixture { public void Off_ReservesNoStripAndLetsTheVerticalBarRunTheWholeHeight() { ScrollView view = NewView(ViewSize, Vector.Zero); view.ScrollsHorizontally = false; CheckEqual(Box(0, 0, 180, 100), view.ContentViewportRect); CheckEqual(Box(180, 0, 20, 100), view.VerticalScrollBar.Bounds); CheckEqual(0, view.HorizontalScrollBar.Bounds.Height); } public void Off_HidesTheBarEvenWhenTheContentOverflows() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.ScrollsHorizontally = false; CheckFalse(view.HorizontalScrollBar.Visible); } public void Off_IgnoresScrollingOnThatAxis() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.ScrollsHorizontally = false; view.ScrollTo(new Vector(50, 60)); CheckEqual(new Vector(0, 60), view.ScrollOffset); } public void TurningOff_ResetsThatAxisAndTurningBackOnDoesNotRestoreIt() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.ScrollTo(new Vector(50, 60)); view.ScrollsHorizontally = false; view.ScrollsHorizontally = true; CheckEqual(new Vector(0, 60), view.ScrollOffset); } public void OnAgain_ReservesTheStripAgain() { ScrollView view = NewView(ViewSize, Vector.Zero); view.ScrollsHorizontally = false; view.ScrollsHorizontally = true; CheckEqual(Box(0, 0, 180, 80), view.ContentViewportRect); } } public class ScrollsVerticallyTests : TestFixture { public void Off_ReservesNoStripAndLetsTheHorizontalBarRunTheWholeWidth() { ScrollView view = NewView(ViewSize, Vector.Zero); view.ScrollsVertically = false; CheckEqual(Box(0, 0, 200, 80), view.ContentViewportRect); CheckEqual(Box(0, 80, 200, 20), view.HorizontalScrollBar.Bounds); CheckEqual(0, view.VerticalScrollBar.Bounds.Width); } public void Off_HidesTheBarEvenWhenTheContentOverflows() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.ScrollsVertically = false; CheckFalse(view.VerticalScrollBar.Visible); } public void Off_IgnoresScrollingOnThatAxis() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.ScrollsVertically = false; view.ScrollTo(new Vector(50, 60)); CheckEqual(new Vector(50, 0), view.ScrollOffset); } public void TurningOff_ResetsThatAxisAndTurningBackOnDoesNotRestoreIt() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.ScrollTo(new Vector(50, 60)); view.ScrollsVertically = false; view.ScrollsVertically = true; CheckEqual(new Vector(50, 0), view.ScrollOffset); } public void BothOff_GivesTheCanvasTheWholeView() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.ScrollsHorizontally = false; view.ScrollsVertically = false; CheckEqual(Box(0, 0, 200, 100), view.ContentViewportRect); } } public class HorizontalScrollBarAnchorTests : TestFixture { public void Bottom_PutsTheStripBelowTheCanvasAndStopsTheBarShortOfTheCorner() { ScrollView view = NewView(ViewSize, Vector.Zero); CheckEqual(Box(0, 80, 180, 20), view.HorizontalScrollBar.Bounds); CheckEqual(Box(180, 0, 20, 80), view.VerticalScrollBar.Bounds); CheckEqual(Box(0, 0, 180, 80), view.ContentViewportRect); } public void Top_PutsTheStripAboveTheCanvasAndStopsTheBarShortOfTheCorner() { ScrollView view = NewView(ViewSize, Vector.Zero); view.HorizontalScrollBarAnchor = Anchor.Top; CheckEqual(Box(0, 0, 180, 20), view.HorizontalScrollBar.Bounds); CheckEqual(Box(180, 20, 20, 80), view.VerticalScrollBar.Bounds); CheckEqual(Box(0, 20, 180, 80), view.ContentViewportRect); } public void Left_Throws() { var view = new ScrollView(); CheckThrow(typeof(ArgumentOutOfRangeException)); view.HorizontalScrollBarAnchor = Anchor.Left; } public void Unknown_Throws() { var view = new ScrollView(); CheckThrow(typeof(ArgumentOutOfRangeException)); view.HorizontalScrollBarAnchor = (Anchor)99; } public void ChangingIt_KeepsTheBarHorizontal() { ScrollView view = NewView(ViewSize, Vector.Zero); view.HorizontalScrollBarAnchor = Anchor.Top; CheckEqual(Orientation.Horizontal, view.HorizontalScrollBar.Orientation); } } public class VerticalScrollBarAnchorTests : TestFixture { public void Right_PutsTheStripBesideTheCanvas() { ScrollView view = NewView(ViewSize, Vector.Zero); CheckEqual(Box(180, 0, 20, 80), view.VerticalScrollBar.Bounds); } public void Left_PutsTheStripBesideTheCanvasAndStopsTheHorizontalBarShortOfTheCorner() { ScrollView view = NewView(ViewSize, Vector.Zero); view.VerticalScrollBarAnchor = Anchor.Left; CheckEqual(Box(0, 0, 20, 80), view.VerticalScrollBar.Bounds); CheckEqual(Box(20, 80, 180, 20), view.HorizontalScrollBar.Bounds); CheckEqual(Box(20, 0, 180, 80), view.ContentViewportRect); } public void TopAndLeft_PutBothStripsOnTheirNearEdges() { ScrollView view = NewView(ViewSize, Vector.Zero); view.HorizontalScrollBarAnchor = Anchor.Top; view.VerticalScrollBarAnchor = Anchor.Left; CheckEqual(Box(20, 0, 180, 20), view.HorizontalScrollBar.Bounds); CheckEqual(Box(0, 20, 20, 80), view.VerticalScrollBar.Bounds); CheckEqual(Box(20, 20, 180, 80), view.ContentViewportRect); } public void Top_Throws() { var view = new ScrollView(); CheckThrow(typeof(ArgumentOutOfRangeException)); view.VerticalScrollBarAnchor = Anchor.Top; } public void Unknown_Throws() { var view = new ScrollView(); CheckThrow(typeof(ArgumentOutOfRangeException)); view.VerticalScrollBarAnchor = (Anchor)99; } public void ChangingIt_KeepsTheBarVertical() { ScrollView view = NewView(ViewSize, Vector.Zero); view.VerticalScrollBarAnchor = Anchor.Left; CheckEqual(Orientation.Vertical, view.VerticalScrollBar.Orientation); } } public class ContentSizeTests : TestFixture { public void ReadsBackWhatWasSet() { CheckEqual(new Vector(400, 500), NewView(ViewSize, new Vector(400, 500)).ContentSize); } public void OverflowingVertically_ShowsOnlyTheVerticalBar() { ScrollView view = NewView(ViewSize, new Vector(100, 500)); CheckFalse(view.HorizontalScrollBar.Visible); CheckTrue(view.VerticalScrollBar.Visible); } public void OverflowingHorizontally_ShowsOnlyTheHorizontalBar() { ScrollView view = NewView(ViewSize, new Vector(400, 50)); CheckTrue(view.HorizontalScrollBar.Visible); CheckFalse(view.VerticalScrollBar.Visible); } public void OverflowingBothWays_ShowsBothBars() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); CheckTrue(view.HorizontalScrollBar.Visible); CheckTrue(view.VerticalScrollBar.Visible); } public void Fitting_HidesBothBarsButKeepsTheStrips() { ScrollView view = NewView(ViewSize, new Vector(100, 50)); CheckFalse(view.HorizontalScrollBar.Visible); CheckFalse(view.VerticalScrollBar.Visible); CheckEqual(Box(0, 0, 180, 80), view.ContentViewportRect); CheckEqual(Box(180, 0, 20, 80), view.VerticalScrollBar.Bounds); } public void ExactlyFillingTheViewport_HidesBothBars() { ScrollView view = NewView(ViewSize, new Vector(180, 80)); CheckFalse(view.HorizontalScrollBar.Visible); CheckFalse(view.VerticalScrollBar.Visible); } public void OverflowingAxisThatDoesNotScroll_ShowsNoBar() { ScrollView view = NewView(ViewSize, new Vector(100, 500)); view.ScrollsVertically = false; CheckFalse(view.VerticalScrollBar.Visible); } public void MeasuresEachBarAgainstItsOwnAxis() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); CheckEqual(400, view.HorizontalScrollBar.ContentSize); CheckEqual(180, view.HorizontalScrollBar.ViewportSize); CheckEqual(500, view.VerticalScrollBar.ContentSize); CheckEqual(80, view.VerticalScrollBar.ViewportSize); } public void Growing_ShowsTheBarWithoutMovingTheCanvas() { ScrollView view = NewView(ViewSize, new Vector(100, 50)); view.ContentSize = new Vector(100, 500); CheckTrue(view.VerticalScrollBar.Visible); CheckEqual(Box(0, 0, 180, 80), view.ContentViewportRect); } public void Shrinking_ReClampsTheScrollOffset() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.ScrollTo(new Vector(0, 300)); view.ContentSize = new Vector(400, 250); CheckEqual(170, view.VerticalScrollBar.ScrollOffset); CheckEqual(new Vector(0, 170), view.ScrollOffset); } } public class BoundsTests : TestFixture { public void LaysOutInTheViewsOwnSpaceRegardlessOfWhereItSits() { var view = new ScrollView { ScrollViewMaterial = WithBars(Thickness), Bounds = Rect.FromPositionAndSize(new Vector(50, 60), ViewSize), }; CheckEqual(Box(180, 0, 20, 80), view.VerticalScrollBar.Bounds); CheckEqual(Box(0, 0, 180, 80), view.ContentViewportRect); } public void Resizing_RelaysOutTheStripsAndCanvas() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.Bounds = Rect.FromPositionAndSize(Vector.Zero, new Vector(300, 150)); CheckEqual(Box(0, 130, 280, 20), view.HorizontalScrollBar.Bounds); CheckEqual(Box(280, 0, 20, 130), view.VerticalScrollBar.Bounds); CheckEqual(280, view.HorizontalScrollBar.ViewportSize); CheckEqual(130, view.VerticalScrollBar.ViewportSize); } public void GrowingUntilTheContentFits_HidesBothBars() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.Bounds = Rect.FromPositionAndSize(Vector.Zero, new Vector(420, 520)); CheckFalse(view.HorizontalScrollBar.Visible); CheckFalse(view.VerticalScrollBar.Visible); } } public class MaterialTests : TestFixture { public void Background_InsetsTheStripsAndTheCanvas() { ScrollView view = NewView(ViewSize, Vector.Zero); view.Material = Bordered(border: 5, debugName: "background"); CheckEqual(Box(0, 70, 170, 20), view.HorizontalScrollBar.Bounds); CheckEqual(Box(170, 0, 20, 70), view.VerticalScrollBar.Bounds); CheckEqual(Box(0, 0, 170, 70), view.ContentViewportRect); } public void CanvasMaterial_ShrinksTheViewportTheBarsPageBy() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); ScrollViewMaterial material = view.ScrollViewMaterial; material.Canvas = Bordered(border: 5, debugName: "canvas"); view.ScrollViewMaterial = material; CheckEqual(Box(5, 5, 170, 70), view.ContentViewportRect); CheckEqual(170, view.HorizontalScrollBar.ViewportSize); CheckEqual(70, view.VerticalScrollBar.ViewportSize); } } public class ScrollViewMaterialTests : TestFixture { static readonly Material Background = new Material { DebugName = "background" }; static readonly Material Canvas = new Material { DebugName = "canvas" }; [IgnoreTest] private static ScrollBarMaterial NamedBar(string name) { return new ScrollBarMaterial { Track = new Material { DebugName = name + " track" }, DecrementButton = new ButtonMaterial(new Material { DebugName = name + " decrement" }), IncrementButton = new ButtonMaterial(new Material { DebugName = name + " increment" }), Thumb = new ButtonMaterial(new Material { DebugName = name + " thumb" }), Background = new Material { DebugName = name + " background" }, }; } static readonly ScrollViewMaterial FullSet = new ScrollViewMaterial { Background = Background, Canvas = Canvas, HorizontalScrollBar = NamedBar("horizontal"), VerticalScrollBar = NamedBar("vertical"), }; public void Setter_AssignsEachChildDirectly() { var view = new ScrollView { ScrollViewMaterial = FullSet }; CheckEqual(Background, view.Material); CheckEqual(FullSet.HorizontalScrollBar, view.HorizontalScrollBar.ScrollBarMaterial); CheckEqual(FullSet.VerticalScrollBar, view.VerticalScrollBar.ScrollBarMaterial); } public void Getter_RoundTripsWhatTheSetterAssigned() { var view = new ScrollView { ScrollViewMaterial = FullSet }; CheckEqual(FullSet, view.ScrollViewMaterial); } public void Getter_ReflectsTheBackgroundChangedDirectlyAfterwards() { var view = new ScrollView { ScrollViewMaterial = FullSet }; var newBackground = new Material { DebugName = "newBackground" }; view.Material = newBackground; CheckEqual(newBackground, view.ScrollViewMaterial.Background); } public void Getter_ReflectsEitherBarChangedDirectlyAfterwards() { var view = new ScrollView { ScrollViewMaterial = FullSet }; var newHorizontalThumb = new ButtonMaterial(new Material { DebugName = "newHorizontalThumb" }); var newVerticalThumb = new ButtonMaterial(new Material { DebugName = "newVerticalThumb" }); view.HorizontalScrollBar.Thumb.ButtonMaterial = newHorizontalThumb; view.VerticalScrollBar.Thumb.ButtonMaterial = newVerticalThumb; CheckEqual(newHorizontalThumb, view.ScrollViewMaterial.HorizontalScrollBar.Thumb); CheckEqual(newVerticalThumb, view.ScrollViewMaterial.VerticalScrollBar.Thumb); } public void Setter_RelaysOutWithTheNewBorders() { ScrollView view = NewView(ViewSize, Vector.Zero); ScrollViewMaterial material = view.ScrollViewMaterial; material.Background = Bordered(border: 5, debugName: "bordered"); view.ScrollViewMaterial = material; CheckEqual(Box(0, 0, 170, 70), view.ContentViewportRect); } public void Setter_WithNothingSet_ClearsTheMaterials() { var view = new ScrollView { ScrollViewMaterial = FullSet }; view.ScrollViewMaterial = default; CheckNull(view.Material); CheckNull(view.ScrollViewMaterial.Canvas); } } public class ScrollOffsetTests : TestFixture { public void Unset_IsZero() { CheckEqual(Vector.Zero, NewView(ViewSize, new Vector(400, 500)).ScrollOffset); } public void ABarBeingScrolledDirectly_MovesTheCanvas() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.VerticalScrollBar.ScrollTo(80); view.HorizontalScrollBar.ScrollTo(40); CheckEqual(new Vector(40, 80), view.ScrollOffset); } } public class ScrollToTests : TestFixture { public void MovesTheCanvasAndBothBarsTogether() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.ScrollTo(new Vector(50, 150)); CheckEqual(new Vector(50, 150), view.ScrollOffset); CheckEqual(50, view.HorizontalScrollBar.ScrollOffset); CheckEqual(150, view.VerticalScrollBar.ScrollOffset); } public void PastTheEnd_ClampsToTheOverhangOnEachAxis() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.ScrollTo(new Vector(9999, 9999)); CheckEqual(new Vector(220, 420), view.ScrollOffset); } public void BeforeTheContentExists_TakesEffectOnceItDoes() { ScrollView view = NewView(ViewSize, Vector.Zero); view.ScrollTo(new Vector(30, 50)); view.ContentSize = new Vector(400, 500); CheckEqual(new Vector(30, 50), view.ScrollOffset); } } public class ScrollByTests : TestFixture { public void AddsTheDeltaToTheCurrentOffset() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.ScrollTo(new Vector(10, 20)); view.ScrollBy(new Vector(5, 30)); CheckEqual(new Vector(15, 50), view.ScrollOffset); } public void OnAnAxisThatDoesNotScroll_LeavesItAlone() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.ScrollsHorizontally = false; view.ScrollBy(new Vector(5, 30)); CheckEqual(new Vector(0, 30), view.ScrollOffset); } } public class MouseWheelTests : TestFixture { [IgnoreTest] private static Scalar StepOf(ScrollBar bar) { return bar.StepAmount.Resolve(bar.ViewportSize); } public void TowardTheUser_ScrollsDownByTheVerticalBarsStep() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); CheckTrue(view.MouseWheel(new MouseState(), notches: -1)); CheckEqual(new Vector(0, StepOf(view.VerticalScrollBar)), view.ScrollOffset); } public void AwayFromTheUser_ScrollsBackUp() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.ScrollTo(new Vector(0, 100)); view.MouseWheel(new MouseState(), notches: 1); CheckEqual(new Vector(0, 100 - StepOf(view.VerticalScrollBar)), view.ScrollOffset); } public void OnlyTheHorizontalAxisHavingAnywhereToGo_ScrollsHorizontally() { ScrollView view = NewView(ViewSize, new Vector(400, 60)); CheckTrue(view.MouseWheel(new MouseState(), notches: -1)); CheckEqual(new Vector(StepOf(view.HorizontalScrollBar), 0), view.ScrollOffset); } public void VerticalScrollingTurnedOff_ScrollsHorizontally() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.ScrollsVertically = false; CheckTrue(view.MouseWheel(new MouseState(), notches: -1)); CheckEqual(new Vector(StepOf(view.HorizontalScrollBar), 0), view.ScrollOffset); } public void ContentThatFits_DeclinesIt() { ScrollView view = NewView(ViewSize, new Vector(100, 50)); CheckFalse(view.MouseWheel(new MouseState(), notches: -1)); CheckEqual(Vector.Zero, view.ScrollOffset); } public void Disabled_DeclinesItAndStaysPut() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.Enabled = false; CheckFalse(view.MouseWheel(new MouseState(), notches: -1)); CheckEqual(Vector.Zero, view.ScrollOffset); } public void OverTheContent_TheViewClaimsItRatherThanItsCanvas() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); var manager = new MouseManager(); manager.Controls.Add(view); Control claimed = manager.MouseWheel(new MouseState { Position = new Vector(50, 40) }, notches: -1); CheckEqual(view, claimed); CheckEqual(StepOf(view.VerticalScrollBar), view.VerticalScrollBar.ScrollOffset); } public void OverAButtonInTheContent_TheButtonDeclinesAndTheViewScrolls() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); view.CanvasChildren.Add(new Button { Bounds = Box(10, 10, 100, 30) }); var manager = new MouseManager(); manager.Controls.Add(view); Control claimed = manager.MouseWheel(new MouseState { Position = new Vector(50, 20) }, notches: -1); CheckEqual(view, claimed); CheckEqual(StepOf(view.VerticalScrollBar), view.VerticalScrollBar.ScrollOffset); } public void OverTheVerticalScrollBar_TheBarTakesIt() { ScrollView view = NewView(ViewSize, new Vector(400, 500)); var manager = new MouseManager(); manager.Controls.Add(view); Control claimed = manager.MouseWheel(new MouseState { Position = new Vector(190, 40) }, notches: -1); CheckEqual(view.VerticalScrollBar, claimed); CheckEqual(StepOf(view.VerticalScrollBar), view.VerticalScrollBar.ScrollOffset); } } public class ScrollIntoViewTests : TestFixture { [IgnoreTest] private static ScrollView BigView() => NewView(ViewSize, new Vector(400, 500)); public void RectAlreadyVisible_DoesNotScroll() { ScrollView view = BigView(); view.ScrollIntoView(Box(10, 10, 30, 30)); CheckEqual(Vector.Zero, view.ScrollOffset); } public void RectVisibleWhileScrolled_DoesNotScroll() { ScrollView view = BigView(); view.ScrollTo(new Vector(100, 100)); view.ScrollIntoView(Box(150, 150, 30, 30)); CheckEqual(new Vector(100, 100), view.ScrollOffset); } public void RectEndingExactlyAtTheViewportEdge_DoesNotScroll() { ScrollView view = BigView(); view.ScrollIntoView(Box(140, 40, 40, 40)); CheckEqual(Vector.Zero, view.ScrollOffset); } public void RectBelow_ScrollsJustEnoughToShowItsFarEdge() { ScrollView view = BigView(); view.ScrollIntoView(Box(0, 150, 30, 40)); CheckEqual(new Vector(0, 110), view.ScrollOffset); } public void RectToTheRight_ScrollsJustEnoughToShowItsFarEdge() { ScrollView view = BigView(); view.ScrollIntoView(Box(300, 0, 40, 30)); CheckEqual(new Vector(160, 0), view.ScrollOffset); } public void RectAbove_ScrollsToItsNearEdge() { ScrollView view = BigView(); view.ScrollTo(new Vector(0, 300)); view.ScrollIntoView(Box(0, 120, 30, 30)); CheckEqual(new Vector(0, 120), view.ScrollOffset); } public void RectLongerThanTheViewport_ShowsItsNearEdge() { ScrollView view = BigView(); view.ScrollIntoView(Box(0, 200, 30, 150)); CheckEqual(new Vector(0, 200), view.ScrollOffset); } public void RectOffBothWays_ScrollsBothAxes() { ScrollView view = BigView(); view.ScrollIntoView(Box(300, 150, 40, 40)); CheckEqual(new Vector(160, 110), view.ScrollOffset); } public void OnAnAxisThatDoesNotScroll_LeavesItAlone() { ScrollView view = BigView(); view.ScrollsVertically = false; view.ScrollIntoView(Box(300, 150, 40, 40)); // With no vertical strip the viewport is the full 200 wide, so the rect's far edge at 340 needs 140. CheckEqual(new Vector(140, 0), view.ScrollOffset); } } public class EnabledTests : TestFixture { public void Disabling_DisablesBothScrollBars() { var view = new ScrollView(); view.Enabled = false; CheckFalse(view.HorizontalScrollBar.Enabled); CheckFalse(view.VerticalScrollBar.Enabled); } public void ReEnabling_ReEnablesBothScrollBars() { var view = new ScrollView { Enabled = false }; view.Enabled = true; CheckTrue(view.HorizontalScrollBar.Enabled); CheckTrue(view.VerticalScrollBar.Enabled); } } public class ScrollAxesTests : TestFixture { public void Default_IsBoth() { CheckEqual(ScrollAxes.Both, new ScrollView().ScrollAxes); } public void HorizontalOff_IsJustVertical() { var view = new ScrollView { ScrollsHorizontally = false }; CheckEqual(ScrollAxes.Vertical, view.ScrollAxes); } public void VerticalOff_IsJustHorizontal() { var view = new ScrollView { ScrollsVertically = false }; CheckEqual(ScrollAxes.Horizontal, view.ScrollAxes); } public void BothOff_IsNone() { var view = new ScrollView { ScrollsHorizontally = false, ScrollsVertically = false }; CheckEqual(ScrollAxes.None, view.ScrollAxes); } } public class ContentViewportRectTests : TestFixture { public void MatchesWhatTheMaterialPredictsForTheBounds() { ScrollView view = NewView(ViewSize, Vector.Zero); ScrollViewMaterial material = view.ScrollViewMaterial; material.Background = Bordered(border: 5, debugName: "background"); material.Canvas = Bordered(border: 3, debugName: "canvas"); view.ScrollViewMaterial = material; CheckEqual(view.ContentViewportRect.Size, material.ViewportSizeFor(view.Bounds.Size, view.ScrollAxes)); } public void MatchesWhatTheMaterialPredicts_WithAnAxisNotScrolling() { ScrollView view = NewView(ViewSize, Vector.Zero); view.ScrollsVertically = false; CheckEqual( view.ContentViewportRect.Size, view.ScrollViewMaterial.ViewportSizeFor(view.Bounds.Size, view.ScrollAxes)); } } } }