diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneSoloResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneSoloResultsScreen.cs new file mode 100644 index 0000000000..b3f01d093f --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneSoloResultsScreen.cs @@ -0,0 +1,362 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; +using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Visual.Ranking +{ + public partial class TestSceneSoloResultsScreen : ScreenTestScene + { + private ScoreManager scoreManager = null!; + private RulesetStore rulesetStore = null!; + private BeatmapManager beatmapManager = null!; + + private LeaderboardManager leaderboardManager = null!; + private BeatmapInfo importedBeatmap = null!; + + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, API)); + dependencies.Cache(leaderboardManager = new LeaderboardManager()); + + Dependencies.Cache(Realm); + + return dependencies; + } + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("load leaderboard manager", () => LoadComponent(leaderboardManager)); + + AddStep(@"set beatmap", () => + { + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + Realm.Write(r => + { + foreach (var set in r.All()) + set.Status = BeatmapOnlineStatus.Ranked; + + foreach (var b in r.All()) + b.Status = BeatmapOnlineStatus.Ranked; + }); + importedBeatmap = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); + }); + AddStep("clear all scores", () => Realm.Write(r => r.RemoveAll())); + } + + [Test] + public void TestLocalLeaderboardWithOfflineScore() + { + ScoreInfo localScore = null!; + + AddStep("set leaderboard to local", () => leaderboardManager.FetchWithCriteria(new LeaderboardCriteria(importedBeatmap, importedBeatmap.Ruleset, BeatmapLeaderboardScope.Local, null))); + AddStep("import some local scores", () => + { + for (int i = 0; i < 30; ++i) + { + var score = TestResources.CreateTestScoreInfo(importedBeatmap); + score.TotalScore = 10_000 * (30 - i); + scoreManager.Import(score); + } + + localScore = TestResources.CreateTestScoreInfo(importedBeatmap); + localScore.TotalScore = 151_000; + localScore.Position = null; + scoreManager.Import(localScore); + localScore = localScore.Detach(); + }); + + AddStep("show results", () => LoadScreen(new SoloResultsScreen(localScore))); + AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded); + AddAssert("local score is #16", () => this.ChildrenOfType().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(16)); + } + + [Test] + public void TestLocalLeaderboardWithOnlineScore() + { + ScoreInfo localScore = null!; + + AddStep("set leaderboard to local", () => leaderboardManager.FetchWithCriteria(new LeaderboardCriteria(importedBeatmap, importedBeatmap.Ruleset, BeatmapLeaderboardScope.Local, null))); + AddStep("import some local scores", () => + { + for (int i = 0; i < 30; ++i) + { + var score = TestResources.CreateTestScoreInfo(importedBeatmap); + score.OnlineID = i; + score.TotalScore = 10_000 * (30 - i); + scoreManager.Import(score); + } + + localScore = TestResources.CreateTestScoreInfo(importedBeatmap); + localScore.TotalScore = 151_000; + localScore.OnlineID = 30; + localScore.Position = null; + scoreManager.Import(localScore); + localScore = localScore.Detach(); + }); + + AddStep("show results", () => LoadScreen(new SoloResultsScreen(localScore))); + AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded); + AddAssert("local score is #16", () => this.ChildrenOfType().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(16)); + } + + [Test] + public void TestOnlineLeaderboardWithLessThan50Scores() + { + ScoreInfo localScore = null!; + + AddStep("set leaderboard to global", () => leaderboardManager.FetchWithCriteria(new LeaderboardCriteria(importedBeatmap, importedBeatmap.Ruleset, BeatmapLeaderboardScope.Global, null))); + AddStep("set up request handling", () => dummyAPI.HandleRequest = req => + { + switch (req) + { + case GetScoresRequest getScoresRequest: + var scores = new List(); + + for (int i = 0; i < 30; ++i) + { + var score = TestResources.CreateTestScoreInfo(importedBeatmap); + score.TotalScore = 10_000 * (30 - i); + score.Position = i + 1; + scores.Add(SoloScoreInfo.ForSubmission(score)); + } + + getScoresRequest.TriggerSuccess(new APIScoresCollection { Scores = scores }); + return true; + } + + return false; + }); + + AddStep("show results", () => + { + localScore = TestResources.CreateTestScoreInfo(importedBeatmap); + localScore.TotalScore = 151_000; + localScore.Position = null; + LoadScreen(new SoloResultsScreen(localScore)); + }); + AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded); + AddAssert("local score is #16", () => this.ChildrenOfType().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(16)); + } + + [Test] + public void TestOnlineLeaderboardWithLessThan50Scores_UserIsLast() + { + ScoreInfo localScore = null!; + + AddStep("set leaderboard to global", () => leaderboardManager.FetchWithCriteria(new LeaderboardCriteria(importedBeatmap, importedBeatmap.Ruleset, BeatmapLeaderboardScope.Global, null))); + AddStep("set up request handling", () => dummyAPI.HandleRequest = req => + { + switch (req) + { + case GetScoresRequest getScoresRequest: + var scores = new List(); + + for (int i = 0; i < 30; ++i) + { + var score = TestResources.CreateTestScoreInfo(importedBeatmap); + score.TotalScore = 300_000 + 10_000 * (30 - i); + score.Position = i + 1; + scores.Add(SoloScoreInfo.ForSubmission(score)); + } + + getScoresRequest.TriggerSuccess(new APIScoresCollection { Scores = scores }); + return true; + } + + return false; + }); + + AddStep("show results", () => + { + localScore = TestResources.CreateTestScoreInfo(importedBeatmap); + localScore.TotalScore = 151_000; + localScore.Position = null; + LoadScreen(new SoloResultsScreen(localScore)); + }); + AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded); + AddAssert("local score is #31", () => this.ChildrenOfType().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(31)); + } + + [Test] + public void TestOnlineLeaderboardWithMoreThan50Scores_UserOutsideOfTop50() + { + ScoreInfo localScore = null!; + + AddStep("set leaderboard to global", () => leaderboardManager.FetchWithCriteria(new LeaderboardCriteria(importedBeatmap, importedBeatmap.Ruleset, BeatmapLeaderboardScope.Global, null))); + AddStep("set up request handling", () => dummyAPI.HandleRequest = req => + { + switch (req) + { + case GetScoresRequest getScoresRequest: + var scores = new List(); + + for (int i = 0; i < 50; ++i) + { + var score = TestResources.CreateTestScoreInfo(importedBeatmap); + score.TotalScore = 500_000 + 10_000 * (50 - i); + score.Position = i + 1; + scores.Add(SoloScoreInfo.ForSubmission(score)); + } + + var userBest = TestResources.CreateTestScoreInfo(importedBeatmap); + userBest.TotalScore = 50_000; + + getScoresRequest.TriggerSuccess(new APIScoresCollection + { + Scores = scores, + UserScore = new APIScoreWithPosition + { + Score = SoloScoreInfo.ForSubmission(userBest), + Position = 133_337, + } + }); + return true; + } + + return false; + }); + + AddStep("show results", () => + { + localScore = TestResources.CreateTestScoreInfo(importedBeatmap); + localScore.TotalScore = 151_000; + localScore.Position = null; + LoadScreen(new SoloResultsScreen(localScore)); + }); + AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded); + AddAssert("local score has no position", () => this.ChildrenOfType().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.Null); + AddAssert("user best position preserved", () => this.ChildrenOfType().Any(p => p.ScorePosition.Value == 133_337)); + } + + [Test] + public void TestOnlineLeaderboardWithMoreThan50Scores_UserInTop50() + { + ScoreInfo localScore = null!; + + AddStep("set leaderboard to global", () => leaderboardManager.FetchWithCriteria(new LeaderboardCriteria(importedBeatmap, importedBeatmap.Ruleset, BeatmapLeaderboardScope.Global, null))); + AddStep("set up request handling", () => dummyAPI.HandleRequest = req => + { + switch (req) + { + case GetScoresRequest getScoresRequest: + var scores = new List(); + + for (int i = 0; i < 50; ++i) + { + var score = TestResources.CreateTestScoreInfo(importedBeatmap); + score.TotalScore = 500_000 + 10_000 * (50 - i); + score.Position = i + 1; + scores.Add(SoloScoreInfo.ForSubmission(score)); + } + + var userBest = TestResources.CreateTestScoreInfo(importedBeatmap); + userBest.TotalScore = 50_000; + + getScoresRequest.TriggerSuccess(new APIScoresCollection + { + Scores = scores, + UserScore = new APIScoreWithPosition + { + Score = SoloScoreInfo.ForSubmission(userBest), + Position = 133_337, + } + }); + return true; + } + + return false; + }); + + AddStep("show results", () => + { + localScore = TestResources.CreateTestScoreInfo(importedBeatmap); + localScore.TotalScore = 651_000; + localScore.Position = null; + LoadScreen(new SoloResultsScreen(localScore)); + }); + AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded); + AddAssert("local score is #36", () => this.ChildrenOfType().Single().GetPanelForScore(localScore).ScorePosition.Value, () => Is.EqualTo(36)); + AddAssert("user best position incremented by 1", () => this.ChildrenOfType().Any(p => p.ScorePosition.Value == 133_338)); + } + + [Test] + public void TestOnlineLeaderboardDeduplication() + { + AddStep("set leaderboard to global", () => leaderboardManager.FetchWithCriteria(new LeaderboardCriteria(importedBeatmap, importedBeatmap.Ruleset, BeatmapLeaderboardScope.Global, null))); + AddStep("set up request handling", () => dummyAPI.HandleRequest = req => + { + switch (req) + { + case GetScoresRequest getScoresRequest: + var scores = new List(); + + for (int i = 0; i < 50; ++i) + { + var score = TestResources.CreateTestScoreInfo(importedBeatmap); + score.TotalScore = 500_000 + 10_000 * (50 - i); + score.Position = i + 1; + scores.Add(SoloScoreInfo.ForSubmission(score)); + } + + var userBest = SoloScoreInfo.ForSubmission(TestResources.CreateTestScoreInfo(importedBeatmap)); + userBest.TotalScore = 151_000; + userBest.ID = 12345; + + getScoresRequest.TriggerSuccess(new APIScoresCollection + { + Scores = scores, + UserScore = new APIScoreWithPosition + { + Score = userBest, + Position = 133_337, + } + }); + return true; + } + + return false; + }); + + AddStep("show results", () => + { + var localScore = TestResources.CreateTestScoreInfo(importedBeatmap); + localScore.TotalScore = 151_000; + localScore.OnlineID = 12345; + localScore.Position = null; + LoadScreen(new SoloResultsScreen(localScore)); + }); + AddUntilStep("wait for loaded", () => ((Drawable)Stack.CurrentScreen).IsLoaded); + AddAssert("only one score with ID 12345", () => this.ChildrenOfType().Count(s => s.Score.OnlineID == 12345), () => Is.EqualTo(1)); + AddAssert("user best position preserved", () => this.ChildrenOfType().Any(p => p.ScorePosition.Value == 133_337)); + } + } +}