Merge branch 'master' of https://github.com/ppy/osu into present-recommended

 Conflicts:
	osu.Game/OsuGameBase.cs
	osu.Game/Screens/Select/DifficultyRecommender.cs
	osu.Game/Screens/Select/SongSelect.cs
This commit is contained in:
Endrik Tombak
2020-11-21 13:59:59 +02:00
1470 changed files with 49711 additions and 13757 deletions

View File

@ -17,6 +17,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osuTK.Graphics;
@ -33,6 +34,8 @@ namespace osu.Game.Tests.Visual.Navigation
protected TestOsuGame Game;
protected override bool UseFreshStoragePerRun => true;
[BackgroundDependencyLoader]
private void load(GameHost host)
{
@ -100,6 +103,8 @@ namespace osu.Game.Tests.Visual.Navigation
public new BeatmapManager BeatmapManager => base.BeatmapManager;
public new ScoreManager ScoreManager => base.ScoreManager;
public new SettingsPanel Settings => base.Settings;
public new MusicController MusicController => base.MusicController;

View File

@ -1,24 +1,37 @@
// 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.Containers;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Overlays;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Navigation
{
public class TestScenePerformFromScreen : OsuGameTestScene
{
private bool actionPerformed;
public override void SetUpSteps()
{
AddStep("reset status", () => actionPerformed = false);
base.SetUpSteps();
}
[Test]
public void TestPerformAtMenu()
{
AddAssert("could perform immediately", () =>
{
bool actionPerformed = false;
Game.PerformFromScreen(_ => actionPerformed = true);
return actionPerformed;
});
AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddAssert("did perform", () => actionPerformed);
}
[Test]
@ -26,12 +39,9 @@ namespace osu.Game.Tests.Visual.Navigation
{
PushAndConfirm(() => new PlaySongSelect());
AddAssert("could perform immediately", () =>
{
bool actionPerformed = false;
Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) });
return actionPerformed;
});
AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }));
AddAssert("did perform", () => actionPerformed);
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
}
[Test]
@ -39,7 +49,6 @@ namespace osu.Game.Tests.Visual.Navigation
{
PushAndConfirm(() => new PlaySongSelect());
bool actionPerformed = false;
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddUntilStep("returned to menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
AddAssert("did perform", () => actionPerformed);
@ -51,7 +60,6 @@ namespace osu.Game.Tests.Visual.Navigation
PushAndConfirm(() => new PlaySongSelect());
PushAndConfirm(() => new PlayerLoader(() => new Player()));
bool actionPerformed = false;
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }));
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
AddAssert("did perform", () => actionPerformed);
@ -63,10 +71,121 @@ namespace osu.Game.Tests.Visual.Navigation
PushAndConfirm(() => new PlaySongSelect());
PushAndConfirm(() => new PlayerLoader(() => new Player()));
bool actionPerformed = false;
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu);
AddAssert("did perform", () => actionPerformed);
}
[Test]
public void TestOverlaysAlwaysClosed()
{
ChatOverlay chat = null;
AddUntilStep("is at menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
AddUntilStep("wait for chat load", () => (chat = Game.ChildrenOfType<ChatOverlay>().SingleOrDefault()) != null);
AddStep("show chat", () => InputManager.Key(Key.F8));
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddUntilStep("still at menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
AddAssert("did perform", () => actionPerformed);
AddAssert("chat closed", () => chat.State.Value == Visibility.Hidden);
}
[TestCase(true)]
[TestCase(false)]
public void TestPerformBlockedByDialog(bool confirmed)
{
DialogBlockingScreen blocker = null;
PushAndConfirm(() => blocker = new DialogBlockingScreen());
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddWaitStep("wait a bit", 10);
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is DialogBlockingScreen);
AddAssert("did not perform", () => !actionPerformed);
AddAssert("only one exit attempt", () => blocker.ExitAttempts == 1);
AddUntilStep("wait for dialog display", () => Game.Dependencies.Get<DialogOverlay>().IsLoaded);
if (confirmed)
{
AddStep("accept dialog", () => InputManager.Key(Key.Number1));
AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get<DialogOverlay>().CurrentDialog == null);
AddUntilStep("did perform", () => actionPerformed);
}
else
{
AddStep("cancel dialog", () => InputManager.Key(Key.Number2));
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is DialogBlockingScreen);
AddAssert("did not perform", () => !actionPerformed);
}
}
[TestCase(true)]
[TestCase(false)]
public void TestPerformBlockedByDialogNested(bool confirmSecond)
{
DialogBlockingScreen blocker = null;
DialogBlockingScreen blocker2 = null;
PushAndConfirm(() => blocker = new DialogBlockingScreen());
PushAndConfirm(() => blocker2 = new DialogBlockingScreen());
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddUntilStep("wait for dialog", () => blocker2.ExitAttempts == 1);
AddWaitStep("wait a bit", 10);
AddUntilStep("wait for dialog display", () => Game.Dependencies.Get<DialogOverlay>().IsLoaded);
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen == blocker2);
AddAssert("did not perform", () => !actionPerformed);
AddAssert("only one exit attempt", () => blocker2.ExitAttempts == 1);
AddStep("accept dialog", () => InputManager.Key(Key.Number1));
AddUntilStep("screen changed", () => Game.ScreenStack.CurrentScreen == blocker);
AddUntilStep("wait for second dialog", () => blocker.ExitAttempts == 1);
AddAssert("did not perform", () => !actionPerformed);
AddAssert("only one exit attempt", () => blocker.ExitAttempts == 1);
if (confirmSecond)
{
AddStep("accept dialog", () => InputManager.Key(Key.Number1));
AddUntilStep("did perform", () => actionPerformed);
}
else
{
AddStep("cancel dialog", () => InputManager.Key(Key.Number2));
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen == blocker);
AddAssert("did not perform", () => !actionPerformed);
}
}
public class DialogBlockingScreen : OsuScreen
{
[Resolved]
private DialogOverlay dialogOverlay { get; set; }
private int dialogDisplayCount;
public int ExitAttempts { get; private set; }
public override bool OnExiting(IScreen next)
{
ExitAttempts++;
if (dialogDisplayCount++ < 1)
{
dialogOverlay.Push(new ConfirmExitDialog(this.Exit, () => { }));
return true;
}
return base.OnExiting(next);
}
}
}
}

View File

@ -80,6 +80,13 @@ namespace osu.Game.Tests.Visual.Navigation
private void returnToMenu()
{
// if we don't pause, there's a chance the track may change at the main menu out of our control (due to reaching the end of the track).
AddStep("pause audio", () =>
{
if (Game.MusicController.IsPlaying)
Game.MusicController.TogglePause();
});
AddStep("return to menu", () => Game.ScreenStack.CurrentScreen.Exit());
AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
}

View File

@ -0,0 +1,171 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
namespace osu.Game.Tests.Visual.Navigation
{
public class TestScenePresentScore : OsuGameTestScene
{
private BeatmapSetInfo beatmap;
[SetUpSteps]
public new void SetUpSteps()
{
AddStep("import beatmap", () =>
{
var difficulty = new BeatmapDifficulty();
var metadata = new BeatmapMetadata
{
Artist = "SomeArtist",
AuthorString = "SomeAuthor",
Title = "import"
};
beatmap = Game.BeatmapManager.Import(new BeatmapSetInfo
{
Hash = Guid.NewGuid().ToString(),
OnlineBeatmapSetID = 1,
Metadata = metadata,
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
OnlineBeatmapID = 1 * 1024,
Metadata = metadata,
BaseDifficulty = difficulty,
Ruleset = new OsuRuleset().RulesetInfo
},
new BeatmapInfo
{
OnlineBeatmapID = 1 * 2048,
Metadata = metadata,
BaseDifficulty = difficulty,
Ruleset = new OsuRuleset().RulesetInfo
},
}
}).Result;
});
}
[Test]
public void TestFromMainMenu([Values] ScorePresentType type)
{
var firstImport = importScore(1);
var secondimport = importScore(3);
presentAndConfirm(firstImport, type);
returnToMenu();
presentAndConfirm(secondimport, type);
returnToMenu();
returnToMenu();
}
[Test]
public void TestFromMainMenuDifferentRuleset([Values] ScorePresentType type)
{
var firstImport = importScore(1);
var secondimport = importScore(3, new ManiaRuleset().RulesetInfo);
presentAndConfirm(firstImport, type);
returnToMenu();
presentAndConfirm(secondimport, type);
returnToMenu();
returnToMenu();
}
[Test]
public void TestFromSongSelect([Values] ScorePresentType type)
{
var firstImport = importScore(1);
presentAndConfirm(firstImport, type);
var secondimport = importScore(3);
presentAndConfirm(secondimport, type);
}
[Test]
public void TestFromSongSelectDifferentRuleset([Values] ScorePresentType type)
{
var firstImport = importScore(1);
presentAndConfirm(firstImport, type);
var secondimport = importScore(3, new ManiaRuleset().RulesetInfo);
presentAndConfirm(secondimport, type);
}
private void returnToMenu()
{
// if we don't pause, there's a chance the track may change at the main menu out of our control (due to reaching the end of the track).
AddStep("pause audio", () =>
{
if (Game.MusicController.IsPlaying)
Game.MusicController.TogglePause();
});
AddStep("return to menu", () => Game.ScreenStack.CurrentScreen.Exit());
AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
}
private Func<ScoreInfo> importScore(int i, RulesetInfo ruleset = null)
{
ScoreInfo imported = null;
AddStep($"import score {i}", () =>
{
imported = Game.ScoreManager.Import(new ScoreInfo
{
Hash = Guid.NewGuid().ToString(),
OnlineScoreID = i,
Beatmap = beatmap.Beatmaps.First(),
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
}).Result;
});
AddAssert($"import {i} succeeded", () => imported != null);
return () => imported;
}
/// <summary>
/// Some tests test waiting for a particular screen twice in a row, but expect a new instance each time.
/// There's a case where they may succeed incorrectly if we don't compare against the previous instance.
/// </summary>
private IScreen lastWaitedScreen;
private void presentAndConfirm(Func<ScoreInfo> getImport, ScorePresentType type)
{
AddStep("present score", () => Game.PresentScore(getImport(), type));
switch (type)
{
case ScorePresentType.Results:
AddUntilStep("wait for results", () => lastWaitedScreen != Game.ScreenStack.CurrentScreen && Game.ScreenStack.CurrentScreen is ResultsScreen);
AddStep("store last waited screen", () => lastWaitedScreen = Game.ScreenStack.CurrentScreen);
AddUntilStep("correct score displayed", () => ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score.ID == getImport().ID);
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Ruleset.ID);
break;
case ScorePresentType.Gameplay:
AddUntilStep("wait for player loader", () => lastWaitedScreen != Game.ScreenStack.CurrentScreen && Game.ScreenStack.CurrentScreen is ReplayPlayerLoader);
AddStep("store last waited screen", () => lastWaitedScreen = Game.ScreenStack.CurrentScreen);
AddUntilStep("correct score displayed", () => ((ReplayPlayerLoader)Game.ScreenStack.CurrentScreen).Score.ID == getImport().ID);
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Ruleset.ID);
break;
}
}
}
}

View File

@ -4,13 +4,16 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Toolbar;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Options;
using osu.Game.Tests.Beatmaps.IO;
using osuTK;
using osuTK.Input;
@ -45,7 +48,6 @@ namespace osu.Game.Tests.Visual.Navigation
Player player = null;
WorkingBeatmap beatmap() => Game.Beatmap.Value;
Track track() => beatmap().Track;
PushAndConfirm(() => new TestSongSelect());
@ -54,20 +56,34 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
if (withUserPause)
AddStep("pause", () => Game.Dependencies.Get<MusicController>().Stop());
AddStep("pause", () => Game.Dependencies.Get<MusicController>().Stop(true));
AddStep("press enter", () => pressAndRelease(Key.Enter));
AddStep("press enter", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null);
AddUntilStep("wait for fail", () => player.HasFailed);
AddUntilStep("wait for track stop", () => !track().IsRunning);
AddAssert("Ensure time before preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime);
AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying);
AddAssert("Ensure time before preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime);
pushEscape();
AddUntilStep("wait for track playing", () => track().IsRunning);
AddAssert("Ensure time wasn't reset to preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime);
AddUntilStep("wait for track playing", () => Game.MusicController.IsPlaying);
AddAssert("Ensure time wasn't reset to preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime);
}
[Test]
public void TestMenuMakesMusic()
{
TestSongSelect songSelect = null;
PushAndConfirm(() => songSelect = new TestSongSelect());
AddUntilStep("wait for no track", () => Game.MusicController.CurrentTrack.IsDummyDevice);
AddStep("return to menu", () => songSelect.Exit());
AddUntilStep("wait for track", () => !Game.MusicController.CurrentTrack.IsDummyDevice && Game.MusicController.IsPlaying);
}
[Test]
@ -106,11 +122,11 @@ namespace osu.Game.Tests.Visual.Navigation
public void TestOpenOptionsAndExitWithEscape()
{
AddUntilStep("Wait for options to load", () => Game.Settings.IsLoaded);
AddStep("Enter menu", () => pressAndRelease(Key.Enter));
AddStep("Enter menu", () => InputManager.Key(Key.Enter));
AddStep("Move mouse to options overlay", () => InputManager.MoveMouseTo(optionsButtonPosition));
AddStep("Click options overlay", () => InputManager.Click(MouseButton.Left));
AddAssert("Options overlay was opened", () => Game.Settings.State.Value == Visibility.Visible);
AddStep("Hide options overlay using escape", () => pressAndRelease(Key.Escape));
AddStep("Hide options overlay using escape", () => InputManager.Key(Key.Escape));
AddAssert("Options overlay was closed", () => Game.Settings.State.Value == Visibility.Hidden);
}
@ -122,16 +138,58 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("Wait for music controller", () => Game.MusicController.IsLoaded);
AddStep("Seek close to end", () =>
{
Game.MusicController.SeekTo(Game.Beatmap.Value.Track.Length - 1000);
Game.Beatmap.Value.Track.Completed += () => trackCompleted = true;
Game.MusicController.SeekTo(Game.MusicController.CurrentTrack.Length - 1000);
Game.MusicController.CurrentTrack.Completed += () => trackCompleted = true;
});
AddUntilStep("Track was completed", () => trackCompleted);
AddUntilStep("Track was restarted", () => Game.Beatmap.Value.Track.IsRunning);
AddUntilStep("Track was restarted", () => Game.MusicController.IsPlaying);
}
[Test]
public void TestModSelectInput()
{
TestSongSelect songSelect = null;
PushAndConfirm(() => songSelect = new TestSongSelect());
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
AddStep("Change ruleset to osu!taiko", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.Number2);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddAssert("Ruleset changed to osu!taiko", () => Game.Toolbar.ChildrenOfType<ToolbarRulesetSelector>().Single().Current.Value.ID == 1);
AddAssert("Mods overlay still visible", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
}
[Test]
public void TestBeatmapOptionsInput()
{
TestSongSelect songSelect = null;
PushAndConfirm(() => songSelect = new TestSongSelect());
AddStep("Show options overlay", () => songSelect.BeatmapOptionsOverlay.Show());
AddStep("Change ruleset to osu!taiko", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.Number2);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddAssert("Ruleset changed to osu!taiko", () => Game.Toolbar.ChildrenOfType<ToolbarRulesetSelector>().Single().Current.Value.ID == 1);
AddAssert("Options overlay still visible", () => songSelect.BeatmapOptionsOverlay.State.Value == Visibility.Visible);
}
private void pushEscape() =>
AddStep("Press escape", () => pressAndRelease(Key.Escape));
AddStep("Press escape", () => InputManager.Key(Key.Escape));
private void exitViaEscapeAndConfirm()
{
@ -146,15 +204,11 @@ namespace osu.Game.Tests.Visual.Navigation
ConfirmAtMainMenu();
}
private void pressAndRelease(Key key)
{
InputManager.PressKey(key);
InputManager.ReleaseKey(key);
}
private class TestSongSelect : PlaySongSelect
{
public ModSelectOverlay ModSelectOverlay => ModSelect;
public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions;
}
}
}