// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; using osu.Game.Screens.SelectV2; using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] public partial class TestSceneBeatmapCarouselV2DifficultyGrouping : BeatmapCarouselV2TestScene { [SetUpSteps] public void SetUpSteps() { RemoveAllBeatmaps(); CreateCarousel(); SortBy(new FilterCriteria { Group = GroupMode.Difficulty, Sort = SortMode.Difficulty }); AddBeatmaps(10, 3); WaitForDrawablePanels(); } [Test] public void TestOpenCloseGroupWithNoSelectionMouse() { AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); CheckNoSelection(); ClickVisiblePanel(0); AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); CheckNoSelection(); ClickVisiblePanel(0); AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); CheckNoSelection(); } [Test] public void TestOpenCloseGroupWithNoSelectionKeyboard() { AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); CheckNoSelection(); SelectNextPanel(); Select(); AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); AddAssert("keyboard selected is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True); CheckNoSelection(); Select(); AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); AddAssert("keyboard selected is collapsed", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False); CheckNoSelection(); } [Test] public void TestCarouselRemembersSelection() { SelectNextGroup(); object? selection = null; AddStep("store drawable selection", () => selection = GetSelectedPanel()?.Item?.Model); CheckHasSelection(); AddAssert("drawable selection non-null", () => selection, () => Is.Not.Null); AddAssert("drawable selection matches carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection)); RemoveAllBeatmaps(); AddUntilStep("no drawable selection", GetSelectedPanel, () => Is.Null); AddBeatmaps(10); WaitForDrawablePanels(); CheckHasSelection(); AddAssert("no drawable selection", GetSelectedPanel, () => Is.Null); AddStep("add previous selection", () => BeatmapSets.Add(((BeatmapInfo)selection!).BeatmapSet!)); AddAssert("selection matches original carousel selection", () => selection, () => Is.EqualTo(Carousel.CurrentSelection)); AddUntilStep("drawable selection restored", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(selection)); AddAssert("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True); ClickVisiblePanel(0); AddUntilStep("carousel item not visible", GetSelectedPanel, () => Is.Null); ClickVisiblePanel(0); AddUntilStep("carousel item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True); } [Test] public void TestGroupSelectionOnHeader() { SelectNextGroup(); WaitForGroupSelection(0, 0); SelectPrevPanel(); AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True); SelectPrevGroup(); WaitForGroupSelection(0, 0); AddAssert("keyboard selected panel is contracted", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.False); SelectPrevGroup(); WaitForGroupSelection(0, 0); AddAssert("keyboard selected panel is expanded", () => GetKeyboardSelectedPanel()?.Expanded.Value, () => Is.True); } [Test] public void TestKeyboardSelection() { SelectNextPanel(); SelectNextPanel(); SelectNextPanel(); SelectNextPanel(); CheckNoSelection(); // open first group Select(); CheckNoSelection(); AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); SelectNextPanel(); Select(); WaitForGroupSelection(0, 0); SelectNextGroup(); WaitForGroupSelection(0, 1); SelectNextGroup(); WaitForGroupSelection(0, 2); SelectPrevGroup(); WaitForGroupSelection(0, 1); SelectPrevGroup(); WaitForGroupSelection(0, 0); SelectPrevGroup(); WaitForGroupSelection(2, 9); } [Test] public void TestInputHandlingWithinGaps() { AddBeatmaps(5, 2); WaitForDrawablePanels(); SelectNextGroup(); clickOnPanel(0, 1, p => p.LayoutRectangle.TopLeft + new Vector2(20f, -1f)); WaitForGroupSelection(0, 1); clickOnPanel(0, 0, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); WaitForGroupSelection(0, 0); SelectNextPanel(); Select(); WaitForGroupSelection(0, 1); clickOnGroup(0, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); AddAssert("group 0 collapsed", () => this.ChildrenOfType().OrderBy(g => g.Y).ElementAt(0).Expanded.Value, () => Is.False); clickOnGroup(0, p => p.LayoutRectangle.Centre); AddAssert("group 0 expanded", () => this.ChildrenOfType().OrderBy(g => g.Y).ElementAt(0).Expanded.Value, () => Is.True); AddStep("scroll to end", () => Scroll.ScrollToEnd(false)); clickOnPanel(0, 4, p => p.LayoutRectangle.BottomLeft + new Vector2(20f, 1f)); WaitForGroupSelection(0, 4); clickOnGroup(1, p => p.LayoutRectangle.TopLeft + new Vector2(20f, -1f)); AddAssert("group 1 expanded", () => this.ChildrenOfType().OrderBy(g => g.Y).ElementAt(1).Expanded.Value, () => Is.True); } private void clickOnGroup(int group, Func pos) { AddStep($"click on group{group}", () => { var groupingFilter = Carousel.Filters.OfType().Single(); var model = groupingFilter.GroupItems.Keys.ElementAt(group); var panel = this.ChildrenOfType().Single(b => ReferenceEquals(b.Item!.Model, model)); InputManager.MoveMouseTo(panel.ToScreenSpace(pos(panel))); InputManager.Click(MouseButton.Left); }); } private void clickOnPanel(int group, int panel, Func pos) { AddStep($"click on group{group} panel{panel}", () => { var groupingFilter = Carousel.Filters.OfType().Single(); var g = groupingFilter.GroupItems.Keys.ElementAt(group); // offset by one because the group itself is included in the items list. object model = groupingFilter.GroupItems[g].ElementAt(panel + 1).Model; var p = this.ChildrenOfType().Single(b => ReferenceEquals(b.Item!.Model, model)); InputManager.MoveMouseTo(p.ToScreenSpace(pos(p))); InputManager.Click(MouseButton.Left); }); } } }