2019-01-24 16:43:03 +08:00
// 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
using System ;
using System.Collections.Generic ;
using System.Linq ;
using NUnit.Framework ;
using osu.Framework.Allocation ;
2021-11-22 13:29:46 +08:00
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 ;
2022-01-27 15:38:02 +08:00
using osu.Framework.Utils ;
2018-04-13 17:19:50 +08:00
using osu.Game.Beatmaps ;
using osu.Game.Configuration ;
using osu.Game.Rulesets ;
2021-10-29 17:22:23 +08:00
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 ;
2021-11-24 16:59:23 +08:00
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 > ( ) ;
2021-11-22 13:26:51 +08:00
private readonly HashSet < Guid > eagerSelectedIDs = new HashSet < Guid > ( ) ;
2018-04-13 17:19:50 +08:00
2021-10-02 11:44:22 +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 ;
}
2022-01-27 15:38:02 +08:00
[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 ( ) ;
}
}
2020-10-13 16:26:17 +08:00
[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 + + )
{
2021-10-02 11:44:22 +08:00
AddStep ( "store selection" , ( ) = > selection = carousel . SelectedBeatmapInfo ) ;
2020-06-25 19:01:29 +08:00
if ( isIterating )
2022-01-07 23:40:14 +08:00
AddUntilStep ( "selection changed" , ( ) = > ! carousel . SelectedBeatmapInfo ? . Equals ( selection ) = = true ) ;
2020-06-25 19:01:29 +08:00
else
2021-11-22 16:15:26 +08:00
AddUntilStep ( "selection not changed" , ( ) = > carousel . SelectedBeatmapInfo . Equals ( selection ) ) ;
2020-06-25 19:01:29 +08:00
}
}
}
2020-04-11 16:17:18 +08:00
[Test]
public void TestRecommendedSelection ( )
{
2020-07-13 16:05:29 +08:00
loadBeatmaps ( carouselAdjust : carousel = > carousel . GetRecommendedBeatmap = beatmaps = > beatmaps . LastOrDefault ( ) ) ;
2020-04-11 16:17:18 +08:00
2020-07-13 16:05:29 +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>
2019-09-25 01:42:12 +08:00
[Test]
public void TestTraversal ( )
2018-04-13 17:19:50 +08:00
{
2019-09-25 01:42:12 +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 ;
for ( int i = 0 ; i < total_set_count ; i + + )
2021-11-24 17:14:18 +08:00
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
}
}
2020-03-29 03:02:55 +08:00
[Test]
2020-03-29 23:05:07 +08:00
public void TestTraversalBeyondVisibleDifficulties ( )
2020-03-29 03:02:55 +08:00
{
var sets = new List < BeatmapSetInfo > ( ) ;
2020-03-29 23:55:47 +08:00
const int total_set_count = 20 ;
2020-03-29 03:02:55 +08:00
2020-03-29 23:05:07 +08:00
for ( int i = 0 ; i < total_set_count ; i + + )
2021-11-24 16:59:23 +08:00
sets . Add ( TestResources . CreateTestBeatmapSetInfo ( 3 ) ) ;
2020-03-29 03:02:55 +08:00
2020-03-29 23:05:07 +08:00
loadBeatmaps ( sets ) ;
2020-03-29 03:02:55 +08:00
// 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)
2020-03-29 03:02:55 +08:00
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)
2020-03-29 03:02:55 +08:00
// 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 ) ;
}
2020-03-29 03:02:55 +08:00
}
2018-04-13 17:19:50 +08:00
/// <summary>
/// Test filtering
/// </summary>
2019-09-25 01:42:12 +08:00
[Test]
public void TestFiltering ( )
2018-04-13 17:19:50 +08:00
{
2019-09-25 01:42:12 +08:00
loadBeatmaps ( ) ;
2018-04-13 17:19:50 +08:00
// basic filtering
setSelected ( 1 , 1 ) ;
2021-11-24 16:59:23 +08:00
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 ) ;
2019-09-21 05:06:20 +08:00
setSelected ( 1 , 3 ) ;
2019-10-03 16:21:14 +08:00
}
[Test]
public void TestFilterRange ( )
{
2021-11-24 16:59:23 +08:00
string searchText = null ;
2019-10-03 16:21:14 +08:00
loadBeatmaps ( ) ;
// buffer the selection
setSelected ( 3 , 2 ) ;
2021-11-24 16:59:23 +08:00
AddStep ( "get search text" , ( ) = > searchText = carousel . SelectedBeatmapSet . Metadata . Title ) ;
2019-10-03 16:21:14 +08:00
setSelected ( 1 , 3 ) ;
2019-09-21 05:06:20 +08:00
AddStep ( "Apply a range filter" , ( ) = > carousel . Filter ( new FilterCriteria
{
2021-11-24 16:59:23 +08:00
SearchText = searchText ,
2019-09-21 05:06:20 +08:00
StarDifficulty = new FilterCriteria . OptionalRange < double >
{
Min = 2 ,
Max = 5.5 ,
IsLowerInclusive = true
}
} , false ) ) ;
2019-10-03 16:21:14 +08:00
// should reselect the buffered selection.
waitForSelection ( 3 , 2 ) ;
2018-04-13 17:19:50 +08:00
}
/// <summary>
/// Test random non-repeating algorithm
/// </summary>
2019-09-25 01:42:12 +08:00
[Test]
public void TestRandom ( )
2018-04-13 17:19:50 +08:00
{
2019-09-25 01:42:12 +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 ) ) ;
2021-11-24 16:59:23 +08:00
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>
2019-09-25 01:42:12 +08:00
[Test]
public void TestAddRemove ( )
2018-04-13 17:19:50 +08:00
{
2019-09-25 01:42:12 +08:00
loadBeatmaps ( ) ;
2021-11-24 16:59:23 +08:00
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 ) ;
2021-11-24 16:59:23 +08:00
AddStep ( "Remove set" , ( ) = > carousel . RemoveBeatmapSet ( firstAdded ) ) ;
2018-04-13 17:19:50 +08:00
checkVisibleItemCount ( false , set_count + 1 ) ;
setSelected ( set_count + 1 , 1 ) ;
2021-11-24 16:59:23 +08:00
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
}
2020-03-20 14:01:26 +08:00
[Test]
public void TestSelectionEnteringFromEmptyRuleset ( )
{
var sets = new List < BeatmapSetInfo > ( ) ;
AddStep ( "Create beatmaps for taiko only" , ( ) = >
{
2020-03-23 10:12:36 +08:00
sets . Clear ( ) ;
2021-11-24 16:59:23 +08:00
var rulesetBeatmapSet = TestResources . CreateTestBeatmapSetInfo ( 1 ) ;
2020-03-20 14:01:26 +08:00
var taikoRuleset = rulesets . AvailableRulesets . ElementAt ( 1 ) ;
2021-11-22 15:10:13 +08:00
rulesetBeatmapSet . Beatmaps . ForEach ( b = > b . Ruleset = taikoRuleset ) ;
2020-03-20 14:01:26 +08:00
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 ) ) ;
2021-10-02 11:44:22 +08:00
AddAssert ( "Something is selected" , ( ) = > carousel . SelectedBeatmapInfo ! = null ) ;
2020-03-20 14:01:26 +08:00
}
2018-04-13 17:19:50 +08:00
/// <summary>
/// Test sorting
/// </summary>
2019-09-25 01:42:12 +08:00
[Test]
public void TestSorting ( )
2018-04-13 17:19:50 +08:00
{
2021-11-24 16:59:23 +08:00
var sets = new List < BeatmapSetInfo > ( ) ;
const string zzz_string = "zzzzz" ;
for ( int i = 0 ; i < 20 ; i + + )
{
2021-11-24 17:14:18 +08:00
var set = TestResources . CreateTestBeatmapSetInfo ( ) ;
2021-11-24 16:59:23 +08:00
if ( i = = 4 )
2021-11-22 13:29:46 +08:00
set . Beatmaps . ForEach ( b = > b . Metadata . Artist = zzz_string ) ;
2021-11-24 16:59:23 +08:00
if ( i = = 16 )
2022-01-18 22:30:40 +08:00
set . Beatmaps . ForEach ( b = > b . Metadata . Author . Username = zzz_string ) ;
2021-11-24 16:59:23 +08:00
sets . Add ( set ) ;
}
loadBeatmaps ( sets ) ;
2019-09-25 01:42:12 +08:00
2018-04-13 17:19:50 +08:00
AddStep ( "Sort by author" , ( ) = > carousel . Filter ( new FilterCriteria { Sort = SortMode . Author } , false ) ) ;
2021-11-24 16:59:23 +08:00
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 ) ) ;
2021-11-24 16:59:23 +08:00
AddAssert ( $"Check {zzz_string} is at bottom" , ( ) = > carousel . BeatmapSets . Last ( ) . Metadata . Artist = = zzz_string ) ;
2018-04-13 17:19:50 +08:00
}
2019-10-28 05:55:46 +08:00
[Test]
public void TestSortingStability ( )
{
var sets = new List < BeatmapSetInfo > ( ) ;
for ( int i = 0 ; i < 20 ; i + + )
{
2021-11-24 17:14:18 +08:00
var set = TestResources . CreateTestBeatmapSetInfo ( ) ;
2021-11-22 13:29:46 +08:00
// 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" ;
2019-10-28 05:55:46 +08:00
sets . Add ( set ) ;
}
2021-11-22 13:29:46 +08:00
int idOffset = sets . First ( ) . OnlineID ;
2021-11-24 16:59:23 +08:00
2019-10-28 05:55:46 +08:00
loadBeatmaps ( sets ) ;
AddStep ( "Sort by artist" , ( ) = > carousel . Filter ( new FilterCriteria { Sort = SortMode . Artist } , false ) ) ;
2021-11-24 16:59:23 +08:00
AddAssert ( "Items remain in original order" , ( ) = > carousel . BeatmapSets . Select ( ( set , index ) = > set . OnlineID = = index + idOffset ) . All ( b = > b ) ) ;
2019-10-28 05:55:46 +08:00
AddStep ( "Sort by title" , ( ) = > carousel . Filter ( new FilterCriteria { Sort = SortMode . Title } , false ) ) ;
2021-11-24 16:59:23 +08:00
AddAssert ( "Items remain in original order" , ( ) = > carousel . BeatmapSets . Select ( ( set , index ) = > set . OnlineID = = index + idOffset ) . All ( b = > b ) ) ;
2019-10-28 05:55:46 +08:00
}
2019-10-07 14:13:58 +08:00
[Test]
public void TestSortingWithFiltered ( )
{
List < BeatmapSetInfo > sets = new List < BeatmapSetInfo > ( ) ;
for ( int i = 0 ; i < 3 ; i + + )
{
2021-11-24 16:59:23 +08:00
var set = TestResources . CreateTestBeatmapSetInfo ( 3 ) ;
2021-11-11 16:18:51 +08:00
set . Beatmaps [ 0 ] . StarRating = 3 - i ;
set . Beatmaps [ 2 ] . StarRating = 6 + i ;
2019-10-07 14:13:58 +08:00
sets . Add ( set ) ;
}
loadBeatmaps ( sets ) ;
AddStep ( "Filter to normal" , ( ) = > carousel . Filter ( new FilterCriteria { Sort = SortMode . Difficulty , SearchText = "Normal" } , false ) ) ;
2021-11-22 16:15:26 +08:00
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 ) ) ;
2021-11-22 16:15:26 +08:00
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
}
2019-09-25 01:42:12 +08:00
[Test]
public void TestRemoveAll ( )
2018-04-13 17:19:50 +08:00
{
2019-09-25 01:42:12 +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 ( ) ;
}
2019-09-25 01:42:12 +08:00
[Test]
public void TestEmptyTraversal ( )
2018-04-13 17:19:50 +08:00
{
2019-09-25 01:42:12 +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 ( ) ;
}
2019-09-25 01:42:12 +08:00
[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-09-25 01:42:12 +08:00
2019-11-15 10:46:32 +08:00
AddStep ( "create hidden set" , ( ) = >
{
2021-11-24 16:59:23 +08:00
hidingSet = TestResources . CreateTestBeatmapSetInfo ( 3 ) ;
2019-11-15 10:46:32 +08:00
hidingSet . Beatmaps [ 1 ] . Hidden = true ;
hiddenList . Clear ( ) ;
hiddenList . Add ( hidingSet ) ;
} ) ;
loadBeatmaps ( hiddenList ) ;
2019-09-25 01:42:12 +08:00
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 ) ;
} ) ;
}
}
2019-09-25 01:42:12 +08:00
[Test]
public void TestSelectingFilteredRuleset ( )
2018-04-13 17:19:50 +08:00
{
2019-11-15 10:46:32 +08:00
BeatmapSetInfo testMixed = null ;
2022-02-03 15:04:05 +08:00
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" , ( ) = >
{
2021-11-25 15:27:55 +08:00
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 ) ) ;
2022-01-27 14:19:48 +08:00
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
{
2021-11-25 15:27:55 +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 ) ) ;
}
2019-09-25 01:42:12 +08:00
[Test]
2020-03-19 17:58:22 +08:00
public void TestCarouselRemembersSelection ( )
2018-04-13 17:19:50 +08:00
{
2019-09-25 01:42:12 +08:00
List < BeatmapSetInfo > manySets = new List < BeatmapSetInfo > ( ) ;
2018-04-13 17:19:50 +08:00
for ( int i = 1 ; i < = 50 ; i + + )
2021-11-25 15:27:55 +08:00
manySets . Add ( TestResources . CreateTestBeatmapSetInfo ( 3 ) ) ;
2019-09-25 01:42:12 +08:00
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 ) ;
} ) ;
}
// 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" , ( ) = >
{
for ( int i = 0 ; i < 10 ; i + + )
{
2021-11-24 16:59:23 +08:00
manySets . Add ( TestResources . CreateTestBeatmapSetInfo ( 3 , new [ ]
2020-03-19 17:58:22 +08:00
{
// all taiko except for first
2021-11-24 16:59:23 +08:00
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 ) ;
} ) ;
2021-10-02 11:44:22 +08:00
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 ) ;
2021-11-22 13:29:46 +08:00
eagerSelectedIDs . Add ( carousel . SelectedBeatmapSet . ID ) ;
2020-03-19 17:58:22 +08:00
} ) ;
2021-11-22 16:15:26 +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" , ( ) = >
{
2021-11-24 16:59:23 +08:00
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
{
2021-11-11 16:19:53 +08:00
DifficultyName = $"Stars: {i}" ,
2021-11-11 16:18:51 +08:00
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 ) ;
}
2020-10-13 16:26:17 +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
{
2020-10-12 13:23:18 +08:00
bool changed = false ;
2019-11-13 18:09:03 +08:00
2022-02-03 15:04:05 +08:00
if ( beatmapSets = = null )
2019-10-07 13:45:26 +08:00
{
2022-02-03 15:04:05 +08:00
beatmapSets = new List < BeatmapSetInfo > ( ) ;
2019-10-07 13:45:26 +08:00
2022-02-03 15:04:05 +08:00
for ( int i = 1 ; i < = ( count ? ? set_count ) ; i + + )
2020-10-12 13:23:18 +08:00
{
2022-02-03 15:04:05 +08:00
beatmapSets . Add ( randomDifficulties
? TestResources . CreateTestBeatmapSetInfo ( )
: TestResources . CreateTestBeatmapSetInfo ( 3 ) ) ;
2020-10-12 13:23:18 +08:00
}
2022-02-03 15:04:05 +08:00
}
createCarousel ( beatmapSets , c = >
{
carouselAdjust ? . Invoke ( c ) ;
2019-10-07 13:45:26 +08:00
2020-03-20 14:01: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 ) ;
}
2022-02-03 15:04:05 +08:00
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
{
2019-11-15 10:18:19 +08:00
selectedSets . Clear ( ) ;
eagerSelectedIDs . Clear ( ) ;
2020-07-13 16:05:29 +08:00
carousel = new TestBeatmapCarousel
2019-11-13 18:09:03 +08:00
{
RelativeSizeAxes = Axes . Both ,
} ;
2020-07-13 16:05:29 +08:00
carouselAdjust ? . Invoke ( carousel ) ;
2022-02-03 15:04:05 +08:00
carousel . BeatmapSets = beatmapSets ;
2020-07-13 16:05:29 +08:00
( target ? ? this ) . Child = carousel ;
2019-11-13 18:09:03 +08:00
} ) ;
}
2019-10-07 13:45:26 +08:00
private void ensureRandomFetchSuccess ( ) = >
2021-11-22 16:15:26 +08:00
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 )
2021-11-24 16:59:23 +08:00
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
2021-10-02 11:44:22 +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 ) ;
}
}
2021-05-27 15:12:49 +08:00
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 ) ;
2021-05-27 15:12:49 +08:00
}
2019-10-07 13:45:26 +08:00
2022-01-27 15:38:02 +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 ;
2021-10-02 11:44:22 +08:00
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 ( )
{
2020-10-12 13:23:18 +08:00
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-03-12 14:26:22 +08:00
2020-10-13 16:09:54 +08:00
public IEnumerable < DrawableCarouselItem > Items
{
get
{
2020-11-26 17:28:52 +08:00
foreach ( var item in Scroll . Children )
2020-10-13 16:09:54 +08:00
{
yield return item ;
if ( item is DrawableCarouselBeatmapSet set )
{
2020-10-13 18:15:56 +08:00
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
}
}
}