Merge branch 'master' into gameplay-leaderboards

This commit is contained in:
Dean Herbert
2022-09-16 15:44:58 +09:00
136 changed files with 1144 additions and 15039 deletions

View File

@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private class TestOsuRuleset : OsuRuleset
{
public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(skin);
public override ISkin CreateSkinTransformer(ISkin skin, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(skin);
private class TestOsuLegacySkinTransformer : OsuLegacySkinTransformer
{

View File

@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("hit first hitobject", () =>
{
InputManager.Click(MouseButton.Left);
return nextObjectEntry.Result.HasResult;
return nextObjectEntry.Result?.HasResult == true;
});
AddAssert("check correct object after hit", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[1]);

View File

@ -1,63 +0,0 @@
// 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.
#nullable disable
using System.Linq;
using System.Runtime.InteropServices;
using NUnit.Framework;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Models;
using osu.Game.Scoring;
using osu.Game.Skinning;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Visual.Navigation
{
public class TestEFToRealmMigration : OsuGameTestScene
{
public override void RecycleLocalStorage(bool isDisposing)
{
base.RecycleLocalStorage(isDisposing);
if (isDisposing)
return;
using (var outStream = LocalStorage.CreateFileSafely(DatabaseContextFactory.DATABASE_NAME))
using (var stream = TestResources.OpenResource(DatabaseContextFactory.DATABASE_NAME))
stream.CopyTo(outStream);
}
[SetUp]
public void SetUp()
{
if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS && RuntimeInformation.OSArchitecture == Architecture.Arm64)
Assert.Ignore("EF-to-realm migrations are not supported on M1 ARM architectures.");
}
public override void SetUpSteps()
{
// base SetUpSteps are executed before the above SetUp, therefore early-return to allow ignoring test properly.
// attempting to ignore here would yield a TargetInvocationException instead.
if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS && RuntimeInformation.OSArchitecture == Architecture.Arm64)
return;
base.SetUpSteps();
}
[Test]
public void TestMigration()
{
// Numbers are taken from the test database (see commit f03de16ee5a46deac3b5f2ca1edfba5c4c5dca7d).
AddAssert("Check beatmaps", () => Game.Dependencies.Get<RealmAccess>().Run(r => r.All<BeatmapSetInfo>().Count(s => !s.Protected) == 1));
AddAssert("Check skins", () => Game.Dependencies.Get<RealmAccess>().Run(r => r.All<SkinInfo>().Count(s => !s.Protected) == 1));
AddAssert("Check scores", () => Game.Dependencies.Get<RealmAccess>().Run(r => r.All<ScoreInfo>().Count() == 1));
// One extra file is created during realm migration / startup due to the circles intro import.
AddAssert("Check files", () => Game.Dependencies.Get<RealmAccess>().Run(r => r.All<RealmFile>().Count() == 271));
}
}
}

View File

@ -35,6 +35,8 @@ namespace osu.Game.Tests.Visual.Online
private OsuConfigManager localConfig;
private bool returnCursorOnResponse;
[BackgroundDependencyLoader]
private void load()
{
@ -61,6 +63,7 @@ namespace osu.Game.Tests.Visual.Online
searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse
{
BeatmapSets = setsForResponse,
Cursor = returnCursorOnResponse ? new Cursor() : null,
});
return true;
@ -106,7 +109,7 @@ namespace osu.Game.Tests.Visual.Online
{
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray()));
AddStep("show many results", () => fetchFor(getManyBeatmaps(100).ToArray()));
AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any(d => d.IsPresent));
@ -127,10 +130,10 @@ namespace osu.Game.Tests.Visual.Online
{
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray()));
AddStep("show many results", () => fetchFor(getManyBeatmaps(100).ToArray()));
assertAllCardsOfType<BeatmapCardNormal>(100);
AddStep("show more results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 30).ToArray()));
AddStep("show more results", () => fetchFor(getManyBeatmaps(30).ToArray()));
assertAllCardsOfType<BeatmapCardNormal>(30);
}
@ -139,7 +142,7 @@ namespace osu.Game.Tests.Visual.Online
{
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray()));
AddStep("show many results", () => fetchFor(getManyBeatmaps(100).ToArray()));
assertAllCardsOfType<BeatmapCardNormal>(100);
setCardSize(BeatmapCardSize.Extra, viaConfig);
@ -161,7 +164,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("fetch for 0 beatmaps", () => fetchFor());
placeholderShown();
AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray()));
AddStep("show many results", () => fetchFor(getManyBeatmaps(100).ToArray()));
AddUntilStep("wait for loaded", () => this.ChildrenOfType<BeatmapCard>().Count() == 100);
AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any(d => d.IsPresent));
@ -180,6 +183,32 @@ namespace osu.Game.Tests.Visual.Online
});
}
/// <summary>
/// During pagination, the first beatmap of the second page may be a duplicate of the last beatmap from the previous page.
/// This is currently the case with osu!web API due to ES relevance score's presence in the response cursor.
/// See: https://github.com/ppy/osu-web/issues/9270
/// </summary>
[Test]
public void TestDuplicatedBeatmapOnlyShowsOnce()
{
APIBeatmapSet beatmapSet = null;
AddStep("show many results", () =>
{
beatmapSet = CreateAPIBeatmapSet(Ruleset.Value);
beatmapSet.Title = "last beatmap of first page";
fetchFor(getManyBeatmaps(49).Append(beatmapSet).ToArray(), true);
});
AddUntilStep("wait for loaded", () => this.ChildrenOfType<BeatmapCard>().Count() == 50);
AddStep("set next page", () => setSearchResponse(getManyBeatmaps(49).Prepend(beatmapSet).ToArray(), false));
AddStep("scroll to end", () => overlay.ChildrenOfType<OverlayScrollContainer>().Single().ScrollToEnd());
AddUntilStep("wait for loaded", () => this.ChildrenOfType<BeatmapCard>().Count() == 99);
AddAssert("beatmap not duplicated", () => overlay.ChildrenOfType<BeatmapCard>().Count(c => c.BeatmapSet.Equals(beatmapSet)) == 1);
}
[Test]
public void TestUserWithoutSupporterUsesSupporterOnlyFiltersWithoutResults()
{
@ -336,15 +365,25 @@ namespace osu.Game.Tests.Visual.Online
private static int searchCount;
private void fetchFor(params APIBeatmapSet[] beatmaps)
private APIBeatmapSet[] getManyBeatmaps(int count) => Enumerable.Range(0, count).Select(_ => CreateAPIBeatmapSet(Ruleset.Value)).ToArray();
private void fetchFor(params APIBeatmapSet[] beatmaps) => fetchFor(beatmaps, false);
private void fetchFor(APIBeatmapSet[] beatmaps, bool hasNextPage)
{
setsForResponse.Clear();
setsForResponse.AddRange(beatmaps);
setSearchResponse(beatmaps, hasNextPage);
// trigger arbitrary change for fetching.
searchControl.Query.Value = $"search {searchCount++}";
}
private void setSearchResponse(APIBeatmapSet[] beatmaps, bool hasNextPage)
{
setsForResponse.Clear();
setsForResponse.AddRange(beatmaps);
returnCursorOnResponse = hasNextPage;
}
private void setRankAchievedFilter(ScoreRank[] ranks)
{
AddStep($"set Rank Achieved filter to [{string.Join(',', ranks)}]", () =>

View File

@ -3,9 +3,13 @@
#nullable disable
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapListing;
@ -15,12 +19,13 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneBeatmapListingSortTabControl : OsuTestScene
{
private readonly BeatmapListingSortTabControl control;
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
public TestSceneBeatmapListingSortTabControl()
{
BeatmapListingSortTabControl control;
OsuSpriteText current;
OsuSpriteText direction;
@ -45,5 +50,83 @@ namespace osu.Game.Tests.Visual.UserInterface
control.SortDirection.BindValueChanged(sortDirection => direction.Text = $"Sort direction: {sortDirection.NewValue}", true);
control.Current.BindValueChanged(criteria => current.Text = $"Criteria: {criteria.NewValue}", true);
}
[Test]
public void TestRankedSort()
{
criteriaShowsOnCategory(true, SortCriteria.Ranked, SearchCategory.Any);
criteriaShowsOnCategory(true, SortCriteria.Ranked, SearchCategory.Leaderboard);
criteriaShowsOnCategory(true, SortCriteria.Ranked, SearchCategory.Ranked);
criteriaShowsOnCategory(true, SortCriteria.Ranked, SearchCategory.Qualified);
criteriaShowsOnCategory(true, SortCriteria.Ranked, SearchCategory.Loved);
criteriaShowsOnCategory(true, SortCriteria.Ranked, SearchCategory.Favourites);
criteriaShowsOnCategory(false, SortCriteria.Ranked, SearchCategory.Pending);
criteriaShowsOnCategory(false, SortCriteria.Ranked, SearchCategory.Wip);
criteriaShowsOnCategory(false, SortCriteria.Ranked, SearchCategory.Graveyard);
criteriaShowsOnCategory(true, SortCriteria.Ranked, SearchCategory.Mine);
}
[Test]
public void TestUpdatedSort()
{
criteriaShowsOnCategory(true, SortCriteria.Updated, SearchCategory.Any);
criteriaShowsOnCategory(false, SortCriteria.Updated, SearchCategory.Leaderboard);
criteriaShowsOnCategory(false, SortCriteria.Updated, SearchCategory.Ranked);
criteriaShowsOnCategory(false, SortCriteria.Updated, SearchCategory.Qualified);
criteriaShowsOnCategory(false, SortCriteria.Updated, SearchCategory.Loved);
criteriaShowsOnCategory(true, SortCriteria.Updated, SearchCategory.Favourites);
criteriaShowsOnCategory(true, SortCriteria.Updated, SearchCategory.Pending);
criteriaShowsOnCategory(true, SortCriteria.Updated, SearchCategory.Wip);
criteriaShowsOnCategory(true, SortCriteria.Updated, SearchCategory.Graveyard);
criteriaShowsOnCategory(true, SortCriteria.Updated, SearchCategory.Mine);
}
[Test]
public void TestNominationsSort()
{
criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Any);
criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Leaderboard);
criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Ranked);
criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Qualified);
criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Loved);
criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Favourites);
criteriaShowsOnCategory(true, SortCriteria.Nominations, SearchCategory.Pending);
criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Wip);
criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Graveyard);
criteriaShowsOnCategory(false, SortCriteria.Nominations, SearchCategory.Mine);
}
[Test]
public void TestResetNoQuery()
{
resetUsesCriteriaOnCategory(SortCriteria.Ranked, SearchCategory.Any);
resetUsesCriteriaOnCategory(SortCriteria.Ranked, SearchCategory.Leaderboard);
resetUsesCriteriaOnCategory(SortCriteria.Ranked, SearchCategory.Ranked);
resetUsesCriteriaOnCategory(SortCriteria.Ranked, SearchCategory.Qualified);
resetUsesCriteriaOnCategory(SortCriteria.Ranked, SearchCategory.Loved);
resetUsesCriteriaOnCategory(SortCriteria.Ranked, SearchCategory.Favourites);
resetUsesCriteriaOnCategory(SortCriteria.Updated, SearchCategory.Pending);
resetUsesCriteriaOnCategory(SortCriteria.Updated, SearchCategory.Wip);
resetUsesCriteriaOnCategory(SortCriteria.Updated, SearchCategory.Graveyard);
resetUsesCriteriaOnCategory(SortCriteria.Updated, SearchCategory.Mine);
}
private void criteriaShowsOnCategory(bool expected, SortCriteria criteria, SearchCategory category)
{
AddAssert($"{criteria.ToString().ToLowerInvariant()} {(expected ? "shown" : "not shown")} on {category.ToString().ToLowerInvariant()}", () =>
{
control.Reset(category, false);
return control.ChildrenOfType<TabControl<SortCriteria>>().Single().Items.Contains(criteria) == expected;
});
}
private void resetUsesCriteriaOnCategory(SortCriteria criteria, SearchCategory category)
{
AddAssert($"reset uses {criteria.ToString().ToLowerInvariant()} on {category.ToString().ToLowerInvariant()}", () =>
{
control.Reset(category, false);
return control.Current.Value == criteria;
});
}
}
}

View File

@ -0,0 +1,68 @@
// 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.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.UserInterface
{
[TestFixture]
public class TestSceneModsEffectDisplay : OsuTestScene
{
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
[Resolved]
private OsuColour colours { get; set; } = null!;
[Test]
public void TestModsEffectDisplay()
{
TestDisplay testDisplay = null!;
Box background = null!;
AddStep("add display", () =>
{
Add(testDisplay = new TestDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
});
var boxes = testDisplay.ChildrenOfType<Box>();
background = boxes.First();
});
AddStep("set value to default", () => testDisplay.Current.Value = 50);
AddUntilStep("colours are correct", () => testDisplay.Container.Colour == Color4.White && background.Colour == colourProvider.Background3);
AddStep("set value to less", () => testDisplay.Current.Value = 40);
AddUntilStep("colours are correct", () => testDisplay.Container.Colour == colourProvider.Background5 && background.Colour == colours.ForModType(ModType.DifficultyReduction));
AddStep("set value to bigger", () => testDisplay.Current.Value = 60);
AddUntilStep("colours are correct", () => testDisplay.Container.Colour == colourProvider.Background5 && background.Colour == colours.ForModType(ModType.DifficultyIncrease));
}
private class TestDisplay : ModsEffectDisplay
{
public Container<Drawable> Container => Content;
protected override LocalisableString Label => "Test display";
public TestDisplay()
{
Current.Default = 50;
}
}
}
}

View File

@ -12,9 +12,9 @@ using osu.Framework.Utils;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Updater;
using osuTK;
using osuTK.Input;
using osu.Game.Updater;
namespace osu.Game.Tests.Visual.UserInterface
{
@ -48,6 +48,77 @@ namespace osu.Game.Tests.Visual.UserInterface
notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; };
});
[Test]
public void TestForwardWithFlingRight()
{
bool activated = false;
SimpleNotification notification = null!;
AddStep("post", () =>
{
activated = false;
notificationOverlay.Post(notification = new SimpleNotification
{
Text = @"Welcome to osu!. Enjoy your stay!",
Activated = () => activated = true,
});
});
AddStep("start drag", () =>
{
InputManager.MoveMouseTo(notification.ChildrenOfType<Notification>().Single());
InputManager.PressButton(MouseButton.Left);
InputManager.MoveMouseTo(notification.ChildrenOfType<Notification>().Single().ScreenSpaceDrawQuad.Centre + new Vector2(500, 0));
});
AddStep("fling away", () =>
{
InputManager.ReleaseButton(MouseButton.Left);
});
AddAssert("was not closed", () => !notification.WasClosed);
AddAssert("was not activated", () => !activated);
AddAssert("is not read", () => !notification.Read);
AddAssert("is not toast", () => !notification.IsInToastTray);
AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero));
AddAssert("unread count one", () => notificationOverlay.UnreadCount.Value == 1);
}
[Test]
public void TestDismissWithoutActivationFling()
{
bool activated = false;
SimpleNotification notification = null!;
AddStep("post", () =>
{
activated = false;
notificationOverlay.Post(notification = new SimpleNotification
{
Text = @"Welcome to osu!. Enjoy your stay!",
Activated = () => activated = true,
});
});
AddStep("start drag", () =>
{
InputManager.MoveMouseTo(notification.ChildrenOfType<Notification>().Single());
InputManager.PressButton(MouseButton.Left);
InputManager.MoveMouseTo(notification.ChildrenOfType<Notification>().Single().ScreenSpaceDrawQuad.Centre + new Vector2(-500, 0));
});
AddStep("fling away", () =>
{
InputManager.ReleaseButton(MouseButton.Left);
});
AddUntilStep("wait for closed", () => notification.WasClosed);
AddAssert("was not activated", () => !activated);
AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero));
AddAssert("unread count zero", () => notificationOverlay.UnreadCount.Value == 0);
}
[Test]
public void TestDismissWithoutActivationCloseButton()
{
@ -75,6 +146,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("wait for closed", () => notification.WasClosed);
AddAssert("was not activated", () => !activated);
AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero));
AddAssert("unread count zero", () => notificationOverlay.UnreadCount.Value == 0);
}
[Test]
@ -220,6 +292,26 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("cancel notification", () => notification.State = ProgressNotificationState.Cancelled);
}
[Test]
public void TestReadState()
{
SimpleNotification notification = null!;
AddStep(@"post", () => notificationOverlay.Post(notification = new BackgroundNotification { Text = @"Welcome to osu!. Enjoy your stay!" }));
AddUntilStep("check is toast", () => !notification.IsInToastTray);
AddAssert("light is not visible", () => notification.ChildrenOfType<Notification.NotificationLight>().Single().Alpha == 0);
AddUntilStep("wait for forward to overlay", () => !notification.IsInToastTray);
setState(Visibility.Visible);
AddAssert("state is not read", () => !notification.Read);
AddUntilStep("light is visible", () => notification.ChildrenOfType<Notification.NotificationLight>().Single().Alpha == 1);
setState(Visibility.Hidden);
setState(Visibility.Visible);
AddAssert("state is read", () => notification.Read);
AddUntilStep("light is not visible", () => notification.ChildrenOfType<Notification.NotificationLight>().Single().Alpha == 0);
}
[Test]
public void TestUpdateNotificationFlow()
{