1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-27 13:53:10 +08:00
osu-lazer/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs

1117 lines
40 KiB
C#
Raw Normal View History

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2018-04-13 17:19:50 +08:00
2022-06-17 15:37:17 +08:00
#nullable disable
2018-04-13 17:19:50 +08:00
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
2018-04-13 17:19:50 +08:00
using osu.Framework.Graphics;
2019-11-13 18:09:03 +08:00
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
2018-04-13 17:19:50 +08:00
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
2018-04-13 17:19:50 +08:00
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter;
using osu.Game.Tests.Resources;
2020-06-25 19:01:29 +08:00
using osuTK.Input;
2018-04-13 17:19:50 +08:00
2019-03-25 00:02:36 +08:00
namespace osu.Game.Tests.Visual.SongSelect
2018-04-13 17:19:50 +08:00
{
[TestFixture]
2020-06-25 19:01:29 +08:00
public class TestSceneBeatmapCarousel : OsuManualInputManagerTestScene
2018-04-13 17:19:50 +08:00
{
private TestBeatmapCarousel carousel;
private RulesetStore rulesets;
private readonly Stack<BeatmapSetInfo> selectedSets = new Stack<BeatmapSetInfo>();
private readonly HashSet<Guid> eagerSelectedIDs = new HashSet<Guid>();
2018-04-13 17:19:50 +08:00
private BeatmapInfo currentSelection => carousel.SelectedBeatmapInfo;
2018-04-13 17:19:50 +08:00
private const int set_count = 5;
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
}
[Test]
public void TestExternalRulesetChange()
{
createCarousel(new List<BeatmapSetInfo>());
AddStep("filter to ruleset 0", () => carousel.Filter(new FilterCriteria
{
Ruleset = rulesets.AvailableRulesets.ElementAt(0),
AllowConvertedBeatmaps = true,
}, false));
AddStep("add mixed ruleset beatmapset", () =>
{
var testMixed = TestResources.CreateTestBeatmapSetInfo(3);
for (int i = 0; i <= 2; i++)
{
testMixed.Beatmaps[i].Ruleset = rulesets.AvailableRulesets.ElementAt(i);
}
carousel.UpdateBeatmapSet(testMixed);
});
AddUntilStep("wait for filtered difficulties", () =>
{
var visibleBeatmapPanels = carousel.Items.OfType<DrawableCarouselBeatmap>().Where(p => p.IsPresent).ToArray();
return visibleBeatmapPanels.Length == 1
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 0) == 1;
});
AddStep("filter to ruleset 1", () => carousel.Filter(new FilterCriteria
{
Ruleset = rulesets.AvailableRulesets.ElementAt(1),
AllowConvertedBeatmaps = true,
}, false));
AddUntilStep("wait for filtered difficulties", () =>
{
var visibleBeatmapPanels = carousel.Items.OfType<DrawableCarouselBeatmap>().Where(p => p.IsPresent).ToArray();
return visibleBeatmapPanels.Length == 2
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 0) == 1
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 1) == 1;
});
AddStep("filter to ruleset 2", () => carousel.Filter(new FilterCriteria
{
Ruleset = rulesets.AvailableRulesets.ElementAt(2),
AllowConvertedBeatmaps = true,
}, false));
AddUntilStep("wait for filtered difficulties", () =>
{
var visibleBeatmapPanels = carousel.Items.OfType<DrawableCarouselBeatmap>().Where(p => p.IsPresent).ToArray();
return visibleBeatmapPanels.Length == 2
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 0) == 1
&& visibleBeatmapPanels.Count(p => ((CarouselBeatmap)p.Item).BeatmapInfo.Ruleset.OnlineID == 2) == 1;
});
}
[Test]
public void TestScrollPositionMaintainedOnAdd()
{
loadBeatmaps(count: 1, randomDifficulties: false);
for (int i = 0; i < 10; i++)
{
AddRepeatStep("Add some sets", () => carousel.UpdateBeatmapSet(TestResources.CreateTestBeatmapSetInfo()), 4);
checkSelectionIsCentered();
}
}
[Test]
public void TestScrollPositionMaintainedOnDelete()
{
loadBeatmaps(count: 50, randomDifficulties: false);
for (int i = 0; i < 10; i++)
{
AddRepeatStep("Remove some sets", () =>
carousel.RemoveBeatmapSet(carousel.Items.Select(item => item.Item)
.OfType<CarouselBeatmapSet>()
.OrderBy(item => item.GetHashCode())
.First(item => item.State.Value != CarouselItemState.Selected && item.Visible).BeatmapSet), 4);
checkSelectionIsCentered();
}
}
[Test]
public void TestManyPanels()
{
loadBeatmaps(count: 5000, randomDifficulties: true);
}
2020-06-25 19:01:29 +08:00
[Test]
public void TestKeyRepeat()
{
loadBeatmaps();
advanceSelection(false);
AddStep("press down arrow", () => InputManager.PressKey(Key.Down));
BeatmapInfo selection = null;
checkSelectionIterating(true);
AddStep("press up arrow", () => InputManager.PressKey(Key.Up));
checkSelectionIterating(true);
AddStep("release down arrow", () => InputManager.ReleaseKey(Key.Down));
checkSelectionIterating(true);
AddStep("release up arrow", () => InputManager.ReleaseKey(Key.Up));
checkSelectionIterating(false);
void checkSelectionIterating(bool isIterating)
{
for (int i = 0; i < 3; i++)
{
AddStep("store selection", () => selection = carousel.SelectedBeatmapInfo);
2020-06-25 19:01:29 +08:00
if (isIterating)
AddUntilStep("selection changed", () => !carousel.SelectedBeatmapInfo?.Equals(selection) == true);
2020-06-25 19:01:29 +08:00
else
AddUntilStep("selection not changed", () => carousel.SelectedBeatmapInfo?.Equals(selection) == true);
2020-06-25 19:01:29 +08:00
}
}
}
2020-04-11 16:17:18 +08:00
[Test]
public void TestRecommendedSelection()
{
loadBeatmaps(carouselAdjust: carousel => carousel.GetRecommendedBeatmap = beatmaps => beatmaps.LastOrDefault());
2020-04-11 16:17:18 +08:00
AddStep("select last", () => carousel.SelectBeatmap(carousel.BeatmapSets.Last().Beatmaps.Last()));
2020-04-11 16:17:18 +08:00
// check recommended was selected
advanceSelection(direction: 1, diff: false);
waitForSelection(1, 3);
// change away from recommended
advanceSelection(direction: -1, diff: true);
waitForSelection(1, 2);
// next set, check recommended
advanceSelection(direction: 1, diff: false);
waitForSelection(2, 3);
// next set, check recommended
advanceSelection(direction: 1, diff: false);
waitForSelection(3, 3);
// go back to first set and ensure user selection was retained
advanceSelection(direction: -1, diff: false);
advanceSelection(direction: -1, diff: false);
waitForSelection(1, 2);
}
2018-04-13 17:19:50 +08:00
/// <summary>
/// Test keyboard traversal
/// </summary>
[Test]
public void TestTraversal()
2018-04-13 17:19:50 +08:00
{
loadBeatmaps();
2020-07-13 19:03:07 +08:00
AddStep("select first", () => carousel.SelectBeatmap(carousel.BeatmapSets.First().Beatmaps.First()));
2019-09-29 12:23:18 +08:00
waitForSelection(1, 1);
2018-04-13 17:19:50 +08:00
advanceSelection(direction: 1, diff: true);
2019-09-29 12:23:18 +08:00
waitForSelection(1, 2);
2018-04-13 17:19:50 +08:00
advanceSelection(direction: -1, diff: false);
2019-09-29 12:23:18 +08:00
waitForSelection(set_count, 1);
2018-04-13 17:19:50 +08:00
advanceSelection(direction: -1, diff: true);
2019-09-29 12:23:18 +08:00
waitForSelection(set_count - 1, 3);
2018-04-13 17:19:50 +08:00
advanceSelection(diff: false);
advanceSelection(diff: false);
2019-09-29 12:23:18 +08:00
waitForSelection(1, 2);
2018-04-13 17:19:50 +08:00
advanceSelection(direction: -1, diff: true);
advanceSelection(direction: -1, diff: true);
2019-09-29 12:23:18 +08:00
waitForSelection(set_count, 3);
2018-04-13 17:19:50 +08:00
}
2020-03-28 19:10:20 +08:00
[TestCase(true)]
[TestCase(false)]
2020-03-29 23:05:07 +08:00
public void TestTraversalBeyondVisible(bool forwards)
2020-03-27 02:32:43 +08:00
{
var sets = new List<BeatmapSetInfo>();
2020-03-29 23:05:07 +08:00
const int total_set_count = 200;
AddStep("Populuate beatmap sets", () =>
{
sets.Clear();
for (int i = 0; i < total_set_count; i++)
sets.Add(TestResources.CreateTestBeatmapSetInfo());
});
2020-03-27 02:32:43 +08:00
loadBeatmaps(sets);
2020-03-29 23:05:07 +08:00
for (int i = 1; i < total_set_count; i += i)
selectNextAndAssert(i);
2020-03-27 02:32:43 +08:00
void selectNextAndAssert(int amount)
{
2020-03-29 23:05:07 +08:00
setSelected(forwards ? 1 : total_set_count, 1);
AddStep($"{(forwards ? "Next" : "Previous")} beatmap {amount} times", () =>
2020-03-27 02:32:43 +08:00
{
for (int i = 0; i < amount; i++)
{
2020-03-28 19:10:20 +08:00
carousel.SelectNext(forwards ? 1 : -1);
2020-03-27 02:32:43 +08:00
}
});
2020-03-29 23:05:07 +08:00
waitForSelection(forwards ? amount + 1 : total_set_count - amount);
2020-03-27 02:32:43 +08:00
}
}
[Test]
2020-03-29 23:05:07 +08:00
public void TestTraversalBeyondVisibleDifficulties()
{
var sets = new List<BeatmapSetInfo>();
2020-03-29 23:55:47 +08:00
const int total_set_count = 20;
AddStep("Populuate beatmap sets", () =>
{
sets.Clear();
for (int i = 0; i < total_set_count; i++)
sets.Add(TestResources.CreateTestBeatmapSetInfo(3));
});
2020-03-29 23:05:07 +08:00
loadBeatmaps(sets);
// Selects next set once, difficulty index doesn't change
selectNextAndAssert(3, true, 2, 1);
2020-03-29 22:46:28 +08:00
// Selects next set 16 times (50 \ 3 == 16), difficulty index changes twice (50 % 3 == 2)
selectNextAndAssert(50, true, 17, 3);
2020-03-29 22:46:28 +08:00
// Travels around the carousel thrice (200 \ 60 == 3)
// continues to select 20 times (200 \ 60 == 20)
// selects next set 6 times (20 \ 3 == 6)
// difficulty index changes twice (20 % 3 == 2)
selectNextAndAssert(200, true, 7, 3);
// All same but in reverse
selectNextAndAssert(3, false, 19, 3);
selectNextAndAssert(50, false, 4, 1);
selectNextAndAssert(200, false, 14, 1);
2020-03-29 23:05:07 +08:00
void selectNextAndAssert(int amount, bool forwards, int expectedSet, int expectedDiff)
{
// Select very first or very last difficulty
setSelected(forwards ? 1 : 20, forwards ? 1 : 3);
AddStep($"{(forwards ? "Next" : "Previous")} difficulty {amount} times", () =>
{
for (int i = 0; i < amount; i++)
carousel.SelectNext(forwards ? 1 : -1, false);
});
waitForSelection(expectedSet, expectedDiff);
}
}
2018-04-13 17:19:50 +08:00
/// <summary>
/// Test filtering
/// </summary>
[Test]
public void TestFiltering()
2018-04-13 17:19:50 +08:00
{
loadBeatmaps();
2018-04-13 17:19:50 +08:00
// basic filtering
setSelected(1, 1);
AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = carousel.BeatmapSets.ElementAt(2).Metadata.Title }, false));
2018-04-13 17:19:50 +08:00
checkVisibleItemCount(diff: false, count: 1);
checkVisibleItemCount(diff: true, count: 3);
2019-09-29 12:23:18 +08:00
waitForSelection(3, 1);
2018-04-13 17:19:50 +08:00
advanceSelection(diff: true, count: 4);
2019-09-29 12:23:18 +08:00
waitForSelection(3, 2);
2018-04-13 17:19:50 +08:00
AddStep("Un-filter (debounce)", () => carousel.Filter(new FilterCriteria()));
2019-03-19 16:24:26 +08:00
AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask);
2018-04-13 17:19:50 +08:00
checkVisibleItemCount(diff: false, count: set_count);
checkVisibleItemCount(diff: true, count: 3);
// test filtering some difficulties (and keeping current beatmap set selected).
setSelected(1, 2);
AddStep("Filter some difficulties", () => carousel.Filter(new FilterCriteria { SearchText = "Normal" }, false));
2019-09-29 12:23:18 +08:00
waitForSelection(1, 1);
2018-04-13 17:19:50 +08:00
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
2019-09-29 12:23:18 +08:00
waitForSelection(1, 1);
2018-04-13 17:19:50 +08:00
AddStep("Filter all", () => carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false));
checkVisibleItemCount(false, 0);
checkVisibleItemCount(true, 0);
AddAssert("Selection is null", () => currentSelection == null);
advanceSelection(true);
AddAssert("Selection is null", () => currentSelection == null);
advanceSelection(false);
AddAssert("Selection is null", () => currentSelection == null);
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
AddAssert("Selection is non-null", () => currentSelection != null);
setSelected(1, 3);
}
[Test]
public void TestFilterRange()
{
string searchText = null;
loadBeatmaps();
// buffer the selection
setSelected(3, 2);
AddStep("get search text", () => searchText = carousel.SelectedBeatmapSet!.Metadata.Title);
setSelected(1, 3);
AddStep("Apply a range filter", () => carousel.Filter(new FilterCriteria
{
SearchText = searchText,
StarDifficulty = new FilterCriteria.OptionalRange<double>
{
Min = 2,
Max = 5.5,
IsLowerInclusive = true
}
}, false));
// should reselect the buffered selection.
waitForSelection(3, 2);
2018-04-13 17:19:50 +08:00
}
/// <summary>
/// Test random non-repeating algorithm
/// </summary>
[Test]
public void TestRandom()
2018-04-13 17:19:50 +08:00
{
loadBeatmaps();
2018-04-13 17:19:50 +08:00
setSelected(1, 1);
nextRandom();
ensureRandomDidntRepeat();
nextRandom();
ensureRandomDidntRepeat();
nextRandom();
ensureRandomDidntRepeat();
prevRandom();
ensureRandomFetchSuccess();
prevRandom();
ensureRandomFetchSuccess();
nextRandom();
ensureRandomDidntRepeat();
nextRandom();
ensureRandomDidntRepeat();
nextRandom();
AddAssert("ensure repeat", () => selectedSets.Contains(carousel.SelectedBeatmapSet));
AddStep("Add set with 100 difficulties", () => carousel.UpdateBeatmapSet(TestResources.CreateTestBeatmapSetInfo(100, rulesets.AvailableRulesets.ToArray())));
2018-04-13 17:19:50 +08:00
AddStep("Filter Extra", () => carousel.Filter(new FilterCriteria { SearchText = "Extra 10" }, false));
checkInvisibleDifficultiesUnselectable();
checkInvisibleDifficultiesUnselectable();
checkInvisibleDifficultiesUnselectable();
checkInvisibleDifficultiesUnselectable();
checkInvisibleDifficultiesUnselectable();
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
}
/// <summary>
/// Test adding and removing beatmap sets
/// </summary>
[Test]
public void TestAddRemove()
2018-04-13 17:19:50 +08:00
{
loadBeatmaps();
var firstAdded = TestResources.CreateTestBeatmapSetInfo();
var secondAdded = TestResources.CreateTestBeatmapSetInfo();
AddStep("Add new set", () => carousel.UpdateBeatmapSet(firstAdded));
AddStep("Add new set", () => carousel.UpdateBeatmapSet(secondAdded));
2018-04-13 17:19:50 +08:00
checkVisibleItemCount(false, set_count + 2);
AddStep("Remove set", () => carousel.RemoveBeatmapSet(firstAdded));
2018-04-13 17:19:50 +08:00
checkVisibleItemCount(false, set_count + 1);
setSelected(set_count + 1, 1);
AddStep("Remove set", () => carousel.RemoveBeatmapSet(secondAdded));
2018-04-13 17:19:50 +08:00
checkVisibleItemCount(false, set_count);
2019-09-29 12:23:18 +08:00
waitForSelection(set_count);
2018-04-13 17:19:50 +08:00
}
[Test]
public void TestSelectionEnteringFromEmptyRuleset()
{
var sets = new List<BeatmapSetInfo>();
AddStep("Create beatmaps for taiko only", () =>
{
sets.Clear();
var rulesetBeatmapSet = TestResources.CreateTestBeatmapSetInfo(1);
var taikoRuleset = rulesets.AvailableRulesets.ElementAt(1);
rulesetBeatmapSet.Beatmaps.ForEach(b => b.Ruleset = taikoRuleset);
sets.Add(rulesetBeatmapSet);
});
loadBeatmaps(sets, () => new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) });
AddStep("Set non-empty mode filter", () =>
carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1) }, false));
AddAssert("Something is selected", () => carousel.SelectedBeatmapInfo != null);
}
[Test]
public void TestSortingDateSubmitted()
{
var sets = new List<BeatmapSetInfo>();
const string zzz_string = "zzzzz";
AddStep("Populuate beatmap sets", () =>
{
sets.Clear();
for (int i = 0; i < 20; i++)
{
var set = TestResources.CreateTestBeatmapSetInfo(5);
if (i >= 2 && i < 10)
set.DateSubmitted = DateTimeOffset.Now.AddMinutes(i);
if (i < 5)
set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_string);
sets.Add(set);
}
});
loadBeatmaps(sets);
AddStep("Sort by date submitted", () => carousel.Filter(new FilterCriteria { Sort = SortMode.DateSubmitted }, false));
checkVisibleItemCount(diff: false, count: 8);
checkVisibleItemCount(diff: true, count: 5);
AddStep("Sort by date submitted and string", () => carousel.Filter(new FilterCriteria
{
Sort = SortMode.DateSubmitted,
SearchText = zzz_string
}, false));
checkVisibleItemCount(diff: false, count: 3);
checkVisibleItemCount(diff: true, count: 5);
}
[Test]
public void TestSorting()
2018-04-13 17:19:50 +08:00
{
var sets = new List<BeatmapSetInfo>();
const string zzz_string = "zzzzz";
AddStep("Populuate beatmap sets", () =>
{
sets.Clear();
for (int i = 0; i < 20; i++)
{
var set = TestResources.CreateTestBeatmapSetInfo();
if (i == 4)
set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_string);
if (i == 16)
set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_string);
sets.Add(set);
}
});
loadBeatmaps(sets);
2018-04-13 17:19:50 +08:00
AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false));
AddAssert($"Check {zzz_string} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Author.Username == zzz_string);
2018-04-13 17:19:50 +08:00
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
AddAssert($"Check {zzz_string} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_string);
2018-04-13 17:19:50 +08:00
}
2022-08-01 23:43:01 +08:00
/// <summary>
/// Ensures stability is maintained on different sort modes for items with equal properties.
/// </summary>
[Test]
public void TestSortingStability()
{
var sets = new List<BeatmapSetInfo>();
int idOffset = 0;
AddStep("Populuate beatmap sets", () =>
{
sets.Clear();
for (int i = 0; i < 10; i++)
{
var set = TestResources.CreateTestBeatmapSetInfo();
// only need to set the first as they are a shared reference.
var beatmap = set.Beatmaps.First();
beatmap.Metadata.Artist = $"artist {i / 2}";
beatmap.Metadata.Title = $"title {9 - i}";
sets.Add(set);
}
idOffset = sets.First().OnlineID;
});
loadBeatmaps(sets);
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b));
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
AddAssert("Items are in reverse order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + sets.Count - index - 1).All(b => b));
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
AddAssert("Items reset to original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b));
}
2022-08-01 23:43:01 +08:00
/// <summary>
/// Ensures stability is maintained on different sort modes while a new item is added to the carousel.
/// </summary>
[Test]
public void TestSortingStabilityWithNewItems()
{
List<BeatmapSetInfo> sets = new List<BeatmapSetInfo>();
int idOffset = 0;
2022-08-01 23:43:01 +08:00
AddStep("Populuate beatmap sets", () =>
2022-08-01 23:43:01 +08:00
{
sets.Clear();
2022-08-01 23:43:01 +08:00
for (int i = 0; i < 3; i++)
{
var set = TestResources.CreateTestBeatmapSetInfo(3);
2022-08-01 23:43:01 +08:00
// only need to set the first as they are a shared reference.
var beatmap = set.Beatmaps.First();
2022-08-01 23:43:01 +08:00
beatmap.Metadata.Artist = "same artist";
beatmap.Metadata.Title = "same title";
2022-08-01 23:43:01 +08:00
sets.Add(set);
}
idOffset = sets.First().OnlineID;
});
2022-08-01 23:43:01 +08:00
loadBeatmaps(sets);
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
assertOriginalOrderMaintained();
2022-08-01 23:43:01 +08:00
AddStep("Add new item", () =>
{
var set = TestResources.CreateTestBeatmapSetInfo();
// only need to set the first as they are a shared reference.
var beatmap = set.Beatmaps.First();
beatmap.Metadata.Artist = "same artist";
beatmap.Metadata.Title = "same title";
carousel.UpdateBeatmapSet(set);
});
assertOriginalOrderMaintained();
2022-08-01 23:43:01 +08:00
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
assertOriginalOrderMaintained();
void assertOriginalOrderMaintained()
{
AddAssert("Items remain in original order",
() => carousel.BeatmapSets.Select(s => s.OnlineID), () => Is.EqualTo(carousel.BeatmapSets.Select((set, index) => idOffset + index)));
}
2022-08-01 23:43:01 +08:00
}
2019-10-07 14:13:58 +08:00
[Test]
public void TestSortingWithFiltered()
{
List<BeatmapSetInfo> sets = new List<BeatmapSetInfo>();
AddStep("Populuate beatmap sets", () =>
2019-10-07 14:13:58 +08:00
{
sets.Clear();
for (int i = 0; i < 3; i++)
{
var set = TestResources.CreateTestBeatmapSetInfo(3);
set.Beatmaps[0].StarRating = 3 - i;
set.Beatmaps[2].StarRating = 6 + i;
sets.Add(set);
}
});
2019-10-07 14:13:58 +08:00
loadBeatmaps(sets);
AddStep("Filter to normal", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Normal" }, false));
AddAssert("Check first set at end", () => carousel.BeatmapSets.First().Equals(sets.Last()));
AddAssert("Check last set at start", () => carousel.BeatmapSets.Last().Equals(sets.First()));
2019-10-07 14:13:58 +08:00
AddStep("Filter to insane", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Difficulty, SearchText = "Insane" }, false));
AddAssert("Check first set at start", () => carousel.BeatmapSets.First().Equals(sets.First()));
AddAssert("Check last set at end", () => carousel.BeatmapSets.Last().Equals(sets.Last()));
2019-10-07 14:13:58 +08:00
}
[Test]
public void TestRemoveAll()
2018-04-13 17:19:50 +08:00
{
loadBeatmaps();
2018-04-13 17:19:50 +08:00
setSelected(2, 1);
AddAssert("Selection is non-null", () => currentSelection != null);
AddStep("Remove selected", () => carousel.RemoveBeatmapSet(carousel.SelectedBeatmapSet!));
2019-09-29 12:23:18 +08:00
waitForSelection(2);
2018-04-13 17:19:50 +08:00
AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First()));
AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First()));
2019-09-29 12:23:18 +08:00
waitForSelection(1);
2018-04-13 17:19:50 +08:00
2019-03-19 16:24:26 +08:00
AddUntilStep("Remove all", () =>
2018-04-13 17:19:50 +08:00
{
if (!carousel.BeatmapSets.Any()) return true;
carousel.RemoveBeatmapSet(carousel.BeatmapSets.Last());
return false;
2019-03-19 16:24:26 +08:00
});
2018-04-13 17:19:50 +08:00
checkNoSelection();
}
[Test]
public void TestEmptyTraversal()
2018-04-13 17:19:50 +08:00
{
loadBeatmaps(new List<BeatmapSetInfo>());
2018-04-13 17:19:50 +08:00
advanceSelection(direction: 1, diff: false);
checkNoSelection();
advanceSelection(direction: 1, diff: true);
checkNoSelection();
advanceSelection(direction: -1, diff: false);
checkNoSelection();
advanceSelection(direction: -1, diff: true);
checkNoSelection();
}
[Test]
public void TestHiding()
2018-04-13 17:19:50 +08:00
{
2019-11-15 10:46:32 +08:00
BeatmapSetInfo hidingSet = null;
List<BeatmapSetInfo> hiddenList = new List<BeatmapSetInfo>();
2019-11-15 10:46:32 +08:00
AddStep("create hidden set", () =>
{
hidingSet = TestResources.CreateTestBeatmapSetInfo(3);
2019-11-15 10:46:32 +08:00
hidingSet.Beatmaps[1].Hidden = true;
hiddenList.Clear();
hiddenList.Add(hidingSet);
});
loadBeatmaps(hiddenList);
2018-04-13 17:19:50 +08:00
setSelected(1, 1);
checkVisibleItemCount(true, 2);
advanceSelection(true);
2019-09-29 12:23:18 +08:00
waitForSelection(1, 3);
2018-04-13 17:19:50 +08:00
setHidden(3);
2019-09-29 12:23:18 +08:00
waitForSelection(1, 1);
2018-04-13 17:19:50 +08:00
setHidden(2, false);
advanceSelection(true);
2019-09-29 12:23:18 +08:00
waitForSelection(1, 2);
2018-04-13 17:19:50 +08:00
setHidden(1);
2019-09-29 12:23:18 +08:00
waitForSelection(1, 2);
2018-04-13 17:19:50 +08:00
setHidden(2);
checkNoSelection();
void setHidden(int diff, bool hidden = true)
{
AddStep((hidden ? "" : "un") + $"hide diff {diff}", () =>
{
hidingSet.Beatmaps[diff - 1].Hidden = hidden;
carousel.UpdateBeatmapSet(hidingSet);
});
}
}
[Test]
public void TestSelectingFilteredRuleset()
2018-04-13 17:19:50 +08:00
{
2019-11-15 10:46:32 +08:00
BeatmapSetInfo testMixed = null;
createCarousel(new List<BeatmapSetInfo>());
2019-11-13 18:09:03 +08:00
2018-04-13 17:19:50 +08:00
AddStep("add mixed ruleset beatmapset", () =>
{
testMixed = TestResources.CreateTestBeatmapSetInfo(3);
2019-11-15 10:46:32 +08:00
2018-04-13 17:19:50 +08:00
for (int i = 0; i <= 2; i++)
{
testMixed.Beatmaps[i].Ruleset = rulesets.AvailableRulesets.ElementAt(i);
}
carousel.UpdateBeatmapSet(testMixed);
});
AddStep("filter to ruleset 0", () =>
carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false));
AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false));
AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 0);
2018-04-13 17:19:50 +08:00
AddStep("remove mixed set", () =>
{
carousel.RemoveBeatmapSet(testMixed);
testMixed = null;
});
2020-03-20 14:02:13 +08:00
BeatmapSetInfo testSingle = null;
AddStep("add single ruleset beatmapset", () =>
2018-04-13 17:19:50 +08:00
{
testSingle = TestResources.CreateTestBeatmapSetInfo(3);
2020-03-20 14:02:13 +08:00
testSingle.Beatmaps.ForEach(b =>
{
b.Ruleset = rulesets.AvailableRulesets.ElementAt(1);
});
carousel.UpdateBeatmapSet(testSingle);
2018-04-13 17:19:50 +08:00
});
AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testSingle.Beatmaps[0], false));
checkNoSelection();
AddStep("remove single ruleset set", () => carousel.RemoveBeatmapSet(testSingle));
}
[Test]
2020-03-19 17:58:22 +08:00
public void TestCarouselRemembersSelection()
2018-04-13 17:19:50 +08:00
{
List<BeatmapSetInfo> manySets = new List<BeatmapSetInfo>();
2018-04-13 17:19:50 +08:00
AddStep("Populuate beatmap sets", () =>
{
manySets.Clear();
for (int i = 1; i <= 50; i++)
manySets.Add(TestResources.CreateTestBeatmapSetInfo(3));
});
loadBeatmaps(manySets);
2018-04-13 17:19:50 +08:00
advanceSelection(direction: 1, diff: false);
2020-03-19 17:58:22 +08:00
for (int i = 0; i < 5; i++)
{
AddStep("Toggle non-matching filter", () =>
{
carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false);
});
AddStep("Restore no filter", () =>
{
carousel.Filter(new FilterCriteria(), false);
eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID);
2020-03-19 17:58:22 +08:00
});
}
// always returns to same selection as long as it's available.
AddAssert("Selection was remembered", () => eagerSelectedIDs.Count == 1);
}
[Test]
public void TestRandomFallbackOnNonMatchingPrevious()
{
List<BeatmapSetInfo> manySets = new List<BeatmapSetInfo>();
AddStep("populate maps", () =>
{
manySets.Clear();
2020-03-19 17:58:22 +08:00
for (int i = 0; i < 10; i++)
{
manySets.Add(TestResources.CreateTestBeatmapSetInfo(3, new[]
2020-03-19 17:58:22 +08:00
{
// all taiko except for first
rulesets.GetRuleset(i > 0 ? 1 : 0)
}));
2020-03-19 17:58:22 +08:00
}
});
loadBeatmaps(manySets);
for (int i = 0; i < 10; i++)
{
AddStep("Reset filter", () => carousel.Filter(new FilterCriteria(), false));
AddStep("select first beatmap", () => carousel.SelectBeatmap(manySets.First().Beatmaps.First()));
AddStep("Toggle non-matching filter", () =>
{
carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false);
});
AddAssert("selection lost", () => carousel.SelectedBeatmapInfo == null);
2020-03-19 17:58:22 +08:00
AddStep("Restore different ruleset filter", () =>
{
carousel.Filter(new FilterCriteria { Ruleset = rulesets.GetRuleset(1) }, false);
eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID);
2020-03-19 17:58:22 +08:00
});
AddAssert("selection changed", () => !carousel.SelectedBeatmapInfo!.Equals(manySets.First().Beatmaps.First()));
2020-03-19 17:58:22 +08:00
}
AddAssert("Selection was random", () => eagerSelectedIDs.Count > 2);
2018-04-13 17:19:50 +08:00
}
2020-01-24 18:40:20 +08:00
[Test]
public void TestFilteringByUserStarDifficulty()
{
BeatmapSetInfo set = null;
loadBeatmaps(new List<BeatmapSetInfo>());
AddStep("add mixed difficulty set", () =>
{
set = TestResources.CreateTestBeatmapSetInfo(1);
2020-01-24 18:40:20 +08:00
set.Beatmaps.Clear();
for (int i = 1; i <= 15; i++)
{
2022-01-10 13:52:59 +08:00
set.Beatmaps.Add(new BeatmapInfo(new OsuRuleset().RulesetInfo, new BeatmapDifficulty(), new BeatmapMetadata())
2020-01-24 18:40:20 +08:00
{
DifficultyName = $"Stars: {i}",
StarRating = i,
2020-01-24 18:40:20 +08:00
});
}
carousel.UpdateBeatmapSet(set);
});
AddStep("select added set", () => carousel.SelectBeatmap(set.Beatmaps[0], false));
AddStep("filter [5..]", () => carousel.Filter(new FilterCriteria { UserStarDifficulty = { Min = 5 } }));
AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask);
checkVisibleItemCount(true, 11);
AddStep("filter to [0..7]", () => carousel.Filter(new FilterCriteria { UserStarDifficulty = { Max = 7 } }));
AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask);
checkVisibleItemCount(true, 7);
AddStep("filter to [5..7]", () => carousel.Filter(new FilterCriteria { UserStarDifficulty = { Min = 5, Max = 7 } }));
AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask);
checkVisibleItemCount(true, 3);
AddStep("filter [2..2]", () => carousel.Filter(new FilterCriteria { UserStarDifficulty = { Min = 2, Max = 2 } }));
AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask);
checkVisibleItemCount(true, 1);
AddStep("filter to [0..]", () => carousel.Filter(new FilterCriteria { UserStarDifficulty = { Min = 0 } }));
AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask);
checkVisibleItemCount(true, 15);
}
2022-07-18 17:06:11 +08:00
private void loadBeatmaps(List<BeatmapSetInfo> beatmapSets = null, Func<FilterCriteria> initialCriteria = null, Action<BeatmapCarousel> carouselAdjust = null, int? count = null,
bool randomDifficulties = false)
2019-10-07 13:45:26 +08:00
{
bool changed = false;
2019-11-13 18:09:03 +08:00
if (beatmapSets == null)
2019-10-07 13:45:26 +08:00
{
beatmapSets = new List<BeatmapSetInfo>();
2019-10-07 13:45:26 +08:00
for (int i = 1; i <= (count ?? set_count); i++)
{
beatmapSets.Add(randomDifficulties
? TestResources.CreateTestBeatmapSetInfo()
: TestResources.CreateTestBeatmapSetInfo(3));
}
}
createCarousel(beatmapSets, c =>
{
carouselAdjust?.Invoke(c);
2019-10-07 13:45:26 +08:00
carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria());
2019-10-07 13:45:26 +08:00
carousel.BeatmapSetsChanged = () => changed = true;
carousel.BeatmapSets = beatmapSets;
});
AddUntilStep("Wait for load", () => changed);
}
private void createCarousel(List<BeatmapSetInfo> beatmapSets, Action<BeatmapCarousel> carouselAdjust = null, Container target = null)
2019-11-13 18:09:03 +08:00
{
2019-11-13 18:42:33 +08:00
AddStep("Create carousel", () =>
2019-11-13 18:09:03 +08:00
{
selectedSets.Clear();
eagerSelectedIDs.Clear();
carousel = new TestBeatmapCarousel
2019-11-13 18:09:03 +08:00
{
RelativeSizeAxes = Axes.Both,
};
carouselAdjust?.Invoke(carousel);
carousel.BeatmapSets = beatmapSets;
(target ?? this).Child = carousel;
2019-11-13 18:09:03 +08:00
});
}
2019-10-07 13:45:26 +08:00
private void ensureRandomFetchSuccess() =>
AddAssert("ensure prev random fetch worked", () => selectedSets.Peek().Equals(carousel.SelectedBeatmapSet));
2019-10-07 13:45:26 +08:00
private void waitForSelection(int set, int? diff = null) =>
AddUntilStep($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () =>
{
if (diff != null)
return carousel.SelectedBeatmapInfo?.Equals(carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First()) == true;
2019-10-07 13:45:26 +08:00
return carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Contains(carousel.SelectedBeatmapInfo);
2019-10-07 13:45:26 +08:00
});
private void setSelected(int set, int diff) =>
AddStep($"select set{set} diff{diff}", () =>
carousel.SelectBeatmap(carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First()));
private void advanceSelection(bool diff, int direction = 1, int count = 1)
{
if (count == 1)
2019-11-11 19:53:22 +08:00
{
2019-10-07 13:45:26 +08:00
AddStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () =>
carousel.SelectNext(direction, !diff));
2019-11-11 19:53:22 +08:00
}
2019-10-07 13:45:26 +08:00
else
{
AddRepeatStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () =>
carousel.SelectNext(direction, !diff), count);
}
}
private void checkVisibleItemCount(bool diff, int count)
{
// until step required as we are querying against alive items, which are loaded asynchronously inside DrawableCarouselBeatmapSet.
AddUntilStep($"{count} {(diff ? "diffs" : "sets")} visible", () =>
2019-10-07 13:45:26 +08:00
carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count);
}
2019-10-07 13:45:26 +08:00
private void checkSelectionIsCentered()
{
AddAssert("Selected panel is centered", () =>
{
return Precision.AlmostEquals(
carousel.ScreenSpaceDrawQuad.Centre,
carousel.Items
.First(i => i.Item.State.Value == CarouselItemState.Selected)
.ScreenSpaceDrawQuad.Centre, 100);
});
}
2019-10-07 13:45:26 +08:00
private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null);
private void nextRandom() =>
AddStep("select random next", () =>
{
carousel.RandomAlgorithm.Value = RandomSelectAlgorithm.RandomPermutation;
if (!selectedSets.Any() && carousel.SelectedBeatmapInfo != null)
2019-10-07 13:45:26 +08:00
selectedSets.Push(carousel.SelectedBeatmapSet);
carousel.SelectNextRandom();
selectedSets.Push(carousel.SelectedBeatmapSet);
});
private void ensureRandomDidntRepeat() =>
AddAssert("ensure no repeats", () => selectedSets.Distinct().Count() == selectedSets.Count);
private void prevRandom() => AddStep("select random last", () =>
{
carousel.SelectPreviousRandom();
selectedSets.Pop();
});
private bool selectedBeatmapVisible()
{
var currentlySelected = carousel.Items.FirstOrDefault(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected);
2019-10-07 13:45:26 +08:00
if (currentlySelected == null)
return true;
return currentlySelected.Item.Visible;
}
private void checkInvisibleDifficultiesUnselectable()
{
nextRandom();
AddAssert("Selection is visible", selectedBeatmapVisible);
}
2018-04-13 17:19:50 +08:00
private class TestBeatmapCarousel : BeatmapCarousel
{
2018-07-18 09:12:14 +08:00
public bool PendingFilterTask => PendingFilter != null;
2020-10-13 16:09:54 +08:00
public IEnumerable<DrawableCarouselItem> Items
{
get
{
foreach (var item in Scroll.Children)
2020-10-13 16:09:54 +08:00
{
yield return item;
if (item is DrawableCarouselBeatmapSet set)
{
foreach (var difficulty in set.DrawableBeatmaps)
2020-10-13 16:09:54 +08:00
yield return difficulty;
}
}
}
}
2018-04-13 17:19:50 +08:00
}
}
}